Skip to main content

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