1use super::internal::slider_presenter::{
2 create_default_slider_presenter, SliderPresenter, SliderTemplate, SliderVisualState,
3};
4use super::*;
5use crate::logger;
6use crate::node::WeakFlexBox;
7use crate::{focus_adorner, focus_visibility};
8
9const DEFAULT_SLIDER_LENGTH: f32 = 180.0;
10
11fn clamp(value: f32, min: f32, max: f32) -> f32 {
12 if value < min {
13 return min;
14 }
15 if value > max {
16 return max;
17 }
18 value
19}
20
21fn create_slider_presenter(
22 template: Option<Rc<dyn SliderTemplate>>,
23 sizing: Option<SliderSizing>,
24) -> Rc<dyn SliderPresenter> {
25 if let Some(template) = template {
26 return template.create(sizing);
27 }
28 if let Some(template_set) = get_control_templates() {
29 if let Some(template) = template_set.slider {
30 return template.create(sizing);
31 }
32 }
33 create_default_slider_presenter(sizing)
34}
35
36#[derive(Clone)]
37pub struct Slider {
38 root: FlexBox,
39 slider_presenter: Rc<RefCell<Rc<dyn SliderPresenter>>>,
40 template_override: Rc<RefCell<Option<Rc<dyn SliderTemplate>>>>,
41 sizing_value: Rc<Cell<Option<SliderSizing>>>,
42 colors_value: Rc<Cell<Option<SliderColors>>>,
43 min: Rc<Cell<f32>>,
44 max: Rc<Cell<f32>>,
45 step: Rc<Cell<f32>>,
46 value: Rc<Cell<f32>>,
47 length: Rc<Cell<f32>>,
48 orientation: Rc<Cell<Orientation>>,
49 changed: Rc<RefCell<Option<SliderChangedCallback>>>,
50 hovered_state: Rc<Cell<bool>>,
51 dragging_state: Rc<Cell<bool>>,
52 focused_state: Rc<Cell<bool>>,
53 weak_root: Rc<WeakNodeRef>,
54 weak_flex_root: WeakFlexBox,
55}
56
57impl Default for Slider {
58 fn default() -> Self {
59 Self::new()
60 }
61}
62
63impl Slider {
64 pub fn new() -> Self {
65 let root = flex_box();
66 let slider_presenter = create_slider_presenter(None, None);
67 let presenter_root = slider_presenter.root();
68 presenter_root.position_type(PositionType::Absolute);
69
70 root.interactive(true)
71 .focusable(true, 0)
72 .semantic_role(SemanticRole::Slider)
73 .reflect_semantic_disabled_from_enabled()
74 .semantic_orientation(Orientation::Horizontal)
75 .semantic_value_range(0.0, 0.0, 100.0)
76 .default_semantic_label("Slider")
77 .cursor(CursorStyle::Pointer)
78 .child(&presenter_root);
79
80 let control = Self {
81 weak_root: Rc::new(root.node_ref().downgrade()),
82 weak_flex_root: root.downgrade(),
83 root,
84 slider_presenter: Rc::new(RefCell::new(slider_presenter)),
85 template_override: Rc::new(RefCell::new(None)),
86 sizing_value: Rc::new(Cell::new(None)),
87 colors_value: Rc::new(Cell::new(None)),
88 min: Rc::new(Cell::new(0.0)),
89 max: Rc::new(Cell::new(100.0)),
90 step: Rc::new(Cell::new(1.0)),
91 value: Rc::new(Cell::new(0.0)),
92 length: Rc::new(Cell::new(DEFAULT_SLIDER_LENGTH)),
93 orientation: Rc::new(Cell::new(Orientation::Horizontal)),
94 changed: Rc::new(RefCell::new(None)),
95 hovered_state: Rc::new(Cell::new(false)),
96 dragging_state: Rc::new(Cell::new(false)),
97 focused_state: Rc::new(Cell::new(false)),
98 };
99 control.install_visual_subscriptions();
100 control.bind_events();
101 let target = SliderEventTarget::from_slider(&control);
102 control.persist_state(crate::persisted::persisted_value_adapter(
103 "slider-value",
104 crate::persisted::PersistedFloat32Codec,
105 1,
106 {
107 let value = control.value.clone();
108 move || Some(value.get())
109 },
110 move |value| {
111 target.apply_persisted_value(value);
112 },
113 ));
114 control.set_value_inner(0.0, false, false);
115 control.handle_theme_changed();
116 control
117 }
118
119 fn bind_events(&self) {
120 let target = SliderEventTarget::from_slider(self);
121 self.root.on_pointer_enter(move |_event| {
122 target.handle_pointer_enter();
123 });
124
125 let target = SliderEventTarget::from_slider(self);
126 self.root.on_pointer_leave(move |_event| {
127 target.handle_pointer_leave();
128 });
129
130 let target = SliderEventTarget::from_slider(self);
131 self.root.on_pointer_down(move |event| {
132 target.handle_pointer_down(event);
133 });
134
135 let target = SliderEventTarget::from_slider(self);
136 self.root.on_pointer_move(move |event| {
137 target.handle_pointer_move(event);
138 });
139
140 let target = SliderEventTarget::from_slider(self);
141 self.root.on_pointer_up(move |event| {
142 target.handle_pointer_up(event);
143 });
144
145 let target = SliderEventTarget::from_slider(self);
146 self.root.on_key_down(move |event| {
147 target.sync_focus_chrome();
148 target.key_value(event);
149 });
150
151 let target = SliderEventTarget::from_slider(self);
152 self.root.on_focus_changed(move |event| {
153 if target.focused_state.get() != event.focused {
154 target.focused_state.set(event.focused);
155 target.handle_theme_changed();
156 }
157 });
158 }
159
160 pub fn min(&self, value: f32) -> &Self {
161 self.min.set(value);
162 if self.max.get() < value {
163 self.max.set(value);
164 }
165 self.set_value_inner(self.value.get(), true, false);
166 self
167 }
168
169 pub fn max(&self, value: f32) -> &Self {
170 self.max.set(value);
171 if self.min.get() > value {
172 self.min.set(value);
173 }
174 self.set_value_inner(self.value.get(), true, false);
175 self
176 }
177
178 pub fn step(&self, value: f32) -> &Self {
179 if value <= 0.0 {
180 logger::warn(
181 "Layout",
182 &format!("Slider.step() received {value}; clamping to 1.0."),
183 );
184 }
185 self.step.set(if value > 0.0 { value } else { 1.0 });
186 self.set_value_inner(self.value.get(), true, false);
187 self
188 }
189
190 pub fn value(&self, value: f32) -> &Self {
191 self.set_value_inner(value, true, false);
192 self
193 }
194
195 pub fn length(&self, value: f32) -> &Self {
196 let thumb_size = self.slider_presenter.borrow().metrics().thumb_size;
197 if value <= thumb_size {
198 logger::warn(
199 "Layout",
200 &format!(
201 "Slider.length() received {value}; clamping to a value above the thumb size."
202 ),
203 );
204 }
205 self.length.set(if value > thumb_size {
206 value
207 } else {
208 thumb_size + 1.0
209 });
210 self.sync_presentation();
211 self
212 }
213
214 pub fn orientation(&self, orientation: Orientation) -> &Self {
215 let orientation = if orientation == Orientation::Vertical {
216 Orientation::Vertical
217 } else {
218 Orientation::Horizontal
219 };
220 self.orientation.set(orientation);
221 self.root.semantic_orientation(orientation);
222 self.sync_semantic_label();
223 self.sync_presentation();
224 self
225 }
226
227 pub fn sizing(&self, sizing: SliderSizing) -> &Self {
228 self.set_sizing(Some(sizing))
229 }
230
231 pub fn clear_sizing(&self) -> &Self {
232 self.set_sizing(None)
233 }
234
235 fn set_sizing(&self, sizing: Option<SliderSizing>) -> &Self {
236 self.sizing_value.set(sizing);
237 self.replace_presenter(create_slider_presenter(
238 self.template_override.borrow().clone(),
239 self.sizing_value.get(),
240 ));
241 let thumb_size = self.slider_presenter.borrow().metrics().thumb_size;
242 if self.length.get() <= thumb_size {
243 self.length.set(thumb_size + 1.0);
244 }
245 self.sync_presentation();
246 self
247 }
248
249 pub fn colors(&self, colors: SliderColors) -> &Self {
250 self.set_colors(Some(colors))
251 }
252
253 pub fn clear_colors(&self) -> &Self {
254 self.set_colors(None)
255 }
256
257 fn set_colors(&self, colors: Option<SliderColors>) -> &Self {
258 self.colors_value.set(colors);
259 self.sync_presentation();
260 self
261 }
262
263 pub fn template(&self, template: Rc<dyn SliderTemplate>) -> &Self {
264 self.set_template(Some(template))
265 }
266
267 pub fn clear_template(&self) -> &Self {
268 self.set_template(None)
269 }
270
271 fn set_template(&self, template: Option<Rc<dyn SliderTemplate>>) -> &Self {
272 self.template_override.replace(template.clone());
273 self.replace_presenter(create_slider_presenter(template, self.sizing_value.get()));
274 let thumb_size = self.slider_presenter.borrow().metrics().thumb_size;
275 if self.length.get() <= thumb_size {
276 logger::warn(
277 "Layout",
278 "Slider.template() increased the thumb size beyond the current slider length; clamping length to stay interactive.",
279 );
280 self.length.set(thumb_size + 1.0);
281 }
282 self.sync_presentation();
283 self
284 }
285
286 pub fn enabled(&self, enabled: bool) -> &Self {
287 self.root.enabled(enabled);
288 if !enabled && self.dragging_state.replace(false) {
289 if let Some(handle) = upgraded_handle(&self.weak_root) {
290 crate::event::release_pointer(handle);
291 unsafe { crate::ffi::fui_release_pointer_capture() };
292 }
293 }
294 self.handle_theme_changed();
295 self
296 }
297
298 pub fn on_changed(&self, handler: impl Fn(SliderChangedEventArgs) + 'static) -> &Self {
299 *self.changed.borrow_mut() = Some(Rc::new(handler));
300 self
301 }
302
303 pub fn current_value(&self) -> f32 {
304 self.value.get()
305 }
306
307 fn install_visual_subscriptions(&self) {
308 let target = SliderEventTarget::from_slider(self);
309 let theme_guard = subscribe(move |_theme| {
310 target.handle_theme_changed();
311 });
312 self.root
313 .retained_node_ref()
314 .retain_attachment(Rc::new(theme_guard));
315
316 let target = SliderEventTarget::from_slider(self);
317 let focus_guard = focus_visibility::subscribe(move |_visible| {
318 target.sync_focus_chrome();
319 });
320 self.root
321 .retained_node_ref()
322 .retain_attachment(Rc::new(focus_guard));
323 }
324
325 fn set_value_inner(&self, value: f32, emit: bool, announce: bool) {
326 SliderEventTarget::from_slider(self).set_value_inner(value, emit, announce);
327 }
328
329 fn create_visual_state(&self) -> SliderVisualState {
330 create_slider_visual_state(
331 self.value.get(),
332 self.min.get(),
333 self.max.get(),
334 self.orientation.get(),
335 self.hovered_state.get(),
336 self.dragging_state.get(),
337 self.focused_state.get(),
338 self.is_enabled(),
339 )
340 }
341
342 fn sync_presentation(&self) {
343 let state = self.create_visual_state();
344 sync_slider_geometry_with_state(
345 &self.root,
346 self.slider_presenter.borrow().as_ref(),
347 state,
348 self.length.get(),
349 );
350 sync_slider_visual_state_with_state(
351 &self.root,
352 self.slider_presenter.borrow().as_ref(),
353 state,
354 self.colors_value.get(),
355 );
356 }
357
358 fn sync_semantic_label(&self) {
359 self.root
360 .default_semantic_label(if self.orientation.get() == Orientation::Vertical {
361 "Vertical slider"
362 } else {
363 "Slider"
364 });
365 }
366
367 fn handle_theme_changed(&self) {
368 self.root.cursor(if self.is_enabled() {
369 CursorStyle::Pointer
370 } else {
371 CursorStyle::Default
372 });
373 self.sync_presentation();
374 self.sync_focus_chrome();
375 }
376
377 fn is_enabled(&self) -> bool {
378 self.root.retained_node_ref().is_enabled_for_routing()
379 }
380
381 fn sync_focus_chrome(&self) {
382 sync_slider_focus_chrome(&self.root, self.focused_state.get(), self.is_enabled());
383 }
384
385 fn replace_presenter(&self, next_presenter: Rc<dyn SliderPresenter>) {
386 let previous = self.slider_presenter.borrow().clone();
387 if Rc::ptr_eq(&previous, &next_presenter) {
388 return;
389 }
390 let previous_root = previous.root();
391 let next_root = next_presenter.root();
392 next_root.position_type(PositionType::Absolute);
393 self.root.remove_child(&previous_root);
394 self.root.child(&next_root);
395 previous_root.dispose();
396 self.slider_presenter.replace(next_presenter);
397 }
398}
399
400impl Node for Slider {
401 fn retained_node_ref(&self) -> NodeRef {
402 self.root.retained_node_ref()
403 }
404
405 fn build_self(&self) {
406 self.sync_presentation();
407 self.sync_focus_chrome();
408 self.root.build_self();
409 }
410}
411
412impl HasFlexBoxRoot for Slider {
413 fn flex_box_root(&self) -> &FlexBox {
414 &self.root
415 }
416}
417
418impl ThemeBindable for Slider {
419 fn theme_binding_node(&self) -> NodeRef {
420 self.root.retained_node_ref()
421 }
422
423 fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
424 let target = SliderEventTarget::from_slider(self);
425 Box::new(move || target.upgrade())
426 }
427}
428
429#[derive(Clone)]
430struct SliderEventTarget {
431 weak_root: Rc<WeakNodeRef>,
432 weak_flex_root: WeakFlexBox,
433 slider_presenter: Rc<RefCell<Rc<dyn SliderPresenter>>>,
434 template_override: Rc<RefCell<Option<Rc<dyn SliderTemplate>>>>,
435 sizing_value: Rc<Cell<Option<SliderSizing>>>,
436 min: Rc<Cell<f32>>,
437 max: Rc<Cell<f32>>,
438 step: Rc<Cell<f32>>,
439 value: Rc<Cell<f32>>,
440 length: Rc<Cell<f32>>,
441 orientation: Rc<Cell<Orientation>>,
442 changed: Rc<RefCell<Option<SliderChangedCallback>>>,
443 focused_state: Rc<Cell<bool>>,
444 hovered_state: Rc<Cell<bool>>,
445 dragging_state: Rc<Cell<bool>>,
446 colors_value: Rc<Cell<Option<SliderColors>>>,
447}
448
449impl SliderEventTarget {
450 fn from_slider(slider: &Slider) -> Self {
451 Self {
452 weak_root: slider.weak_root.clone(),
453 weak_flex_root: slider.weak_flex_root.clone(),
454 slider_presenter: slider.slider_presenter.clone(),
455 template_override: slider.template_override.clone(),
456 sizing_value: slider.sizing_value.clone(),
457 min: slider.min.clone(),
458 max: slider.max.clone(),
459 step: slider.step.clone(),
460 value: slider.value.clone(),
461 length: slider.length.clone(),
462 orientation: slider.orientation.clone(),
463 changed: slider.changed.clone(),
464 focused_state: slider.focused_state.clone(),
465 hovered_state: slider.hovered_state.clone(),
466 dragging_state: slider.dragging_state.clone(),
467 colors_value: slider.colors_value.clone(),
468 }
469 }
470
471 fn upgrade(&self) -> Option<Slider> {
472 Some(Slider {
473 root: self.weak_flex_root.upgrade()?,
474 slider_presenter: self.slider_presenter.clone(),
475 template_override: self.template_override.clone(),
476 sizing_value: self.sizing_value.clone(),
477 colors_value: self.colors_value.clone(),
478 min: self.min.clone(),
479 max: self.max.clone(),
480 step: self.step.clone(),
481 value: self.value.clone(),
482 length: self.length.clone(),
483 orientation: self.orientation.clone(),
484 changed: self.changed.clone(),
485 hovered_state: self.hovered_state.clone(),
486 dragging_state: self.dragging_state.clone(),
487 focused_state: self.focused_state.clone(),
488 weak_root: self.weak_root.clone(),
489 weak_flex_root: self.weak_flex_root.clone(),
490 })
491 }
492
493 fn is_enabled(&self) -> bool {
494 self.weak_flex_root
495 .upgrade()
496 .map(|root| root.retained_node_ref().is_enabled_for_routing())
497 .unwrap_or(false)
498 }
499
500 fn handle_pointer_enter(&self) {
501 if !self.is_enabled() {
502 return;
503 }
504 self.hovered_state.set(true);
505 self.sync_presentation();
506 }
507
508 fn handle_pointer_leave(&self) {
509 if !self.is_enabled() {
510 return;
511 }
512 self.hovered_state.set(false);
513 if !self.dragging_state.get() {
514 self.sync_presentation();
515 }
516 }
517
518 fn handle_pointer_down(&self, event: &mut PointerEventArgs) {
519 if !self.is_enabled() {
520 return;
521 }
522 self.hovered_state.set(true);
523 self.dragging_state.set(true);
524 self.update_value_from_pointer(event.scene_x, event.scene_y, true, true);
525 self.sync_presentation();
526 event.capture_pointer();
527 event.handled = true;
528 }
529
530 fn handle_pointer_move(&self, event: &mut PointerEventArgs) {
531 if !self.is_enabled() || !self.dragging_state.get() || event.buttons.is_empty() {
532 return;
533 }
534 self.update_value_from_pointer(event.scene_x, event.scene_y, true, true);
535 event.handled = true;
536 }
537
538 fn handle_pointer_up(&self, event: &mut PointerEventArgs) {
539 if !self.is_enabled() || !self.dragging_state.get() {
540 return;
541 }
542 self.update_value_from_pointer(event.scene_x, event.scene_y, true, true);
543 self.dragging_state.set(false);
544 self.sync_presentation();
545 event.release_pointer_capture();
546 event.handled = true;
547 }
548
549 fn key_value(&self, event: &mut KeyEventArgs) {
550 if !self.is_enabled() || event.modifiers != 0 {
551 return;
552 }
553 let mut next = self.value.get();
554 match event.key.as_str() {
555 "Home" => next = self.min.get(),
556 "End" => next = self.max.get(),
557 "ArrowUp" if self.orientation.get() == Orientation::Vertical => next += self.step.get(),
558 "ArrowDown" if self.orientation.get() == Orientation::Vertical => {
559 next -= self.step.get()
560 }
561 "ArrowRight" if self.orientation.get() == Orientation::Horizontal => {
562 next += self.step.get()
563 }
564 "ArrowLeft" if self.orientation.get() == Orientation::Horizontal => {
565 next -= self.step.get()
566 }
567 _ => return,
568 }
569 self.set_value_inner(next, true, true);
570 event.handled = true;
571 }
572
573 fn apply_persisted_value(&self, value: f32) {
574 self.set_value_inner(value, true, false);
575 }
576
577 fn set_value_inner(&self, next: f32, emit: bool, announce: bool) {
578 let normalized =
579 normalize_slider_value(next, self.min.get(), self.max.get(), self.step.get());
580 if (normalized - self.value.get()).abs() <= f32::EPSILON {
581 self.sync_presentation();
582 return;
583 }
584 self.value.set(normalized);
585 if let Some(root) = self.weak_flex_root.upgrade() {
586 root.semantic_value_range(normalized, self.min.get(), self.max.get());
587 if announce && root.has_built_handle() {
588 ui::request_semantic_announcement(root.handle().raw());
589 }
590 }
591 self.sync_presentation();
592 if emit {
593 if let Some(callback) = self.changed.borrow().clone() {
594 callback(SliderChangedEventArgs { value: normalized });
595 }
596 }
597 }
598
599 fn update_value_from_pointer(&self, x: f32, y: f32, emit: bool, announce: bool) {
600 let thumb_size = self.slider_presenter.borrow().metrics().thumb_size;
601 let available = self.length.get() - thumb_size;
602 if available <= 0.0 {
603 return;
604 }
605 let (mut local_x, mut local_y) = (x, y);
606 if let Some(handle) = upgraded_handle(&self.weak_root) {
607 if let Some(bounds) = ui::get_bounds(handle.raw()) {
608 local_x = x - bounds[0];
609 local_y = y - bounds[1];
610 }
611 }
612 let leading_inset = SLIDER_OUTER_INSET + (thumb_size * 0.5);
613 let offset = if self.orientation.get() == Orientation::Vertical {
614 available - clamp(local_y - leading_inset, 0.0, available)
615 } else {
616 clamp(local_x - leading_inset, 0.0, available)
617 };
618 let fraction = clamp(offset / available, 0.0, 1.0);
619 let next = self.min.get() + ((self.max.get() - self.min.get()) * fraction);
620 self.set_value_inner(next, emit, announce);
621 }
622
623 fn create_visual_state(&self) -> SliderVisualState {
624 create_slider_visual_state(
625 self.value.get(),
626 self.min.get(),
627 self.max.get(),
628 self.orientation.get(),
629 self.hovered_state.get(),
630 self.dragging_state.get(),
631 self.focused_state.get(),
632 self.is_enabled(),
633 )
634 }
635
636 fn sync_presentation(&self) {
637 let Some(root) = self.weak_flex_root.upgrade() else {
638 return;
639 };
640 let presenter = self.slider_presenter.borrow().clone();
641 let state = self.create_visual_state();
642 sync_slider_geometry_with_state(&root, presenter.as_ref(), state, self.length.get());
643 sync_slider_visual_state_with_state(
644 &root,
645 presenter.as_ref(),
646 state,
647 self.colors_value.get(),
648 );
649 }
650
651 fn handle_theme_changed(&self) {
652 let Some(root) = self.weak_flex_root.upgrade() else {
653 return;
654 };
655 root.cursor(if self.is_enabled() {
656 CursorStyle::Pointer
657 } else {
658 CursorStyle::Default
659 });
660 self.sync_presentation();
661 self.sync_focus_chrome();
662 }
663
664 fn sync_focus_chrome(&self) {
665 let Some(root) = self.weak_flex_root.upgrade() else {
666 return;
667 };
668 sync_slider_focus_chrome(
669 &root,
670 self.focused_state.get(),
671 root.retained_node_ref().is_enabled_for_routing(),
672 );
673 }
674}
675
676#[allow(clippy::too_many_arguments)]
677fn create_slider_visual_state(
678 value: f32,
679 min: f32,
680 max: f32,
681 orientation: Orientation,
682 hovered: bool,
683 dragging: bool,
684 focused: bool,
685 enabled: bool,
686) -> SliderVisualState {
687 let normalized_value = if max > min {
688 clamp((value - min) / (max - min), 0.0, 1.0)
689 } else {
690 0.0
691 };
692 SliderVisualState::new(
693 value,
694 min,
695 max,
696 normalized_value,
697 orientation,
698 hovered,
699 dragging,
700 focused,
701 enabled,
702 )
703}
704
705fn sync_slider_geometry_with_state(
706 root: &FlexBox,
707 presenter: &dyn SliderPresenter,
708 state: SliderVisualState,
709 length: f32,
710) {
711 let metrics = presenter.metrics();
712 if state.orientation == Orientation::Vertical {
713 root.width(
714 metrics.thumb_size + (SLIDER_OUTER_INSET * 2.0) + metrics.cross_axis_extra,
715 Unit::Pixel,
716 )
717 .height(length + (SLIDER_OUTER_INSET * 2.0), Unit::Pixel);
718 } else {
719 root.width(length + (SLIDER_OUTER_INSET * 2.0), Unit::Pixel)
720 .height(
721 metrics.thumb_size + (SLIDER_OUTER_INSET * 2.0) + metrics.cross_axis_extra,
722 Unit::Pixel,
723 );
724 }
725 presenter
726 .root()
727 .position_type(PositionType::Absolute)
728 .position(SLIDER_CHILD_INSET, SLIDER_CHILD_INSET);
729 presenter.layout(state, length);
730 root.semantic_value_range(state.value, state.min, state.max);
731 root.default_semantic_label(if state.orientation == Orientation::Vertical {
732 "Vertical slider"
733 } else {
734 "Slider"
735 });
736}
737
738fn sync_slider_visual_state_with_state(
739 root: &FlexBox,
740 presenter: &dyn SliderPresenter,
741 state: SliderVisualState,
742 colors: Option<SliderColors>,
743) {
744 let theme = current_theme();
745 root.corner_radius(theme.spacing.sm)
746 .border(SLIDER_FOCUS_BORDER_WIDTH, theme.colors.background)
747 .padding(
748 SLIDER_PADDING,
749 SLIDER_PADDING,
750 SLIDER_PADDING,
751 SLIDER_PADDING,
752 )
753 .opacity(if state.enabled { 1.0 } else { 0.6 });
754 presenter.apply(theme, state, colors);
755}
756
757fn sync_slider_focus_chrome(root: &FlexBox, focused: bool, enabled: bool) {
758 if focused && enabled && focus_visibility::keyboard_focus_visible() {
759 focus_adorner::show_standard(root, current_theme().spacing.sm);
760 return;
761 }
762 focus_adorner::hide_owner(root);
763}