git.lucas.co / cce-ui
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 }