cosmic/widget/segmented_button/
widget.rs

1// Copyright 2022 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use super::model::{Entity, Model, Selectable};
5use crate::iced_core::id::Internal;
6use crate::theme::{SegmentedButton as Style, THEME};
7use crate::widget::dnd_destination::DragId;
8use crate::widget::menu::{
9    self, CloseCondition, ItemHeight, ItemWidth, MenuBarState, PathHighlight, menu_roots_children,
10    menu_roots_diff,
11};
12use crate::widget::{Icon, icon};
13use crate::{Element, Renderer};
14use derive_setters::Setters;
15use iced::clipboard::dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent};
16use iced::clipboard::mime::AllowedMimeTypes;
17use iced::touch::Finger;
18use iced::{
19    Alignment, Background, Color, Event, Length, Padding, Rectangle, Size, Task, Vector, alignment,
20    event, keyboard, mouse, touch,
21};
22use iced_core::mouse::ScrollDelta;
23use iced_core::text::{LineHeight, Renderer as TextRenderer, Shaping, Wrapping};
24use iced_core::widget::{self, operation, tree};
25use iced_core::{Border, Gradient, Point, Renderer as IcedRenderer, Shadow, Text};
26use iced_core::{Clipboard, Layout, Shell, Widget, layout, renderer, widget::Tree};
27use iced_runtime::{Action, task};
28use slotmap::{Key, SecondaryMap};
29use std::borrow::Cow;
30use std::collections::HashSet;
31use std::collections::hash_map::DefaultHasher;
32use std::hash::{Hash, Hasher};
33use std::marker::PhantomData;
34use std::mem;
35use std::time::{Duration, Instant};
36
37/// A command that focuses a segmented item stored in a widget.
38pub fn focus<Message: 'static>(id: Id) -> Task<Message> {
39    task::effect(Action::Widget(Box::new(operation::focusable::focus(id.0))))
40}
41
42pub enum ItemBounds {
43    Button(Entity, Rectangle),
44    Divider(Rectangle, bool),
45}
46
47/// Isolates variant-specific behaviors from [`SegmentedButton`].
48pub trait SegmentedVariant {
49    /// Get the appearance for this variant of the widget.
50    fn variant_appearance(
51        theme: &crate::Theme,
52        style: &crate::theme::SegmentedButton,
53    ) -> super::Appearance;
54
55    /// Calculates the bounds for visible buttons.
56    fn variant_bounds<'b>(
57        &'b self,
58        state: &'b LocalState,
59        bounds: Rectangle,
60    ) -> Box<dyn Iterator<Item = ItemBounds> + 'b>;
61
62    /// Calculates the layout of this variant.
63    fn variant_layout(
64        &self,
65        state: &mut LocalState,
66        renderer: &crate::Renderer,
67        limits: &layout::Limits,
68    ) -> Size;
69}
70
71/// A conjoined group of items that function together as a button.
72#[derive(Setters)]
73#[must_use]
74pub struct SegmentedButton<'a, Variant, SelectionMode, Message>
75where
76    Model<SelectionMode>: Selectable,
77    SelectionMode: Default,
78{
79    /// The model borrowed from the application create this widget.
80    #[setters(skip)]
81    pub(super) model: &'a Model<SelectionMode>,
82    /// iced widget ID
83    pub(super) id: Id,
84    /// The icon used for the close button.
85    pub(super) close_icon: Icon,
86    /// Scrolling switches focus between tabs.
87    pub(super) scrollable_focus: bool,
88    /// Show the close icon only when item is hovered.
89    pub(super) show_close_icon_on_hover: bool,
90    /// Padding of the whole widget.
91    #[setters(into)]
92    pub(super) padding: Padding,
93    /// Whether to place dividers between buttons.
94    pub(super) dividers: bool,
95    /// Alignment of button contents.
96    pub(super) button_alignment: Alignment,
97    /// Padding around a button.
98    pub(super) button_padding: [u16; 4],
99    /// Desired height of a button.
100    pub(super) button_height: u16,
101    /// Spacing between icon and text in button.
102    pub(super) button_spacing: u16,
103    /// Maximum width of a button.
104    pub(super) maximum_button_width: u16,
105    /// Minimum width of a button.
106    pub(super) minimum_button_width: u16,
107    /// Spacing for each indent.
108    pub(super) indent_spacing: u16,
109    /// Desired font for active tabs.
110    pub(super) font_active: Option<crate::font::Font>,
111    /// Desired font for hovered tabs.
112    pub(super) font_hovered: Option<crate::font::Font>,
113    /// Desired font for inactive tabs.
114    pub(super) font_inactive: Option<crate::font::Font>,
115    /// Size of the font.
116    pub(super) font_size: f32,
117    /// Desired width of the widget.
118    pub(super) width: Length,
119    /// Desired height of the widget.
120    pub(super) height: Length,
121    /// Desired spacing between items.
122    pub(super) spacing: u16,
123    /// LineHeight of the font.
124    pub(super) line_height: LineHeight,
125    /// Style to draw the widget in.
126    #[setters(into)]
127    pub(super) style: Style,
128    /// The context menu to display when a context is activated
129    #[setters(skip)]
130    pub(super) context_menu: Option<Vec<menu::Tree<'a, Message, crate::Renderer>>>,
131    /// Emits the ID of the item that was activated.
132    #[setters(skip)]
133    pub(super) on_activate: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
134    #[setters(skip)]
135    pub(super) on_close: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
136    #[setters(skip)]
137    pub(super) on_context: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
138    #[setters(skip)]
139    pub(super) on_middle_press: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
140    #[setters(skip)]
141    pub(super) on_dnd_drop:
142        Option<Box<dyn Fn(Entity, Vec<u8>, String, DndAction) -> Message + 'static>>,
143    pub(super) mimes: Vec<String>,
144    #[setters(skip)]
145    pub(super) on_dnd_enter: Option<Box<dyn Fn(Entity, Vec<String>) -> Message + 'static>>,
146    #[setters(skip)]
147    pub(super) on_dnd_leave: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
148    #[setters(strip_option)]
149    pub(super) drag_id: Option<DragId>,
150    #[setters(skip)]
151    /// Defines the implementation of this struct
152    variant: PhantomData<Variant>,
153}
154
155impl<'a, Variant, SelectionMode, Message> SegmentedButton<'a, Variant, SelectionMode, Message>
156where
157    Self: SegmentedVariant,
158    Model<SelectionMode>: Selectable,
159    SelectionMode: Default,
160{
161    #[inline]
162    pub fn new(model: &'a Model<SelectionMode>) -> Self {
163        Self {
164            model,
165            id: Id::unique(),
166            close_icon: icon::from_name("window-close-symbolic").size(16).icon(),
167            scrollable_focus: false,
168            show_close_icon_on_hover: false,
169            button_alignment: Alignment::Start,
170            padding: Padding::from(0.0),
171            dividers: false,
172            button_padding: [0, 0, 0, 0],
173            button_height: 32,
174            button_spacing: 0,
175            minimum_button_width: u16::MIN,
176            maximum_button_width: u16::MAX,
177            indent_spacing: 16,
178            font_active: None,
179            font_hovered: None,
180            font_inactive: None,
181            font_size: 14.0,
182            height: Length::Shrink,
183            width: Length::Fill,
184            spacing: 0,
185            line_height: LineHeight::default(),
186            style: Style::default(),
187            context_menu: None,
188            on_activate: None,
189            on_close: None,
190            on_context: None,
191            on_middle_press: None,
192            on_dnd_drop: None,
193            on_dnd_enter: None,
194            on_dnd_leave: None,
195            mimes: Vec::new(),
196            variant: PhantomData,
197            drag_id: None,
198        }
199    }
200
201    pub fn context_menu(mut self, context_menu: Option<Vec<menu::Tree<'a, Message>>>) -> Self
202    where
203        Message: 'static,
204    {
205        self.context_menu = context_menu.map(|menus| {
206            vec![menu::Tree::with_children(
207                crate::widget::row::<'static, Message>(),
208                menus,
209            )]
210        });
211
212        if let Some(ref mut context_menu) = self.context_menu {
213            context_menu.iter_mut().for_each(menu::Tree::set_index);
214        }
215
216        self
217    }
218
219    /// Emitted when a tab is pressed.
220    pub fn on_activate<T>(mut self, on_activate: T) -> Self
221    where
222        T: Fn(Entity) -> Message + 'static,
223    {
224        self.on_activate = Some(Box::new(on_activate));
225        self
226    }
227
228    /// Emitted when a tab close button is pressed.
229    pub fn on_close<T>(mut self, on_close: T) -> Self
230    where
231        T: Fn(Entity) -> Message + 'static,
232    {
233        self.on_close = Some(Box::new(on_close));
234        self
235    }
236
237    /// Emitted when a button is right-clicked.
238    pub fn on_context<T>(mut self, on_context: T) -> Self
239    where
240        T: Fn(Entity) -> Message + 'static,
241    {
242        self.on_context = Some(Box::new(on_context));
243        self
244    }
245
246    /// Emitted when the middle mouse button is pressed on a button.
247    pub fn on_middle_press<T>(mut self, on_middle_press: T) -> Self
248    where
249        T: Fn(Entity) -> Message + 'static,
250    {
251        self.on_middle_press = Some(Box::new(on_middle_press));
252        self
253    }
254
255    /// Check if an item is enabled.
256    fn is_enabled(&self, key: Entity) -> bool {
257        self.model.items.get(key).map_or(false, |item| item.enabled)
258    }
259
260    /// Handle the dnd drop event.
261    pub fn on_dnd_drop<D: AllowedMimeTypes>(
262        mut self,
263        dnd_drop_handler: impl Fn(Entity, Option<D>, DndAction) -> Message + 'static,
264    ) -> Self {
265        self.on_dnd_drop = Some(Box::new(move |entity, data, mime, action| {
266            dnd_drop_handler(entity, D::try_from((data, mime)).ok(), action)
267        }));
268        self.mimes = D::allowed().iter().cloned().collect();
269        self
270    }
271
272    /// Handle the dnd enter event.
273    pub fn on_dnd_enter(
274        mut self,
275        dnd_enter_handler: impl Fn(Entity, Vec<String>) -> Message + 'static,
276    ) -> Self {
277        self.on_dnd_enter = Some(Box::new(dnd_enter_handler));
278        self
279    }
280
281    /// Handle the dnd leave event.
282    pub fn on_dnd_leave(mut self, dnd_leave_handler: impl Fn(Entity) -> Message + 'static) -> Self {
283        self.on_dnd_leave = Some(Box::new(dnd_leave_handler));
284        self
285    }
286
287    /// Item the previous item in the widget.
288    fn focus_previous(&mut self, state: &mut LocalState) -> event::Status {
289        match state.focused_item {
290            Item::Tab(entity) => {
291                let mut keys = self.iterate_visible_tabs(state).rev();
292
293                while let Some(key) = keys.next() {
294                    if key == entity {
295                        for key in keys {
296                            // Skip disabled buttons.
297                            if !self.is_enabled(key) {
298                                continue;
299                            }
300
301                            state.focused_item = Item::Tab(key);
302                            return event::Status::Captured;
303                        }
304
305                        break;
306                    }
307                }
308
309                if self.prev_tab_sensitive(state) {
310                    state.focused_item = Item::PrevButton;
311                    return event::Status::Captured;
312                }
313            }
314
315            Item::NextButton => {
316                if let Some(last) = self.last_tab(state) {
317                    state.focused_item = Item::Tab(last);
318                    return event::Status::Captured;
319                }
320            }
321
322            Item::None => {
323                if self.next_tab_sensitive(state) {
324                    state.focused_item = Item::NextButton;
325                    return event::Status::Captured;
326                } else if let Some(last) = self.last_tab(state) {
327                    state.focused_item = Item::Tab(last);
328                    return event::Status::Captured;
329                }
330            }
331
332            Item::PrevButton | Item::Set => (),
333        }
334
335        state.focused_item = Item::None;
336        event::Status::Ignored
337    }
338
339    /// Item the next item in the widget.
340    fn focus_next(&mut self, state: &mut LocalState) -> event::Status {
341        match state.focused_item {
342            Item::Tab(entity) => {
343                let mut keys = self.iterate_visible_tabs(state);
344                while let Some(key) = keys.next() {
345                    if key == entity {
346                        for key in keys {
347                            // Skip disabled buttons.
348                            if !self.is_enabled(key) {
349                                continue;
350                            }
351
352                            state.focused_item = Item::Tab(key);
353                            return event::Status::Captured;
354                        }
355
356                        break;
357                    }
358                }
359
360                if self.next_tab_sensitive(state) {
361                    state.focused_item = Item::NextButton;
362                    return event::Status::Captured;
363                }
364            }
365
366            Item::PrevButton => {
367                if let Some(first) = self.first_tab(state) {
368                    state.focused_item = Item::Tab(first);
369                    return event::Status::Captured;
370                }
371            }
372
373            Item::None => {
374                if self.prev_tab_sensitive(state) {
375                    state.focused_item = Item::PrevButton;
376                    return event::Status::Captured;
377                } else if let Some(first) = self.first_tab(state) {
378                    state.focused_item = Item::Tab(first);
379                    return event::Status::Captured;
380                }
381            }
382
383            Item::NextButton | Item::Set => (),
384        }
385
386        state.focused_item = Item::None;
387        event::Status::Ignored
388    }
389
390    fn iterate_visible_tabs<'b>(
391        &'b self,
392        state: &LocalState,
393    ) -> impl DoubleEndedIterator<Item = Entity> + 'b {
394        self.model
395            .order
396            .iter()
397            .copied()
398            .skip(state.buttons_offset)
399            .take(state.buttons_visible)
400    }
401
402    fn first_tab(&self, state: &LocalState) -> Option<Entity> {
403        self.model.order.get(state.buttons_offset).copied()
404    }
405
406    fn last_tab(&self, state: &LocalState) -> Option<Entity> {
407        self.model
408            .order
409            .get(state.buttons_offset + state.buttons_visible)
410            .copied()
411    }
412
413    #[allow(clippy::unused_self)]
414    fn prev_tab_sensitive(&self, state: &LocalState) -> bool {
415        state.buttons_offset > 0
416    }
417
418    fn next_tab_sensitive(&self, state: &LocalState) -> bool {
419        state.buttons_offset < self.model.order.len() - state.buttons_visible
420    }
421
422    pub(super) fn button_dimensions(
423        &self,
424        state: &mut LocalState,
425        font: crate::font::Font,
426        button: Entity,
427    ) -> (f32, f32) {
428        let mut width = 0.0f32;
429        let mut icon_spacing = 0.0f32;
430
431        // Add text to measurement if text was given.
432        if let Some((text, entry)) = self
433            .model
434            .text
435            .get(button)
436            .zip(state.paragraphs.entry(button))
437        {
438            if !text.is_empty() {
439                icon_spacing = f32::from(self.button_spacing);
440                let paragraph = entry.or_insert_with(|| {
441                    crate::Plain::new(Text {
442                        content: text.as_ref(),
443                        size: iced::Pixels(self.font_size),
444                        bounds: Size::INFINITY,
445                        font,
446                        horizontal_alignment: alignment::Horizontal::Left,
447                        vertical_alignment: alignment::Vertical::Center,
448                        shaping: Shaping::Advanced,
449                        wrapping: Wrapping::default(),
450                        line_height: self.line_height,
451                    })
452                });
453
454                let size = paragraph.min_bounds();
455                width += size.width;
456            }
457        }
458
459        // Add indent to measurement if found.
460        if let Some(indent) = self.model.indent(button) {
461            width = f32::from(indent).mul_add(f32::from(self.indent_spacing), width);
462        }
463
464        // Add icon to measurement if icon was given.
465        if let Some(icon) = self.model.icon(button) {
466            width += f32::from(icon.size) + icon_spacing;
467        } else if self.model.is_active(button) {
468            // Add selection icon measurements when widget is a selection widget.
469            if let crate::theme::SegmentedButton::Control = self.style {
470                width += 16.0 + icon_spacing;
471            }
472        }
473
474        // Add close button to measurement if found.
475        if self.model.is_closable(button) {
476            width += f32::from(self.close_icon.size) + f32::from(self.button_spacing);
477        }
478
479        // Add button padding to the max size found
480        width += f32::from(self.button_padding[0]) + f32::from(self.button_padding[2]);
481        width = width.min(f32::from(self.maximum_button_width));
482
483        (width, f32::from(self.button_height))
484    }
485
486    pub(super) fn max_button_dimensions(
487        &self,
488        state: &mut LocalState,
489        renderer: &Renderer,
490    ) -> (f32, f32) {
491        let mut width = 0.0f32;
492        let mut height = 0.0f32;
493        let font = renderer.default_font();
494
495        for key in self.model.order.iter().copied() {
496            let (button_width, button_height) = self.button_dimensions(state, font, key);
497
498            state.internal_layout.push((
499                Size::new(button_width, button_height),
500                Size::new(
501                    button_width
502                        - f32::from(self.button_padding[0])
503                        - f32::from(self.button_padding[2]),
504                    button_height,
505                ),
506            ));
507
508            height = height.max(button_height);
509            width = width.max(button_width);
510        }
511
512        for (size, actual) in &mut state.internal_layout {
513            size.height = height;
514            actual.height = height;
515        }
516
517        (width, height)
518    }
519
520    fn button_is_focused(&self, state: &LocalState, key: Entity) -> bool {
521        self.on_activate.is_some() && Item::Tab(key) == state.focused_item
522    }
523
524    fn button_is_hovered(&self, state: &LocalState, key: Entity) -> bool {
525        self.on_activate.is_some() && state.hovered == Item::Tab(key)
526            || state
527                .dnd_state
528                .drag_offer
529                .as_ref()
530                .is_some_and(|id| id.data.is_some_and(|d| d == key))
531    }
532
533    /// Returns the drag id of the destination.
534    ///
535    /// # Panics
536    /// Panics if the destination has been assigned a Set id, which is invalid.
537    #[must_use]
538    pub fn get_drag_id(&self) -> u128 {
539        self.drag_id.map_or_else(
540            || {
541                u128::from(match &self.id.0.0 {
542                    Internal::Unique(id) | Internal::Custom(id, _) => *id,
543                    Internal::Set(_) => panic!("Invalid Id assigned to dnd destination."),
544                })
545            },
546            |id| id.0,
547        )
548    }
549}
550
551impl<Variant, SelectionMode, Message> Widget<Message, crate::Theme, Renderer>
552    for SegmentedButton<'_, Variant, SelectionMode, Message>
553where
554    Self: SegmentedVariant,
555    Model<SelectionMode>: Selectable,
556    SelectionMode: Default,
557    Message: 'static + Clone,
558{
559    fn children(&self) -> Vec<Tree> {
560        let mut children = Vec::new();
561
562        // Assign the context menu's elements as this widget's children.
563        if let Some(ref context_menu) = self.context_menu {
564            let mut tree = Tree::empty();
565            tree.state = tree::State::new(MenuBarState::default());
566            tree.children = menu_roots_children(context_menu);
567            children.push(tree);
568        }
569
570        children
571    }
572
573    fn tag(&self) -> tree::Tag {
574        tree::Tag::of::<LocalState>()
575    }
576
577    fn state(&self) -> tree::State {
578        #[allow(clippy::default_trait_access)]
579        tree::State::new(LocalState {
580            paragraphs: SecondaryMap::new(),
581            text_hashes: SecondaryMap::new(),
582            buttons_visible: Default::default(),
583            buttons_offset: Default::default(),
584            collapsed: Default::default(),
585            focused: Default::default(),
586            focused_item: Default::default(),
587            hovered: Default::default(),
588            known_length: Default::default(),
589            middle_clicked: Default::default(),
590            internal_layout: Default::default(),
591            context_cursor: Point::default(),
592            show_context: Default::default(),
593            wheel_timestamp: Default::default(),
594            dnd_state: Default::default(),
595            fingers_pressed: Default::default(),
596            pressed_item: None,
597        })
598    }
599
600    fn diff(&mut self, tree: &mut Tree) {
601        let state = tree.state.downcast_mut::<LocalState>();
602
603        for key in self.model.order.iter().copied() {
604            if let Some(text) = self.model.text.get(key) {
605                let (font, button_state) =
606                    if self.model.is_active(key) || self.button_is_focused(state, key) {
607                        (self.font_active, 0)
608                    } else if self.button_is_hovered(state, key) {
609                        (self.font_hovered, 1)
610                    } else {
611                        (self.font_inactive, 2)
612                    };
613
614                let font = font.unwrap_or_else(crate::font::default);
615                let mut hasher = DefaultHasher::new();
616                text.hash(&mut hasher);
617                font.hash(&mut hasher);
618                button_state.hash(&mut hasher);
619                let text_hash = hasher.finish();
620
621                if let Some(prev_hash) = state.text_hashes.insert(key, text_hash) {
622                    if prev_hash == text_hash {
623                        continue;
624                    }
625                }
626
627                let text = Text {
628                    content: text.as_ref(),
629                    size: iced::Pixels(self.font_size),
630                    bounds: Size::INFINITY,
631                    font,
632                    horizontal_alignment: alignment::Horizontal::Left,
633                    vertical_alignment: alignment::Vertical::Center,
634                    shaping: Shaping::Advanced,
635                    wrapping: Wrapping::default(),
636                    line_height: self.line_height,
637                };
638
639                if let Some(paragraph) = state.paragraphs.get_mut(key) {
640                    paragraph.update(text);
641                } else {
642                    state.paragraphs.insert(key, crate::Plain::new(text));
643                }
644            }
645        }
646
647        // Diff the context menu
648        if let Some(context_menu) = &mut self.context_menu {
649            if tree.children.is_empty() {
650                let mut child_tree = Tree::empty();
651                child_tree.state = tree::State::new(MenuBarState::default());
652                tree.children.push(child_tree);
653            } else {
654                tree.children.truncate(1);
655            }
656            menu_roots_diff(context_menu, &mut tree.children[0]);
657        } else {
658            tree.children.clear();
659        }
660    }
661
662    fn size(&self) -> Size<Length> {
663        Size::new(self.width, self.height)
664    }
665
666    fn layout(
667        &self,
668        tree: &mut Tree,
669        renderer: &Renderer,
670        limits: &layout::Limits,
671    ) -> layout::Node {
672        let state = tree.state.downcast_mut::<LocalState>();
673        let limits = limits.shrink(self.padding);
674        let size = self
675            .variant_layout(state, renderer, &limits)
676            .expand(self.padding);
677        layout::Node::new(size)
678    }
679
680    #[allow(clippy::too_many_lines)]
681    fn on_event(
682        &mut self,
683        tree: &mut Tree,
684        mut event: Event,
685        layout: Layout<'_>,
686        cursor_position: mouse::Cursor,
687        _renderer: &Renderer,
688        _clipboard: &mut dyn Clipboard,
689        shell: &mut Shell<'_, Message>,
690        _viewport: &iced::Rectangle,
691    ) -> event::Status {
692        let bounds = layout.bounds();
693        let state = tree.state.downcast_mut::<LocalState>();
694        state.hovered = Item::None;
695
696        let my_id = self.get_drag_id();
697
698        if let Event::Dnd(e) = &mut event {
699            let entity = state
700                .dnd_state
701                .drag_offer
702                .as_ref()
703                .map(|dnd_state| dnd_state.data);
704            match e {
705                DndEvent::Offer(
706                    id,
707                    OfferEvent::Enter {
708                        x, y, mime_types, ..
709                    },
710                ) if Some(my_id) == *id => {
711                    let entity = self
712                        .variant_bounds(state, bounds)
713                        .filter_map(|item| match item {
714                            ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
715                            _ => None,
716                        })
717                        .find(|(_key, bounds)| bounds.contains(Point::new(*x as f32, *y as f32)))
718                        .map(|(key, _)| key);
719
720                    let on_dnd_enter =
721                        self.on_dnd_enter
722                            .as_ref()
723                            .zip(entity)
724                            .map(|(on_enter, entity)| {
725                                move |_, _, mime_types| on_enter(entity, mime_types)
726                            });
727
728                    _ = state.dnd_state.on_enter::<Message>(
729                        *x,
730                        *y,
731                        mime_types.clone(),
732                        on_dnd_enter,
733                        entity,
734                    );
735                }
736                DndEvent::Offer(id, OfferEvent::Leave | OfferEvent::LeaveDestination)
737                    if Some(my_id) == *id =>
738                {
739                    if let Some(Some(entity)) = entity {
740                        if let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() {
741                            shell.publish(on_dnd_leave(entity));
742                        }
743                    }
744                    _ = state.dnd_state.on_leave::<Message>(None);
745                }
746                DndEvent::Offer(id, OfferEvent::Motion { x, y }) if Some(my_id) == *id => {
747                    let new = self
748                        .variant_bounds(state, bounds)
749                        .filter_map(|item| match item {
750                            ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
751                            _ => None,
752                        })
753                        .find(|(_key, bounds)| bounds.contains(Point::new(*x as f32, *y as f32)))
754                        .map(|(key, _)| key);
755                    if let Some(new_entity) = new {
756                        state.dnd_state.on_motion::<Message>(
757                            *x,
758                            *y,
759                            None::<fn(_, _) -> Message>,
760                            None::<fn(_, _, _) -> Message>,
761                            Some(new_entity),
762                        );
763                        if Some(Some(new_entity)) != entity {
764                            let prev_action = state
765                                .dnd_state
766                                .drag_offer
767                                .as_ref()
768                                .map(|dnd| dnd.selected_action);
769                            if let Some(on_dnd_enter) = self.on_dnd_enter.as_ref() {
770                                shell.publish(on_dnd_enter(new_entity, Vec::new()));
771                            }
772                            if let Some(dnd) = state.dnd_state.drag_offer.as_mut() {
773                                dnd.data = Some(new_entity);
774                                if let Some(prev_action) = prev_action {
775                                    dnd.selected_action = prev_action;
776                                }
777                            }
778                        }
779                    } else if entity.is_some() {
780                        state.dnd_state.on_motion::<Message>(
781                            *x,
782                            *y,
783                            None::<fn(_, _) -> Message>,
784                            None::<fn(_, _, _) -> Message>,
785                            None,
786                        );
787                        if let Some(on_dnd_leave) = self.on_dnd_leave.as_ref() {
788                            if let Some(Some(entity)) = entity {
789                                shell.publish(on_dnd_leave(entity));
790                            }
791                        }
792                    }
793                }
794                DndEvent::Offer(id, OfferEvent::Drop) if Some(my_id) == *id => {
795                    _ = state
796                        .dnd_state
797                        .on_drop::<Message>(None::<fn(_, _) -> Message>);
798                }
799                DndEvent::Offer(id, OfferEvent::SelectedAction(action)) if Some(my_id) == *id => {
800                    if state.dnd_state.drag_offer.is_some() {
801                        _ = state
802                            .dnd_state
803                            .on_action_selected::<Message>(*action, None::<fn(_) -> Message>);
804                    }
805                }
806                DndEvent::Offer(id, OfferEvent::Data { data, mime_type }) if Some(my_id) == *id => {
807                    if let Some(Some(entity)) = entity {
808                        let on_drop = self.on_dnd_drop.as_ref();
809                        let on_drop = on_drop.map(|on_drop| {
810                            |mime, data, action, _, _| on_drop(entity, data, mime, action)
811                        });
812
813                        if let (Some(msg), ret) = state.dnd_state.on_data_received(
814                            mem::take(mime_type),
815                            mem::take(data),
816                            None::<fn(_, _) -> Message>,
817                            on_drop,
818                        ) {
819                            shell.publish(msg);
820                            return ret;
821                        }
822                    }
823                }
824                _ => {}
825            }
826        }
827
828        if cursor_position.is_over(bounds) {
829            let fingers_pressed = state.fingers_pressed.len();
830
831            match event {
832                Event::Touch(touch::Event::FingerPressed { id, .. }) => {
833                    state.fingers_pressed.insert(id);
834                }
835
836                Event::Touch(touch::Event::FingerLifted { id, .. }) => {
837                    state.fingers_pressed.remove(&id);
838                }
839
840                _ => (),
841            }
842
843            // Check for clicks on the previous and next tab buttons, when tabs are collapsed.
844            if state.collapsed {
845                // Check if the prev tab button was clicked.
846                if cursor_position.is_over(prev_tab_bounds(&bounds, f32::from(self.button_height)))
847                    && self.prev_tab_sensitive(state)
848                {
849                    state.hovered = Item::PrevButton;
850                    if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
851                    | Event::Touch(touch::Event::FingerLifted { .. }) = event
852                    {
853                        state.buttons_offset -= 1;
854                    }
855                } else {
856                    // Check if the next tab button was clicked.
857                    if cursor_position
858                        .is_over(next_tab_bounds(&bounds, f32::from(self.button_height)))
859                        && self.next_tab_sensitive(state)
860                    {
861                        state.hovered = Item::NextButton;
862
863                        if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
864                        | Event::Touch(touch::Event::FingerLifted { .. }) = event
865                        {
866                            state.buttons_offset += 1;
867                        }
868                    }
869                }
870            }
871
872            for (key, bounds) in self
873                .variant_bounds(state, bounds)
874                .filter_map(|item| match item {
875                    ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
876                    _ => None,
877                })
878                .collect::<Vec<_>>()
879            {
880                if cursor_position.is_over(bounds) {
881                    if self.model.items[key].enabled {
882                        // Record that the mouse is hovering over this button.
883                        state.hovered = Item::Tab(key);
884
885                        // If marked as closable, show a close icon.
886                        if self.model.items[key].closable {
887                            // Emit close message if the close button is pressed.
888                            if let Some(on_close) = self.on_close.as_ref() {
889                                if cursor_position
890                                    .is_over(close_bounds(bounds, f32::from(self.close_icon.size)))
891                                    && (left_button_released(&event)
892                                        || (touch_lifted(&event) && fingers_pressed == 1))
893                                {
894                                    shell.publish(on_close(key));
895                                    return event::Status::Captured;
896                                }
897
898                                if self.on_middle_press.is_none() {
899                                    // Emit close message if the tab is middle clicked.
900                                    if let Event::Mouse(mouse::Event::ButtonReleased(
901                                        mouse::Button::Middle,
902                                    )) = event
903                                    {
904                                        if state.middle_clicked == Some(Item::Tab(key)) {
905                                            shell.publish(on_close(key));
906                                            return event::Status::Captured;
907                                        }
908
909                                        state.middle_clicked = None;
910                                    }
911                                }
912                            }
913                        }
914
915                        if let Event::Mouse(mouse::Event::ButtonReleased(_))
916                        | Event::Touch(touch::Event::FingerLifted { .. }) = event
917                        {
918                            state.focused = false;
919                            state.focused_item = Item::None;
920                        }
921
922                        if let Some(on_activate) = self.on_activate.as_ref() {
923                            if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
924                            | Event::Touch(touch::Event::FingerPressed { .. }) = event
925                            {
926                                state.pressed_item = Some(Item::Tab(key));
927                            } else if let Event::Mouse(mouse::Event::ButtonReleased(
928                                mouse::Button::Left,
929                            ))
930                            | Event::Touch(touch::Event::FingerLifted { .. }) = event
931                            {
932                                let mut can_activate = true;
933                                if state.pressed_item != Some(Item::Tab(key)) {
934                                    can_activate = false;
935                                }
936
937                                if can_activate {
938                                    shell.publish(on_activate(key));
939                                    state.focused = true;
940                                    state.focused_item = Item::Tab(key);
941                                    state.pressed_item = None;
942                                    return event::Status::Captured;
943                                }
944                            }
945                        }
946
947                        // Present a context menu on a right click event.
948                        if self.context_menu.is_some() {
949                            if let Some(on_context) = self.on_context.as_ref() {
950                                if right_button_released(&event)
951                                    || (touch_lifted(&event) && fingers_pressed == 2)
952                                {
953                                    state.show_context = Some(key);
954                                    state.context_cursor =
955                                        cursor_position.position().unwrap_or_default();
956
957                                    let menu_state =
958                                        tree.children[0].state.downcast_mut::<MenuBarState>();
959                                    menu_state.open = true;
960                                    menu_state.view_cursor = cursor_position;
961
962                                    shell.publish(on_context(key));
963                                    return event::Status::Captured;
964                                }
965                            }
966                        }
967
968                        if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) =
969                            event
970                        {
971                            state.middle_clicked = Some(Item::Tab(key));
972                            if let Some(on_middle_press) = self.on_middle_press.as_ref() {
973                                shell.publish(on_middle_press(key));
974                                return event::Status::Captured;
975                            }
976                        }
977                    }
978
979                    break;
980                }
981            }
982
983            if self.scrollable_focus {
984                if let Some(on_activate) = self.on_activate.as_ref() {
985                    if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event {
986                        let current = Instant::now();
987
988                        // Permit successive scroll wheel events only after a given delay.
989                        if state.wheel_timestamp.map_or(true, |previous| {
990                            current.duration_since(previous) > Duration::from_millis(250)
991                        }) {
992                            state.wheel_timestamp = Some(current);
993
994                            match delta {
995                                ScrollDelta::Lines { y, .. } | ScrollDelta::Pixels { y, .. } => {
996                                    let mut activate_key = None;
997
998                                    if y < 0.0 {
999                                        let mut prev_key = Entity::null();
1000
1001                                        for key in self.model.order.iter().copied() {
1002                                            if self.model.is_active(key) && !prev_key.is_null() {
1003                                                activate_key = Some(prev_key);
1004                                            }
1005
1006                                            if self.model.is_enabled(key) {
1007                                                prev_key = key;
1008                                            }
1009                                        }
1010                                    } else if y > 0.0 {
1011                                        let mut buttons = self.model.order.iter().copied();
1012                                        while let Some(key) = buttons.next() {
1013                                            if self.model.is_active(key) {
1014                                                for key in buttons {
1015                                                    if self.model.is_enabled(key) {
1016                                                        activate_key = Some(key);
1017                                                        break;
1018                                                    }
1019                                                }
1020                                                break;
1021                                            }
1022                                        }
1023                                    }
1024
1025                                    if let Some(key) = activate_key {
1026                                        shell.publish(on_activate(key));
1027                                        state.focused = true;
1028                                        state.focused_item = Item::Tab(key);
1029                                        return event::Status::Captured;
1030                                    }
1031                                }
1032                            }
1033                        }
1034                    }
1035                }
1036            }
1037        }
1038
1039        if state.focused {
1040            if let Event::Keyboard(keyboard::Event::KeyPressed {
1041                key: keyboard::Key::Named(keyboard::key::Named::Tab),
1042                modifiers,
1043                ..
1044            }) = event
1045            {
1046                return if modifiers.shift() {
1047                    self.focus_previous(state)
1048                } else {
1049                    self.focus_next(state)
1050                };
1051            }
1052
1053            if let Some(on_activate) = self.on_activate.as_ref() {
1054                if let Event::Keyboard(keyboard::Event::KeyReleased {
1055                    key: keyboard::Key::Named(keyboard::key::Named::Enter),
1056                    ..
1057                }) = event
1058                {
1059                    match state.focused_item {
1060                        Item::Tab(entity) => {
1061                            shell.publish(on_activate(entity));
1062                        }
1063
1064                        Item::PrevButton => {
1065                            if self.prev_tab_sensitive(state) {
1066                                state.buttons_offset -= 1;
1067
1068                                // If the change would cause it to be insensitive, focus the first tab.
1069                                if !self.prev_tab_sensitive(state) {
1070                                    if let Some(first) = self.first_tab(state) {
1071                                        state.focused_item = Item::Tab(first);
1072                                    }
1073                                }
1074                            }
1075                        }
1076
1077                        Item::NextButton => {
1078                            if self.next_tab_sensitive(state) {
1079                                state.buttons_offset += 1;
1080
1081                                // If the change would cause it to be insensitive, focus the last tab.
1082                                if !self.next_tab_sensitive(state) {
1083                                    if let Some(last) = self.last_tab(state) {
1084                                        state.focused_item = Item::Tab(last);
1085                                    }
1086                                }
1087                            }
1088                        }
1089
1090                        Item::None | Item::Set => (),
1091                    }
1092
1093                    return event::Status::Captured;
1094                }
1095            }
1096        }
1097
1098        event::Status::Ignored
1099    }
1100
1101    fn operate(
1102        &self,
1103        tree: &mut Tree,
1104        _layout: Layout<'_>,
1105        _renderer: &Renderer,
1106        operation: &mut dyn iced_core::widget::Operation<()>,
1107    ) {
1108        let state = tree.state.downcast_mut::<LocalState>();
1109        operation.focusable(state, Some(&self.id.0));
1110
1111        if let Item::Set = state.focused_item {
1112            if self.prev_tab_sensitive(state) {
1113                state.focused_item = Item::PrevButton;
1114            } else if let Some(first) = self.first_tab(state) {
1115                state.focused_item = Item::Tab(first);
1116            }
1117        }
1118    }
1119
1120    fn mouse_interaction(
1121        &self,
1122        tree: &Tree,
1123        layout: Layout<'_>,
1124        cursor_position: mouse::Cursor,
1125        _viewport: &iced::Rectangle,
1126        _renderer: &Renderer,
1127    ) -> iced_core::mouse::Interaction {
1128        if self.on_activate.is_none() {
1129            return iced_core::mouse::Interaction::default();
1130        }
1131        let state = tree.state.downcast_ref::<LocalState>();
1132        let bounds = layout.bounds();
1133
1134        if cursor_position.is_over(bounds) {
1135            let hovered_button = self
1136                .variant_bounds(state, bounds)
1137                .filter_map(|item| match item {
1138                    ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
1139                    _ => None,
1140                })
1141                .find(|(_key, bounds)| cursor_position.is_over(*bounds));
1142
1143            if let Some((key, _bounds)) = hovered_button {
1144                return if self.model.items[key].enabled {
1145                    iced_core::mouse::Interaction::Pointer
1146                } else {
1147                    iced_core::mouse::Interaction::Idle
1148                };
1149            }
1150        }
1151
1152        iced_core::mouse::Interaction::Idle
1153    }
1154
1155    #[allow(clippy::too_many_lines)]
1156    fn draw(
1157        &self,
1158        tree: &Tree,
1159        renderer: &mut Renderer,
1160        theme: &crate::Theme,
1161        style: &renderer::Style,
1162        layout: Layout<'_>,
1163        cursor: mouse::Cursor,
1164        viewport: &iced::Rectangle,
1165    ) {
1166        let state = tree.state.downcast_ref::<LocalState>();
1167        let appearance = Self::variant_appearance(theme, &self.style);
1168        let bounds: Rectangle = layout.bounds();
1169        let button_amount = self.model.items.len();
1170
1171        // Modifies alpha color when `on_activate` is unset.
1172        let apply_alpha = |mut c: Color| {
1173            if self.on_activate.is_none() {
1174                c.a /= 2.0;
1175            }
1176
1177            c
1178        };
1179
1180        // Maps `apply_alpha` to background color.
1181        let bg_with_alpha = |mut b| {
1182            match &mut b {
1183                Background::Color(c) => {
1184                    *c = apply_alpha(*c);
1185                }
1186
1187                Background::Gradient(g) => {
1188                    let Gradient::Linear(l) = g;
1189                    for c in &mut l.stops {
1190                        let Some(stop) = c else {
1191                            continue;
1192                        };
1193                        stop.color = apply_alpha(stop.color);
1194                    }
1195                }
1196            }
1197            b
1198        };
1199
1200        // Draw the background, if a background was defined.
1201        if let Some(background) = appearance.background {
1202            renderer.fill_quad(
1203                renderer::Quad {
1204                    bounds,
1205                    border: Border {
1206                        radius: appearance.border_radius,
1207                        ..Border::default()
1208                    },
1209                    shadow: Shadow::default(),
1210                },
1211                bg_with_alpha(background),
1212            );
1213        }
1214
1215        // Draw previous and next tab buttons if there is a need to paginate tabs.
1216        if state.collapsed {
1217            let mut tab_bounds = prev_tab_bounds(&bounds, f32::from(self.button_height));
1218
1219            // Previous tab button
1220            let mut background_appearance =
1221                if self.on_activate.is_some() && Item::PrevButton == state.focused_item {
1222                    Some(appearance.focus)
1223                } else if self.on_activate.is_some() && Item::PrevButton == state.hovered {
1224                    Some(appearance.hover)
1225                } else {
1226                    None
1227                };
1228
1229            if let Some(background_appearance) = background_appearance.take() {
1230                renderer.fill_quad(
1231                    renderer::Quad {
1232                        bounds: tab_bounds,
1233                        border: Border {
1234                            radius: theme.cosmic().radius_s().into(),
1235                            ..Default::default()
1236                        },
1237                        shadow: Shadow::default(),
1238                    },
1239                    background_appearance
1240                        .background
1241                        .map_or(Background::Color(Color::TRANSPARENT), bg_with_alpha),
1242                );
1243            }
1244
1245            draw_icon::<Message>(
1246                renderer,
1247                theme,
1248                style,
1249                cursor,
1250                viewport,
1251                apply_alpha(if state.buttons_offset == 0 {
1252                    appearance.inactive.text_color
1253                } else if let Item::PrevButton = state.focused_item {
1254                    appearance.focus.text_color
1255                } else {
1256                    appearance.active.text_color
1257                }),
1258                Rectangle {
1259                    x: tab_bounds.x + 8.0,
1260                    y: tab_bounds.y + f32::from(self.button_height) / 4.0,
1261                    width: 16.0,
1262                    height: 16.0,
1263                },
1264                icon::from_name("go-previous-symbolic").size(16).icon(),
1265            );
1266
1267            tab_bounds = next_tab_bounds(&bounds, f32::from(self.button_height));
1268
1269            // Next tab button
1270            background_appearance =
1271                if self.on_activate.is_some() && Item::NextButton == state.focused_item {
1272                    Some(appearance.focus)
1273                } else if self.on_activate.is_some() && Item::NextButton == state.hovered {
1274                    Some(appearance.hover)
1275                } else {
1276                    None
1277                };
1278
1279            if let Some(background_appearance) = background_appearance {
1280                renderer.fill_quad(
1281                    renderer::Quad {
1282                        bounds: tab_bounds,
1283                        border: Border {
1284                            radius: theme.cosmic().radius_s().into(),
1285                            ..Default::default()
1286                        },
1287                        shadow: Shadow::default(),
1288                    },
1289                    background_appearance
1290                        .background
1291                        .unwrap_or(Background::Color(Color::TRANSPARENT)),
1292                );
1293            }
1294
1295            draw_icon::<Message>(
1296                renderer,
1297                theme,
1298                style,
1299                cursor,
1300                viewport,
1301                apply_alpha(if self.next_tab_sensitive(state) {
1302                    appearance.active.text_color
1303                } else if let Item::NextButton = state.focused_item {
1304                    appearance.focus.text_color
1305                } else {
1306                    appearance.inactive.text_color
1307                }),
1308                Rectangle {
1309                    x: tab_bounds.x + 8.0,
1310                    y: tab_bounds.y + f32::from(self.button_height) / 4.0,
1311                    width: 16.0,
1312                    height: 16.0,
1313                },
1314                icon::from_name("go-next-symbolic").size(16).icon(),
1315            );
1316        }
1317
1318        // Draw each of the items in the widget.
1319        let mut nth = 0;
1320        self.variant_bounds(state, bounds).for_each(move |item| {
1321            let (key, mut bounds) = match item {
1322                // Draw a button
1323                ItemBounds::Button(entity, bounds) => (entity, bounds),
1324
1325                // Draw a divider between buttons
1326                ItemBounds::Divider(bounds, accented) => {
1327                    renderer.fill_quad(
1328                        renderer::Quad {
1329                            bounds,
1330                            border: Border::default(),
1331                            shadow: Shadow::default(),
1332                        },
1333                        {
1334                            let theme = crate::theme::active();
1335                            if accented {
1336                                Background::Color(theme.cosmic().small_widget_divider().into())
1337                            } else {
1338                                Background::Color(theme.cosmic().primary_container_divider().into())
1339                            }
1340                        },
1341                    );
1342
1343                    return;
1344                }
1345            };
1346
1347            let center_y = bounds.center_y();
1348
1349            let menu_open = !tree.children.is_empty()
1350                && tree.children[0].state.downcast_ref::<MenuBarState>().open;
1351
1352            let key_is_active = self.model.is_active(key);
1353            let key_is_hovered = self.button_is_hovered(state, key);
1354            let key_has_context_menu_open = menu_open && state.show_context == Some(key);
1355            let status_appearance = if self.button_is_focused(state, key) {
1356                appearance.focus
1357            } else if key_is_active {
1358                appearance.active
1359            } else if key_is_hovered || key_has_context_menu_open {
1360                appearance.hover
1361            } else {
1362                appearance.inactive
1363            };
1364
1365            let button_appearance = if nth == 0 {
1366                status_appearance.first
1367            } else if nth + 1 == button_amount {
1368                status_appearance.last
1369            } else {
1370                status_appearance.middle
1371            };
1372
1373            // Render the background of the button.
1374            if status_appearance.background.is_some() {
1375                renderer.fill_quad(
1376                    renderer::Quad {
1377                        bounds,
1378                        border: Border {
1379                            radius: button_appearance.border_radius,
1380                            ..Default::default()
1381                        },
1382                        shadow: Shadow::default(),
1383                    },
1384                    status_appearance
1385                        .background
1386                        .map_or(Background::Color(Color::TRANSPARENT), bg_with_alpha),
1387                );
1388            }
1389
1390            // Draw the bottom border defined for this button.
1391            if let Some((width, background)) = button_appearance.border_bottom {
1392                let mut bounds = bounds;
1393                bounds.y = bounds.y + bounds.height - width;
1394                bounds.height = width;
1395
1396                let rad_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0;
1397                renderer.fill_quad(
1398                    renderer::Quad {
1399                        bounds,
1400                        border: Border {
1401                            radius: rad_0.into(),
1402                            ..Default::default()
1403                        },
1404                        shadow: Shadow::default(),
1405                    },
1406                    bg_with_alpha(background.into()),
1407                );
1408            }
1409
1410            let original_bounds = bounds;
1411
1412            bounds.x += f32::from(self.button_padding[0]);
1413            bounds.width -= f32::from(self.button_padding[0]) - f32::from(self.button_padding[2]);
1414
1415            // Adjust bounds by indent
1416            if let Some(indent) = self.model.indent(key) {
1417                let adjustment = f32::from(indent) * f32::from(self.indent_spacing);
1418                bounds.x += adjustment;
1419                bounds.width -= adjustment;
1420            }
1421
1422            // Align contents of the button to the requested `button_alignment`.
1423            {
1424                let actual_width = state.internal_layout[nth].1.width;
1425
1426                let offset = match self.button_alignment {
1427                    Alignment::Start => None,
1428                    Alignment::Center => Some((bounds.width - actual_width) / 2.0),
1429                    Alignment::End => Some(bounds.width - actual_width),
1430                };
1431
1432                if let Some(offset) = offset {
1433                    bounds.x += offset - f32::from(self.button_padding[0]);
1434                    bounds.width = actual_width;
1435                }
1436            }
1437
1438            // Draw the image beside the text.
1439            if let Some(icon) = self.model.icon(key) {
1440                let mut image_bounds = bounds;
1441                let width = f32::from(icon.size);
1442                let offset = width + f32::from(self.button_spacing);
1443                image_bounds.y = center_y - width / 2.0;
1444
1445                draw_icon::<Message>(
1446                    renderer,
1447                    theme,
1448                    style,
1449                    cursor,
1450                    viewport,
1451                    apply_alpha(status_appearance.text_color),
1452                    Rectangle {
1453                        width,
1454                        height: width,
1455                        ..image_bounds
1456                    },
1457                    icon.clone(),
1458                );
1459
1460                bounds.x += offset;
1461            } else {
1462                // Draw the selection indicator if widget is a segmented selection, and the item is selected.
1463                if key_is_active {
1464                    if let crate::theme::SegmentedButton::Control = self.style {
1465                        let mut image_bounds = bounds;
1466                        image_bounds.y = center_y - 16.0 / 2.0;
1467
1468                        draw_icon::<Message>(
1469                            renderer,
1470                            theme,
1471                            style,
1472                            cursor,
1473                            viewport,
1474                            apply_alpha(status_appearance.text_color),
1475                            Rectangle {
1476                                width: 16.0,
1477                                height: 16.0,
1478                                ..image_bounds
1479                            },
1480                            crate::widget::icon(
1481                                match crate::widget::common::object_select().data() {
1482                                    crate::iced_core::svg::Data::Bytes(bytes) => {
1483                                        crate::widget::icon::from_svg_bytes(bytes.as_ref())
1484                                    }
1485                                    crate::iced_core::svg::Data::Path(path) => {
1486                                        crate::widget::icon::from_path(path.clone())
1487                                    }
1488                                },
1489                            ),
1490                        );
1491
1492                        let offset = 16.0 + f32::from(self.button_spacing);
1493
1494                        bounds.x += offset;
1495                    }
1496                }
1497            }
1498
1499            // Whether to show the close button on this tab.
1500            let show_close_button =
1501                (key_is_active || !self.show_close_icon_on_hover || key_is_hovered)
1502                    && self.model.is_closable(key);
1503
1504            // Width of the icon used by the close button, which we will subtract from the text bounds.
1505            let close_icon_width = if show_close_button {
1506                f32::from(self.close_icon.size)
1507            } else {
1508                0.0
1509            };
1510
1511            bounds.width = original_bounds.width
1512                - (bounds.x - original_bounds.x)
1513                - close_icon_width
1514                - f32::from(self.button_padding[2]);
1515
1516            bounds.y = center_y;
1517
1518            if self.model.text(key).is_some_and(|text| !text.is_empty()) {
1519                // Draw the text for this segmented button or tab.
1520                renderer.fill_paragraph(
1521                    state.paragraphs[key].raw(),
1522                    bounds.position(),
1523                    apply_alpha(status_appearance.text_color),
1524                    Rectangle {
1525                        x: bounds.x,
1526                        width: bounds.width,
1527                        ..original_bounds
1528                    },
1529                );
1530            }
1531
1532            // Draw a close button if set.
1533            if show_close_button {
1534                let close_button_bounds = close_bounds(original_bounds, close_icon_width);
1535
1536                draw_icon::<Message>(
1537                    renderer,
1538                    theme,
1539                    style,
1540                    cursor,
1541                    viewport,
1542                    apply_alpha(status_appearance.text_color),
1543                    close_button_bounds,
1544                    self.close_icon.clone(),
1545                );
1546            }
1547
1548            nth += 1;
1549        });
1550    }
1551
1552    fn overlay<'b>(
1553        &'b mut self,
1554        tree: &'b mut Tree,
1555        layout: iced_core::Layout<'_>,
1556        _renderer: &Renderer,
1557        translation: Vector,
1558    ) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, Renderer>> {
1559        let state = tree.state.downcast_ref::<LocalState>();
1560
1561        let Some(entity) = state.show_context else {
1562            return None;
1563        };
1564
1565        let bounds = self
1566            .variant_bounds(state, layout.bounds())
1567            .find_map(|item| match item {
1568                ItemBounds::Button(e, bounds) if e == entity => Some(bounds),
1569                _ => None,
1570            });
1571        let Some(mut bounds) = bounds else {
1572            return None;
1573        };
1574
1575        let Some(context_menu) = self.context_menu.as_mut() else {
1576            return None;
1577        };
1578
1579        if !tree.children[0].state.downcast_ref::<MenuBarState>().open {
1580            return None;
1581        }
1582
1583        bounds.x = state.context_cursor.x;
1584        bounds.y = state.context_cursor.y;
1585
1586        Some(
1587            crate::widget::menu::Menu {
1588                tree: &mut tree.children[0],
1589                menu_roots: context_menu,
1590                bounds_expand: 16,
1591                menu_overlays_parent: true,
1592                close_condition: CloseCondition {
1593                    leave: false,
1594                    click_outside: true,
1595                    click_inside: true,
1596                },
1597                item_width: ItemWidth::Uniform(240),
1598                item_height: ItemHeight::Dynamic(40),
1599                bar_bounds: bounds,
1600                main_offset: -(bounds.height as i32),
1601                cross_offset: 0,
1602                root_bounds_list: vec![bounds],
1603                path_highlight: Some(PathHighlight::MenuActive),
1604                style: &crate::theme::menu_bar::MenuBarStyle::Default,
1605                position: Point::new(translation.x, translation.y),
1606            }
1607            .overlay(),
1608        )
1609    }
1610
1611    fn drag_destinations(
1612        &self,
1613        _state: &Tree,
1614        layout: Layout<'_>,
1615        _renderer: &Renderer,
1616        dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
1617    ) {
1618        let bounds = layout.bounds();
1619
1620        let my_id = self.get_drag_id();
1621        let dnd_rect = DndDestinationRectangle {
1622            id: my_id,
1623            rectangle: dnd::Rectangle {
1624                x: f64::from(bounds.x),
1625                y: f64::from(bounds.y),
1626                width: f64::from(bounds.width),
1627                height: f64::from(bounds.height),
1628            },
1629            mime_types: self.mimes.clone().into_iter().map(Cow::Owned).collect(),
1630            actions: DndAction::Copy | DndAction::Move,
1631            preferred: DndAction::Move,
1632        };
1633        dnd_rectangles.push(dnd_rect);
1634    }
1635}
1636
1637impl<'a, Variant, SelectionMode, Message> From<SegmentedButton<'a, Variant, SelectionMode, Message>>
1638    for Element<'a, Message>
1639where
1640    SegmentedButton<'a, Variant, SelectionMode, Message>: SegmentedVariant,
1641    Variant: 'static,
1642    Model<SelectionMode>: Selectable,
1643    SelectionMode: Default,
1644    Message: 'static + Clone,
1645{
1646    fn from(mut widget: SegmentedButton<'a, Variant, SelectionMode, Message>) -> Self {
1647        if widget.model.items.is_empty() {
1648            widget.spacing = 0;
1649        }
1650
1651        Self::new(widget)
1652    }
1653}
1654
1655/// State that is maintained by each individual widget.
1656pub struct LocalState {
1657    /// Defines how many buttons to show at a time.
1658    pub(super) buttons_visible: usize,
1659    /// Button visibility offset, when collapsed.
1660    pub(super) buttons_offset: usize,
1661    /// Whether buttons need to be collapsed to preserve minimum width
1662    pub(super) collapsed: bool,
1663    /// If the widget is focused or not.
1664    focused: bool,
1665    /// The key inside the widget that is currently focused.
1666    focused_item: Item,
1667    /// The ID of the button that is being hovered. Defaults to null.
1668    hovered: Item,
1669    /// The ID of the button that was middle-clicked, but not yet released.
1670    middle_clicked: Option<Item>,
1671    /// Last known length of the model.
1672    pub(super) known_length: usize,
1673    /// Dimensions of internal buttons when shrinking
1674    pub(super) internal_layout: Vec<(Size, Size)>,
1675    /// The paragraphs for each text.
1676    paragraphs: SecondaryMap<Entity, crate::Plain>,
1677    /// Used to detect changes in text.
1678    text_hashes: SecondaryMap<Entity, u64>,
1679    /// Location of cursor when context menu was opened.
1680    context_cursor: Point,
1681    /// Track whether an item is currently showing a context menu.
1682    show_context: Option<Entity>,
1683    /// Time since last tab activation from wheel movements.
1684    wheel_timestamp: Option<Instant>,
1685    /// Dnd state
1686    pub dnd_state: crate::widget::dnd_destination::State<Option<Entity>>,
1687    /// Tracks multi-touch events
1688    fingers_pressed: HashSet<Finger>,
1689    /// The currently pressed item
1690    pressed_item: Option<Item>,
1691}
1692
1693#[derive(Debug, Default, PartialEq)]
1694enum Item {
1695    NextButton,
1696    #[default]
1697    None,
1698    PrevButton,
1699    Set,
1700    Tab(Entity),
1701}
1702
1703impl operation::Focusable for LocalState {
1704    fn is_focused(&self) -> bool {
1705        self.focused
1706    }
1707
1708    fn focus(&mut self) {
1709        self.focused = true;
1710        self.focused_item = Item::Set;
1711    }
1712
1713    fn unfocus(&mut self) {
1714        self.focused = false;
1715        self.focused_item = Item::None;
1716        self.show_context = None;
1717    }
1718}
1719
1720/// The iced identifier of a segmented button.
1721#[derive(Debug, Clone, PartialEq)]
1722pub struct Id(widget::Id);
1723
1724impl Id {
1725    /// Creates a custom [`Id`].
1726    pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
1727        Self(widget::Id::new(id))
1728    }
1729
1730    /// Creates a unique [`Id`].
1731    ///
1732    /// This function produces a different [`Id`] every time it is called.
1733    #[must_use]
1734    #[inline]
1735    pub fn unique() -> Self {
1736        Self(widget::Id::unique())
1737    }
1738}
1739
1740impl From<Id> for widget::Id {
1741    fn from(id: Id) -> Self {
1742        id.0
1743    }
1744}
1745
1746/// Calculates the bounds of the close button within the area of an item.
1747fn close_bounds(area: Rectangle<f32>, icon_size: f32) -> Rectangle<f32> {
1748    Rectangle {
1749        x: area.x + area.width - icon_size - 8.0,
1750        y: area.center_y() - (icon_size / 2.0),
1751        width: icon_size,
1752        height: icon_size,
1753    }
1754}
1755
1756/// Calculate the bounds of the `next_tab` button.
1757fn next_tab_bounds(bounds: &Rectangle, button_height: f32) -> Rectangle {
1758    Rectangle {
1759        x: bounds.x + bounds.width - button_height,
1760        y: bounds.y,
1761        width: button_height,
1762        height: button_height,
1763    }
1764}
1765
1766/// Calculate the bounds of the `prev_tab` button.
1767fn prev_tab_bounds(bounds: &Rectangle, button_height: f32) -> Rectangle {
1768    Rectangle {
1769        x: bounds.x,
1770        y: bounds.y,
1771        width: button_height,
1772        height: button_height,
1773    }
1774}
1775
1776#[allow(clippy::too_many_arguments)]
1777fn draw_icon<Message: 'static>(
1778    renderer: &mut Renderer,
1779    theme: &crate::Theme,
1780    style: &renderer::Style,
1781    cursor: mouse::Cursor,
1782    viewport: &Rectangle,
1783    color: Color,
1784    bounds: Rectangle,
1785    icon: Icon,
1786) {
1787    let layout_node = layout::Node::new(Size {
1788        width: bounds.width,
1789        height: bounds.width,
1790    })
1791    .move_to(Point {
1792        x: bounds.x,
1793        y: bounds.y,
1794    });
1795
1796    Widget::<Message, crate::Theme, Renderer>::draw(
1797        Element::<Message>::from(icon.clone()).as_widget(),
1798        &Tree::empty(),
1799        renderer,
1800        theme,
1801        &renderer::Style {
1802            icon_color: color,
1803            text_color: color,
1804            scale_factor: style.scale_factor,
1805        },
1806        Layout::new(&layout_node),
1807        cursor,
1808        viewport,
1809    );
1810}
1811
1812fn left_button_released(event: &Event) -> bool {
1813    matches!(
1814        event,
1815        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left,))
1816    )
1817}
1818
1819fn right_button_released(event: &Event) -> bool {
1820    matches!(
1821        event,
1822        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right,))
1823    )
1824}
1825
1826fn touch_lifted(event: &Event) -> bool {
1827    matches!(event, Event::Touch(touch::Event::FingerLifted { .. }))
1828}