use color::{Color, Gradient};
use element::{self, Element, new_element};
use graphics::{self, Context, Graphics, Transformed};
use graphics::character::CharacterCache;
use std::f64::consts::PI;
use std::path::PathBuf;
use text::Text;
use transform_2d::{self, Transform2D};
#[derive(Clone, Debug)]
pub struct Form {
pub theta: f64,
pub scale: f64,
pub x: f64,
pub y: f64,
pub alpha: f32,
pub form: BasicForm,
}
#[derive(Clone, Debug)]
pub enum FillStyle {
Solid(Color),
Texture(PathBuf),
Grad(Gradient),
}
#[derive(Copy, Clone, Debug)]
pub enum LineCap {
Flat,
Round,
Padded,
}
#[derive(Copy, Clone, Debug)]
pub enum LineJoin {
Smooth,
Sharp(f64),
Clipped,
}
#[derive(Clone, Debug)]
pub struct LineStyle {
pub color: Color,
pub width: f64,
pub cap: LineCap,
pub join: LineJoin,
pub dashing: Vec<i64>,
pub dash_offset: i64,
}
impl LineStyle {
pub fn default() -> LineStyle {
LineStyle {
color: ::color::black(),
width: 1.0,
cap: LineCap::Flat,
join: LineJoin::Sharp(10.0),
dashing: Vec::new(),
dash_offset: 0,
}
}
#[inline]
pub fn width(self, w: f64) -> LineStyle {
LineStyle { width: w, ..self }
}
}
pub fn solid(color: Color) -> LineStyle {
LineStyle { color: color, ..LineStyle::default() }
}
pub fn dashed(color: Color) -> LineStyle {
LineStyle { color: color, dashing: vec![8, 4], ..LineStyle::default() }
}
pub fn dotted(color: Color) -> LineStyle {
LineStyle { color: color, dashing: vec![3, 3], ..LineStyle::default() }
}
#[derive(Clone, Debug)]
pub enum BasicForm {
PointPath(LineStyle, PointPath),
Shape(ShapeStyle, Shape),
OutlinedText(LineStyle, Text),
Text(Text),
Image(i32, i32, (i32, i32), PathBuf),
Element(Element),
Group(Transform2D, Vec<Form>),
}
#[derive(Clone, Debug)]
pub enum ShapeStyle {
Line(LineStyle),
Fill(FillStyle),
}
impl Form {
fn new(basic_form: BasicForm) -> Form {
Form {
theta: 0.0,
scale: 1.0,
x: 0.0,
y: 0.0,
alpha: 1.0,
form: basic_form,
}
}
#[inline]
pub fn shift(self, x: f64, y: f64) -> Form {
Form { x: self.x + x, y: self.y + y, ..self }
}
#[inline]
pub fn shift_x(self, x: f64) -> Form {
self.shift(x, 0.0)
}
#[inline]
pub fn shift_y(self, y: f64) -> Form {
self.shift(0.0, y)
}
#[inline]
pub fn scale(self, scale: f64) -> Form {
Form { scale: self.scale * scale, ..self }
}
#[inline]
pub fn rotate(self, theta: f64) -> Form {
Form { theta: self.theta + theta, ..self }
}
#[inline]
pub fn alpha(self, alpha: f32) -> Form {
Form { alpha: alpha, ..self }
}
}
pub fn to_form(element: Element) -> Form {
Form::new(BasicForm::Element(element))
}
pub fn group(forms: Vec<Form>) -> Form {
Form::new(BasicForm::Group(transform_2d::identity(), forms))
}
pub fn group_transform(matrix: Transform2D, forms: Vec<Form>) -> Form {
Form::new(BasicForm::Group(matrix, forms))
}
pub fn traced(style: LineStyle, path: PointPath) -> Form {
Form::new(BasicForm::PointPath(style, path))
}
pub fn line(style: LineStyle, x1: f64, y1: f64, x2: f64, y2: f64) -> Form {
traced(style, segment((x1, y1), (x2, y2)))
}
pub fn sprite(w: i32, h: i32, pos: (i32, i32), path: PathBuf) -> Form {
Form::new(BasicForm::Image(w, h, pos, path))
}
pub fn collage(w: i32, h: i32, forms: Vec<Form>) -> Element {
new_element(w, h, element::Prim::Collage(w, h, forms))
}
#[derive(Clone, Debug)]
pub struct PointPath(pub Vec<(f64, f64)>);
pub fn point_path(points: Vec<(f64, f64)>) -> PointPath {
PointPath(points)
}
pub fn segment(a: (f64, f64), b: (f64, f64)) -> PointPath {
PointPath(vec![a, b])
}
#[derive(Clone, Debug)]
pub struct Shape(pub Vec<(f64, f64)>);
impl Shape {
#[inline]
fn fill(self, style: FillStyle) -> Form {
Form::new(BasicForm::Shape(ShapeStyle::Fill(style), self))
}
#[inline]
pub fn filled(self, color: Color) -> Form {
self.fill(FillStyle::Solid(color))
}
#[inline]
pub fn textured(self, path: PathBuf) -> Form {
self.fill(FillStyle::Texture(path))
}
#[inline]
pub fn gradient(self, grad: Gradient) -> Form {
self.fill(FillStyle::Grad(grad))
}
#[inline]
pub fn outlined(self, style: LineStyle) -> Form {
Form::new(BasicForm::Shape(ShapeStyle::Line(style), self))
}
}
pub fn polygon(points: Vec<(f64, f64)>) -> Shape {
Shape(points)
}
pub fn rect(w: f64, h: f64) -> Shape {
let hw = w / 2.0;
let hh = h / 2.0;
Shape(vec![ (0.0-hw, 0.0-hh), (0.0-hw, hh), (hw, hh), (hw, 0.0-hh) ])
}
pub fn square(n: f64) -> Shape {
rect(n, n)
}
pub fn oval(w: f64, h: f64) -> Shape {
let n: usize = 50;
let t = 2.0 * PI / n as f64;
let hw = w / 2.0;
let hh = h / 2.0;
let f = |i: f64| (hw * (t*i).cos(), hh * (t*i).sin());
let points = (0..n-1).map(|i| f(i as f64)).collect();
Shape(points)
}
pub fn circle(r: f64) -> Shape {
let d = 2.0 * r;
oval(d, d)
}
pub fn ngon(n: usize, r: f64) -> Shape {
let t = 2.0 * PI / n as f64;
let f = |i: f64| (r * (t*i).cos(), r * (t*i).sin());
let points = (0..n).map(|i| f(i as f64)).collect();
Shape(points)
}
pub fn text(t: Text) -> Form {
Form::new(BasicForm::Text(t))
}
pub fn draw_form<'a, C: CharacterCache, G: Graphics<Texture=C::Texture>>(
form: &Form,
alpha: f32,
backend: &mut G,
maybe_character_cache: &mut Option<&mut C>,
context: Context,
) {
let Form { theta, scale, x, y, alpha, ref form } = *form;
let context = context.trans(x, y).scale(scale, scale).rot_rad(theta);
match *form {
BasicForm::PointPath(ref line_style, PointPath(ref points)) => {
let LineStyle { color, width, cap, join, ref dashing, dash_offset } = *line_style;
let color = convert_color(color, alpha);
let mut draw_line = |(x1, y1), (x2, y2)| {
if dashing.is_empty() {
let line = match cap {
LineCap::Flat => graphics::Line::new(color, width / 2.0),
LineCap::Round => graphics::Line::new_round(color, width / 2.0),
LineCap::Padded => unimplemented!(),
};
line.draw([x1, y1, x2, y2], &context.draw_state, context.transform, backend);
} else {
unimplemented!();
}
};
for window in points.windows(2) {
let (a, b) = (window[0], window[1]);
draw_line(a, b);
}
},
BasicForm::Shape(ref shape_style, Shape(ref points)) => {
match *shape_style {
ShapeStyle::Line(ref line_style) => {
let LineStyle { color, width, cap, join, ref dashing, dash_offset } = *line_style;
let color = convert_color(color, alpha);
let mut draw_line = |(x1, y1), (x2, y2)| {
let line = match cap {
LineCap::Flat => graphics::Line::new(color, width / 2.0),
LineCap::Round => graphics::Line::new_round(color, width / 2.0),
LineCap::Padded => unimplemented!(),
};
line.draw([x1, y1, x2, y2], &context.draw_state, context.transform, backend);
};
for window in points.windows(2) {
let (a, b) = (window[0], window[1]);
draw_line(a, b);
}
if points.len() > 2 {
draw_line(points[points.len()-1], points[0])
}
},
ShapeStyle::Fill(ref fill_style) => match *fill_style {
FillStyle::Solid(color) => {
let color = convert_color(color, alpha);
let polygon = graphics::Polygon::new(color);
let points: Vec<_> = points.iter().map(|&(x, y)| [x, y]).collect();
polygon.draw(&points[..], &context.draw_state, context.transform, backend);
},
FillStyle::Texture(ref path) => {
unimplemented!();
},
FillStyle::Grad(ref gradient) => {
unimplemented!();
},
},
}
},
BasicForm::OutlinedText(ref line_style, ref text) => {
unimplemented!();
},
BasicForm::Text(ref text) => {
let context = context.scale(1.0, -1.0);
if let Some(ref mut character_cache) = *maybe_character_cache {
use text::Style as TextStyle;
use text::Position as TextPosition;
use text::TextUnit;
let (total_width, max_height) = text.sequence.iter().fold((0.0, 0.0), |(w, h), unit| {
let TextUnit { ref string, ref style } = *unit;
let TextStyle { ref typeface, height, color, bold, italic, line, monospace } = *style;
let height = height.unwrap_or(16.0);
let new_total_width = w + character_cache.width(height as u32, &string);
let new_max_height = if height > h { height } else { h };
(new_total_width, new_max_height)
});
let x_offset = match text.position {
TextPosition::Center => -(total_width / 2.0).floor(),
TextPosition::ToLeft => -total_width.floor(),
TextPosition::ToRight => 0.0
};
let y_offset = (max_height / 3.0).floor(); let context = context.trans(x_offset, y_offset);
for unit in text.sequence.iter() {
let TextUnit { ref string, ref style } = *unit;
let TextStyle { ref typeface, height, color, bold, italic, line, monospace } = *style;
let height = height.unwrap_or(16.0).floor();
let color = convert_color(color, alpha);
graphics::text::Text::colored(color, height as u32)
.draw(&string[..], *character_cache, &context.draw_state, context.transform, backend);
}
}
},
BasicForm::Image(src_x, src_y, (w, h), ref path) => {
unimplemented!();
},
BasicForm::Group(ref group_transform, ref forms) => {
let Transform2D(matrix) = Transform2D(context.transform.clone())
.multiply(group_transform.clone());
let context = Context { transform: matrix, ..context };
for form in forms.iter() {
draw_form(form, alpha, backend, maybe_character_cache, context);
}
},
BasicForm::Element(ref element) =>
element::draw_element(element, alpha, backend, maybe_character_cache, context),
}
}
fn convert_color(color: Color, alpha: f32) -> [f32; 4] {
use color::hsl_to_rgb;
let ((r, g, b), a) = match color {
Color::Hsla(h, s, l, a) => (hsl_to_rgb(h, s, l), a),
Color::Rgba(r, g, b, a) => ((r, g, b), a),
};
[r, g, b, a * alpha]
}