use {Color, Colorable, Point, Positionable, Rect, Scalar, Sizeable, Theme};
use graph;
use utils::{vec2_add, vec2_sub};
use widget::{self, Widget};
use widget::triangles::Triangle;
#[derive(Copy, Clone, Debug, WidgetCommon_)]
pub struct Line {
#[conrod(common_builder)]
pub common: widget::CommonBuilder,
pub start: Point,
pub end: Point,
pub style: Style,
pub should_centre_points: bool,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct State {
pub start: Point,
pub end: Point,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Style {
pub maybe_pattern: Option<Pattern>,
pub maybe_color: Option<Color>,
pub maybe_thickness: Option<Scalar>,
pub maybe_cap: Option<Cap>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Pattern {
Solid,
Dashed,
Dotted,
}
#[allow(dead_code)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Cap {
Flat,
Round,
}
impl Line {
pub fn styled(start: Point, end: Point, style: Style) -> Self {
Line {
start: start,
end: end,
common: widget::CommonBuilder::default(),
style: style,
should_centre_points: false,
}
}
pub fn new(start: Point, end: Point) -> Self {
Line::styled(start, end, Style::new())
}
pub fn abs(start: Point, end: Point) -> Self {
Line::abs_styled(start, end, Style::new())
}
pub fn abs_styled(start: Point, end: Point, style: Style) -> Self {
let (xy, dim) = Rect::from_corners(start, end).xy_dim();
Line::styled(start, end, style).wh(dim).xy(xy)
}
pub fn centred(start: Point, end: Point) -> Self {
Line::centred_styled(start, end, Style::new())
}
pub fn centred_styled(start: Point, end: Point, style: Style) -> Self {
let dim = Rect::from_corners(start, end).dim();
let mut line = Line::styled(start, end, style).wh(dim);
line.should_centre_points = true;
line
}
pub fn thickness(mut self, thickness: Scalar) -> Self {
self.style.set_thickness(thickness);
self
}
pub fn solid(mut self) -> Self {
self.style.set_pattern(Pattern::Solid);
self
}
pub fn dashed(mut self) -> Self {
self.style.set_pattern(Pattern::Dashed);
self
}
pub fn dotted(mut self) -> Self {
self.style.set_pattern(Pattern::Dotted);
self
}
}
impl Style {
pub fn new() -> Self {
Style {
maybe_pattern: None,
maybe_color: None,
maybe_thickness: None,
maybe_cap: None,
}
}
pub fn solid() -> Self {
Style::new().pattern(Pattern::Solid)
}
pub fn dashed() -> Self {
Style::new().pattern(Pattern::Dashed)
}
pub fn dotted() -> Self {
Style::new().pattern(Pattern::Dotted)
}
pub fn pattern(mut self, pattern: Pattern) -> Self {
self.set_pattern(pattern);
self
}
pub fn color(mut self, color: Color) -> Self {
self.set_color(color);
self
}
pub fn thickness(mut self, thickness: Scalar) -> Self {
self.set_thickness(thickness);
self
}
pub fn cap(mut self, cap: Cap) -> Self {
self.set_cap(cap);
self
}
pub fn set_pattern(&mut self, pattern: Pattern) {
self.maybe_pattern = Some(pattern);
}
pub fn set_color(&mut self, color: Color) {
self.maybe_color = Some(color);
}
pub fn set_thickness(&mut self, thickness: Scalar) {
self.maybe_thickness = Some(thickness);
}
pub fn set_cap(&mut self, cap: Cap) {
self.maybe_cap = Some(cap);
}
pub fn get_pattern(&self, theme: &Theme) -> Pattern {
const DEFAULT_PATTERN: Pattern = Pattern::Solid;
self.maybe_pattern.or_else(|| theme.widget_style::<Style>().map(|default| {
default.style.maybe_pattern.unwrap_or(DEFAULT_PATTERN)
})).unwrap_or(DEFAULT_PATTERN)
}
pub fn get_color(&self, theme: &Theme) -> Color {
self.maybe_color.or_else(|| theme.widget_style::<Style>().map(|default| {
default.style.maybe_color.unwrap_or(theme.shape_color)
})).unwrap_or(theme.shape_color)
}
pub fn get_thickness(&self, theme: &Theme) -> Scalar {
const DEFAULT_THICKNESS: Scalar = 1.0;
self.maybe_thickness.or_else(|| theme.widget_style::<Style>().map(|default| {
default.style.maybe_thickness.unwrap_or(DEFAULT_THICKNESS)
})).unwrap_or(DEFAULT_THICKNESS)
}
pub fn get_cap(&self, theme: &Theme) -> Cap {
const DEFAULT_CAP: Cap = Cap::Flat;
self.maybe_cap.or_else(|| theme.widget_style::<Style>().map(|default| {
default.style.maybe_cap.unwrap_or(DEFAULT_CAP)
})).unwrap_or(DEFAULT_CAP)
}
}
impl Widget for Line {
type State = State;
type Style = Style;
type Event = ();
fn init_state(&self, _: widget::id::Generator) -> Self::State {
State {
start: [0.0, 0.0],
end: [0.0, 0.0],
}
}
fn style(&self) -> Self::Style {
self.style.clone()
}
fn is_over(&self) -> widget::IsOverFn {
is_over_widget
}
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { rect, state, .. } = args;
let Line { mut start, mut end, should_centre_points, .. } = self;
if should_centre_points {
let original = Rect::from_corners(start, end).xy();
let xy = rect.xy();
let difference = vec2_sub(xy, original);
start = vec2_add(start, difference);
end = vec2_add(end, difference);
}
if state.start != start {
state.update(|state| state.start = start);
}
if state.end != end {
state.update(|state| state.end = end);
}
}
}
impl Colorable for Line {
fn color(mut self, color: Color) -> Self {
self.style.maybe_color = Some(color);
self
}
}
pub fn rect_corners(a: Point, b: Point, half_thickness: Scalar) -> [Point; 4] {
let direction = [b[0] - a[0], b[1] - a[1]];
let mag = (direction[0] * direction[0] + direction[1] * direction[1]).sqrt();
let unit = [direction[0] / mag, direction[1] / mag];
let normal = [-unit[1], unit[0]];
let n = [normal[0] * half_thickness, normal[1] * half_thickness];
let r1 = [a[0] + n[0], a[1] + n[1]];
let r2 = [a[0] - n[0], a[1] - n[1]];
let r3 = [b[0] + n[0], b[1] + n[1]];
let r4 = [b[0] - n[0], b[1] - n[1]];
[r1, r2, r3, r4]
}
pub fn triangles(a: Point, b: Point, half_thickness: Scalar) -> [Triangle<Point>; 2] {
let r = rect_corners(a, b, half_thickness);
let t1 = Triangle([r[0], r[3], r[1]]);
let t2 = Triangle([r[0], r[3], r[2]]);
[t1, t2]
}
pub fn is_over(a: Point, b: Point, thickness: Scalar, point: Point) -> bool {
let half_thickness = thickness * 0.5;
let tris = triangles(a, b, half_thickness);
widget::triangles::is_over(tris.iter().cloned(), point)
}
pub fn is_over_widget(widget: &graph::Container, point: Point, theme: &Theme) -> widget::IsOver {
widget
.unique_widget_state::<Line>()
.map(|widget| {
let thickness = widget.style.get_thickness(theme);
let (a, b) = (widget.state.start, widget.state.end);
is_over(a, b, thickness, point)
})
.unwrap_or_else(|| widget.rect.is_over(point))
.into()
}