geng_ui/widgets/
slider.rs1use super::*;
2
3pub struct Slider<'a> {
4 sense: &'a mut Sense,
5 pos: &'a mut Option<Aabb2<f64>>,
6 hover_pos: &'a mut Option<f64>,
7 tick_radius: &'a mut f32,
8 value: f64,
9 range: RangeInclusive<f64>,
10 f: Box<dyn FnMut(f64) + 'a>,
11}
12
13impl<'a> Slider<'a> {
14 const ANIMATION_SPEED: f32 = 5.0;
15
16 pub fn new(
17 cx: &'a Controller,
18 value: f64,
19 range: RangeInclusive<f64>,
20 f: Box<dyn FnMut(f64) + 'a>,
21 ) -> Self {
22 Slider {
23 sense: cx.get_state(),
24 tick_radius: cx.get_state(),
25 pos: cx.get_state(),
26 hover_pos: cx.get_state(),
27 value,
28 range,
29 f,
30 }
31 }
32}
33
34impl<'a> Widget for Slider<'a> {
35 fn sense(&mut self) -> Option<&mut Sense> {
36 Some(self.sense)
37 }
38 fn update(&mut self, delta_time: f64) {
39 let target_tick_radius = if self.sense.is_hovered() || self.sense.is_captured() {
40 1.0 / 2.0
41 } else {
42 1.0 / 6.0
43 };
44 *self.tick_radius += (target_tick_radius - *self.tick_radius)
45 .clamp_abs(Self::ANIMATION_SPEED * delta_time as f32);
46 }
47 fn draw(&mut self, cx: &mut DrawContext) {
48 *self.pos = Some(cx.position);
49 let draw2d = cx.draw2d;
50 let position = cx.position.map(|x| x as f32);
51 let line_width = position.height() / 3.0;
52 let value_position = if self.range.end() == self.range.start() {
53 *self.tick_radius
54 } else {
55 *self.tick_radius
56 + ((self.value - *self.range.start()) / (*self.range.end() - *self.range.start()))
57 as f32
58 * (position.width() - line_width)
59 };
60 draw2d.draw2d(
61 cx.framebuffer,
62 &PixelPerfectCamera,
63 &draw2d::Quad::new(
64 Aabb2::from_corners(
65 position.bottom_left()
66 + vec2(value_position, (position.height() - line_width) / 2.0),
67 position.top_right()
68 - vec2(line_width / 2.0, (position.height() - line_width) / 2.0),
69 ),
70 cx.theme.usable_color,
71 ),
72 );
73 draw2d.circle(
74 cx.framebuffer,
75 &PixelPerfectCamera,
76 position.top_right() - vec2(line_width / 2.0, position.height() / 2.0),
77 line_width / 2.0,
78 cx.theme.usable_color,
79 );
80 draw2d.draw2d(
81 cx.framebuffer,
82 &PixelPerfectCamera,
83 &draw2d::Quad::new(
84 Aabb2::from_corners(
85 position.bottom_left()
86 + vec2(line_width / 2.0, (position.height() - line_width) / 2.0),
87 position.bottom_left()
88 + vec2(value_position, (position.height() + line_width) / 2.0),
89 ),
90 cx.theme.hover_color,
91 ),
92 );
93 draw2d.draw2d(
94 cx.framebuffer,
95 &PixelPerfectCamera,
96 &draw2d::Ellipse::circle(
97 position.bottom_left() + vec2(line_width / 2.0, position.height() / 2.0),
98 line_width / 2.0,
99 cx.theme.hover_color,
100 ),
101 );
102 draw2d.circle(
103 cx.framebuffer,
104 &PixelPerfectCamera,
105 position.bottom_left() + vec2(value_position, position.height() / 2.0),
106 *self.tick_radius * position.height(),
107 cx.theme.hover_color,
108 );
109 }
110 fn handle_event(&mut self, event: &Event) {
111 let aabb = match *self.pos {
112 Some(pos) => pos,
113 None => return,
114 };
115 if let Event::CursorMove { position } = &event {
116 let position = position.x - aabb.min.x;
117 *self.hover_pos = Some(
118 *self.range.start()
119 + ((position - aabb.height() / 6.0) / (aabb.width() - aabb.height() / 3.0))
120 .clamp(0.0, 1.0)
121 * (*self.range.end() - *self.range.start()),
122 );
123 }
124 if self.sense.is_captured() {
125 if let Event::MousePress { .. } | Event::CursorMove { .. } = &event {
126 if let Some(new_value) = *self.hover_pos {
127 (self.f)(new_value);
128 }
129 }
130 }
131 }
132
133 fn calc_constraints(&mut self, _children: &ConstraintsContext) -> Constraints {
134 Constraints::default()
135 }
136}