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}