improv(segmented-button): Express vertical/horizontal variants as a state machine

It's difficult to make iterative developments when there's two
nearly-identical types that need to be kept synchronized to any change.
Rust gives us traits so we should use them instead of duplicating code.

This made it easier to make styling and layout improvements to both
instances of the segmented button.
This commit is contained in:
Michael Aaron Murphy 2023-01-04 05:37:20 +01:00
parent b704eea531
commit 357de5e9be
No known key found for this signature in database
GPG key ID: B2732D4240C9212C
9 changed files with 633 additions and 726 deletions

View file

@ -3,7 +3,7 @@
use crate::theme::Theme;
use crate::widget::segmented_button;
use iced_core::{Background, BorderRadius, Color};
use iced_core::{Background, BorderRadius};
#[derive(Clone, Copy, Default)]
pub enum SegmentedButton {
@ -24,69 +24,122 @@ impl segmented_button::StyleSheet for Theme {
SegmentedButton::ViewSwitcher => {
let cosmic = self.cosmic();
segmented_button::Appearance {
background: None,
border_color: Color::TRANSPARENT,
border_radius: BorderRadius::from(0.0),
border_width: 0.0,
button_active: segmented_button::ButtonAppearance {
active: segmented_button::ButtonStatusAppearance {
background: Some(Background::Color(cosmic.primary.component.base.into())),
border_bottom: Some((4.0, cosmic.accent.base.into())),
border_radius_first: BorderRadius::from([8.0, 8.0, 0.0, 0.0]),
border_radius_last: BorderRadius::from([8.0, 8.0, 0.0, 0.0]),
border_radius_middle: BorderRadius::from([8.0, 8.0, 0.0, 0.0]),
first: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([8.0, 8.0, 0.0, 0.0]),
border_bottom: Some((4.0, cosmic.accent.base.into())),
..Default::default()
},
middle: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([8.0, 8.0, 0.0, 0.0]),
border_bottom: Some((4.0, cosmic.accent.base.into())),
..Default::default()
},
last: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([8.0, 8.0, 0.0, 0.0]),
border_bottom: Some((4.0, cosmic.accent.base.into())),
..Default::default()
},
text_color: cosmic.accent.base.into(),
},
button_inactive: segmented_button::ButtonAppearance {
inactive: segmented_button::ButtonStatusAppearance {
background: None,
border_bottom: Some((1.0, cosmic.accent.base.into())),
border_radius_first: BorderRadius::from(0.0),
border_radius_last: BorderRadius::from(0.0),
border_radius_middle: BorderRadius::from(0.0),
first: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(0.0),
border_bottom: Some((1.0, cosmic.accent.base.into())),
..Default::default()
},
middle: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(0.0),
border_bottom: Some((1.0, cosmic.accent.base.into())),
..Default::default()
},
last: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(0.0),
border_bottom: Some((1.0, cosmic.accent.base.into())),
..Default::default()
},
text_color: cosmic.primary.on.into(),
},
button_hover: segmented_button::ButtonAppearance {
hover: segmented_button::ButtonStatusAppearance {
background: Some(Background::Color(cosmic.primary.component.hover.into())),
border_bottom: Some((1.0, cosmic.accent.base.into())),
border_radius_first: BorderRadius::from([8.0, 8.0, 0.0, 0.0]),
border_radius_last: BorderRadius::from([8.0, 8.0, 0.0, 0.0]),
border_radius_middle: BorderRadius::from([8.0, 8.0, 0.0, 0.0]),
first: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([8.0, 8.0, 0.0, 0.0]),
border_bottom: Some((1.0, cosmic.accent.base.into())),
..Default::default()
},
middle: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([8.0, 8.0, 0.0, 0.0]),
border_bottom: Some((1.0, cosmic.accent.base.into())),
..Default::default()
},
last: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([8.0, 8.0, 0.0, 0.0]),
border_bottom: Some((1.0, cosmic.accent.base.into())),
..Default::default()
},
text_color: cosmic.accent.base.into(),
},
..Default::default()
}
}
SegmentedButton::Selection => {
let cosmic = self.cosmic();
segmented_button::Appearance {
background: None,
border_color: Color::TRANSPARENT,
border_radius: BorderRadius::from(0.0),
border_width: 0.0,
button_active: segmented_button::ButtonAppearance {
active: segmented_button::ButtonStatusAppearance {
background: Some(Background::Color(
cosmic.secondary.component.divider.into(),
)),
border_bottom: None,
border_radius_first: BorderRadius::from([24.0, 0.0, 0.0, 24.0]),
border_radius_last: BorderRadius::from([0.0, 24.0, 24.0, 0.0]),
border_radius_middle: BorderRadius::from(0.0),
first: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([24.0, 0.0, 0.0, 24.0]),
..Default::default()
},
middle: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(0.0),
..Default::default()
},
last: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([0.0, 24.0, 24.0, 0.0]),
..Default::default()
},
text_color: cosmic.accent.base.into(),
},
button_inactive: segmented_button::ButtonAppearance {
inactive: segmented_button::ButtonStatusAppearance {
background: Some(Background::Color(cosmic.secondary.component.base.into())),
border_bottom: None,
border_radius_first: BorderRadius::from([24.0, 0.0, 0.0, 24.0]),
border_radius_last: BorderRadius::from([0.0, 24.0, 24.0, 0.0]),
border_radius_middle: BorderRadius::from(0.0),
first: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([24.0, 0.0, 0.0, 24.0]),
..Default::default()
},
middle: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(0.0),
..Default::default()
},
last: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([0.0, 24.0, 24.0, 0.0]),
..Default::default()
},
text_color: cosmic.primary.on.into(),
},
button_hover: segmented_button::ButtonAppearance {
hover: segmented_button::ButtonStatusAppearance {
background: Some(Background::Color(cosmic.primary.component.hover.into())),
border_bottom: None,
border_radius_first: BorderRadius::from([24.0, 0.0, 0.0, 24.0]),
border_radius_last: BorderRadius::from([0.0, 24.0, 24.0, 0.0]),
border_radius_middle: BorderRadius::from(0.0),
first: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([24.0, 0.0, 0.0, 24.0]),
..Default::default()
},
middle: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(0.0),
..Default::default()
},
last: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([0.0, 24.0, 24.0, 0.0]),
..Default::default()
},
text_color: cosmic.accent.base.into(),
},
..Default::default()
}
}
SegmentedButton::Custom(func) => func(self),
@ -98,69 +151,113 @@ impl segmented_button::StyleSheet for Theme {
SegmentedButton::ViewSwitcher => {
let cosmic = self.cosmic();
segmented_button::Appearance {
background: None,
border_color: Color::TRANSPARENT,
border_radius: BorderRadius::from(0.0),
border_width: 0.0,
button_active: segmented_button::ButtonAppearance {
active: segmented_button::ButtonStatusAppearance {
background: Some(Background::Color(cosmic.primary.component.base.into())),
border_bottom: None,
border_radius_first: BorderRadius::from(24.0),
border_radius_last: BorderRadius::from(24.0),
border_radius_middle: BorderRadius::from(24.0),
first: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(24.0),
..Default::default()
},
middle: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(24.0),
..Default::default()
},
last: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(24.0),
..Default::default()
},
text_color: cosmic.accent.base.into(),
},
button_inactive: segmented_button::ButtonAppearance {
inactive: segmented_button::ButtonStatusAppearance {
background: None,
border_bottom: None,
border_radius_first: BorderRadius::from(24.0),
border_radius_last: BorderRadius::from(24.0),
border_radius_middle: BorderRadius::from(24.0),
first: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(24.0),
..Default::default()
},
middle: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(24.0),
..Default::default()
},
last: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(24.0),
..Default::default()
},
text_color: cosmic.primary.on.into(),
},
button_hover: segmented_button::ButtonAppearance {
hover: segmented_button::ButtonStatusAppearance {
background: Some(Background::Color(cosmic.primary.component.hover.into())),
border_bottom: None,
border_radius_first: BorderRadius::from(24.0),
border_radius_last: BorderRadius::from(24.0),
border_radius_middle: BorderRadius::from(24.0),
first: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(24.0),
..Default::default()
},
middle: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(24.0),
..Default::default()
},
last: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(24.0),
..Default::default()
},
text_color: cosmic.accent.base.into(),
},
..Default::default()
}
}
SegmentedButton::Selection => {
let cosmic = self.cosmic();
segmented_button::Appearance {
background: None,
border_color: Color::TRANSPARENT,
border_radius: BorderRadius::from(0.0),
border_width: 0.0,
button_active: segmented_button::ButtonAppearance {
active: segmented_button::ButtonStatusAppearance {
background: Some(Background::Color(
cosmic.secondary.component.divider.into(),
)),
border_bottom: None,
border_radius_first: BorderRadius::from([24.0, 24.0, 0.0, 0.0]),
border_radius_last: BorderRadius::from([0.0, 0.0, 24.0, 24.0]),
border_radius_middle: BorderRadius::from(0.0),
first: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([24.0, 24.0, 0.0, 0.0]),
..Default::default()
},
middle: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(0.0),
..Default::default()
},
last: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([0.0, 0.0, 24.0, 24.0]),
..Default::default()
},
text_color: cosmic.accent.base.into(),
},
button_inactive: segmented_button::ButtonAppearance {
inactive: segmented_button::ButtonStatusAppearance {
background: Some(Background::Color(cosmic.secondary.component.base.into())),
border_bottom: None,
border_radius_first: BorderRadius::from([24.0, 24.0, 0.0, 0.0]),
border_radius_last: BorderRadius::from([0.0, 0.0, 24.0, 24.0]),
border_radius_middle: BorderRadius::from(0.0),
first: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([24.0, 24.0, 0.0, 0.0]),
..Default::default()
},
middle: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(0.0),
..Default::default()
},
last: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([0.0, 0.0, 24.0, 24.0]),
..Default::default()
},
text_color: cosmic.primary.on.into(),
},
button_hover: segmented_button::ButtonAppearance {
hover: segmented_button::ButtonStatusAppearance {
background: Some(Background::Color(cosmic.primary.component.hover.into())),
border_bottom: None,
border_radius_first: BorderRadius::from([24.0, 24.0, 0.0, 0.0]),
border_radius_last: BorderRadius::from([0.0, 0.0, 24.0, 24.0]),
border_radius_middle: BorderRadius::from(0.0),
first: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([24.0, 24.0, 0.0, 0.0]),
..Default::default()
},
middle: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from(0.0),
..Default::default()
},
last: segmented_button::ButtonAppearance {
border_radius: BorderRadius::from([0.0, 0.0, 24.0, 24.0]),
..Default::default()
},
text_color: cosmic.accent.base.into(),
},
..Default::default()
}
}
SegmentedButton::Custom(func) => func(self),

View file

@ -25,7 +25,6 @@ pub use toggler::toggler;
pub mod segmented_button;
pub use segmented_button::{
horizontal_segmented_button, vertical_segmented_button, HorizontalSegmentedButton,
VerticalSegmentedButton,
};
pub mod settings;

View file

@ -1,7 +1,7 @@
// Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use super::{HorizontalSegmentedButton, State, VerticalSegmentedButton};
use super::{HorizontalSegmentedButton, SegmentedButton, State, VerticalSegmentedButton};
/// Appears as a collection of tabs for developing a tabbed interface.
///
@ -10,7 +10,7 @@ use super::{HorizontalSegmentedButton, State, VerticalSegmentedButton};
pub fn horizontal_view_switcher<Message, Data>(
state: &State<Data>,
) -> HorizontalSegmentedButton<Message, crate::Renderer> {
HorizontalSegmentedButton::new(&state.inner)
SegmentedButton::new(&state.inner)
.button_padding([16, 0, 16, 0])
.button_height(48)
.style(crate::theme::SegmentedButton::ViewSwitcher)
@ -24,7 +24,7 @@ pub fn horizontal_view_switcher<Message, Data>(
pub fn horizontal_segmented_selection<Message, Data>(
state: &State<Data>,
) -> HorizontalSegmentedButton<Message, crate::Renderer> {
HorizontalSegmentedButton::new(&state.inner)
SegmentedButton::new(&state.inner)
.button_padding([16, 0, 16, 0])
.button_height(32)
.style(crate::theme::SegmentedButton::Selection)
@ -38,7 +38,7 @@ pub fn horizontal_segmented_selection<Message, Data>(
pub fn vertical_segmented_selection<Message, Data>(
state: &State<Data>,
) -> VerticalSegmentedButton<Message, crate::Renderer> {
VerticalSegmentedButton::new(&state.inner)
SegmentedButton::new(&state.inner)
.button_padding([16, 0, 16, 0])
.button_height(32)
.style(crate::theme::SegmentedButton::Selection)
@ -52,7 +52,7 @@ pub fn vertical_segmented_selection<Message, Data>(
pub fn vertical_view_switcher<Message, Data>(
state: &State<Data>,
) -> VerticalSegmentedButton<Message, crate::Renderer> {
VerticalSegmentedButton::new(&state.inner)
SegmentedButton::new(&state.inner)
.button_padding([16, 0, 16, 0])
.button_height(48)
.style(crate::theme::SegmentedButton::ViewSwitcher)

View file

@ -1,172 +1,72 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use super::state::{Key, SharedWidgetState, State};
use super::state::State;
use super::style::StyleSheet;
use super::UniqueWidgetState;
use super::widget::{SegmentedButton, SegmentedVariant};
use derive_setters::Setters;
use iced::{
alignment::{Horizontal, Vertical},
event, mouse, touch, Background, Color, Element, Event, Length, Point, Rectangle, Size,
};
use iced_core::BorderRadius;
use iced_native::widget::tree;
use iced_native::{layout, renderer, widget::Tree, Clipboard, Layout, Shell, Widget};
use iced::{Length, Rectangle, Size};
use iced_native::layout;
/// Creates a [`HorizontalSegmentedButton`].
/// A type marker defining the horizontal variant of a [`SegmentedButton`].
pub struct Horizontal;
/// Horizontal [`SegmentedButton`].
pub type HorizontalSegmentedButton<'a, Message, Renderer> =
SegmentedButton<'a, Horizontal, Message, Renderer>;
/// Horizontal implementation of the [`SegmentedButton`].
#[must_use]
pub fn horizontal_segmented_button<Message, Renderer, Data>(
state: &State<Data>,
) -> HorizontalSegmentedButton<Message, Renderer>
) -> SegmentedButton<Horizontal, Message, Renderer>
where
Renderer: iced_native::Renderer + iced_native::text::Renderer,
Renderer::Theme: StyleSheet,
{
HorizontalSegmentedButton::new(&state.inner)
SegmentedButton::new(&state.inner)
}
/// A widget providing a conjoined set of horizontally-arranged buttons for choosing between.
///
/// The data for the widget comes from a [`State`] that is maintained the application.
#[derive(Setters)]
pub struct HorizontalSegmentedButton<'a, Message, Renderer>
impl<'a, Message, Renderer> SegmentedVariant for SegmentedButton<'a, Horizontal, Message, Renderer>
where
Renderer: iced_native::Renderer + iced_native::text::Renderer,
Renderer::Theme: StyleSheet,
{
/// Contains application state also used for drawing.
#[setters(skip)]
state: &'a SharedWidgetState,
/// Desired font for active tabs.
font_active: Renderer::Font,
/// Desired font for hovered tabs.
font_hovered: Renderer::Font,
/// Desired font for inactive tabs.
font_inactive: Renderer::Font,
/// Desired width of the widget.
width: Length,
/// Desired height of the widget.
height: Length,
/// Padding around a button.
button_padding: [u16; 4],
/// Desired height of a button.
button_height: u16,
/// Desired spacing between buttons.
spacing: u16,
/// Style to draw the widget in.
#[setters(into)]
style: <Renderer::Theme as StyleSheet>::Style,
/// Emits the ID of the activated widget on selection.
#[setters(skip)]
on_activate: Option<Box<dyn Fn(Key) -> Message>>,
}
type Renderer = Renderer;
impl<'a, Message, Renderer> HorizontalSegmentedButton<'a, Message, Renderer>
where
Renderer: iced_native::Renderer + iced_native::text::Renderer,
Renderer::Theme: StyleSheet,
{
#[must_use]
pub fn new(state: &'a SharedWidgetState) -> Self {
Self {
state,
font_active: Renderer::Font::default(),
font_hovered: Renderer::Font::default(),
font_inactive: Renderer::Font::default(),
height: Length::Shrink,
width: Length::Fill,
button_padding: [4, 4, 4, 4],
button_height: 32,
spacing: 0,
style: <Renderer::Theme as StyleSheet>::Style::default(),
on_activate: None,
}
fn variant_appearance(
theme: &<Self::Renderer as iced_native::Renderer>::Theme,
style: &<<Self::Renderer as iced_native::Renderer>::Theme as StyleSheet>::Style,
) -> super::Appearance {
theme.horizontal(style)
}
/// Emits the ID of the activated widget on selection.
#[must_use]
pub fn on_activate(mut self, on_activate: impl Fn(Key) -> Message + 'static) -> Self {
self.on_activate = Some(Box::from(on_activate));
self
}
#[allow(clippy::cast_precision_loss)]
fn variant_button_bounds(&self, mut bounds: Rectangle, nth: usize) -> Rectangle {
let num = self.state.buttons.len();
if num != 0 {
let spacing = f32::from(self.spacing);
bounds.width = (bounds.width - (num as f32 * spacing) + spacing) / num as f32;
/// Creates a closure for generating the layout bounds of the buttons.
fn button_bounds(&self, bounds: Rectangle) -> impl FnMut() -> Rectangle {
let button_amount = self.state.buttons.len();
let width = bounds.width / button_amount as f32;
let spacing = self.spacing as f32;
let half = spacing / 2.0;
let mut bounds = bounds;
bounds.width = width;
let mut counter = 1;
move || {
let mut clone = bounds;
if counter == 1 {
clone.width -= half;
} else if counter == button_amount {
clone.x += spacing;
clone.width -= spacing;
} else {
clone.x += half;
clone.width -= half;
if nth != 0 {
bounds.x += (nth as f32 * bounds.width) + (nth as f32 * spacing);
}
bounds.x += width;
counter += 1;
clone
}
bounds
}
fn measure_button(
&self,
renderer: &Renderer,
text: &str,
text_size: u16,
bounds: Size,
) -> (f32, f32) {
let (mut w, mut h) = renderer.measure(text, text_size, Default::default(), bounds);
w += self.button_padding[0] as f32 + self.button_padding[2] as f32;
h += self.button_padding[1] as f32 + self.button_padding[3] as f32;
h = h.max(self.button_height as f32);
(w, h)
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for HorizontalSegmentedButton<'a, Message, Renderer>
where
Renderer: iced_native::Renderer + iced_native::text::Renderer,
Renderer::Theme: StyleSheet,
Message: 'static + Clone,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<UniqueWidgetState>()
}
fn state(&self) -> tree::State {
tree::State::new(UniqueWidgetState::default())
}
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
let mut width = 0.0f32;
let mut height = 0.0f32;
#[allow(clippy::cast_precision_loss)]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
fn variant_layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
let limits = limits.width(self.width);
let text_size = renderer.default_size();
for (_, content) in self.state.buttons.iter() {
let (w, h) = self.measure_button(renderer, &content.text, text_size, limits.max());
width += w + f32::from(self.spacing * 2);
height = height.max(h);
let (mut width, height) = self.max_button_dimensions(renderer, text_size, limits.max());
let num = self.state.buttons.len();
let spacing = f32::from(self.spacing);
if num != 0 {
width = (num as f32 * width) + (num as f32 * spacing) - spacing;
}
let size = limits
@ -175,176 +75,4 @@ where
layout::Node::new(size)
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let bounds = layout.bounds();
let mut bounds_generator = self.button_bounds(bounds);
let state = tree.state.downcast_mut::<UniqueWidgetState>();
if bounds.contains(cursor_position) {
for (key, _) in self.state.buttons.iter() {
let bounds = bounds_generator();
if bounds.contains(cursor_position) {
// Record that the mouse is hovering over this button.
state.hovered = key;
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;
}
}
}
}
} else {
state.hovered = Key::default();
}
event::Status::Ignored
}
fn mouse_interaction(
&self,
_tree: &Tree,
layout: Layout<'_>,
cursor_position: iced::Point,
_viewport: &iced::Rectangle,
_renderer: &Renderer,
) -> iced_native::mouse::Interaction {
let mut generator = self.button_bounds(layout.bounds());
if (0..self.state.buttons.len()).any(move |_| generator().contains(cursor_position)) {
iced_native::mouse::Interaction::Pointer
} else {
iced_native::mouse::Interaction::Idle
}
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &<Renderer as iced_native::Renderer>::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: iced::Point,
_viewport: &iced::Rectangle,
) {
let state = tree.state.downcast_ref::<UniqueWidgetState>();
let appearance = theme.horizontal(&self.style);
let bounds = layout.bounds();
let button_amount = self.state.buttons.len();
let mut bounds_generator = self.button_bounds(bounds);
// 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: appearance.border_width,
border_color: appearance.border_color,
},
background,
);
}
// Draw each of the buttons in the widget.
for (num, (key, content)) in self.state.buttons.iter().enumerate() {
let bounds = bounds_generator();
let (button_appearance, font) = if self.state.active == key {
(appearance.button_active, &self.font_active)
} else if state.hovered == key {
(appearance.button_hover, &self.font_hovered)
} else {
(appearance.button_inactive, &self.font_inactive)
};
let x = bounds.center_x();
let y = bounds.center_y();
// Render the background of the button.
if button_appearance.background.is_some() {
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: if num == 0 {
button_appearance.border_radius_first
} else if num + 1 == button_amount {
button_appearance.border_radius_last
} else {
button_appearance.border_radius_middle
},
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
button_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;
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: BorderRadius::from(0.0),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
background,
);
}
// Draw the text in this button.
renderer.fill_text(iced_native::text::Text {
content: &content.text,
size: f32::from(renderer.default_size()),
bounds: Rectangle { x, y, ..bounds },
color: button_appearance.text_color,
font: font.clone(),
horizontal_alignment: Horizontal::Center,
vertical_alignment: Vertical::Center,
});
}
}
fn overlay<'b>(
&'b self,
_tree: &'b mut Tree,
_layout: iced_native::Layout<'_>,
_renderer: &Renderer,
) -> Option<iced_native::overlay::Element<'b, Message, Renderer>> {
None
}
}
impl<'a, Message, Renderer> From<HorizontalSegmentedButton<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: iced_native::Renderer + iced_native::text::Renderer + 'a,
Renderer::Theme: StyleSheet,
Message: 'static + Clone,
{
fn from(widget: HorizontalSegmentedButton<'a, Message, Renderer>) -> Self {
Self::new(widget)
}
}

View file

@ -48,15 +48,10 @@ mod horizontal;
mod state;
mod style;
mod vertical;
mod widget;
pub use self::horizontal::{horizontal_segmented_button, HorizontalSegmentedButton};
pub use self::horizontal::{horizontal_segmented_button, Horizontal, HorizontalSegmentedButton};
pub use self::state::{ButtonContent, Key, SecondaryState, SharedWidgetState, State};
pub use self::style::{Appearance, ButtonAppearance, StyleSheet};
pub use self::vertical::{vertical_segmented_button, VerticalSegmentedButton};
/// State that is maintained by each individual widget.
#[derive(Default)]
struct UniqueWidgetState {
/// The ID of the button that is being hovered. Defaults to null.
hovered: Key,
}
pub use self::style::{Appearance, ButtonAppearance, ButtonStatusAppearance, StyleSheet};
pub use self::vertical::{vertical_segmented_button, Vertical, VerticalSegmentedButton};
pub use self::widget::{SegmentedButton, SegmentedVariant};

View file

@ -11,7 +11,7 @@ slotmap::new_key_type! {
/// Contains all state for interacting with a segmented button.
pub struct State<Data> {
/// State that is shareable across widget(s).
/// State that is shared with widget drawing.
pub inner: SharedWidgetState,
/// State unique to the application.

View file

@ -4,25 +4,35 @@
use iced_core::{Background, BorderRadius, Color};
/// The appearance of a segmented button.
#[derive(Clone, Copy)]
#[derive(Default, Clone, Copy)]
pub struct Appearance {
pub background: Option<Background>,
pub border_color: Color,
pub border_radius: BorderRadius,
pub border_width: f32,
pub button_active: ButtonAppearance,
pub button_inactive: ButtonAppearance,
pub button_hover: ButtonAppearance,
pub border_bottom: Option<(f32, Color)>,
pub border_end: Option<(f32, Color)>,
pub border_start: Option<(f32, Color)>,
pub border_top: Option<(f32, Color)>,
pub active: ButtonStatusAppearance,
pub inactive: ButtonStatusAppearance,
pub hover: ButtonStatusAppearance,
}
/// The appearance of a button in the segmented button
#[derive(Clone, Copy)]
#[derive(Default, Clone, Copy)]
pub struct ButtonAppearance {
pub background: Option<Background>,
pub border_radius: BorderRadius,
pub border_bottom: Option<(f32, Color)>,
pub border_radius_first: BorderRadius,
pub border_radius_middle: BorderRadius,
pub border_radius_last: BorderRadius,
pub border_end: Option<(f32, Color)>,
pub border_start: Option<(f32, Color)>,
pub border_top: Option<(f32, Color)>,
}
#[derive(Default, Clone, Copy)]
pub struct ButtonStatusAppearance {
pub background: Option<Background>,
pub first: ButtonAppearance,
pub middle: ButtonAppearance,
pub last: ButtonAppearance,
pub text_color: Color,
}

View file

@ -1,169 +1,72 @@
use super::state::{Key, SharedWidgetState, State};
use super::state::State;
use super::style::StyleSheet;
use super::UniqueWidgetState;
use super::widget::{SegmentedButton, SegmentedVariant};
use derive_setters::Setters;
use iced::{
alignment::{Horizontal, Vertical},
event, mouse, touch, Background, Color, Element, Event, Length, Point, Rectangle, Size,
};
use iced_core::BorderRadius;
use iced_native::widget::tree;
use iced_native::{layout, renderer, widget::Tree, Clipboard, Layout, Shell, Widget};
use iced::{Length, Rectangle, Size};
use iced_native::layout;
/// Creates a [`VerticalSegmentedButton`].
/// A type marker defining the vertical variant of a [`SegmentedButton`].
pub struct Vertical;
/// Vertical [`SegmentedButton`].
pub type VerticalSegmentedButton<'a, Message, Renderer> =
SegmentedButton<'a, Vertical, Message, Renderer>;
/// Vertical implementation of the [`SegmentedButton`].
#[must_use]
pub fn vertical_segmented_button<Message, Renderer, Data>(
state: &State<Data>,
) -> VerticalSegmentedButton<Message, Renderer>
) -> SegmentedButton<Vertical, Message, Renderer>
where
Renderer: iced_native::Renderer + iced_native::text::Renderer,
Renderer::Theme: StyleSheet,
{
VerticalSegmentedButton::new(&state.inner)
SegmentedButton::new(&state.inner)
}
/// A widget providing a conjoined set of linear buttons for choosing between.
///
/// The data for the widget comes from a [`State`] that is maintained the application.
#[derive(Setters)]
pub struct VerticalSegmentedButton<'a, Message, Renderer>
impl<'a, Message, Renderer> SegmentedVariant for SegmentedButton<'a, Vertical, Message, Renderer>
where
Renderer: iced_native::Renderer + iced_native::text::Renderer,
Renderer::Theme: StyleSheet,
{
/// Contains application state also used for drawing.
#[setters(skip)]
state: &'a SharedWidgetState,
/// Desired font for active tabs.
font_active: Renderer::Font,
/// Desired font for hovered tabs.
font_hovered: Renderer::Font,
/// Desired font for inactive tabs.
font_inactive: Renderer::Font,
/// Desired width of the widget.
width: Length,
/// Desired height of the widget.
height: Length,
/// Padding around a button.
button_padding: [u16; 4],
/// Desired height of a button.
button_height: u16,
/// Desired spacing between buttons.
spacing: u16,
/// Style to draw the widget in.
#[setters(into)]
style: <Renderer::Theme as StyleSheet>::Style,
/// Emits the ID of the activated widget on selection.
#[setters(skip)]
on_activate: Option<Box<dyn Fn(Key) -> Message>>,
}
type Renderer = Renderer;
impl<'a, Message, Renderer> VerticalSegmentedButton<'a, Message, Renderer>
where
Renderer: iced_native::Renderer + iced_native::text::Renderer,
Renderer::Theme: StyleSheet,
{
#[must_use]
pub fn new(state: &'a SharedWidgetState) -> Self {
Self {
state,
font_active: Renderer::Font::default(),
font_hovered: Renderer::Font::default(),
font_inactive: Renderer::Font::default(),
height: Length::Shrink,
width: Length::Fill,
button_padding: [4, 4, 4, 4],
button_height: 32,
spacing: 0,
style: <Renderer::Theme as StyleSheet>::Style::default(),
on_activate: None,
}
fn variant_appearance(
theme: &<Self::Renderer as iced_native::Renderer>::Theme,
style: &<<Self::Renderer as iced_native::Renderer>::Theme as StyleSheet>::Style,
) -> super::Appearance {
theme.vertical(style)
}
/// Emits the ID of the activated widget on selection.
#[must_use]
pub fn on_activate(mut self, on_activate: impl Fn(Key) -> Message + 'static) -> Self {
self.on_activate = Some(Box::from(on_activate));
self
}
#[allow(clippy::cast_precision_loss)]
fn variant_button_bounds(&self, mut bounds: Rectangle, nth: usize) -> Rectangle {
let num = self.state.buttons.len();
if num != 0 {
let spacing = f32::from(self.spacing);
bounds.height = (bounds.height - (num as f32 * spacing) + spacing) / num as f32;
/// Creates a closure for generating the layout bounds of the buttons.
fn button_bounds(&self, bounds: Rectangle) -> impl FnMut() -> Rectangle {
let button_amount = self.state.buttons.len();
let height = bounds.height / button_amount as f32;
let spacing = self.spacing as f32;
let half = spacing / 2.0;
let mut bounds = bounds;
bounds.height = height;
let mut counter = 1;
move || {
let mut clone = bounds;
if counter == 1 {
clone.height -= half;
} else if counter == button_amount {
clone.y += spacing;
clone.height -= spacing;
} else {
clone.y += half;
clone.height -= half;
if nth != 0 {
bounds.y += (nth as f32 * bounds.height) + (nth as f32 * spacing);
}
bounds.y += height;
counter += 1;
clone
}
bounds
}
fn measure_button(
&self,
renderer: &Renderer,
text: &str,
text_size: u16,
bounds: Size,
) -> (f32, f32) {
let (mut w, mut h) = renderer.measure(text, text_size, Default::default(), bounds);
w += self.button_padding[0] as f32 + self.button_padding[2] as f32;
h += self.button_padding[1] as f32 + self.button_padding[3] as f32;
h = h.max(self.button_height as f32);
(w, h)
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for VerticalSegmentedButton<'a, Message, Renderer>
where
Renderer: iced_native::Renderer + iced_native::text::Renderer,
Renderer::Theme: StyleSheet,
Message: 'static + Clone,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<UniqueWidgetState>()
}
fn state(&self) -> tree::State {
tree::State::new(UniqueWidgetState::default())
}
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
let mut width = 0.0f32;
let mut height = 0.0f32;
#[allow(clippy::cast_precision_loss)]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
fn variant_layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
let limits = limits.width(self.width);
let text_size = renderer.default_size();
for (_, content) in self.state.buttons.iter() {
let (w, h) = self.measure_button(renderer, &content.text, text_size, limits.max());
height += h + f32::from(self.spacing * 2);
width = width.max(w);
let (width, mut height) = self.max_button_dimensions(renderer, text_size, limits.max());
let num = self.state.buttons.len();
let spacing = f32::from(self.spacing);
if num != 0 {
height = (num as f32 * height) + (num as f32 * spacing) - spacing;
}
let size = limits
@ -172,176 +75,4 @@ where
layout::Node::new(size)
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let bounds = layout.bounds();
let mut bounds_generator = self.button_bounds(bounds);
let state = tree.state.downcast_mut::<UniqueWidgetState>();
if bounds.contains(cursor_position) {
for (key, _) in self.state.buttons.iter() {
let bounds = bounds_generator();
if bounds.contains(cursor_position) {
// Record that the mouse is hovering over this button.
state.hovered = key;
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;
}
}
}
}
} else {
state.hovered = Key::default();
}
event::Status::Ignored
}
fn mouse_interaction(
&self,
_tree: &Tree,
layout: Layout<'_>,
cursor_position: iced::Point,
_viewport: &iced::Rectangle,
_renderer: &Renderer,
) -> iced_native::mouse::Interaction {
let mut generator = self.button_bounds(layout.bounds());
if (0..self.state.buttons.len()).any(move |_| generator().contains(cursor_position)) {
iced_native::mouse::Interaction::Pointer
} else {
iced_native::mouse::Interaction::Idle
}
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &<Renderer as iced_native::Renderer>::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: iced::Point,
_viewport: &iced::Rectangle,
) {
let state = tree.state.downcast_ref::<UniqueWidgetState>();
let appearance = theme.vertical(&self.style);
let bounds = layout.bounds();
let button_amount = self.state.buttons.len();
let mut bounds_generator = self.button_bounds(bounds);
// 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: appearance.border_width,
border_color: appearance.border_color,
},
background,
);
}
// Draw each of the buttons in the widget.
for (num, (key, content)) in self.state.buttons.iter().enumerate() {
let bounds = bounds_generator();
let (button_appearance, font) = if self.state.active == key {
(appearance.button_active, &self.font_active)
} else if state.hovered == key {
(appearance.button_hover, &self.font_hovered)
} else {
(appearance.button_inactive, &self.font_inactive)
};
let x = bounds.center_x();
let y = bounds.center_y();
// Render the background of the button.
if button_appearance.background.is_some() {
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: if num == 0 {
button_appearance.border_radius_first
} else if num + 1 == button_amount {
button_appearance.border_radius_last
} else {
button_appearance.border_radius_middle
},
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
button_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;
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: BorderRadius::from(0.0),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
background,
);
}
// Draw the text in this button.
renderer.fill_text(iced_native::text::Text {
content: &content.text,
size: f32::from(renderer.default_size()),
bounds: Rectangle { x, y, ..bounds },
color: button_appearance.text_color,
font: font.clone(),
horizontal_alignment: Horizontal::Center,
vertical_alignment: Vertical::Center,
});
}
}
fn overlay<'b>(
&'b self,
_tree: &'b mut Tree,
_layout: iced_native::Layout<'_>,
_renderer: &Renderer,
) -> Option<iced_native::overlay::Element<'b, Message, Renderer>> {
None
}
}
impl<'a, Message, Renderer> From<VerticalSegmentedButton<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: iced_native::Renderer + iced_native::text::Renderer + 'a,
Renderer::Theme: StyleSheet,
Message: 'static + Clone,
{
fn from(widget: VerticalSegmentedButton<'a, Message, Renderer>) -> Self {
Self::new(widget)
}
}

View file

@ -0,0 +1,347 @@
use std::marker::PhantomData;
use super::state::{Key, SharedWidgetState};
use super::style::StyleSheet;
use derive_setters::Setters;
use iced::{
alignment, event, mouse, touch, Background, Color, Element, Event, Length, Point, Rectangle,
Size,
};
use iced_core::BorderRadius;
use iced_native::widget::tree;
use iced_native::{layout, renderer, widget::Tree, Clipboard, Layout, Shell, Widget};
/// Isolates variant-specific behaviors from [`SegmentedButton`].
pub trait SegmentedVariant {
type Renderer: iced_native::Renderer;
/// Get the appearance for this variant of the widget.
fn variant_appearance(
theme: &<Self::Renderer as iced_native::Renderer>::Theme,
style: &<<Self::Renderer as iced_native::Renderer>::Theme as StyleSheet>::Style,
) -> super::Appearance
where
<Self::Renderer as iced_native::Renderer>::Theme: StyleSheet;
/// 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.
fn variant_layout(&self, renderer: &Self::Renderer, limits: &layout::Limits) -> layout::Node;
}
#[derive(Setters)]
pub struct SegmentedButton<'a, Variant, Message, Renderer>
where
Renderer: iced_native::Renderer + iced_native::text::Renderer,
Renderer::Theme: StyleSheet,
{
/// Contains application state also used for drawing.
#[setters(skip)]
pub(super) state: &'a SharedWidgetState,
/// Desired font for active tabs.
pub(super) font_active: Renderer::Font,
/// Desired font for hovered tabs.
pub(super) font_hovered: Renderer::Font,
/// Desired font for inactive tabs.
pub(super) font_inactive: Renderer::Font,
/// Desired width of the widget.
pub(super) width: Length,
/// Desired height of the widget.
pub(super) height: Length,
/// Padding around a button.
pub(super) button_padding: [u16; 4],
/// Desired height of a button.
pub(super) button_height: u16,
/// Desired spacing between buttons.
pub(super) spacing: u16,
/// Style to draw the widget in.
#[setters(into)]
pub(super) style: <Renderer::Theme as StyleSheet>::Style,
#[setters(skip)]
/// Emits the ID of the activated widget on selection.
pub(super) on_activate: Option<Box<dyn Fn(Key) -> Message>>,
#[setters(skip)]
/// Defines the implementation of this struct
variant: PhantomData<Variant>,
}
impl<'a, Variant, Message, Renderer> SegmentedButton<'a, Variant, Message, Renderer>
where
Renderer: iced_native::Renderer + iced_native::text::Renderer,
Renderer::Theme: StyleSheet,
Self: SegmentedVariant<Renderer = Renderer>,
{
#[must_use]
pub fn new(state: &'a SharedWidgetState) -> Self {
Self {
state,
font_active: Renderer::Font::default(),
font_hovered: Renderer::Font::default(),
font_inactive: Renderer::Font::default(),
height: Length::Shrink,
width: Length::Fill,
button_padding: [4, 4, 4, 4],
button_height: 32,
spacing: 0,
style: <Renderer::Theme as StyleSheet>::Style::default(),
on_activate: None,
variant: PhantomData,
}
}
/// Emits the ID of the activated widget on selection.
#[must_use]
pub fn on_activate(mut self, on_activate: impl Fn(Key) -> Message + 'static) -> Self {
self.on_activate = Some(Box::from(on_activate));
self
}
pub(super) fn measure_button(
&self,
renderer: &Renderer,
text: &str,
text_size: u16,
bounds: Size,
) -> (f32, f32) {
let (mut w, mut h) = renderer.measure(text, text_size, Default::default(), bounds);
w += f32::from(self.button_padding[0]) + f32::from(self.button_padding[2]);
h += f32::from(self.button_padding[1]) + f32::from(self.button_padding[3]);
h = h.max(f32::from(self.button_height));
(w, h)
}
pub(super) fn max_button_dimensions(
&self,
renderer: &Renderer,
text_size: u16,
bounds: Size,
) -> (f32, f32) {
let mut width = 0.0f32;
let mut height = 0.0f32;
for (_, content) in self.state.buttons.iter() {
let (w, h) = self.measure_button(renderer, &content.text, text_size, bounds);
height = height.max(h);
width = width.max(w);
}
(width, height)
}
}
impl<'a, Variant, Message, Renderer> Widget<Message, Renderer>
for SegmentedButton<'a, Variant, Message, Renderer>
where
Renderer: iced_native::Renderer + iced_native::text::Renderer,
Renderer::Theme: StyleSheet,
Self: SegmentedVariant<Renderer = Renderer>,
Message: 'static + Clone,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<UniqueWidgetState>()
}
fn state(&self) -> tree::State {
tree::State::new(UniqueWidgetState::default())
}
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
self.variant_layout(renderer, limits)
}
fn on_event(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let bounds = layout.bounds();
let state = tree.state.downcast_mut::<UniqueWidgetState>();
if bounds.contains(cursor_position) {
for (nth, (key, _)) in self.state.buttons.iter().enumerate() {
let bounds = self.variant_button_bounds(bounds, nth);
if bounds.contains(cursor_position) {
// Record that the mouse is hovering over this button.
state.hovered = key;
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;
}
}
}
}
} else {
state.hovered = Key::default();
}
event::Status::Ignored
}
fn mouse_interaction(
&self,
_tree: &Tree,
layout: Layout<'_>,
cursor_position: iced::Point,
_viewport: &iced::Rectangle,
_renderer: &Renderer,
) -> iced_native::mouse::Interaction {
let bounds = layout.bounds();
if (0..self.state.buttons.len()).any(|nth| {
self.variant_button_bounds(bounds, nth)
.contains(cursor_position)
}) {
iced_native::mouse::Interaction::Pointer
} else {
iced_native::mouse::Interaction::Idle
}
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &<Renderer as iced_native::Renderer>::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: iced::Point,
_viewport: &iced::Rectangle,
) {
let state = tree.state.downcast_ref::<UniqueWidgetState>();
let appearance = Self::variant_appearance(theme, &self.style);
let bounds = layout.bounds();
let button_amount = self.state.buttons.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 buttons in the widget.
for (nth, (key, content)) in self.state.buttons.iter().enumerate() {
let bounds = self.variant_button_bounds(bounds, nth);
let (status_appearance, font) = if self.state.active == key {
(appearance.active, &self.font_active)
} else if state.hovered == key {
(appearance.hover, &self.font_hovered)
} else {
(appearance.inactive, &self.font_inactive)
};
let x = bounds.center_x();
let y = bounds.center_y();
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;
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: BorderRadius::from(0.0),
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
background,
);
}
// Draw the text in this button.
renderer.fill_text(iced_native::text::Text {
content: &content.text,
size: f32::from(renderer.default_size()),
bounds: Rectangle { x, y, ..bounds },
color: status_appearance.text_color,
font: font.clone(),
horizontal_alignment: alignment::Horizontal::Center,
vertical_alignment: alignment::Vertical::Center,
});
}
}
fn overlay<'b>(
&'b self,
_tree: &'b mut Tree,
_layout: iced_native::Layout<'_>,
_renderer: &Renderer,
) -> Option<iced_native::overlay::Element<'b, Message, Renderer>> {
None
}
}
impl<'a, Variant, Message, Renderer> From<SegmentedButton<'a, Variant, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: iced_native::Renderer + iced_native::text::Renderer + 'a,
Renderer::Theme: StyleSheet,
SegmentedButton<'a, Variant, Message, Renderer>: SegmentedVariant<Renderer = Renderer>,
Variant: 'static,
Message: 'static + Clone,
{
fn from(mut widget: SegmentedButton<'a, Variant, Message, Renderer>) -> Self {
if widget.state.buttons.is_empty() {
widget.spacing = 0;
}
Self::new(widget)
}
}
/// State that is maintained by each individual widget.
#[derive(Default)]
struct UniqueWidgetState {
/// The ID of the button that is being hovered. Defaults to null.
hovered: Key,
}