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