vizia_core/views/
scrollbar.rs
1use crate::context::TreeProps;
2use crate::prelude::*;
3
4pub struct Scrollbar<L1> {
6 value: L1,
7 orientation: Orientation,
8
9 reference_points: Option<(f32, f32)>,
10 dragging: bool,
11
12 on_changing: Option<Box<dyn Fn(&mut EventContext, f32)>>,
13
14 scroll_to_cursor: bool,
15}
16
17enum ScrollBarEvent {
18 SetScrollToCursor(bool),
19}
20
21impl<L1: Lens<Target = f32>> Scrollbar<L1> {
22 pub fn new<F, L2: Lens<Target = f32>>(
24 cx: &mut Context,
25 value: L1,
26 ratio: L2,
27 orientation: Orientation,
28 callback: F,
29 ) -> Handle<Self>
30 where
31 F: 'static + Fn(&mut EventContext, f32),
32 {
33 Self {
34 value,
35 orientation,
36 reference_points: None,
37 on_changing: Some(Box::new(callback)),
38 scroll_to_cursor: false,
39 dragging: false,
40 }
41 .build(cx, move |cx| {
42 Element::new(cx)
43 .class("thumb")
44 .focusable(true)
45 .bind(value, move |handle, value| {
46 let value = value.get(&handle);
47 match orientation {
48 Orientation::Horizontal => {
49 handle.left(Units::Stretch(value)).right(Units::Stretch(1.0 - value))
50 }
51 Orientation::Vertical => {
52 handle.top(Units::Stretch(value)).bottom(Units::Stretch(1.0 - value))
53 }
54 };
55 })
56 .bind(ratio, move |handle, ratio| {
57 let ratio = ratio.get(&handle);
58 match orientation {
59 Orientation::Horizontal => handle.width(Units::Percentage(ratio * 100.0)),
60 Orientation::Vertical => handle.height(Units::Percentage(ratio * 100.0)),
61 };
62 })
63 .position_type(PositionType::Absolute);
64 })
65 .pointer_events(PointerEvents::Auto)
66 .class(match orientation {
67 Orientation::Horizontal => "horizontal",
68 Orientation::Vertical => "vertical",
69 })
70 }
71
72 fn container_and_thumb_size(&self, cx: &mut EventContext) -> (f32, f32) {
73 let current = cx.current();
74 let child = cx.tree.get_child(current, 0).unwrap();
75 match &self.orientation {
76 Orientation::Horizontal => (cx.cache.get_width(current), cx.cache.get_width(child)),
77 Orientation::Vertical => (cx.cache.get_height(current), cx.cache.get_height(child)),
78 }
79 }
80
81 fn thumb_bounds(&self, cx: &mut EventContext) -> BoundingBox {
82 let child = cx.first_child();
83
84 cx.with_current(child, |cx| cx.bounds())
85 }
86
87 fn compute_new_value(&self, cx: &mut EventContext, physical_delta: f32, value_ref: f32) -> f32 {
88 let (size, thumb_size) = self.container_and_thumb_size(cx);
90 let negative_space = size - thumb_size;
91 if negative_space == 0.0 {
92 value_ref
93 } else {
94 let logical_delta = physical_delta / negative_space;
96 value_ref + logical_delta
97 }
98 }
99
100 fn change(&mut self, cx: &mut EventContext, new_val: f32) {
101 if let Some(callback) = &self.on_changing {
102 callback(cx, new_val.clamp(0.0, 1.0));
103 }
104 }
105}
106
107impl<L1: 'static + Lens<Target = f32>> View for Scrollbar<L1> {
108 fn element(&self) -> Option<&'static str> {
109 Some("scrollbar")
110 }
111
112 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
113 event.map(|scrollbar_event, _| match scrollbar_event {
114 ScrollBarEvent::SetScrollToCursor(flag) => {
115 self.scroll_to_cursor = *flag;
116 }
117 });
118
119 event.map(|window_event, meta| {
120 let pos = match &self.orientation {
121 Orientation::Horizontal => cx.mouse.cursor_x,
122 Orientation::Vertical => cx.mouse.cursor_y,
123 };
124 match window_event {
125 WindowEvent::MouseDown(MouseButton::Left) => {
126 if meta.target != cx.current() {
127 self.reference_points = Some((pos, self.value.get(cx)));
128 cx.capture();
129 cx.set_active(true);
130 self.dragging = true;
131 cx.with_current(Entity::root(), |cx| {
132 cx.set_pointer_events(false);
133 });
134 } else if self.scroll_to_cursor {
135 cx.capture();
136 cx.set_active(true);
137 self.dragging = true;
138 cx.with_current(Entity::root(), |cx| {
139 cx.set_pointer_events(false);
140 });
141 let thumb_bounds = self.thumb_bounds(cx);
142 let bounds = cx.bounds();
143 let sx = bounds.w - thumb_bounds.w;
144 let sy = bounds.h - thumb_bounds.h;
145 match self.orientation {
146 Orientation::Horizontal => {
147 let px = cx.mouse.cursor_x - cx.bounds().x - thumb_bounds.w / 2.0;
148 let x = (px / sx).clamp(0.0, 1.0);
149 if let Some(callback) = &self.on_changing {
150 (callback)(cx, x);
151 }
152 }
153
154 Orientation::Vertical => {
155 let py = cx.mouse.cursor_y - cx.bounds().y - thumb_bounds.h / 2.0;
156 let y = (py / sy).clamp(0.0, 1.0);
157 if let Some(callback) = &self.on_changing {
158 (callback)(cx, y);
159 }
160 }
161 }
162 } else {
163 let (_, jump) = self.container_and_thumb_size(cx);
164 let t = self.thumb_bounds(cx);
166 let physical_delta = match &self.orientation {
167 Orientation::Horizontal => {
168 if cx.mouse.cursor_x < t.x {
169 -jump
170 } else if cx.mouse.cursor_x >= t.x + t.w {
171 jump
172 } else {
173 return;
174 }
175 }
176 Orientation::Vertical => {
177 if cx.mouse.cursor_y < t.y {
178 -jump
179 } else if cx.mouse.cursor_y >= t.y + t.h {
180 jump
181 } else {
182 return;
183 }
184 }
185 };
186 let changed =
187 self.compute_new_value(cx, physical_delta, self.value.get(cx));
188 self.change(cx, changed);
189 }
190 }
191
192 WindowEvent::MouseUp(MouseButton::Left) => {
193 self.reference_points = None;
194 cx.focus_with_visibility(false);
195 cx.release();
196 cx.set_active(false);
197 self.dragging = false;
198 cx.with_current(Entity::root(), |cx| {
199 cx.set_pointer_events(true);
200 });
201 }
202
203 WindowEvent::MouseMove(_, _) => {
204 if self.dragging {
205 if let Some((mouse_ref, value_ref)) = self.reference_points {
206 let physical_delta = pos - mouse_ref;
207 let changed = self.compute_new_value(cx, physical_delta, value_ref);
208 self.change(cx, changed);
209 } else if self.scroll_to_cursor {
210 let thumb_bounds = self.thumb_bounds(cx);
211 let bounds = cx.bounds();
212 let sx = bounds.w - thumb_bounds.w;
213 let sy = bounds.h - thumb_bounds.h;
214 match self.orientation {
215 Orientation::Horizontal => {
216 let px =
217 cx.mouse.cursor_x - cx.bounds().x - thumb_bounds.w / 2.0;
218 let x = (px / sx).clamp(0.0, 1.0);
219 if let Some(callback) = &self.on_changing {
220 (callback)(cx, x);
221 }
222 }
223
224 Orientation::Vertical => {
225 let py =
226 cx.mouse.cursor_y - cx.bounds().y - thumb_bounds.h / 2.0;
227 let y = (py / sy).clamp(0.0, 1.0);
228 if let Some(callback) = &self.on_changing {
229 (callback)(cx, y);
230 }
231 }
232 }
233 }
234 }
235 }
236
237 _ => {}
238 }
239 });
240 }
241}
242
243impl<L1: 'static + Lens<Target = f32>> Handle<'_, Scrollbar<L1>> {
244 pub fn scroll_to_cursor(mut self, scroll_to_cursor: impl Res<bool>) -> Self {
246 let entity = self.entity();
247 scroll_to_cursor.set_or_bind(self.context(), entity, |cx, val| {
248 let v = val.get(cx);
249 cx.emit(ScrollBarEvent::SetScrollToCursor(v));
250 });
251
252 self
253 }
254}