1use super::{RangeBase, ScrollBar, Thumb};
5use crate::metadata::PseudoClassesAttribute;
6use crate::{Button, Control, ControlImpl};
7use ferroui_base::input::{InputElementImpl, VectorEventArgs};
8use ferroui_base::interactivity::{InteractiveImpl, RoutedEventHandlerToken};
9use ferroui_base::layout::{Layoutable, LayoutableImpl, Orientation};
10use ferroui_base::utilities::MathUtilities;
11use ferroui_base::{
12 ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
13 FerroPropertyChangedEventArgs, Nullable, Point, Rect, Ref, Size, StyledElement, StyledElementImpl, StyledProperty,
14 Vector, VisualImpl,
15};
16use std::cell::Cell;
17
18const PC_VERTICAL: &str = ":vertical";
19const PC_HORIZONTAL: &str = ":horizontal";
20
21#[repr(C)]
24pub struct Track {
25 base: Control,
26 deferred_thumb_drag: Cell<Option<Vector>>,
27 last_drag: Cell<Vector>,
28 thumb_center_offset: Cell<f64>,
29 density: Cell<f64>,
30 thumb_drag_delta_token: Cell<Option<RoutedEventHandlerToken>>,
31 thumb_drag_completed_token: Cell<Option<RoutedEventHandlerToken>>,
32}
33
34ferro_class! {
35 Track: Control, virtuals TrackImpl: ControlImpl {
36 fn value_from_point(this, point: Point) -> f64;
42 fn value_from_distance(this, horizontal: f64, vertical: f64) -> f64;
45 }
46}
47ferroui_base::ferro_class_info!(Track { new: Track::new });
48
49ferro_impl_classes!(Track: StyledElementImpl, VisualImpl, InteractiveImpl, InputElementImpl, ControlImpl);
50
51impl FerroObjectImpl for Track {
52 fn constructed(this: &Self) {
53 Self::parent_constructed(this);
54 this.update_pseudo_classes(this.orientation());
55 }
56
57 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
58 Self::parent_on_property_changed(this, change);
59
60 if change.property() == Self::orientation_property().as_property() {
61 this.update_pseudo_classes(change.get_new_value::<Orientation>());
62 } else if change.property() == Self::defer_thumb_drag_property().as_property()
63 && !change.get_new_value::<bool>()
64 {
65 this.apply_deferred_thumb_drag();
66 }
67 }
68}
69
70impl LayoutableImpl for Track {
71 fn measure_override(this: &Self, available_size: Size) -> Size {
72 let mut desired_size = Size::new(0.0, 0.0);
73
74 if let Some(thumb) = this.thumb() {
77 thumb.measure(available_size);
78 desired_size = thumb.desired_size();
79 }
80
81 if !this.viewport_size().is_nan() {
82 if this.orientation() == Orientation::Vertical {
84 desired_size = desired_size.with_height(0.0);
85 } else {
86 desired_size = desired_size.with_width(0.0);
87 }
88 }
89
90 desired_size
91 }
92
93 fn arrange_override(this: &Self, arrange_size: Size) -> Size {
94 let is_vertical = this.orientation() == Orientation::Vertical;
95 let viewport_size = f64::max(0.0, this.viewport_size());
96
97 let (mut decrease_button_length, mut thumb_length, mut increase_button_length) =
101 if this.viewport_size().is_nan() {
102 this.compute_slider_lengths(arrange_size, is_vertical)
103 } else {
104 match this.compute_scroll_bar_lengths(arrange_size, viewport_size, is_vertical) {
107 Some(lengths) => lengths,
108 None => return arrange_size,
109 }
110 };
111
112 let mut offset = Point::default();
114 let mut piece_size = arrange_size;
115 let is_direction_reversed = this.is_direction_reversed();
116
117 if is_vertical {
118 Self::coerce_length(&mut decrease_button_length, arrange_size.height);
119 Self::coerce_length(&mut increase_button_length, arrange_size.height);
120 Self::coerce_length(&mut thumb_length, arrange_size.height);
121
122 offset = offset.with_y(if is_direction_reversed { decrease_button_length + thumb_length } else { 0.0 });
123 piece_size = piece_size.with_height(increase_button_length);
124
125 if let Some(increase_button) = this.increase_button() {
126 increase_button.arrange(Rect::from_position_size(offset, piece_size));
127 }
128
129 offset = offset.with_y(if is_direction_reversed { 0.0 } else { increase_button_length + thumb_length });
130 piece_size = piece_size.with_height(decrease_button_length);
131
132 if let Some(decrease_button) = this.decrease_button() {
133 decrease_button.arrange(Rect::from_position_size(offset, piece_size));
134 }
135
136 offset = offset.with_y(if is_direction_reversed { decrease_button_length } else { increase_button_length });
137 piece_size = piece_size.with_height(thumb_length);
138
139 if let Some(thumb) = this.thumb() {
140 let bounds = Rect::from_position_size(offset, piece_size);
141 let adjust = this.calculate_thumb_adjustment(&thumb, bounds);
142 thumb.arrange(bounds);
143 thumb.adjust_drag(adjust);
144 }
145
146 this.thumb_center_offset.set(offset.y + (thumb_length * 0.5));
147 } else {
148 Self::coerce_length(&mut decrease_button_length, arrange_size.width);
149 Self::coerce_length(&mut increase_button_length, arrange_size.width);
150 Self::coerce_length(&mut thumb_length, arrange_size.width);
151
152 offset = offset.with_x(if is_direction_reversed { increase_button_length + thumb_length } else { 0.0 });
153 piece_size = piece_size.with_width(decrease_button_length);
154
155 if let Some(decrease_button) = this.decrease_button() {
156 decrease_button.arrange(Rect::from_position_size(offset, piece_size));
157 }
158
159 offset = offset.with_x(if is_direction_reversed { 0.0 } else { decrease_button_length + thumb_length });
160 piece_size = piece_size.with_width(increase_button_length);
161
162 if let Some(increase_button) = this.increase_button() {
163 increase_button.arrange(Rect::from_position_size(offset, piece_size));
164 }
165
166 offset = offset.with_x(if is_direction_reversed { increase_button_length } else { decrease_button_length });
167 piece_size = piece_size.with_width(thumb_length);
168
169 if let Some(thumb) = this.thumb() {
170 let bounds = Rect::from_position_size(offset, piece_size);
171 let adjust = this.calculate_thumb_adjustment(&thumb, bounds);
172 thumb.arrange(bounds);
173 thumb.adjust_drag(adjust);
174 }
175
176 this.thumb_center_offset.set(offset.x + (thumb_length * 0.5));
177 }
178
179 this.last_drag.set(Vector::default());
180 arrange_size
181 }
182}
183
184impl TrackImpl for Track {
185 fn value_from_point(this: &Self, point: Point) -> f64 {
186 let val = if this.orientation() == Orientation::Horizontal {
188 this.thumb_value()
189 + this.value_from_distance(
190 point.x - this.thumb_center_offset.get(),
191 point.y - (this.bounds().height * 0.5),
192 )
193 } else {
194 this.thumb_value()
195 + this.value_from_distance(
196 point.x - (this.bounds().width * 0.5),
197 point.y - this.thumb_center_offset.get(),
198 )
199 };
200
201 f64::max(this.minimum(), f64::min(this.maximum(), val))
202 }
203
204 fn value_from_distance(this: &Self, horizontal: f64, vertical: f64) -> f64 {
205 let scale = if this.is_direction_reversed() { -1.0 } else { 1.0 };
206
207 if this.orientation() == Orientation::Horizontal {
208 scale * horizontal * this.density.get()
209 } else {
210 -1.0 * scale * vertical * this.density.get()
212 }
213 }
214}
215
216impl Track {
217 pub const PSEUDO_CLASSES: PseudoClassesAttribute = PseudoClassesAttribute::new(&[PC_VERTICAL, PC_HORIZONTAL]);
219}
220
221ferroui_base::ferro_properties! { impl Track {
222 ferro_property!(
223 pub fn minimum_property() -> StyledProperty<f64> {
225 RangeBase::minimum_property().add_owner::<Track>()
226 }
227 );
228
229 ferro_property!(
230 pub fn maximum_property() -> StyledProperty<f64> {
232 RangeBase::maximum_property().add_owner::<Track>()
233 }
234 );
235
236 ferro_property!(
237 pub fn value_property() -> StyledProperty<f64> {
239 RangeBase::value_property().add_owner::<Track>()
240 }
241 );
242
243 ferro_property!(
244 pub fn viewport_size_property() -> StyledProperty<f64> {
246 ScrollBar::viewport_size_property().add_owner::<Track>()
247 }
248 );
249
250 ferro_property!(
251 pub fn orientation_property() -> StyledProperty<Orientation> {
253 ScrollBar::orientation_property().add_owner::<Track>()
254 }
255 );
256
257 ferro_property!(
258 pub fn thumb_property() -> StyledProperty<Option<Ref<Thumb>>> {
260 FerroProperty::register::<Track, _>("Thumb", None)
261 }
262 );
263
264 ferro_property!(
265 pub fn increase_button_property() -> StyledProperty<Option<Ref<Button>>> {
267 FerroProperty::register::<Track, _>("IncreaseButton", None)
268 }
269 );
270
271 ferro_property!(
272 pub fn decrease_button_property() -> StyledProperty<Option<Ref<Button>>> {
274 FerroProperty::register::<Track, _>("DecreaseButton", None)
275 }
276 );
277
278 ferro_property!(
279 pub fn is_direction_reversed_property() -> StyledProperty<bool> {
281 FerroProperty::register::<Track, _>("IsDirectionReversed", false)
282 }
283 );
284
285 ferro_property!(
286 pub fn ignore_thumb_drag_property() -> StyledProperty<bool> {
288 FerroProperty::register::<Track, _>("IgnoreThumbDrag", false)
289 }
290 );
291
292 ferro_property!(
293 pub fn defer_thumb_drag_property() -> StyledProperty<bool> {
295 FerroProperty::register::<Track, _>("DeferThumbDrag", false)
296 }
297 );
298} }
299
300impl Track {
301 fn static_constructor() {
302 Self::thumb_property().changed().add_class_handler::<Track>(|x, e| x.thumb_changed(e));
303 Self::increase_button_property().changed().add_class_handler::<Track>(|x, e| x.button_changed(e));
304 Self::decrease_button_property().changed().add_class_handler::<Track>(|x, e| x.button_changed(e));
305 Layoutable::affects_arrange::<Track>(&[
306 Self::is_direction_reversed_property().as_property(),
307 Self::minimum_property().as_property(),
308 Self::maximum_property().as_property(),
309 Self::value_property().as_property(),
310 Self::orientation_property().as_property(),
311 ]);
312 }
313
314 pub fn construct() -> Self {
316 Self {
317 base: Control::construct(),
318 deferred_thumb_drag: Cell::new(None),
319 last_drag: Cell::new(Vector::default()),
320 thumb_center_offset: Cell::new(0.0),
321 density: Cell::new(0.0),
322 thumb_drag_delta_token: Cell::new(None),
323 thumb_drag_completed_token: Cell::new(None),
324 }
325 }
326
327 pub fn new() -> Ref<Self> {
328 instantiate(Self::construct())
329 }
330
331 pub fn minimum(&self) -> f64 {
333 self.get_value(Self::minimum_property())
334 }
335
336 pub fn set_minimum(&self, value: f64) {
337 self.set_value(Self::minimum_property(), value)
338 }
339
340 pub fn maximum(&self) -> f64 {
342 self.get_value(Self::maximum_property())
343 }
344
345 pub fn set_maximum(&self, value: f64) {
346 self.set_value(Self::maximum_property(), value)
347 }
348
349 pub fn value(&self) -> f64 {
351 self.get_value(Self::value_property())
352 }
353
354 pub fn set_range_value(&self, value: f64) {
355 self.set_value(Self::value_property(), value)
356 }
357
358 fn thumb_value(&self) -> f64 {
361 self.value()
362 + match self.deferred_thumb_drag.get() {
363 None => 0.0,
364 Some(vector) => self.value_from_distance(vector.x, vector.y),
365 }
366 }
367
368 pub fn viewport_size(&self) -> f64 {
370 self.get_value(Self::viewport_size_property())
371 }
372
373 pub fn set_viewport_size(&self, value: f64) {
374 self.set_value(Self::viewport_size_property(), value)
375 }
376
377 pub fn orientation(&self) -> Orientation {
379 self.get_value(Self::orientation_property())
380 }
381
382 pub fn set_orientation(&self, value: Orientation) {
383 self.set_value(Self::orientation_property(), value)
384 }
385
386 pub fn thumb(&self) -> Option<Ref<Thumb>> {
388 self.get_value(Self::thumb_property())
389 }
390
391 pub fn set_thumb(&self, value: impl Into<Nullable<Thumb>>) {
392 self.set_value(Self::thumb_property(), value.into().0)
393 }
394
395 pub fn increase_button(&self) -> Option<Ref<Button>> {
397 self.get_value(Self::increase_button_property())
398 }
399
400 pub fn set_increase_button(&self, value: impl Into<Nullable<Button>>) {
401 self.set_value(Self::increase_button_property(), value.into().0)
402 }
403
404 pub fn decrease_button(&self) -> Option<Ref<Button>> {
406 self.get_value(Self::decrease_button_property())
407 }
408
409 pub fn set_decrease_button(&self, value: impl Into<Nullable<Button>>) {
410 self.set_value(Self::decrease_button_property(), value.into().0)
411 }
412
413 pub fn is_direction_reversed(&self) -> bool {
415 self.get_value(Self::is_direction_reversed_property())
416 }
417
418 pub fn set_is_direction_reversed(&self, value: bool) {
419 self.set_value(Self::is_direction_reversed_property(), value)
420 }
421
422 pub fn ignore_thumb_drag(&self) -> bool {
424 self.get_value(Self::ignore_thumb_drag_property())
425 }
426
427 pub fn set_ignore_thumb_drag(&self, value: bool) {
428 self.set_value(Self::ignore_thumb_drag_property(), value)
429 }
430
431 pub fn defer_thumb_drag(&self) -> bool {
433 self.get_value(Self::defer_thumb_drag_property())
434 }
435
436 pub fn set_defer_thumb_drag(&self, value: bool) {
437 self.set_value(Self::defer_thumb_drag_property(), value)
438 }
439
440 fn calculate_thumb_adjustment(&self, thumb: &Thumb, new_thumb_bounds: Rect) -> Vector {
441 let thumb_delta: Vector = (new_thumb_bounds.position() - thumb.bounds().position()).into();
442 self.last_drag.get() - thumb_delta
443 }
444
445 fn coerce_length(component_length: &mut f64, track_length: f64) {
446 if *component_length < 0.0 {
447 *component_length = 0.0;
448 } else if *component_length > track_length || component_length.is_nan() {
449 *component_length = track_length;
450 }
451 }
452
453 fn compute_slider_lengths(&self, arrange_size: Size, is_vertical: bool) -> (f64, f64, f64) {
456 let min = self.minimum();
457 let range = f64::max(0.0, self.maximum() - min);
458 let offset = f64::min(range, self.thumb_value() - min);
459
460 let thumb = self.thumb();
461
462 let (track_length, mut thumb_length) = if is_vertical {
464 (arrange_size.height, thumb.map_or(0.0, |thumb| thumb.desired_size().height))
465 } else {
466 (arrange_size.width, thumb.map_or(0.0, |thumb| thumb.desired_size().width))
467 };
468
469 Self::coerce_length(&mut thumb_length, track_length);
470
471 let remaining_track_length = track_length - thumb_length;
472
473 let mut decrease_button_length = remaining_track_length * offset / range;
474 Self::coerce_length(&mut decrease_button_length, remaining_track_length);
475
476 let mut increase_button_length = remaining_track_length - decrease_button_length;
477 Self::coerce_length(&mut increase_button_length, remaining_track_length);
478
479 self.density.set(range / remaining_track_length);
480
481 (decrease_button_length, thumb_length, increase_button_length)
482 }
483
484 fn compute_scroll_bar_lengths(
487 &self,
488 arrange_size: Size,
489 viewport_size: f64,
490 is_vertical: bool,
491 ) -> Option<(f64, f64, f64)> {
492 let min = self.minimum();
493 let range = f64::max(0.0, self.maximum() - min);
494 let offset = f64::min(range, self.thumb_value() - min);
495 let extent = f64::max(0.0, range) + viewport_size;
496 let track_length = if is_vertical { arrange_size.height } else { arrange_size.width };
497 let mut thumb_min_length = 10.0;
498
499 let min_length_property =
500 if is_vertical { Layoutable::min_height_property() } else { Layoutable::min_width_property() };
501
502 if let Some(thumb) = self.thumb() {
503 if thumb.is_set(min_length_property.as_property()) {
504 thumb_min_length = thumb.get_value(min_length_property);
505 }
506 }
507
508 let mut thumb_length = track_length * viewport_size / extent;
509 Self::coerce_length(&mut thumb_length, track_length);
510 thumb_length = f64::max(thumb_min_length, thumb_length);
511
512 let not_enough_content_to_scroll = MathUtilities::less_than_or_close(range, 0.0);
514 let thumb_longer_than_track = thumb_length > track_length;
515
516 if not_enough_content_to_scroll || thumb_longer_than_track {
519 self.show_children(false);
520 self.thumb_center_offset.set(f64::NAN);
521 self.density.set(f64::NAN);
522 return None; }
524
525 self.show_children(true);
526
527 let remaining_track_length = track_length - thumb_length;
529 let mut decrease_button_length = remaining_track_length * offset / range;
530 Self::coerce_length(&mut decrease_button_length, remaining_track_length);
531
532 let mut increase_button_length = remaining_track_length - decrease_button_length;
533 Self::coerce_length(&mut increase_button_length, remaining_track_length);
534
535 self.density.set(range / remaining_track_length);
536
537 Some((decrease_button_length, thumb_length, increase_button_length))
538 }
539
540 fn thumb_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
541 let (old_thumb, new_thumb) = e.get_old_and_new_value::<Option<Ref<Thumb>>>();
542
543 if let Some(old_thumb) = old_thumb {
544 if let Some(token) = self.thumb_drag_delta_token.take() {
545 old_thumb.remove_handler(Thumb::drag_delta_event(), token);
546 }
547 if let Some(token) = self.thumb_drag_completed_token.take() {
548 old_thumb.remove_handler(Thumb::drag_completed_event(), token);
549 }
550 StyledElement::logical_children(self).remove(&old_thumb.clone().upcast());
551 self.visual_children().remove(&old_thumb.upcast());
552 }
553
554 if let Some(new_thumb) = new_thumb {
555 let weak = self.to_ref().downgrade();
556 self.thumb_drag_delta_token.set(Some(new_thumb.drag_delta(move |_, e| {
557 if let Some(this) = weak.upgrade() {
558 this.thumb_dragged(e);
559 }
560 })));
561 let weak = self.to_ref().downgrade();
562 self.thumb_drag_completed_token.set(Some(new_thumb.drag_completed(move |_, _| {
563 if let Some(this) = weak.upgrade() {
564 this.apply_deferred_thumb_drag();
565 }
566 })));
567 StyledElement::logical_children(self).add(new_thumb.clone().upcast());
568 self.visual_children().add(new_thumb.upcast());
569 }
570 }
571
572 fn button_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
573 let (old_button, new_button) = e.get_old_and_new_value::<Option<Ref<Button>>>();
574
575 if let Some(old_button) = old_button {
576 StyledElement::logical_children(self).remove(&old_button.clone().upcast());
577 self.visual_children().remove(&old_button.upcast());
578 }
579
580 if let Some(new_button) = new_button {
581 StyledElement::logical_children(self).add(new_button.clone().upcast());
582 self.visual_children().add(new_button.upcast());
583 }
584 }
585
586 fn thumb_dragged(&self, e: &VectorEventArgs) {
587 if self.ignore_thumb_drag() {
588 return;
589 }
590
591 if self.defer_thumb_drag() {
592 self.deferred_thumb_drag.set(Some(e.vector));
593 self.invalidate_arrange();
594 } else {
595 self.apply_thumb_drag(e.vector);
596 }
597 }
598
599 fn apply_thumb_drag(&self, vector: Vector) {
600 let delta = self.value_from_distance(vector.x, vector.y);
601 let factor = vector / delta;
602 let old_value = self.value();
603
604 self.set_current_value(
605 Self::value_property(),
606 MathUtilities::clamp(self.value() + delta, self.minimum(), self.maximum()),
607 );
608
609 self.last_drag.set(factor * (self.value() - old_value));
613 }
614
615 fn apply_deferred_thumb_drag(&self) {
616 if let Some(vector) = self.deferred_thumb_drag.take() {
617 self.apply_thumb_drag(vector);
618 }
619 }
620
621 fn show_children(&self, visible: bool) {
622 if let Some(thumb) = self.thumb() {
625 thumb.set_is_visible(visible);
626 }
627
628 if let Some(increase_button) = self.increase_button() {
629 increase_button.set_is_visible(visible);
630 }
631
632 if let Some(decrease_button) = self.decrease_button() {
633 decrease_button.set_is_visible(visible);
634 }
635 }
636
637 fn update_pseudo_classes(&self, o: Orientation) {
638 self.pseudo_classes().set(PC_VERTICAL, o == Orientation::Vertical);
639 self.pseudo_classes().set(PC_HORIZONTAL, o == Orientation::Horizontal);
640 }
641}