use {Backend, CharacterCache, Dimension, GlyphCache};
use graph::{self, NodeIndex};
use position::{Align, Depth, Dimensions, Padding, Position, Positionable, Rect, Sizeable};
use std;
use theme::{self, Theme};
use ui::{self, Ui, UiCell};
pub use self::id::Id;
pub use self::index::Index;
#[macro_use] mod builder;
#[macro_use] mod style;
mod id;
mod index;
pub mod scroll;
pub mod primitive;
pub mod button;
pub mod canvas;
pub mod drop_down_list;
pub mod envelope_editor;
pub mod matrix;
pub mod number_dialer;
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, W, B: 'a>
where W: Widget,
B: Backend,
{
pub idx: Index,
pub maybe_parent_idx: Option<Index>,
pub prev: &'a CommonState,
pub state: &'a mut State<'b, W::State>,
pub rect: Rect,
pub style: &'a W::Style,
pub ui: UiCell<'a, B>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct IndexSlot {
maybe_idx: std::cell::Cell<Option<NodeIndex>>,
}
pub struct KidAreaArgs<'a, W, C: 'a>
where W: Widget,
{
pub rect: Rect,
pub style: &'a W::Style,
pub theme: &'a Theme,
pub glyph_cache: &'a GlyphCache<C>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct KidArea {
pub rect: Rect,
pub pad: Padding,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum MaybeParent {
None,
Some(Index),
Unspecified,
}
impl MaybeParent {
pub fn get_unchecked<B>(&self, ui: &Ui<B>, x_pos: Position, y_pos: Position) -> Index
where B: Backend,
{
match *self {
MaybeParent::Some(idx) => idx,
MaybeParent::None => ui.window.into(),
MaybeParent::Unspecified => ui::infer_parent_unchecked(ui, x_pos, y_pos),
}
}
pub fn get<B>(&self, idx: Index, ui: &Ui<B>, x_pos: Position, y_pos: Position) -> Option<Index>
where B: Backend,
{
if idx == ui.window.into() {
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_idx: 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<Index>,
}
#[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>,
}
#[derive(Debug)]
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 struct Cached<W>
where W: Widget,
{
pub state: W::State,
pub style: W::Style,
pub rect: Rect,
pub depth: Depth,
pub kid_area: KidArea,
pub maybe_floating: Option<Floating>,
pub maybe_x_scroll_state: Option<scroll::StateX>,
pub maybe_y_scroll_state: Option<scroll::StateY>,
}
pub type Kind = &'static str;
#[allow(missing_copy_implementations)]
pub struct PreUpdateCache {
pub kind: Kind,
pub idx: Index,
pub maybe_parent_idx: Option<Index>,
pub maybe_x_positioned_relatively_idx: Option<Index>,
pub maybe_y_positioned_relatively_idx: Option<Index>,
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<Index>,
}
pub struct PostUpdateCache<W>
where W: Widget,
{
pub idx: Index,
pub maybe_parent_idx: Option<Index>,
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, B, F>(widget: &W, ui: &Ui<B>, f: F) -> Dimension
where W: Widget,
B: Backend,
F: FnOnce(theme::UniqueDefault<W::Style>) -> Option<Dimension>,
{
ui.theme.widget_style::<W::Style>(widget.unique_kind())
.and_then(f)
.or_else(|| ui.maybe_prev_widget().map(|idx| Dimension::Of(idx, None)))
.unwrap_or_else(|| {
let x_pos = widget.get_x_position(ui);
let y_pos = widget.get_y_position(ui);
let parent_idx = widget.common().maybe_parent_idx.get_unchecked(ui, x_pos, y_pos);
Dimension::Of(parent_idx, None)
})
}
pub fn default_x_dimension<W, B>(widget: &W, ui: &Ui<B>) -> Dimension
where W: Widget,
B: Backend,
{
default_dimension(widget, ui, |default| default.common.maybe_x_dimension)
}
pub fn default_y_dimension<W, B>(widget: &W, ui: &Ui<B>) -> Dimension
where W: Widget,
B: Backend,
{
default_dimension(widget, ui, |default| default.common.maybe_y_dimension)
}
pub trait Widget: Sized {
type State: std::any::Any + PartialEq + std::fmt::Debug;
type Style: Style;
fn common(&self) -> &CommonBuilder;
fn common_mut(&mut self) -> &mut CommonBuilder;
fn unique_kind(&self) -> Kind;
fn init_state(&self) -> Self::State;
fn style(&self) -> Self::Style;
fn update<B: Backend>(self, args: UpdateArgs<Self, B>);
fn default_x_position<B: Backend>(&self, ui: &Ui<B>) -> Position {
ui.theme.widget_style::<Self::Style>(self.unique_kind())
.and_then(|style| style.common.maybe_x_position)
.unwrap_or(ui.theme.x_position)
}
fn default_y_position<B: Backend>(&self, ui: &Ui<B>) -> Position {
ui.theme.widget_style::<Self::Style>(self.unique_kind())
.and_then(|style| style.common.maybe_y_position)
.unwrap_or(ui.theme.y_position)
}
fn default_x_dimension<B: Backend>(&self, ui: &Ui<B>) -> Dimension {
default_x_dimension(self, ui)
}
fn default_y_dimension<B: Backend>(&self, ui: &Ui<B>) -> Dimension {
default_y_dimension(self, ui)
}
fn drag_area(&self,
_dim: Dimensions,
_style: &Self::Style,
_theme: &Theme) -> Option<Rect>
{
None
}
fn kid_area<C: CharacterCache>(&self, args: KidAreaArgs<Self, C>) -> KidArea {
KidArea {
rect: args.rect,
pad: Padding::none(),
}
}
fn parent<I: Into<Index>>(mut self, parent_idx: I) -> Self {
self.common_mut().maybe_parent_idx = MaybeParent::Some(parent_idx.into());
self
}
fn no_parent(mut self) -> Self {
self.common_mut().maybe_parent_idx = 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<I: Into<Index>>(mut self, idx: I) -> Self {
self.common_mut().maybe_graphics_for = Some(idx.into());
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, I, B>(self, idx: I, ui_cell: &'a mut UiCell<'b, B>)
where I: Into<Index>,
B: Backend,
{
let idx: Index = idx.into();
let ui: &'a mut Ui<B> = ui::ref_mut_from_ui_cell(ui_cell);
set_widget(self, idx, ui);
}
}
fn set_widget<B, W>(widget: W, idx: Index, ui: &mut Ui<B>)
where B: Backend,
W: Widget,
{
let kind = widget.unique_kind();
let maybe_widget_state: Option<Cached<W>> = {
let check_container_kind = |container: &mut graph::Container| {
use std::io::Write;
if container.kind != kind {
writeln!(std::io::stderr(),
"A widget of a different kind already exists at the given WidgetId \
({:?}). You tried to insert a {:?}, however the existing widget is a \
{:?}. Check your widgets' `WidgetId`s for errors.",
idx, kind, container.kind).unwrap();
return None;
} else {
container.take_widget_state()
}
};
ui::widget_graph_mut(ui).widget_mut(idx).and_then(check_container_kind)
};
let (maybe_prev_unique_state,
maybe_prev_common,
maybe_prev_style,
maybe_prev_x_scroll_state,
maybe_prev_y_scroll_state) =
maybe_widget_state.map(|prev| {
let Cached {
state,
style,
rect,
depth,
maybe_floating,
maybe_x_scroll_state,
maybe_y_scroll_state,
kid_area,
..
} = prev;
let prev_common = CommonState {
rect: rect,
depth: depth,
maybe_floating: maybe_floating,
kid_area: kid_area,
};
(Some(state),
Some(prev_common),
Some(style),
maybe_x_scroll_state,
maybe_y_scroll_state)
}).unwrap_or_else(|| (None, None, None, None, None));
let maybe_prev_widget_idx = 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_idx = widget.common().maybe_parent_idx.get(idx, 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(idx).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(idx), 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(idx).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, B::CharacterCache> = KidAreaArgs {
rect: rect,
style: &new_style,
theme: &ui.theme,
glyph_cache: &ui.glyph_cache,
};
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| {
scroll::State::update(ui, idx, &prev_kid_area, maybe_prev_x_scroll_state, 0.0)
});
let mut maybe_y_scroll_state = widget.common().maybe_y_scroll.map(|_scroll_args| {
scroll::State::update(ui, idx, &prev_kid_area, maybe_prev_y_scroll_state, 0.0)
});
for scroll in ui.widget_input(idx).scrolls() {
if widget.common().maybe_x_scroll.is_some() {
maybe_x_scroll_state =
Some(scroll::State::update(ui, idx, &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, idx, &prev_kid_area, maybe_y_scroll_state, scroll.y))
}
}
let is_first_set = maybe_prev_common.is_none();
{
use Position::{Place, Relative, Direction, Align, Absolute};
let maybe_positioned_relatively_idx = |pos: Position| match pos {
Place(_, maybe_idx) | Relative(_, maybe_idx) |
Direction(_, _, maybe_idx) | Align(_, maybe_idx) =>
maybe_idx.or(maybe_prev_widget_idx),
Absolute(_) => None,
};
let maybe_x_positioned_relatively_idx = maybe_positioned_relatively_idx(x_pos);
let maybe_y_positioned_relatively_idx = maybe_positioned_relatively_idx(y_pos);
let crop_kids = widget.common().crop_kids;
ui::pre_update_cache(ui, PreUpdateCache {
kind: kind,
idx: idx,
maybe_parent_idx: maybe_parent_idx,
maybe_x_positioned_relatively_idx: maybe_x_positioned_relatively_idx,
maybe_y_positioned_relatively_idx: maybe_y_positioned_relatively_idx,
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,
});
let (unique_state, has_state_updated) = {
let mut unique_state = maybe_prev_unique_state.unwrap_or_else(|| widget.init_state());
let has_updated = {
let mut state = State {
state: &mut unique_state,
has_updated: false,
};
widget.update(UpdateArgs {
idx: idx,
maybe_parent_idx: maybe_parent_idx,
state: &mut state,
prev: &prev_common,
rect: rect,
style: &new_style,
ui: ui::new_ui_cell(ui),
});
state.has_updated
};
(unique_state, has_updated)
};
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_x_scroll_state
|| maybe_y_scroll_state != maybe_prev_y_scroll_state;
let requires_redraw = style_has_changed || state_has_changed || scroll_has_changed;
if requires_redraw {
ui.needs_redraw();
}
ui::post_update_cache::<B, W>(ui, PostUpdateCache {
idx: idx,
maybe_parent_idx: maybe_parent_idx,
state: unique_state,
style: new_style,
});
}
impl IndexSlot {
pub fn new() -> Self {
IndexSlot {
maybe_idx: ::std::cell::Cell::new(None),
}
}
pub fn get<B>(&self, ui: &mut UiCell<B>) -> NodeIndex
where B: Backend,
{
if self.maybe_idx.get().is_none() {
let new_idx = ui.new_unique_node_index();
self.maybe_idx.set(Some(new_idx));
}
self.maybe_idx.get().unwrap()
}
}
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_idx: 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,
{
#[inline]
fn x_position(mut self, x: Position) -> Self {
self.common_mut().style.maybe_x_position = Some(x);
self
}
#[inline]
fn y_position(mut self, y: Position) -> Self {
self.common_mut().style.maybe_y_position = Some(y);
self
}
#[inline]
fn get_x_position<B: Backend>(&self, ui: &Ui<B>) -> 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))
}
#[inline]
fn get_y_position<B: Backend>(&self, ui: &Ui<B>) -> 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))
}
#[inline]
fn depth(mut self, depth: Depth) -> Self {
self.common_mut().style.maybe_depth = Some(depth);
self
}
#[inline]
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::Direction(_, _, maybe_idx) => Some(Position::Align(dir_align, maybe_idx)),
Position::Place(_, maybe_idx) => Some(Position::Align(Align::Middle, maybe_idx)),
Position::Relative(_, maybe_idx) => Some(Position::Relative(0.0, maybe_idx)),
Position::Align(_, _) | Position::Absolute(_) => None,
}
}
impl<W> Sizeable for W
where W: Widget,
{
#[inline]
fn x_dimension(mut self, w: Dimension) -> Self {
self.common_mut().style.maybe_x_dimension = Some(w);
self
}
#[inline]
fn y_dimension(mut self, h: Dimension) -> Self {
self.common_mut().style.maybe_y_dimension = Some(h);
self
}
#[inline]
fn get_x_dimension<B: Backend>(&self, ui: &Ui<B>) -> Dimension {
self.common().style.maybe_x_dimension.unwrap_or_else(|| self.default_x_dimension(ui))
}
#[inline]
fn get_y_dimension<B: Backend>(&self, ui: &Ui<B>) -> Dimension {
self.common().style.maybe_y_dimension.unwrap_or_else(|| self.default_y_dimension(ui))
}
}