yakui_widgets/widgets/
button.rs1use std::borrow::Cow;
2
3use yakui_core::event::{EventInterest, EventResponse, WidgetEvent};
4use yakui_core::geometry::Color;
5use yakui_core::input::MouseButton;
6use yakui_core::widget::{EventContext, Widget};
7use yakui_core::{Alignment, Response};
8
9use crate::colors;
10use crate::style::{TextAlignment, TextStyle};
11use crate::util::widget;
12use crate::widgets::Pad;
13
14use super::{RenderText, RoundRect};
15
16#[derive(Debug)]
30#[non_exhaustive]
31#[must_use = "yakui widgets do nothing if you don't `show` them"]
32pub struct Button {
33 pub text: Cow<'static, str>,
34 pub padding: Pad,
35 pub border_radius: f32,
36 pub style: DynamicButtonStyle,
37 pub hover_style: DynamicButtonStyle,
38 pub down_style: DynamicButtonStyle,
39}
40
41#[derive(Debug, Clone)]
43#[non_exhaustive]
44pub struct DynamicButtonStyle {
45 pub text: TextStyle,
46 pub fill: Color,
47}
48
49impl Default for DynamicButtonStyle {
50 fn default() -> Self {
51 let mut text = TextStyle::label();
52 text.align = TextAlignment::Center;
53
54 Self {
55 text,
56 fill: Color::GRAY,
57 }
58 }
59}
60
61impl Button {
62 pub fn unstyled(text: impl Into<Cow<'static, str>>) -> Self {
63 Self {
64 text: text.into(),
65 padding: Pad::ZERO,
66 border_radius: 0.0,
67 style: DynamicButtonStyle::default(),
68 hover_style: DynamicButtonStyle::default(),
69 down_style: DynamicButtonStyle::default(),
70 }
71 }
72
73 pub fn styled(text: impl Into<Cow<'static, str>>) -> Self {
74 let style = DynamicButtonStyle {
75 fill: colors::BACKGROUND_3,
76 ..Default::default()
77 };
78
79 let hover_style = DynamicButtonStyle {
80 fill: colors::BACKGROUND_3.adjust(1.2),
81 ..Default::default()
82 };
83
84 let down_style = DynamicButtonStyle {
85 fill: colors::BACKGROUND_3.adjust(0.8),
86 ..Default::default()
87 };
88
89 let mut text_style = TextStyle::label();
90 text_style.align = TextAlignment::Center;
91
92 Self {
93 text: text.into(),
94 padding: Pad::balanced(20.0, 10.0),
95 border_radius: 6.0,
96 style,
97 hover_style,
98 down_style,
99 }
100 }
101
102 pub fn show(self) -> Response<ButtonResponse> {
103 widget::<ButtonWidget>(self)
104 }
105}
106
107#[derive(Debug)]
108pub struct ButtonWidget {
109 props: Button,
110 hovering: bool,
111 mouse_down: bool,
112 clicked: bool,
113}
114
115#[derive(Debug)]
116pub struct ButtonResponse {
117 pub hovering: bool,
118 pub clicked: bool,
119}
120
121impl Widget for ButtonWidget {
122 type Props<'a> = Button;
123 type Response = ButtonResponse;
124
125 fn new() -> Self {
126 Self {
127 props: Button::unstyled(Cow::Borrowed("")),
128 hovering: false,
129 mouse_down: false,
130 clicked: false,
131 }
132 }
133
134 fn update(&mut self, props: Self::Props<'_>) -> Self::Response {
135 self.props = props;
136
137 let mut color = self.props.style.fill;
138 let mut text_style = self.props.style.text.clone();
139
140 if self.mouse_down {
141 let style = &self.props.down_style;
142 color = style.fill;
143 text_style = style.text.clone();
144 } else if self.hovering {
145 let style = &self.props.hover_style;
146 color = style.fill;
147 text_style = style.text.clone();
148 }
149
150 let alignment = match text_style.align {
151 TextAlignment::Start => Alignment::CENTER_LEFT,
152 TextAlignment::Center => Alignment::CENTER,
153 TextAlignment::End => Alignment::CENTER_RIGHT,
154 };
155
156 let mut container = RoundRect::new(self.props.border_radius);
157 container.color = color;
158 container.show_children(|| {
159 crate::pad(self.props.padding, || {
160 crate::align(alignment, || {
161 let mut text = RenderText::label(self.props.text.clone());
162 text.style = text_style;
163 text.show();
164 });
165 });
166 });
167
168 let clicked = self.clicked;
169 self.clicked = false;
170
171 Self::Response {
172 hovering: self.hovering,
173 clicked,
174 }
175 }
176
177 fn event_interest(&self) -> EventInterest {
178 EventInterest::MOUSE_INSIDE | EventInterest::MOUSE_OUTSIDE
179 }
180
181 fn event(&mut self, _ctx: EventContext<'_>, event: &WidgetEvent) -> EventResponse {
182 match event {
183 WidgetEvent::MouseEnter => {
184 self.hovering = true;
185 EventResponse::Sink
186 }
187 WidgetEvent::MouseLeave => {
188 self.hovering = false;
189 EventResponse::Sink
190 }
191 WidgetEvent::MouseButtonChanged {
192 button: MouseButton::One,
193 down,
194 inside,
195 ..
196 } => {
197 if *inside {
198 if *down {
199 self.mouse_down = true;
200 EventResponse::Sink
201 } else if self.mouse_down {
202 self.mouse_down = false;
203 self.clicked = true;
204 EventResponse::Sink
205 } else {
206 EventResponse::Bubble
207 }
208 } else {
209 if !*down {
210 self.mouse_down = false;
211 }
212
213 EventResponse::Bubble
214 }
215 }
216 _ => EventResponse::Bubble,
217 }
218 }
219}