yakui_widgets/widgets/
checkbox.rs1use yakui_core::event::{EventInterest, EventResponse, WidgetEvent};
2use yakui_core::geometry::{Constraints, Vec2};
3use yakui_core::input::MouseButton;
4use yakui_core::widget::{EventContext, LayoutContext, PaintContext, Widget};
5use yakui_core::Response;
6
7use crate::shapes::RoundedRectangle;
8use crate::{colors, shapes};
9
10const OUTER_SIZE: f32 = 24.0;
11const INNER_SIZE: f32 = 16.0;
12
13#[derive(Debug)]
27#[non_exhaustive]
28#[must_use = "yakui widgets do nothing if you don't `show` them"]
29pub struct Checkbox {
30 pub checked: bool,
31}
32
33impl Checkbox {
34 pub fn new(checked: bool) -> Self {
35 Self { checked }
36 }
37
38 pub fn show(self) -> Response<CheckboxResponse> {
39 crate::util::widget::<CheckboxWidget>(self)
40 }
41}
42
43#[derive(Debug)]
44pub struct CheckboxWidget {
45 props: Checkbox,
46 hovering: bool,
47 mouse_down: bool,
48 just_toggled: bool,
49}
50
51#[derive(Debug)]
52#[non_exhaustive]
53pub struct CheckboxResponse {
54 pub checked: bool,
55}
56
57impl Widget for CheckboxWidget {
58 type Props<'a> = Checkbox;
59 type Response = CheckboxResponse;
60
61 fn new() -> Self {
62 Self {
63 props: Checkbox::new(false),
64 hovering: false,
65 mouse_down: false,
66 just_toggled: false,
67 }
68 }
69
70 fn update(&mut self, props: Self::Props<'_>) -> Self::Response {
71 self.props = props;
72
73 let mut checked = self.props.checked;
74 if self.just_toggled {
75 checked = !checked;
76 self.just_toggled = false;
77 }
78
79 CheckboxResponse { checked }
80 }
81
82 fn paint(&self, ctx: PaintContext<'_>) {
83 let layout_node = ctx.layout.get(ctx.dom.current()).unwrap();
84
85 let padding = Vec2::splat(OUTER_SIZE - INNER_SIZE);
86 let mut check_rect = layout_node.rect;
87 check_rect.set_pos(check_rect.pos() + padding / 2.0);
88 check_rect.set_size(check_rect.size() - padding);
89
90 let mut bg = RoundedRectangle::new(layout_node.rect, 6.0);
91 bg.color = colors::BACKGROUND_3;
92 bg.add(ctx.paint);
93
94 if self.props.checked {
95 shapes::cross(ctx.paint, check_rect, colors::TEXT);
96 }
97 }
98
99 fn layout(&self, _ctx: LayoutContext<'_>, constraints: Constraints) -> Vec2 {
100 constraints.constrain_min(Vec2::splat(OUTER_SIZE))
101 }
102
103 fn event_interest(&self) -> EventInterest {
104 EventInterest::MOUSE_INSIDE | EventInterest::MOUSE_OUTSIDE
105 }
106
107 fn event(&mut self, _ctx: EventContext<'_>, event: &WidgetEvent) -> EventResponse {
108 match event {
109 WidgetEvent::MouseEnter => {
110 self.hovering = true;
111 EventResponse::Sink
112 }
113 WidgetEvent::MouseLeave => {
114 self.hovering = false;
115 EventResponse::Sink
116 }
117 WidgetEvent::MouseButtonChanged {
118 button: MouseButton::One,
119 down,
120 inside,
121 ..
122 } => {
123 if *inside {
124 if *down {
125 self.mouse_down = true;
126 EventResponse::Sink
127 } else if self.mouse_down {
128 self.mouse_down = false;
129 self.just_toggled = true;
130 EventResponse::Sink
131 } else {
132 EventResponse::Bubble
133 }
134 } else {
135 self.mouse_down = false;
136 EventResponse::Bubble
137 }
138 }
139 _ => EventResponse::Bubble,
140 }
141 }
142}