use graph::{Container, UniqueWidgetState};
use position::{
Align, Depth, Dimension, Dimensions, Padding, Point, 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::image::{self, Image};
pub use self::primitive::line::{self, Line};
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::shape::triangles::{self, Triangles};
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::envelope_editor::EnvelopeEditor;
pub use self::file_navigator::FileNavigator;
pub use self::graph::Graph;
pub use self::grid::Grid;
pub use self::list::List;
pub use self::list_select::ListSelect;
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]
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 graph;
pub mod grid;
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: instant::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, Default, PartialEq)]
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_dragged_from: Option<Point>,
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_dragged_from: Option<Point>,
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>,
pub is_over: IsOverFn,
}
#[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,
}
#[derive(Copy, Clone)]
pub enum IsOver {
Bool(bool),
Widget(Id),
}
impl From<bool> for IsOver {
fn from(b: bool) -> Self {
IsOver::Bool(b)
}
}
impl From<Id> for IsOver {
fn from(id: Id) -> Self {
IsOver::Widget(id)
}
}
pub type IsOverFn = fn(&Container, Point, &Theme) -> IsOver;
pub fn is_over_rect(container: &Container, point: Point, _: &Theme) -> IsOver {
container.rect.is_over(point).into()
}
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 Common {
fn common(&self) -> &CommonBuilder;
fn common_mut(&mut self) -> &mut CommonBuilder;
}
pub trait Widget: Common + Sized {
type State: std::any::Any + Send;
type Style: Style + Send;
type Event;
fn init_state(&self, id_gen: 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 is_over(&self) -> IsOverFn {
is_over_rect
}
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 Container {
ref mut maybe_state,
rect,
depth,
kid_area,
maybe_dragged_from,
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: UniqueWidgetState<W::State, W::Style> = *boxed;
let UniqueWidgetState { state, style } = unique;
(state, style)
}
None => return None,
};
let prev_common = CommonState {
rect: rect,
depth: depth,
maybe_dragged_from: maybe_dragged_from,
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_dragged_from) = maybe_prev_common
.as_ref()
.and_then(|prev| {
if widget.common().maybe_graphics_for.is_some() {
return None;
}
let maybe_drag_area = widget.drag_area(dim, &new_style, &ui.theme);
maybe_drag_area.map(|drag_area| {
let mut current_dragged_from = prev.maybe_dragged_from;
let mut current_xy = prev.rect.xy();
for event in ui.widget_input(id).events() {
match event {
::event::Widget::Drag(drag) => {
if drag.button == input::MouseButton::Left {
if current_dragged_from.is_none() && drag_area.is_over(drag.origin)
{
current_dragged_from = Some(prev.rect.xy());
}
if let Some(dragged_from) = current_dragged_from {
current_xy = [
dragged_from[0] + drag.to[0] - drag.origin[0],
dragged_from[1] + drag.to[1] - drag.origin[1],
];
}
}
}
::event::Widget::Release(::event::Release {
button: ::event::Button::Mouse(input::MouseButton::Left, _),
..
}) => {
current_dragged_from = None;
}
_ => {}
}
}
(current_xy, current_dragged_from)
})
})
.unwrap_or_else(|| {
(
ui.calc_xy(
Some(id),
maybe_parent_id,
x_pos,
y_pos,
dim,
place_on_kid_area,
),
None,
)
});
let rect = Rect::from_xy_dim(xy, dim);
let maybe_floating = if widget.common().is_floating {
fn new_floating() -> Floating {
Floating {
time_last_clicked: instant::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_dragged_from: maybe_dragged_from,
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,
is_over: widget.is_over(),
},
);
}
let prev_common = maybe_prev_common.unwrap_or_else(|| CommonState {
rect: rect,
depth: depth,
maybe_dragged_from: maybe_dragged_from,
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 Default for CommonBuilder {
fn default() -> Self {
CommonBuilder {
style: CommonStyle::default(),
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<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))
}
}