1use 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#[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 soft_max: T,
36 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 breadth: Option<f32>,
55 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
75pub 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 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 (
211 mouse_abs_xy[0] - rect.x.start - start_pad,
212 rect.w() - start_pad - end_pad,
213 )
214 } else {
215 (
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 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 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 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 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 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}