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