1use 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    static LAST_FOCUS_UPDATE: LazyCell<Cell<Instant>> = LazyCell::new(|| Cell::new(Instant::now()));
42}
43
44pub 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
54pub trait SegmentedVariant {
56    const VERTICAL: bool;
57
58    fn variant_appearance(
60        theme: &crate::Theme,
61        style: &crate::theme::SegmentedButton,
62    ) -> super::Appearance;
63
64    fn variant_bounds<'b>(
66        &'b self,
67        state: &'b LocalState,
68        bounds: Rectangle,
69    ) -> Box<dyn Iterator<Item = ItemBounds> + 'b>;
70
71    fn variant_layout(
73        &self,
74        state: &mut LocalState,
75        renderer: &crate::Renderer,
76        limits: &layout::Limits,
77    ) -> Size;
78}
79
80#[derive(Setters)]
82#[must_use]
83pub struct SegmentedButton<'a, Variant, SelectionMode, Message>
84where
85    Model<SelectionMode>: Selectable,
86    SelectionMode: Default,
87{
88    #[setters(skip)]
90    pub(super) model: &'a Model<SelectionMode>,
91    pub(super) id: Id,
93    pub(super) close_icon: Icon,
95    pub(super) scrollable_focus: bool,
97    pub(super) show_close_icon_on_hover: bool,
99    #[setters(into)]
101    pub(super) padding: Padding,
102    pub(super) dividers: bool,
104    pub(super) button_alignment: Alignment,
106    pub(super) button_padding: [u16; 4],
108    pub(super) button_height: u16,
110    pub(super) button_spacing: u16,
112    pub(super) maximum_button_width: u16,
114    pub(super) minimum_button_width: u16,
116    pub(super) indent_spacing: u16,
118    pub(super) font_active: crate::font::Font,
120    pub(super) font_hovered: crate::font::Font,
122    pub(super) font_inactive: crate::font::Font,
124    pub(super) font_size: f32,
126    pub(super) width: Length,
128    pub(super) height: Length,
130    pub(super) spacing: u16,
132    pub(super) line_height: LineHeight,
134    #[setters(into)]
136    pub(super) style: Style,
137    #[setters(skip)]
139    pub(super) context_menu: Option<Vec<menu::Tree<Message>>>,
140    #[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    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    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    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    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    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    fn is_enabled(&self, key: Entity) -> bool {
266        self.model.items.get(key).is_some_and(|item| item.enabled)
267    }
268
269    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().into_owned();
278        self
279    }
280
281    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    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    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                            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    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                            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        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        if let Some(indent) = self.model.indent(button) {
470            width = f32::from(indent).mul_add(f32::from(self.indent_spacing), width);
471        }
472
473        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            if let crate::theme::SegmentedButton::Control = self.style {
479                width += 16.0 + icon_spacing;
480            }
481        }
482
483        if self.model.is_closable(button) {
485            width += f32::from(self.close_icon.size) + f32::from(self.button_spacing);
486        }
487
488        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    #[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        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        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        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            if state.collapsed {
860                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                    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                        state.hovered = Item::Tab(key);
899
900                        if self.model.items[key].closable {
902                            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                                    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                        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                        if state.wheel_timestamp.is_none_or(|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            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 == keyboard::Modifiers::SHIFT {
1058                    self.focus_previous(state)
1059                } else if modifiers.is_empty() {
1060                    self.focus_next(state)
1061                } else {
1062                    event::Status::Ignored
1063                };
1064            }
1065
1066            if let Some(on_activate) = self.on_activate.as_ref() {
1067                if let Event::Keyboard(keyboard::Event::KeyReleased {
1068                    key: keyboard::Key::Named(keyboard::key::Named::Enter),
1069                    ..
1070                }) = event
1071                {
1072                    match state.focused_item {
1073                        Item::Tab(entity) => {
1074                            shell.publish(on_activate(entity));
1075                        }
1076
1077                        Item::PrevButton => {
1078                            if self.prev_tab_sensitive(state) {
1079                                state.buttons_offset -= 1;
1080
1081                                if !self.prev_tab_sensitive(state) {
1083                                    if let Some(first) = self.first_tab(state) {
1084                                        state.focused_item = Item::Tab(first);
1085                                    }
1086                                }
1087                            }
1088                        }
1089
1090                        Item::NextButton => {
1091                            if self.next_tab_sensitive(state) {
1092                                state.buttons_offset += 1;
1093
1094                                if !self.next_tab_sensitive(state) {
1096                                    if let Some(last) = self.last_tab(state) {
1097                                        state.focused_item = Item::Tab(last);
1098                                    }
1099                                }
1100                            }
1101                        }
1102
1103                        Item::None | Item::Set => (),
1104                    }
1105
1106                    return event::Status::Captured;
1107                }
1108            }
1109        }
1110
1111        event::Status::Ignored
1112    }
1113
1114    fn operate(
1115        &self,
1116        tree: &mut Tree,
1117        _layout: Layout<'_>,
1118        _renderer: &Renderer,
1119        operation: &mut dyn iced_core::widget::Operation<()>,
1120    ) {
1121        let state = tree.state.downcast_mut::<LocalState>();
1122        operation.focusable(state, Some(&self.id.0));
1123
1124        if let Item::Set = state.focused_item {
1125            if self.prev_tab_sensitive(state) {
1126                state.focused_item = Item::PrevButton;
1127            } else if let Some(first) = self.first_tab(state) {
1128                state.focused_item = Item::Tab(first);
1129            }
1130        }
1131    }
1132
1133    fn mouse_interaction(
1134        &self,
1135        tree: &Tree,
1136        layout: Layout<'_>,
1137        cursor_position: mouse::Cursor,
1138        _viewport: &iced::Rectangle,
1139        _renderer: &Renderer,
1140    ) -> iced_core::mouse::Interaction {
1141        if self.on_activate.is_none() {
1142            return iced_core::mouse::Interaction::default();
1143        }
1144        let state = tree.state.downcast_ref::<LocalState>();
1145        let bounds = layout.bounds();
1146
1147        if cursor_position.is_over(bounds) {
1148            let hovered_button = self
1149                .variant_bounds(state, bounds)
1150                .filter_map(|item| match item {
1151                    ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
1152                    _ => None,
1153                })
1154                .find(|(_key, bounds)| cursor_position.is_over(*bounds));
1155
1156            if let Some((key, _bounds)) = hovered_button {
1157                return if self.model.items[key].enabled {
1158                    iced_core::mouse::Interaction::Pointer
1159                } else {
1160                    iced_core::mouse::Interaction::Idle
1161                };
1162            }
1163        }
1164
1165        iced_core::mouse::Interaction::Idle
1166    }
1167
1168    #[allow(clippy::too_many_lines)]
1169    fn draw(
1170        &self,
1171        tree: &Tree,
1172        renderer: &mut Renderer,
1173        theme: &crate::Theme,
1174        style: &renderer::Style,
1175        layout: Layout<'_>,
1176        cursor: mouse::Cursor,
1177        viewport: &iced::Rectangle,
1178    ) {
1179        let state = tree.state.downcast_ref::<LocalState>();
1180        let appearance = Self::variant_appearance(theme, &self.style);
1181        let bounds: Rectangle = layout.bounds();
1182        let button_amount = self.model.items.len();
1183
1184        if let Some(background) = appearance.background {
1186            renderer.fill_quad(
1187                renderer::Quad {
1188                    bounds,
1189                    border: appearance.border,
1190                    shadow: Shadow::default(),
1191                },
1192                background,
1193            );
1194        }
1195
1196        if state.collapsed {
1198            let mut tab_bounds = prev_tab_bounds(&bounds, f32::from(self.button_height));
1199
1200            let mut background_appearance =
1202                if self.on_activate.is_some() && Item::PrevButton == state.focused_item {
1203                    Some(appearance.active)
1204                } else if self.on_activate.is_some() && Item::PrevButton == state.hovered {
1205                    Some(appearance.hover)
1206                } else {
1207                    None
1208                };
1209
1210            if let Some(background_appearance) = background_appearance.take() {
1211                renderer.fill_quad(
1212                    renderer::Quad {
1213                        bounds: tab_bounds,
1214                        border: Border {
1215                            radius: theme.cosmic().radius_s().into(),
1216                            ..Default::default()
1217                        },
1218                        shadow: Shadow::default(),
1219                    },
1220                    background_appearance
1221                        .background
1222                        .unwrap_or(Background::Color(Color::TRANSPARENT)),
1223                );
1224            }
1225
1226            draw_icon::<Message>(
1227                renderer,
1228                theme,
1229                style,
1230                cursor,
1231                viewport,
1232                if state.buttons_offset == 0 {
1233                    appearance.inactive.text_color
1234                } else {
1235                    appearance.active.text_color
1236                },
1237                Rectangle {
1238                    x: tab_bounds.x + 8.0,
1239                    y: tab_bounds.y + f32::from(self.button_height) / 4.0,
1240                    width: 16.0,
1241                    height: 16.0,
1242                },
1243                icon::from_name("go-previous-symbolic").size(16).icon(),
1244            );
1245
1246            tab_bounds = next_tab_bounds(&bounds, f32::from(self.button_height));
1247
1248            background_appearance =
1250                if self.on_activate.is_some() && Item::NextButton == state.focused_item {
1251                    Some(appearance.active)
1252                } else if self.on_activate.is_some() && Item::NextButton == state.hovered {
1253                    Some(appearance.hover)
1254                } else {
1255                    None
1256                };
1257
1258            if let Some(background_appearance) = background_appearance {
1259                renderer.fill_quad(
1260                    renderer::Quad {
1261                        bounds: tab_bounds,
1262                        border: Border {
1263                            radius: theme.cosmic().radius_s().into(),
1264                            ..Default::default()
1265                        },
1266                        shadow: Shadow::default(),
1267                    },
1268                    background_appearance
1269                        .background
1270                        .unwrap_or(Background::Color(Color::TRANSPARENT)),
1271                );
1272            }
1273
1274            draw_icon::<Message>(
1275                renderer,
1276                theme,
1277                style,
1278                cursor,
1279                viewport,
1280                if self.next_tab_sensitive(state) {
1281                    appearance.active.text_color
1282                } else if let Item::NextButton = state.focused_item {
1283                    appearance.active.text_color
1284                } else {
1285                    appearance.inactive.text_color
1286                },
1287                Rectangle {
1288                    x: tab_bounds.x + 8.0,
1289                    y: tab_bounds.y + f32::from(self.button_height) / 4.0,
1290                    width: 16.0,
1291                    height: 16.0,
1292                },
1293                icon::from_name("go-next-symbolic").size(16).icon(),
1294            );
1295        }
1296
1297        let rad_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0;
1298
1299        let divider_background = Background::Color(
1300            crate::theme::active()
1301                .cosmic()
1302                .primary_component_divider()
1303                .into(),
1304        );
1305
1306        let mut nth = 0;
1308        self.variant_bounds(state, bounds).for_each(move |item| {
1309            let (key, mut bounds) = match item {
1310                ItemBounds::Button(entity, bounds) => (entity, bounds),
1312
1313                ItemBounds::Divider(bounds, accented) => {
1315                    renderer.fill_quad(
1316                        renderer::Quad {
1317                            bounds,
1318                            border: Border::default(),
1319                            shadow: Shadow::default(),
1320                        },
1321                        {
1322                            let theme = crate::theme::active();
1323                            if accented {
1324                                Background::Color(theme.cosmic().small_widget_divider().into())
1325                            } else {
1326                                Background::Color(theme.cosmic().primary_container_divider().into())
1327                            }
1328                        },
1329                    );
1330
1331                    return;
1332                }
1333            };
1334
1335            let center_y = bounds.center_y();
1336
1337            let menu_open = || {
1338                state.show_context == Some(key)
1339                    && !tree.children.is_empty()
1340                    && tree.children[0]
1341                        .state
1342                        .downcast_ref::<MenuBarState>()
1343                        .inner
1344                        .with_data(|data| data.open)
1345            };
1346
1347            let key_is_active = self.model.is_active(key);
1348            let key_is_focused = state.focused_visible && self.button_is_focused(state, key);
1349            let key_is_hovered = self.button_is_hovered(state, key);
1350            let status_appearance = if self.button_is_pressed(state, key) {
1351                appearance.pressed
1352            } else if key_is_hovered || menu_open() {
1353                appearance.hover
1354            } else if key_is_active {
1355                appearance.active
1356            } else {
1357                appearance.inactive
1358            };
1359
1360            let button_appearance = if nth == 0 {
1361                status_appearance.first
1362            } else if nth + 1 == button_amount {
1363                status_appearance.last
1364            } else {
1365                status_appearance.middle
1366            };
1367
1368            if appearance.active_width > 0.0 {
1370                let active_width = if key_is_active {
1371                    appearance.active_width
1372                } else {
1373                    1.0
1374                };
1375
1376                renderer.fill_quad(
1377                    renderer::Quad {
1378                        bounds: if Self::VERTICAL {
1379                            Rectangle {
1380                                x: bounds.x + bounds.width - active_width,
1381                                width: active_width,
1382                                ..bounds
1383                            }
1384                        } else {
1385                            Rectangle {
1386                                y: bounds.y + bounds.height - active_width,
1387                                height: active_width,
1388                                ..bounds
1389                            }
1390                        },
1391                        border: Border {
1392                            radius: rad_0.into(),
1393                            ..Default::default()
1394                        },
1395                        shadow: Shadow::default(),
1396                    },
1397                    appearance.active.text_color,
1398                );
1399            }
1400
1401            let original_bounds = bounds;
1402            bounds.x += f32::from(self.button_padding[0]);
1403            bounds.width -= f32::from(self.button_padding[0]) - f32::from(self.button_padding[2]);
1404            let mut indent_padding = 0.0;
1405
1406            if let Some(indent) = self.model.indent(key) {
1408                if indent > 0 {
1409                    let adjustment = f32::from(indent) * f32::from(self.indent_spacing);
1410                    bounds.x += adjustment;
1411                    bounds.width -= adjustment;
1412
1413                    if let crate::theme::SegmentedButton::FileNav = self.style {
1415                        if indent > 1 {
1416                            indent_padding = 7.0;
1417
1418                            for level in 1..indent {
1419                                renderer.fill_quad(
1420                                    renderer::Quad {
1421                                        bounds: Rectangle {
1422                                            x: (level as f32)
1423                                                .mul_add(-(self.indent_spacing as f32), bounds.x)
1424                                                + indent_padding,
1425                                            width: 1.0,
1426                                            ..bounds
1427                                        },
1428                                        border: Border {
1429                                            radius: rad_0.into(),
1430                                            ..Default::default()
1431                                        },
1432                                        shadow: Shadow::default(),
1433                                    },
1434                                    divider_background,
1435                                );
1436                            }
1437
1438                            indent_padding += 4.0;
1439                        }
1440                    }
1441                }
1442            }
1443
1444            if key_is_focused || status_appearance.background.is_some() {
1446                renderer.fill_quad(
1447                    renderer::Quad {
1448                        bounds: Rectangle {
1449                            x: bounds.x - f32::from(self.button_padding[0]) + indent_padding,
1450                            width: bounds.width + f32::from(self.button_padding[0])
1451                                - f32::from(self.button_padding[2])
1452                                - indent_padding,
1453                            ..bounds
1454                        },
1455                        border: if key_is_focused {
1456                            Border {
1457                                width: 1.0,
1458                                color: appearance.active.text_color,
1459                                radius: button_appearance.border.radius,
1460                            }
1461                        } else {
1462                            button_appearance.border
1463                        },
1464                        shadow: Shadow::default(),
1465                    },
1466                    status_appearance
1467                        .background
1468                        .unwrap_or(Background::Color(Color::TRANSPARENT)),
1469                );
1470            }
1471
1472            {
1474                let actual_width = state.internal_layout[nth].1.width;
1475
1476                let offset = match self.button_alignment {
1477                    Alignment::Start => None,
1478                    Alignment::Center => Some((bounds.width - actual_width) / 2.0),
1479                    Alignment::End => Some(bounds.width - actual_width),
1480                };
1481
1482                if let Some(offset) = offset {
1483                    bounds.x += offset - f32::from(self.button_padding[0]);
1484                    bounds.width = actual_width;
1485                }
1486            }
1487
1488            if let Some(icon) = self.model.icon(key) {
1490                let mut image_bounds = bounds;
1491                let width = f32::from(icon.size);
1492                let offset = width + f32::from(self.button_spacing);
1493                image_bounds.y = center_y - width / 2.0;
1494
1495                draw_icon::<Message>(
1496                    renderer,
1497                    theme,
1498                    style,
1499                    cursor,
1500                    viewport,
1501                    status_appearance.text_color,
1502                    Rectangle {
1503                        width,
1504                        height: width,
1505                        ..image_bounds
1506                    },
1507                    icon.clone(),
1508                );
1509
1510                bounds.x += offset;
1511            } else {
1512                if key_is_active {
1514                    if let crate::theme::SegmentedButton::Control = self.style {
1515                        let mut image_bounds = bounds;
1516                        image_bounds.y = center_y - 8.0;
1517
1518                        draw_icon::<Message>(
1519                            renderer,
1520                            theme,
1521                            style,
1522                            cursor,
1523                            viewport,
1524                            status_appearance.text_color,
1525                            Rectangle {
1526                                width: 16.0,
1527                                height: 16.0,
1528                                ..image_bounds
1529                            },
1530                            crate::widget::icon(
1531                                match crate::widget::common::object_select().data() {
1532                                    crate::iced_core::svg::Data::Bytes(bytes) => {
1533                                        crate::widget::icon::from_svg_bytes(bytes.as_ref())
1534                                    }
1535                                    crate::iced_core::svg::Data::Path(path) => {
1536                                        crate::widget::icon::from_path(path.clone())
1537                                    }
1538                                },
1539                            ),
1540                        );
1541
1542                        let offset = 16.0 + f32::from(self.button_spacing);
1543
1544                        bounds.x += offset;
1545                    }
1546                }
1547            }
1548
1549            let show_close_button =
1551                (key_is_active || !self.show_close_icon_on_hover || key_is_hovered)
1552                    && self.model.is_closable(key);
1553
1554            let close_icon_width = if show_close_button {
1556                f32::from(self.close_icon.size)
1557            } else {
1558                0.0
1559            };
1560
1561            bounds.width = original_bounds.width
1562                - (bounds.x - original_bounds.x)
1563                - close_icon_width
1564                - f32::from(self.button_padding[2]);
1565
1566            bounds.y = center_y;
1567
1568            if self.model.text(key).is_some_and(|text| !text.is_empty()) {
1569                renderer.fill_paragraph(
1571                    state.paragraphs[key].raw(),
1572                    bounds.position(),
1573                    status_appearance.text_color,
1574                    Rectangle {
1575                        x: bounds.x,
1576                        width: bounds.width,
1577                        ..original_bounds
1578                    },
1579                );
1580            }
1581
1582            if show_close_button {
1584                let close_button_bounds = close_bounds(original_bounds, close_icon_width);
1585
1586                draw_icon::<Message>(
1587                    renderer,
1588                    theme,
1589                    style,
1590                    cursor,
1591                    viewport,
1592                    status_appearance.text_color,
1593                    close_button_bounds,
1594                    self.close_icon.clone(),
1595                );
1596            }
1597
1598            nth += 1;
1599        });
1600    }
1601
1602    fn overlay<'b>(
1603        &'b mut self,
1604        tree: &'b mut Tree,
1605        layout: iced_core::Layout<'_>,
1606        _renderer: &Renderer,
1607        translation: Vector,
1608    ) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, Renderer>> {
1609        let state = tree.state.downcast_ref::<LocalState>();
1610        let menu_state = state.menu_state.clone();
1611
1612        let entity = state.show_context?;
1613
1614        let mut bounds =
1615            self.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
1621        let context_menu = self.context_menu.as_mut()?;
1622
1623        if !menu_state.inner.with_data(|data| data.open) {
1624            return None;
1626        }
1627        bounds.x = state.context_cursor.x;
1628        bounds.y = state.context_cursor.y;
1629
1630        Some(
1631            crate::widget::menu::Menu {
1632                tree: menu_state,
1633                menu_roots: std::borrow::Cow::Owned(context_menu.clone()),
1634                bounds_expand: 16,
1635                menu_overlays_parent: true,
1636                close_condition: CloseCondition {
1637                    leave: false,
1638                    click_outside: true,
1639                    click_inside: true,
1640                },
1641                item_width: ItemWidth::Uniform(240),
1642                item_height: ItemHeight::Dynamic(40),
1643                bar_bounds: bounds,
1644                main_offset: -bounds.height as i32,
1645                cross_offset: 0,
1646                root_bounds_list: vec![bounds],
1647                path_highlight: Some(PathHighlight::MenuActive),
1648                style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default),
1649                position: Point::new(translation.x, translation.y),
1650                is_overlay: true,
1651                window_id: window::Id::NONE,
1652                depth: 0,
1653                on_surface_action: None,
1654            }
1655            .overlay(),
1656        )
1657    }
1658
1659    fn drag_destinations(
1660        &self,
1661        _state: &Tree,
1662        layout: Layout<'_>,
1663        _renderer: &Renderer,
1664        dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
1665    ) {
1666        let bounds = layout.bounds();
1667
1668        let my_id = self.get_drag_id();
1669        let dnd_rect = DndDestinationRectangle {
1670            id: my_id,
1671            rectangle: dnd::Rectangle {
1672                x: f64::from(bounds.x),
1673                y: f64::from(bounds.y),
1674                width: f64::from(bounds.width),
1675                height: f64::from(bounds.height),
1676            },
1677            mime_types: self.mimes.clone().into_iter().map(Cow::Owned).collect(),
1678            actions: DndAction::Copy | DndAction::Move,
1679            preferred: DndAction::Move,
1680        };
1681        dnd_rectangles.push(dnd_rect);
1682    }
1683}
1684
1685impl<'a, Variant, SelectionMode, Message> From<SegmentedButton<'a, Variant, SelectionMode, Message>>
1686    for Element<'a, Message>
1687where
1688    SegmentedButton<'a, Variant, SelectionMode, Message>: SegmentedVariant,
1689    Variant: 'static,
1690    Model<SelectionMode>: Selectable,
1691    SelectionMode: Default,
1692    Message: 'static + Clone,
1693{
1694    fn from(mut widget: SegmentedButton<'a, Variant, SelectionMode, Message>) -> Self {
1695        if widget.model.items.is_empty() {
1696            widget.spacing = 0;
1697        }
1698
1699        Self::new(widget)
1700    }
1701}
1702
1703#[derive(Debug, Clone, Copy)]
1704struct Focus {
1705    updated_at: Instant,
1706    now: Instant,
1707}
1708
1709pub struct LocalState {
1711    pub(crate) menu_state: MenuBarState,
1713    pub(super) buttons_visible: usize,
1715    pub(super) buttons_offset: usize,
1717    pub(super) collapsed: bool,
1719    focused_visible: bool,
1721    focused: Option<Focus>,
1723    focused_item: Item,
1725    hovered: Item,
1727    middle_clicked: Option<Item>,
1729    pub(super) known_length: usize,
1731    pub(super) internal_layout: Vec<(Size, Size)>,
1733    paragraphs: SecondaryMap<Entity, crate::Plain>,
1735    text_hashes: SecondaryMap<Entity, u64>,
1737    context_cursor: Point,
1739    show_context: Option<Entity>,
1741    wheel_timestamp: Option<Instant>,
1743    pub dnd_state: crate::widget::dnd_destination::State<Option<Entity>>,
1745    fingers_pressed: HashSet<Finger>,
1747    pressed_item: Option<Item>,
1749}
1750
1751#[derive(Debug, Default, PartialEq)]
1752enum Item {
1753    NextButton,
1754    #[default]
1755    None,
1756    PrevButton,
1757    Set,
1758    Tab(Entity),
1759}
1760
1761impl LocalState {
1762    fn set_focused(&mut self) {
1763        let now = Instant::now();
1764        LAST_FOCUS_UPDATE.with(|x| x.set(now));
1765
1766        self.focused = Some(Focus {
1767            updated_at: now,
1768            now,
1769        });
1770    }
1771}
1772
1773impl operation::Focusable for LocalState {
1774    fn is_focused(&self) -> bool {
1775        self.focused
1776            .is_some_and(|f| f.updated_at == LAST_FOCUS_UPDATE.with(|f| f.get()))
1777    }
1778
1779    fn focus(&mut self) {
1780        self.set_focused();
1781        self.focused_visible = true;
1782        self.focused_item = Item::Set;
1783    }
1784
1785    fn unfocus(&mut self) {
1786        self.focused = None;
1787        self.focused_item = Item::None;
1788        self.focused_visible = false;
1789        self.show_context = None;
1790    }
1791}
1792
1793#[derive(Debug, Clone, PartialEq)]
1795pub struct Id(widget::Id);
1796
1797impl Id {
1798    pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
1800        Self(widget::Id::new(id))
1801    }
1802
1803    #[must_use]
1807    #[inline]
1808    pub fn unique() -> Self {
1809        Self(widget::Id::unique())
1810    }
1811}
1812
1813impl From<Id> for widget::Id {
1814    fn from(id: Id) -> Self {
1815        id.0
1816    }
1817}
1818
1819fn close_bounds(area: Rectangle<f32>, icon_size: f32) -> Rectangle<f32> {
1821    Rectangle {
1822        x: area.x + area.width - icon_size - 8.0,
1823        y: area.center_y() - (icon_size / 2.0),
1824        width: icon_size,
1825        height: icon_size,
1826    }
1827}
1828
1829fn next_tab_bounds(bounds: &Rectangle, button_height: f32) -> Rectangle {
1831    Rectangle {
1832        x: bounds.x + bounds.width - button_height,
1833        y: bounds.y,
1834        width: button_height,
1835        height: button_height,
1836    }
1837}
1838
1839fn prev_tab_bounds(bounds: &Rectangle, button_height: f32) -> Rectangle {
1841    Rectangle {
1842        x: bounds.x,
1843        y: bounds.y,
1844        width: button_height,
1845        height: button_height,
1846    }
1847}
1848
1849#[allow(clippy::too_many_arguments)]
1850fn draw_icon<Message: 'static>(
1851    renderer: &mut Renderer,
1852    theme: &crate::Theme,
1853    style: &renderer::Style,
1854    cursor: mouse::Cursor,
1855    viewport: &Rectangle,
1856    color: Color,
1857    bounds: Rectangle,
1858    icon: Icon,
1859) {
1860    let layout_node = layout::Node::new(Size {
1861        width: bounds.width,
1862        height: bounds.width,
1863    })
1864    .move_to(Point {
1865        x: bounds.x,
1866        y: bounds.y,
1867    });
1868
1869    Widget::<Message, crate::Theme, Renderer>::draw(
1870        Element::<Message>::from(icon).as_widget(),
1871        &Tree::empty(),
1872        renderer,
1873        theme,
1874        &renderer::Style {
1875            icon_color: color,
1876            text_color: color,
1877            scale_factor: style.scale_factor,
1878        },
1879        Layout::new(&layout_node),
1880        cursor,
1881        viewport,
1882    );
1883}
1884
1885fn left_button_released(event: &Event) -> bool {
1886    matches!(
1887        event,
1888        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left,))
1889    )
1890}
1891
1892fn right_button_released(event: &Event) -> bool {
1893    matches!(
1894        event,
1895        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right,))
1896    )
1897}
1898
1899fn is_pressed(event: &Event) -> bool {
1900    matches!(
1901        event,
1902        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
1903            | Event::Touch(touch::Event::FingerPressed { .. })
1904    )
1905}
1906
1907fn is_lifted(event: &Event) -> bool {
1908    matches!(
1909        event,
1910        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left,))
1911            | Event::Touch(touch::Event::FingerLifted { .. })
1912    )
1913}
1914
1915fn touch_lifted(event: &Event) -> bool {
1916    matches!(event, Event::Touch(touch::Event::FingerLifted { .. }))
1917}