1use num::{Float, NumCast, ToPrimitive};
4use position::{Padding, Range, Rect, Scalar};
5use text;
6use widget;
7use widget::triangles::Triangle;
8use {Borderable, Color, Colorable, FontSize, Labelable, Positionable, Widget};
9
10#[derive(WidgetCommon_)]
18pub struct Slider<'a, T> {
19 #[conrod(common_builder)]
20 common: widget::CommonBuilder,
21 value: T,
22 min: T,
23 max: T,
24 pub skew: f32,
32 maybe_label: Option<&'a str>,
33 style: Style,
34 pub enabled: bool,
36}
37
38#[derive(Copy, Clone, Debug, Default, PartialEq, WidgetStyle_)]
40pub struct Style {
41 #[conrod(default = "theme.shape_color")]
43 pub color: Option<Color>,
44 #[conrod(default = "theme.border_width")]
46 pub border: Option<Scalar>,
47 #[conrod(default = "theme.border_color")]
49 pub border_color: Option<Color>,
50 #[conrod(default = "theme.label_color")]
52 pub label_color: Option<Color>,
53 #[conrod(default = "theme.font_size_medium")]
55 pub label_font_size: Option<FontSize>,
56 #[conrod(default = "theme.font_id")]
58 pub label_font_id: Option<Option<text::font::Id>>,
59}
60
61widget_ids! {
62 struct Ids {
63 triangles,
64 label,
65 }
66}
67
68pub struct State {
70 ids: Ids,
71}
72
73impl<'a, T> Slider<'a, T> {
74 pub fn new(value: T, min: T, max: T) -> Self {
76 Slider {
77 common: widget::CommonBuilder::default(),
78 style: Style::default(),
79 value: value,
80 min: min,
81 max: max,
82 skew: 1.0,
83 maybe_label: None,
84 enabled: true,
85 }
86 }
87
88 pub fn label_font_id(mut self, font_id: text::font::Id) -> Self {
90 self.style.label_font_id = Some(Some(font_id));
91 self
92 }
93
94 builder_methods! {
95 pub skew { skew = f32 }
96 pub enabled { enabled = bool }
97 }
98}
99
100impl<'a, T> Widget for Slider<'a, T>
101where
102 T: Float + NumCast + ToPrimitive,
103{
104 type State = State;
105 type Style = Style;
106 type Event = Option<T>;
107
108 fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
109 State {
110 ids: Ids::new(id_gen),
111 }
112 }
113
114 fn style(&self) -> Self::Style {
115 self.style.clone()
116 }
117
118 fn kid_area(&self, args: widget::KidAreaArgs<Self>) -> widget::KidArea {
119 const LABEL_PADDING: Scalar = 10.0;
120 widget::KidArea {
121 rect: args.rect,
122 pad: Padding {
123 x: Range::new(LABEL_PADDING, LABEL_PADDING),
124 y: Range::new(LABEL_PADDING, LABEL_PADDING),
125 },
126 }
127 }
128
129 fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
131 use utils::{clamp, map_range, value_from_perc};
132
133 let widget::UpdateArgs {
134 id,
135 state,
136 rect,
137 style,
138 ui,
139 ..
140 } = args;
141 let Slider {
142 value,
143 min,
144 max,
145 skew,
146 maybe_label,
147 ..
148 } = self;
149
150 let is_horizontal = rect.w() > rect.h();
151 let border = style.border(ui.theme());
152 let inner_rect = rect.pad(border);
153
154 let new_value = if let Some(mouse) = ui.widget_input(id).mouse() {
155 if mouse.buttons.left().is_down() {
156 let mouse_abs_xy = mouse.abs_xy();
157 if is_horizontal {
158 let inner_w = inner_rect.w();
160 let slider_w = mouse_abs_xy[0] - inner_rect.x.start;
161 let perc = clamp(slider_w, 0.0, inner_w) / inner_w;
162 let skewed_perc = (perc).powf(skew as f64);
163 let w_perc = skewed_perc;
164 value_from_perc(w_perc as f32, min, max)
165 } else {
166 let inner_h = inner_rect.h();
168 let slider_h = mouse_abs_xy[1] - inner_rect.y.start;
169 let perc = clamp(slider_h, 0.0, inner_h) / inner_h;
170 let skewed_perc = (perc).powf(skew as f64);
171 let h_perc = skewed_perc;
172 value_from_perc(h_perc as f32, min, max)
173 }
174 } else {
175 value
176 }
177 } else {
178 value
179 };
180
181 let interaction_color = |ui: &::ui::UiCell, color: Color| {
183 ui.widget_input(id)
184 .mouse()
185 .map(|mouse| {
186 if mouse.buttons.left().is_down() {
187 color.clicked()
188 } else {
189 color.highlighted()
190 }
191 })
192 .unwrap_or(color)
193 };
194
195 let value_perc = map_range(new_value, min, max, 0.0, 1.0);
197 let unskewed_perc = value_perc.powf(1.0 / skew as f64);
198 let (slider_rect, blank_rect) = if is_horizontal {
199 let left = inner_rect.x.start;
200 let slider = map_range(unskewed_perc, 0.0, 1.0, left, inner_rect.x.end);
201 let right = inner_rect.x.end;
202 let y = inner_rect.y;
203 let slider_rect = Rect {
204 x: Range::new(left, slider),
205 y: y,
206 };
207 let blank_rect = Rect {
208 x: Range::new(slider, right),
209 y: y,
210 };
211 (slider_rect, blank_rect)
212 } else {
213 let bottom = inner_rect.y.start;
214 let slider = map_range(unskewed_perc, 0.0, 1.0, bottom, inner_rect.y.end);
215 let top = inner_rect.y.end;
216 let x = inner_rect.x;
217 let slider_rect = Rect {
218 x: x,
219 y: Range::new(bottom, slider),
220 };
221 let blank_rect = Rect {
222 x: x,
223 y: Range::new(slider, top),
224 };
225 (slider_rect, blank_rect)
226 };
227
228 let border_triangles = widget::bordered_rectangle::border_triangles(rect, border);
229 let (a, b) = widget::rectangle::triangles(slider_rect);
230 let slider_triangles = [a, b];
231 let (a, b) = widget::rectangle::triangles(blank_rect);
232 let blank_triangles = [a, b];
233
234 let border_color = interaction_color(ui, style.border_color(ui.theme())).to_rgb();
235 let color = interaction_color(ui, style.color(ui.theme())).to_rgb();
236
237 let border_colored_triangles = border_triangles
239 .as_ref()
240 .into_iter()
241 .flat_map(|tris| tris.iter().cloned())
242 .chain(blank_triangles.iter().cloned())
243 .map(|Triangle(ps)| {
244 Triangle([
245 (ps[0], border_color),
246 (ps[1], border_color),
247 (ps[2], border_color),
248 ])
249 });
250
251 let slider_colored_triangles = slider_triangles
253 .iter()
254 .cloned()
255 .map(|Triangle(ps)| Triangle([(ps[0], color), (ps[1], color), (ps[2], color)]));
256
257 let triangles = border_colored_triangles.chain(slider_colored_triangles);
259
260 widget::Triangles::multi_color(triangles)
261 .with_bounding_rect(rect)
262 .graphics_for(id)
263 .parent(id)
264 .set(state.ids.triangles, ui);
265
266 if let Some(label) = maybe_label {
268 let label_color = style.label_color(ui.theme());
269 let font_size = style.label_font_size(ui.theme());
270 let font_id = style.label_font_id(&ui.theme).or(ui.fonts.ids().next());
271 widget::Text::new(label)
273 .and_then(font_id, widget::Text::font_id)
274 .and(|text| {
275 if is_horizontal {
276 text.mid_left_of(id)
277 } else {
278 text.mid_bottom_of(id)
279 }
280 })
281 .graphics_for(id)
282 .color(label_color)
283 .font_size(font_size)
284 .set(state.ids.label, ui);
285 }
286
287 if value != new_value {
289 Some(new_value)
290 } else {
291 None
292 }
293 }
294}
295
296impl<'a, T> Colorable for Slider<'a, T> {
297 builder_method!(color { style.color = Some(Color) });
298}
299
300impl<'a, T> Borderable for Slider<'a, T> {
301 builder_methods! {
302 border { style.border = Some(Scalar) }
303 border_color { style.border_color = Some(Color) }
304 }
305}
306
307impl<'a, T> Labelable<'a> for Slider<'a, T> {
308 builder_methods! {
309 label { maybe_label = Some(&'a str) }
310 label_color { style.label_color = Some(Color) }
311 label_font_size { style.label_font_size = Some(FontSize) }
312 }
313}