use graph;
use position::{Align, Depth, Dimension, Dimensions, Padding, Position,
Positionable, Rect, Relative, Sizeable};
use std;
use text::font;
use theme::{self, Theme};
use ui::{self, Ui, UiCell};
pub use self::id::Id;
pub use self::primitive::line::{self, Line};
pub use self::primitive::image::{self, Image};
pub use self::primitive::point_path::{self, PointPath};
pub use self::primitive::shape::circle::{self, Circle};
pub use self::primitive::shape::oval::{self, Oval};
pub use self::primitive::shape::polygon::{self, Polygon};
pub use self::primitive::shape::rectangle::{self, Rectangle};
pub use self::primitive::text::{self, Text};
pub use self::bordered_rectangle::BorderedRectangle;
pub use self::button::Button;
pub use self::canvas::Canvas;
pub use self::collapsible_area::CollapsibleArea;
pub use self::drop_down_list::DropDownList;
pub use self::list_select::ListSelect;
pub use self::envelope_editor::EnvelopeEditor;
pub use self::file_navigator::FileNavigator;
pub use self::list::List;
pub use self::matrix::Matrix;
pub use self::number_dialer::NumberDialer;
pub use self::plot_path::PlotPath;
pub use self::range_slider::RangeSlider;
pub use self::rounded_rectangle::RoundedRectangle;
pub use self::scrollbar::Scrollbar;
pub use self::slider::Slider;
pub use self::tabs::Tabs;
pub use self::text_box::TextBox;
pub use self::text_edit::TextEdit;
pub use self::title_bar::TitleBar;
pub use self::toggle::Toggle;
pub use self::xy_pad::XYPad;
#[macro_use] mod builder;
#[macro_use] mod style;
#[macro_use] pub mod id;
pub mod scroll;
pub mod primitive;
pub mod bordered_rectangle;
pub mod button;
pub mod canvas;
pub mod collapsible_area;
pub mod drop_down_list;
pub mod envelope_editor;
pub mod file_navigator;
pub mod list;
pub mod list_select;
pub mod matrix;
pub mod number_dialer;
pub mod plot_path;
pub mod range_slider;
pub mod rounded_rectangle;
pub mod scrollbar;
pub mod slider;
pub mod tabs;
pub mod text_box;
pub mod text_edit;
pub mod title_bar;
pub mod toggle;
pub mod xy_pad;
pub struct UpdateArgs<'a, 'b: 'a, 'c, 'd: 'c, W>
where W: Widget,
{
pub id: Id,
pub maybe_parent_id: Option<Id>,
pub prev: &'a CommonState,
pub state: &'a mut State<'b, W::State>,
pub rect: Rect,
pub style: &'a W::Style,
pub ui: &'c mut UiCell<'d>,
}
pub struct KidAreaArgs<'a, W>
where W: Widget,
{
pub rect: Rect,
pub style: &'a W::Style,
pub theme: &'a Theme,
pub fonts: &'a font::Map,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct KidArea {
pub rect: Rect,
pub pad: Padding,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum MaybeParent {
None,
Some(Id),
Unspecified,
}
impl MaybeParent {
pub fn get_unchecked(&self, ui: &Ui, x_pos: Position, y_pos: Position) -> Id {
match *self {
MaybeParent::Some(id) => id,
MaybeParent::None => ui.window.into(),
MaybeParent::Unspecified => ui::infer_parent_unchecked(ui, x_pos, y_pos),
}
}
pub fn get(&self, id: Id, ui: &Ui, x_pos: Position, y_pos: Position) -> Option<Id> {
if id == ui.window {
None
} else {
Some(self.get_unchecked(ui, x_pos, y_pos))
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Floating {
pub time_last_clicked: std::time::Instant,
}
#[derive(Clone, Copy, Debug)]
pub struct CommonBuilder {
pub style: CommonStyle,
pub maybe_parent_id: MaybeParent,
pub is_floating: bool,
pub crop_kids: bool,
pub maybe_x_scroll: Option<scroll::Scroll>,
pub maybe_y_scroll: Option<scroll::Scroll>,
pub place_on_kid_area: bool,
pub maybe_graphics_for: Option<Id>,
}
#[derive(Clone, Copy, Debug)]
pub struct CommonStyle {
pub maybe_x_dimension: Option<Dimension>,
pub maybe_y_dimension: Option<Dimension>,
pub maybe_x_position: Option<Position>,
pub maybe_y_position: Option<Position>,
pub maybe_depth: Option<Depth>,
}
pub struct State<'a, T: 'a> {
state: &'a mut T,
has_updated: bool,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CommonState {
pub rect: Rect,
pub depth: Depth,
pub maybe_floating: Option<Floating>,
pub kid_area: KidArea,
pub maybe_x_scroll_state: Option<scroll::StateX>,
pub maybe_y_scroll_state: Option<scroll::StateY>,
}
#[allow(missing_docs)]
#[allow(missing_copy_implementations)]
pub struct PreUpdateCache {
pub type_id: std::any::TypeId,
pub id: Id,
pub maybe_parent_id: Option<Id>,
pub maybe_x_positioned_relatively_id: Option<Id>,
pub maybe_y_positioned_relatively_id: Option<Id>,
pub rect: Rect,
pub depth: Depth,
pub kid_area: KidArea,
pub maybe_floating: Option<Floating>,
pub crop_kids: bool,
pub maybe_x_scroll_state: Option<scroll::StateX>,
pub maybe_y_scroll_state: Option<scroll::StateY>,
pub maybe_graphics_for: Option<Id>,
}
#[allow(missing_docs)]
pub struct PostUpdateCache<W>
where W: Widget,
{
pub id: Id,
pub maybe_parent_id: Option<Id>,
pub state: W::State,
pub style: W::Style,
}
pub trait Style: std::any::Any + std::fmt::Debug + PartialEq + Sized {}
impl<T> Style for T where T: std::any::Any + std::fmt::Debug + PartialEq + Sized {}
fn default_dimension<W, F>(widget: &W, ui: &Ui, f: F) -> Dimension
where W: Widget,
F: FnOnce(theme::UniqueDefault<W::Style>) -> Option<Dimension>,
{
ui.theme.widget_style::<W::Style>()
.and_then(f)
.or_else(|| ui.maybe_prev_widget().map(|id| Dimension::Of(id, None)))
.unwrap_or_else(|| {
let x_pos = widget.get_x_position(ui);
let y_pos = widget.get_y_position(ui);
let parent_id = widget.common().maybe_parent_id.get_unchecked(ui, x_pos, y_pos);
Dimension::Of(parent_id, None)
})
}
pub fn default_x_dimension<W>(widget: &W, ui: &Ui) -> Dimension
where W: Widget,
{
default_dimension(widget, ui, |default| default.common.maybe_x_dimension)
}
pub fn default_y_dimension<W>(widget: &W, ui: &Ui) -> Dimension
where W: Widget,
{
default_dimension(widget, ui, |default| default.common.maybe_y_dimension)
}
pub trait Widget: Sized {
type State: std::any::Any + Send;
type Style: Style + Send;
type Event;
fn common(&self) -> &CommonBuilder;
fn common_mut(&mut self) -> &mut CommonBuilder;
fn init_state(&self, id::Generator) -> Self::State;
fn style(&self) -> Self::Style;
fn update(self, args: UpdateArgs<Self>) -> Self::Event;
fn default_x_position(&self, ui: &Ui) -> Position {
ui.theme.widget_style::<Self::Style>()
.and_then(|style| style.common.maybe_x_position)
.unwrap_or(ui.theme.x_position)
}
fn default_y_position(&self, ui: &Ui) -> Position {
ui.theme.widget_style::<Self::Style>()
.and_then(|style| style.common.maybe_y_position)
.unwrap_or(ui.theme.y_position)
}
fn default_x_dimension(&self, ui: &Ui) -> Dimension {
default_x_dimension(self, ui)
}
fn default_y_dimension(&self, ui: &Ui) -> Dimension {
default_y_dimension(self, ui)
}
fn drag_area(&self,
_dim: Dimensions,
_style: &Self::Style,
_theme: &Theme) -> Option<Rect>
{
None
}
fn kid_area(&self, args: KidAreaArgs<Self>) -> KidArea {
KidArea {
rect: args.rect,
pad: Padding::none(),
}
}
fn parent(mut self, parent_id: Id) -> Self {
self.common_mut().maybe_parent_id = MaybeParent::Some(parent_id);
self
}
fn no_parent(mut self) -> Self {
self.common_mut().maybe_parent_id = MaybeParent::None;
self
}
fn place_on_kid_area(mut self, b: bool) -> Self {
self.common_mut().place_on_kid_area = b;
self
}
fn graphics_for(mut self, id: Id) -> Self {
self.common_mut().maybe_graphics_for = Some(id);
self
}
fn floating(mut self, is_floating: bool) -> Self {
self.common_mut().is_floating = is_floating;
self
}
fn crop_kids(mut self) -> Self {
self.common_mut().crop_kids = true;
self
}
fn scroll_kids(self) -> Self {
self.scroll_kids_vertically().scroll_kids_horizontally().crop_kids()
}
fn scroll_kids_vertically(mut self) -> Self {
self.common_mut().maybe_y_scroll = Some(scroll::Scroll::new());
self.crop_kids()
}
fn scroll_kids_horizontally(mut self) -> Self {
self.common_mut().maybe_x_scroll = Some(scroll::Scroll::new());
self.crop_kids()
}
#[inline]
fn and<F>(self, build: F) -> Self
where F: FnOnce(Self) -> Self,
{
build(self)
}
#[inline]
fn and_mut<F>(mut self, mutate: F) -> Self
where F: FnOnce(&mut Self),
{
mutate(&mut self);
self
}
#[inline]
fn and_if<F>(self, cond: bool, build: F) -> Self
where F: FnOnce(Self) -> Self,
{
if cond { build(self) } else { self }
}
#[inline]
fn and_then<T, F>(self, maybe: Option<T>, build: F) -> Self
where F: FnOnce(Self, T) -> Self,
{
if let Some(t) = maybe { build(self, t) } else { self }
}
fn set<'a, 'b>(self, id: Id, ui_cell: &'a mut UiCell<'b>) -> Self::Event {
set_widget(self, id, ui_cell)
}
}
fn set_widget<'a, 'b, W>(widget: W, id: Id, ui: &'a mut UiCell<'b>) -> W::Event
where W: Widget,
{
let type_id = std::any::TypeId::of::<W::State>();
let (maybe_prev_unique_state, maybe_prev_common, maybe_prev_style) =
ui::widget_graph_mut(ui::ref_mut_from_ui_cell(ui))
.widget_mut(id)
.and_then(|container| {
if container.type_id != type_id {
use std::io::Write;
writeln!(std::io::stderr(),
"A widget of a different type already exists at the given WidgetId \
({:?}). You tried to insert a {:?}, however the existing widget is a \
{:?}. Check your widgets' `WidgetId`s for errors.",
id, type_id, container.type_id).unwrap();
return None;
}
let graph::Container {
ref mut maybe_state,
rect,
depth,
kid_area,
maybe_floating,
maybe_x_scroll_state,
maybe_y_scroll_state,
..
} = *container;
let (state, style) = match maybe_state.take().and_then(|a| a.downcast().ok()) {
Some(boxed) => {
let unique: graph::UniqueWidgetState<W::State, W::Style> = *boxed;
let graph::UniqueWidgetState { state, style } = unique;
(state, style)
},
None => return None,
};
let prev_common = CommonState {
rect: rect,
depth: depth,
maybe_floating: maybe_floating,
kid_area: kid_area,
maybe_x_scroll_state: maybe_x_scroll_state,
maybe_y_scroll_state: maybe_y_scroll_state,
};
Some((Some(state), Some(prev_common), Some(style)))
})
.unwrap_or_else(|| (None, None, None));
let maybe_prev_widget_id = ui.maybe_prev_widget();
let new_style = widget.style();
let depth = widget.get_depth();
let dim = widget.get_wh(&ui).unwrap_or([0.0, 0.0]);
let x_pos = widget.get_x_position(ui);
let y_pos = widget.get_y_position(ui);
let place_on_kid_area = widget.common().place_on_kid_area;
let maybe_parent_id = widget.common().maybe_parent_id.get(id, ui, x_pos, y_pos);
let xy = maybe_prev_common
.as_ref()
.and_then(|prev| {
let maybe_drag_area = widget.drag_area(dim, &new_style, &ui.theme);
maybe_drag_area.map(|drag_area| {
let mut left_mouse_drags = ui.widget_input(id).drags().left();
let maybe_first_drag = left_mouse_drags.next();
let prev_xy = prev.rect.xy();
maybe_first_drag
.and_then(|first_drag| {
if drag_area.is_over(first_drag.from) {
let total_drag_xy = left_mouse_drags
.fold(first_drag.delta_xy, |total, drag| {
[total[0] + drag.delta_xy[0], total[1] + drag.delta_xy[1]]
});
Some([prev_xy[0] + total_drag_xy[0], prev_xy[1] + total_drag_xy[1]])
} else {
None
}
})
.unwrap_or(prev_xy)
})
})
.unwrap_or_else(|| ui.calc_xy(Some(id), x_pos, y_pos, dim, place_on_kid_area));
let rect = Rect::from_xy_dim(xy, dim);
let maybe_floating = if widget.common().is_floating {
fn new_floating() -> Floating {
Floating { time_last_clicked: std::time::Instant::now() }
}
match maybe_prev_common.as_ref() {
Some(prev) => {
let maybe_mouse = ui.widget_input(id).mouse();
match (prev.maybe_floating, maybe_mouse) {
(Some(prev_floating), Some(mouse)) => {
if mouse.buttons.left().is_down() {
Some(new_floating())
} else {
Some(prev_floating)
}
},
(Some(prev_floating), None) => Some(prev_floating),
_ => Some(new_floating()),
}
},
None => Some(new_floating()),
}
} else {
None
};
let kid_area = {
let args: KidAreaArgs<W> = KidAreaArgs {
rect: rect,
style: &new_style,
theme: &ui.theme,
fonts: &ui.fonts,
};
widget.kid_area(args)
};
let prev_kid_area = maybe_prev_common.map(|common| common.kid_area)
.unwrap_or_else(|| kid_area);
let mut maybe_x_scroll_state = widget.common().maybe_x_scroll.map(|_scroll_args| {
let maybe_prev = maybe_prev_common.as_ref().and_then(|p| p.maybe_x_scroll_state);
scroll::State::update(ui, id, &prev_kid_area, maybe_prev, 0.0)
});
let mut maybe_y_scroll_state = widget.common().maybe_y_scroll.map(|_scroll_args| {
let maybe_prev = maybe_prev_common.as_ref().and_then(|p| p.maybe_y_scroll_state);
scroll::State::update(ui, id, &prev_kid_area, maybe_prev, 0.0)
});
for scroll in ui.widget_input(id).scrolls() {
if widget.common().maybe_x_scroll.is_some() {
maybe_x_scroll_state =
Some(scroll::State::update(ui, id, &prev_kid_area, maybe_x_scroll_state, scroll.x))
}
if widget.common().maybe_y_scroll.is_some() {
maybe_y_scroll_state =
Some(scroll::State::update(ui, id, &prev_kid_area, maybe_y_scroll_state, scroll.y))
}
}
let is_first_set = maybe_prev_common.is_none();
{
let maybe_positioned_relatively_id = |pos: Position| match pos {
Position::Relative(_, maybe_id) => maybe_id.or(maybe_prev_widget_id),
Position::Absolute(_) => None,
};
let maybe_x_positioned_relatively_id = maybe_positioned_relatively_id(x_pos);
let maybe_y_positioned_relatively_id = maybe_positioned_relatively_id(y_pos);
let crop_kids = widget.common().crop_kids;
let ui: &mut Ui = ui::ref_mut_from_ui_cell(ui);
ui::pre_update_cache(ui, PreUpdateCache {
type_id: type_id,
id: id,
maybe_parent_id: maybe_parent_id,
maybe_x_positioned_relatively_id: maybe_x_positioned_relatively_id,
maybe_y_positioned_relatively_id: maybe_y_positioned_relatively_id,
rect: rect,
depth: depth,
kid_area: kid_area,
maybe_floating: maybe_floating,
crop_kids: crop_kids,
maybe_y_scroll_state: maybe_y_scroll_state,
maybe_x_scroll_state: maybe_x_scroll_state,
maybe_graphics_for: widget.common().maybe_graphics_for,
});
}
let prev_common = maybe_prev_common.unwrap_or_else(|| CommonState {
rect: rect,
depth: depth,
maybe_floating: maybe_floating,
kid_area: kid_area,
maybe_x_scroll_state: maybe_x_scroll_state,
maybe_y_scroll_state: maybe_y_scroll_state,
});
let (unique_state, has_state_updated, event) = {
let mut unique_state = maybe_prev_unique_state.unwrap_or_else(|| {
widget.init_state(ui.widget_id_generator())
});
let (has_updated, event) = {
let mut state = State {
state: &mut unique_state,
has_updated: false,
};
let event = widget.update(UpdateArgs {
id: id,
maybe_parent_id: maybe_parent_id,
state: &mut state,
prev: &prev_common,
rect: rect,
style: &new_style,
ui: ui,
});
(state.has_updated, event)
};
(unique_state, has_updated, event)
};
let state_has_changed = has_state_updated
|| rect != prev_common.rect
|| depth != prev_common.depth
|| is_first_set;
let style_has_changed = maybe_prev_style.map(|style| style != new_style).unwrap_or(false);
let scroll_has_changed =
maybe_x_scroll_state != maybe_prev_common.as_ref().and_then(|p| p.maybe_x_scroll_state)
|| maybe_y_scroll_state != maybe_prev_common.as_ref().and_then(|p| p.maybe_y_scroll_state);
let requires_redraw = style_has_changed || state_has_changed || scroll_has_changed;
let ui: &mut Ui = ui::ref_mut_from_ui_cell(ui);
if requires_redraw {
ui.needs_redraw();
}
ui::post_update_cache::<W>(ui, PostUpdateCache {
id: id,
maybe_parent_id: maybe_parent_id,
state: unique_state,
style: new_style,
});
event
}
impl<'a, T> State<'a, T> {
pub fn update<F>(&mut self, f: F) where F: FnOnce(&mut T) {
self.has_updated = true;
f(self.state);
}
}
impl<'a, T> std::ops::Deref for State<'a, T> {
type Target = T;
fn deref(&self) -> &T {
&self.state
}
}
impl CommonBuilder {
pub fn new() -> CommonBuilder {
CommonBuilder {
style: CommonStyle::new(),
maybe_parent_id: MaybeParent::Unspecified,
place_on_kid_area: true,
maybe_graphics_for: None,
is_floating: false,
maybe_x_scroll: None,
maybe_y_scroll: None,
crop_kids: false,
}
}
}
impl CommonStyle {
pub fn new() -> Self {
CommonStyle {
maybe_x_dimension: None,
maybe_y_dimension: None,
maybe_x_position: None,
maybe_y_position: None,
maybe_depth: None,
}
}
}
impl<W> Positionable for W
where W: Widget,
{
fn x_position(mut self, x: Position) -> Self {
self.common_mut().style.maybe_x_position = Some(x);
self
}
fn y_position(mut self, y: Position) -> Self {
self.common_mut().style.maybe_y_position = Some(y);
self
}
fn get_x_position(&self, ui: &Ui) -> Position {
let from_y_position = || self.common().style.maybe_y_position
.and_then(|y_pos| infer_position_from_other_position(y_pos, Align::Start));
self.common().style.maybe_x_position
.or_else(from_y_position)
.unwrap_or(self.default_x_position(ui))
}
fn get_y_position(&self, ui: &Ui) -> Position {
let from_x_position = || self.common().style.maybe_x_position
.and_then(|x_pos| infer_position_from_other_position(x_pos, Align::End));
self.common().style.maybe_y_position
.or_else(from_x_position)
.unwrap_or(self.default_y_position(ui))
}
fn depth(mut self, depth: Depth) -> Self {
self.common_mut().style.maybe_depth = Some(depth);
self
}
fn get_depth(&self) -> Depth {
const DEFAULT_DEPTH: Depth = 0.0;
self.common().style.maybe_depth.unwrap_or(DEFAULT_DEPTH)
}
}
fn infer_position_from_other_position(other_pos: Position, dir_align: Align) -> Option<Position> {
match other_pos {
Position::Relative(Relative::Direction(_, _), maybe_id) =>
Some(Position::Relative(Relative::Align(dir_align), maybe_id)),
Position::Relative(Relative::Place(_), maybe_id) =>
Some(Position::Relative(Relative::Align(Align::Middle), maybe_id)),
Position::Relative(Relative::Scalar(_), maybe_id) =>
Some(Position::Relative(Relative::Scalar(0.0), maybe_id)),
Position::Relative(Relative::Align(_), _) |
Position::Absolute(_) => None,
}
}
impl<W> Sizeable for W
where W: Widget,
{
fn x_dimension(mut self, w: Dimension) -> Self {
self.common_mut().style.maybe_x_dimension = Some(w);
self
}
fn y_dimension(mut self, h: Dimension) -> Self {
self.common_mut().style.maybe_y_dimension = Some(h);
self
}
fn get_x_dimension(&self, ui: &Ui) -> Dimension {
self.common().style.maybe_x_dimension.unwrap_or_else(|| self.default_x_dimension(ui))
}
fn get_y_dimension(&self, ui: &Ui) -> Dimension {
self.common().style.maybe_y_dimension.unwrap_or_else(|| self.default_y_dimension(ui))
}
}