vizia_core/views/
xypad.rs1use crate::prelude::*;
2
3pub struct XYPad {
5 is_dragging: bool,
6
7 on_change: Option<Box<dyn Fn(&mut EventContext, f32, f32)>>,
8}
9
10impl XYPad {
11 fn normalized_from_cursor(cx: &EventContext, current: Entity, x: f32, y: f32) -> (f32, f32) {
12 let bounds = cx.transformed_bounds(current);
13 let width = bounds.width().max(f32::EPSILON);
14 let height = bounds.height().max(f32::EPSILON);
15
16 let dx = ((x - bounds.left()) / width).clamp(0.0, 1.0);
17 let dy = ((y - bounds.top()) / height).clamp(0.0, 1.0);
18
19 (dx, dy)
20 }
21
22 pub fn new<R: Res<(f32, f32)> + 'static>(cx: &mut Context, value: R) -> Handle<Self> {
24 let value_state = value.to_signal(cx);
25 let left = Memo::new(move |_| Percentage(value_state.get().0 * 100.0));
26 let top = Memo::new(move |_| Percentage((1.0 - value_state.get().1) * 100.0));
27
28 Self { is_dragging: false, on_change: None }
29 .build(cx, |cx| {
30 Element::new(cx)
32 .position_type(PositionType::Absolute)
33 .left(left)
34 .top(top)
35 .translate(Translate::new(
36 Length::Value(LengthValue::Px(-6.0)),
37 Length::Value(LengthValue::Px(-6.0)),
38 ))
39 .size(Pixels(10.0))
40 .corner_radius(Percentage(50.0))
41 .border_width(Pixels(2.0))
42 .hoverable(false)
43 .class("thumb");
44 })
45 .overflow(Overflow::Hidden)
46 .border_width(Pixels(1.0))
47 .size(Pixels(200.0))
48 }
49}
50
51impl View for XYPad {
52 fn element(&self) -> Option<&'static str> {
53 Some("xypad")
54 }
55
56 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
57 event.map(|window_event, meta| match window_event {
58 WindowEvent::MouseDown(button) if *button == MouseButton::Left => {
59 if cx.is_disabled() {
60 return;
61 }
62 let current = cx.current();
63 let (mouse_x, mouse_y) = cx.mouse.left.pos_down;
64 if meta.target == current || meta.target.is_descendant_of(cx.tree, current) {
65 cx.capture();
66 let (dx, dy) = Self::normalized_from_cursor(cx, current, mouse_x, mouse_y);
67
68 self.is_dragging = true;
69
70 if let Some(callback) = &self.on_change {
71 (callback)(cx, dx, 1.0 - dy);
72 }
73 }
74 }
75
76 WindowEvent::MouseUp(button) if *button == MouseButton::Left => {
77 cx.set_active(false);
78 cx.release();
79 self.is_dragging = false;
80 }
81
82 WindowEvent::MouseMove(x, y) => {
83 if self.is_dragging {
84 let current = cx.current();
85 let (dx, dy) = Self::normalized_from_cursor(cx, current, *x, *y);
86
87 if let Some(callback) = &self.on_change {
88 (callback)(cx, dx, 1.0 - dy);
89 }
90 }
91 }
92
93 _ => {}
94 });
95 }
96}
97
98impl Handle<'_, XYPad> {
99 pub fn on_change<F: Fn(&mut EventContext, f32, f32) + 'static>(self, callback: F) -> Self {
101 self.modify(|xypad| xypad.on_change = Some(Box::new(callback)))
102 }
103}