use {Color, Colorable, FontSize, Borderable, Labelable, Positionable, Sizeable};
use position::{self, Align, Scalar};
use text;
use utils;
use widget::{self, Widget};
pub type Idx = usize;
pub type Len = usize;
#[derive(WidgetCommon_)]
pub struct DropDownList<'a, T: 'a> {
#[conrod(common_builder)]
common: widget::CommonBuilder,
items: &'a [T],
selected: Option<Idx>,
maybe_label: Option<&'a str>,
style: Style,
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 = "text::Justify::Center")]
pub label_justify: Option<text::Justify>,
#[conrod(default = "position::Relative::Align(Align::Middle)")]
pub label_x: Option<position::Relative>,
#[conrod(default = "position::Relative::Align(Align::Middle)")]
pub label_y: Option<position::Relative>,
#[conrod(default = "None")]
pub maybe_max_visible_height: Option<Option<MaxHeight>>,
#[conrod(default = "None")]
pub scrollbar_position: Option<Option<widget::list::ScrollbarPosition>>,
#[conrod(default = "None")]
pub scrollbar_width: Option<Option<Scalar>>,
#[conrod(default = "theme.font_id")]
pub label_font_id: Option<Option<text::font::Id>>,
}
widget_ids! {
struct Ids {
closed_menu,
list,
}
}
pub struct State {
menu_state: MenuState,
ids: Ids,
}
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum MaxHeight {
Items(usize),
Scalar(f64),
}
#[derive(PartialEq, Clone, Copy, Debug)]
enum MenuState {
Closed,
Open,
}
impl<'a, T> DropDownList<'a, T> {
pub fn new(items: &'a [T], selected: Option<Idx>) -> Self {
DropDownList {
common: widget::CommonBuilder::default(),
items: items,
selected: selected,
maybe_label: None,
enabled: true,
style: Style::default(),
}
}
builder_methods!{
pub enabled { enabled = bool }
}
pub fn max_visible_items(mut self, num: usize) -> Self {
self.style.maybe_max_visible_height = Some(Some(MaxHeight::Items(num)));
self
}
pub fn max_visible_height(mut self, height: f64) -> Self {
self.style.maybe_max_visible_height = Some(Some(MaxHeight::Scalar(height)));
self
}
pub fn scrollbar_next_to(mut self) -> Self {
self.style.scrollbar_position = Some(Some(widget::list::ScrollbarPosition::NextTo));
self
}
pub fn scrollbar_on_top(mut self) -> Self {
self.style.scrollbar_position = Some(Some(widget::list::ScrollbarPosition::OnTop));
self
}
pub fn no_scrollbar(mut self) -> Self {
self.style.scrollbar_position = Some(None);
self
}
pub fn scrollbar_width(mut self, w: Scalar) -> Self {
self.style.scrollbar_width = Some(Some(w));
self
}
pub fn label_font_id(mut self, font_id: text::font::Id) -> Self {
self.style.label_font_id = Some(Some(font_id));
self
}
pub fn left_justify_label(mut self) -> Self {
self.style.label_justify = Some(text::Justify::Left);
self
}
pub fn right_justify_label(mut self) -> Self {
self.style.label_justify = Some(text::Justify::Right);
self
}
pub fn center_justify_label(mut self) -> Self {
self.style.label_justify = Some(text::Justify::Center);
self
}
pub fn label_x(mut self, x: position::Relative) -> Self {
self.style.label_x = Some(x);
self
}
pub fn label_y(mut self, y: position::Relative) -> Self {
self.style.label_y = Some(y);
self
}
}
impl<'a, T> Widget for DropDownList<'a, T>
where T: AsRef<str>,
{
type State = State;
type Style = Style;
type Event = Option<Idx>;
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
State {
menu_state: MenuState::Closed,
ids: Ids::new(id_gen),
}
}
fn style(&self) -> Self::Style {
self.style.clone()
}
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { id, state, rect, style, ui, .. } = args;
let num_items = self.items.len();
let selected = self.selected.and_then(|idx| if idx < num_items { Some(idx) }
else { None });
let mut clicked_item = None;
let new_menu_state = match state.menu_state {
MenuState::Closed => {
let label = selected
.map(|i| self.items[i].as_ref())
.unwrap_or_else(|| self.maybe_label.unwrap_or(""));
let was_clicked = {
let mut button = widget::Button::new()
.xy(rect.xy())
.wh(rect.dim())
.label(label)
.parent(id);
button.style = style.button_style(false);
button.set(state.ids.closed_menu, ui).was_clicked()
};
if was_clicked { MenuState::Open } else { MenuState::Closed }
},
MenuState::Open => {
let (_, y, w, h) = rect.x_y_w_h();
let max_visible_height = {
let bottom_win_y = (-ui.window_dim()[1]) / 2.0;
const WINDOW_PADDING: Scalar = 20.0;
let max = y + h / 2.0 - bottom_win_y - WINDOW_PADDING;
style.maybe_max_visible_height(ui.theme()).map(|max_height| {
let height = match max_height {
MaxHeight::Items(num) => h * num as Scalar,
MaxHeight::Scalar(height) => height,
};
utils::partial_min(height, max)
}).unwrap_or(max)
};
let num_items = self.items.len();
let item_h = h;
let list_h = max_visible_height.min(num_items as Scalar * item_h);
let scrollbar_color = style.border_color(&ui.theme);
let scrollbar_position = style.scrollbar_position(&ui.theme);
let scrollbar_width = style.scrollbar_width(&ui.theme)
.unwrap_or_else(|| {
ui.theme.widget_style::<widget::scrollbar::Style>()
.and_then(|style| style.style.thickness)
.unwrap_or(10.0)
});
let (mut events, scrollbar) = widget::ListSelect::single(num_items)
.flow_down()
.item_size(item_h)
.w_h(w, list_h)
.and(|ls| match scrollbar_position {
Some(widget::list::ScrollbarPosition::NextTo) => ls.scrollbar_next_to(),
Some(widget::list::ScrollbarPosition::OnTop) => ls.scrollbar_on_top(),
None => ls,
})
.scrollbar_color(scrollbar_color)
.scrollbar_thickness(scrollbar_width)
.mid_top_of(id)
.floating(true)
.set(state.ids.list, ui);
while let Some(event) = events.next(ui, |i| Some(i) == selected) {
use widget::list_select::Event;
match event {
Event::Item(item) => {
let i = item.i;
let label = self.items[i].as_ref();
let mut button = widget::Button::new().label(label);
button.style = style.button_style(Some(i) == selected);
item.set(button, ui);
},
Event::Selection(ix) => clicked_item = Some(ix),
_ => (),
}
}
if let Some(scrollbar) = scrollbar {
scrollbar.set(ui);
}
let should_close = clicked_item.is_some() ||
clicked_item.is_none()
&& ui.global_input().current.mouse.buttons.pressed().next().is_some()
&& match ui.global_input().current.widget_capturing_mouse {
None => true,
Some(capturing) => !ui.widget_graph()
.does_recursive_depth_edge_exist(id, capturing),
};
if should_close { MenuState::Closed } else { MenuState::Open }
}
};
if state.menu_state != new_menu_state {
state.update(|state| state.menu_state = new_menu_state);
}
clicked_item
}
}
impl Style {
pub fn button_style(&self, is_selected: bool) -> widget::button::Style {
widget::button::Style {
color: self.color.map(|c| if is_selected { c.highlighted() } else { c }),
border: self.border,
border_color: self.border_color,
label_color: self.label_color,
label_font_size: self.label_font_size,
label_justify: self.label_justify,
label_x: self.label_x,
label_y: self.label_y,
label_font_id: self.label_font_id,
}
}
}
impl<'a, T> Colorable for DropDownList<'a, T> {
builder_method!(color { style.color = Some(Color) });
}
impl<'a, T> Borderable for DropDownList<'a, T> {
builder_methods!{
border { style.border = Some(Scalar) }
border_color { style.border_color = Some(Color) }
}
}
impl<'a, T> Labelable<'a> for DropDownList<'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) }
}
}