improv(segmented_button): rework layout, drawing, and styling to fix visual flaws
This commit is contained in:
parent
bd353c6b54
commit
366a450977
9 changed files with 364 additions and 170 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
use cosmic_config::{Config, ConfigGet, ConfigSet, CosmicConfigEntry};
|
use cosmic_config::{Config, ConfigGet, CosmicConfigEntry};
|
||||||
|
|
||||||
/// ID for the ThemeMode config
|
/// ID for the ThemeMode config
|
||||||
pub const THEME_MODE_ID: &str = "com.system76.CosmicTheme.Mode";
|
pub const THEME_MODE_ID: &str = "com.system76.CosmicTheme.Mode";
|
||||||
|
|
|
||||||
|
|
@ -157,19 +157,32 @@ impl Theme {
|
||||||
pub fn accent_color(&self) -> Srgba {
|
pub fn accent_color(&self) -> Srgba {
|
||||||
self.accent.base.clone()
|
self.accent.base.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get @success_color
|
/// get @success_color
|
||||||
pub fn success_color(&self) -> Srgba {
|
pub fn success_color(&self) -> Srgba {
|
||||||
self.success.base.clone()
|
self.success.base.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get @destructive_color
|
/// get @destructive_color
|
||||||
pub fn destructive_color(&self) -> Srgba {
|
pub fn destructive_color(&self) -> Srgba {
|
||||||
self.destructive.base.clone()
|
self.destructive.base.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get @warning_color
|
/// get @warning_color
|
||||||
pub fn warning_color(&self) -> Srgba {
|
pub fn warning_color(&self) -> Srgba {
|
||||||
self.warning.base.clone()
|
self.warning.base.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// get @small_container_widget
|
||||||
|
pub fn small_container_widget(&self) -> Srgba {
|
||||||
|
self.palette.gray_2.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get @small_widget_divider
|
||||||
|
pub fn small_widget_divider(&self) -> Srgba {
|
||||||
|
self.palette.neutral_9.clone()
|
||||||
|
}
|
||||||
|
|
||||||
// Containers
|
// Containers
|
||||||
/// get @bg_color
|
/// get @bg_color
|
||||||
pub fn bg_color(&self) -> Srgba {
|
pub fn bg_color(&self) -> Srgba {
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ impl StyleSheet for Theme {
|
||||||
Appearance {
|
Appearance {
|
||||||
border_radius: cosmic.corner_radii.radius_0.into(),
|
border_radius: cosmic.corner_radii.radius_0.into(),
|
||||||
inactive: ItemStatusAppearance {
|
inactive: ItemStatusAppearance {
|
||||||
background: Some(Background::Color(neutral_5.into())),
|
background: Some(Background::Color(cosmic.small_container_widget().into())),
|
||||||
first: ItemAppearance {
|
first: ItemAppearance {
|
||||||
border_radius: Radius::from([rad_m[0], rad_0[1], rad_0[2], rad_m[3]]),
|
border_radius: Radius::from([rad_m[0], rad_0[1], rad_0[2], rad_m[3]]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
||||||
|
|
@ -27,18 +27,23 @@ pub fn nav_bar<Message>(
|
||||||
where
|
where
|
||||||
Message: Clone + 'static,
|
Message: Clone + 'static,
|
||||||
{
|
{
|
||||||
|
let theme = crate::theme::active();
|
||||||
|
let space_s = theme.cosmic().space_s();
|
||||||
|
let space_xs = theme.cosmic().space_xs();
|
||||||
|
let space_xxs = theme.cosmic().space_xxs();
|
||||||
|
|
||||||
segmented_button::vertical(model)
|
segmented_button::vertical(model)
|
||||||
.button_height(32)
|
.button_height(32)
|
||||||
.button_padding([16, 10, 16, 10])
|
.button_padding([space_s, space_xs, space_s, space_xs])
|
||||||
.button_spacing(8)
|
.button_spacing(space_xxs)
|
||||||
|
.spacing(space_xxs)
|
||||||
.on_activate(on_activate)
|
.on_activate(on_activate)
|
||||||
.spacing(8)
|
|
||||||
.style(crate::theme::SegmentedButton::ViewSwitcher)
|
.style(crate::theme::SegmentedButton::ViewSwitcher)
|
||||||
.apply(scrollable)
|
.apply(scrollable)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.apply(container)
|
.apply(container)
|
||||||
|
.padding(space_xxs)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.padding(11)
|
|
||||||
.style(theme::Container::custom(nav_bar_style))
|
.style(theme::Container::custom(nav_bar_style))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
|
|
||||||
//! Implementation details for the horizontal layout of a segmented button.
|
//! Implementation details for the horizontal layout of a segmented button.
|
||||||
|
|
||||||
use super::model::{Entity, Model, Selectable};
|
use super::model::{Model, Selectable};
|
||||||
use super::style::StyleSheet;
|
use super::style::StyleSheet;
|
||||||
use super::widget::{LocalState, SegmentedButton, SegmentedVariant};
|
use super::widget::{ItemBounds, LocalState, SegmentedButton, SegmentedVariant};
|
||||||
|
|
||||||
use iced::{Length, Rectangle, Size};
|
use iced::{Length, Rectangle, Size};
|
||||||
use iced_core::layout;
|
use iced_core::layout;
|
||||||
|
|
@ -21,7 +21,6 @@ pub struct Horizontal;
|
||||||
/// Horizontal implementation of the [`SegmentedButton`].
|
/// Horizontal implementation of the [`SegmentedButton`].
|
||||||
///
|
///
|
||||||
/// For details on the model, see the [`segmented_button`](super) module for more details.
|
/// For details on the model, see the [`segmented_button`](super) module for more details.
|
||||||
#[must_use]
|
|
||||||
pub fn horizontal<SelectionMode: Default, Message>(
|
pub fn horizontal<SelectionMode: Default, Message>(
|
||||||
model: &Model<SelectionMode>,
|
model: &Model<SelectionMode>,
|
||||||
) -> SegmentedButton<Horizontal, SelectionMode, Message>
|
) -> SegmentedButton<Horizontal, SelectionMode, Message>
|
||||||
|
|
@ -45,11 +44,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::cast_precision_loss)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
fn variant_button_bounds<'b>(
|
fn variant_bounds<'b>(
|
||||||
&'b self,
|
&'b self,
|
||||||
state: &'b LocalState,
|
state: &'b LocalState,
|
||||||
mut bounds: Rectangle,
|
mut bounds: Rectangle,
|
||||||
) -> Box<dyn Iterator<Item = (Entity, Rectangle)> + 'b> {
|
) -> Box<dyn Iterator<Item = ItemBounds> + 'b> {
|
||||||
let num = state.buttons_visible;
|
let num = state.buttons_visible;
|
||||||
let spacing = f32::from(self.spacing);
|
let spacing = f32::from(self.spacing);
|
||||||
let mut homogenous_width = 0.0;
|
let mut homogenous_width = 0.0;
|
||||||
|
|
@ -66,6 +65,8 @@ where
|
||||||
/ num as f32;
|
/ num as f32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let segmented_selection = matches!(self.style, crate::theme::SegmentedButton::Selection);
|
||||||
|
|
||||||
Box::new(
|
Box::new(
|
||||||
self.model
|
self.model
|
||||||
.order
|
.order
|
||||||
|
|
@ -74,17 +75,32 @@ where
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.skip(state.buttons_offset)
|
.skip(state.buttons_offset)
|
||||||
.take(state.buttons_visible)
|
.take(state.buttons_visible)
|
||||||
.map(move |(nth, key)| {
|
.flat_map(move |(nth, key)| {
|
||||||
let mut this_bounds = bounds;
|
let mut layout_bounds = bounds;
|
||||||
|
|
||||||
|
let layout_size = &state.internal_layout[nth].0;
|
||||||
|
|
||||||
if !state.collapsed && Length::Shrink == self.width {
|
if !state.collapsed && Length::Shrink == self.width {
|
||||||
this_bounds.width = state.internal_layout[nth].width;
|
layout_bounds.width = layout_size.width;
|
||||||
} else {
|
} else {
|
||||||
this_bounds.width = homogenous_width;
|
layout_bounds.width = homogenous_width;
|
||||||
}
|
}
|
||||||
|
|
||||||
bounds.x += this_bounds.width + spacing;
|
bounds.x += layout_bounds.width + spacing;
|
||||||
(key, this_bounds)
|
|
||||||
|
let button_bounds = ItemBounds::Button(key, layout_bounds);
|
||||||
|
let mut divider = None;
|
||||||
|
|
||||||
|
if self.dividers && segmented_selection && nth + 1 < num {
|
||||||
|
divider = Some(ItemBounds::Divider(Rectangle {
|
||||||
|
width: 1.0,
|
||||||
|
..bounds
|
||||||
|
}));
|
||||||
|
|
||||||
|
bounds.x += 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::iter::once(button_bounds).chain(divider)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -97,9 +113,9 @@ where
|
||||||
state: &mut LocalState,
|
state: &mut LocalState,
|
||||||
renderer: &crate::Renderer,
|
renderer: &crate::Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> Size {
|
||||||
|
state.internal_layout.clear();
|
||||||
let num = self.model.order.len();
|
let num = self.model.order.len();
|
||||||
let mut total_width = 0.0;
|
|
||||||
let spacing = f32::from(self.spacing);
|
let spacing = f32::from(self.spacing);
|
||||||
let limits = limits.width(self.width);
|
let limits = limits.width(self.width);
|
||||||
let mut size;
|
let mut size;
|
||||||
|
|
@ -116,31 +132,20 @@ where
|
||||||
|
|
||||||
if let Length::Shrink = self.width {
|
if let Length::Shrink = self.width {
|
||||||
// Buttons will be rendered at their smallest widths possible.
|
// Buttons will be rendered at their smallest widths possible.
|
||||||
state.internal_layout.clear();
|
let max_height = self.max_button_dimensions(state, renderer).1;
|
||||||
|
|
||||||
let font = renderer.default_font();
|
|
||||||
let mut total_height = 0.0f32;
|
|
||||||
|
|
||||||
for &button in &self.model.order {
|
|
||||||
let (mut width, height) = self.button_dimensions(state, font, button);
|
|
||||||
width = f32::from(self.minimum_button_width).max(width);
|
|
||||||
total_width += width + spacing;
|
|
||||||
total_height = total_height.max(height);
|
|
||||||
|
|
||||||
state.internal_layout.push(Size::new(width, height));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the max available width for placing buttons into.
|
// Get the max available width for placing buttons into.
|
||||||
let max_size = limits.height(Length::Fixed(total_height)).resolve(
|
let max_size = limits.height(Length::Fixed(max_height)).resolve(
|
||||||
Length::Fill,
|
Length::Fill,
|
||||||
total_height,
|
max_height,
|
||||||
Size::new(f32::MAX, total_height),
|
Size::new(f32::MAX, max_height),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut visible_width = f32::from(self.button_height) * 2.0;
|
// let mut visible_width = f32::from(self.button_height) * 2.0;
|
||||||
|
let mut visible_width = 0.0;
|
||||||
state.buttons_visible = 0;
|
state.buttons_visible = 0;
|
||||||
|
|
||||||
for button_size in &state.internal_layout {
|
for (button_size, _actual_size) in &state.internal_layout {
|
||||||
visible_width += button_size.width;
|
visible_width += button_size.width;
|
||||||
|
|
||||||
if max_size.width >= visible_width {
|
if max_size.width >= visible_width {
|
||||||
|
|
@ -152,27 +157,48 @@ where
|
||||||
visible_width += spacing;
|
visible_width += spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
visible_width -= spacing;
|
||||||
|
|
||||||
state.collapsed = num > 1 && state.buttons_visible != num;
|
state.collapsed = num > 1 && state.buttons_visible != num;
|
||||||
|
|
||||||
// If collapsed, use the maximum width available.
|
// If collapsed, use the maximum width available.
|
||||||
visible_width = if state.collapsed {
|
if state.collapsed {
|
||||||
max_size.width
|
visible_width = max_size.width;
|
||||||
} else {
|
}
|
||||||
total_width
|
|
||||||
};
|
|
||||||
|
|
||||||
size = limits
|
size = limits
|
||||||
.width(Length::Fixed(visible_width))
|
.width(Length::Fixed(visible_width))
|
||||||
.height(Length::Fixed(total_height))
|
.height(Length::Fixed(max_height))
|
||||||
.resolve(
|
.resolve(
|
||||||
visible_width,
|
visible_width,
|
||||||
total_height,
|
max_height,
|
||||||
Size::new(visible_width, total_height),
|
Size::new(visible_width, max_height),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Buttons will be rendered with equal widths.
|
// Buttons will be rendered with equal widths.
|
||||||
state.buttons_visible = self.model.items.len();
|
state.buttons_visible = self.model.items.len();
|
||||||
let (width, height) = self.max_button_dimensions(state, renderer, limits.max());
|
|
||||||
|
let mut width = 0.0f32;
|
||||||
|
let mut height = 0.0f32;
|
||||||
|
let font = renderer.default_font();
|
||||||
|
|
||||||
|
for key in self.model.order.iter().copied() {
|
||||||
|
let (button_width, button_height) = self.button_dimensions(state, font, key);
|
||||||
|
|
||||||
|
state.internal_layout.push((
|
||||||
|
Size::new(button_width, button_height),
|
||||||
|
Size::new(
|
||||||
|
button_width
|
||||||
|
- f32::from(self.button_padding[0])
|
||||||
|
- f32::from(self.button_padding[2]),
|
||||||
|
button_height,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
height = height.max(button_height);
|
||||||
|
width = width.max(button_width);
|
||||||
|
}
|
||||||
|
|
||||||
let total_width = (state.buttons_visible as f32) * (width + spacing);
|
let total_width = (state.buttons_visible as f32) * (width + spacing);
|
||||||
|
|
||||||
size = limits.height(Length::Fixed(height)).resolve(
|
size = limits.height(Length::Fixed(height)).resolve(
|
||||||
|
|
@ -201,6 +227,6 @@ where
|
||||||
state.buttons_offset = 0;
|
state.buttons_offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
layout::Node::new(size)
|
size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@
|
||||||
|
|
||||||
use super::model::{Entity, Model, Selectable};
|
use super::model::{Entity, Model, Selectable};
|
||||||
use super::style::StyleSheet;
|
use super::style::StyleSheet;
|
||||||
use super::widget::{LocalState, SegmentedButton, SegmentedVariant};
|
use super::widget::{ItemBounds, LocalState, SegmentedButton, SegmentedVariant};
|
||||||
|
|
||||||
use iced::{Length, Rectangle, Size};
|
use iced::{Alignment, Length, Rectangle, Size};
|
||||||
use iced_core::layout;
|
use iced_core::layout;
|
||||||
|
|
||||||
/// A type marker defining the vertical variant of a [`SegmentedButton`].
|
/// A type marker defining the vertical variant of a [`SegmentedButton`].
|
||||||
|
|
@ -45,11 +45,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::cast_precision_loss)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
fn variant_button_bounds<'b>(
|
fn variant_bounds<'b>(
|
||||||
&'b self,
|
&'b self,
|
||||||
state: &'b LocalState,
|
state: &'b LocalState,
|
||||||
mut bounds: Rectangle,
|
mut bounds: Rectangle,
|
||||||
) -> Box<dyn Iterator<Item = (Entity, Rectangle)> + 'b> {
|
) -> Box<dyn Iterator<Item = ItemBounds> + 'b> {
|
||||||
let spacing = f32::from(self.spacing);
|
let spacing = f32::from(self.spacing);
|
||||||
|
|
||||||
Box::new(
|
Box::new(
|
||||||
|
|
@ -58,11 +58,16 @@ where
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(move |(_nth, key)| {
|
.map(move |(nth, key)| {
|
||||||
let mut this_bounds = bounds;
|
let mut layout_bounds = bounds;
|
||||||
this_bounds.height = state.internal_layout[0].height;
|
|
||||||
bounds.y += this_bounds.height + spacing;
|
let layout_size = state.internal_layout[nth].0;
|
||||||
(key, this_bounds)
|
|
||||||
|
layout_bounds.height = layout_size.height;
|
||||||
|
|
||||||
|
bounds.y += layout_bounds.height + spacing;
|
||||||
|
|
||||||
|
ItemBounds::Button(key, layout_bounds)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -75,11 +80,16 @@ where
|
||||||
state: &mut LocalState,
|
state: &mut LocalState,
|
||||||
renderer: &crate::Renderer,
|
renderer: &crate::Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> Size {
|
||||||
state.internal_layout.clear();
|
state.internal_layout.clear();
|
||||||
let limits = limits.width(self.width);
|
let limits = limits.width(self.width);
|
||||||
let (width, mut height) = self.max_button_dimensions(state, renderer, limits.max());
|
|
||||||
state.internal_layout.push(Size::new(width, height));
|
let (width, mut height) = self.max_button_dimensions(state, renderer);
|
||||||
|
|
||||||
|
for (size, actual) in &mut state.internal_layout {
|
||||||
|
size.width = width;
|
||||||
|
actual.width = height;
|
||||||
|
}
|
||||||
|
|
||||||
let num = self.model.items.len();
|
let num = self.model.items.len();
|
||||||
let spacing = f32::from(self.spacing);
|
let spacing = f32::from(self.spacing);
|
||||||
|
|
@ -87,12 +97,11 @@ where
|
||||||
if num != 0 {
|
if num != 0 {
|
||||||
height = (num as f32 * height) + (num as f32 * spacing) - spacing;
|
height = (num as f32 * height) + (num as f32 * spacing) - spacing;
|
||||||
}
|
}
|
||||||
let size = limits.height(Length::Fixed(height)).resolve(
|
|
||||||
|
limits.height(Length::Fixed(height)).resolve(
|
||||||
self.width,
|
self.width,
|
||||||
self.height,
|
self.height,
|
||||||
Size::new(width, height),
|
Size::new(width, height),
|
||||||
);
|
)
|
||||||
|
|
||||||
layout::Node::new(size)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ use crate::widget::{icon, Icon};
|
||||||
use crate::{Element, Renderer};
|
use crate::{Element, Renderer};
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
use iced::{
|
use iced::{
|
||||||
alignment, event, keyboard, mouse, touch, Background, Color, Command, Event, Length, Rectangle,
|
alignment, event, keyboard, mouse, touch, Alignment, Background, Color, Command, Event, Length,
|
||||||
Size,
|
Padding, Rectangle, Size,
|
||||||
};
|
};
|
||||||
use iced_core::mouse::ScrollDelta;
|
use iced_core::mouse::ScrollDelta;
|
||||||
use iced_core::text::{LineHeight, Paragraph, Renderer as TextRenderer, Shaping};
|
use iced_core::text::{LineHeight, Paragraph, Renderer as TextRenderer, Shaping};
|
||||||
|
|
@ -24,6 +24,11 @@ pub fn focus<Message: 'static>(id: Id) -> Command<Message> {
|
||||||
Command::widget(operation::focusable::focus(id.0))
|
Command::widget(operation::focusable::focus(id.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) enum ItemBounds {
|
||||||
|
Button(Entity, Rectangle),
|
||||||
|
Divider(Rectangle),
|
||||||
|
}
|
||||||
|
|
||||||
/// Isolates variant-specific behaviors from [`SegmentedButton`].
|
/// Isolates variant-specific behaviors from [`SegmentedButton`].
|
||||||
pub trait SegmentedVariant {
|
pub trait SegmentedVariant {
|
||||||
/// Get the appearance for this variant of the widget.
|
/// Get the appearance for this variant of the widget.
|
||||||
|
|
@ -33,11 +38,11 @@ pub trait SegmentedVariant {
|
||||||
) -> super::Appearance;
|
) -> super::Appearance;
|
||||||
|
|
||||||
/// Calculates the bounds for visible buttons.
|
/// Calculates the bounds for visible buttons.
|
||||||
fn variant_button_bounds<'b>(
|
fn variant_bounds<'b>(
|
||||||
&'b self,
|
&'b self,
|
||||||
state: &'b LocalState,
|
state: &'b LocalState,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
) -> Box<dyn Iterator<Item = (Entity, Rectangle)> + 'b>;
|
) -> Box<dyn Iterator<Item = ItemBounds> + 'b>;
|
||||||
|
|
||||||
/// Calculates the layout of this variant.
|
/// Calculates the layout of this variant.
|
||||||
fn variant_layout(
|
fn variant_layout(
|
||||||
|
|
@ -45,7 +50,7 @@ pub trait SegmentedVariant {
|
||||||
state: &mut LocalState,
|
state: &mut LocalState,
|
||||||
renderer: &crate::Renderer,
|
renderer: &crate::Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node;
|
) -> Size;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A conjoined group of items that function together as a button.
|
/// A conjoined group of items that function together as a button.
|
||||||
|
|
@ -67,6 +72,13 @@ where
|
||||||
pub(super) scrollable_focus: bool,
|
pub(super) scrollable_focus: bool,
|
||||||
/// Show the close icon only when item is hovered.
|
/// Show the close icon only when item is hovered.
|
||||||
pub(super) show_close_icon_on_hover: bool,
|
pub(super) show_close_icon_on_hover: bool,
|
||||||
|
/// Padding of the whole widget.
|
||||||
|
#[setters(into)]
|
||||||
|
pub(super) padding: Padding,
|
||||||
|
/// Whether to place dividers between buttons.
|
||||||
|
pub(super) dividers: bool,
|
||||||
|
/// Alignment of button contents.
|
||||||
|
pub(super) button_alignment: Alignment,
|
||||||
/// Padding around a button.
|
/// Padding around a button.
|
||||||
pub(super) button_padding: [u16; 4],
|
pub(super) button_padding: [u16; 4],
|
||||||
/// Desired height of a button.
|
/// Desired height of a button.
|
||||||
|
|
@ -119,9 +131,12 @@ where
|
||||||
close_icon: icon::from_name("window-close-symbolic").size(16).icon(),
|
close_icon: icon::from_name("window-close-symbolic").size(16).icon(),
|
||||||
scrollable_focus: false,
|
scrollable_focus: false,
|
||||||
show_close_icon_on_hover: false,
|
show_close_icon_on_hover: false,
|
||||||
button_padding: [4, 4, 4, 4],
|
button_alignment: Alignment::Start,
|
||||||
|
padding: Padding::from(0.0),
|
||||||
|
dividers: false,
|
||||||
|
button_padding: [0, 0, 0, 0],
|
||||||
button_height: 32,
|
button_height: 32,
|
||||||
button_spacing: 4,
|
button_spacing: 0,
|
||||||
minimum_button_width: 150,
|
minimum_button_width: 150,
|
||||||
indent_spacing: 16,
|
indent_spacing: 16,
|
||||||
font_active: None,
|
font_active: None,
|
||||||
|
|
@ -338,14 +353,17 @@ where
|
||||||
|
|
||||||
// Add icon to measurement if icon was given.
|
// Add icon to measurement if icon was given.
|
||||||
if let Some(icon) = self.model.icon(button) {
|
if let Some(icon) = self.model.icon(button) {
|
||||||
height = height.max(f32::from(icon.size));
|
|
||||||
width += f32::from(icon.size) + f32::from(self.button_spacing);
|
width += f32::from(icon.size) + f32::from(self.button_spacing);
|
||||||
|
} else if self.model.is_active(button) {
|
||||||
|
// Add selection icon measurements when widget is a selection widget.
|
||||||
|
if let crate::theme::SegmentedButton::Selection = self.style {
|
||||||
|
width += 16.0 + f32::from(self.button_spacing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add close button to measurement if found.
|
// Add close button to measurement if found.
|
||||||
if self.model.is_closable(button) {
|
if self.model.is_closable(button) {
|
||||||
height = height.max(f32::from(self.close_icon.size));
|
width += f32::from(self.close_icon.size) + f32::from(self.button_spacing);
|
||||||
width += f32::from(self.close_icon.size) + f32::from(self.button_spacing) + 8.0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add button padding to the max size found
|
// Add button padding to the max size found
|
||||||
|
|
@ -360,7 +378,6 @@ where
|
||||||
&self,
|
&self,
|
||||||
state: &mut LocalState,
|
state: &mut LocalState,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
_bounds: Size,
|
|
||||||
) -> (f32, f32) {
|
) -> (f32, f32) {
|
||||||
let mut width = 0.0f32;
|
let mut width = 0.0f32;
|
||||||
let mut height = 0.0f32;
|
let mut height = 0.0f32;
|
||||||
|
|
@ -369,10 +386,25 @@ where
|
||||||
for key in self.model.order.iter().copied() {
|
for key in self.model.order.iter().copied() {
|
||||||
let (button_width, button_height) = self.button_dimensions(state, font, key);
|
let (button_width, button_height) = self.button_dimensions(state, font, key);
|
||||||
|
|
||||||
|
state.internal_layout.push((
|
||||||
|
Size::new(button_width, button_height),
|
||||||
|
Size::new(
|
||||||
|
button_width
|
||||||
|
- f32::from(self.button_padding[0])
|
||||||
|
- f32::from(self.button_padding[2]),
|
||||||
|
button_height,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
height = height.max(button_height);
|
height = height.max(button_height);
|
||||||
width = width.max(button_width);
|
width = width.max(button_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (size, actual) in &mut state.internal_layout {
|
||||||
|
size.height = height;
|
||||||
|
actual.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
(width, height)
|
(width, height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -436,7 +468,12 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
self.variant_layout(tree.state.downcast_mut::<LocalState>(), renderer, limits)
|
let state = tree.state.downcast_mut::<LocalState>();
|
||||||
|
let limits = limits.shrink(self.padding);
|
||||||
|
let size = self
|
||||||
|
.variant_layout(state, renderer, &limits)
|
||||||
|
.expand(self.padding);
|
||||||
|
layout::Node::new(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
|
|
@ -453,17 +490,14 @@ where
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let state = tree.state.downcast_mut::<LocalState>();
|
let state = tree.state.downcast_mut::<LocalState>();
|
||||||
|
state.hovered = Item::None;
|
||||||
|
|
||||||
if cursor_position.is_over(bounds) {
|
if cursor_position.is_over(bounds) {
|
||||||
// Check for clicks on the previous and next tab buttons, when tabs are collapsed.
|
// Check for clicks on the previous and next tab buttons, when tabs are collapsed.
|
||||||
if state.collapsed {
|
if state.collapsed {
|
||||||
// Check if the prev tab button was clicked.
|
// Check if the prev tab button was clicked.
|
||||||
if cursor_position.is_over(Rectangle {
|
if cursor_position.is_over(prev_tab_bounds(&bounds, f32::from(self.button_height)))
|
||||||
x: bounds.x,
|
&& self.prev_tab_sensitive(state)
|
||||||
y: bounds.y,
|
|
||||||
width: f32::from(self.button_height),
|
|
||||||
height: f32::from(self.button_height),
|
|
||||||
}) && self.prev_tab_sensitive(state)
|
|
||||||
{
|
{
|
||||||
state.hovered = Item::PrevButton;
|
state.hovered = Item::PrevButton;
|
||||||
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||||
|
|
@ -473,12 +507,9 @@ where
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Check if the next tab button was clicked.
|
// Check if the next tab button was clicked.
|
||||||
if cursor_position.is_over(Rectangle {
|
if cursor_position
|
||||||
x: bounds.x + bounds.width - f32::from(self.button_height),
|
.is_over(next_tab_bounds(&bounds, f32::from(self.button_height)))
|
||||||
y: bounds.y,
|
&& self.next_tab_sensitive(state)
|
||||||
width: f32::from(self.button_height),
|
|
||||||
height: f32::from(self.button_height),
|
|
||||||
}) && self.next_tab_sensitive(state)
|
|
||||||
{
|
{
|
||||||
state.hovered = Item::NextButton;
|
state.hovered = Item::NextButton;
|
||||||
|
|
||||||
|
|
@ -492,7 +523,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
for (key, bounds) in self
|
for (key, bounds) in self
|
||||||
.variant_button_bounds(state, bounds)
|
.variant_bounds(state, bounds)
|
||||||
|
.filter_map(|item| match item {
|
||||||
|
ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
{
|
{
|
||||||
if cursor_position.is_over(bounds) {
|
if cursor_position.is_over(bounds) {
|
||||||
|
|
@ -504,11 +539,9 @@ where
|
||||||
if self.model.items[key].closable {
|
if self.model.items[key].closable {
|
||||||
// Emit close message if the close button is pressed.
|
// Emit close message if the close button is pressed.
|
||||||
if let Some(on_close) = self.on_close.as_ref() {
|
if let Some(on_close) = self.on_close.as_ref() {
|
||||||
if cursor_position.is_over(close_bounds(
|
if cursor_position
|
||||||
bounds,
|
.is_over(close_bounds(bounds, f32::from(self.close_icon.size)))
|
||||||
f32::from(self.close_icon.size),
|
{
|
||||||
self.button_padding,
|
|
||||||
)) {
|
|
||||||
if let Event::Mouse(mouse::Event::ButtonReleased(
|
if let Event::Mouse(mouse::Event::ButtonReleased(
|
||||||
mouse::Button::Left,
|
mouse::Button::Left,
|
||||||
))
|
))
|
||||||
|
|
@ -595,8 +628,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
state.hovered = Item::None;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -698,14 +729,20 @@ where
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
|
|
||||||
if cursor_position.is_over(bounds) {
|
if cursor_position.is_over(bounds) {
|
||||||
for (key, bounds) in self.variant_button_bounds(state, bounds) {
|
let hovered_button = self
|
||||||
if cursor_position.is_over(bounds) {
|
.variant_bounds(state, bounds)
|
||||||
return if self.model.items[key].enabled {
|
.filter_map(|item| match item {
|
||||||
iced_core::mouse::Interaction::Pointer
|
ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
|
||||||
} else {
|
_ => None,
|
||||||
iced_core::mouse::Interaction::Idle
|
})
|
||||||
};
|
.find(|(_key, bounds)| cursor_position.is_over(*bounds));
|
||||||
}
|
|
||||||
|
if let Some((key, _bounds)) = hovered_button {
|
||||||
|
return if self.model.items[key].enabled {
|
||||||
|
iced_core::mouse::Interaction::Pointer
|
||||||
|
} else {
|
||||||
|
iced_core::mouse::Interaction::Idle
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -725,21 +762,25 @@ where
|
||||||
) {
|
) {
|
||||||
let state = tree.state.downcast_ref::<LocalState>();
|
let state = tree.state.downcast_ref::<LocalState>();
|
||||||
let appearance = Self::variant_appearance(theme, &self.style);
|
let appearance = Self::variant_appearance(theme, &self.style);
|
||||||
let bounds = layout.bounds();
|
let bounds: Rectangle = layout.bounds();
|
||||||
let button_amount = self.model.items.len();
|
let button_amount = self.model.items.len();
|
||||||
|
|
||||||
|
// Modifies alpha color when `on_activate` is unset.
|
||||||
let apply_alpha = |mut c: Color| {
|
let apply_alpha = |mut c: Color| {
|
||||||
if self.on_activate.is_some() {
|
if self.on_activate.is_none() {
|
||||||
c
|
|
||||||
} else {
|
|
||||||
c.a /= 2.0;
|
c.a /= 2.0;
|
||||||
c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Maps `apply_alpha` to background color.
|
||||||
let bg_with_alpha = |mut b| {
|
let bg_with_alpha = |mut b| {
|
||||||
match &mut b {
|
match &mut b {
|
||||||
Background::Color(c) => {
|
Background::Color(c) => {
|
||||||
*c = apply_alpha(*c);
|
*c = apply_alpha(*c);
|
||||||
}
|
}
|
||||||
|
|
||||||
Background::Gradient(g) => {
|
Background::Gradient(g) => {
|
||||||
let Gradient::Linear(mut l) = g;
|
let Gradient::Linear(mut l) = g;
|
||||||
for c in &mut l.stops {
|
for c in &mut l.stops {
|
||||||
|
|
@ -752,6 +793,7 @@ where
|
||||||
}
|
}
|
||||||
b
|
b
|
||||||
};
|
};
|
||||||
|
|
||||||
// Draw the background, if a background was defined.
|
// Draw the background, if a background was defined.
|
||||||
if let Some(background) = appearance.background {
|
if let Some(background) = appearance.background {
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
|
|
@ -759,7 +801,7 @@ where
|
||||||
bounds,
|
bounds,
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: appearance.border_radius,
|
radius: appearance.border_radius,
|
||||||
..Default::default()
|
..Border::default()
|
||||||
},
|
},
|
||||||
shadow: Shadow::default(),
|
shadow: Shadow::default(),
|
||||||
},
|
},
|
||||||
|
|
@ -769,6 +811,8 @@ where
|
||||||
|
|
||||||
// Draw previous and next tab buttons if there is a need to paginate tabs.
|
// Draw previous and next tab buttons if there is a need to paginate tabs.
|
||||||
if state.collapsed {
|
if state.collapsed {
|
||||||
|
let mut tab_bounds = prev_tab_bounds(&bounds, f32::from(self.button_height));
|
||||||
|
|
||||||
// Previous tab button
|
// Previous tab button
|
||||||
let mut background_appearance =
|
let mut background_appearance =
|
||||||
if self.on_activate.is_some() && Item::PrevButton == state.focused_item {
|
if self.on_activate.is_some() && Item::PrevButton == state.focused_item {
|
||||||
|
|
@ -782,12 +826,7 @@ where
|
||||||
if let Some(background_appearance) = background_appearance.take() {
|
if let Some(background_appearance) = background_appearance.take() {
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: Rectangle {
|
bounds: tab_bounds,
|
||||||
x: bounds.x,
|
|
||||||
y: bounds.y,
|
|
||||||
width: f32::from(self.button_height),
|
|
||||||
height: bounds.height,
|
|
||||||
},
|
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: theme.cosmic().radius_s().into(),
|
radius: theme.cosmic().radius_s().into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -814,14 +853,16 @@ where
|
||||||
appearance.active.text_color
|
appearance.active.text_color
|
||||||
}),
|
}),
|
||||||
Rectangle {
|
Rectangle {
|
||||||
x: bounds.x + f32::from(self.button_height) / 4.0,
|
x: tab_bounds.x + 8.0,
|
||||||
y: bounds.y + f32::from(self.button_height) / 4.0,
|
y: tab_bounds.y + f32::from(self.button_height) / 4.0,
|
||||||
width: 16.0,
|
width: 16.0,
|
||||||
height: 16.0,
|
height: 16.0,
|
||||||
},
|
},
|
||||||
icon::from_name("go-previous-symbolic").size(16).icon(),
|
icon::from_name("go-previous-symbolic").size(16).icon(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
tab_bounds = next_tab_bounds(&bounds, f32::from(self.button_height));
|
||||||
|
|
||||||
// Next tab button
|
// Next tab button
|
||||||
background_appearance =
|
background_appearance =
|
||||||
if self.on_activate.is_some() && Item::NextButton == state.focused_item {
|
if self.on_activate.is_some() && Item::NextButton == state.focused_item {
|
||||||
|
|
@ -835,12 +876,7 @@ where
|
||||||
if let Some(background_appearance) = background_appearance {
|
if let Some(background_appearance) = background_appearance {
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: Rectangle {
|
bounds: tab_bounds,
|
||||||
x: bounds.x + bounds.width - f32::from(self.button_height),
|
|
||||||
y: bounds.y,
|
|
||||||
width: f32::from(self.button_height),
|
|
||||||
height: bounds.height,
|
|
||||||
},
|
|
||||||
border: Border {
|
border: Border {
|
||||||
radius: theme.cosmic().radius_s().into(),
|
radius: theme.cosmic().radius_s().into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -866,10 +902,16 @@ where
|
||||||
} else {
|
} else {
|
||||||
appearance.inactive.text_color
|
appearance.inactive.text_color
|
||||||
}),
|
}),
|
||||||
|
// Rectangle {
|
||||||
|
// x: bounds.x + bounds.width - f32::from(self.button_height)
|
||||||
|
// + f32::from(self.button_height) / 4.0,
|
||||||
|
// y: f32::from(self.button_height) / 2.0 - f32::from(self.button_height) / 2.0,
|
||||||
|
// width: 16.0,
|
||||||
|
// height: 16.0,
|
||||||
|
// },
|
||||||
Rectangle {
|
Rectangle {
|
||||||
x: bounds.x + bounds.width - f32::from(self.button_height)
|
x: tab_bounds.x + 8.0,
|
||||||
+ f32::from(self.button_height) / 4.0,
|
y: tab_bounds.y + f32::from(self.button_height) / 4.0,
|
||||||
y: bounds.y + f32::from(self.button_height) / 4.0,
|
|
||||||
width: 16.0,
|
width: 16.0,
|
||||||
height: 16.0,
|
height: 16.0,
|
||||||
},
|
},
|
||||||
|
|
@ -878,10 +920,34 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw each of the items in the widget.
|
// Draw each of the items in the widget.
|
||||||
for (nth, (key, mut bounds)) in self.variant_button_bounds(state, bounds).enumerate() {
|
let mut nth = 0;
|
||||||
|
self.variant_bounds(state, bounds).for_each(move |item| {
|
||||||
|
let (key, mut bounds) = match item {
|
||||||
|
// Draw a button
|
||||||
|
ItemBounds::Button(entity, bounds) => (entity, bounds),
|
||||||
|
|
||||||
|
// Draw a divider between buttons
|
||||||
|
ItemBounds::Divider(bounds) => {
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds,
|
||||||
|
border: Border::default(),
|
||||||
|
shadow: Shadow::default(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
let theme = crate::theme::active();
|
||||||
|
Background::Color(theme.cosmic().small_widget_divider().into())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let center_y = bounds.center_y();
|
||||||
|
|
||||||
let key_is_active = self.model.is_active(key);
|
let key_is_active = self.model.is_active(key);
|
||||||
let key_is_hovered = self.on_activate.is_some() && state.hovered == Item::Tab(key);
|
let key_is_hovered = self.on_activate.is_some() && state.hovered == Item::Tab(key);
|
||||||
|
|
||||||
let (status_appearance, font) =
|
let (status_appearance, font) =
|
||||||
if self.on_activate.is_some() && Item::Tab(key) == state.focused_item {
|
if self.on_activate.is_some() && Item::Tab(key) == state.focused_item {
|
||||||
(appearance.focus, &self.font_active)
|
(appearance.focus, &self.font_active)
|
||||||
|
|
@ -892,6 +958,7 @@ where
|
||||||
} else {
|
} else {
|
||||||
(appearance.inactive, &self.font_inactive)
|
(appearance.inactive, &self.font_inactive)
|
||||||
};
|
};
|
||||||
|
|
||||||
let font = font.unwrap_or_else(|| renderer.default_font());
|
let font = font.unwrap_or_else(|| renderer.default_font());
|
||||||
|
|
||||||
let button_appearance = if nth == 0 {
|
let button_appearance = if nth == 0 {
|
||||||
|
|
@ -941,7 +1008,8 @@ where
|
||||||
|
|
||||||
let original_bounds = bounds;
|
let original_bounds = bounds;
|
||||||
|
|
||||||
let y = bounds.center_y();
|
bounds.x += f32::from(self.button_padding[0]);
|
||||||
|
bounds.width -= f32::from(self.button_padding[0]) - f32::from(self.button_padding[2]);
|
||||||
|
|
||||||
// Adjust bounds by indent
|
// Adjust bounds by indent
|
||||||
if let Some(indent) = self.model.indent(key) {
|
if let Some(indent) = self.model.indent(key) {
|
||||||
|
|
@ -950,15 +1018,28 @@ where
|
||||||
bounds.width -= adjustment;
|
bounds.width -= adjustment;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the image beside the text.
|
// Align contents of the button to the requested `button_alignment`.
|
||||||
let horizontal_alignment = if let Some(icon) = self.model.icon(key) {
|
{
|
||||||
bounds.x += f32::from(self.button_padding[0]);
|
let actual_width = state.internal_layout[nth].1.width;
|
||||||
|
|
||||||
|
let offset = match self.button_alignment {
|
||||||
|
Alignment::Start => None,
|
||||||
|
Alignment::Center => Some((bounds.width - actual_width) / 2.0),
|
||||||
|
Alignment::End => Some(bounds.width - actual_width),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(offset) = offset {
|
||||||
|
bounds.x += offset - f32::from(self.button_padding[0]);
|
||||||
|
bounds.width = actual_width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the image beside the text.
|
||||||
|
if let Some(icon) = self.model.icon(key) {
|
||||||
let mut image_bounds = bounds;
|
let mut image_bounds = bounds;
|
||||||
let width = f32::from(icon.size);
|
let width = f32::from(icon.size);
|
||||||
let offset = width + f32::from(self.button_spacing);
|
let offset = width + f32::from(self.button_spacing);
|
||||||
image_bounds.y += f32::from(self.button_padding[1]);
|
image_bounds.y = center_y - width / 2.0;
|
||||||
image_bounds.y = y - width / 2.0;
|
|
||||||
|
|
||||||
draw_icon::<Message>(
|
draw_icon::<Message>(
|
||||||
renderer,
|
renderer,
|
||||||
|
|
@ -977,12 +1058,44 @@ where
|
||||||
|
|
||||||
bounds.x += offset;
|
bounds.x += offset;
|
||||||
bounds.width -= offset;
|
bounds.width -= offset;
|
||||||
|
|
||||||
alignment::Horizontal::Left
|
|
||||||
} else {
|
} else {
|
||||||
bounds.x = bounds.center_x();
|
// Draw the selection indicator if widget is a segmented selection, and the item is selected.
|
||||||
alignment::Horizontal::Center
|
if key_is_active {
|
||||||
};
|
if let crate::theme::SegmentedButton::Selection = self.style {
|
||||||
|
let mut image_bounds = bounds;
|
||||||
|
image_bounds.y = center_y - 16.0 / 2.0;
|
||||||
|
|
||||||
|
draw_icon::<Message>(
|
||||||
|
renderer,
|
||||||
|
theme,
|
||||||
|
style,
|
||||||
|
cursor,
|
||||||
|
viewport,
|
||||||
|
apply_alpha(status_appearance.text_color),
|
||||||
|
Rectangle {
|
||||||
|
width: 16.0,
|
||||||
|
height: 16.0,
|
||||||
|
..image_bounds
|
||||||
|
},
|
||||||
|
crate::widget::icon(
|
||||||
|
match crate::widget::common::object_select().data() {
|
||||||
|
crate::iced_core::svg::Data::Bytes(bytes) => {
|
||||||
|
crate::widget::icon::from_svg_bytes(bytes.as_ref())
|
||||||
|
}
|
||||||
|
crate::iced_core::svg::Data::Path(path) => {
|
||||||
|
crate::widget::icon::from_path(path.clone())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let offset = 16.0 + f32::from(self.button_spacing);
|
||||||
|
|
||||||
|
bounds.x += offset;
|
||||||
|
bounds.width -= offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Whether to show the close button on this tab.
|
// Whether to show the close button on this tab.
|
||||||
let show_close_button =
|
let show_close_button =
|
||||||
|
|
@ -997,7 +1110,7 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(text) = self.model.text(key) {
|
if let Some(text) = self.model.text(key) {
|
||||||
bounds.y = y;
|
bounds.y = center_y;
|
||||||
|
|
||||||
// Draw the text for this segmented button or tab.
|
// Draw the text for this segmented button or tab.
|
||||||
renderer.fill_text(
|
renderer.fill_text(
|
||||||
|
|
@ -1006,7 +1119,7 @@ where
|
||||||
size: iced::Pixels(self.font_size),
|
size: iced::Pixels(self.font_size),
|
||||||
bounds: bounds.size(),
|
bounds: bounds.size(),
|
||||||
font,
|
font,
|
||||||
horizontal_alignment,
|
horizontal_alignment: alignment::Horizontal::Left,
|
||||||
vertical_alignment: alignment::Vertical::Center,
|
vertical_alignment: alignment::Vertical::Center,
|
||||||
shaping: Shaping::Advanced,
|
shaping: Shaping::Advanced,
|
||||||
line_height: self.line_height,
|
line_height: self.line_height,
|
||||||
|
|
@ -1014,15 +1127,7 @@ where
|
||||||
bounds.position(),
|
bounds.position(),
|
||||||
apply_alpha(status_appearance.text_color),
|
apply_alpha(status_appearance.text_color),
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: {
|
width: bounds.width - close_icon_width,
|
||||||
let width = bounds.width - close_icon_width;
|
|
||||||
// TODO: determine cause of differences here.
|
|
||||||
if self.model.icon(key).is_some() {
|
|
||||||
width - f32::from(self.button_spacing)
|
|
||||||
} else {
|
|
||||||
width - 12.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
..original_bounds
|
..original_bounds
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -1030,8 +1135,7 @@ where
|
||||||
|
|
||||||
// Draw a close button if set.
|
// Draw a close button if set.
|
||||||
if show_close_button {
|
if show_close_button {
|
||||||
let close_button_bounds =
|
let close_button_bounds = close_bounds(original_bounds, close_icon_width);
|
||||||
close_bounds(original_bounds, close_icon_width, self.button_padding);
|
|
||||||
|
|
||||||
draw_icon::<Message>(
|
draw_icon::<Message>(
|
||||||
renderer,
|
renderer,
|
||||||
|
|
@ -1044,7 +1148,9 @@ where
|
||||||
self.close_icon.clone(),
|
self.close_icon.clone(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
nth += 1;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overlay<'b>(
|
fn overlay<'b>(
|
||||||
|
|
@ -1093,14 +1199,14 @@ pub struct LocalState {
|
||||||
/// Last known length of the model.
|
/// Last known length of the model.
|
||||||
pub(super) known_length: usize,
|
pub(super) known_length: usize,
|
||||||
/// Dimensions of internal buttons when shrinking
|
/// Dimensions of internal buttons when shrinking
|
||||||
pub(super) internal_layout: Vec<Size>,
|
pub(super) internal_layout: Vec<(Size, Size)>,
|
||||||
/// The paragraphs for each text.
|
/// The paragraphs for each text.
|
||||||
paragraphs: SecondaryMap<Entity, crate::Paragraph>,
|
paragraphs: SecondaryMap<Entity, crate::Paragraph>,
|
||||||
/// Time since last tab activation from wheel movements.
|
/// Time since last tab activation from wheel movements.
|
||||||
wheel_timestamp: Option<Instant>,
|
wheel_timestamp: Option<Instant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
enum Item {
|
enum Item {
|
||||||
NextButton,
|
NextButton,
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -1152,17 +1258,35 @@ impl From<Id> for widget::Id {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates the bounds of the close button within the area of an item.
|
/// 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> {
|
fn close_bounds(area: Rectangle<f32>, icon_size: f32) -> Rectangle<f32> {
|
||||||
let unpadded_height = area.height - f32::from(button_padding[1]) - f32::from(button_padding[3]);
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
x: area.x + area.width - icon_size - 8.0,
|
x: area.x + area.width - icon_size - 8.0,
|
||||||
y: area.y + (unpadded_height / 2.0) - (icon_size / 2.0),
|
y: area.center_y() - (icon_size / 2.0),
|
||||||
width: icon_size,
|
width: icon_size,
|
||||||
height: icon_size,
|
height: icon_size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculate the bounds of the `next_tab` button.
|
||||||
|
fn next_tab_bounds(bounds: &Rectangle, button_height: f32) -> Rectangle {
|
||||||
|
Rectangle {
|
||||||
|
x: bounds.x + bounds.width - button_height,
|
||||||
|
y: bounds.y + button_height / 4.0,
|
||||||
|
width: button_height,
|
||||||
|
height: button_height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the bounds of the `prev_tab` button.
|
||||||
|
fn prev_tab_bounds(bounds: &Rectangle, button_height: f32) -> Rectangle {
|
||||||
|
Rectangle {
|
||||||
|
x: bounds.x,
|
||||||
|
y: bounds.y + button_height / 4.0,
|
||||||
|
width: button_height,
|
||||||
|
height: button_height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn draw_icon<Message: 'static>(
|
fn draw_icon<Message: 'static>(
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
|
|
|
||||||
|
|
@ -14,16 +14,22 @@ use super::segmented_button::{
|
||||||
/// The data for the widget comes from a model that is maintained the application.
|
/// The data for the widget comes from a model that is maintained the application.
|
||||||
///
|
///
|
||||||
/// For details on the model, see the [`segmented_button`] module for more details.
|
/// For details on the model, see the [`segmented_button`] module for more details.
|
||||||
#[must_use]
|
|
||||||
pub fn horizontal<SelectionMode: Default, Message>(
|
pub fn horizontal<SelectionMode: Default, Message>(
|
||||||
model: &Model<SelectionMode>,
|
model: &Model<SelectionMode>,
|
||||||
) -> HorizontalSegmentedButton<SelectionMode, Message>
|
) -> HorizontalSegmentedButton<SelectionMode, Message>
|
||||||
where
|
where
|
||||||
Model<SelectionMode>: Selectable,
|
Model<SelectionMode>: Selectable,
|
||||||
{
|
{
|
||||||
|
let theme = crate::theme::active();
|
||||||
|
let space_s = theme.cosmic().space_s();
|
||||||
|
let space_xxs = theme.cosmic().space_xxs();
|
||||||
|
|
||||||
segmented_button::horizontal(model)
|
segmented_button::horizontal(model)
|
||||||
.button_padding([16, 0, 16, 0])
|
.button_alignment(iced::Alignment::Center)
|
||||||
|
.dividers(true)
|
||||||
.button_height(32)
|
.button_height(32)
|
||||||
|
.button_padding([space_s, 0, space_s, 0])
|
||||||
|
.button_spacing(space_xxs)
|
||||||
.style(crate::theme::SegmentedButton::Selection)
|
.style(crate::theme::SegmentedButton::Selection)
|
||||||
.font_active(Some(crate::font::FONT_SEMIBOLD))
|
.font_active(Some(crate::font::FONT_SEMIBOLD))
|
||||||
}
|
}
|
||||||
|
|
@ -33,7 +39,6 @@ where
|
||||||
/// The data for the widget comes from a model that is maintained the application.
|
/// The data for the widget comes from a model that is maintained the application.
|
||||||
///
|
///
|
||||||
/// For details on the model, see the [`segmented_button`] module for more details.
|
/// For details on the model, see the [`segmented_button`] module for more details.
|
||||||
#[must_use]
|
|
||||||
pub fn vertical<SelectionMode, Message>(
|
pub fn vertical<SelectionMode, Message>(
|
||||||
model: &Model<SelectionMode>,
|
model: &Model<SelectionMode>,
|
||||||
) -> VerticalSegmentedButton<SelectionMode, Message>
|
) -> VerticalSegmentedButton<SelectionMode, Message>
|
||||||
|
|
@ -41,9 +46,16 @@ where
|
||||||
Model<SelectionMode>: Selectable,
|
Model<SelectionMode>: Selectable,
|
||||||
SelectionMode: Default,
|
SelectionMode: Default,
|
||||||
{
|
{
|
||||||
|
let theme = crate::theme::active();
|
||||||
|
let space_s = theme.cosmic().space_s();
|
||||||
|
let space_xxs = theme.cosmic().space_xxs();
|
||||||
|
|
||||||
segmented_button::vertical(model)
|
segmented_button::vertical(model)
|
||||||
.button_padding([16, 0, 16, 0])
|
.button_alignment(iced::Alignment::Center)
|
||||||
|
.dividers(true)
|
||||||
.button_height(32)
|
.button_height(32)
|
||||||
|
.button_padding([space_s, 0, space_s, 0])
|
||||||
|
.button_spacing(space_xxs)
|
||||||
.style(crate::theme::SegmentedButton::Selection)
|
.style(crate::theme::SegmentedButton::Selection)
|
||||||
.font_active(Some(crate::font::FONT_SEMIBOLD))
|
.font_active(Some(crate::font::FONT_SEMIBOLD))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,16 +14,19 @@ use super::segmented_button::{
|
||||||
/// The data for the widget comes from a model supplied by the application.
|
/// The data for the widget comes from a model supplied by the application.
|
||||||
///
|
///
|
||||||
/// For details on the model, see the [`segmented_button`] module for more details.
|
/// For details on the model, see the [`segmented_button`] module for more details.
|
||||||
#[must_use]
|
|
||||||
pub fn horizontal<SelectionMode: Default, Message>(
|
pub fn horizontal<SelectionMode: Default, Message>(
|
||||||
model: &Model<SelectionMode>,
|
model: &Model<SelectionMode>,
|
||||||
) -> HorizontalSegmentedButton<SelectionMode, Message>
|
) -> HorizontalSegmentedButton<SelectionMode, Message>
|
||||||
where
|
where
|
||||||
Model<SelectionMode>: Selectable,
|
Model<SelectionMode>: Selectable,
|
||||||
{
|
{
|
||||||
|
let theme = crate::theme::active();
|
||||||
|
let space_s = theme.cosmic().space_s();
|
||||||
|
let space_xs = theme.cosmic().space_xs();
|
||||||
|
|
||||||
segmented_button::horizontal(model)
|
segmented_button::horizontal(model)
|
||||||
.button_padding([16, 0, 16, 0])
|
.button_height(44)
|
||||||
.button_height(48)
|
.button_padding([space_s, space_xs, space_s, space_xs])
|
||||||
.style(crate::theme::SegmentedButton::ViewSwitcher)
|
.style(crate::theme::SegmentedButton::ViewSwitcher)
|
||||||
.font_active(Some(crate::font::FONT_SEMIBOLD))
|
.font_active(Some(crate::font::FONT_SEMIBOLD))
|
||||||
}
|
}
|
||||||
|
|
@ -31,9 +34,7 @@ where
|
||||||
/// A collection of tabs for developing a tabbed interface.
|
/// A collection of tabs for developing a tabbed interface.
|
||||||
///
|
///
|
||||||
/// The data for the widget comes from a model that is maintained the application.
|
/// The data for the widget comes from a model that is maintained the application.
|
||||||
///
|
|
||||||
/// For details on the model, see the [`segmented_button`] module for more details.
|
/// For details on the model, see the [`segmented_button`] module for more details.
|
||||||
#[must_use]
|
|
||||||
pub fn vertical<SelectionMode, Message>(
|
pub fn vertical<SelectionMode, Message>(
|
||||||
model: &Model<SelectionMode>,
|
model: &Model<SelectionMode>,
|
||||||
) -> VerticalSegmentedButton<SelectionMode, Message>
|
) -> VerticalSegmentedButton<SelectionMode, Message>
|
||||||
|
|
@ -41,9 +42,13 @@ where
|
||||||
Model<SelectionMode>: Selectable,
|
Model<SelectionMode>: Selectable,
|
||||||
SelectionMode: Default,
|
SelectionMode: Default,
|
||||||
{
|
{
|
||||||
|
let theme = crate::theme::active();
|
||||||
|
let space_s = theme.cosmic().space_s();
|
||||||
|
let space_xs = theme.cosmic().space_xs();
|
||||||
|
|
||||||
SegmentedButton::new(model)
|
SegmentedButton::new(model)
|
||||||
.button_padding([16, 0, 16, 0])
|
.button_height(44)
|
||||||
.button_height(48)
|
.button_padding([space_s, space_xs, space_s, space_xs])
|
||||||
.style(crate::theme::SegmentedButton::ViewSwitcher)
|
.style(crate::theme::SegmentedButton::ViewSwitcher)
|
||||||
.font_active(Some(crate::font::FONT_SEMIBOLD))
|
.font_active(Some(crate::font::FONT_SEMIBOLD))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue