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