vizia_core/views/
xypad.rs

1use crate::prelude::*;
2
3/// A view which allows the user to manipulate 2 floating point values simultaneously on a two dimensional pane.
4pub struct XYPad {
5    is_dragging: bool,
6
7    on_change: Option<Box<dyn Fn(&mut EventContext, f32, f32)>>,
8}
9
10impl XYPad {
11    /// creates a new [XYPad] view.
12    pub fn new<L: Lens<Target = (f32, f32)>>(cx: &mut Context, lens: L) -> Handle<Self> {
13        Self { is_dragging: false, on_change: None }
14            .build(cx, |cx| {
15                // Thumb
16                Element::new(cx)
17                    .position_type(PositionType::Absolute)
18                    .left(lens.map(|(x, _)| Percentage(*x * 100.0)))
19                    .top(lens.map(|(_, y)| Percentage((1.0 - *y) * 100.0)))
20                    .translate(Translate::new(
21                        Length::Value(LengthValue::Px(-6.0)),
22                        Length::Value(LengthValue::Px(-6.0)),
23                    ))
24                    .size(Pixels(10.0))
25                    .corner_radius(Percentage(50.0))
26                    .border_width(Pixels(2.0))
27                    .hoverable(false)
28                    .class("thumb");
29            })
30            .overflow(Overflow::Hidden)
31            .border_width(Pixels(1.0))
32            .size(Pixels(200.0))
33    }
34}
35
36impl View for XYPad {
37    fn element(&self) -> Option<&'static str> {
38        Some("xypad")
39    }
40
41    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
42        event.map(|window_event, meta| match window_event {
43            WindowEvent::MouseDown(button) if *button == MouseButton::Left => {
44                if cx.is_disabled() {
45                    return;
46                }
47                let current = cx.current();
48                cx.capture();
49                let mouse = cx.mouse();
50                if meta.target == current {
51                    let mut dx = (mouse.left.pos_down.0 - cx.cache.get_posx(current))
52                        / cx.cache.get_width(current);
53                    let mut dy = (mouse.left.pos_down.1 - cx.cache.get_posy(current))
54                        / cx.cache.get_height(current);
55
56                    dx = dx.clamp(0.0, 1.0);
57                    dy = dy.clamp(0.0, 1.0);
58
59                    self.is_dragging = true;
60
61                    if let Some(callback) = &self.on_change {
62                        (callback)(cx, dx, 1.0 - dy);
63                    }
64                }
65            }
66
67            WindowEvent::MouseUp(button) if *button == MouseButton::Left => {
68                cx.set_active(false);
69                cx.release();
70                self.is_dragging = false;
71                if meta.target == cx.current() {
72                    cx.release();
73                }
74            }
75
76            WindowEvent::MouseMove(x, y) => {
77                if self.is_dragging {
78                    let current = cx.current();
79                    let mut dx = (*x - cx.cache.get_posx(current)) / cx.cache.get_width(current);
80                    let mut dy = (*y - cx.cache.get_posy(current)) / cx.cache.get_height(current);
81
82                    dx = dx.clamp(0.0, 1.0);
83                    dy = dy.clamp(0.0, 1.0);
84
85                    if let Some(callback) = &self.on_change {
86                        (callback)(cx, dx, 1.0 - dy);
87                    }
88                }
89            }
90
91            _ => {}
92        });
93    }
94}
95
96impl Handle<'_, XYPad> {
97    /// Set the callback which will be triggered when the XYPad is manipulated.
98    pub fn on_change<F: Fn(&mut EventContext, f32, f32) + 'static>(self, callback: F) -> Self {
99        self.modify(|xypad| xypad.on_change = Some(Box::new(callback)))
100    }
101}