use {Color, Colorable, FontSize, Borderable, Labelable, Positionable, Widget};
use num::{Float, NumCast, ToPrimitive};
use position::{Padding, Range, Rect, Scalar};
use text;
use widget;
use widget::triangles::Triangle;
#[derive(WidgetCommon_)]
pub struct Slider<'a, T> {
#[conrod(common_builder)]
common: widget::CommonBuilder,
value: T,
min: T,
max: T,
pub skew: f32,
maybe_label: Option<&'a str>,
style: Style,
pub enabled: bool,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, WidgetStyle_)]
pub struct Style {
#[conrod(default = "theme.shape_color")]
pub color: Option<Color>,
#[conrod(default = "theme.border_width")]
pub border: Option<Scalar>,
#[conrod(default = "theme.border_color")]
pub border_color: Option<Color>,
#[conrod(default = "theme.label_color")]
pub label_color: Option<Color>,
#[conrod(default = "theme.font_size_medium")]
pub label_font_size: Option<FontSize>,
#[conrod(default = "theme.font_id")]
pub label_font_id: Option<Option<text::font::Id>>,
}
widget_ids! {
struct Ids {
triangles,
label,
}
}
pub struct State {
ids: Ids,
}
impl<'a, T> Slider<'a, T> {
pub fn new(value: T, min: T, max: T) -> Self {
Slider {
common: widget::CommonBuilder::default(),
style: Style::default(),
value: value,
min: min,
max: max,
skew: 1.0,
maybe_label: None,
enabled: true,
}
}
pub fn label_font_id(mut self, font_id: text::font::Id) -> Self {
self.style.label_font_id = Some(Some(font_id));
self
}
builder_methods!{
pub skew { skew = f32 }
pub enabled { enabled = bool }
}
}
impl<'a, T> Widget for Slider<'a, T>
where T: Float + NumCast + ToPrimitive,
{
type State = State;
type Style = Style;
type Event = Option<T>;
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
State {
ids: Ids::new(id_gen),
}
}
fn style(&self) -> Self::Style {
self.style.clone()
}
fn kid_area(&self, args: widget::KidAreaArgs<Self>) -> widget::KidArea {
const LABEL_PADDING: Scalar = 10.0;
widget::KidArea {
rect: args.rect,
pad: Padding {
x: Range::new(LABEL_PADDING, LABEL_PADDING),
y: Range::new(LABEL_PADDING, LABEL_PADDING),
},
}
}
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
use utils::{clamp, map_range, value_from_perc};
let widget::UpdateArgs { id, state, rect, style, ui, .. } = args;
let Slider { value, min, max, skew, maybe_label, .. } = self;
let is_horizontal = rect.w() > rect.h();
let border = style.border(ui.theme());
let inner_rect = rect.pad(border);
let new_value = if let Some(mouse) = ui.widget_input(id).mouse() {
if mouse.buttons.left().is_down() {
let mouse_abs_xy = mouse.abs_xy();
if is_horizontal {
let inner_w = inner_rect.w();
let slider_w = mouse_abs_xy[0] - inner_rect.x.start;
let perc = clamp(slider_w, 0.0, inner_w) / inner_w;
let skewed_perc = (perc).powf(skew as f64);
let w_perc = skewed_perc;
value_from_perc(w_perc as f32, min, max)
} else {
let inner_h = inner_rect.h();
let slider_h = mouse_abs_xy[1] - inner_rect.y.start;
let perc = clamp(slider_h, 0.0, inner_h) / inner_h;
let skewed_perc = (perc).powf(skew as f64);
let h_perc = skewed_perc;
value_from_perc(h_perc as f32, min, max)
}
} else {
value
}
} else {
value
};
let interaction_color = |ui: &::ui::UiCell, color: Color|
ui.widget_input(id).mouse()
.map(|mouse| if mouse.buttons.left().is_down() {
color.clicked()
} else {
color.highlighted()
})
.unwrap_or(color);
let value_perc = map_range(new_value, min, max, 0.0, 1.0);
let unskewed_perc = value_perc.powf(1.0 / skew as f64);
let (slider_rect, blank_rect) = if is_horizontal {
let left = inner_rect.x.start;
let slider = map_range(unskewed_perc, 0.0, 1.0, left, inner_rect.x.end);
let right = inner_rect.x.end;
let y = inner_rect.y;
let slider_rect = Rect { x: Range::new(left, slider), y: y };
let blank_rect = Rect { x: Range::new(slider, right), y: y };
(slider_rect, blank_rect)
} else {
let bottom = inner_rect.y.start;
let slider = map_range(unskewed_perc, 0.0, 1.0, bottom, inner_rect.y.end);
let top = inner_rect.y.end;
let x = inner_rect.x;
let slider_rect = Rect { x: x, y: Range::new(bottom, slider) };
let blank_rect = Rect { x: x, y: Range::new(slider, top) };
(slider_rect, blank_rect)
};
let border_triangles = widget::bordered_rectangle::border_triangles(rect, border);
let (a, b) = widget::rectangle::triangles(slider_rect);
let slider_triangles = [a, b];
let (a, b) = widget::rectangle::triangles(blank_rect);
let blank_triangles = [a, b];
let border_color = interaction_color(ui, style.border_color(ui.theme())).to_rgb();
let color = interaction_color(ui, style.color(ui.theme())).to_rgb();
let border_colored_triangles = border_triangles
.as_ref()
.into_iter()
.flat_map(|tris| tris.iter().cloned())
.chain(blank_triangles.iter().cloned())
.map(|Triangle(ps)| Triangle([
(ps[0], border_color),
(ps[1], border_color),
(ps[2], border_color)
]));
let slider_colored_triangles = slider_triangles
.iter()
.cloned()
.map(|Triangle(ps)| Triangle([(ps[0], color), (ps[1], color), (ps[2], color)]));
let triangles = border_colored_triangles.chain(slider_colored_triangles);
widget::Triangles::multi_color(triangles)
.with_bounding_rect(rect)
.graphics_for(id)
.parent(id)
.set(state.ids.triangles, ui);
if let Some(label) = maybe_label {
let label_color = style.label_color(ui.theme());
let font_size = style.label_font_size(ui.theme());
let font_id = style.label_font_id(&ui.theme).or(ui.fonts.ids().next());
widget::Text::new(label)
.and_then(font_id, widget::Text::font_id)
.and(|text| if is_horizontal { text.mid_left_of(id) }
else { text.mid_bottom_of(id) })
.graphics_for(id)
.color(label_color)
.font_size(font_size)
.set(state.ids.label, ui);
}
if value != new_value { Some(new_value) } else { None }
}
}
impl<'a, T> Colorable for Slider<'a, T> {
builder_method!(color { style.color = Some(Color) });
}
impl<'a, T> Borderable for Slider<'a, T> {
builder_methods!{
border { style.border = Some(Scalar) }
border_color { style.border_color = Some(Color) }
}
}
impl<'a, T> Labelable<'a> for Slider<'a, T> {
builder_methods!{
label { maybe_label = Some(&'a str) }
label_color { style.label_color = Some(Color) }
label_font_size { style.label_font_size = Some(FontSize) }
}
}