use {
Color,
Colorable,
FontSize,
Borderable,
Labelable,
Positionable,
Sizeable,
Theme,
Ui,
UiCell,
Widget,
};
use position::{self, Dimensions, Padding, Place, Position, Range, Rect, Scalar};
use position::Direction::{Forwards, Backwards};
use text;
use widget;
#[derive(Copy, Clone, Debug, WidgetCommon_)]
pub struct Canvas<'a> {
#[conrod(common_builder)]
pub common: widget::CommonBuilder,
pub style: Style,
pub maybe_title_bar_label: Option<&'a str>,
pub maybe_splits: Option<FlowOfSplits<'a>>,
}
pub struct State {
ids: Ids,
}
widget_ids! {
struct Ids {
rectangle,
title_bar,
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, WidgetStyle_)]
pub struct Style {
#[conrod(default = "theme.background_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 = "Length::Weight(1.0)")]
pub length: Option<Length>,
#[conrod(default = "theme.padding.x.start")]
pub pad_left: Option<Scalar>,
#[conrod(default = "theme.padding.x.end")]
pub pad_right: Option<Scalar>,
#[conrod(default = "theme.padding.y.start")]
pub pad_bottom: Option<Scalar>,
#[conrod(default = "theme.padding.y.end")]
pub pad_top: Option<Scalar>,
#[conrod(default = "None")]
pub title_bar_color: Option<Option<Color>>,
#[conrod(default = "theme.label_color")]
pub title_bar_text_color: Option<Color>,
#[conrod(default = "theme.font_size_medium")]
pub title_bar_font_size: Option<FontSize>,
#[conrod(default = "Some(widget::text::Wrap::Whitespace)")]
pub title_bar_maybe_wrap: Option<Option<widget::text::Wrap>>,
#[conrod(default = "1.0")]
pub title_bar_line_spacing: Option<Scalar>,
#[conrod(default = "text::Justify::Center")]
pub title_bar_justify: Option<text::Justify>,
}
pub type ListOfSplits<'a> = &'a [(widget::Id, Canvas<'a>)];
pub type FlowOfSplits<'a> = (Direction, ListOfSplits<'a>);
pub type Weight = Scalar;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Length {
Absolute(Scalar),
Weight(Weight),
}
#[derive(Copy, Clone, Debug)]
pub enum Direction {
X(position::Direction),
Y(position::Direction),
}
impl<'a> Canvas<'a> {
pub fn new() -> Self {
Canvas {
common: widget::CommonBuilder::default(),
style: Style::default(),
maybe_title_bar_label: None,
maybe_splits: None,
}
}
builder_methods!{
pub title_bar { maybe_title_bar_label = Some(&'a str) }
pub pad_left { style.pad_left = Some(Scalar) }
pub pad_right { style.pad_right = Some(Scalar) }
pub pad_bottom { style.pad_bottom = Some(Scalar) }
pub pad_top { style.pad_top = Some(Scalar) }
pub with_style { style = Style }
}
pub fn length(mut self, length: Scalar) -> Self {
self.style.length = Some(Length::Absolute(length));
self
}
pub fn length_weight(mut self, weight: Weight) -> Self {
self.style.length = Some(Length::Weight(weight));
self
}
fn flow(mut self, direction: Direction, splits: ListOfSplits<'a>) -> Self {
self.maybe_splits = Some((direction, splits));
self
}
pub fn flow_right(self, splits: ListOfSplits<'a>) -> Self {
self.flow(Direction::X(Forwards), splits)
}
pub fn flow_left(self, splits: ListOfSplits<'a>) -> Self {
self.flow(Direction::X(Backwards), splits)
}
pub fn flow_up(self, splits: ListOfSplits<'a>) -> Self {
self.flow(Direction::Y(Forwards), splits)
}
pub fn flow_down(self, splits: ListOfSplits<'a>) -> Self {
self.flow(Direction::Y(Backwards), splits)
}
#[inline]
pub fn pad(self, pad: Scalar) -> Self {
self.pad_left(pad).pad_right(pad).pad_bottom(pad).pad_top(pad)
}
#[inline]
pub fn padding(self, pad: Padding) -> Self {
self.pad_left(pad.x.start)
.pad_right(pad.x.end)
.pad_bottom(pad.y.start)
.pad_top(pad.y.end)
}
pub fn title_bar_color(mut self, color: Color) -> Self {
self.style.title_bar_color = Some(Some(color));
self
}
}
impl<'a> Widget for Canvas<'a> {
type State = State;
type Style = Style;
type Event = ();
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 default_x_position(&self, _ui: &Ui) -> Position {
Position::Relative(position::Relative::Place(Place::Middle), None)
}
fn default_y_position(&self, _ui: &Ui) -> Position {
Position::Relative(position::Relative::Place(Place::Middle), None)
}
fn drag_area(&self, dim: Dimensions, style: &Style, theme: &Theme) -> Option<Rect> {
self.maybe_title_bar_label.map(|_| {
let font_size = style.title_bar_font_size(theme);
let (h, rel_y) = title_bar_h_rel_y(dim[1], font_size);
let rel_xy = [0.0, rel_y];
let dim = [dim[0], h];
Rect::from_xy_dim(rel_xy, dim)
})
}
fn kid_area(&self, args: widget::KidAreaArgs<Self>) -> widget::KidArea {
let widget::KidAreaArgs { rect, style, theme, .. } = args;
if self.maybe_title_bar_label.is_some() {
let font_size = style.title_bar_font_size(theme);
let title_bar = title_bar(rect, font_size);
widget::KidArea {
rect: rect.pad_top(title_bar.h()),
pad: style.padding(theme),
}
} else {
widget::KidArea {
rect: rect,
pad: style.padding(theme),
}
}
}
fn update(self, args: widget::UpdateArgs<Self>) {
let widget::UpdateArgs { id, state, rect, mut ui, .. } = args;
let Canvas { style, maybe_title_bar_label, maybe_splits, .. } = self;
let dim = rect.dim();
let color = style.color(ui.theme());
let border = style.border(ui.theme());
let border_color = style.border_color(ui.theme());
widget::BorderedRectangle::new(dim)
.color(color)
.border(border)
.border_color(border_color)
.middle_of(id)
.graphics_for(id)
.place_on_kid_area(false)
.set(state.ids.rectangle, &mut ui);
if let Some(label) = maybe_title_bar_label {
let color = style.title_bar_color(&ui.theme).unwrap_or(color);
let font_size = style.title_bar_font_size(&ui.theme);
let label_color = style.title_bar_text_color(&ui.theme);
let justify = style.title_bar_justify(&ui.theme);
let line_spacing = style.title_bar_line_spacing(&ui.theme);
let maybe_wrap = style.title_bar_maybe_wrap(&ui.theme);
widget::TitleBar::new(label, state.ids.rectangle)
.and_mut(|title_bar| {
title_bar.style.maybe_wrap = Some(maybe_wrap);
title_bar.style.justify = Some(justify);
})
.color(color)
.border(border)
.border_color(border_color)
.label_font_size(font_size)
.label_color(label_color)
.line_spacing(line_spacing)
.graphics_for(id)
.place_on_kid_area(false)
.set(state.ids.title_bar, &mut ui);
}
if let Some((direction, splits)) = maybe_splits {
let (total_abs, total_weight) =
splits.iter().fold((0.0, 0.0), |(abs, weight), &(_, split)| {
match split.style.length(ui.theme()) {
Length::Absolute(a) => (abs + a, weight),
Length::Weight(w) => (abs, weight + w),
}
});
let kid_area = ui.kid_area_of(id).expect("No KidArea found");
let kid_area_range = match direction {
Direction::X(_) => kid_area.x,
Direction::Y(_) => kid_area.y,
};
let total_length = kid_area_range.len();
let non_abs_length = (total_length - total_abs).max(0.0);
let weight_normaliser = 1.0 / total_weight;
let length = |split: &Self, ui: &UiCell| -> Scalar {
match split.style.length(ui.theme()) {
Length::Absolute(length) => length,
Length::Weight(weight) => weight * weight_normaliser * non_abs_length,
}
};
let set_split = |split_id: widget::Id, split: Canvas<'a>, ui: &mut UiCell| {
split.parent(id).set(split_id, ui);
};
match direction {
Direction::X(direction) => match direction {
Forwards => for (i, &(split_id, split)) in splits.iter().enumerate() {
let w = length(&split, &ui);
let split = match i {
0 => split.h(kid_area.h()).mid_left_of(id),
_ => split.right(0.0),
}.w(w);
set_split(split_id, split, &mut ui);
},
Backwards => for (i, &(split_id, split)) in splits.iter().enumerate() {
let w = length(&split, &ui);
let split = match i {
0 => split.h(kid_area.h()).mid_right_of(id),
_ => split.left(0.0),
}.w(w);
set_split(split_id, split, &mut ui);
},
},
Direction::Y(direction) => match direction {
Forwards => for (i, &(split_id, split)) in splits.iter().enumerate() {
let h = length(&split, &ui);
let split = match i {
0 => split.w(kid_area.w()).mid_bottom_of(id),
_ => split.up(0.0),
}.h(h);
set_split(split_id, split, &mut ui);
},
Backwards => for (i, &(split_id, split)) in splits.iter().enumerate() {
let h = length(&split, &ui);
let split = match i {
0 => split.w(kid_area.w()).mid_top_of(id),
_ => split.down(0.0),
}.h(h);
set_split(split_id, split, &mut ui);
},
},
}
}
}
}
fn title_bar_h_rel_y(canvas_h: Scalar, font_size: FontSize) -> (Scalar, Scalar) {
let h = widget::title_bar::calc_height(font_size);
let rel_y = canvas_h / 2.0 - h / 2.0;
(h, rel_y)
}
fn title_bar(canvas: Rect, font_size: FontSize) -> Rect {
let (c_w, c_h) = canvas.w_h();
let (h, rel_y) = title_bar_h_rel_y(c_h, font_size);
let xy = [0.0, rel_y];
let dim = [c_w, h];
Rect::from_xy_dim(xy, dim)
}
impl Style {
pub fn padding(&self, theme: &Theme) -> position::Padding {
position::Padding {
x: Range::new(self.pad_left(theme), self.pad_right(theme)),
y: Range::new(self.pad_bottom(theme), self.pad_top(theme)),
}
}
}
impl<'a> ::color::Colorable for Canvas<'a> {
builder_method!(color { style.color = Some(Color) });
}
impl<'a> ::border::Borderable for Canvas<'a> {
builder_methods!{
border { style.border = Some(Scalar) }
border_color { style.border_color = Some(Color) }
}
}
impl<'a> ::label::Labelable<'a> for Canvas<'a> {
fn label(self, text: &'a str) -> Self {
self.title_bar(text)
}
builder_methods!{
label_color { style.title_bar_text_color = Some(Color) }
label_font_size { style.title_bar_font_size = Some(FontSize) }
}
}