use color::Color;
use form::{self, Form};
use graphics::character::CharacterCache;
use graphics::{Context, Graphics, Transformed};
use self::Three::{P, Z, N};
use std::path::PathBuf;
use transform_2d;
#[derive(Clone, Debug)]
pub struct Properties {
pub width: i32,
pub height: i32,
pub opacity: f32,
pub crop: Option<(f64, f64, f64, f64)>,
pub color: Option<Color>,
}
#[derive(Clone, Debug)]
pub struct Element {
pub props: Properties,
pub element: Prim,
}
impl Element {
#[inline]
pub fn width(self, new_width: i32) -> Element {
let Element { props, element } = self;
let new_props = match element {
Prim::Image(_, w, h, _) | Prim::Collage(w, h, _) => {
Properties {
height: (h as f32 / w as f32 * new_width as f32).round() as i32,
..props
}
},
_ => props,
};
Element { props: new_props, element: element }
}
#[inline]
pub fn height(self, new_height: i32) -> Element {
let Element { props, element } = self;
let new_props = match element {
Prim::Image(_, w, h, _) | Prim::Collage(w, h, _) => {
Properties {
width: (w as f32 / h as f32 * new_height as f32).round() as i32,
..props
}
},
_ => props,
};
Element { props: new_props, element: element }
}
#[inline]
pub fn size(self, new_w: i32, new_h: i32) -> Element {
self.height(new_h).width(new_w)
}
#[inline]
pub fn opacity(mut self, opacity: f32) -> Element {
self.props.opacity = opacity;
self
}
#[inline]
pub fn color(mut self, color: Color) -> Element {
self.props.color = Some(color);
self
}
#[inline]
pub fn crop(self, x: f64, y: f64, w: f64, h: f64) -> Element {
let Element { props, element } = self;
let new_props = Properties { crop: Some((x, y, w, h)), ..props };
Element { props: new_props, element: element }
}
#[inline]
pub fn container(self, w: i32, h: i32, pos: Position) -> Element {
new_element(w, h, Prim::Container(pos, Box::new(self)))
}
#[inline]
pub fn clear(self, color: Color) -> Element {
new_element(self.get_width(), self.get_height(),
Prim::Cleared(color, Box::new(self)))
}
#[inline]
pub fn above(self, other: Element) -> Element {
new_element(::std::cmp::max(self.get_width(), other.get_width()),
self.get_height() + other.get_height(),
Prim::Flow(down(), vec![self, other]))
}
#[inline]
pub fn below(self, other: Element) -> Element {
other.above(self)
}
#[inline]
pub fn beside(self, other: Element) -> Element {
new_element(self.get_width() + other.get_width(),
::std::cmp::max(self.get_height(), other.get_height()),
Prim::Flow(right(), vec![self, other]))
}
pub fn get_width(&self) -> i32 { self.props.width }
pub fn get_height(&self) -> i32 { self.props.height }
pub fn get_size(&self) -> (i32, i32) { (self.props.width, self.props.height) }
#[inline]
pub fn draw<'a, C, G>(&self, renderer: &mut Renderer<'a, C, G>)
where
C: CharacterCache,
G: Graphics<Texture=C::Texture>,
{
let Renderer {
context,
ref mut backend,
ref mut maybe_character_cache,
} = *renderer;
let view_size = context.get_view_size();
let context = context.trans(view_size[0] / 2.0, view_size[1] / 2.0).scale(1.0, -1.0);
draw_element(self, 1.0, *backend, maybe_character_cache, context);
}
pub fn is_over(&self, x: i32, y: i32) -> bool {
unimplemented!();
}
}
pub fn size_of(e: &Element) -> (i32, i32) {
(e.props.width, e.props.height)
}
#[inline]
pub fn new_element(w: i32, h: i32, element: Prim) -> Element {
Element {
props: Properties {
width: w,
height: h,
opacity: 1.0,
color: None,
crop: None,
},
element: element,
}
}
pub fn spacer(w: i32, h: i32) -> Element {
new_element(w, h, Prim::Spacer)
}
pub fn empty() -> Element {
spacer(0, 0)
}
#[derive(Clone, Debug)]
pub enum Prim {
Image(ImageStyle, i32, i32, PathBuf),
Container(Position, Box<Element>),
Flow(Direction, Vec<Element>),
Collage(i32, i32, Vec<Form>),
Cleared(Color, Box<Element>),
Spacer,
}
#[derive(Copy, Clone, Debug)]
pub enum ImageStyle {
Plain,
Fitted,
Cropped(i32, i32),
Tiled,
}
pub fn image(w: i32, h: i32, path: PathBuf) -> Element {
new_element(w, h, Prim::Image(ImageStyle::Plain, w, h, path))
}
pub fn fitted_image(w: i32, h: i32, path: PathBuf) -> Element {
new_element(w, h, Prim::Image(ImageStyle::Fitted, w, h, path))
}
pub fn cropped_image(x: i32, y: i32, w: i32, h: i32, path: PathBuf) -> Element {
new_element(w, h, Prim::Image(ImageStyle::Cropped(x, y), w, h, path))
}
pub fn tiled_image(w: i32, h: i32, path: PathBuf) -> Element {
new_element(w, h, Prim::Image(ImageStyle::Tiled, w, h, path))
}
#[derive(Copy, Clone, Debug)]
pub enum Three { P, Z, N }
#[derive(Copy, Clone, Debug)]
pub enum Pos { Absolute(i32), Relative(f32) }
#[derive(Copy, Clone, Debug)]
pub struct Position {
horizontal: Three,
vertical: Three,
x: Pos,
y: Pos,
}
#[derive(Copy, Clone, Debug)]
pub enum Direction { Up, Down, Left, Right, In, Out }
pub fn flow(dir: Direction, elements: Vec<Element>) -> Element {
if elements.is_empty() { return empty() }
let max_w = elements.iter().map(|e| e.get_width()).max().unwrap();
let max_h = elements.iter().map(|e| e.get_height()).max().unwrap();
let sum_w = elements.iter().fold(0, |total, e| total + e.get_width());
let sum_h = elements.iter().fold(0, |total, e| total + e.get_height());
let new_flow = |w: i32, h: i32| new_element(w, h, Prim::Flow(dir, elements));
match dir {
Direction::Up | Direction::Down => new_flow(max_w, sum_h),
Direction::Left | Direction::Right => new_flow(sum_w, max_h),
Direction::In | Direction::Out => new_flow(max_w, max_h),
}
}
pub fn layers(elements: Vec<Element>) -> Element {
let max_w = elements.iter().map(|e| e.get_width()).max().unwrap();
let max_h = elements.iter().map(|e| e.get_height()).max().unwrap();
new_element(max_w, max_h, Prim::Flow(outward(), elements))
}
pub fn absolute(i: i32) -> Pos { Pos::Absolute(i) }
pub fn relative(f: f32) -> Pos { Pos::Relative(f) }
#[inline]
fn p(h: Three, v: Three, x: Pos, y: Pos) -> Position {
Position { horizontal: h, vertical: v, x: x, y: y }
}
pub fn middle() -> Position { p(Z, Z, relative(0.5), relative(0.5)) }
pub fn top_left() -> Position { p(N, P, absolute(0), absolute(0)) }
pub fn top_right() -> Position { p(P, P, absolute(0), absolute(0)) }
pub fn bottom_left() -> Position { p(N, N, absolute(0), absolute(0)) }
pub fn bottom_right() -> Position { p(P, N, absolute(0), absolute(0)) }
pub fn mid_left() -> Position { p(N, Z, absolute(0), relative(0.5)) }
pub fn mid_right() -> Position { p(P, Z, absolute(0), relative(0.5)) }
pub fn mid_top() -> Position { p(Z, P, relative(0.5), absolute(0)) }
pub fn mid_bottom() -> Position { p(Z, N, relative(0.5), absolute(0)) }
pub fn middle_at(x: Pos, y: Pos) -> Position { p(Z, Z, x, y) }
pub fn top_left_at(x: Pos, y: Pos) -> Position { p(N, P, x, y) }
pub fn top_right_at(x: Pos, y: Pos) -> Position { p(P, P, x, y) }
pub fn bottom_left_at(x: Pos, y: Pos) -> Position { p(N, N, x, y) }
pub fn bottom_right_at(x: Pos, y: Pos) -> Position { p(P, N, x, y) }
pub fn mid_left_at(x: Pos, y: Pos) -> Position { p(N, Z, x, y) }
pub fn mid_right_at(x: Pos, y: Pos) -> Position { p(P, Z, x, y) }
pub fn mid_top_at(x: Pos, y: Pos) -> Position { p(Z, P, x, y) }
pub fn mid_bottom_at(x: Pos, y: Pos) -> Position { p(Z, N, x, y) }
pub fn up() -> Direction { Direction::Up }
pub fn down() -> Direction { Direction::Down }
pub fn left() -> Direction { Direction::Left }
pub fn right() -> Direction { Direction::Right }
pub fn inward() -> Direction { Direction::In }
pub fn outward() -> Direction { Direction::Out }
pub struct Renderer<'a, C: 'a, G: 'a> {
context: Context,
backend: &'a mut G,
maybe_character_cache: Option<&'a mut C>,
}
impl<'a, C, G> Renderer<'a, C, G> {
pub fn new(context: Context, backend: &'a mut G) -> Renderer<'a, C, G> {
Renderer {
context: context,
backend: backend,
maybe_character_cache: None,
}
}
pub fn character_cache(self, character_cache: &'a mut C) -> Renderer<'a, C, G> {
Renderer { maybe_character_cache: Some(character_cache), ..self }
}
}
pub fn draw_element<'a, C: CharacterCache, G: Graphics<Texture=C::Texture>>(
element: &Element,
opacity: f32,
backend: &mut G,
maybe_character_cache: &mut Option<&mut C>,
context: Context,
) {
let Element { ref props, ref element } = *element;
let context = match props.crop {
Some((x, y, w, h)) => {
use utils::{clamp, map_range};
let Context { draw_state, .. } = context;
let view_dim = context.get_view_size();
let draw_dim = match context.viewport {
Some(viewport) => [viewport.draw_size[0] as f64, viewport.draw_size[1] as f64],
None => view_dim,
};
let left = -draw_dim[0] / 2.0;
let right = draw_dim[0] / 2.0;
let bottom = -draw_dim[1] / 2.0;
let top = draw_dim[1] / 2.0;
let w_scale = 1.0 / (view_dim[0] / draw_dim[0]);
let h_scale = 1.0 / (view_dim[1] / draw_dim[1]);
let w = (w * w_scale) as u16;
let h = (h * h_scale) as u16;
let x = map_range(x - w as f64 / 2.0, left, right, 0, draw_dim[0] as i32);
let y = map_range(y - h as f64 / 2.0, bottom, top, 0, draw_dim[1] as i32);
let x_neg = if x < 0 { x } else { 0 };
let y_neg = if y < 0 { y } else { 0 };
let x = ::std::cmp::max(0, x) as u16;
let y = ::std::cmp::max(0, y) as u16;
let w = ::std::cmp::max(0, (w as i32 + x_neg)) as u16;
let h = ::std::cmp::max(0, (h as i32 + y_neg)) as u16;
Context { draw_state: draw_state.scissor(x, y, w, h), ..context }
},
None => context,
};
match *element {
Prim::Image(style, w, h, ref path) => {
let Properties { width, height, opacity, color, .. } = *props;
match style {
ImageStyle::Plain => {
unimplemented!();
},
ImageStyle::Fitted => {
unimplemented!();
},
ImageStyle::Cropped(x, y) => {
unimplemented!();
},
ImageStyle::Tiled => {
unimplemented!();
},
}
},
Prim::Container(position, ref element) => {
let Position { horizontal, vertical, x, y } = position;
let context = match (x, y) {
(Pos::Relative(x), Pos::Relative(y)) => context.trans(x as f64, y as f64),
(Pos::Absolute(x), Pos::Relative(y)) => Context {
transform: transform_2d::matrix(1.0, 0.0, 0.0, 1.0, x as f64, 0.0).0,
..context
}.trans(0.0, y as f64),
(Pos::Relative(x), Pos::Absolute(y)) => Context {
transform: transform_2d::matrix(1.0, 0.0, 0.0, 1.0, 0.0, y as f64).0,
..context
}.trans(x as f64, 0.0),
(Pos::Absolute(x), Pos::Absolute(y)) => Context {
transform: transform_2d::matrix(1.0, 0.0, 0.0, 1.0, x as f64, y as f64).0,
..context
},
};
let new_opacity = opacity * props.opacity;
draw_element(element, new_opacity, backend, maybe_character_cache, context);
}
Prim::Flow(direction, ref elements) => {
let mut context = context;
match direction {
Direction::Up | Direction::Down => {
let multi = if let Direction::Up = direction { 1.0 } else { -1.0 };
let mut half_prev_height = 0.0;
for element in elements.iter() {
let half_height = element.get_height() as f64 / 2.0;
let new_opacity = opacity * props.opacity;
draw_element(element, new_opacity, backend, maybe_character_cache, context);
let y_trans = half_height + half_prev_height;
context = context.trans(0.0, y_trans * multi);
half_prev_height = half_height;
}
},
Direction::Left | Direction::Right => {
let multi = if let Direction::Right = direction { 1.0 } else { -1.0 };
let mut half_prev_width = 0.0;
for element in elements.iter() {
let half_width = element.get_width() as f64 / 2.0;
let new_opacity = opacity * props.opacity;
draw_element(element, new_opacity, backend, maybe_character_cache, context);
let x_trans = half_width + half_prev_width;
context = context.trans(x_trans * multi, 0.0);
half_prev_width = half_width;
}
},
Direction::Out => {
for element in elements.iter() {
let new_opacity = opacity * props.opacity;
draw_element(element, new_opacity, backend, maybe_character_cache, context);
}
}
Direction::In => {
for element in elements.iter().rev() {
let new_opacity = opacity * props.opacity;
draw_element(element, new_opacity, backend, maybe_character_cache, context);
}
}
}
},
Prim::Collage(w, h, ref forms) => {
for form in forms.iter() {
let new_opacity = opacity * props.opacity;
form::draw_form(form, new_opacity, backend, maybe_character_cache, context);
}
},
Prim::Cleared(color, ref element) => {
backend.clear_color(color.to_fsa());
draw_element(element, opacity, backend, maybe_character_cache, context);
},
Prim::Spacer => {},
}
}