GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
src/widget/input/trackpad.rs (6.5K)
1 use crate::scene::layout::Rect;
2 use crate::scene::paint::PaintCtx;
3 use crate::widget::model::{Adapted, EventCtx, Input, Layout, Paint};
4 use crate::widget::*;
5
6 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
7 pub struct Finger {
8 pub slot: usize,
9 pub x: f32,
10 pub y: f32,
11 }
12
13 /// Touchpad visualization/input area (narrow-trait model, Phase 6as leaf sweep). The
14 /// content rect is cached on assignment (the ParametersBg pattern) because the finger
15 /// math runs from events and drags as well as paint; the control label is the adapter's
16 /// detached one above the pad, like every control's.
17 #[derive(Debug, Clone)]
18 pub struct Trackpad {
19 rect: Rect,
20 label: Option<String>,
21 hovered: bool,
22 pub fingers: Vec<Finger>,
23 /// Recessed style: the touch area is a well carved into the plate below,
24 /// a faint dark wash for its floor, instead of the framed dark pane.
25 /// Defaults to `control_relief()`.
26 recessed: Option<bool>,
27 }
28
29 impl Trackpad {
30 /// The style in force: the per-widget override (`with_recessed`) when set, else
31 /// the DE's `control_relief`, read live so a runtime switch
32 /// (`layout::set_control_relief`) restyles every control at once.
33 fn recessed(&self) -> bool {
34 self.recessed.unwrap_or_else(crate::layout::control_relief)
35 }
36
37 pub fn new() -> Adapted<Trackpad> {
38 Adapted::new(Trackpad {
39 rect: Rect { x: 0.0, y: 0.0, width: 0.0, height: 0.0 },
40 label: None,
41 hovered: false,
42 fingers: Vec::new(),
43 recessed: None,
44 })
45 }
46
47 pub fn set_fingers(&mut self, fingers: Vec<Finger>) {
48 self.fingers = fingers;
49 }
50 }
51
52 impl Adapted<Trackpad> {
53 /// Recessed style: see the `recessed` field.
54 pub fn with_recessed(mut self, recessed: bool) -> Self {
55 self.recessed = Some(recessed);
56 self
57 }
58 }
59
60 impl Trackpad {
61 /// The touch area: the cached block rect less the detached-label strip above it.
62 fn touch_area(&self) -> (f32, f32, f32, f32) {
63 let top = crate::widget::input::slider::detached_strip(&self.label);
64 (self.rect.x, self.rect.y + top, self.rect.width, self.rect.height - top)
65 }
66
67 fn finger_at(&self, px: f32, py: f32) -> Finger {
68 let (x, y, w, h) = self.touch_area();
69 Finger {
70 slot: 0,
71 x: ((px - x) / w).clamp(0.0, 1.0),
72 y: ((py - y) / h).clamp(0.0, 1.0),
73 }
74 }
75 }
76
77 impl Layout for Trackpad {
78 fn rect_assigned(&mut self, rect: Rect) {
79 self.rect = rect;
80 }
81 }
82
83 impl Paint for Trackpad {
84 fn widget_font(&self) -> Option<String> {
85 Some(crate::layout::control_label_font())
86 }
87
88 fn color(&self) -> [f32; 4] {
89 // The floor is the canvas well's (`paint`); no base fill of its own.
90 [0.0, 0.0, 0.0, 0.0]
91 }
92
93 fn sync_label(&mut self, label: &str) {
94 self.label = Some(label.to_string());
95 }
96
97 fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
98 let _ = rect; // geometry reads the assignment cache (events/drags share it)
99 let (x, y, w, visual_h) = self.touch_area();
100
101 let area = Rect { x, y, width: w, height: visual_h };
102 // 1+2. A canvas well in the plate — the floor tint and rim every draw-in
103 // opening shares (`PaintCtx::canvas_well`), rounded like the text wells;
104 // the fingers draw over the rim.
105 let radius = crate::layout::textbox_corner_radius();
106 ctx.canvas_well(area, radius, &crate::scene::Material::pane(), self.recessed(), false);
107
108 // 3. Fingers
109 for finger in &self.fingers {
110 let rx = finger.x.clamp(0.0, 1.0);
111 let ry = finger.y.clamp(0.0, 1.0);
112 let fx = x + rx * w;
113 let fy = y + ry * visual_h;
114 let dot_size = 12.0;
115
116 // Glow (outer light blue), then core (solid blue/purple)
117 ctx.quad(
118 Rect {
119 x: fx - (dot_size + 6.0) / 2.0,
120 y: fy - (dot_size + 6.0) / 2.0,
121 width: dot_size + 6.0,
122 height: dot_size + 6.0,
123 },
124 [0.35, 0.55, 0.95, 0.4],
125 );
126 ctx.quad(
127 Rect { x: fx - dot_size / 2.0, y: fy - dot_size / 2.0, width: dot_size, height: dot_size },
128 [0.45, 0.65, 1.0, 1.0],
129 );
130 }
131
132 // 4. The "Touchpad Area" hint (the control label is the adapter's), in
133 // the label font like every other word a control draws.
134 ctx.text_with(
135 "Touchpad Area".to_string(),
136 x + 12.0,
137 y + visual_h - 22.0,
138 11.0,
139 [0x73, 0x73, 0x8c],
140 None,
141 // A fixed string in a pad far wider than it, so this is belt and
142 // braces — but a narrow pad is a layout the caller may choose.
143 Some([x, y, x + rect.width, y + visual_h]),
144 );
145 }
146 }
147
148 impl Input for Trackpad {
149 fn on_event(&mut self, event: &Event, _ectx: &mut EventCtx) -> bool {
150 match event {
151 Event::MouseButton { button, state, x, y, .. } => {
152 if *button != MouseButton::Left {
153 return false;
154 }
155 let (ax, ay, aw, ah) = self.touch_area();
156 if *x >= ax && *x <= ax + aw && *y >= ay && *y <= ay + ah {
157 if *state == ElementState::Pressed {
158 self.fingers = vec![self.finger_at(*x, *y)];
159 } else {
160 self.fingers.clear();
161 }
162 return true;
163 }
164 false
165 }
166 Event::MouseEnter => {
167 self.hovered = true;
168 false
169 }
170 Event::MouseLeave => {
171 self.hovered = false;
172 false
173 }
174 _ => false,
175 }
176 }
177
178 fn draggable(&self, _rect: Rect) -> bool {
179 true
180 }
181
182 fn is_dragging(&self) -> bool {
183 !self.fingers.is_empty()
184 }
185
186 fn drag_begin(&mut self, px: f32, py: f32, _rect: Rect) {
187 let (_, _, w, h) = self.touch_area();
188 if w > 0.0 && h > 0.0 {
189 self.fingers = vec![self.finger_at(px, py)];
190 }
191 }
192
193 fn drag_update(&mut self, px: f32, py: f32, _rect: Rect) -> bool {
194 let (_, _, w, h) = self.touch_area();
195 if w > 0.0 && h > 0.0 {
196 self.fingers = vec![self.finger_at(px, py)];
197 true
198 } else {
199 false
200 }
201 }
202
203 fn drag_end(&mut self) {
204 self.fingers.clear();
205 }
206 }