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).map_or(false, |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().iter().cloned().collect();
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.map_or(true, |previous| {
991 current.duration_since(previous) > Duration::from_millis(250)
992 }) {
993 state.wheel_timestamp = Some(current);
994
995 match delta {
996 ScrollDelta::Lines { y, .. } | ScrollDelta::Pixels { y, .. } => {
997 let mut activate_key = None;
998
999 if y < 0.0 {
1000 let mut prev_key = Entity::null();
1001
1002 for key in self.model.order.iter().copied() {
1003 if self.model.is_active(key) && !prev_key.is_null() {
1004 activate_key = Some(prev_key);
1005 }
1006
1007 if self.model.is_enabled(key) {
1008 prev_key = key;
1009 }
1010 }
1011 } else if y > 0.0 {
1012 let mut buttons = self.model.order.iter().copied();
1013 while let Some(key) = buttons.next() {
1014 if self.model.is_active(key) {
1015 for key in buttons {
1016 if self.model.is_enabled(key) {
1017 activate_key = Some(key);
1018 break;
1019 }
1020 }
1021 break;
1022 }
1023 }
1024 }
1025
1026 if let Some(key) = activate_key {
1027 shell.publish(on_activate(key));
1028 state.set_focused();
1029 state.focused_item = Item::Tab(key);
1030 return event::Status::Captured;
1031 }
1032 }
1033 }
1034 }
1035 }
1036 }
1037 }
1038 } else if state.is_focused() {
1039 if is_pressed(&event) {
1041 state.unfocus();
1042 state.pressed_item = None;
1043 return event::Status::Ignored;
1044 }
1045 } else if is_lifted(&event) {
1046 state.pressed_item = None;
1047 }
1048
1049 if state.is_focused() {
1050 if let Event::Keyboard(keyboard::Event::KeyPressed {
1051 key: keyboard::Key::Named(keyboard::key::Named::Tab),
1052 modifiers,
1053 ..
1054 }) = event
1055 {
1056 state.focused_visible = true;
1057 return if modifiers.shift() {
1058 self.focus_previous(state)
1059 } else {
1060 self.focus_next(state)
1061 };
1062 }
1063
1064 if let Some(on_activate) = self.on_activate.as_ref() {
1065 if let Event::Keyboard(keyboard::Event::KeyReleased {
1066 key: keyboard::Key::Named(keyboard::key::Named::Enter),
1067 ..
1068 }) = event
1069 {
1070 match state.focused_item {
1071 Item::Tab(entity) => {
1072 shell.publish(on_activate(entity));
1073 }
1074
1075 Item::PrevButton => {
1076 if self.prev_tab_sensitive(state) {
1077 state.buttons_offset -= 1;
1078
1079 if !self.prev_tab_sensitive(state) {
1081 if let Some(first) = self.first_tab(state) {
1082 state.focused_item = Item::Tab(first);
1083 }
1084 }
1085 }
1086 }
1087
1088 Item::NextButton => {
1089 if self.next_tab_sensitive(state) {
1090 state.buttons_offset += 1;
1091
1092 if !self.next_tab_sensitive(state) {
1094 if let Some(last) = self.last_tab(state) {
1095 state.focused_item = Item::Tab(last);
1096 }
1097 }
1098 }
1099 }
1100
1101 Item::None | Item::Set => (),
1102 }
1103
1104 return event::Status::Captured;
1105 }
1106 }
1107 }
1108
1109 event::Status::Ignored
1110 }
1111
1112 fn operate(
1113 &self,
1114 tree: &mut Tree,
1115 _layout: Layout<'_>,
1116 _renderer: &Renderer,
1117 operation: &mut dyn iced_core::widget::Operation<()>,
1118 ) {
1119 let state = tree.state.downcast_mut::<LocalState>();
1120 operation.focusable(state, Some(&self.id.0));
1121
1122 if let Item::Set = state.focused_item {
1123 if self.prev_tab_sensitive(state) {
1124 state.focused_item = Item::PrevButton;
1125 } else if let Some(first) = self.first_tab(state) {
1126 state.focused_item = Item::Tab(first);
1127 }
1128 }
1129 }
1130
1131 fn mouse_interaction(
1132 &self,
1133 tree: &Tree,
1134 layout: Layout<'_>,
1135 cursor_position: mouse::Cursor,
1136 _viewport: &iced::Rectangle,
1137 _renderer: &Renderer,
1138 ) -> iced_core::mouse::Interaction {
1139 if self.on_activate.is_none() {
1140 return iced_core::mouse::Interaction::default();
1141 }
1142 let state = tree.state.downcast_ref::<LocalState>();
1143 let bounds = layout.bounds();
1144
1145 if cursor_position.is_over(bounds) {
1146 let hovered_button = self
1147 .variant_bounds(state, bounds)
1148 .filter_map(|item| match item {
1149 ItemBounds::Button(entity, bounds) => Some((entity, bounds)),
1150 _ => None,
1151 })
1152 .find(|(_key, bounds)| cursor_position.is_over(*bounds));
1153
1154 if let Some((key, _bounds)) = hovered_button {
1155 return if self.model.items[key].enabled {
1156 iced_core::mouse::Interaction::Pointer
1157 } else {
1158 iced_core::mouse::Interaction::Idle
1159 };
1160 }
1161 }
1162
1163 iced_core::mouse::Interaction::Idle
1164 }
1165
1166 #[allow(clippy::too_many_lines)]
1167 fn draw(
1168 &self,
1169 tree: &Tree,
1170 renderer: &mut Renderer,
1171 theme: &crate::Theme,
1172 style: &renderer::Style,
1173 layout: Layout<'_>,
1174 cursor: mouse::Cursor,
1175 viewport: &iced::Rectangle,
1176 ) {
1177 let state = tree.state.downcast_ref::<LocalState>();
1178 let appearance = Self::variant_appearance(theme, &self.style);
1179 let bounds: Rectangle = layout.bounds();
1180 let button_amount = self.model.items.len();
1181
1182 if let Some(background) = appearance.background {
1184 renderer.fill_quad(
1185 renderer::Quad {
1186 bounds,
1187 border: appearance.border,
1188 shadow: Shadow::default(),
1189 },
1190 background,
1191 );
1192 }
1193
1194 if state.collapsed {
1196 let mut tab_bounds = prev_tab_bounds(&bounds, f32::from(self.button_height));
1197
1198 let mut background_appearance =
1200 if self.on_activate.is_some() && Item::PrevButton == state.focused_item {
1201 Some(appearance.active)
1202 } else if self.on_activate.is_some() && Item::PrevButton == state.hovered {
1203 Some(appearance.hover)
1204 } else {
1205 None
1206 };
1207
1208 if let Some(background_appearance) = background_appearance.take() {
1209 renderer.fill_quad(
1210 renderer::Quad {
1211 bounds: tab_bounds,
1212 border: Border {
1213 radius: theme.cosmic().radius_s().into(),
1214 ..Default::default()
1215 },
1216 shadow: Shadow::default(),
1217 },
1218 background_appearance
1219 .background
1220 .unwrap_or(Background::Color(Color::TRANSPARENT)),
1221 );
1222 }
1223
1224 draw_icon::<Message>(
1225 renderer,
1226 theme,
1227 style,
1228 cursor,
1229 viewport,
1230 if state.buttons_offset == 0 {
1231 appearance.inactive.text_color
1232 } else {
1233 appearance.active.text_color
1234 },
1235 Rectangle {
1236 x: tab_bounds.x + 8.0,
1237 y: tab_bounds.y + f32::from(self.button_height) / 4.0,
1238 width: 16.0,
1239 height: 16.0,
1240 },
1241 icon::from_name("go-previous-symbolic").size(16).icon(),
1242 );
1243
1244 tab_bounds = next_tab_bounds(&bounds, f32::from(self.button_height));
1245
1246 background_appearance =
1248 if self.on_activate.is_some() && Item::NextButton == state.focused_item {
1249 Some(appearance.active)
1250 } else if self.on_activate.is_some() && Item::NextButton == state.hovered {
1251 Some(appearance.hover)
1252 } else {
1253 None
1254 };
1255
1256 if let Some(background_appearance) = background_appearance {
1257 renderer.fill_quad(
1258 renderer::Quad {
1259 bounds: tab_bounds,
1260 border: Border {
1261 radius: theme.cosmic().radius_s().into(),
1262 ..Default::default()
1263 },
1264 shadow: Shadow::default(),
1265 },
1266 background_appearance
1267 .background
1268 .unwrap_or(Background::Color(Color::TRANSPARENT)),
1269 );
1270 }
1271
1272 draw_icon::<Message>(
1273 renderer,
1274 theme,
1275 style,
1276 cursor,
1277 viewport,
1278 if self.next_tab_sensitive(state) {
1279 appearance.active.text_color
1280 } else if let Item::NextButton = state.focused_item {
1281 appearance.active.text_color
1282 } else {
1283 appearance.inactive.text_color
1284 },
1285 Rectangle {
1286 x: tab_bounds.x + 8.0,
1287 y: tab_bounds.y + f32::from(self.button_height) / 4.0,
1288 width: 16.0,
1289 height: 16.0,
1290 },
1291 icon::from_name("go-next-symbolic").size(16).icon(),
1292 );
1293 }
1294
1295 let rad_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0;
1296
1297 let divider_background = Background::Color(
1298 crate::theme::active()
1299 .cosmic()
1300 .primary_component_divider()
1301 .into(),
1302 );
1303
1304 let mut nth = 0;
1306 self.variant_bounds(state, bounds).for_each(move |item| {
1307 let (key, mut bounds) = match item {
1308 ItemBounds::Button(entity, bounds) => (entity, bounds),
1310
1311 ItemBounds::Divider(bounds, accented) => {
1313 renderer.fill_quad(
1314 renderer::Quad {
1315 bounds,
1316 border: Border::default(),
1317 shadow: Shadow::default(),
1318 },
1319 {
1320 let theme = crate::theme::active();
1321 if accented {
1322 Background::Color(theme.cosmic().small_widget_divider().into())
1323 } else {
1324 Background::Color(theme.cosmic().primary_container_divider().into())
1325 }
1326 },
1327 );
1328
1329 return;
1330 }
1331 };
1332
1333 let center_y = bounds.center_y();
1334
1335 let menu_open = || {
1336 state.show_context == Some(key)
1337 && !tree.children.is_empty()
1338 && tree.children[0]
1339 .state
1340 .downcast_ref::<MenuBarState>()
1341 .inner
1342 .with_data(|data| data.open)
1343 };
1344
1345 let key_is_active = self.model.is_active(key);
1346 let key_is_focused = state.focused_visible && self.button_is_focused(state, key);
1347 let key_is_hovered = self.button_is_hovered(state, key);
1348 let status_appearance = if self.button_is_pressed(state, key) {
1349 appearance.pressed
1350 } else if key_is_hovered || menu_open() {
1351 appearance.hover
1352 } else if key_is_active {
1353 appearance.active
1354 } else {
1355 appearance.inactive
1356 };
1357
1358 let button_appearance = if nth == 0 {
1359 status_appearance.first
1360 } else if nth + 1 == button_amount {
1361 status_appearance.last
1362 } else {
1363 status_appearance.middle
1364 };
1365
1366 if appearance.active_width > 0.0 {
1368 let active_width = if key_is_active {
1369 appearance.active_width
1370 } else {
1371 1.0
1372 };
1373
1374 renderer.fill_quad(
1375 renderer::Quad {
1376 bounds: if Self::VERTICAL {
1377 Rectangle {
1378 x: bounds.x + bounds.width - active_width,
1379 width: active_width,
1380 ..bounds
1381 }
1382 } else {
1383 Rectangle {
1384 y: bounds.y + bounds.height - active_width,
1385 height: active_width,
1386 ..bounds
1387 }
1388 },
1389 border: Border {
1390 radius: rad_0.into(),
1391 ..Default::default()
1392 },
1393 shadow: Shadow::default(),
1394 },
1395 appearance.active.text_color,
1396 );
1397 }
1398
1399 let original_bounds = bounds;
1400 bounds.x += f32::from(self.button_padding[0]);
1401 bounds.width -= f32::from(self.button_padding[0]) - f32::from(self.button_padding[2]);
1402 let mut indent_padding = 0.0;
1403
1404 if let Some(indent) = self.model.indent(key) {
1406 if indent > 0 {
1407 let adjustment = f32::from(indent) * f32::from(self.indent_spacing);
1408 bounds.x += adjustment;
1409 bounds.width -= adjustment;
1410
1411 if let crate::theme::SegmentedButton::FileNav = self.style {
1413 if indent > 1 {
1414 indent_padding = 7.0;
1415
1416 for level in 1..indent {
1417 renderer.fill_quad(
1418 renderer::Quad {
1419 bounds: Rectangle {
1420 x: bounds.x
1421 - (level as f32 * self.indent_spacing as f32)
1422 + indent_padding,
1423 width: 1.0,
1424 ..bounds
1425 },
1426 border: Border {
1427 radius: rad_0.into(),
1428 ..Default::default()
1429 },
1430 shadow: Shadow::default(),
1431 },
1432 divider_background,
1433 );
1434 }
1435
1436 indent_padding += 4.0;
1437 }
1438 }
1439 }
1440 }
1441
1442 if key_is_focused || status_appearance.background.is_some() {
1444 renderer.fill_quad(
1445 renderer::Quad {
1446 bounds: Rectangle {
1447 x: bounds.x - f32::from(self.button_padding[0]) + indent_padding,
1448 width: bounds.width + f32::from(self.button_padding[0])
1449 - f32::from(self.button_padding[2])
1450 - indent_padding,
1451 ..bounds
1452 },
1453 border: if key_is_focused {
1454 Border {
1455 width: 1.0,
1456 color: appearance.active.text_color,
1457 radius: button_appearance.border.radius,
1458 }
1459 } else {
1460 button_appearance.border
1461 },
1462 shadow: Shadow::default(),
1463 },
1464 status_appearance
1465 .background
1466 .unwrap_or(Background::Color(Color::TRANSPARENT)),
1467 );
1468 }
1469
1470 {
1472 let actual_width = state.internal_layout[nth].1.width;
1473
1474 let offset = match self.button_alignment {
1475 Alignment::Start => None,
1476 Alignment::Center => Some((bounds.width - actual_width) / 2.0),
1477 Alignment::End => Some(bounds.width - actual_width),
1478 };
1479
1480 if let Some(offset) = offset {
1481 bounds.x += offset - f32::from(self.button_padding[0]);
1482 bounds.width = actual_width;
1483 }
1484 }
1485
1486 if let Some(icon) = self.model.icon(key) {
1488 let mut image_bounds = bounds;
1489 let width = f32::from(icon.size);
1490 let offset = width + f32::from(self.button_spacing);
1491 image_bounds.y = center_y - width / 2.0;
1492
1493 draw_icon::<Message>(
1494 renderer,
1495 theme,
1496 style,
1497 cursor,
1498 viewport,
1499 status_appearance.text_color,
1500 Rectangle {
1501 width,
1502 height: width,
1503 ..image_bounds
1504 },
1505 icon.clone(),
1506 );
1507
1508 bounds.x += offset;
1509 } else {
1510 if key_is_active {
1512 if let crate::theme::SegmentedButton::Control = self.style {
1513 let mut image_bounds = bounds;
1514 image_bounds.y = center_y - 8.0;
1515
1516 draw_icon::<Message>(
1517 renderer,
1518 theme,
1519 style,
1520 cursor,
1521 viewport,
1522 status_appearance.text_color,
1523 Rectangle {
1524 width: 16.0,
1525 height: 16.0,
1526 ..image_bounds
1527 },
1528 crate::widget::icon(
1529 match crate::widget::common::object_select().data() {
1530 crate::iced_core::svg::Data::Bytes(bytes) => {
1531 crate::widget::icon::from_svg_bytes(bytes.as_ref())
1532 }
1533 crate::iced_core::svg::Data::Path(path) => {
1534 crate::widget::icon::from_path(path.clone())
1535 }
1536 },
1537 ),
1538 );
1539
1540 let offset = 16.0 + f32::from(self.button_spacing);
1541
1542 bounds.x += offset;
1543 }
1544 }
1545 }
1546
1547 let show_close_button =
1549 (key_is_active || !self.show_close_icon_on_hover || key_is_hovered)
1550 && self.model.is_closable(key);
1551
1552 let close_icon_width = if show_close_button {
1554 f32::from(self.close_icon.size)
1555 } else {
1556 0.0
1557 };
1558
1559 bounds.width = original_bounds.width
1560 - (bounds.x - original_bounds.x)
1561 - close_icon_width
1562 - f32::from(self.button_padding[2]);
1563
1564 bounds.y = center_y;
1565
1566 if self.model.text(key).is_some_and(|text| !text.is_empty()) {
1567 renderer.fill_paragraph(
1569 state.paragraphs[key].raw(),
1570 bounds.position(),
1571 status_appearance.text_color,
1572 Rectangle {
1573 x: bounds.x,
1574 width: bounds.width,
1575 ..original_bounds
1576 },
1577 );
1578 }
1579
1580 if show_close_button {
1582 let close_button_bounds = close_bounds(original_bounds, close_icon_width);
1583
1584 draw_icon::<Message>(
1585 renderer,
1586 theme,
1587 style,
1588 cursor,
1589 viewport,
1590 status_appearance.text_color,
1591 close_button_bounds,
1592 self.close_icon.clone(),
1593 );
1594 }
1595
1596 nth += 1;
1597 });
1598 }
1599
1600 fn overlay<'b>(
1601 &'b mut self,
1602 tree: &'b mut Tree,
1603 layout: iced_core::Layout<'_>,
1604 _renderer: &Renderer,
1605 translation: Vector,
1606 ) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, Renderer>> {
1607 let state = tree.state.downcast_ref::<LocalState>();
1608 let menu_state = state.menu_state.clone();
1609
1610 let Some(entity) = state.show_context else {
1611 return None;
1612 };
1613
1614 let bounds = self
1615 .variant_bounds(state, layout.bounds())
1616 .find_map(|item| match item {
1617 ItemBounds::Button(e, bounds) if e == entity => Some(bounds),
1618 _ => None,
1619 });
1620 let Some(mut bounds) = bounds else {
1621 return None;
1622 };
1623
1624 let Some(context_menu) = self.context_menu.as_mut() else {
1625 return None;
1626 };
1627
1628 if !menu_state.inner.with_data(|data| data.open) {
1629 return None;
1631 }
1632 bounds.x = state.context_cursor.x;
1633 bounds.y = state.context_cursor.y;
1634
1635 Some(
1636 crate::widget::menu::Menu {
1637 tree: menu_state,
1638 menu_roots: std::borrow::Cow::Owned(context_menu.clone()),
1639 bounds_expand: 16,
1640 menu_overlays_parent: true,
1641 close_condition: CloseCondition {
1642 leave: false,
1643 click_outside: true,
1644 click_inside: true,
1645 },
1646 item_width: ItemWidth::Uniform(240),
1647 item_height: ItemHeight::Dynamic(40),
1648 bar_bounds: bounds,
1649 main_offset: -bounds.height as i32,
1650 cross_offset: 0,
1651 root_bounds_list: vec![bounds],
1652 path_highlight: Some(PathHighlight::MenuActive),
1653 style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default),
1654 position: Point::new(translation.x, translation.y),
1655 is_overlay: true,
1656 window_id: window::Id::NONE,
1657 depth: 0,
1658 on_surface_action: None,
1659 }
1660 .overlay(),
1661 )
1662 }
1663
1664 fn drag_destinations(
1665 &self,
1666 _state: &Tree,
1667 layout: Layout<'_>,
1668 _renderer: &Renderer,
1669 dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
1670 ) {
1671 let bounds = layout.bounds();
1672
1673 let my_id = self.get_drag_id();
1674 let dnd_rect = DndDestinationRectangle {
1675 id: my_id,
1676 rectangle: dnd::Rectangle {
1677 x: f64::from(bounds.x),
1678 y: f64::from(bounds.y),
1679 width: f64::from(bounds.width),
1680 height: f64::from(bounds.height),
1681 },
1682 mime_types: self.mimes.clone().into_iter().map(Cow::Owned).collect(),
1683 actions: DndAction::Copy | DndAction::Move,
1684 preferred: DndAction::Move,
1685 };
1686 dnd_rectangles.push(dnd_rect);
1687 }
1688}
1689
1690impl<'a, Variant, SelectionMode, Message> From<SegmentedButton<'a, Variant, SelectionMode, Message>>
1691 for Element<'a, Message>
1692where
1693 SegmentedButton<'a, Variant, SelectionMode, Message>: SegmentedVariant,
1694 Variant: 'static,
1695 Model<SelectionMode>: Selectable,
1696 SelectionMode: Default,
1697 Message: 'static + Clone,
1698{
1699 fn from(mut widget: SegmentedButton<'a, Variant, SelectionMode, Message>) -> Self {
1700 if widget.model.items.is_empty() {
1701 widget.spacing = 0;
1702 }
1703
1704 Self::new(widget)
1705 }
1706}
1707
1708#[derive(Debug, Clone, Copy)]
1709struct Focus {
1710 updated_at: Instant,
1711 now: Instant,
1712}
1713
1714pub struct LocalState {
1716 pub(crate) menu_state: MenuBarState,
1718 pub(super) buttons_visible: usize,
1720 pub(super) buttons_offset: usize,
1722 pub(super) collapsed: bool,
1724 focused_visible: bool,
1726 focused: Option<Focus>,
1728 focused_item: Item,
1730 hovered: Item,
1732 middle_clicked: Option<Item>,
1734 pub(super) known_length: usize,
1736 pub(super) internal_layout: Vec<(Size, Size)>,
1738 paragraphs: SecondaryMap<Entity, crate::Plain>,
1740 text_hashes: SecondaryMap<Entity, u64>,
1742 context_cursor: Point,
1744 show_context: Option<Entity>,
1746 wheel_timestamp: Option<Instant>,
1748 pub dnd_state: crate::widget::dnd_destination::State<Option<Entity>>,
1750 fingers_pressed: HashSet<Finger>,
1752 pressed_item: Option<Item>,
1754}
1755
1756#[derive(Debug, Default, PartialEq)]
1757enum Item {
1758 NextButton,
1759 #[default]
1760 None,
1761 PrevButton,
1762 Set,
1763 Tab(Entity),
1764}
1765
1766impl LocalState {
1767 fn set_focused(&mut self) {
1768 let now = Instant::now();
1769 LAST_FOCUS_UPDATE.with(|x| x.set(now));
1770
1771 self.focused = Some(Focus {
1772 updated_at: now,
1773 now,
1774 });
1775 }
1776}
1777
1778impl operation::Focusable for LocalState {
1779 fn is_focused(&self) -> bool {
1780 self.focused.map_or(false, |f| {
1781 f.updated_at == LAST_FOCUS_UPDATE.with(|f| f.get())
1782 })
1783 }
1784
1785 fn focus(&mut self) {
1786 self.set_focused();
1787 self.focused_visible = true;
1788 self.focused_item = Item::Set;
1789 }
1790
1791 fn unfocus(&mut self) {
1792 self.focused = None;
1793 self.focused_item = Item::None;
1794 self.focused_visible = false;
1795 self.show_context = None;
1796 }
1797}
1798
1799#[derive(Debug, Clone, PartialEq)]
1801pub struct Id(widget::Id);
1802
1803impl Id {
1804 pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
1806 Self(widget::Id::new(id))
1807 }
1808
1809 #[must_use]
1813 #[inline]
1814 pub fn unique() -> Self {
1815 Self(widget::Id::unique())
1816 }
1817}
1818
1819impl From<Id> for widget::Id {
1820 fn from(id: Id) -> Self {
1821 id.0
1822 }
1823}
1824
1825fn close_bounds(area: Rectangle<f32>, icon_size: f32) -> Rectangle<f32> {
1827 Rectangle {
1828 x: area.x + area.width - icon_size - 8.0,
1829 y: area.center_y() - (icon_size / 2.0),
1830 width: icon_size,
1831 height: icon_size,
1832 }
1833}
1834
1835fn next_tab_bounds(bounds: &Rectangle, button_height: f32) -> Rectangle {
1837 Rectangle {
1838 x: bounds.x + bounds.width - button_height,
1839 y: bounds.y,
1840 width: button_height,
1841 height: button_height,
1842 }
1843}
1844
1845fn prev_tab_bounds(bounds: &Rectangle, button_height: f32) -> Rectangle {
1847 Rectangle {
1848 x: bounds.x,
1849 y: bounds.y,
1850 width: button_height,
1851 height: button_height,
1852 }
1853}
1854
1855#[allow(clippy::too_many_arguments)]
1856fn draw_icon<Message: 'static>(
1857 renderer: &mut Renderer,
1858 theme: &crate::Theme,
1859 style: &renderer::Style,
1860 cursor: mouse::Cursor,
1861 viewport: &Rectangle,
1862 color: Color,
1863 bounds: Rectangle,
1864 icon: Icon,
1865) {
1866 let layout_node = layout::Node::new(Size {
1867 width: bounds.width,
1868 height: bounds.width,
1869 })
1870 .move_to(Point {
1871 x: bounds.x,
1872 y: bounds.y,
1873 });
1874
1875 Widget::<Message, crate::Theme, Renderer>::draw(
1876 Element::<Message>::from(icon.clone()).as_widget(),
1877 &Tree::empty(),
1878 renderer,
1879 theme,
1880 &renderer::Style {
1881 icon_color: color,
1882 text_color: color,
1883 scale_factor: style.scale_factor,
1884 },
1885 Layout::new(&layout_node),
1886 cursor,
1887 viewport,
1888 );
1889}
1890
1891fn left_button_released(event: &Event) -> bool {
1892 matches!(
1893 event,
1894 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left,))
1895 )
1896}
1897
1898fn right_button_released(event: &Event) -> bool {
1899 matches!(
1900 event,
1901 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right,))
1902 )
1903}
1904
1905fn is_pressed(event: &Event) -> bool {
1906 matches!(
1907 event,
1908 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
1909 | Event::Touch(touch::Event::FingerPressed { .. })
1910 )
1911}
1912
1913fn is_lifted(event: &Event) -> bool {
1914 matches!(
1915 event,
1916 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left,))
1917 | Event::Touch(touch::Event::FingerLifted { .. })
1918 )
1919}
1920
1921fn touch_lifted(event: &Event) -> bool {
1922 matches!(event, Event::Touch(touch::Event::FingerLifted { .. }))
1923}