maycoon_widgets/
button.rs1use maycoon_core::app::context::AppContext;
2use maycoon_core::app::info::AppInfo;
3use maycoon_core::app::update::Update;
4use maycoon_core::layout;
5use maycoon_core::layout::{LayoutNode, LayoutStyle, LengthPercentage, StyleNode};
6use maycoon_core::signal::MaybeSignal;
7use maycoon_core::vgi::kurbo::{Affine, Rect, RoundedRect, RoundedRectRadii, Vec2};
8use maycoon_core::vgi::{Brush, Scene};
9use maycoon_core::widget::{BoxedWidget, Widget, WidgetChildExt, WidgetLayoutExt};
10use maycoon_core::window::{ElementState, MouseButton};
11use maycoon_theme::id::WidgetId;
12use maycoon_theme::theme::Theme;
13
14pub struct Button {
26 child: BoxedWidget,
27 state: ButtonState,
28 on_pressed: MaybeSignal<Update>,
29 layout_style: MaybeSignal<LayoutStyle>,
30}
31
32impl Button {
33 #[inline(always)]
35 pub fn new(child: impl Widget + 'static) -> Self {
36 Self {
37 child: Box::new(child),
38 state: ButtonState::Idle,
39 on_pressed: MaybeSignal::value(Update::empty()),
40 layout_style: LayoutStyle {
41 padding: layout::Rect::<LengthPercentage> {
42 left: LengthPercentage::length(12.0),
43 right: LengthPercentage::length(12.0),
44 top: LengthPercentage::length(2.0),
45 bottom: LengthPercentage::length(10.0),
46 },
47 ..Default::default()
48 }
49 .into(),
50 }
51 }
52
53 #[inline(always)]
55 pub fn with_on_pressed(mut self, on_pressed: impl Into<MaybeSignal<Update>>) -> Self {
56 self.on_pressed = on_pressed.into();
57 self
58 }
59}
60
61impl WidgetChildExt for Button {
62 #[inline(always)]
63 fn set_child(&mut self, child: impl Widget + 'static) {
64 self.child = Box::new(child);
65 }
66}
67
68impl WidgetLayoutExt for Button {
69 #[inline(always)]
70 fn set_layout_style(&mut self, layout_style: impl Into<MaybeSignal<LayoutStyle>>) {
71 self.layout_style = layout_style.into();
72 }
73}
74
75impl Widget for Button {
76 fn render(
77 &mut self,
78 scene: &mut dyn Scene,
79 theme: &mut dyn Theme,
80 layout_node: &LayoutNode,
81 info: &AppInfo,
82 context: AppContext,
83 ) {
84 let brush = if let Some(style) = theme.of(self.widget_id()) {
85 match self.state {
86 ButtonState::Idle => Brush::Solid(style.get_color("color_idle").unwrap()),
87 ButtonState::Hovered => Brush::Solid(style.get_color("color_hovered").unwrap()),
88 ButtonState::Pressed => Brush::Solid(style.get_color("color_pressed").unwrap()),
89 ButtonState::Released => Brush::Solid(style.get_color("color_hovered").unwrap()),
90 }
91 } else {
92 Brush::Solid(match self.state {
93 ButtonState::Idle => theme.defaults().interactive().inactive(),
94 ButtonState::Hovered => theme.defaults().interactive().hover(),
95 ButtonState::Pressed => theme.defaults().interactive().active(),
96 ButtonState::Released => theme.defaults().interactive().hover(),
97 })
98 };
99
100 scene.draw_rounded_rect(
101 &brush,
102 None,
103 None,
104 &RoundedRect::from_rect(
105 Rect::new(
106 layout_node.layout.location.x as f64,
107 layout_node.layout.location.y as f64,
108 (layout_node.layout.location.x + layout_node.layout.size.width) as f64,
109 (layout_node.layout.location.y + layout_node.layout.size.height) as f64,
110 ),
111 RoundedRectRadii::from_single_radius(10.0),
112 ),
113 );
114
115 {
116 theme.globals_mut().invert_text_color = true;
117
118 let mut child_scene = scene.dyn_clone();
119 child_scene.reset();
120
121 self.child.render(
122 child_scene.as_mut(),
123 theme,
124 &layout_node.children[0],
125 info,
126 context,
127 );
128
129 scene.append(
130 child_scene.as_ref(),
131 Some(Affine::translate(Vec2::new(
132 layout_node.layout.location.x as f64,
133 layout_node.layout.location.y as f64,
134 ))),
135 );
136
137 theme.globals_mut().invert_text_color = false;
138 }
139 }
140
141 #[inline(always)]
142 fn layout_style(&self) -> StyleNode {
143 StyleNode {
144 style: self.layout_style.get().clone(),
145 children: vec![self.child.layout_style()],
146 }
147 }
148
149 fn update(&mut self, layout: &LayoutNode, _: AppContext, info: &AppInfo) -> Update {
150 let mut update = Update::empty();
151 let old_state = self.state;
152
153 if let Some(cursor) = info.cursor_pos
155 && layout::intersects(cursor, &layout.layout)
156 {
157 if self.state != ButtonState::Pressed {
159 self.state = ButtonState::Hovered;
160 }
161
162 for (_, btn, el) in &info.buttons {
164 if *btn == MouseButton::Left {
165 match el {
166 ElementState::Pressed => {
167 self.state = ButtonState::Pressed;
168 },
169
170 ElementState::Released => {
172 self.state = ButtonState::Released;
173 update |= *self.on_pressed.get();
174 },
175 }
176 }
177 }
178 } else {
179 self.state = ButtonState::Idle;
181 }
182
183 if old_state != self.state {
185 update |= Update::DRAW;
186 }
187
188 update
189 }
190
191 #[inline(always)]
192 fn widget_id(&self) -> WidgetId {
193 WidgetId::new("maycoon-widgets", "Button")
194 }
195}
196
197#[derive(Copy, Clone, Eq, PartialEq, Debug)]
199pub enum ButtonState {
200 Idle,
202 Hovered,
204 Pressed,
206 Released,
209}