libcosmic/src/widget/segmented_button/widget.rs

835 lines
29 KiB
Rust
Raw Normal View History

// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use super::model::{Entity, Model, Selectable};
use crate::theme::{SegmentedButton as Style, THEME};
use crate::widget::{icon, Icon};
use crate::{Element, Renderer};
use derive_setters::Setters;
use iced::{
alignment, event, keyboard, mouse, touch, Background, Color, Command, Event, Length, Rectangle,
Size,
};
use iced_core::mouse::ScrollDelta;
2023-11-30 14:01:42 -05:00
use iced_core::text::{LineHeight, Paragraph, Renderer as TextRenderer, Shaping};
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
use iced_core::widget::{self, operation, tree};
use iced_core::{layout, renderer, widget::Tree, Clipboard, Layout, Shell, Widget};
use iced_core::{Point, Renderer as IcedRenderer, Text};
use slotmap::{Key, SecondaryMap};
use std::marker::PhantomData;
use std::time::{Duration, Instant};
/// State that is maintained by each individual widget.
#[derive(Default)]
2023-11-30 14:01:42 -05:00
pub struct LocalState {
/// The first focusable key.
first: Entity,
/// If the widget is focused or not.
focused: bool,
/// The key inside the widget that is currently focused.
focused_key: Entity,
/// The ID of the button that is being hovered. Defaults to null.
hovered: Entity,
2023-11-30 14:01:42 -05:00
/// The paragraphs for each text.
paragraphs: SecondaryMap<Entity, crate::Paragraph>,
/// Time since last tab activation from wheel movements.
wheel_timestamp: Option<Instant>,
}
impl operation::Focusable for LocalState {
fn is_focused(&self) -> bool {
self.focused
}
fn focus(&mut self) {
self.focused = true;
self.focused_key = self.first;
}
fn unfocus(&mut self) {
self.focused = false;
self.focused_key = Entity::default();
}
}
/// Isolates variant-specific behaviors from [`SegmentedButton`].
pub trait SegmentedVariant {
/// Get the appearance for this variant of the widget.
fn variant_appearance(
theme: &crate::Theme,
style: &crate::theme::SegmentedButton,
) -> super::Appearance;
/// Calculates the bounds for the given button by its position.
fn variant_button_bounds(&self, bounds: Rectangle, position: usize) -> Rectangle;
/// Calculates the layout of this variant.
2023-11-30 14:01:42 -05:00
fn variant_layout(
&self,
state: &mut LocalState,
renderer: &crate::Renderer,
limits: &layout::Limits,
) -> layout::Node;
}
/// A conjoined group of items that function together as a button.
#[derive(Setters)]
#[must_use]
pub struct SegmentedButton<'a, Variant, SelectionMode, Message>
where
Model<SelectionMode>: Selectable,
SelectionMode: Default,
{
/// The model borrowed from the application create this widget.
#[setters(skip)]
pub(super) model: &'a Model<SelectionMode>,
/// iced widget ID
pub(super) id: Option<Id>,
/// The icon used for the close button.
pub(super) close_icon: Icon,
/// Show the close icon only when item is hovered.
pub(super) show_close_icon_on_hover: bool,
/// Padding around a button.
pub(super) button_padding: [u16; 4],
/// Desired height of a button.
pub(super) button_height: u16,
/// Spacing between icon and text in button.
pub(super) button_spacing: u16,
2023-11-16 08:00:33 -07:00
/// Spacing for each indent.
pub(super) indent_spacing: u16,
/// Desired font for active tabs.
pub(super) font_active: Option<crate::font::Font>,
/// Desired font for hovered tabs.
pub(super) font_hovered: Option<crate::font::Font>,
/// Desired font for inactive tabs.
pub(super) font_inactive: Option<crate::font::Font>,
/// Size of the font.
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
pub(super) font_size: f32,
/// Desired width of the widget.
pub(super) width: Length,
/// Desired height of the widget.
pub(super) height: Length,
/// Desired spacing between items.
pub(super) spacing: u16,
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
/// LineHeight of the font.
pub(super) line_height: LineHeight,
/// Style to draw the widget in.
#[setters(into)]
pub(super) style: Style,
/// Emits the ID of the item that was activated.
2023-09-29 16:14:03 -04:00
#[setters(skip)]
pub(super) on_activate: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
#[setters(skip)]
pub(super) on_close: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
#[setters(skip)]
/// Defines the implementation of this struct
variant: PhantomData<Variant>,
}
impl<'a, Variant, SelectionMode, Message> SegmentedButton<'a, Variant, SelectionMode, Message>
where
Self: SegmentedVariant,
Model<SelectionMode>: Selectable,
SelectionMode: Default,
{
#[must_use]
pub fn new(model: &'a Model<SelectionMode>) -> Self {
Self {
model,
id: None,
close_icon: icon::from_name("window-close-symbolic").size(16).icon(),
show_close_icon_on_hover: false,
button_padding: [4, 4, 4, 4],
button_height: 32,
button_spacing: 4,
2023-11-16 08:00:33 -07:00
indent_spacing: 16,
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
font_active: None,
font_hovered: None,
font_inactive: None,
font_size: 14.0,
height: Length::Shrink,
width: Length::Fill,
spacing: 0,
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
line_height: LineHeight::default(),
style: Style::default(),
on_activate: None,
on_close: None,
variant: PhantomData,
}
}
2023-09-29 16:14:03 -04:00
pub fn on_activate<T>(mut self, on_activate: T) -> Self
where
T: Fn(Entity) -> Message + 'static,
{
self.on_activate = Some(Box::new(on_activate));
self
}
pub fn on_close<T>(mut self, on_close: T) -> Self
where
T: Fn(Entity) -> Message + 'static,
{
self.on_close = Some(Box::new(on_close));
self
}
/// Check if an item is enabled.
fn is_enabled(&self, key: Entity) -> bool {
self.model.items.get(key).map_or(false, |item| item.enabled)
}
/// Focus the previous item in the widget.
fn focus_previous(&mut self, state: &mut LocalState) -> event::Status {
let mut keys = self.model.order.iter().copied().rev();
while let Some(key) = keys.next() {
if key == state.focused_key {
for key in keys {
// Skip disabled buttons.
if !self.is_enabled(key) {
continue;
}
state.focused_key = key;
return event::Status::Captured;
}
break;
}
}
state.focused_key = Entity::default();
event::Status::Ignored
}
/// Focus the next item in the widget.
fn focus_next(&mut self, state: &mut LocalState) -> event::Status {
let mut keys = self.model.order.iter().copied();
while let Some(key) = keys.next() {
if key == state.focused_key {
for key in keys {
// Skip disabled buttons.
if !self.is_enabled(key) {
continue;
}
state.focused_key = key;
return event::Status::Captured;
}
break;
}
}
state.focused_key = Entity::default();
event::Status::Ignored
}
2023-11-30 14:01:42 -05:00
pub(super) fn max_button_dimensions(
&self,
state: &mut LocalState,
renderer: &Renderer,
2023-12-04 10:14:50 -05:00
_bounds: Size,
2023-11-30 14:01:42 -05:00
) -> (f32, f32) {
let mut width = 0.0f32;
let mut height = 0.0f32;
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
let font = renderer.default_font();
for key in self.model.order.iter().copied() {
let mut button_width = 0.0f32;
let mut button_height = 0.0f32;
// Add text to measurement if text was given.
2023-11-30 14:01:42 -05:00
if let Some((text, entry)) = self.model.text.get(key).zip(state.paragraphs.entry(key)) {
let paragraph = entry.or_insert_with(|| {
crate::Paragraph::with_text(Text {
content: text,
size: iced::Pixels(self.font_size),
bounds: Size::INFINITY,
font,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: Shaping::Advanced,
line_height: self.line_height,
})
});
let Size { width, height } = paragraph.min_bounds();
2023-08-21 11:52:19 -04:00
button_width = width;
button_height = height;
}
2023-11-16 08:00:33 -07:00
// Add indent to measurement if found.
if let Some(indent) = self.model.indent(key) {
button_width += f32::from(indent) * f32::from(self.indent_spacing);
}
// Add icon to measurement if icon was given.
if let Some(icon) = self.model.icon(key) {
button_height = button_height.max(f32::from(icon.size));
button_width += f32::from(icon.size) + f32::from(self.button_spacing);
}
// Add close button to measurement if found.
if self.model.is_closable(key) {
button_height = button_height.max(f32::from(self.close_icon.size));
button_width +=
f32::from(self.close_icon.size) + f32::from(self.button_spacing) + 8.0;
}
height = height.max(button_height);
width = width.max(button_width);
}
// Add button padding to the max size found
width += f32::from(self.button_padding[0]) + f32::from(self.button_padding[2]);
height += f32::from(self.button_padding[1]) + f32::from(self.button_padding[3]);
height = height.max(f32::from(self.button_height));
(width, height)
}
}
impl<'a, Variant, SelectionMode, Message> Widget<Message, Renderer>
for SegmentedButton<'a, Variant, SelectionMode, Message>
where
Self: SegmentedVariant,
Model<SelectionMode>: Selectable,
SelectionMode: Default,
Message: 'static + Clone,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<LocalState>()
}
fn state(&self) -> tree::State {
2023-11-30 14:01:42 -05:00
// update the paragraphs for the model
tree::State::new(LocalState {
first: self.model.order.iter().copied().next().unwrap_or_default(),
2023-11-30 14:01:42 -05:00
paragraphs: SecondaryMap::new(),
..LocalState::default()
})
}
2023-11-30 14:01:42 -05:00
fn diff(&mut self, tree: &mut Tree) {
for e in self.model.order.iter().copied() {
if let Some(text) = self.model.text.get(e) {
let text = Text {
content: text,
size: iced::Pixels(self.font_size),
bounds: Size::INFINITY,
font: self.font_active.unwrap_or(crate::font::FONT),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
shaping: Shaping::Advanced,
line_height: self.line_height,
};
if let Some(paragraph) = tree
.state
.downcast_mut::<LocalState>()
.paragraphs
.get_mut(e)
{
paragraph.update(text);
} else {
tree.state
.downcast_mut::<LocalState>()
.paragraphs
.insert(e, crate::Paragraph::with_text(text));
}
}
}
}
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
2023-11-30 14:01:42 -05:00
fn layout(
&self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.variant_layout(tree.state.downcast_mut::<LocalState>(), renderer, limits)
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
2023-06-15 11:16:32 -04:00
cursor_position: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
2023-08-21 11:52:19 -04:00
_viewport: &iced::Rectangle,
) -> event::Status {
let bounds = layout.bounds();
let state = tree.state.downcast_mut::<LocalState>();
2023-06-15 11:16:32 -04:00
if cursor_position.is_over(bounds) {
for (nth, key) in self.model.order.iter().copied().enumerate() {
let bounds = self.variant_button_bounds(bounds, nth);
2023-06-15 11:16:32 -04:00
if cursor_position.is_over(bounds) {
if self.model.items[key].enabled {
// Record that the mouse is hovering over this button.
state.hovered = key;
// If marked as closable, show a close icon.
if self.model.items[key].closable {
// Emit close message if the close button is pressed.
if let Some(on_close) = self.on_close.as_ref() {
2023-06-15 11:16:32 -04:00
if cursor_position.is_over(close_bounds(
bounds,
f32::from(self.close_icon.size),
self.button_padding,
2023-06-15 11:16:32 -04:00
)) {
if let Event::Mouse(mouse::Event::ButtonReleased(
mouse::Button::Left,
))
| Event::Touch(touch::Event::FingerLifted { .. }) = event
{
shell.publish(on_close(key));
return event::Status::Captured;
}
}
// Emit close message if the tab is middle clicked.
if let Event::Mouse(mouse::Event::ButtonReleased(
mouse::Button::Middle,
)) = event
{
shell.publish(on_close(key));
return event::Status::Captured;
}
}
}
if let Some(on_activate) = self.on_activate.as_ref() {
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. }) = event
{
shell.publish(on_activate(key));
return event::Status::Captured;
}
}
}
break;
}
}
if let Some(on_activate) = self.on_activate.as_ref() {
if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event {
let current = Instant::now();
// Permit successive scroll wheel events only after a given delay.
if state.wheel_timestamp.map_or(true, |previous| {
current.duration_since(previous) > Duration::from_millis(250)
}) {
state.wheel_timestamp = Some(current);
match delta {
ScrollDelta::Lines { y, .. } | ScrollDelta::Pixels { y, .. } => {
let mut activate_key = None;
if y < 0.0 {
let mut prev_key = Entity::null();
for key in self.model.order.iter().copied() {
if self.model.is_active(key) && !prev_key.is_null() {
activate_key = Some(prev_key);
}
if self.model.is_enabled(key) {
prev_key = key;
}
}
} else if y > 0.0 {
let mut buttons = self.model.order.iter().copied();
while let Some(key) = buttons.next() {
if self.model.is_active(key) {
for key in buttons {
if self.model.is_enabled(key) {
activate_key = Some(key);
break;
}
}
break;
}
}
}
if let Some(key) = activate_key {
shell.publish(on_activate(key));
return event::Status::Captured;
}
}
}
}
}
}
} else {
state.hovered = Entity::default();
}
if state.focused {
if let Event::Keyboard(keyboard::Event::KeyPressed {
key_code: keyboard::KeyCode::Tab,
modifiers,
..
}) = event
{
return if modifiers.shift() {
self.focus_previous(state)
} else {
self.focus_next(state)
};
}
if let Some(on_activate) = self.on_activate.as_ref() {
if let Event::Keyboard(keyboard::Event::KeyReleased {
key_code: keyboard::KeyCode::Enter,
..
}) = event
{
shell.publish(on_activate(state.focused_key));
return event::Status::Captured;
}
}
}
event::Status::Ignored
}
fn operate(
&self,
tree: &mut Tree,
_layout: Layout<'_>,
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
_renderer: &Renderer,
operation: &mut dyn iced_core::widget::Operation<
iced_core::widget::OperationOutputWrapper<Message>,
>,
) {
let state = tree.state.downcast_mut::<LocalState>();
operation.focusable(state, self.id.as_ref().map(|id| &id.0));
}
fn mouse_interaction(
&self,
_tree: &Tree,
layout: Layout<'_>,
2023-06-15 11:16:32 -04:00
cursor_position: mouse::Cursor,
_viewport: &iced::Rectangle,
_renderer: &Renderer,
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
) -> iced_core::mouse::Interaction {
let bounds = layout.bounds();
2023-06-15 11:16:32 -04:00
if cursor_position.is_over(bounds) {
for (nth, key) in self.model.order.iter().copied().enumerate() {
2023-06-15 11:16:32 -04:00
if cursor_position.is_over(self.variant_button_bounds(bounds, nth)) {
return if self.model.items[key].enabled {
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
iced_core::mouse::Interaction::Pointer
} else {
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
iced_core::mouse::Interaction::Idle
};
}
}
}
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
iced_core::mouse::Interaction::Idle
}
#[allow(clippy::too_many_lines)]
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &crate::Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &iced::Rectangle,
) {
let state = tree.state.downcast_ref::<LocalState>();
let appearance = Self::variant_appearance(theme, &self.style);
let bounds = layout.bounds();
let button_amount = self.model.items.len();
// Draw the background, if a background was defined.
if let Some(background) = appearance.background {
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: appearance.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
background,
);
}
// Draw each of the items in the widget.
for (nth, key) in self.model.order.iter().copied().enumerate() {
let mut bounds = self.variant_button_bounds(bounds, nth);
let key_is_active = self.model.is_active(key);
let key_is_hovered = state.hovered == key;
let (status_appearance, font) = if state.focused_key == key {
(appearance.focus, &self.font_active)
} else if key_is_active {
(appearance.active, &self.font_active)
} else if key_is_hovered {
(appearance.hover, &self.font_hovered)
} else {
(appearance.inactive, &self.font_inactive)
};
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
let font = font.unwrap_or_else(|| renderer.default_font());
let button_appearance = if nth == 0 {
status_appearance.first
} else if nth + 1 == button_amount {
status_appearance.last
} else {
status_appearance.middle
};
// Render the background of the button.
if status_appearance.background.is_some() {
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: button_appearance.border_radius,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
status_appearance
.background
.unwrap_or(Background::Color(Color::TRANSPARENT)),
);
}
// Draw the bottom border defined for this button.
if let Some((width, background)) = button_appearance.border_bottom {
let mut bounds = bounds;
bounds.y = bounds.y + bounds.height - width;
bounds.height = width;
let rad_0 = THEME.with(|t| t.borrow().cosmic().corner_radii.radius_0);
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: rad_0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
background,
);
}
let original_bounds = bounds;
let y = bounds.center_y();
2023-11-16 08:00:33 -07:00
// Adjust bounds by indent
if let Some(indent) = self.model.indent(key) {
let adjustment = f32::from(indent) * f32::from(self.indent_spacing);
bounds.x += adjustment;
bounds.width -= adjustment;
}
// Draw the image beside the text.
let horizontal_alignment = if let Some(icon) = self.model.icon(key) {
bounds.x += f32::from(self.button_padding[0]);
bounds.y += f32::from(self.button_padding[1]);
bounds.width -=
f32::from(self.button_padding[0]) - f32::from(self.button_padding[2]);
bounds.height -=
f32::from(self.button_padding[1]) - f32::from(self.button_padding[3]);
let width = f32::from(icon.size);
let offset = width + f32::from(self.button_spacing);
bounds.y = y - width / 2.0;
let mut layout_node = layout::Node::new(Size {
width,
height: width,
});
layout_node.move_to(Point {
x: bounds.x,
y: bounds.y,
});
Widget::<Message, Renderer>::draw(
2023-11-30 14:01:42 -05:00
Element::<Message>::from(icon.clone()).as_widget(),
&Tree::empty(),
renderer,
theme,
&renderer::Style {
icon_color: status_appearance.text_color,
text_color: status_appearance.text_color,
scale_factor: style.scale_factor,
},
Layout::new(&layout_node),
cursor,
viewport,
);
bounds.x += offset;
bounds.width -= offset;
alignment::Horizontal::Left
} else {
bounds.x = bounds.center_x();
alignment::Horizontal::Center
};
// Whether to show the close button on this tab.
let show_close_button =
(key_is_active || !self.show_close_icon_on_hover || key_is_hovered)
&& self.model.is_closable(key);
// Width of the icon used by the close button, which we will subtract from the text bounds.
let close_icon_width = if show_close_button {
f32::from(self.close_icon.size)
} else {
0.0
};
if let Some(text) = self.model.text(key) {
bounds.y = y;
// Draw the text for this segmented button or tab.
2023-11-30 14:01:42 -05:00
renderer.fill_text(
iced_core::text::Text {
content: text,
size: iced::Pixels(self.font_size),
bounds: bounds.size(),
font,
horizontal_alignment,
vertical_alignment: alignment::Vertical::Center,
shaping: Shaping::Advanced,
line_height: self.line_height,
},
bounds.position(),
status_appearance.text_color,
Rectangle {
width: bounds.width - close_icon_width - 16.0,
..original_bounds
},
2023-11-30 14:01:42 -05:00
);
}
// Draw a close button if set.
if show_close_button {
let close_button_bounds =
close_bounds(original_bounds, close_icon_width, self.button_padding);
let mut layout_node = layout::Node::new(Size {
width: close_button_bounds.width,
height: close_button_bounds.height,
});
layout_node.move_to(Point {
x: close_button_bounds.x,
y: close_button_bounds.y,
});
Widget::<Message, Renderer>::draw(
&Element::<Message>::from(self.close_icon.clone()),
&Tree::empty(),
renderer,
theme,
&renderer::Style {
icon_color: status_appearance.text_color,
text_color: status_appearance.text_color,
scale_factor: style.scale_factor,
},
Layout::new(&layout_node),
cursor,
viewport,
);
}
}
}
fn overlay<'b>(
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
&'b mut self,
_tree: &'b mut Tree,
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
_layout: iced_core::Layout<'_>,
_renderer: &Renderer,
Cosmic advanced text (#103) * wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
2023-05-30 12:03:15 -04:00
) -> Option<iced_core::overlay::Element<'b, Message, Renderer>> {
None
}
}
impl<'a, Variant, SelectionMode, Message> From<SegmentedButton<'a, Variant, SelectionMode, Message>>
for Element<'a, Message>
where
SegmentedButton<'a, Variant, SelectionMode, Message>: SegmentedVariant,
Variant: 'static,
Model<SelectionMode>: Selectable,
SelectionMode: Default,
Message: 'static + Clone,
{
fn from(mut widget: SegmentedButton<'a, Variant, SelectionMode, Message>) -> Self {
if widget.model.items.is_empty() {
widget.spacing = 0;
}
Self::new(widget)
}
}
/// A command that focuses a segmented item stored in a widget.
pub fn focus<Message: 'static>(id: Id) -> Command<Message> {
Command::widget(operation::focusable::focus(id.0))
}
/// The iced identifier of a segmented button.
2023-09-29 16:14:03 -04:00
#[derive(Debug, Clone, PartialEq)]
pub struct Id(widget::Id);
impl Id {
/// Creates a custom [`Id`].
pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
Self(widget::Id::new(id))
}
/// Creates a unique [`Id`].
///
/// This function produces a different [`Id`] every time it is called.
#[must_use]
pub fn unique() -> Self {
Self(widget::Id::unique())
}
}
impl From<Id> for widget::Id {
fn from(id: Id) -> Self {
id.0
}
}
/// Calculates the bounds of the close button within the area of an item.
fn close_bounds(area: Rectangle<f32>, icon_size: f32, button_padding: [u16; 4]) -> Rectangle<f32> {
let unpadded_height = area.height - f32::from(button_padding[1]) - f32::from(button_padding[3]);
Rectangle {
x: area.x + area.width - icon_size - 8.0,
y: area.y + (unpadded_height / 2.0) - (icon_size / 2.0),
width: icon_size,
height: icon_size,
}
}