veloren_voxygen/ui/widgets/
image_slider.rs

1//! A widget for selecting a single value along some linear range.
2use conrod_core::{
3    Color, Colorable, Positionable, Rect, Sizeable, Widget, WidgetCommon, builder_methods, image,
4    position::Range,
5    utils,
6    widget::{self, Image},
7    widget_ids,
8};
9use num::{Float, Integer, Num, NumCast};
10
11pub enum Discrete {}
12pub enum Continuous {}
13
14pub trait ValueFromPercent<T> {
15    fn value_from_percent(percent: f32, min: T, max: T) -> T;
16}
17
18/// Linear value selection.
19///
20/// If the slider's width is greater than its height, it will automatically
21/// become a horizontal slider, otherwise it will be a vertical slider.
22///
23/// Its reaction is triggered if the value is updated or if the mouse button is
24/// released while the cursor is above the rectangle.
25#[derive(WidgetCommon)]
26pub struct ImageSlider<T, K> {
27    #[conrod(common_builder)]
28    common: widget::CommonBuilder,
29    value: T,
30    min: T,
31    max: T,
32    // If `value > soft_max` we will display the slider at `soft_max` along with a faded ghost
33    // slider at `value`. The slider displayed at `soft_max` is purely a visual indicator and has
34    // no effect on the values produced by this slider.
35    soft_max: T,
36    /// The amount in which the slider's display should be skewed.
37    ///
38    /// Higher skew amounts (above 1.0) will weigh lower values.
39    ///
40    /// Lower skew amounts (below 1.0) will weigh higher values.
41    ///
42    /// All skew amounts should be greater than 0.0.
43    skew: f32,
44    track: Track,
45    slider: Slider,
46    kind: std::marker::PhantomData<K>,
47}
48
49struct Track {
50    image_id: image::Id,
51    color: Option<Color>,
52    // TODO: this is being set by users but we don't use it for anything here, figure out what it
53    // is supposed to be used for
54    breadth: Option<f32>,
55    // Padding on the ends of the track constraining the slider to a smaller area.
56    padding: (f32, f32),
57}
58
59struct Slider {
60    image_id: image::Id,
61    hover_image_id: Option<image::Id>,
62    press_image_id: Option<image::Id>,
63    color: Option<Color>,
64    length: Option<f32>,
65}
66
67widget_ids! {
68    struct Ids {
69        track,
70        slider,
71        soft_max_slider,
72    }
73}
74
75/// Represents the state of the ImageSlider widget.
76pub struct State {
77    ids: Ids,
78}
79
80impl<T, K> ImageSlider<T, K> {
81    builder_methods! {
82        pub skew { skew = f32 }
83        pub soft_max { soft_max = T }
84        pub pad_track { track.padding = (f32, f32) }
85        pub hover_image { slider.hover_image_id = Some(image::Id) }
86        pub press_image { slider.press_image_id = Some(image::Id) }
87        pub track_breadth { track.breadth = Some(f32) }
88        pub slider_length { slider.length = Some(f32) }
89        pub track_color { track.color = Some(Color) }
90        pub slider_color { slider.color = Some(Color) }
91    }
92
93    fn new(value: T, min: T, max: T, slider_image_id: image::Id, track_image_id: image::Id) -> Self
94    where
95        T: Copy,
96    {
97        Self {
98            common: widget::CommonBuilder::default(),
99            value,
100            min,
101            max,
102            soft_max: max,
103            skew: 1.0,
104            track: Track {
105                image_id: track_image_id,
106                color: None,
107                breadth: None,
108                padding: (0.0, 0.0),
109            },
110            slider: Slider {
111                image_id: slider_image_id,
112                hover_image_id: None,
113                press_image_id: None,
114                color: None,
115                length: None,
116            },
117            kind: std::marker::PhantomData,
118        }
119    }
120}
121
122impl<T> ImageSlider<T, Continuous>
123where
124    T: Float,
125{
126    pub fn continuous(
127        value: T,
128        min: T,
129        max: T,
130        slider_image_id: image::Id,
131        track_image_id: image::Id,
132    ) -> Self {
133        ImageSlider::new(value, min, max, slider_image_id, track_image_id)
134    }
135}
136
137impl<T> ImageSlider<T, Discrete>
138where
139    T: Integer + Copy,
140{
141    pub fn discrete(
142        value: T,
143        min: T,
144        max: T,
145        slider_image_id: image::Id,
146        track_image_id: image::Id,
147    ) -> Self {
148        ImageSlider::new(value, min, max, slider_image_id, track_image_id)
149    }
150}
151
152impl<T: Float> ValueFromPercent<T> for Continuous {
153    fn value_from_percent(percent: f32, min: T, max: T) -> T {
154        utils::value_from_perc(percent, min, max)
155    }
156}
157impl<T: Integer + NumCast> ValueFromPercent<T> for Discrete {
158    fn value_from_percent(percent: f32, min: T, max: T) -> T {
159        NumCast::from(
160            utils::value_from_perc(percent, min.to_f32().unwrap(), max.to_f32().unwrap()).round(),
161        )
162        .unwrap()
163    }
164}
165
166impl<T, K> Widget for ImageSlider<T, K>
167where
168    T: NumCast + Num + Copy + PartialOrd,
169    K: ValueFromPercent<T>,
170{
171    type Event = Option<T>;
172    type State = State;
173    type Style = ();
174
175    fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
176        State {
177            ids: Ids::new(id_gen),
178        }
179    }
180
181    fn style(&self) -> Self::Style {}
182
183    /// Update the state of the Slider.
184    fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
185        let widget::UpdateArgs {
186            id,
187            state,
188            rect,
189            ui,
190            ..
191        } = args;
192        let ImageSlider {
193            value,
194            min,
195            max,
196            skew,
197            track,
198            slider,
199            ..
200        } = self;
201        let (start_pad, end_pad) = (track.padding.0 as f64, track.padding.1 as f64);
202
203        let is_horizontal = rect.w() > rect.h();
204
205        let new_value = if let Some(mouse) = ui.widget_input(id).mouse() {
206            if mouse.buttons.left().is_down() {
207                let mouse_abs_xy = mouse.abs_xy();
208                let (mouse_offset, track_length) = if is_horizontal {
209                    // Horizontal
210                    (
211                        mouse_abs_xy[0] - rect.x.start - start_pad,
212                        rect.w() - start_pad - end_pad,
213                    )
214                } else {
215                    // Vertical
216                    (
217                        mouse_abs_xy[1] - rect.y.start - start_pad,
218                        rect.h() - start_pad - end_pad,
219                    )
220                };
221                let perc = utils::clamp(mouse_offset, 0.0, track_length) / track_length;
222                let skewed_perc = (perc).powf(skew as f64);
223                K::value_from_percent(skewed_perc as f32, min, max)
224            } else {
225                value
226            }
227        } else {
228            value
229        };
230
231        // Track
232        let track_rect = if is_horizontal {
233            let h = slider.length.map_or(rect.h() / 3.0, |h| h as f64);
234            Rect {
235                y: Range::from_pos_and_len(rect.y(), h),
236                ..rect
237            }
238        } else {
239            let w = slider.length.map_or(rect.w() / 3.0, |w| w as f64);
240            Rect {
241                x: Range::from_pos_and_len(rect.x(), w),
242                ..rect
243            }
244        };
245
246        let (x, y, w, h) = track_rect.x_y_w_h();
247        Image::new(track.image_id)
248            .x_y(x, y)
249            .w_h(w, h)
250            .parent(id)
251            .graphics_for(id)
252            .color(track.color)
253            .set(state.ids.track, ui);
254
255        // Slider
256        let slider_image = ui
257            .widget_input(id)
258            .mouse()
259            .map(|mouse| {
260                if mouse.buttons.left().is_down() {
261                    slider
262                        .press_image_id
263                        .or(slider.hover_image_id)
264                        .unwrap_or(slider.image_id)
265                } else {
266                    slider.hover_image_id.unwrap_or(slider.image_id)
267                }
268            })
269            .unwrap_or(slider.image_id);
270
271        // A rectangle for positioning and sizing the slider.
272        let slider_rect = |slider_value| {
273            let value_perc = utils::map_range(slider_value, min, max, 0.0, 1.0);
274            let unskewed_perc = value_perc.powf(1.0 / skew as f64);
275            if is_horizontal {
276                let pos = utils::map_range(
277                    unskewed_perc,
278                    0.0,
279                    1.0,
280                    rect.x.start + start_pad,
281                    rect.x.end - end_pad,
282                );
283                let w = slider.length.map_or(rect.w() / 10.0, |w| w as f64);
284                Rect {
285                    x: Range::from_pos_and_len(pos, w),
286                    ..rect
287                }
288            } else {
289                let pos = utils::map_range(
290                    unskewed_perc,
291                    0.0,
292                    1.0,
293                    rect.y.start + start_pad,
294                    rect.y.end - end_pad,
295                );
296                let h = slider.length.map_or(rect.h() / 10.0, |h| h as f64);
297                Rect {
298                    y: Range::from_pos_and_len(pos, h),
299                    ..rect
300                }
301            }
302        };
303
304        // Whether soft max slider needs to be displayed and main slider faded to look
305        // like a ghost.
306        let over_soft_max = new_value > self.soft_max;
307
308        let (x, y, w, h) = slider_rect(new_value).x_y_w_h();
309        let fade = if over_soft_max { 0.5 } else { 1.0 };
310        Image::new(slider_image)
311            .x_y(x, y)
312            .w_h(w, h)
313            .parent(id)
314            .graphics_for(id)
315            .color(Some(
316                slider
317                    .color
318                    .map_or(Color::Rgba(1.0, 1.0, 1.0, fade), |c: Color| c.alpha(fade)),
319            ))
320            .set(state.ids.slider, ui);
321
322        if over_soft_max {
323            let (x, y, w, h) = slider_rect(self.soft_max).x_y_w_h();
324            Image::new(slider_image)
325                .x_y(x, y)
326                .w_h(w, h)
327                .parent(id)
328                .graphics_for(id)
329                .color(slider.color)
330                .set(state.ids.soft_max_slider, ui);
331        }
332
333        // If the value has just changed, return the new value.
334        if value != new_value {
335            Some(new_value)
336        } else {
337            None
338        }
339    }
340}
341
342impl<T, K> Colorable for ImageSlider<T, K> {
343    fn color(mut self, color: Color) -> Self {
344        self.slider.color = Some(color);
345        self.track.color = Some(color);
346        self
347    }
348}