druid/widget/
slider.rs

1// Copyright 2019 The Druid Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! A slider widget.
16
17use crate::debug_state::DebugState;
18use crate::kurbo::{Circle, Line};
19use crate::theme::TEXT_COLOR;
20use crate::widget::prelude::*;
21use crate::widget::Axis;
22use crate::{theme, Color, KeyOrValue, LinearGradient, Point, Rect, UnitPoint, Vec2, WidgetPod};
23use druid::kurbo::{PathEl, Shape};
24use druid::piet::{PietText, PietTextLayout, Text, TextLayout, TextLayoutBuilder};
25use tracing::{instrument, trace, warn};
26
27const TRACK_THICKNESS: f64 = 4.0;
28const BORDER_WIDTH: f64 = 2.0;
29const KNOB_STROKE_WIDTH: f64 = 2.0;
30
31/// A slider, allowing interactive update of a numeric value.
32///
33/// This slider implements `Widget<f64>`, and works on values clamped
34/// in the range `min..max`.
35#[derive(Debug, Clone, Default)]
36pub struct Slider {
37    mapping: SliderValueMapping,
38    knob: SliderKnob,
39    track_color: Option<KeyOrValue<Color>>,
40    knob_style: KnobStyle,
41}
42
43/// A range slider, allowing interactive update of two numeric values .
44///
45/// This slider implements `Widget<(f64, f64)>`, and works on value pairs clamped
46/// in the range `min..max`, where the left value is always smaller than the right.
47#[derive(Debug, Clone, Default)]
48pub struct RangeSlider {
49    mapping: SliderValueMapping,
50    left_knob: SliderKnob,
51    right_knob: SliderKnob,
52    track_color: Option<KeyOrValue<Color>>,
53    knob_style: KnobStyle,
54}
55
56/// A annotated Slider or RangeSlider
57pub struct Annotated<T, W: Widget<T>> {
58    inner: WidgetPod<T, W>,
59
60    mapping: SliderValueMapping,
61    labeled_steps: f64,
62    unlabeled_steps: f64,
63
64    labels: Vec<PietTextLayout>,
65}
66
67#[derive(Copy, Clone, Debug)]
68pub struct SliderValueMapping {
69    min: f64,
70    max: f64,
71    step: Option<f64>,
72    axis: Axis,
73}
74
75#[derive(Debug, Clone, Default)]
76struct SliderKnob {
77    hovered: bool,
78    active: bool,
79    offset: f64,
80}
81
82/// The shape of the slider knobs.
83#[derive(Debug, Copy, Clone)]
84pub enum KnobStyle {
85    /// Circle
86    Circle,
87    /// Wedge
88    Wedge,
89}
90
91impl Default for KnobStyle {
92    fn default() -> Self {
93        Self::Circle
94    }
95}
96
97impl Slider {
98    /// Create a new `Slider`.
99    pub fn new() -> Slider {
100        Default::default()
101    }
102
103    /// Builder-style method to set the range covered by this slider.
104    ///
105    /// The default range is `0.0..1.0`.
106    pub fn with_range(mut self, min: f64, max: f64) -> Self {
107        self.mapping.min = min;
108        self.mapping.max = max;
109        self
110    }
111
112    /// Builder-style method to set the stepping.
113    ///
114    /// The default step size is `0.0` (smooth).
115    pub fn with_step(mut self, step: f64) -> Self {
116        if step < 0.0 {
117            warn!("bad stepping (must be positive): {}", step);
118            return self;
119        }
120        self.mapping.step = if step > 0.0 {
121            Some(step)
122        } else {
123            // A stepping value of 0.0 would yield an infinite amount of steps.
124            // Enforce no stepping instead.
125            None
126        };
127        self
128    }
129
130    /// Builder-style method to set the track color.
131    ///
132    /// The default color is `None`.
133    pub fn track_color(mut self, color: impl Into<Option<KeyOrValue<Color>>>) -> Self {
134        self.track_color = color.into();
135        self
136    }
137
138    /// Builder-style method to set the knob style.
139    ///
140    /// The default is `Circle`.
141    pub fn knob_style(mut self, knob_style: KnobStyle) -> Self {
142        self.knob_style = knob_style;
143        self
144    }
145
146    /// Builder-style method to the axis on which the slider moves.
147    ///
148    /// The default is `Horizontal`.
149    pub fn axis(mut self, axis: Axis) -> Self {
150        self.mapping.axis = axis;
151        self
152    }
153
154    /// Returns the Mapping of this Slider.
155    pub fn get_mapping(&self) -> SliderValueMapping {
156        self.mapping
157    }
158
159    /// Builder-style method to create an annotated range slider.
160    ///
161    pub fn annotated(self, named_steps: f64, unnamed_steps: f64) -> Annotated<f64, Self> {
162        let mapping = self.mapping;
163        Annotated::new(self, mapping, named_steps, unnamed_steps)
164    }
165}
166
167impl Widget<f64> for Slider {
168    #[instrument(name = "Slider", level = "trace", skip(self, ctx, event, data, env))]
169    fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut f64, env: &Env) {
170        if !ctx.is_disabled() {
171            self.knob
172                .handle_input(ctx, event, data, env, self.mapping, self.knob_style);
173
174            ctx.set_active(self.knob.is_active());
175
176            if let Event::MouseDown(me) = event {
177                if !self.knob.active {
178                    self.knob.activate(0.0);
179                    let knob_size = env.get(theme::BASIC_WIDGET_HEIGHT);
180                    *data = self
181                        .mapping
182                        .calculate_value(me.pos, knob_size, ctx.size(), 0.0);
183                    ctx.request_paint();
184                    ctx.set_active(true);
185                }
186            }
187        }
188    }
189
190    #[instrument(name = "Slider", level = "trace", skip(self, ctx, event, _data, _env))]
191    fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, _data: &f64, _env: &Env) {
192        match event {
193            // checked in LifeCycle::WidgetAdded because logging may not be setup in with_range
194            LifeCycle::WidgetAdded => self.mapping.check_range(),
195            LifeCycle::DisabledChanged(_) => ctx.request_paint(),
196            _ => (),
197        }
198    }
199
200    #[instrument(
201        name = "Slider",
202        level = "trace",
203        skip(self, ctx, _old_data, _data, _env)
204    )]
205    fn update(&mut self, ctx: &mut UpdateCtx, _old_data: &f64, _data: &f64, _env: &Env) {
206        ctx.request_paint();
207    }
208
209    #[instrument(name = "Slider", level = "trace", skip(self, ctx, bc, _data, env))]
210    fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, _data: &f64, env: &Env) -> Size {
211        bc.debug_check("Slider");
212        slider_layout(ctx, bc, env, self.mapping)
213    }
214
215    #[instrument(name = "Slider", level = "trace", skip(self, ctx, data, env))]
216    fn paint(&mut self, ctx: &mut PaintCtx, data: &f64, env: &Env) {
217        paint_slider_background(ctx, 0.0, *data, &self.track_color, self.mapping, env);
218
219        self.knob
220            .paint(ctx, *data, env, self.mapping, self.knob_style);
221    }
222
223    fn debug_state(&self, data: &f64) -> DebugState {
224        DebugState {
225            display_name: self.short_type_name().to_string(),
226            main_value: data.to_string(),
227            ..Default::default()
228        }
229    }
230}
231
232impl RangeSlider {
233    /// Create a new `RangeSlider`.
234    pub fn new() -> RangeSlider {
235        Default::default()
236    }
237
238    /// Builder-style method to set the range covered by this range slider.
239    ///
240    /// The default range is `0.0..1.0`.
241    pub fn with_range(mut self, min: f64, max: f64) -> Self {
242        self.mapping.min = min;
243        self.mapping.max = max;
244        self
245    }
246
247    /// Builder-style method to set the stepping.
248    ///
249    /// The default step size is `0.0` (smooth).
250    pub fn with_step(mut self, step: f64) -> Self {
251        if step < 0.0 {
252            warn!("bad stepping (must be positive): {}", step);
253            return self;
254        }
255        self.mapping.step = if step > 0.0 {
256            Some(step)
257        } else {
258            // A stepping value of 0.0 would yield an infinite amount of steps.
259            // Enforce no stepping instead.
260            None
261        };
262        self
263    }
264
265    /// Builder-style method to set the track color.
266    ///
267    /// The default color is `None`.
268    pub fn track_color(mut self, color: impl Into<Option<KeyOrValue<Color>>>) -> Self {
269        self.track_color = color.into();
270        self
271    }
272
273    /// Builder-style method to set the knob style.
274    ///
275    /// The default is `Circle`.
276    pub fn knob_style(mut self, knob_style: KnobStyle) -> Self {
277        self.knob_style = knob_style;
278        self
279    }
280
281    /// Builder-style method to set the axis on which the slider moves.
282    ///
283    /// The default is `Horizontal`.
284    pub fn axis(mut self, axis: Axis) -> Self {
285        self.mapping.axis = axis;
286        self
287    }
288
289    /// Returns the Mapping of this Slider.
290    pub fn get_mapping(&self) -> SliderValueMapping {
291        self.mapping
292    }
293
294    /// Builder-style method to create an annotated range slider.
295    ///
296    pub fn annotated(self, named_steps: f64, unnamed_steps: f64) -> Annotated<(f64, f64), Self> {
297        let mapping = self.mapping;
298        Annotated::new(self, mapping, named_steps, unnamed_steps)
299    }
300}
301
302impl Widget<(f64, f64)> for RangeSlider {
303    #[instrument(
304        name = "RangeSlider",
305        level = "trace",
306        skip(self, ctx, event, data, env)
307    )]
308    fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut (f64, f64), env: &Env) {
309        if !ctx.is_disabled() {
310            if !self.right_knob.is_active() {
311                self.left_knob.handle_input(
312                    ctx,
313                    event,
314                    &mut data.0,
315                    env,
316                    self.mapping,
317                    self.knob_style,
318                );
319                data.0 = data.0.min(data.1);
320                //Ensure that the left knob stays left
321
322                if self.left_knob.is_active() {
323                    self.right_knob.deactivate();
324                }
325            }
326            if !self.left_knob.is_active() {
327                self.right_knob.handle_input(
328                    ctx,
329                    event,
330                    &mut data.1,
331                    env,
332                    self.mapping,
333                    self.knob_style,
334                );
335                //Ensure that the right knob stays right
336                data.1 = data.1.max(data.0);
337
338                if self.right_knob.is_active() {
339                    self.left_knob.deactivate();
340                }
341            }
342            ctx.set_active(self.left_knob.is_active() || self.right_knob.is_active());
343
344            if let Event::MouseDown(me) = event {
345                if !self.left_knob.is_active() && !self.right_knob.is_active() {
346                    let knob_size = env.get(theme::BASIC_WIDGET_HEIGHT);
347                    let press_value =
348                        self.mapping
349                            .calculate_value(me.pos, knob_size, ctx.size(), 0.0);
350
351                    if press_value - data.0 < data.1 - press_value {
352                        self.left_knob.activate(0.0);
353                        data.0 = press_value;
354                    } else {
355                        self.right_knob.activate(0.0);
356                        data.1 = press_value;
357                    }
358                    ctx.set_active(true);
359                    ctx.request_paint();
360                }
361            }
362        }
363    }
364
365    #[instrument(
366        name = "RangeSlider",
367        level = "trace",
368        skip(self, ctx, event, _data, _env)
369    )]
370    fn lifecycle(
371        &mut self,
372        ctx: &mut LifeCycleCtx,
373        event: &LifeCycle,
374        _data: &(f64, f64),
375        _env: &Env,
376    ) {
377        match event {
378            // checked in LifeCycle::WidgetAdded because logging may not be setup in with_range
379            LifeCycle::WidgetAdded => self.mapping.check_range(),
380            LifeCycle::DisabledChanged(_) => ctx.request_paint(),
381            _ => (),
382        }
383    }
384
385    #[instrument(
386        name = "RangeSlider",
387        level = "trace",
388        skip(self, ctx, _old_data, _data, _env)
389    )]
390    fn update(
391        &mut self,
392        ctx: &mut UpdateCtx,
393        _old_data: &(f64, f64),
394        _data: &(f64, f64),
395        _env: &Env,
396    ) {
397        ctx.request_paint();
398    }
399
400    #[instrument(name = "RangeSlider", level = "trace", skip(self, ctx, bc, _data, env))]
401    fn layout(
402        &mut self,
403        ctx: &mut LayoutCtx,
404        bc: &BoxConstraints,
405        _data: &(f64, f64),
406        env: &Env,
407    ) -> Size {
408        bc.debug_check("Slider");
409        slider_layout(ctx, bc, env, self.mapping)
410    }
411
412    #[instrument(name = "RangeSlider", level = "trace", skip(self, ctx, data, env))]
413    fn paint(&mut self, ctx: &mut PaintCtx, data: &(f64, f64), env: &Env) {
414        paint_slider_background(ctx, data.0, data.1, &self.track_color, self.mapping, env);
415
416        // We paint the left knob at last since it receives events first and therefore behaves like
417        // being "on top".
418        self.right_knob
419            .paint(ctx, data.1, env, self.mapping, self.knob_style);
420        self.left_knob
421            .paint(ctx, data.0, env, self.mapping, self.knob_style);
422    }
423
424    fn debug_state(&self, data: &(f64, f64)) -> DebugState {
425        DebugState {
426            display_name: self.short_type_name().to_string(),
427            main_value: format!("{data:?}"),
428            ..Default::default()
429        }
430    }
431}
432
433impl<T, W: Widget<T>> Annotated<T, W> {
434    pub fn new(
435        inner: W,
436        mapping: SliderValueMapping,
437        labeled_steps: f64,
438        unlabeled_steps: f64,
439    ) -> Self {
440        Annotated {
441            inner: WidgetPod::new(inner),
442
443            mapping,
444            labeled_steps: labeled_steps.abs(),
445            unlabeled_steps: unlabeled_steps.abs(),
446
447            labels: Vec::new(),
448        }
449    }
450
451    fn sanitise_values(&mut self) {
452        let labeled = self.mapping.range() / self.labeled_steps;
453        if !labeled.is_finite() || labeled > 100.0 {
454            warn!("Annotated: provided labeled interval \"{}\" has too many steps inside the sliders range {}..{}", self.labeled_steps, self.mapping.min, self.mapping.max);
455            self.labeled_steps = self.mapping.range() / 5.0;
456        }
457
458        let unlabeled = self.mapping.range() / self.unlabeled_steps;
459        if !unlabeled.is_finite() || unlabeled > 10000.0 {
460            warn!("Annotated: provided unlabeled interval \"{}\" has too many steps inside the sliders range {}..{}", self.unlabeled_steps, self.mapping.min, self.mapping.max);
461            self.unlabeled_steps = self.mapping.range() / 20.0;
462        }
463    }
464
465    fn build_labels(&mut self, text: &mut PietText, text_color: Color) {
466        self.labels.clear();
467
468        let mut walk = self.mapping.min;
469        while walk < self.mapping.max + f64::EPSILON * 10.0 {
470            let layout = text
471                .new_text_layout(format!("{walk}"))
472                .text_color(text_color)
473                .build()
474                .unwrap();
475
476            self.labels.push(layout);
477
478            walk += self.labeled_steps;
479        }
480    }
481
482    fn line_dir(&self) -> Vec2 {
483        match self.mapping.axis {
484            Axis::Horizontal => Vec2::new(0.0, 1.0),
485            Axis::Vertical => Vec2::new(-1.0, 0.0),
486        }
487    }
488}
489
490impl<T: Data, W: Widget<T>> Widget<T> for Annotated<T, W> {
491    #[instrument(name = "Annotated", level = "trace", skip(self, ctx, event, data, env))]
492    fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
493        self.inner.event(ctx, event, data, env);
494    }
495
496    #[instrument(name = "Annotated", level = "trace", skip(self, ctx, event, data, env))]
497    fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
498        if let LifeCycle::WidgetAdded = event {
499            self.sanitise_values();
500            self.build_labels(ctx.text(), env.get(TEXT_COLOR));
501        }
502        self.inner.lifecycle(ctx, event, data, env);
503    }
504
505    #[instrument(
506        name = "Annotated",
507        level = "trace",
508        skip(self, ctx, _old_data, data, env)
509    )]
510    fn update(&mut self, ctx: &mut UpdateCtx, _old_data: &T, data: &T, env: &Env) {
511        self.inner.update(ctx, data, env);
512        if ctx.env_key_changed(&TEXT_COLOR) {
513            self.build_labels(ctx.text(), env.get(TEXT_COLOR));
514            ctx.request_paint();
515        }
516    }
517
518    #[instrument(name = "Annotated", level = "trace", skip(self, bc, ctx, data, env))]
519    fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
520        let label_size = Size::new(40.0, 20.0);
521
522        match self.mapping.axis {
523            Axis::Vertical => {
524                let child_bc = bc.shrink((label_size.width, 0.0));
525                let child_size = self.inner.layout(ctx, &child_bc, data, env);
526                self.inner
527                    .set_origin(ctx, Point::new(label_size.width, 0.0));
528
529                Size::new(child_size.width + label_size.width, child_size.height)
530            }
531            Axis::Horizontal => {
532                let child_bc = bc.shrink((0.0, label_size.height));
533                let child_size = self.inner.layout(ctx, &child_bc, data, env);
534                self.inner.set_origin(ctx, Point::ZERO);
535
536                ctx.set_baseline_offset(self.inner.baseline_offset() + label_size.height);
537                Size::new(child_size.width, child_size.height + label_size.height)
538            }
539        }
540    }
541
542    #[instrument(name = "Annotated", level = "trace", skip(self, ctx, data, env))]
543    fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
544        let short_stroke = 3.0;
545        let long_stroke = 6.0;
546        let stroke_offset = 6.0;
547
548        let slider_offset = Point::new(self.inner.layout_rect().x0, self.inner.layout_rect().y0);
549
550        let knob_size = env.get(theme::BASIC_WIDGET_HEIGHT);
551        let slider_size = self.inner.layout_rect().size();
552
553        let text_color = env.get(TEXT_COLOR);
554
555        let mut walk = self.mapping.min;
556        while walk < self.mapping.max + f64::EPSILON * 10.0 {
557            let center = self
558                .mapping
559                .get_point(walk, knob_size, slider_size)
560                .to_vec2()
561                + slider_offset.to_vec2();
562
563            let line = Line::new(
564                (center + self.line_dir() * stroke_offset).to_point(),
565                (center + self.line_dir() * (stroke_offset + short_stroke)).to_point(),
566            );
567
568            ctx.stroke(line, &text_color, 1.0);
569            walk += self.unlabeled_steps;
570        }
571
572        let mut walk = self.mapping.min;
573        let mut labels = self.labels.iter();
574        while walk < self.mapping.max + f64::EPSILON * 10.0 {
575            let center = self
576                .mapping
577                .get_point(walk, knob_size, slider_size)
578                .to_vec2()
579                + slider_offset.to_vec2();
580
581            let line = Line::new(
582                (center + self.line_dir() * stroke_offset).to_point(),
583                (center + self.line_dir() * (stroke_offset + long_stroke)).to_point(),
584            );
585
586            ctx.stroke(line, &text_color, 1.0);
587
588            let label = labels.next().unwrap();
589            let origin = match self.mapping.axis {
590                Axis::Horizontal => Vec2::new(label.size().width / 2.0, 0.0),
591                Axis::Vertical => Vec2::new(label.size().width, label.size().height / 2.0),
592            };
593
594            ctx.draw_text(
595                label,
596                (center + self.line_dir() * (stroke_offset + long_stroke) - origin).to_point(),
597            );
598
599            walk += self.labeled_steps;
600        }
601
602        self.inner.paint(ctx, data, env);
603    }
604
605    fn debug_state(&self, data: &T) -> DebugState {
606        DebugState {
607            display_name: "Annotated".to_string(),
608            children: vec![self.inner.widget().debug_state(data)],
609            ..Default::default()
610        }
611    }
612}
613
614impl SliderValueMapping {
615    pub fn new() -> Self {
616        Self {
617            min: 0.0,
618            max: 1.0,
619            step: None,
620            axis: Axis::Horizontal,
621        }
622    }
623
624    fn calculate_value(
625        &self,
626        mouse_pos: Point,
627        knob_size: f64,
628        slider_size: Size,
629        offset: f64,
630    ) -> f64 {
631        // The vertical slider has its lowest value at the bottom.
632        let mouse_pos = Point::new(mouse_pos.x, slider_size.height - mouse_pos.y);
633
634        let scalar = (self.axis.major_pos(mouse_pos) - knob_size / 2.)
635            / (self.axis.major(slider_size) - knob_size);
636
637        let mut value =
638            (self.min + scalar * (self.max - self.min) + offset).clamp(self.min, self.max);
639
640        if let Some(step) = self.step {
641            let max_step_value = ((self.max - self.min) / step).floor() * step + self.min;
642            if value > max_step_value {
643                // edge case: make sure max is reachable
644                let left_dist = value - max_step_value;
645                let right_dist = self.max - value;
646                value = if left_dist < right_dist {
647                    max_step_value
648                } else {
649                    self.max
650                };
651            } else {
652                // snap to discrete intervals
653                value = (((value - self.min) / step).round() * step + self.min).min(self.max);
654            }
655        }
656        value
657    }
658
659    fn get_point(&self, value: f64, knob_size: f64, widget_size: Size) -> Point {
660        let knob_major =
661            (self.axis.major(widget_size) - knob_size) * self.normalize(value) + knob_size / 2.;
662        let (w, h) = self.axis.pack(knob_major, knob_size / 2.);
663        Point::new(w, widget_size.height - h)
664    }
665
666    fn normalize(&self, data: f64) -> f64 {
667        (data.clamp(self.min, self.max) - self.min) / (self.max - self.min)
668    }
669
670    /// check self.min <= self.max, if not swaps the values.
671    fn check_range(&mut self) {
672        if self.max < self.min {
673            warn!(
674                "min({}) should be less than max({}), swapping the values",
675                self.min, self.max
676            );
677            std::mem::swap(&mut self.max, &mut self.min);
678        }
679    }
680
681    /// the distance between min and max
682    fn range(&self) -> f64 {
683        self.max - self.min
684    }
685}
686
687impl Default for SliderValueMapping {
688    fn default() -> Self {
689        SliderValueMapping {
690            min: 0.0,
691            max: 1.0,
692            step: None,
693            axis: Axis::Horizontal,
694        }
695    }
696}
697
698impl SliderKnob {
699    fn handle_input(
700        &mut self,
701        ctx: &mut EventCtx,
702        event: &Event,
703        data: &mut f64,
704        env: &Env,
705        mapping: SliderValueMapping,
706        knob_style: KnobStyle,
707    ) {
708        let knob_size = env.get(theme::BASIC_WIDGET_HEIGHT);
709        let slider_size = ctx.size();
710
711        let point_to_val = |point: Point, offset: f64| {
712            mapping.calculate_value(point, knob_size, slider_size, offset)
713        };
714
715        let hit_test = |val: &mut f64, mouse_pos: Point| {
716            let center = mapping.get_point(*val, knob_size, slider_size);
717            match knob_style {
718                KnobStyle::Circle => center.distance(mouse_pos) < knob_size,
719                KnobStyle::Wedge => {
720                    (&knob_wedge(center, knob_size, mapping.axis)[..]).winding(mouse_pos) != 0
721                }
722            }
723        };
724
725        match event {
726            Event::MouseDown(mouse) => {
727                if !ctx.is_disabled() && hit_test(data, mouse.pos) {
728                    self.offset = *data - point_to_val(mouse.pos, 0.0);
729                    self.active = true;
730                    ctx.request_paint();
731                }
732            }
733            Event::MouseUp(mouse) => {
734                if self.active && !ctx.is_disabled() {
735                    *data = point_to_val(mouse.pos, self.offset);
736                    ctx.request_paint();
737                }
738                self.active = false;
739            }
740            Event::MouseMove(mouse) => {
741                if !ctx.is_disabled() {
742                    if self.active {
743                        *data = point_to_val(mouse.pos, self.offset);
744                        ctx.request_paint();
745                    }
746                    if ctx.is_hot() {
747                        let knob_hover = hit_test(data, mouse.pos);
748                        if knob_hover != self.hovered {
749                            self.hovered = knob_hover;
750                            ctx.request_paint();
751                        }
752                    }
753                } else {
754                    self.active = false
755                }
756            }
757            _ => (),
758        }
759    }
760
761    fn deactivate(&mut self) {
762        self.hovered = false;
763        self.active = false;
764    }
765
766    fn activate(&mut self, x_offset: f64) {
767        self.hovered = true;
768        self.active = true;
769        self.offset = x_offset;
770    }
771
772    fn is_active(&self) -> bool {
773        self.active
774    }
775
776    fn paint(
777        &self,
778        ctx: &mut PaintCtx,
779        value: f64,
780        env: &Env,
781        settings: SliderValueMapping,
782        knob_style: KnobStyle,
783    ) {
784        let knob_size = env.get(theme::BASIC_WIDGET_HEIGHT);
785
786        let knob_gradient = if ctx.is_disabled() {
787            LinearGradient::new(
788                UnitPoint::TOP,
789                UnitPoint::BOTTOM,
790                (
791                    env.get(theme::DISABLED_FOREGROUND_LIGHT),
792                    env.get(theme::DISABLED_FOREGROUND_DARK),
793                ),
794            )
795        } else if self.active {
796            LinearGradient::new(
797                UnitPoint::TOP,
798                UnitPoint::BOTTOM,
799                (
800                    env.get(theme::FOREGROUND_DARK),
801                    env.get(theme::FOREGROUND_LIGHT),
802                ),
803            )
804        } else {
805            LinearGradient::new(
806                UnitPoint::TOP,
807                UnitPoint::BOTTOM,
808                (
809                    env.get(theme::FOREGROUND_LIGHT),
810                    env.get(theme::FOREGROUND_DARK),
811                ),
812            )
813        };
814
815        //Paint the border
816        let border_color = if (self.hovered || self.active) && !ctx.is_disabled() {
817            env.get(theme::FOREGROUND_LIGHT)
818        } else {
819            env.get(theme::FOREGROUND_DARK)
820        };
821
822        match knob_style {
823            KnobStyle::Circle => {
824                let knob_circle = Circle::new(
825                    settings.get_point(value, knob_size, ctx.size()),
826                    (knob_size - KNOB_STROKE_WIDTH) / 2.,
827                );
828
829                ctx.stroke(knob_circle, &border_color, KNOB_STROKE_WIDTH);
830
831                //Actually paint the knob
832                ctx.fill(knob_circle, &knob_gradient);
833            }
834            KnobStyle::Wedge => {
835                let center = settings.get_point(value, knob_size, ctx.size());
836
837                let knob_wedge = knob_wedge(center, knob_size, settings.axis);
838
839                ctx.stroke(&knob_wedge[..], &border_color, KNOB_STROKE_WIDTH);
840
841                //Actually paint the knob
842                ctx.fill(&knob_wedge[..], &knob_gradient);
843            }
844        }
845    }
846}
847
848fn knob_wedge(center: Point, knob_size: f64, axis: Axis) -> [PathEl; 6] {
849    let (top, right, left, middle, down) = match axis {
850        Axis::Horizontal => (
851            Vec2::new(0.0, center.y - knob_size / 2.0),
852            Vec2::new(center.x + knob_size / 3.5, 0.0),
853            Vec2::new(center.x - knob_size / 3.5, 0.0),
854            Vec2::new(0.0, center.y + knob_size / 5.0),
855            Vec2::new(center.x, center.y + knob_size / 2.0),
856        ),
857        Axis::Vertical => (
858            Vec2::new(center.x + knob_size / 2.0, 0.0),
859            Vec2::new(0.0, center.y + knob_size / 3.5),
860            Vec2::new(0.0, center.y - knob_size / 3.5),
861            Vec2::new(center.x - knob_size / 5.0, 0.0),
862            Vec2::new(center.x - knob_size / 2.0, center.y),
863        ),
864    };
865
866    [
867        PathEl::MoveTo(down.to_point()),
868        PathEl::LineTo((right + middle).to_point()),
869        PathEl::LineTo((right + top).to_point()),
870        PathEl::LineTo((left + top).to_point()),
871        PathEl::LineTo((left + middle).to_point()),
872        PathEl::ClosePath,
873    ]
874}
875
876fn paint_slider_background(
877    ctx: &mut PaintCtx,
878    lower: f64,
879    higher: f64,
880    track_color: &Option<KeyOrValue<Color>>,
881    mapping: SliderValueMapping,
882    env: &Env,
883) {
884    let size = ctx.size();
885    let knob_size = env.get(theme::BASIC_WIDGET_HEIGHT);
886
887    //Paint the background
888    let background_rect = Rect::from_points(
889        mapping.get_point(mapping.min, knob_size, size),
890        mapping.get_point(mapping.max, knob_size, size),
891    )
892    .inset((TRACK_THICKNESS - BORDER_WIDTH) / 2.)
893    .to_rounded_rect(2.);
894
895    let background_gradient = LinearGradient::new(
896        UnitPoint::TOP,
897        UnitPoint::BOTTOM,
898        (
899            env.get(theme::BACKGROUND_LIGHT),
900            env.get(theme::BACKGROUND_DARK),
901        ),
902    );
903
904    ctx.stroke(background_rect, &env.get(theme::BORDER_DARK), BORDER_WIDTH);
905
906    ctx.fill(background_rect, &background_gradient);
907
908    if let Some(color) = track_color {
909        let color = color.resolve(env);
910
911        let shape = Rect::from_points(
912            mapping.get_point(lower, knob_size, size),
913            mapping.get_point(higher, knob_size, size),
914        )
915        .inset(TRACK_THICKNESS / 2.0)
916        .to_rounded_rect(2.);
917
918        ctx.fill(shape, &color);
919    }
920}
921
922fn slider_layout(
923    ctx: &mut LayoutCtx,
924    bc: &BoxConstraints,
925    env: &Env,
926    mapping: SliderValueMapping,
927) -> Size {
928    let height = env.get(theme::BASIC_WIDGET_HEIGHT);
929    let width = env.get(theme::WIDE_WIDGET_WIDTH);
930    let size = bc.constrain(mapping.axis.pack(width, height));
931
932    if mapping.axis == Axis::Horizontal {
933        let baseline_offset = (height / 2.0) - TRACK_THICKNESS;
934
935        ctx.set_baseline_offset(baseline_offset);
936        trace!(
937            "Computed layout: size={}, baseline_offset={:?}",
938            size,
939            baseline_offset
940        );
941    } else {
942        trace!("Computed layout: size={}", size,);
943    }
944
945    size
946}