1use iced_runtime::core::widget::Id;
10use iced_runtime::{Action, Task, keyboard, task};
11
12use iced_core::event::{self, Event};
13use iced_core::renderer::{self, Quad, Renderer};
14use iced_core::touch;
15use iced_core::widget::Operation;
16use iced_core::widget::tree::{self, Tree};
17use iced_core::{
18 Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget,
19};
20use iced_core::{Border, mouse};
21use iced_core::{Shadow, overlay};
22use iced_core::{layout, svg};
23use iced_renderer::core::widget::operation;
24
25use crate::theme::THEME;
26
27pub use super::style::{Catalog, Style};
28
29enum Variant<Message> {
31 Normal,
32 Image {
33 close_icon: svg::Handle,
34 on_remove: Option<Message>,
35 },
36}
37
38#[allow(missing_debug_implementations)]
40#[must_use]
41pub struct Button<'a, Message> {
42 id: Id,
43 #[cfg(feature = "a11y")]
44 name: Option<std::borrow::Cow<'a, str>>,
45 #[cfg(feature = "a11y")]
46 description: Option<iced_accessibility::Description<'a>>,
47 #[cfg(feature = "a11y")]
48 label: Option<Vec<iced_accessibility::accesskit::NodeId>>,
49 content: crate::Element<'a, Message>,
50 on_press: Option<Box<dyn Fn(Vector, Rectangle) -> Message + 'a>>,
51 on_press_down: Option<Box<dyn Fn(Vector, Rectangle) -> Message + 'a>>,
52 width: Length,
53 height: Length,
54 padding: Padding,
55 selected: bool,
56 style: crate::theme::Button,
57 variant: Variant<Message>,
58 force_enabled: bool,
59}
60
61impl<'a, Message: Clone + 'a> Button<'a, Message> {
62 pub(super) fn new(content: impl Into<crate::Element<'a, Message>>) -> Self {
64 Self {
65 id: Id::unique(),
66 #[cfg(feature = "a11y")]
67 name: None,
68 #[cfg(feature = "a11y")]
69 description: None,
70 #[cfg(feature = "a11y")]
71 label: None,
72 content: content.into(),
73 on_press: None,
74 on_press_down: None,
75 width: Length::Shrink,
76 height: Length::Shrink,
77 padding: Padding::new(5.0),
78 selected: false,
79 style: crate::theme::Button::default(),
80 variant: Variant::Normal,
81 force_enabled: false,
82 }
83 }
84
85 pub fn new_image(
86 content: impl Into<crate::Element<'a, Message>>,
87 on_remove: Option<Message>,
88 ) -> Self {
89 Self {
90 id: Id::unique(),
91 #[cfg(feature = "a11y")]
92 name: None,
93 #[cfg(feature = "a11y")]
94 description: None,
95 force_enabled: false,
96 #[cfg(feature = "a11y")]
97 label: None,
98 content: content.into(),
99 on_press: None,
100 on_press_down: None,
101 width: Length::Shrink,
102 height: Length::Shrink,
103 padding: Padding::new(5.0),
104 selected: false,
105 style: crate::theme::Button::default(),
106 variant: Variant::Image {
107 on_remove,
108 close_icon: crate::widget::icon::from_name("window-close-symbolic")
109 .size(8)
110 .icon()
111 .into_svg_handle()
112 .unwrap_or_else(|| {
113 let bytes: &'static [u8] = &[];
114 iced_core::svg::Handle::from_memory(bytes)
115 }),
116 },
117 }
118 }
119
120 #[inline]
122 pub fn id(mut self, id: Id) -> Self {
123 self.id = id;
124 self
125 }
126
127 #[inline]
129 pub fn width(mut self, width: impl Into<Length>) -> Self {
130 self.width = width.into();
131 self
132 }
133
134 #[inline]
136 pub fn height(mut self, height: impl Into<Length>) -> Self {
137 self.height = height.into();
138 self
139 }
140
141 #[inline]
143 pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
144 self.padding = padding.into();
145 self
146 }
147
148 #[inline]
152 pub fn on_press(mut self, on_press: Message) -> Self {
153 self.on_press = Some(Box::new(move |_, _| on_press.clone()));
154 self
155 }
156
157 #[inline]
161 pub fn on_press_with_rectangle(
162 mut self,
163 on_press: impl Fn(Vector, Rectangle) -> Message + 'a,
164 ) -> Self {
165 self.on_press = Some(Box::new(on_press));
166 self
167 }
168
169 #[inline]
173 pub fn on_press_down(mut self, on_press: Message) -> Self {
174 self.on_press_down = Some(Box::new(move |_, _| on_press.clone()));
175 self
176 }
177
178 #[inline]
182 pub fn on_press_down_with_rectange(
183 mut self,
184 on_press: impl Fn(Vector, Rectangle) -> Message + 'a,
185 ) -> Self {
186 self.on_press_down = Some(Box::new(on_press));
187 self
188 }
189
190 #[inline]
195 pub fn on_press_maybe(mut self, on_press: Option<Message>) -> Self {
196 if let Some(m) = on_press {
197 self.on_press(m)
198 } else {
199 self.on_press = None;
200 self
201 }
202 }
203
204 #[inline]
208 pub fn on_press_maybe_with_rectangle(
209 mut self,
210 on_press: impl Fn(Vector, Rectangle) -> Message + 'a,
211 ) -> Self {
212 self.on_press = Some(Box::new(on_press));
213 self
214 }
215
216 #[inline]
221 pub fn on_press_down_maybe(mut self, on_press: Option<Message>) -> Self {
222 if let Some(m) = on_press {
223 self.on_press(m)
224 } else {
225 self.on_press_down = None;
226 self
227 }
228 }
229
230 #[inline]
234 pub fn on_press_down_maybe_with_rectangle(
235 mut self,
236 on_press: impl Fn(Vector, Rectangle) -> Message + 'a,
237 ) -> Self {
238 self.on_press_down = Some(Box::new(on_press));
239 self
240 }
241
242 #[inline]
244 pub fn force_enabled(mut self, enabled: bool) -> Self {
245 self.force_enabled = enabled;
246 self
247 }
248
249 #[inline]
253 pub fn selected(mut self, selected: bool) -> Self {
254 self.selected = selected;
255
256 self
257 }
258
259 #[inline]
261 pub fn class(mut self, style: crate::theme::Button) -> Self {
262 self.style = style;
263 self
264 }
265
266 #[cfg(feature = "a11y")]
267 pub fn name(mut self, name: impl Into<std::borrow::Cow<'a, str>>) -> Self {
269 self.name = Some(name.into());
270 self
271 }
272
273 #[cfg(feature = "a11y")]
274 pub fn description_widget<T: iced_accessibility::Describes>(mut self, description: &T) -> Self {
276 self.description = Some(iced_accessibility::Description::Id(
277 description.description(),
278 ));
279 self
280 }
281
282 #[cfg(feature = "a11y")]
283 pub fn description(mut self, description: impl Into<std::borrow::Cow<'a, str>>) -> Self {
285 self.description = Some(iced_accessibility::Description::Text(description.into()));
286 self
287 }
288
289 #[cfg(feature = "a11y")]
290 pub fn label(mut self, label: &dyn iced_accessibility::Labels) -> Self {
292 self.label = Some(label.label().into_iter().map(|l| l.into()).collect());
293 self
294 }
295}
296
297impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
298 for Button<'a, Message>
299{
300 fn tag(&self) -> tree::Tag {
301 tree::Tag::of::<State>()
302 }
303
304 fn state(&self) -> tree::State {
305 tree::State::new(State::new())
306 }
307
308 fn children(&self) -> Vec<Tree> {
309 vec![Tree::new(&self.content)]
310 }
311
312 fn diff(&mut self, tree: &mut Tree) {
313 tree.diff_children(std::slice::from_mut(&mut self.content));
314 }
315
316 fn size(&self) -> iced_core::Size<Length> {
317 iced_core::Size::new(self.width, self.height)
318 }
319
320 fn layout(
321 &self,
322 tree: &mut Tree,
323 renderer: &crate::Renderer,
324 limits: &layout::Limits,
325 ) -> layout::Node {
326 layout(
327 renderer,
328 limits,
329 self.width,
330 self.height,
331 self.padding,
332 |renderer, limits| {
333 self.content
334 .as_widget()
335 .layout(&mut tree.children[0], renderer, limits)
336 },
337 )
338 }
339
340 fn operate(
341 &self,
342 tree: &mut Tree,
343 layout: Layout<'_>,
344 renderer: &crate::Renderer,
345 operation: &mut dyn Operation<()>,
346 ) {
347 operation.container(None, layout.bounds(), &mut |operation| {
348 self.content.as_widget().operate(
349 &mut tree.children[0],
350 layout
351 .children()
352 .next()
353 .unwrap()
354 .with_virtual_offset(layout.virtual_offset()),
355 renderer,
356 operation,
357 );
358 });
359 let state = tree.state.downcast_mut::<State>();
360 operation.focusable(state, Some(&self.id));
361 }
362
363 fn on_event(
364 &mut self,
365 tree: &mut Tree,
366 event: Event,
367 layout: Layout<'_>,
368 cursor: mouse::Cursor,
369 renderer: &crate::Renderer,
370 clipboard: &mut dyn Clipboard,
371 shell: &mut Shell<'_, Message>,
372 viewport: &Rectangle,
373 ) -> event::Status {
374 if let Variant::Image {
375 on_remove: Some(on_remove),
376 ..
377 } = &self.variant
378 {
379 match event {
381 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
382 | Event::Touch(touch::Event::FingerPressed { .. }) => {
383 if let Some(position) = cursor.position() {
384 if removal_bounds(layout.bounds(), 4.0).contains(position) {
385 shell.publish(on_remove.clone());
386 return event::Status::Captured;
387 }
388 }
389 }
390
391 _ => (),
392 }
393 }
394
395 if self.content.as_widget_mut().on_event(
396 &mut tree.children[0],
397 event.clone(),
398 layout
399 .children()
400 .next()
401 .unwrap()
402 .with_virtual_offset(layout.virtual_offset()),
403 cursor,
404 renderer,
405 clipboard,
406 shell,
407 viewport,
408 ) == event::Status::Captured
409 {
410 return event::Status::Captured;
411 }
412
413 update(
414 self.id.clone(),
415 event,
416 layout,
417 cursor,
418 shell,
419 self.on_press.as_deref(),
420 self.on_press_down.as_deref(),
421 || tree.state.downcast_mut::<State>(),
422 )
423 }
424
425 #[allow(clippy::too_many_lines)]
426 fn draw(
427 &self,
428 tree: &Tree,
429 renderer: &mut crate::Renderer,
430 theme: &crate::Theme,
431 renderer_style: &renderer::Style,
432 layout: Layout<'_>,
433 cursor: mouse::Cursor,
434 viewport: &Rectangle,
435 ) {
436 let bounds = layout.bounds();
437 if !viewport.intersects(&bounds) {
438 return;
439 }
440
441 let Some(content_layout) = layout.children().next() else {
443 return;
444 };
445
446 let mut headerbar_alpha = None;
447
448 let is_enabled =
449 self.on_press.is_some() || self.on_press_down.is_some() || self.force_enabled;
450 let is_mouse_over = cursor.position().is_some_and(|p| bounds.contains(p));
451
452 let state = tree.state.downcast_ref::<State>();
453
454 let styling = if !is_enabled {
455 theme.disabled(&self.style)
456 } else if is_mouse_over {
457 if state.is_pressed {
458 if !self.selected && matches!(self.style, crate::theme::Button::HeaderBar) {
459 headerbar_alpha = Some(0.8);
460 }
461
462 theme.pressed(state.is_focused, self.selected, &self.style)
463 } else {
464 if !self.selected && matches!(self.style, crate::theme::Button::HeaderBar) {
465 headerbar_alpha = Some(0.8);
466 }
467 theme.hovered(state.is_focused, self.selected, &self.style)
468 }
469 } else {
470 if !self.selected && matches!(self.style, crate::theme::Button::HeaderBar) {
471 headerbar_alpha = Some(0.75);
472 }
473
474 theme.active(state.is_focused, self.selected, &self.style)
475 };
476
477 let mut icon_color = styling.icon_color.unwrap_or(renderer_style.icon_color);
478
479 let mut text_color = if matches!(self.style, crate::theme::Button::MenuRoot) {
481 icon_color
482 } else {
483 styling.text_color.unwrap_or(renderer_style.text_color)
484 };
485
486 if let Some(alpha) = headerbar_alpha {
487 icon_color.a = alpha;
488 text_color.a = alpha;
489 }
490
491 draw::<_, crate::Theme>(
492 renderer,
493 bounds,
494 *viewport,
495 &styling,
496 |renderer, _styling| {
497 self.content.as_widget().draw(
498 &tree.children[0],
499 renderer,
500 theme,
501 &renderer::Style {
502 icon_color,
503 text_color,
504 scale_factor: renderer_style.scale_factor,
505 },
506 content_layout.with_virtual_offset(layout.virtual_offset()),
507 cursor,
508 &viewport.intersection(&bounds).unwrap_or_default(),
509 );
510 },
511 matches!(self.variant, Variant::Image { .. }),
512 );
513
514 if let Variant::Image {
515 close_icon,
516 on_remove,
517 } = &self.variant
518 {
519 renderer.with_layer(*viewport, |renderer| {
520 let selection_background = theme.selection_background();
521
522 let c_rad = THEME.lock().unwrap().cosmic().corner_radii;
523
524 if self.selected {
525 renderer.fill_quad(
526 Quad {
527 bounds: Rectangle {
528 width: 24.0,
529 height: 20.0,
530 x: bounds.x + styling.border_width,
531 y: bounds.y + (bounds.height - 20.0 - styling.border_width),
532 },
533 border: Border {
534 radius: [
535 c_rad.radius_0[0],
536 c_rad.radius_s[1],
537 c_rad.radius_0[2],
538 c_rad.radius_s[3],
539 ]
540 .into(),
541 ..Default::default()
542 },
543 shadow: Shadow::default(),
544 },
545 selection_background,
546 );
547
548 let svg_handle = svg::Svg::new(crate::widget::common::object_select().clone())
549 .color(icon_color);
550 let bounds = Rectangle {
551 width: 16.0,
552 height: 16.0,
553 x: bounds.x + 5.0 + styling.border_width,
554 y: bounds.y + (bounds.height - 18.0 - styling.border_width),
555 };
556 if bounds.intersects(viewport) {
557 iced_core::svg::Renderer::draw_svg(renderer, svg_handle, bounds);
558 }
559 }
560
561 if on_remove.is_some() {
562 if let Some(position) = cursor.position() {
563 if bounds.contains(position) {
564 let bounds = removal_bounds(layout.bounds(), 4.0);
565 renderer.fill_quad(
566 renderer::Quad {
567 bounds,
568 shadow: Shadow::default(),
569 border: Border {
570 radius: c_rad.radius_m.into(),
571 ..Default::default()
572 },
573 },
574 selection_background,
575 );
576 let svg_handle = svg::Svg::new(close_icon.clone()).color(icon_color);
577 iced_core::svg::Renderer::draw_svg(
578 renderer,
579 svg_handle,
580 Rectangle {
581 width: 16.0,
582 height: 16.0,
583 x: bounds.x + 4.0,
584 y: bounds.y + 4.0,
585 },
586 );
587 }
588 }
589 }
590 });
591 }
592 }
593
594 fn mouse_interaction(
595 &self,
596 _tree: &Tree,
597 layout: Layout<'_>,
598 cursor: mouse::Cursor,
599 _viewport: &Rectangle,
600 _renderer: &crate::Renderer,
601 ) -> mouse::Interaction {
602 mouse_interaction(
603 layout.with_virtual_offset(layout.virtual_offset()),
604 cursor,
605 self.on_press.is_some(),
606 )
607 }
608
609 fn overlay<'b>(
610 &'b mut self,
611 tree: &'b mut Tree,
612 layout: Layout<'_>,
613 renderer: &crate::Renderer,
614 mut translation: Vector,
615 ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
616 let position = layout.bounds().position();
617 translation.x += position.x;
618 translation.y += position.y;
619 self.content.as_widget_mut().overlay(
620 &mut tree.children[0],
621 layout
622 .children()
623 .next()
624 .unwrap()
625 .with_virtual_offset(layout.virtual_offset()),
626 renderer,
627 translation,
628 )
629 }
630
631 #[cfg(feature = "a11y")]
632 fn a11y_nodes(
634 &self,
635 layout: Layout<'_>,
636 state: &Tree,
637 p: mouse::Cursor,
638 ) -> iced_accessibility::A11yTree {
639 use iced_accessibility::{
640 A11yNode, A11yTree,
641 accesskit::{Action, DefaultActionVerb, NodeBuilder, NodeId, Rect, Role},
642 };
643 if matches!(state.state, iced_core::widget::tree::State::None) {
645 tracing::info!("Button state is missing.");
646 return A11yTree::default();
647 }
648
649 let child_layout = layout.children().next().unwrap();
650 let child_tree = state.children.first();
651
652 let Rectangle {
653 x,
654 y,
655 width,
656 height,
657 } = layout.bounds();
658 let bounds = Rect::new(x as f64, y as f64, (x + width) as f64, (y + height) as f64);
659 let is_hovered = state.state.downcast_ref::<State>().is_hovered;
660
661 let mut node = NodeBuilder::new(Role::Button);
662 node.add_action(Action::Focus);
663 node.add_action(Action::Default);
664 node.set_bounds(bounds);
665 if let Some(name) = self.name.as_ref() {
666 node.set_name(name.clone());
667 }
668 match self.description.as_ref() {
669 Some(iced_accessibility::Description::Id(id)) => {
670 node.set_described_by(id.iter().cloned().map(NodeId::from).collect::<Vec<_>>());
671 }
672 Some(iced_accessibility::Description::Text(text)) => {
673 node.set_description(text.clone());
674 }
675 None => {}
676 }
677
678 if let Some(label) = self.label.as_ref() {
679 node.set_labelled_by(label.clone());
680 }
681
682 if self.on_press.is_none() {
683 node.set_disabled();
684 }
685 if is_hovered {
686 node.set_hovered();
687 }
688 node.set_default_action_verb(DefaultActionVerb::Click);
689
690 if let Some(child_tree) = child_tree.map(|child_tree| {
691 self.content.as_widget().a11y_nodes(
692 child_layout.with_virtual_offset(layout.virtual_offset()),
693 child_tree,
694 p,
695 )
696 }) {
697 A11yTree::node_with_child_tree(A11yNode::new(node, self.id.clone()), child_tree)
698 } else {
699 A11yTree::leaf(node, self.id.clone())
700 }
701 }
702
703 fn id(&self) -> Option<Id> {
704 Some(self.id.clone())
705 }
706
707 fn set_id(&mut self, id: Id) {
708 self.id = id;
709 }
710}
711
712impl<'a, Message: Clone + 'a> From<Button<'a, Message>> for crate::Element<'a, Message> {
713 fn from(button: Button<'a, Message>) -> Self {
714 Self::new(button)
715 }
716}
717
718#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
720#[allow(clippy::struct_field_names)]
721pub struct State {
722 is_hovered: bool,
723 is_pressed: bool,
724 is_focused: bool,
725}
726
727impl State {
728 #[inline]
730 pub fn new() -> Self {
731 Self::default()
732 }
733
734 #[inline]
736 pub fn is_focused(self) -> bool {
737 self.is_focused
738 }
739
740 #[inline]
742 pub fn is_hovered(self) -> bool {
743 self.is_hovered
744 }
745
746 #[inline]
748 pub fn focus(&mut self) {
749 self.is_focused = true;
750 }
751
752 #[inline]
754 pub fn unfocus(&mut self) {
755 self.is_focused = false;
756 }
757}
758
759#[allow(clippy::needless_pass_by_value, clippy::too_many_arguments)]
762pub fn update<'a, Message: Clone>(
763 _id: Id,
764 event: Event,
765 layout: Layout<'_>,
766 cursor: mouse::Cursor,
767 shell: &mut Shell<'_, Message>,
768 on_press: Option<&dyn Fn(Vector, Rectangle) -> Message>,
769 on_press_down: Option<&dyn Fn(Vector, Rectangle) -> Message>,
770 state: impl FnOnce() -> &'a mut State,
771) -> event::Status {
772 match event {
773 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
774 | Event::Touch(touch::Event::FingerPressed { .. }) => {
775 let state = state();
777 state.unfocus();
778
779 if on_press.is_some() || on_press_down.is_some() {
780 let bounds = layout.bounds();
781
782 if cursor.is_over(bounds) {
783 state.is_pressed = true;
784
785 if let Some(on_press_down) = on_press_down {
786 let msg = (on_press_down)(layout.virtual_offset(), layout.bounds());
787 shell.publish(msg);
788 }
789
790 return event::Status::Captured;
791 }
792 }
793 }
794 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
795 | Event::Touch(touch::Event::FingerLifted { .. }) => {
796 if let Some(on_press) = on_press.clone() {
797 let state = state();
798
799 if state.is_pressed {
800 state.is_pressed = false;
801
802 let bounds = layout.bounds();
803
804 if cursor.is_over(bounds) {
805 let msg = (on_press)(layout.virtual_offset(), layout.bounds());
806 shell.publish(msg);
807 }
808
809 return event::Status::Captured;
810 }
811 } else if on_press_down.is_some() {
812 let state = state();
813 state.is_pressed = false;
814 }
815 }
816 #[cfg(feature = "a11y")]
817 Event::A11y(event_id, iced_accessibility::accesskit::ActionRequest { action, .. }) => {
818 let state = state();
819 if let Some(Some(on_press)) = (event_id == event_id
820 && matches!(action, iced_accessibility::accesskit::Action::Default))
821 .then(|| on_press.clone())
822 {
823 state.is_pressed = false;
824 let msg = (on_press)(layout.virtual_offset(), layout.bounds());
825
826 shell.publish(msg);
827 }
828 return event::Status::Captured;
829 }
830 Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
831 if let Some(on_press) = on_press.clone() {
832 let state = state();
833 if state.is_focused && key == keyboard::Key::Named(keyboard::key::Named::Enter) {
834 state.is_pressed = true;
835 let msg = (on_press)(layout.virtual_offset(), layout.bounds());
836
837 shell.publish(msg);
838 return event::Status::Captured;
839 }
840 }
841 }
842 Event::Touch(touch::Event::FingerLost { .. }) | Event::Mouse(mouse::Event::CursorLeft) => {
843 let state = state();
844 state.is_hovered = false;
845 state.is_pressed = false;
846 }
847 _ => {}
848 }
849
850 event::Status::Ignored
851}
852
853#[allow(clippy::too_many_arguments)]
854pub fn draw<Renderer: iced_core::Renderer, Theme>(
855 renderer: &mut Renderer,
856 bounds: Rectangle,
857 viewport_bounds: Rectangle,
858 styling: &super::style::Style,
859 draw_contents: impl FnOnce(&mut Renderer, &Style),
860 is_image: bool,
861) where
862 Theme: super::style::Catalog,
863{
864 let doubled_border_width = styling.border_width * 2.0;
865 let doubled_outline_width = styling.outline_width * 2.0;
866
867 if styling.outline_width > 0.0 {
868 renderer.fill_quad(
869 renderer::Quad {
870 bounds: Rectangle {
871 x: bounds.x - styling.border_width - styling.outline_width,
872 y: bounds.y - styling.border_width - styling.outline_width,
873 width: bounds.width + doubled_border_width + doubled_outline_width,
874 height: bounds.height + doubled_border_width + doubled_outline_width,
875 },
876 border: Border {
877 width: styling.outline_width,
878 color: styling.outline_color,
879 radius: styling.border_radius,
880 },
881 shadow: Shadow::default(),
882 },
883 Color::TRANSPARENT,
884 );
885 }
886
887 if styling.background.is_some() || styling.border_width > 0.0 {
888 if styling.shadow_offset != Vector::default() {
889 renderer.fill_quad(
891 renderer::Quad {
892 bounds: Rectangle {
893 x: bounds.x + styling.shadow_offset.x,
894 y: bounds.y + styling.shadow_offset.y,
895 width: bounds.width,
896 height: bounds.height,
897 },
898 border: Border {
899 radius: styling.border_radius,
900 ..Default::default()
901 },
902 shadow: Shadow::default(),
903 },
904 Background::Color([0.0, 0.0, 0.0, 0.5].into()),
905 );
906 }
907
908 if let Some(background) = styling.background {
910 renderer.fill_quad(
911 renderer::Quad {
912 bounds,
913 border: Border {
914 radius: styling.border_radius,
915 ..Default::default()
916 },
917 shadow: Shadow::default(),
918 },
919 background,
920 );
921 }
922
923 if let Some(overlay) = styling.overlay {
925 renderer.fill_quad(
926 renderer::Quad {
927 bounds,
928 border: Border {
929 radius: styling.border_radius,
930 ..Default::default()
931 },
932 shadow: Shadow::default(),
933 },
934 overlay,
935 );
936 }
937
938 draw_contents(renderer, styling);
940
941 let mut clipped_bounds = viewport_bounds.intersection(&bounds).unwrap_or_default();
942 clipped_bounds.height += styling.border_width;
943 clipped_bounds.width += 1.0;
944
945 renderer.with_layer(clipped_bounds, |renderer| {
947 renderer.fill_quad(
948 renderer::Quad {
949 bounds,
950 border: Border {
951 width: styling.border_width,
952 color: styling.border_color,
953 radius: styling.border_radius,
954 },
955 shadow: Shadow::default(),
956 },
957 Color::TRANSPARENT,
958 );
959 })
960 } else {
961 draw_contents(renderer, styling);
962 }
963}
964
965pub fn layout<Renderer>(
967 renderer: &Renderer,
968 limits: &layout::Limits,
969 width: Length,
970 height: Length,
971 padding: Padding,
972 layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
973) -> layout::Node {
974 let limits = limits.width(width).height(height);
975
976 let mut content = layout_content(renderer, &limits.shrink(padding));
977 let padding = padding.fit(content.size(), limits.max());
978 let size = limits
979 .shrink(padding)
980 .resolve(width, height, content.size())
981 .expand(padding);
982
983 content = content.move_to(Point::new(padding.left, padding.top));
984
985 layout::Node::with_children(size, vec![content])
986}
987
988#[must_use]
990pub fn mouse_interaction(
991 layout: Layout<'_>,
992 cursor: mouse::Cursor,
993 is_enabled: bool,
994) -> mouse::Interaction {
995 let is_mouse_over = cursor.is_over(layout.bounds());
996
997 if is_mouse_over && is_enabled {
998 mouse::Interaction::Pointer
999 } else {
1000 mouse::Interaction::default()
1001 }
1002}
1003
1004pub fn focus<Message: 'static>(id: Id) -> Task<Message> {
1006 task::effect(Action::Widget(Box::new(operation::focusable::focus(id))))
1007}
1008
1009impl operation::Focusable for State {
1010 #[inline]
1011 fn is_focused(&self) -> bool {
1012 Self::is_focused(*self)
1013 }
1014
1015 #[inline]
1016 fn focus(&mut self) {
1017 Self::focus(self);
1018 }
1019
1020 #[inline]
1021 fn unfocus(&mut self) {
1022 Self::unfocus(self);
1023 }
1024}
1025
1026fn removal_bounds(bounds: Rectangle, offset: f32) -> Rectangle {
1027 Rectangle {
1028 x: bounds.x + bounds.width - 12.0 - offset,
1029 y: bounds.y - 12.0 + offset,
1030 width: 24.0,
1031 height: 24.0,
1032 }
1033}