cosmic/widget/segmented_button/
horizontal.rs

1// Copyright 2022 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4//! Implementation details for the horizontal layout of a segmented button.
5
6use super::model::{Model, Selectable};
7use super::style::StyleSheet;
8use super::widget::{ItemBounds, LocalState, SegmentedButton, SegmentedVariant};
9
10use iced::{Length, Rectangle, Size};
11use iced_core::layout;
12use iced_core::text::Renderer;
13
14/// Horizontal [`SegmentedButton`].
15pub type HorizontalSegmentedButton<'a, SelectionMode, Message> =
16    SegmentedButton<'a, Horizontal, SelectionMode, Message>;
17
18/// A type marker defining the horizontal variant of a [`SegmentedButton`].
19pub struct Horizontal;
20
21/// Horizontal implementation of the [`SegmentedButton`].
22///
23/// For details on the model, see the [`segmented_button`](super) module for more details.
24pub fn horizontal<SelectionMode: Default, Message>(
25    model: &Model<SelectionMode>,
26) -> SegmentedButton<Horizontal, SelectionMode, Message>
27where
28    Model<SelectionMode>: Selectable,
29{
30    SegmentedButton::new(model)
31}
32
33impl<SelectionMode, Message> SegmentedVariant
34    for SegmentedButton<'_, Horizontal, SelectionMode, Message>
35where
36    Model<SelectionMode>: Selectable,
37    SelectionMode: Default,
38{
39    const VERTICAL: bool = false;
40
41    fn variant_appearance(
42        theme: &crate::Theme,
43        style: &crate::theme::SegmentedButton,
44    ) -> super::Appearance {
45        theme.horizontal(style)
46    }
47
48    #[allow(clippy::cast_precision_loss)]
49    fn variant_bounds<'b>(
50        &'b self,
51        state: &'b LocalState,
52        mut bounds: Rectangle,
53    ) -> Box<dyn Iterator<Item = ItemBounds> + 'b> {
54        let num = state.buttons_visible;
55        let spacing = f32::from(self.spacing);
56        let mut homogenous_width = 0.0;
57
58        if Length::Shrink != self.width || state.collapsed {
59            let mut width_offset = 0.0;
60            if state.collapsed {
61                bounds.x += f32::from(self.button_height);
62                width_offset = f32::from(self.button_height) * 2.0;
63            }
64
65            homogenous_width = ((num as f32).mul_add(-spacing, bounds.width - width_offset)
66                + spacing)
67                / num as f32;
68        }
69
70        let is_control = matches!(self.style, crate::theme::SegmentedButton::Control);
71
72        Box::new(
73            self.model
74                .order
75                .iter()
76                .copied()
77                .enumerate()
78                .skip(state.buttons_offset)
79                .take(state.buttons_visible)
80                .flat_map(move |(nth, key)| {
81                    let mut layout_bounds = bounds;
82
83                    let layout_size = &state.internal_layout[nth].0;
84
85                    if !state.collapsed && Length::Shrink == self.width {
86                        layout_bounds.width = layout_size.width;
87                    } else {
88                        layout_bounds.width = homogenous_width;
89                    }
90
91                    bounds.x += layout_bounds.width + spacing;
92
93                    let button_bounds = ItemBounds::Button(key, layout_bounds);
94                    let mut divider = None;
95
96                    if self.dividers && is_control && nth + 1 < num {
97                        divider = Some(ItemBounds::Divider(
98                            Rectangle {
99                                width: 1.0,
100                                ..bounds
101                            },
102                            true,
103                        ));
104
105                        bounds.x += 1.0;
106                    }
107
108                    std::iter::once(button_bounds).chain(divider)
109                }),
110        )
111    }
112
113    #[allow(clippy::cast_precision_loss)]
114    #[allow(clippy::cast_possible_truncation)]
115    #[allow(clippy::cast_sign_loss)]
116    fn variant_layout(
117        &self,
118        state: &mut LocalState,
119        renderer: &crate::Renderer,
120        limits: &layout::Limits,
121    ) -> Size {
122        state.internal_layout.clear();
123        let num = self.model.order.len();
124        let spacing = f32::from(self.spacing);
125        let size;
126
127        let mut reduce_button_offset = false;
128        if state.known_length != num {
129            if state.known_length > num {
130                state.buttons_offset -= state.buttons_offset.min(state.known_length - num);
131            } else {
132                reduce_button_offset = true;
133            }
134
135            state.known_length = num;
136        }
137
138        if let Length::Shrink = self.width {
139            // Buttons will be rendered at their smallest widths possible.
140            let max_height = self.max_button_dimensions(state, renderer).1;
141
142            // Get the max available width for placing buttons into.
143            let max_size = limits.height(Length::Fixed(max_height)).resolve(
144                Length::Fill,
145                max_height,
146                Size::new(limits.max().width, max_height),
147            );
148
149            let mut visible_width = 0.0;
150            state.buttons_visible = 0;
151
152            for (button_size, _actual_size) in &state.internal_layout {
153                visible_width += button_size.width;
154
155                if max_size.width - spacing >= visible_width {
156                    state.buttons_visible += 1;
157                } else {
158                    visible_width = max_size.width - max_height;
159                    break;
160                }
161
162                visible_width += spacing;
163            }
164
165            visible_width -= spacing;
166
167            state.collapsed = num > 1 && state.buttons_visible != num;
168
169            size = limits
170                .height(Length::Fixed(max_height))
171                .min_width(visible_width)
172                .min();
173        } else {
174            // Buttons will be rendered with equal widths.
175            state.buttons_visible = self.model.items.len();
176
177            let mut width = 0.0f32;
178            let font = renderer.default_font();
179
180            for key in self.model.order.iter().copied() {
181                let (button_width, button_height) = self.button_dimensions(state, font, key);
182
183                state.internal_layout.push((
184                    Size::new(button_width, button_height),
185                    Size::new(
186                        button_width
187                            - f32::from(self.button_padding[0])
188                            - f32::from(self.button_padding[2]),
189                        button_height,
190                    ),
191                ));
192
193                width = width.max(button_width);
194            }
195
196            let height = f32::from(self.button_height);
197
198            size = limits.height(Length::Fixed(height)).max();
199
200            let actual_width = size.width as usize;
201            let minimum_width = state.buttons_visible * self.minimum_button_width as usize;
202            state.collapsed = actual_width < minimum_width;
203
204            if state.collapsed {
205                state.buttons_visible =
206                    (actual_width / self.minimum_button_width as usize).min(state.buttons_visible);
207            }
208        }
209
210        if !state.collapsed {
211            state.buttons_offset = 0;
212        } else if reduce_button_offset {
213            state.buttons_offset = num - state.buttons_visible;
214        }
215
216        size
217    }
218}