From 347e091f1ae8d7c6c721937de2cddbac58cff82b Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 5 Nov 2015 19:13:52 +1100 Subject: [PATCH 001/124] Add a basic Line widget --- src/lib.rs | 2 + src/position.rs | 10 +++ src/theme.rs | 3 + src/widget/line.rs | 197 +++++++++++++++++++++++++++++++++++++++++++++ src/widget/mod.rs | 1 + 5 files changed, 213 insertions(+) create mode 100644 src/widget/line.rs diff --git a/src/lib.rs b/src/lib.rs index e1f0327ab..bde540c8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,7 @@ pub use widget::drop_down_list::DropDownList; pub use widget::envelope_editor::EnvelopeEditor; pub use widget::envelope_editor::EnvelopePoint; pub use widget::label::Label; +pub use widget::line::Line; pub use widget::matrix::Matrix as WidgetMatrix; pub use widget::number_dialer::NumberDialer; pub use widget::slider::Slider; @@ -40,6 +41,7 @@ pub use widget::canvas::Style as CanvasStyle; pub use widget::drop_down_list::Style as DropDownListStyle; pub use widget::envelope_editor::Style as EnvelopeEditorStyle; pub use widget::label::Style as LabelStyle; +pub use widget::line::Style as LineStyle; pub use widget::number_dialer::Style as NumberDialerStyle; pub use widget::slider::Style as SliderStyle; pub use widget::tabs::Style as TabsStyle; diff --git a/src/position.rs b/src/position.rs index 05c10c939..1eda01e3e 100644 --- a/src/position.rs +++ b/src/position.rs @@ -771,6 +771,16 @@ impl Rect { } } + /// Construct a Rect from the coordinates of two points. + pub fn from_corners(a: Point, b: Point) -> Rect { + let (left, right) = if a[0] < b[0] { (a[0], b[0]) } else { (b[0], a[0]) }; + let (bottom, top) = if a[1] < b[1] { (a[1], b[1]) } else { (b[1], a[1]) }; + Rect { + x: Range { start: left, end: right }, + y: Range { start: bottom, end: top }, + } + } + /// The Rect representing the area in which two Rects overlap. pub fn overlap(self, other: Rect) -> Option { self.x.overlap(other.x).and_then(|x| self.y.overlap(other.y).map(|y| Rect { x: x, y: y })) diff --git a/src/theme.rs b/src/theme.rs index 54023dc55..43a685611 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -48,6 +48,8 @@ pub struct Theme { pub maybe_drop_down_list: Option>, /// Optional style defaults for an EnvelopeEditor. pub maybe_envelope_editor: Option>, + /// Optional style defaults for a Line. + pub maybe_line: Option>, /// Optional style defaults for a Matrix. pub maybe_matrix: Option>, /// Optional style defaults for a NumberDialer. @@ -132,6 +134,7 @@ impl Theme { maybe_canvas: None, maybe_drop_down_list: None, maybe_envelope_editor: None, + maybe_line: None, maybe_matrix: None, maybe_number_dialer: None, maybe_slider: None, diff --git a/src/widget/line.rs b/src/widget/line.rs new file mode 100644 index 000000000..8700ebf47 --- /dev/null +++ b/src/widget/line.rs @@ -0,0 +1,197 @@ + +use Scalar; +use color::{Color, Colorable}; +use elmesque::Element; +use graphics::character::CharacterCache; +use position::{Point, Rect, Sizeable}; +use theme::Theme; +use widget::{self, Widget}; + + +/// A simple, non-interactive widget for drawing a single straight Line. +#[derive(Copy, Clone, Debug)] +pub struct Line { + /// The start of the line. + pub start: Point, + /// The end of the line. + pub end: Point, + /// Data necessary and common for all widget types. + pub common: widget::CommonBuilder, + /// Unique styling. + pub style: Style, +} + +/// Unique state for the Line widget. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct State { + /// The start of the line. + start: Point, + /// The end of the line. + end: Point, +} + +/// Unique styling for a Line widget. +#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] +pub struct Style { + /// The patter for the line. + pub maybe_pattern: Option, + /// Color of the Button's pressable area. + pub maybe_color: Option, + /// The thickness of the line. + pub maybe_thickness: Option, +} + +/// The pattern used to draw the line. +#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] +pub enum Pattern { + Solid, + Dashed, + Dotted, +} + + +impl Line { + + /// Construct a new Line widget builder. + pub fn new(start: Point, end: Point) -> Self { + let dim = Rect::from_corners(start, end).dim(); + Line { + start: start, + end: end, + common: widget::CommonBuilder::new(), + style: Style::new(), + }.dim(dim) + } + + /// The thickness or width of the Line. + /// + /// Use this instead of `Positionable::width` for the thickness of the `Line`, as `width` and + /// `height` refer to the dimensions of the bounding rectangle. + pub fn thickness(mut self, thickness: Scalar) -> Self { + self.style.maybe_thickness = Some(thickness); + self + } + + /// Make a solid line. + pub fn solid(mut self) -> Self { + self.style.maybe_pattern = Some(Pattern::Solid); + self + } + + /// Make a line with a Dashed pattern. + pub fn dashed(mut self) -> Self { + self.style.maybe_pattern = Some(Pattern::Dashed); + self + } + + /// Make a line with a Dotted pattern. + pub fn dotted(mut self) -> Self { + self.style.maybe_pattern = Some(Pattern::Dotted); + self + } + +} + + +impl Style { + + /// Constructor for a default Line Style. + pub fn new() -> Self { + Style { + maybe_pattern: None, + maybe_color: None, + maybe_thickness: None, + } + } + + /// The Pattern for the Line. + pub fn pattern(&self, theme: &Theme) -> Pattern { + const DEFAULT_PATTERN: Pattern = Pattern::Solid; + self.maybe_pattern.or_else(|| theme.maybe_line.as_ref().map(|default| { + default.style.maybe_pattern.unwrap_or(DEFAULT_PATTERN) + })).unwrap_or(DEFAULT_PATTERN) + } + + /// The Color for the Line. + pub fn color(&self, theme: &Theme) -> Color { + self.maybe_color.or_else(|| theme.maybe_line.as_ref().map(|default| { + default.style.maybe_color.unwrap_or(theme.shape_color) + })).unwrap_or(theme.shape_color) + } + + /// The width or thickness of the Line. + pub fn thickness(&self, theme: &Theme) -> Scalar { + const DEFAULT_THICKNESS: Scalar = 1.0; + self.maybe_thickness.or_else(|| theme.maybe_line.as_ref().map(|default| { + default.style.maybe_thickness.unwrap_or(DEFAULT_THICKNESS) + })).unwrap_or(DEFAULT_THICKNESS) + } + +} + + +impl Widget for Line { + type State = State; + type Style = Style; + + fn common(&self) -> &widget::CommonBuilder { + &self.common + } + + fn common_mut(&mut self) -> &mut widget::CommonBuilder { + &mut self.common + } + + fn unique_kind(&self) -> &'static str { + "Button" + } + + fn init_state(&self) -> State { + State { + start: [0.0, 0.0], + end: [0.0, 0.0], + } + } + + fn style(&self) -> Style { + self.style.clone() + } + + /// Update the state of the Line. + fn update(self, args: widget::UpdateArgs) { + let widget::UpdateArgs { state, .. } = args; + let Line { start, end, .. } = self; + + if state.view().start != start { + state.update(|state| state.start = start); + } + + if state.view().end != end { + state.update(|state| state.end = end); + } + } + + /// Construct an Element for the Line. + fn draw(args: widget::DrawArgs) -> Element { + use elmesque::form::{collage, segment, solid, traced}; + let widget::DrawArgs { rect, state, style, theme, .. } = args; + let (x, y, w, h) = rect.x_y_w_h(); + let color = style.color(theme); + let thickness = style.thickness(theme); + let a = (state.start[0], state.start[1]); + let b = (state.end[0], state.end[1]); + let form = traced(solid(color).width(thickness), segment(a, b)).shift(x, y); + collage(w as i32, h as i32, vec![form]) + } + +} + + +impl Colorable for Line { + fn color(mut self, color: Color) -> Self { + self.style.maybe_color = Some(color); + self + } +} + + diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 58b7488a8..620706016 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -23,6 +23,7 @@ pub mod canvas; pub mod drop_down_list; pub mod envelope_editor; pub mod label; +pub mod line; pub mod matrix; pub mod number_dialer; pub mod slider; From 84d946bc5cb30fc02cc038dbda9af407f131289b Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 5 Nov 2015 19:58:34 +1100 Subject: [PATCH 002/124] Add basic Rectangle widget. Still need to make PointPath widget before completing Outline style variant --- src/widget/button.rs | 2 +- src/widget/line.rs | 12 ++-- src/widget/mod.rs | 1 + src/widget/rectangle.rs | 128 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 src/widget/rectangle.rs diff --git a/src/widget/button.rs b/src/widget/button.rs index 0d1a73e8d..db920a5da 100644 --- a/src/widget/button.rs +++ b/src/widget/button.rs @@ -183,7 +183,7 @@ impl<'a, F> Widget for Button<'a, F> where F: FnMut() { let xy = rect.xy(); let dim = rect.dim(); - // Retrieve the styling for the Element.. + // Retrieve the styling for the Element. let color = state.color(style.color(theme)); let frame = style.frame(theme); let frame_color = style.frame_color(theme); diff --git a/src/widget/line.rs b/src/widget/line.rs index 8700ebf47..a81060b24 100644 --- a/src/widget/line.rs +++ b/src/widget/line.rs @@ -105,7 +105,7 @@ impl Style { } /// The Pattern for the Line. - pub fn pattern(&self, theme: &Theme) -> Pattern { + pub fn get_pattern(&self, theme: &Theme) -> Pattern { const DEFAULT_PATTERN: Pattern = Pattern::Solid; self.maybe_pattern.or_else(|| theme.maybe_line.as_ref().map(|default| { default.style.maybe_pattern.unwrap_or(DEFAULT_PATTERN) @@ -113,14 +113,14 @@ impl Style { } /// The Color for the Line. - pub fn color(&self, theme: &Theme) -> Color { + pub fn get_color(&self, theme: &Theme) -> Color { self.maybe_color.or_else(|| theme.maybe_line.as_ref().map(|default| { default.style.maybe_color.unwrap_or(theme.shape_color) })).unwrap_or(theme.shape_color) } /// The width or thickness of the Line. - pub fn thickness(&self, theme: &Theme) -> Scalar { + pub fn get_thickness(&self, theme: &Theme) -> Scalar { const DEFAULT_THICKNESS: Scalar = 1.0; self.maybe_thickness.or_else(|| theme.maybe_line.as_ref().map(|default| { default.style.maybe_thickness.unwrap_or(DEFAULT_THICKNESS) @@ -143,7 +143,7 @@ impl Widget for Line { } fn unique_kind(&self) -> &'static str { - "Button" + "Line" } fn init_state(&self) -> State { @@ -176,8 +176,8 @@ impl Widget for Line { use elmesque::form::{collage, segment, solid, traced}; let widget::DrawArgs { rect, state, style, theme, .. } = args; let (x, y, w, h) = rect.x_y_w_h(); - let color = style.color(theme); - let thickness = style.thickness(theme); + let color = style.get_color(theme); + let thickness = style.get_thickness(theme); let a = (state.start[0], state.start[1]); let b = (state.end[0], state.end[1]); let form = traced(solid(color).width(thickness), segment(a, b)).shift(x, y); diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 620706016..c90e2847b 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -26,6 +26,7 @@ pub mod label; pub mod line; pub mod matrix; pub mod number_dialer; +pub mod rectangle; pub mod slider; pub mod split; pub mod tabs; diff --git a/src/widget/rectangle.rs b/src/widget/rectangle.rs new file mode 100644 index 000000000..2d79e3b81 --- /dev/null +++ b/src/widget/rectangle.rs @@ -0,0 +1,128 @@ + +use Scalar; +use color::{Color, Colorable}; +use elmesque::Element; +use frame::Frameable; +use graphics::character::CharacterCache; +use label::{FontSize, Labelable}; +use mouse::Mouse; +use position::Positionable; +use theme::Theme; +use ui::GlyphCache; +use widget::{self, Widget}; + + +/// A basic, non-interactive rectangle shape widget. +#[derive(Clone, Debug)] +pub struct Rectangle { + common: widget::CommonBuilder, + style: Style, +} + +/// Styling for the Rectangle. +#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] +pub enum Style { + /// The outline of the rectangle with this style. + Outline(super::line::Style), + /// A rectangle filled with this color. + Filled(Option), +} + +impl Style { + + /// The default Rectangle Style. + pub fn new() -> Self { + Style::Filled(None) + } + + /// Get the color of the Rectangle. + pub fn get_color(&self, theme: &Theme) -> Color { + match *self { + Style::Filled(maybe_color) => maybe_color.unwrap_or(theme.shape_color), + Style::Outline(style) => style.get_color(theme), + } + } + +} + +impl Rectangle { + + /// Create a new rectangle builder. + pub fn new() -> Self { + Rectangle { + common: widget::CommonBuilder::new(), + style: Style::new(), + } + } + + /// Build an outlined rectangle rather than a filled one. + pub fn outline(mut self, mut line_style: super::line::Style) -> Self { + if line_style.maybe_color.is_none() { + line_style.maybe_color = match self.style { + Style::Outline(prev_style) => prev_style.maybe_color, + Style::Filled(maybe_color) => maybe_color, + }; + } + self.style = Style::Outline(line_style); + self + } + +} + + +impl Widget for Rectangle { + type State = (); + type Style = Style; + + fn common(&self) -> &widget::CommonBuilder { + &self.common + } + + fn common_mut(&mut self) -> &mut widget::CommonBuilder { + &mut self.common + } + + fn unique_kind(&self) -> &'static str { + "Rectangle" + } + + fn init_state(&self) -> () { + () + } + + fn style(&self) -> Style { + self.style.clone() + } + + /// Update the state of the Rectangle. + fn update(self, args: widget::UpdateArgs) { + // No unique state to update for the boring ol' Rectangle! + } + + /// Construct an Element for the Line. + fn draw(args: widget::DrawArgs) -> Element { + use elmesque::form::{self, collage, segment, solid, traced}; + let widget::DrawArgs { rect, state, style, theme, .. } = args; + let (x, y, w, h) = rect.x_y_w_h(); + + let color = style.get_color(theme); + match *style { + Style::Filled(_) => { + let form = form::rect(w, h).filled(color).shift(x, y); + collage(w as i32, h as i32, vec![form]) + }, + Style::Outline(line_style) => { + let thickness = line_style.get_thickness(theme); + let a = (rect.x.start, rect.y.end); + let b = (rect.x.end, rect.y.end); + let c = (rect.x.end, rect.y.start); + let d = (rect.x.start, rect.y.start); + // TODO: Use PointPath + ::elmesque::element::empty() + }, + } + } + + +} + From 5dc6cb782c3cf91b80521a3bb7d9d539bd06bdf8 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 8 Nov 2015 20:03:32 +1100 Subject: [PATCH 003/124] Added rectangle, line, point_path, framed_rectangle, oval, circle and label primitive widgets --- src/widget/primitive/label.rs | 149 +++++++++ src/widget/primitive/line.rs | 298 ++++++++++++++++++ src/widget/primitive/mod.rs | 9 + src/widget/primitive/point_path.rs | 286 +++++++++++++++++ src/widget/primitive/shape/circle.rs | 45 +++ .../primitive/shape/framed_rectangle.rs | 148 +++++++++ src/widget/primitive/shape/mod.rs | 68 ++++ src/widget/primitive/shape/oval.rs | 152 +++++++++ src/widget/primitive/shape/polygon.rs | 294 +++++++++++++++++ src/widget/primitive/shape/rectangle.rs | 146 +++++++++ 10 files changed, 1595 insertions(+) create mode 100644 src/widget/primitive/label.rs create mode 100644 src/widget/primitive/line.rs create mode 100644 src/widget/primitive/mod.rs create mode 100644 src/widget/primitive/point_path.rs create mode 100644 src/widget/primitive/shape/circle.rs create mode 100644 src/widget/primitive/shape/framed_rectangle.rs create mode 100644 src/widget/primitive/shape/mod.rs create mode 100644 src/widget/primitive/shape/oval.rs create mode 100644 src/widget/primitive/shape/polygon.rs create mode 100644 src/widget/primitive/shape/rectangle.rs diff --git a/src/widget/primitive/label.rs b/src/widget/primitive/label.rs new file mode 100644 index 000000000..22abc004b --- /dev/null +++ b/src/widget/primitive/label.rs @@ -0,0 +1,149 @@ + +use Scalar; +use color::{Color, Colorable}; +use elmesque::Element; +use graphics::character::CharacterCache; +use label::FontSize; +use theme::Theme; +use ui::GlyphCache; +use widget::{self, Widget}; + + +/// Displays some given text centred within a rectangle. +#[derive(Clone, Debug)] +pub struct Label<'a> { + /// Data necessary and common for all widget builder types. + pub common: widget::CommonBuilder, + /// The text to be drawn by the **Label**. + pub text: &'a str, + /// The unique styling for the **Label**. + pub style: Style, +} + +/// The styling for a Label's renderable Element. +#[allow(missing_docs, missing_copy_implementations)] +#[derive(Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] +pub struct Style { + /// The font size for the label. + pub maybe_font_size: Option, + /// The color of the label. + pub maybe_color: Option, +} + +/// The state to be stored between updates for the Label. +#[derive(Clone, Debug, PartialEq)] +pub struct State { + string: String, +} + + +impl<'a> Label<'a> { + + /// Construct a new Label widget. + pub fn new(text: &'a str) -> Label<'a> { + Label { + common: widget::CommonBuilder::new(), + text: text, + style: Style::new(), + } + } + + /// Set the font size for the label. + #[inline] + pub fn font_size(mut self, size: FontSize) -> Label<'a> { + self.style.maybe_font_size = Some(size); + self + } + +} + + +impl<'a> Widget for Label<'a> { + type State = State; + type Style = Style; + + fn common(&self) -> &widget::CommonBuilder { + &self.common + } + + fn common_mut(&mut self) -> &mut widget::CommonBuilder { + &mut self.common + } + + fn unique_kind(&self) -> &'static str { + "Label" + } + + fn init_state(&self) -> State { + State { + string: String::new(), + } + } + + fn style(&self) -> Style { + self.style.clone() + } + + fn default_width(&self, theme: &Theme, glyph_cache: &GlyphCache) -> Scalar { + glyph_cache.width(self.style.font_size(theme), self.text) + } + + fn default_height(&self, theme: &Theme) -> Scalar { + self.style.font_size(theme) as Scalar + } + + /// Update the state of the Label. + fn update(self, args: widget::UpdateArgs) { + let widget::UpdateArgs { state, .. } = args; + if &state.view().string[..] != self.text { + state.update(|state| state.string = self.text.to_owned()); + } + } + + /// Construct an Element for the Label. + fn draw(args: widget::DrawArgs) -> Element { + use elmesque::form::{text, collage}; + use elmesque::text::Text; + let widget::DrawArgs { rect, state: &State { ref string }, style, theme, .. } = args; + let size = style.font_size(theme); + let color = style.color(theme); + let (x, y, w, h) = rect.x_y_w_h(); + let form = text(Text::from_string(string.clone()) + .color(color) + .height(size as f64)).shift(x.floor(), y.floor()); + collage(w as i32, h as i32, vec![form]) + } + +} + + +impl Style { + + /// Construct the default Style. + pub fn new() -> Style { + Style { + maybe_color: None, + maybe_font_size: None, + } + } + + /// Get the Color for an Element. + pub fn color(&self, theme: &Theme) -> Color { + self.maybe_color.unwrap_or(theme.label_color) + } + + /// Get the label font size for an Element. + pub fn font_size(&self, theme: &Theme) -> FontSize { + self.maybe_font_size.unwrap_or(theme.font_size_medium) + } + +} + + +impl<'a> Colorable for Label<'a> { + fn color(mut self, color: Color) -> Self { + self.style.maybe_color = Some(color); + self + } +} + diff --git a/src/widget/primitive/line.rs b/src/widget/primitive/line.rs new file mode 100644 index 000000000..591f7bb07 --- /dev/null +++ b/src/widget/primitive/line.rs @@ -0,0 +1,298 @@ + +use Scalar; +use color::{Color, Colorable}; +use elmesque::Element; +use graphics::character::CharacterCache; +use position::{Point, Positionable, Rect, Sizeable}; +use theme::Theme; +use vecmath::{vec2_add, vec2_sub}; +use widget::{self, Widget}; + + +/// A simple, non-interactive widget for drawing a single straight Line. +#[derive(Copy, Clone, Debug)] +pub struct Line { + /// The start of the line. + pub start: Point, + /// The end of the line. + pub end: Point, + /// Data necessary and common for all widget builder types. + pub common: widget::CommonBuilder, + /// Unique styling. + pub style: Style, + /// Whether or not the line should be automatically centred to the widget position. + pub should_centre_points: bool, +} + +/// Unique state for the Line widget. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct State { + /// The start of the line. + start: Point, + /// The end of the line. + end: Point, +} + +/// Unique styling for a Line widget. +#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] +pub struct Style { + /// The patter for the line. + pub maybe_pattern: Option, + /// Color of the Button's pressable area. + pub maybe_color: Option, + /// The thickness of the line. + pub maybe_thickness: Option, +} + +/// The pattern used to draw the line. +#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] +pub enum Pattern { + Solid, + Dashed, + Dotted, +} + + +impl Line { + + /// Build a new **Line** widget with the given style. + pub fn styled(start: Point, end: Point, style: Style) -> Self { + Line { + start: start, + end: end, + common: widget::CommonBuilder::new(), + style: style, + should_centre_points: false, + } + } + + /// Build a new default **Line** widget. + pub fn new(start: Point, end: Point) -> Self { + Line::styled(start, end, Style::new()) + } + + /// Build a new **Line** whose bounding box is fit to the absolute co-ordinates of the line + /// points. + /// + /// If you would rather centre the start and end to the middle of the bounding box, use + /// [**Line::centred**](./struct.Line#method.centred) instead. + pub fn abs(start: Point, end: Point) -> Self { + Line::abs_styled(start, end, Style::new()) + } + + /// The same as [**Line::abs**](./struct.Line#method.abs) but with the given style. + 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).dim(dim).point(xy) + } + + /// Build a new **Line** and shift the location of the start and end points so that the centre + /// of their bounding rectangle lies at the position determined by the layout for the **Line** + /// widget. + /// + /// This is useful if your points simply describe the line's angle and magnitude, and you want + /// to position them using conrod's auto-layout or `Positionable` methods. + /// + /// If you would rather centre the bounding box to the points, use + /// [**Line::abs**](./struct.Line#method.abs) instead. + pub fn centred(start: Point, end: Point) -> Self { + Line::centred_styled(start, end, Style::new()) + } + + /// The same as [**Line::centred**](./struct.Line#method.centred) but with the given style. + 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).dim(dim); + line.should_centre_points = true; + line + } + + /// The thickness or width of the Line. + /// + /// Use this instead of `Positionable::width` for the thickness of the `Line`, as `width` and + /// `height` refer to the dimensions of the bounding rectangle. + pub fn thickness(mut self, thickness: Scalar) -> Self { + self.style.set_thickness(thickness); + self + } + + /// Make a solid line. + pub fn solid(mut self) -> Self { + self.style.set_pattern(Pattern::Solid); + self + } + + /// Make a line with a Dashed pattern. + pub fn dashed(mut self) -> Self { + self.style.set_pattern(Pattern::Dashed); + self + } + + /// Make a line with a Dotted pattern. + pub fn dotted(mut self) -> Self { + self.style.set_pattern(Pattern::Dotted); + self + } + +} + + +impl Style { + + /// Constructor for a default Line Style. + pub fn new() -> Self { + Style { + maybe_pattern: None, + maybe_color: None, + maybe_thickness: None, + } + } + + /// Make a solid line. + pub fn solid() -> Self { + Style::new().pattern(Pattern::Solid) + } + + /// Make a line with a Dashed pattern. + pub fn dashed() -> Self { + Style::new().pattern(Pattern::Dashed) + } + + /// Make a line with a Dotted pattern. + pub fn dotted() -> Self { + Style::new().pattern(Pattern::Dotted) + } + + /// The style with some given pattern. + pub fn pattern(mut self, pattern: Pattern) -> Self { + self.set_pattern(pattern); + self + } + + /// The style with some given color. + pub fn color(mut self, color: Color) -> Self { + self.set_color(color); + self + } + + /// The style with some given thickness. + pub fn thickness(mut self, thickness: Scalar) -> Self { + self.set_thickness(thickness); + self + } + + /// Set the pattern for the line. + pub fn set_pattern(&mut self, pattern: Pattern) { + self.maybe_pattern = Some(pattern); + } + + /// Set the color for the line. + pub fn set_color(&mut self, color: Color) { + self.maybe_color = Some(color); + } + + /// Set the thickness for the line. + pub fn set_thickness(&mut self, thickness: Scalar) { + self.maybe_thickness = Some(thickness); + } + + /// The Pattern for the Line. + pub fn get_pattern(&self, theme: &Theme) -> Pattern { + const DEFAULT_PATTERN: Pattern = Pattern::Solid; + self.maybe_pattern.or_else(|| theme.maybe_line.as_ref().map(|default| { + default.style.maybe_pattern.unwrap_or(DEFAULT_PATTERN) + })).unwrap_or(DEFAULT_PATTERN) + } + + /// The Color for the Line. + pub fn get_color(&self, theme: &Theme) -> Color { + self.maybe_color.or_else(|| theme.maybe_line.as_ref().map(|default| { + default.style.maybe_color.unwrap_or(theme.shape_color) + })).unwrap_or(theme.shape_color) + } + + /// The width or thickness of the Line. + pub fn get_thickness(&self, theme: &Theme) -> Scalar { + const DEFAULT_THICKNESS: Scalar = 1.0; + self.maybe_thickness.or_else(|| theme.maybe_line.as_ref().map(|default| { + default.style.maybe_thickness.unwrap_or(DEFAULT_THICKNESS) + })).unwrap_or(DEFAULT_THICKNESS) + } + +} + + +impl Widget for Line { + type State = State; + type Style = Style; + + fn common(&self) -> &widget::CommonBuilder { + &self.common + } + + fn common_mut(&mut self) -> &mut widget::CommonBuilder { + &mut self.common + } + + fn unique_kind(&self) -> &'static str { + "Line" + } + + fn init_state(&self) -> State { + State { + start: [0.0, 0.0], + end: [0.0, 0.0], + } + } + + fn style(&self) -> Style { + self.style.clone() + } + + /// Update the state of the Line. + fn update(self, args: widget::UpdateArgs) { + let widget::UpdateArgs { rect, state, .. } = args; + let Line { mut start, mut end, should_centre_points, .. } = self; + + // Check whether or not we need to shift the line to the xy position. + 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.view().start != start { + state.update(|state| state.start = start); + } + + if state.view().end != end { + state.update(|state| state.end = end); + } + } + + /// Construct an Element for the Line. + fn draw(args: widget::DrawArgs) -> Element { + use elmesque::form::{collage, segment, solid, traced}; + let widget::DrawArgs { rect, state, style, theme, .. } = args; + let (w, h) = rect.w_h(); + let color = style.get_color(theme); + let thickness = style.get_thickness(theme); + let a = (state.start[0], state.start[1]); + let b = (state.end[0], state.end[1]); + let form = traced(solid(color).width(thickness), segment(a, b)); + collage(w as i32, h as i32, vec![form]) + } + +} + + +impl Colorable for Line { + fn color(mut self, color: Color) -> Self { + self.style.maybe_color = Some(color); + self + } +} + + diff --git a/src/widget/primitive/mod.rs b/src/widget/primitive/mod.rs new file mode 100644 index 000000000..625e5943e --- /dev/null +++ b/src/widget/primitive/mod.rs @@ -0,0 +1,9 @@ + + +pub mod label; +pub mod line; +pub mod point_path; +pub mod shape; + + + diff --git a/src/widget/primitive/point_path.rs b/src/widget/primitive/point_path.rs new file mode 100644 index 000000000..961ffc075 --- /dev/null +++ b/src/widget/primitive/point_path.rs @@ -0,0 +1,286 @@ +use CharacterCache; +use elmesque::Element; +use position::{Point, Positionable, Range, Rect, Sizeable}; +use super::line::Style as LineStyle; +use theme::Theme; +use vecmath::{vec2_add, vec2_sub}; +use widget::{self, Widget}; + + +/// A simple, non-interactive widget for drawing a series of lines and/or points. +#[derive(Clone, Debug)] +pub struct PointPath { + /// Some iterator yielding a series of Points. + pub points: I, + /// Data necessary and common for all widget builder types. + pub common: widget::CommonBuilder, + /// Unique styling for the PointPath. + pub style: Style, + /// Whether or not the points should be automatically centred to the widget position. + pub maybe_shift_to_centre_from: Option, +} + +/// State that is unique to the PointPath. +#[derive(Clone, Debug, PartialEq)] +pub struct State { + /// An owned version of the list of points. + points: Vec, +} + +/// Styling that is unique to the PointPath. +#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] +pub struct Style { + /// Whether or not to draw Lines, Points or Both. + pub maybe_kind: Option, +} + +/// Whether or not to draw Lines, Points or Both. +#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] +pub enum StyleKind { + /// Draw only the lines between the points. + Lines(LineStyle), + /// Draw only the points. + Points, + /// Draw both the lines and the points. + Both(LineStyle), +} + +// pub struct PointStyle { +// shape: +// } + + +/// Find the bounding rect for the given series of points. +fn bounding_box_for_points(mut points: I) -> Rect + where I: Iterator, +{ + points.next().map(|first| { + let start_rect = Rect { + x: Range { start: first[0], end: first[0] }, + y: Range { start: first[1], end: first[1] }, + }; + points.fold(start_rect, Rect::stretch_to_point) + }).unwrap_or_else(|| Rect::from_xy_dim([0.0, 0.0], [0.0, 0.0])) +} + + +impl PointPath { + + /// The same as [**PointPath::new**](./struct.PointPath#method.new) but with th given style. + pub fn styled(points: I, style: Style) -> Self { + PointPath { + points: points, + common: widget::CommonBuilder::new(), + style: style, + maybe_shift_to_centre_from: None, + } + } + + /// Build a new default PointPath widget. + /// + /// It is recommended that you also see the `abs` and `centred` constructors for smart + /// positioning and layout. + pub fn new(points: I) -> Self { + PointPath::styled(points, Style::new()) + } + + /// Build a new PointPath whose bounding box is fit to the absolute co-ordinates of the points. + /// + /// This requires that the `points` iterator is `Clone` so that we may iterate through and + /// determine the bounding box of the `points`. + /// + /// If you would rather centre the points to the middle of the bounding box, use + /// [**PointPath::centred**](./struct.PointPath#method.centred) instead. + pub fn abs(points: I) -> Self + where I: IntoIterator + Clone, + { + PointPath::abs_styled(points, Style::new()) + } + + /// The same as [**PointPath::abs**](./struct.PointPath#method.abs) but constructs the + /// **PointPath** with the given style. + pub fn abs_styled(points: I, style: Style) -> Self + where I: IntoIterator + Clone, + { + let points_clone = points.clone().into_iter(); + let (xy, dim) = bounding_box_for_points(points_clone).xy_dim(); + PointPath::styled(points, style).dim(dim).point(xy) + } + + /// Build a new **PointPath** and shift the location of the points so that the centre of their + /// bounding rectangle lies at the position determined for the **PointPath** widget. + /// + /// This is useful if your points simply describe a shape and you want to position them using + /// conrod's auto-layout or **Positionable** methods. + /// + /// If you would rather centre the bounding box to the points, use + /// [**PointPath::abs**](./struct.PointPath#method.abs) instead. + pub fn centred(points: I) -> Self + where I: IntoIterator + Clone, + { + PointPath::centred_styled(points, Style::new()) + } + + /// The same as [**PointPath::centred**](./struct.PointPath#method.centred) but constructs the + /// **PointPath** with the given style. + pub fn centred_styled(points: I, style: Style) -> Self + where I: IntoIterator + Clone, + { + let points_clone = points.clone().into_iter(); + let (xy, dim) = bounding_box_for_points(points_clone).xy_dim(); + let mut point_path = PointPath::styled(points, style).dim(dim); + point_path.maybe_shift_to_centre_from = Some(xy); + point_path + } + +} + + +impl Style { + + /// Constructor for a default **PointPath** Style. + pub fn new() -> Self { + Style { + maybe_kind: None, + } + } + + /// Constructor for a specific kind of **PointPath** Style. + pub fn from_kind(kind: StyleKind) -> Self { + Style { + maybe_kind: Some(kind), + } + } + + /// Construct a **PointPath** drawn as the lines between the points. + pub fn lines() -> Self { + Style::lines_styled(LineStyle::new()) + } + + /// Construct a **PointPath** with only the points drawn. + pub fn points() -> Self { + Style::from_kind(StyleKind::Points) + } + + /// Construct a **PointPath** with both lines and points drawn. + pub fn lines_and_points() -> Self { + Style::from_kind(StyleKind::Both(LineStyle::new())) + } + + /// Same as [**Style::lines**](./struct.Style#method.lines) but with the given style. + pub fn lines_styled(style: LineStyle) -> Self { + Style::from_kind(StyleKind::Lines(style)) + } + + /// Set the kind of styling for the **PointPath**. + pub fn set_kind(&mut self, kind: StyleKind) { + self.maybe_kind = Some(kind); + } + + /// Get the kind of styling for the **PointPath** Style. + pub fn get_kind(&self, theme: &Theme) -> StyleKind { + fn default_kind() -> StyleKind { + StyleKind::Lines(super::line::Style::new()) + } + self.maybe_kind.or_else(|| theme.maybe_point_path.as_ref().map(|default| { + default.style.maybe_kind.unwrap_or_else(default_kind) + })).unwrap_or_else(default_kind) + } + +} + + +impl Widget for PointPath + where I: IntoIterator, +{ + type State = State; + type Style = Style; + + fn common(&self) -> &widget::CommonBuilder { + &self.common + } + + fn common_mut(&mut self) -> &mut widget::CommonBuilder { + &mut self.common + } + + fn unique_kind(&self) -> &'static str { + "Line" + } + + fn init_state(&self) -> State { + State { + points: Vec::new(), + } + } + + fn style(&self) -> Style { + self.style.clone() + } + + /// Update the state of the Line. + fn update(self, args: widget::UpdateArgs) { + use utils::{iter_diff, IterDiff}; + let widget::UpdateArgs { rect, state, .. } = args; + let PointPath { points, maybe_shift_to_centre_from, .. } = self; + + // A function that compares the given points iterator to the points currently owned by + // `State` and updates only if necessary. + fn update_points(state: &mut widget::State, points: I) + where I: IntoIterator, + { + match iter_diff(&state.view().points, points) { + Some(IterDiff::FirstMismatch(i, mismatch)) => state.update(|state| { + state.points.truncate(i); + state.points.extend(mismatch); + }), + Some(IterDiff::Longer(remaining)) => + state.update(|state| state.points.extend(remaining)), + Some(IterDiff::Shorter(total)) => + state.update(|state| state.points.truncate(total)), + None => (), + } + } + + match maybe_shift_to_centre_from { + Some(original) => { + let xy = rect.xy(); + let difference = vec2_sub(xy, original); + update_points(state, points.into_iter().map(|point| vec2_add(point, difference))) + }, + None => update_points(state, points), + } + } + + /// Construct an Element for the Line. + fn draw(args: widget::DrawArgs) -> Element { + let widget::DrawArgs { rect, state, style, theme, .. } = args; + match style.get_kind(theme) { + StyleKind::Lines(line_style) | StyleKind::Both(line_style) => { + draw_lines(state.points.iter().cloned(), rect, line_style, theme) + }, + StyleKind::Points => unimplemented!(), + } + } +} + + +/// Produce a renderable **Element** for the given point path as a series of lines. +pub fn draw_lines(points: I, rect: Rect, style: LineStyle, theme: &Theme) -> Element + where I: Iterator + Clone, +{ + use elmesque::form::{collage, segment, solid, traced}; + let mut ends = points.clone(); + ends.next().map(|_| { + let (w, h) = rect.w_h(); + let color = style.get_color(theme); + let thickness = style.get_thickness(theme); + let forms = points.zip(ends).map(move |(start, end)| { + let a = (start[0], start[1]); + let b = (end[0], end[1]); + traced(solid(color).width(thickness), segment(a, b)) + }); + collage(w as i32, h as i32, forms.collect()) + }).unwrap_or_else(|| ::elmesque::element::empty()) +} + diff --git a/src/widget/primitive/shape/circle.rs b/src/widget/primitive/shape/circle.rs new file mode 100644 index 000000000..43a9521a6 --- /dev/null +++ b/src/widget/primitive/shape/circle.rs @@ -0,0 +1,45 @@ + +use {Color, Dimensions, LineStyle, Scalar}; +use super::oval::Oval; +use super::Style as Style; + +/// A tiny wrapper around the **Oval** widget type. +#[derive(Copy, Clone, Debug)] +pub struct Circle; + + +fn rad_to_dim(radius: Scalar) -> Dimensions { + let side = radius * 2.0; + [side, side] +} + + +impl Circle { + + /// Build a circular **Oval** with the given dimensions and style. + pub fn styled(radius: Scalar, style: Style) -> Oval { + Oval::styled(rad_to_dim(radius), style) + } + + /// Build a new **Fill**ed circular **Oval**. + pub fn fill(radius: Scalar) -> Oval { + Oval::fill(rad_to_dim(radius)) + } + + /// Build a new circular **Oval** **Fill**ed with the given color. + pub fn fill_with(radius: Scalar, color: Color) -> Oval { + Oval::fill_with(rad_to_dim(radius), color) + } + + /// Build a new circular **Outline**d **Oval** widget. + pub fn outline(radius: Scalar) -> Oval { + Oval::outline(rad_to_dim(radius)) + } + + /// Build a new circular **Oval** **Outline**d with the given style. + pub fn outline_styled(radius: Scalar, line_style: LineStyle) -> Oval { + Oval::outline_styled(rad_to_dim(radius), line_style) + } + +} + diff --git a/src/widget/primitive/shape/framed_rectangle.rs b/src/widget/primitive/shape/framed_rectangle.rs new file mode 100644 index 000000000..c7fab537e --- /dev/null +++ b/src/widget/primitive/shape/framed_rectangle.rs @@ -0,0 +1,148 @@ +use {CharacterCache, Scalar, Theme}; +use color::{Color, Colorable}; +use elmesque::Element; +use frame::Frameable; +use position::{Dimensions, Sizeable}; +use widget::{self, Widget}; + + +/// A filled rectangle widget that may or may not have some frame. +#[derive(Copy, Clone, Debug)] +pub struct FramedRectangle { + /// Data necessary and common for all widget builder types. + pub common: widget::CommonBuilder, + /// Unique styling for the **FramedRectangle**. + pub style: Style, +} + + +/// Unique styling for the **FramedRectangle** widget. +#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] +pub struct Style { + /// Shape styling for the inner rectangle. + pub maybe_color: Option, + /// The thickness of the frame. + pub maybe_frame: Option, + /// The color of the frame. + pub maybe_frame_color: Option, +} + + +impl FramedRectangle { + + /// Build a new **FramedRectangle**. + pub fn new(dim: Dimensions) -> Self { + FramedRectangle { + common: widget::CommonBuilder::new(), + style: Style::new(), + }.dim(dim) + } + +} + + +impl Widget for FramedRectangle { + type State = (); + type Style = Style; + + fn common(&self) -> &widget::CommonBuilder { + &self.common + } + + fn common_mut(&mut self) -> &mut widget::CommonBuilder { + &mut self.common + } + + fn unique_kind(&self) -> &'static str { + "FramedRectangle" + } + + fn init_state(&self) -> () { + () + } + + fn style(&self) -> Style { + self.style.clone() + } + + /// Update the state of the Rectangle. + fn update(self, _args: widget::UpdateArgs) { + // Nothing to update here! + } + + /// Construct an Element for the Rectangle. + fn draw(args: widget::DrawArgs) -> Element { + use elmesque::form::{self, collage}; + let widget::DrawArgs { rect, style, theme, .. } = args; + + let (x, y, w, h) = rect.x_y_w_h(); + let frame = style.get_frame(theme); + + let maybe_frame_form = if frame > 0.0 { + let frame_color = style.get_frame_color(theme); + Some(form::rect(w, h).filled(frame_color).shift(x, y)) + } else { + None + }; + + let (inner_w, inner_h) = rect.pad(frame).w_h(); + let color = style.get_color(theme); + let inner_form = form::rect(inner_w, inner_h).filled(color).shift(x, y); + + let forms = maybe_frame_form.into_iter().chain(::std::iter::once(inner_form)); + collage(w as i32, h as i32, forms.collect()) + } + +} + + +impl Style { + + /// Construct the default Style. + pub fn new() -> Style { + Style { + maybe_color: None, + maybe_frame: None, + maybe_frame_color: None, + } + } + + /// Get the Color for an Element. + pub fn get_color(&self, theme: &Theme) -> Color { + self.maybe_color.unwrap_or_else(|| theme.shape_color) + } + + /// Get the frame for an Element. + pub fn get_frame(&self, theme: &Theme) -> f64 { + self.maybe_frame.unwrap_or_else(|| theme.frame_width) + } + + /// Get the frame Color for an Element. + pub fn get_frame_color(&self, theme: &Theme) -> Color { + self.maybe_frame_color.unwrap_or_else(|| theme.frame_color) + } + +} + + + + +impl Colorable for FramedRectangle { + fn color(mut self, color: Color) -> Self { + self.style.maybe_color = Some(color); + self + } +} + + +impl Frameable for FramedRectangle { + fn frame(mut self, width: Scalar) -> Self { + self.style.maybe_frame = Some(width); + self + } + fn frame_color(mut self, color: Color) -> Self { + self.style.maybe_frame_color = Some(color); + self + } +} + diff --git a/src/widget/primitive/shape/mod.rs b/src/widget/primitive/shape/mod.rs new file mode 100644 index 000000000..e8b3bd746 --- /dev/null +++ b/src/widget/primitive/shape/mod.rs @@ -0,0 +1,68 @@ +//! A module encompassing the primitive 2D shape widgets. + +use color::Color; +use super::line::Style as LineStyle; +use theme::Theme; + +pub mod circle; +pub mod oval; +pub mod polygon; +pub mod rectangle; +pub mod framed_rectangle; + + +/// The style for some 2D shape. +#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] +pub enum Style { + /// The outline of the shape with this style. + Outline(LineStyle), + /// A rectangle filled with this color. + Fill(Option), +} + + +impl Style { + + /// A default `Fill` style. + pub fn fill() -> Self { + Style::Fill(None) + } + + /// A `Fill` style with some given `Color`. + pub fn fill_with(color: Color) -> Self { + Style::Fill(Some(color)) + } + + /// A default `Outline` style. + pub fn outline() -> Self { + Style::Outline(LineStyle::new()) + } + + /// A default `Outline` style. + pub fn outline_styled(line_style: LineStyle) -> Self { + Style::Outline(line_style) + } + + /// The style with some given Color. + pub fn color(mut self, color: Color) -> Self { + self.set_color(color); + self + } + + /// Set the color for the style. + pub fn set_color(&mut self, color: Color) { + match *self { + Style::Fill(ref mut maybe_color) => *maybe_color = Some(color), + Style::Outline(ref mut line_style) => line_style.set_color(color), + } + } + + /// Get the color of the Rectangle. + pub fn get_color(&self, theme: &Theme) -> Color { + match *self { + Style::Fill(maybe_color) => maybe_color.unwrap_or(theme.shape_color), + Style::Outline(style) => style.get_color(theme), + } + } + +} diff --git a/src/widget/primitive/shape/oval.rs b/src/widget/primitive/shape/oval.rs new file mode 100644 index 000000000..4d5752df4 --- /dev/null +++ b/src/widget/primitive/shape/oval.rs @@ -0,0 +1,152 @@ +use {CharacterCache, LineStyle, Scalar}; +use color::{Color, Colorable}; +use elmesque::Element; +use position::{Dimensions, Sizeable}; +use super::Style as Style; +use widget::{self, Widget}; +use widget::primitive::point_path; + + + +/// A simple, non-interactive widget for drawing a single **Oval**. +#[derive(Copy, Clone, Debug)] +pub struct Oval { + /// Data necessary and common for all widget builder types. + pub common: widget::CommonBuilder, + /// Unique styling. + pub style: Style, +} + +/// Unique state for the **Oval**. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct State { + kind: Kind, +} + +/// Whether the **Oval** is drawn as an **Outline** or **Fill**ed with a color. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Kind { + /// Only the **Outline** of the **Oval** is drawn. + Outline, + /// The **Oval**'s area is **Fill**ed with some color. + Fill, +} + + +impl Oval { + + /// Build an **Oval** with the given dimensions and style. + pub fn styled(dim: Dimensions, style: Style) -> Self { + Oval { + common: widget::CommonBuilder::new(), + style: style, + }.dim(dim) + } + + /// Build a new **Fill**ed **Oval**. + pub fn fill(dim: Dimensions) -> Self { + Oval::styled(dim, Style::fill()) + } + + /// Build a new **Oval** **Fill**ed with the given color. + pub fn fill_with(dim: Dimensions, color: Color) -> Self { + Oval::styled(dim, Style::fill_with(color)) + } + + /// Build a new **Outline**d **Oval** widget. + pub fn outline(dim: Dimensions) -> Self { + Oval::styled(dim, Style::outline()) + } + + /// Build a new **Oval** **Outline**d with the given style. + pub fn outline_styled(dim: Dimensions, line_style: LineStyle) -> Self { + Oval::styled(dim, Style::outline_styled(line_style)) + } + +} + + +impl Widget for Oval { + type State = State; + type Style = Style; + + fn common(&self) -> &widget::CommonBuilder { + &self.common + } + + fn common_mut(&mut self) -> &mut widget::CommonBuilder { + &mut self.common + } + + fn unique_kind(&self) -> &'static str { + "Oval" + } + + fn init_state(&self) -> State { + State { + kind: Kind::Fill, + } + } + + fn style(&self) -> Style { + self.style.clone() + } + + /// Update the state of the Oval. + fn update(self, args: widget::UpdateArgs) { + let widget::UpdateArgs { state, style, .. } = args; + + let kind = match *style { + Style::Fill(_) => Kind::Fill, + Style::Outline(_) => Kind::Outline, + }; + + if state.view().kind != kind { + state.update(|state| state.kind = kind); + } + } + + /// Construct an Element for the Oval. + fn draw(args: widget::DrawArgs) -> Element { + use elmesque::form::{collage, polygon}; + use std::f64::consts::PI; + + let widget::DrawArgs { rect, style, theme, .. } = args; + let (x, y, w, h) = rect.x_y_w_h(); + const CIRCLE_RESOLUTION: usize = 50; + let t = 2.0 * PI / CIRCLE_RESOLUTION as Scalar; + let hw = w / 2.0; + let hh = h / 2.0; + let f = |i: Scalar| [x + hw * (t*i).cos(), y + hh * (t*i).sin()]; + let points = (0..CIRCLE_RESOLUTION+1).map(|i| f(i as f64)); + + match *style { + + // Draw a filled oval. + Style::Fill(_) => { + let color = style.get_color(theme); + // FIXME: This allocation is unnecessary and could be replaced with an iterator. + let points = points.map(|p| (p[0], p[1])).collect(); + let form = polygon(points).filled(color); + collage(w as i32, h as i32, vec![form]) + }, + + // Draw only the outline of the oval. + Style::Outline(line_style) => { + // FIXME: This allocation is unnecessary and could be replaced with an iterator. + let points: Vec<_> = points.collect(); + point_path::draw_lines(points.iter().cloned(), rect, line_style, theme) + }, + } + } + +} + + +impl Colorable for Oval { + fn color(mut self, color: Color) -> Self { + self.style.set_color(color); + self + } +} + diff --git a/src/widget/primitive/shape/polygon.rs b/src/widget/primitive/shape/polygon.rs new file mode 100644 index 000000000..c0e1bca0b --- /dev/null +++ b/src/widget/primitive/shape/polygon.rs @@ -0,0 +1,294 @@ + +use {CharacterCache, Element, LineStyle}; +use color::{Color, Colorable}; +use position::{Point, Positionable, Sizeable}; +use super::Style; +use widget::{self, Widget}; +use widget::primitive::point_path; +use utils::bounding_box_for_points; +use vecmath::{vec2_add, vec2_sub}; + + +/// A basic, non-interactive, arbitarry **Polygon** widget. +/// +/// The **Polygon** is described by specifying its corners in order. +/// +/// **Polygon** will automatically close all shapes, so the given list of points does not need to +/// start and end with the same position. +#[derive(Copy, Clone, Debug)] +pub struct Polygon { + /// The points describing the corners of the **Polygon**. + pub points: I, + /// Data necessary and common for all widget builder types. + pub common: widget::CommonBuilder, + /// Unique styling for the **Polygon**. + pub style: Style, + /// Whether or not the points should be automatically centred to the widget position. + pub maybe_shift_to_centre_from: Option, +} + +/// Unique state for the **Polygon**. +#[derive(Clone, Debug, PartialEq)] +pub struct State { + /// Whether the rectangle is drawn as an outline or a filled color. + kind: Kind, + /// An owned version of the points yielded by the **Polygon**'s `points` iterator. + points: Vec, +} + +/// Whether the rectangle is drawn as an outline or a filled color. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Kind { + /// Only the outline of the rectangle is drawn. + Outline, + /// The rectangle area is filled with some color. + Fill, +} + + +impl Polygon { + + /// Build a polygon with the given points and style. + pub fn styled(points: I, style: Style) -> Self { + Polygon { + points: points, + common: widget::CommonBuilder::new(), + style: style, + maybe_shift_to_centre_from: None, + } + } + + /// Build a **Polygon** with the default **Fill** style. + pub fn fill(points: I) -> Self { + Polygon::styled(points, Style::fill()) + } + + /// Build a **Polygon** **Fill**ed with the given **Color**. + pub fn fill_with(points: I, color: Color) -> Self { + Polygon::styled(points, Style::fill_with(color)) + } + + /// Build a **Polygon** with the default **Outline** style. + pub fn outline(points: I) -> Self { + Polygon::styled(points, Style::outline()) + } + + /// Build a **Polygon** **Outline**ed with the given line style. + pub fn outline_styled(points: I, style: LineStyle) -> Self { + Polygon::styled(points, Style::outline_styled(style)) + } + + /// Build a new filled **Polygon** whose bounding box is fit to the absolute co-ordinates of + /// the points. + /// + /// This requires that the `points` iterator is `Clone` so that we may iterate through and + /// determine the bounding box of the `points`. + /// + /// If you would rather centre the points to the middle of the bounding box, use + /// the [**Polygon::centred**](./struct.Polygon#method.centred) methods instead. + pub fn abs_styled(points: I, style: Style) -> Self + where I: IntoIterator + Clone, + { + let points_clone = points.clone().into_iter(); + let (xy, dim) = bounding_box_for_points(points_clone).xy_dim(); + Polygon::styled(points, style).dim(dim).point(xy) + } + + /// The same as [**Polygon::abs_styled**](./struct.Polygon#method.abs_styled) but builds the + /// **Polygon** with the default **Fill** style. + pub fn abs_fill(points: I) -> Self + where I: IntoIterator + Clone, + { + Polygon::abs_styled(points, Style::fill()) + } + + /// The same as [**Polygon::abs_styled**](./struct.Polygon#method.abs_styled) but builds the + /// **Polygon** **Fill**ed with the given **Color**. + pub fn abs_fill_with(points: I, color: Color) -> Self + where I: IntoIterator + Clone, + { + Polygon::abs_styled(points, Style::fill_with(color)) + } + + /// The same as [**Polygon::abs_styled**](./struct.Polygon#method.abs_styled) but builds the + /// **Polygon** with the default **Outline** style. + pub fn abs_outline(points: I) -> Self + where I: IntoIterator + Clone, + { + Polygon::abs_styled(points, Style::outline()) + } + + /// The same as [**Polygon::abs_styled**](./struct.Polygon#method.abs_styled) but builds the + /// **Polygon** with the given **Outline** styling. + pub fn abs_outline_styled(points: I, style: LineStyle) -> Self + where I: IntoIterator + Clone, + { + Polygon::abs_styled(points, Style::outline_styled(style)) + } + + /// Build a new **Polygon** and shift the location of the points so that the centre of their + /// bounding rectangle lies at the position determined for the **Polygon** widget. + /// + /// This is useful if your points simply describe a shape and you want to position them using + /// conrod's auto-layout and/or **Positionable** methods. + /// + /// If you would rather centre the bounding box to the points, use the + /// [**Polygon::abs**](./struct.Polygon#method.abs) constructor method instead. + pub fn centred_styled(points: I, style: Style) -> Self + where I: IntoIterator + Clone, + { + let points_clone = points.clone().into_iter(); + let (xy, dim) = bounding_box_for_points(points_clone).xy_dim(); + let mut polygon = Polygon::styled(points, style).dim(dim); + polygon.maybe_shift_to_centre_from = Some(xy); + polygon + } + + /// The same as [**Polygon::centred_styled**](./struct.Polygon#method.centred_styled) but + /// constructs the **Polygon** with the default **Fill** style. + pub fn centred_fill(points: I) -> Self + where I: IntoIterator + Clone, + { + Polygon::centred_styled(points, Style::fill()) + } + + /// The same as [**Polygon::centred_styled**](./struct.Polygon#method.centred_styled) but + /// constructs the **Polygon** **Fill**ed with the given color. + pub fn centred_fill_with(points: I, color: Color) -> Self + where I: IntoIterator + Clone, + { + Polygon::centred_styled(points, Style::fill_with(color)) + } + + /// The same as [**Polygon::centred_styled**](./struct.Polygon#method.centred_styled) but + /// constructs the **Polygon** with the default **Outline** style. + pub fn centred_outline(points: I) -> Self + where I: IntoIterator + Clone, + { + Polygon::centred_styled(points, Style::outline()) + } + + /// The same as [**Polygon::centred_styled**](./struct.Polygon#method.centred_styled) but + /// constructs the **Polygon** **Outline**d with the given styling. + pub fn centred_outline_styled(points: I, style: LineStyle) -> Self + where I: IntoIterator + Clone, + { + Polygon::centred_styled(points, Style::outline_styled(style)) + } + +} + + +impl Widget for Polygon + where I: IntoIterator, +{ + type State = State; + type Style = Style; + + fn common(&self) -> &widget::CommonBuilder { + &self.common + } + + fn common_mut(&mut self) -> &mut widget::CommonBuilder { + &mut self.common + } + + fn unique_kind(&self) -> &'static str { + "Polygon" + } + + fn init_state(&self) -> State { + State { + kind: Kind::Fill, + points: Vec::new(), + } + } + + fn style(&self) -> Style { + self.style.clone() + } + + /// Update the state of the Polygon. + fn update(self, args: widget::UpdateArgs) { + use utils::{iter_diff, IterDiff}; + let widget::UpdateArgs { rect, state, style, .. } = args; + let Polygon { points, maybe_shift_to_centre_from, .. } = self; + + // A function that compares the given points iterator to the points currently owned by + // `State` and updates only if necessary. + fn update_points(state: &mut widget::State, points: I) + where I: IntoIterator, + { + match iter_diff(&state.view().points, points) { + Some(IterDiff::FirstMismatch(i, mismatch)) => state.update(|state| { + state.points.truncate(i); + state.points.extend(mismatch); + }), + Some(IterDiff::Longer(remaining)) => + state.update(|state| state.points.extend(remaining)), + Some(IterDiff::Shorter(total)) => + state.update(|state| state.points.truncate(total)), + None => (), + } + } + + // Check whether or not we need to centre the points. + match maybe_shift_to_centre_from { + Some(original) => { + let xy = rect.xy(); + let difference = vec2_sub(xy, original); + update_points(state, points.into_iter().map(|point| vec2_add(point, difference))) + }, + None => update_points(state, points), + } + + let kind = match *style { + Style::Fill(_) => Kind::Fill, + Style::Outline(_) => Kind::Outline, + }; + + if state.view().kind != kind { + state.update(|state| state.kind = kind); + } + } + + /// Construct an Element for the Polygon. + fn draw(args: widget::DrawArgs) -> Element { + let widget::DrawArgs { rect, state, style, theme, .. } = args; + match *style { + + // Draw a filled polygon. + Style::Fill(_) => { + use elmesque::form::{collage, polygon}; + let (w, h) = rect.w_h(); + let color = style.get_color(theme); + let mut points = state.points.iter().cloned(); + let first = points.next(); + let points = first.into_iter().chain(points).chain(first) + .map(|p| (p[0], p[1])) + .collect(); // FIXME: Unnecessary and could be replaced with an iterator. + let form = polygon(points).filled(color); + collage(w as i32, h as i32, vec![form]) + }, + + // The outline variant is drawn by an internal widget, so no need to draw it. + Style::Outline(line_style) => { + let mut points = state.points.iter().cloned(); + let first = points.next(); + let points = first.into_iter().chain(points).chain(first); + point_path::draw_lines(points, rect, line_style, theme) + }, + + } + } + +} + + +impl Colorable for Polygon { + fn color(mut self, color: Color) -> Self { + self.style.set_color(color); + self + } +} + diff --git a/src/widget/primitive/shape/rectangle.rs b/src/widget/primitive/shape/rectangle.rs new file mode 100644 index 000000000..b89427505 --- /dev/null +++ b/src/widget/primitive/shape/rectangle.rs @@ -0,0 +1,146 @@ +use {CharacterCache, LineStyle}; +use color::{Color, Colorable}; +use elmesque::Element; +use position::{Dimensions, Sizeable}; +use super::Style as Style; +use widget::{self, Widget}; +use widget::primitive::point_path; + + +/// A basic, non-interactive rectangle shape widget. +#[derive(Copy, Clone, Debug)] +pub struct Rectangle { + /// Data necessary and common for all widget builder types. + pub common: widget::CommonBuilder, + /// Unique styling for the **Rectangle**. + pub style: Style, +} + +/// Unique state for the Rectangle. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct State { + kind: Kind, +} + +/// Whether the rectangle is drawn as an outline or a filled color. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Kind { + /// Only the outline of the rectangle is drawn. + Outline, + /// The rectangle area is filled with some color. + Fill, +} + + +impl Rectangle { + + /// Build a rectangle with the dimensions and style. + pub fn styled(dim: Dimensions, style: Style) -> Self { + Rectangle { + common: widget::CommonBuilder::new(), + style: style, + }.dim(dim) + } + + /// Build a new filled rectangle. + pub fn fill(dim: Dimensions) -> Self { + Rectangle::styled(dim, Style::fill()) + } + + /// Build a new filled rectangle widget filled with the given color. + pub fn fill_with(dim: Dimensions, color: Color) -> Self { + Rectangle::styled(dim, Style::fill_with(color)) + } + + /// Build a new outlined rectangle widget. + pub fn outline(dim: Dimensions) -> Self { + Rectangle::styled(dim, Style::outline()) + } + + /// Build an outlined rectangle rather than a filled one. + pub fn outline_styled(dim: Dimensions, line_style: LineStyle) -> Self { + Rectangle::styled(dim, Style::outline_styled(line_style)) + } + +} + + +impl Widget for Rectangle { + type State = State; + type Style = Style; + + fn common(&self) -> &widget::CommonBuilder { + &self.common + } + + fn common_mut(&mut self) -> &mut widget::CommonBuilder { + &mut self.common + } + + fn unique_kind(&self) -> &'static str { + "Rectangle" + } + + fn init_state(&self) -> State { + State { + kind: Kind::Fill, + } + } + + fn style(&self) -> Style { + self.style.clone() + } + + /// Update the state of the Rectangle. + fn update(self, args: widget::UpdateArgs) { + let widget::UpdateArgs { state, style, .. } = args; + + let kind = match *style { + Style::Fill(_) => Kind::Fill, + Style::Outline(_) => Kind::Outline, + }; + + if state.view().kind != kind { + state.update(|state| state.kind = kind); + } + } + + /// Construct an Element for the Rectangle. + fn draw(args: widget::DrawArgs) -> Element { + use elmesque::form::{self, collage}; + let widget::DrawArgs { rect, style, theme, .. } = args; + match *style { + + // Draw a filled rectangle. + Style::Fill(_) => { + let (x, y, w, h) = rect.x_y_w_h(); + let color = style.get_color(theme); + let form = form::rect(w, h).filled(color).shift(x, y); + collage(w as i32, h as i32, vec![form]) + }, + + // Draw only the outline of the rectangle. + Style::Outline(line_style) => { + let points = { + use std::iter::once; + let tl = [rect.x.start, rect.y.end]; + let tr = [rect.x.end, rect.y.end]; + let br = [rect.x.end, rect.y.start]; + let bl = [rect.x.start, rect.y.start]; + once(tl).chain(once(tr)).chain(once(br)).chain(once(bl)).chain(once(tl)) + }; + point_path::draw_lines(points, rect, line_style, theme) + }, + } + } + +} + + +impl Colorable for Rectangle { + fn color(mut self, color: Color) -> Self { + self.style.set_color(color); + self + } +} + From a795f2d8d493abba69492799f64174c587c9f77c Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 8 Nov 2015 20:03:56 +1100 Subject: [PATCH 004/124] Added example for primitive widgets --- examples/primitives.rs | 87 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 examples/primitives.rs diff --git a/examples/primitives.rs b/examples/primitives.rs new file mode 100644 index 000000000..3518b033a --- /dev/null +++ b/examples/primitives.rs @@ -0,0 +1,87 @@ + +#[macro_use] extern crate conrod; +extern crate find_folder; +extern crate piston_window; + +use conrod::{Theme, Widget}; +use piston_window::*; + +type Ui = conrod::Ui; + +fn main() { + + // Construct the window. + let window: PistonWindow = + WindowSettings::new("Primitives Demo", [400, 720]) + .exit_on_esc(true).build().unwrap(); + + // construct our `Ui`. + let mut ui = { + let assets = find_folder::Search::KidsThenParents(3, 5) + .for_folder("assets").unwrap(); + let font_path = assets.join("fonts/NotoSans/NotoSans-Regular.ttf"); + let theme = Theme::default(); + let glyph_cache = Glyphs::new(&font_path, window.factory.borrow().clone()); + Ui::new(glyph_cache.unwrap(), theme) + }; + + // Poll events from the window. + for event in window { + ui.handle_event(&event); + event.draw_2d(|c, g| { + set_ui(&mut ui); + ui.draw_if_changed(c, g); + }); + } + +} + + +// Draw the Ui. +fn set_ui(ui: &mut Ui) { + use conrod::{Circle, Line, Oval, PointPath, Polygon, Positionable, Rectangle, Split}; + use std::iter::once; + + // Generate a unique const `WidgetId` for each widget. + widget_ids!{ + CANVAS, + LINE, + POINT_PATH, + RECTANGLE_FILL, + RECTANGLE_OUTLINE, + TRAPEZOID, + OVAL_FILL, + OVAL_OUTLINE, + CIRCLE, + }; + + // The background canvas upon which we'll place our widgets. + Split::new(CANVAS).pad(80.0).set(ui); + + Line::centred([-40.0, -40.0], [40.0, 40.0]).top_left_of(CANVAS).set(LINE, ui); + + let left = [-40.0, -40.0]; + let top = [0.0, 40.0]; + let right = [40.0, -40.0]; + let points = once(left).chain(once(top)).chain(once(right)); + PointPath::centred(points).down(80.0).set(POINT_PATH, ui); + + Rectangle::fill([80.0, 80.0]).down(80.0).set(RECTANGLE_FILL, ui); + + Rectangle::outline([80.0, 80.0]).down(80.0).set(RECTANGLE_OUTLINE, ui); + + let bl = [-40.0, -40.0]; + let tl = [-20.0, 40.0]; + let tr = [20.0, 40.0]; + let br = [40.0, -40.0]; + let points = once(bl).chain(once(tl)).chain(once(tr)).chain(once(br)); + Polygon::centred_fill(points).right_from(LINE, 80.0).set(TRAPEZOID, ui); + + Oval::fill([40.0, 80.0]).down(80.0).align_middle_x().set(OVAL_FILL, ui); + + Oval::outline([80.0, 40.0]).down(100.0).align_middle_x().set(OVAL_OUTLINE, ui); + + Circle::fill(40.0).down(100.0).align_middle_x().set(CIRCLE, ui); +} + + From 2dc30f51449e0cbddd89726d5b6b7923a5c0d8df Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 8 Nov 2015 20:04:30 +1100 Subject: [PATCH 005/124] Make assets folder search depth deeper --- examples/all_widgets.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/all_widgets.rs b/examples/all_widgets.rs index 699b85625..189f4f068 100644 --- a/examples/all_widgets.rs +++ b/examples/all_widgets.rs @@ -146,7 +146,7 @@ fn main() { let event_iter = window.events().ups(60).max_fps(60); let mut gl = GlGraphics::new(opengl); - let assets = find_folder::Search::ParentsThenKids(3, 3) + let assets = find_folder::Search::KidsThenParents(3, 5) .for_folder("assets").unwrap(); let font_path = assets.join("fonts/NotoSans/NotoSans-Regular.ttf"); let theme = Theme::default(); From 049b1b0db6dd2214495442ace3197fb62cabcaf0 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 8 Nov 2015 20:04:46 +1100 Subject: [PATCH 006/124] Expose primitive widgets --- src/lib.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bde540c8a..aac32f4a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,13 +20,20 @@ extern crate rustc_serialize; extern crate vecmath; +pub use widget::primitive::label::Label; +pub use widget::primitive::line::Line; +pub use widget::primitive::point_path::PointPath; +pub use widget::primitive::shape::circle::Circle; +pub use widget::primitive::shape::framed_rectangle::FramedRectangle; +pub use widget::primitive::shape::polygon::Polygon; +pub use widget::primitive::shape::oval::Oval; +pub use widget::primitive::shape::rectangle::Rectangle; + pub use widget::button::Button; pub use widget::canvas::Canvas; pub use widget::drop_down_list::DropDownList; pub use widget::envelope_editor::EnvelopeEditor; pub use widget::envelope_editor::EnvelopePoint; -pub use widget::label::Label; -pub use widget::line::Line; pub use widget::matrix::Matrix as WidgetMatrix; pub use widget::number_dialer::NumberDialer; pub use widget::slider::Slider; @@ -36,12 +43,17 @@ pub use widget::text_box::TextBox; pub use widget::toggle::Toggle; pub use widget::xy_pad::XYPad; + +pub use widget::primitive::label::Style as LabelStyle; +pub use widget::primitive::line::Style as LineStyle; +pub use widget::primitive::point_path::Style as PointPathStyle; +pub use widget::primitive::shape::Style as ShapeStyle; +pub use widget::primitive::shape::framed_rectangle::Style as FramedRectangleStyle; + pub use widget::button::Style as ButtonStyle; pub use widget::canvas::Style as CanvasStyle; pub use widget::drop_down_list::Style as DropDownListStyle; pub use widget::envelope_editor::Style as EnvelopeEditorStyle; -pub use widget::label::Style as LabelStyle; -pub use widget::line::Style as LineStyle; pub use widget::number_dialer::Style as NumberDialerStyle; pub use widget::slider::Style as SliderStyle; pub use widget::tabs::Style as TabsStyle; From 594270e8b63d2f1f97edcf09310f5490963b5bf2 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 8 Nov 2015 20:05:01 +1100 Subject: [PATCH 007/124] Add primitive widgets --- src/theme.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/theme.rs b/src/theme.rs index 43a685611..d85579aed 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -49,11 +49,13 @@ pub struct Theme { /// Optional style defaults for an EnvelopeEditor. pub maybe_envelope_editor: Option>, /// Optional style defaults for a Line. - pub maybe_line: Option>, + pub maybe_line: Option>, /// Optional style defaults for a Matrix. pub maybe_matrix: Option>, /// Optional style defaults for a NumberDialer. pub maybe_number_dialer: Option>, + /// Optional style defaults for a PointPath. + pub maybe_point_path: Option>, /// Optional style defaults for a Scrollbar. pub maybe_scrollbar: Option, /// Optional style defaults for a Slider. @@ -137,6 +139,7 @@ impl Theme { maybe_line: None, maybe_matrix: None, maybe_number_dialer: None, + maybe_point_path: None, maybe_slider: None, maybe_tabs: None, maybe_text_box: None, From 77a91941052d7a7979a5571ba33c0c7a90506b60 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 8 Nov 2015 20:06:02 +1100 Subject: [PATCH 008/124] Add type and functions for diff-ing iterators, add function for finding bounding box of a series of points --- src/utils.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/src/utils.rs b/src/utils.rs index 5e179776d..99bc3b6bd 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,6 +4,9 @@ use num::{Float, NumCast, PrimInt, ToPrimitive}; +use position::{Point, Range, Rect}; +use std::borrow::Cow; +use std::iter::{Chain, once, Once}; /// Compare to PartialOrd values and return the min. @@ -89,6 +92,86 @@ pub fn val_to_string } } +/// Find the bounding rect for the given series of points. +pub fn bounding_box_for_points(mut points: I) -> Rect + where I: Iterator, +{ + points.next().map(|first| { + let start_rect = Rect { + x: Range { start: first[0], end: first[0] }, + y: Range { start: first[1], end: first[1] }, + }; + points.fold(start_rect, Rect::stretch_to_point) + }).unwrap_or_else(|| Rect::from_xy_dim([0.0, 0.0], [0.0, 0.0])) +} + +/// A type returned by the `iter_diff` function. +/// +/// Represents way in which the elements (of type `E`) yielded by the iterator `I` differ to some +/// other iterator yielding borrowed elements of the same type. +/// +/// `I` is some `Iterator` yielding elements of type `E`. +pub enum IterDiff { + /// The index of the first non-matching element along with the iterator's remaining elements + /// starting with the first mis-matched element. + FirstMismatch(usize, Chain, I>), + /// The remaining elements of the iterator. + Longer(Chain, I>), + /// The total number of elements that were in the iterator. + Shorter(usize), +} + +/// Compares every element yielded by both elems and new_elems in lock-step. +/// +/// If the number of elements yielded by `b` is less than the number of elements yielded by `a`, +/// the number of `b` elements yielded will be returned as `IterDiff::Shorter`. +/// +/// If the two elements of a step differ, the index of those elements along with the remaining +/// elements are returned as `IterDiff::FirstMismatch`. +/// +/// If `a` becomes exhausted before `b` becomes exhausted, the remaining `b` elements will be +/// returned as `IterDiff::Longer`. +/// +/// This function is useful when comparing a non-`Clone` `Iterator` of elements to some existing +/// collection. If there is any difference between the elements yielded by the iterator and those +/// of the collection, a suitable `IterDiff` is returned so that the existing collection may be +/// updated with the difference using elements from the very same iterator. +pub fn iter_diff<'a, A, B>(a: A, b: B) -> Option> + where A: IntoIterator, + B: IntoIterator, + B::Item: PartialEq + 'a +{ + let mut b = b.into_iter(); + for (i, a_elem) in a.into_iter().enumerate() { + match b.next() { + None => return Some(IterDiff::Shorter(i)), + Some(b_elem) => if *a_elem != b_elem { + return Some(IterDiff::FirstMismatch(i, once(b_elem).chain(b))); + }, + } + } + b.next().map(|elem| IterDiff::Longer(once(elem).chain(b))) +} + +/// Returns `Borrowed` `elems` if `elems` contains the same elements as yielded by `new_elems`. +/// +/// Allocates a new `Vec` and returns `Owned` if either the number of elements or the elements +/// themselves differ. +pub fn write_if_different(elems: &[T], new_elems: I) -> Cow<[T]> + where T: PartialEq + Clone, + I: IntoIterator, +{ + match iter_diff(elems.iter(), new_elems.into_iter()) { + Some(IterDiff::FirstMismatch(i, mismatch)) => + Cow::Owned(elems[0..i].iter().cloned().chain(mismatch).collect()), + Some(IterDiff::Longer(remaining)) => + Cow::Owned(elems.iter().cloned().chain(remaining).collect()), + Some(IterDiff::Shorter(num_new_elems)) => + Cow::Owned(elems.iter().cloned().take(num_new_elems).collect()), + None => Cow::Borrowed(elems), + } +} + #[test] fn test_map_range() { From 184dbd5def9151207ce04e8eb70c6a7b224ce429 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 8 Nov 2015 20:06:54 +1100 Subject: [PATCH 009/124] Add methods for stretching range and rect to some given position/point --- src/position.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/position.rs b/src/position.rs index 1eda01e3e..ab218a6a5 100644 --- a/src/position.rs +++ b/src/position.rs @@ -730,6 +730,30 @@ impl Range { ::utils::clamp(value, self.start, self.end) } + /// Stretch the end that is closest to the given value only if it lies outside the Range. + /// + /// The resulting Range will retain the direction of the original range. + pub fn stretch_to_value(self, value: Scalar) -> Range { + let Range { start, end } = self; + if start <= end { + if value < start { + Range { start: value, end: end } + } else if value > end { + Range { start: start, end: value } + } else { + self + } + } else { + if value < end { + Range { start: start, end: value } + } else if value > start { + Range { start: value, end: end } + } else { + self + } + } + } + } impl ::std::ops::Add for Range { @@ -940,6 +964,15 @@ impl Rect { } } + /// Stretches the closest edge(s) to the given point if the point lies outside of the Rect area. + pub fn stretch_to_point(self, point: Point) -> Rect { + let Rect { x, y } = self; + Rect { + x: x.stretch_to_value(point[0]), + y: y.stretch_to_value(point[1]), + } + } + } impl ::std::ops::Add for Rect { From 52d4c893f5e7a0333127e82765d18f01de6be838 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 8 Nov 2015 20:08:14 +1100 Subject: [PATCH 010/124] Begin changing over widgets to use primitive widgets instead of drawing their own graphics --- src/graph/mod.rs | 5 +- src/widget/button.rs | 160 +++++++++++++++++++++----------- src/widget/label.rs | 125 ------------------------- src/widget/line.rs | 197 ---------------------------------------- src/widget/mod.rs | 30 ++++-- src/widget/rectangle.rs | 128 -------------------------- 6 files changed, 135 insertions(+), 510 deletions(-) delete mode 100644 src/widget/label.rs delete mode 100644 src/widget/line.rs delete mode 100644 src/widget/rectangle.rs diff --git a/src/graph/mod.rs b/src/graph/mod.rs index 7130bb85f..687bf4ede 100644 --- a/src/graph/mod.rs +++ b/src/graph/mod.rs @@ -62,6 +62,8 @@ pub struct Container { /// Whether or not the `Widget`'s cache has was updated during the last update cycle. /// We need to know this so we can check whether or not a widget has been removed. pub was_previously_updated: bool, + /// Whether or not the widget blocks the mouse from interacting with widgets underneath it. + pub mouse_passthrough: bool, } /// A node within the UI Graph. @@ -440,7 +442,7 @@ impl Graph { pub fn pre_update_cache(&mut self, widget: widget::PreUpdateCache) { let widget::PreUpdateCache { kind, idx, maybe_parent_idx, maybe_positioned_relatively_idx, rect, depth, kid_area, - drag_state, maybe_floating, maybe_scrolling, + drag_state, maybe_floating, maybe_scrolling, mouse_passthrough, } = widget; // Construct a new `Container` to place in the `Graph`. @@ -453,6 +455,7 @@ impl Graph { kid_area: kid_area, maybe_floating: maybe_floating, maybe_scrolling: maybe_scrolling, + mouse_passthrough: mouse_passthrough, maybe_element: None, element_has_changed: false, is_updated: true, diff --git a/src/widget/button.rs b/src/widget/button.rs index db920a5da..7cd2a1d44 100644 --- a/src/widget/button.rs +++ b/src/widget/button.rs @@ -1,5 +1,5 @@ -use Scalar; +use {Label, NodeIndex, FramedRectangle, Scalar}; use color::{Color, Colorable}; use elmesque::Element; use frame::Frameable; @@ -41,7 +41,8 @@ pub struct Style { /// Represents the state of the Button widget. #[derive(Clone, Debug, PartialEq)] pub struct State { - maybe_label: Option, + maybe_rectangle_idx: Option, + maybe_label_idx: Option, interaction: Interaction, } @@ -54,10 +55,10 @@ pub enum Interaction { } -impl State { +impl Interaction { /// Alter the widget color depending on the state. fn color(&self, color: Color) -> Color { - match self.interaction { + match *self { Interaction::Normal => color, Interaction::Highlighted => color.highlighted(), Interaction::Clicked => color.clicked(), @@ -112,13 +113,29 @@ impl<'a, F> Widget for Button<'a, F> where F: FnMut() { type State = State; type Style = Style; - fn common(&self) -> &widget::CommonBuilder { &self.common } - fn common_mut(&mut self) -> &mut widget::CommonBuilder { &mut self.common } - fn unique_kind(&self) -> &'static str { "Button" } + fn common(&self) -> &widget::CommonBuilder { + &self.common + } + + fn common_mut(&mut self) -> &mut widget::CommonBuilder { + &mut self.common + } + + fn unique_kind(&self) -> &'static str { + "Button" + } + fn init_state(&self) -> State { - State { maybe_label: None, interaction: Interaction::Normal } + State { + maybe_rectangle_idx: None, + maybe_label_idx: None, + interaction: Interaction::Normal, + } + } + + fn style(&self) -> Style { + self.style.clone() } - fn style(&self) -> Style { self.style.clone() } fn default_width(&self, theme: &Theme, _: &GlyphCache) -> Scalar { const DEFAULT_WIDTH: Scalar = 64.0; @@ -135,12 +152,13 @@ impl<'a, F> Widget for Button<'a, F> where F: FnMut() { } /// Update the state of the Button. - fn update(mut self, args: widget::UpdateArgs) { - let widget::UpdateArgs { state, rect, mut ui, .. } = args; + fn update(self, args: widget::UpdateArgs) { + let widget::UpdateArgs { maybe_parent_idx, idx, state, style, rect, mut ui, .. } = args; + let Button { enabled, maybe_label, mut maybe_react, .. } = self; let maybe_mouse = ui.input().maybe_mouse; // Check whether or not a new interaction has occurred. - let new_interaction = match (self.enabled, maybe_mouse) { + let new_interaction = match (enabled, maybe_mouse) { (false, _) | (true, None) => Interaction::Normal, (true, Some(mouse)) => { let is_over = rect.is_over(mouse.xy); @@ -148,7 +166,7 @@ impl<'a, F> Widget for Button<'a, F> where F: FnMut() { }, }; - // Capture the mouse if it was clicked, uncpature if it was released. + // Capture the mouse if it was clicked, uncapture if it was released. match (state.view().interaction, new_interaction) { (Interaction::Highlighted, Interaction::Clicked) => { ui.capture_mouse(); }, (Interaction::Clicked, Interaction::Highlighted) | @@ -159,57 +177,93 @@ impl<'a, F> Widget for Button<'a, F> where F: FnMut() { // If the mouse was released over button, react. if let (Interaction::Clicked, Interaction::Highlighted) = (state.view().interaction, new_interaction) { - if let Some(ref mut react) = self.maybe_react { react() } + if let Some(ref mut react) = maybe_react { react() } } + // FramedRectangle widget. + let rectangle_idx = state.view().maybe_rectangle_idx + .unwrap_or_else(|| ui.new_unique_node_index()); + let (xy, dim) = rect.xy_dim(); + let frame = style.frame(ui.theme()); + let color = new_interaction.color(style.color(ui.theme())); + let frame_color = style.frame_color(ui.theme()); + FramedRectangle::new(dim) + .point(xy) + .depth(1.0) + .parent(maybe_parent_idx) + .color(color) + .frame(frame) + .frame_color(frame_color) + .set(rectangle_idx, &mut ui); + + // Label widget. + let maybe_label_idx = maybe_label.map(|label| { + let label_idx = state.view().maybe_label_idx + .unwrap_or_else(|| ui.new_unique_node_index()); + let color = style.label_color(ui.theme()); + let font_size = style.label_font_size(ui.theme()); + Label::new(label) + .point(xy) + .depth(1.0) + .color(color) + .font_size(font_size) + .parent(maybe_parent_idx) + .set(label_idx, &mut ui); + label_idx + }); + // If there has been a change in interaction, set the new one. if state.view().interaction != new_interaction { state.update(|state| state.interaction = new_interaction); } - // If the label has changed, update it. - if state.view().maybe_label.as_ref().map(|label| &label[..]) != self.maybe_label { - state.update(|state| { - state.maybe_label = self.maybe_label.as_ref().map(|label| label.to_string()) - }); + // If the rectangle index has changed, update it. + if state.view().maybe_rectangle_idx != Some(rectangle_idx) { + state.update(|state| state.maybe_rectangle_idx = Some(rectangle_idx)); + } + + // If the label index has changed, update it. + if state.view().maybe_label_idx != maybe_label_idx { + state.update(|state| state.maybe_label_idx = maybe_label_idx); } } /// Construct an Element from the given Button State. - fn draw(args: widget::DrawArgs) -> Element { - use elmesque::form::{self, collage, text}; - - let widget::DrawArgs { state, style, theme, rect, .. } = args; - let xy = rect.xy(); - let dim = rect.dim(); - - // Retrieve the styling for the Element. - let color = state.color(style.color(theme)); - let frame = style.frame(theme); - let frame_color = style.frame_color(theme); - - // Construct the frame and inner rectangle forms. - let frame_form = form::rect(dim[0], dim[1]).filled(frame_color); - let (inner_w, inner_h) = (dim[0] - frame * 2.0, dim[1] - frame * 2.0); - let pressable_form = form::rect(inner_w, inner_h).filled(color); - - // Construct the label's Form. - let maybe_label_form = state.maybe_label.as_ref().map(|label_text| { - use elmesque::text::Text; - let label_color = style.label_color(theme); - let size = style.label_font_size(theme); - text(Text::from_string(label_text.to_string()).color(label_color).height(size as f64)) - .shift(xy[0].floor(), xy[1].floor()) - }); - - // Construct the button's Form. - let form_chain = Some(frame_form).into_iter() - .chain(Some(pressable_form)) - .map(|form| form.shift(xy[0], xy[1])) - .chain(maybe_label_form); - - // Turn the form into a renderable Element. - collage(dim[0] as i32, dim[1] as i32, form_chain.collect()) + fn draw(_args: widget::DrawArgs) -> Element { + ::elmesque::element::empty() + // use elmesque::form::{self, collage, text}; + + // let widget::DrawArgs { state, style, theme, rect, .. } = args; + // let xy = rect.xy(); + // let dim = rect.dim(); + + // // Retrieve the styling for the Element. + // let color = state.color(style.color(theme)); + // let frame = style.frame(theme); + // let frame_color = style.frame_color(theme); + + // // Construct the frame and inner rectangle forms. + // let frame_form = form::rect(dim[0], dim[1]).filled(frame_color); + // let (inner_w, inner_h) = (dim[0] - frame * 2.0, dim[1] - frame * 2.0); + // let pressable_form = form::rect(inner_w, inner_h).filled(color); + + // // Construct the label's Form. + // let maybe_label_form = state.maybe_label.as_ref().map(|label_text| { + // use elmesque::text::Text; + // let label_color = style.label_color(theme); + // let size = style.label_font_size(theme); + // text(Text::from_string(label_text.to_string()).color(label_color).height(size as f64)) + // .shift(xy[0].floor(), xy[1].floor()) + // }); + + // // Construct the button's Form. + // let form_chain = Some(frame_form).into_iter() + // .chain(Some(pressable_form)) + // .map(|form| form.shift(xy[0], xy[1])) + // .chain(maybe_label_form); + + // // Turn the form into a renderable Element. + // collage(dim[0] as i32, dim[1] as i32, form_chain.collect()) } } diff --git a/src/widget/label.rs b/src/widget/label.rs deleted file mode 100644 index 18c606472..000000000 --- a/src/widget/label.rs +++ /dev/null @@ -1,125 +0,0 @@ - -use Scalar; -use color::{Color, Colorable}; -use elmesque::Element; -use graphics::character::CharacterCache; -use label::FontSize; -use theme::Theme; -use ui::GlyphCache; -use widget::{self, Widget}; - - -/// Displays some given text centred within a rectangle. -#[derive(Clone, Debug)] -pub struct Label<'a> { - common: widget::CommonBuilder, - text: &'a str, - style: Style, -} - -/// The styling for a Label's renderable Element. -#[allow(missing_docs, missing_copy_implementations)] -#[derive(Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] -pub struct Style { - maybe_font_size: Option, - maybe_color: Option, -} - -/// The state to be stored between updates for the Label. -#[derive(Clone, Debug, PartialEq)] -pub struct State(String); - - -impl<'a> Label<'a> { - - /// Construct a new Label widget. - pub fn new(text: &'a str) -> Label<'a> { - Label { - common: widget::CommonBuilder::new(), - text: text, - style: Style::new(), - } - } - - /// Set the font size for the label. - #[inline] - pub fn font_size(mut self, size: FontSize) -> Label<'a> { - self.style.maybe_font_size = Some(size); - self - } - -} - - -impl<'a> Widget for Label<'a> { - type State = State; - type Style = Style; - fn common(&self) -> &widget::CommonBuilder { &self.common } - fn common_mut(&mut self) -> &mut widget::CommonBuilder { &mut self.common } - fn unique_kind(&self) -> &'static str { "Label" } - fn init_state(&self) -> State { State(String::new()) } - fn style(&self) -> Style { self.style.clone() } - - fn default_width(&self, theme: &Theme, glyph_cache: &GlyphCache) -> Scalar { - glyph_cache.width(self.style.font_size(theme), self.text) - } - - fn default_height(&self, theme: &Theme) -> Scalar { - self.style.font_size(theme) as Scalar - } - - /// Update the state of the Label. - fn update(self, args: widget::UpdateArgs) { - let widget::UpdateArgs { state, .. } = args; - if &state.view().0[..] != self.text { - state.update(|state| *state = State(self.text.to_owned())); - } - } - - /// Construct an Element for the Label. - fn draw(args: widget::DrawArgs) -> Element { - use elmesque::form::{text, collage}; - use elmesque::text::Text; - let widget::DrawArgs { rect, state: &State(ref string), style, theme, .. } = args; - let size = style.font_size(theme); - let color = style.color(theme); - let (x, y, w, h) = rect.x_y_w_h(); - let form = text(Text::from_string(string.clone()) - .color(color) - .height(size as f64)).shift(x.floor(), y.floor()); - collage(w as i32, h as i32, vec![form]) - } - -} - - -impl Style { - - /// Construct the default Style. - pub fn new() -> Style { - Style { - maybe_color: None, - maybe_font_size: None, - } - } - - /// Get the Color for an Element. - pub fn color(&self, theme: &Theme) -> Color { - self.maybe_color.unwrap_or(theme.label_color) - } - - /// Get the label font size for an Element. - pub fn font_size(&self, theme: &Theme) -> FontSize { - self.maybe_font_size.unwrap_or(theme.font_size_medium) - } - -} - - -impl<'a> Colorable for Label<'a> { - fn color(mut self, color: Color) -> Self { - self.style.maybe_color = Some(color); - self - } -} - diff --git a/src/widget/line.rs b/src/widget/line.rs deleted file mode 100644 index a81060b24..000000000 --- a/src/widget/line.rs +++ /dev/null @@ -1,197 +0,0 @@ - -use Scalar; -use color::{Color, Colorable}; -use elmesque::Element; -use graphics::character::CharacterCache; -use position::{Point, Rect, Sizeable}; -use theme::Theme; -use widget::{self, Widget}; - - -/// A simple, non-interactive widget for drawing a single straight Line. -#[derive(Copy, Clone, Debug)] -pub struct Line { - /// The start of the line. - pub start: Point, - /// The end of the line. - pub end: Point, - /// Data necessary and common for all widget types. - pub common: widget::CommonBuilder, - /// Unique styling. - pub style: Style, -} - -/// Unique state for the Line widget. -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct State { - /// The start of the line. - start: Point, - /// The end of the line. - end: Point, -} - -/// Unique styling for a Line widget. -#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] -pub struct Style { - /// The patter for the line. - pub maybe_pattern: Option, - /// Color of the Button's pressable area. - pub maybe_color: Option, - /// The thickness of the line. - pub maybe_thickness: Option, -} - -/// The pattern used to draw the line. -#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] -pub enum Pattern { - Solid, - Dashed, - Dotted, -} - - -impl Line { - - /// Construct a new Line widget builder. - pub fn new(start: Point, end: Point) -> Self { - let dim = Rect::from_corners(start, end).dim(); - Line { - start: start, - end: end, - common: widget::CommonBuilder::new(), - style: Style::new(), - }.dim(dim) - } - - /// The thickness or width of the Line. - /// - /// Use this instead of `Positionable::width` for the thickness of the `Line`, as `width` and - /// `height` refer to the dimensions of the bounding rectangle. - pub fn thickness(mut self, thickness: Scalar) -> Self { - self.style.maybe_thickness = Some(thickness); - self - } - - /// Make a solid line. - pub fn solid(mut self) -> Self { - self.style.maybe_pattern = Some(Pattern::Solid); - self - } - - /// Make a line with a Dashed pattern. - pub fn dashed(mut self) -> Self { - self.style.maybe_pattern = Some(Pattern::Dashed); - self - } - - /// Make a line with a Dotted pattern. - pub fn dotted(mut self) -> Self { - self.style.maybe_pattern = Some(Pattern::Dotted); - self - } - -} - - -impl Style { - - /// Constructor for a default Line Style. - pub fn new() -> Self { - Style { - maybe_pattern: None, - maybe_color: None, - maybe_thickness: None, - } - } - - /// The Pattern for the Line. - pub fn get_pattern(&self, theme: &Theme) -> Pattern { - const DEFAULT_PATTERN: Pattern = Pattern::Solid; - self.maybe_pattern.or_else(|| theme.maybe_line.as_ref().map(|default| { - default.style.maybe_pattern.unwrap_or(DEFAULT_PATTERN) - })).unwrap_or(DEFAULT_PATTERN) - } - - /// The Color for the Line. - pub fn get_color(&self, theme: &Theme) -> Color { - self.maybe_color.or_else(|| theme.maybe_line.as_ref().map(|default| { - default.style.maybe_color.unwrap_or(theme.shape_color) - })).unwrap_or(theme.shape_color) - } - - /// The width or thickness of the Line. - pub fn get_thickness(&self, theme: &Theme) -> Scalar { - const DEFAULT_THICKNESS: Scalar = 1.0; - self.maybe_thickness.or_else(|| theme.maybe_line.as_ref().map(|default| { - default.style.maybe_thickness.unwrap_or(DEFAULT_THICKNESS) - })).unwrap_or(DEFAULT_THICKNESS) - } - -} - - -impl Widget for Line { - type State = State; - type Style = Style; - - fn common(&self) -> &widget::CommonBuilder { - &self.common - } - - fn common_mut(&mut self) -> &mut widget::CommonBuilder { - &mut self.common - } - - fn unique_kind(&self) -> &'static str { - "Line" - } - - fn init_state(&self) -> State { - State { - start: [0.0, 0.0], - end: [0.0, 0.0], - } - } - - fn style(&self) -> Style { - self.style.clone() - } - - /// Update the state of the Line. - fn update(self, args: widget::UpdateArgs) { - let widget::UpdateArgs { state, .. } = args; - let Line { start, end, .. } = self; - - if state.view().start != start { - state.update(|state| state.start = start); - } - - if state.view().end != end { - state.update(|state| state.end = end); - } - } - - /// Construct an Element for the Line. - fn draw(args: widget::DrawArgs) -> Element { - use elmesque::form::{collage, segment, solid, traced}; - let widget::DrawArgs { rect, state, style, theme, .. } = args; - let (x, y, w, h) = rect.x_y_w_h(); - let color = style.get_color(theme); - let thickness = style.get_thickness(theme); - let a = (state.start[0], state.start[1]); - let b = (state.end[0], state.end[1]); - let form = traced(solid(color).width(thickness), segment(a, b)).shift(x, y); - collage(w as i32, h as i32, vec![form]) - } - -} - - -impl Colorable for Line { - fn color(mut self, color: Color) -> Self { - self.style.maybe_color = Some(color); - self - } -} - - diff --git a/src/widget/mod.rs b/src/widget/mod.rs index c90e2847b..d1a27a02f 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -17,16 +17,16 @@ mod id; mod index; pub mod scroll; -// Widget Modules. +// Primitive widget modules. +pub mod primitive; + +// Widget modules. pub mod button; pub mod canvas; pub mod drop_down_list; pub mod envelope_editor; -pub mod label; -pub mod line; pub mod matrix; pub mod number_dialer; -pub mod rectangle; pub mod slider; pub mod split; pub mod tabs; @@ -251,6 +251,8 @@ pub struct PreUpdateCache { pub maybe_floating: Option, /// Scrolling data for the **Widget** if there is some. pub maybe_scrolling: Option, + /// Indicates whether the widget blocks the mouse from interacting with widgets underneath it. + pub mouse_passthrough: bool, } /// **Widget** data to be cached after the **Widget::update** call in the **set_widget** @@ -304,9 +306,10 @@ impl<'a, C> UiRefMut for UiCell<'a, C> where C: CharacterCache { /// - init_state /// - style /// - update -/// - draw /// /// Methods that can be optionally overridden: +/// - draw +/// - mouse_passthrough /// - default_position /// - default_width /// - default_height @@ -387,13 +390,27 @@ pub trait Widget: Sized { /// called on the occasion that the widget's `Style` or `State` has changed. Keep this in mind /// when designing your widget's `Style` and `State` types. /// + /// Note that many widgets return nothing here, as they may be composed of other widgets that + /// take care of producing the renderable **Element**s. + /// /// # Arguments /// * state - The current **Widget::State** which should contain all unique state necessary for /// rendering the **Widget**. /// * style - The current **Widget::Style** of the **Widget**. /// * theme - The currently active **Theme** within the `Ui`. /// * glyph_cache - Used for determining the size of rendered text if necessary. - fn draw(args: DrawArgs) -> Element; + fn draw(_args: DrawArgs) -> Element { + ::elmesque::element::empty() + } + + /// Indicates whether or not the **Widget** should block the mouse from interacting with + /// widgets underneath it. + /// + /// It may be useful to override this for widgets that simply act as graphical elements, + /// allowing the mouse to pass through itself and interact with the underlying widget. + fn mouse_passthrough(&self) -> bool { + false + } /// The default Position for the widget. /// This is used when no Position is explicitly given when instantiating the Widget. @@ -751,6 +768,7 @@ fn set_widget<'a, C, W>(widget: W, idx: Index, ui: &mut Ui) where kid_area: kid_area, maybe_floating: maybe_floating, maybe_scrolling: maybe_new_scrolling, + mouse_passthrough: widget.mouse_passthrough(), }); } diff --git a/src/widget/rectangle.rs b/src/widget/rectangle.rs deleted file mode 100644 index 2d79e3b81..000000000 --- a/src/widget/rectangle.rs +++ /dev/null @@ -1,128 +0,0 @@ - -use Scalar; -use color::{Color, Colorable}; -use elmesque::Element; -use frame::Frameable; -use graphics::character::CharacterCache; -use label::{FontSize, Labelable}; -use mouse::Mouse; -use position::Positionable; -use theme::Theme; -use ui::GlyphCache; -use widget::{self, Widget}; - - -/// A basic, non-interactive rectangle shape widget. -#[derive(Clone, Debug)] -pub struct Rectangle { - common: widget::CommonBuilder, - style: Style, -} - -/// Styling for the Rectangle. -#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] -pub enum Style { - /// The outline of the rectangle with this style. - Outline(super::line::Style), - /// A rectangle filled with this color. - Filled(Option), -} - -impl Style { - - /// The default Rectangle Style. - pub fn new() -> Self { - Style::Filled(None) - } - - /// Get the color of the Rectangle. - pub fn get_color(&self, theme: &Theme) -> Color { - match *self { - Style::Filled(maybe_color) => maybe_color.unwrap_or(theme.shape_color), - Style::Outline(style) => style.get_color(theme), - } - } - -} - -impl Rectangle { - - /// Create a new rectangle builder. - pub fn new() -> Self { - Rectangle { - common: widget::CommonBuilder::new(), - style: Style::new(), - } - } - - /// Build an outlined rectangle rather than a filled one. - pub fn outline(mut self, mut line_style: super::line::Style) -> Self { - if line_style.maybe_color.is_none() { - line_style.maybe_color = match self.style { - Style::Outline(prev_style) => prev_style.maybe_color, - Style::Filled(maybe_color) => maybe_color, - }; - } - self.style = Style::Outline(line_style); - self - } - -} - - -impl Widget for Rectangle { - type State = (); - type Style = Style; - - fn common(&self) -> &widget::CommonBuilder { - &self.common - } - - fn common_mut(&mut self) -> &mut widget::CommonBuilder { - &mut self.common - } - - fn unique_kind(&self) -> &'static str { - "Rectangle" - } - - fn init_state(&self) -> () { - () - } - - fn style(&self) -> Style { - self.style.clone() - } - - /// Update the state of the Rectangle. - fn update(self, args: widget::UpdateArgs) { - // No unique state to update for the boring ol' Rectangle! - } - - /// Construct an Element for the Line. - fn draw(args: widget::DrawArgs) -> Element { - use elmesque::form::{self, collage, segment, solid, traced}; - let widget::DrawArgs { rect, state, style, theme, .. } = args; - let (x, y, w, h) = rect.x_y_w_h(); - - let color = style.get_color(theme); - match *style { - Style::Filled(_) => { - let form = form::rect(w, h).filled(color).shift(x, y); - collage(w as i32, h as i32, vec![form]) - }, - Style::Outline(line_style) => { - let thickness = line_style.get_thickness(theme); - let a = (rect.x.start, rect.y.end); - let b = (rect.x.end, rect.y.end); - let c = (rect.x.end, rect.y.start); - let d = (rect.x.start, rect.y.start); - // TODO: Use PointPath - ::elmesque::element::empty() - }, - } - } - - -} - From e3bccb99c2d20922a302e1475b3a7995ba85e012 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Tue, 10 Nov 2015 13:56:16 +1100 Subject: [PATCH 011/124] Minor changes before switching to update master --- src/theme.rs | 6 +++++- src/widget/button.rs | 6 ++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/theme.rs b/src/theme.rs index d85579aed..3cde53747 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -2,6 +2,7 @@ //! Types a functionality for handling Canvas and Widget theming. //! +use Scalar; use color::{Color, black, white}; use json_io; use position::{Margin, Padding, Position, Horizontal, HorizontalAlign, Vertical, VerticalAlign}; @@ -31,7 +32,7 @@ pub struct Theme { /// A default color for widget frames. pub frame_color: Color, /// A default width for widget frames. - pub frame_width: f64, + pub frame_width: Scalar, /// A default color for widget labels. pub label_color: Color, /// A default "large" font size. @@ -40,6 +41,9 @@ pub struct Theme { pub font_size_medium: u32, /// A default "small" font size. pub font_size_small: u32, + + //pub widget_styling: HashMap<'static str, widget::Style + /// Optional style defaults for a Button widget. pub maybe_button: Option>, /// Optional style defaults for a Canvas widget. diff --git a/src/widget/button.rs b/src/widget/button.rs index 7cd2a1d44..f5c0a4468 100644 --- a/src/widget/button.rs +++ b/src/widget/button.rs @@ -189,8 +189,7 @@ impl<'a, F> Widget for Button<'a, F> where F: FnMut() { let frame_color = style.frame_color(ui.theme()); FramedRectangle::new(dim) .point(xy) - .depth(1.0) - .parent(maybe_parent_idx) + .parent(Some(idx)) .color(color) .frame(frame) .frame_color(frame_color) @@ -204,10 +203,9 @@ impl<'a, F> Widget for Button<'a, F> where F: FnMut() { let font_size = style.label_font_size(ui.theme()); Label::new(label) .point(xy) - .depth(1.0) .color(color) .font_size(font_size) - .parent(maybe_parent_idx) + .parent(Some(idx)) .set(label_idx, &mut ui); label_idx }); From 6c49b7cb4986381637384bf46c2b980b5fdc660d Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 12 Nov 2015 13:30:36 +1100 Subject: [PATCH 012/124] Add picking_passthrough method to allow widgets to be instantiated transparently to picking. Still issues related to the Button internal child widgets --- src/graph/mod.rs | 18 +++++++++++------- src/widget/button.rs | 12 ++++++------ src/widget/mod.rs | 34 ++++++++++++++++++++++------------ 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/graph/mod.rs b/src/graph/mod.rs index 687bf4ede..c1f24b7f4 100644 --- a/src/graph/mod.rs +++ b/src/graph/mod.rs @@ -62,8 +62,8 @@ pub struct Container { /// Whether or not the `Widget`'s cache has was updated during the last update cycle. /// We need to know this so we can check whether or not a widget has been removed. pub was_previously_updated: bool, - /// Whether or not the widget blocks the mouse from interacting with widgets underneath it. - pub mouse_passthrough: bool, + /// Whether or not the widget is included when picking widgets by position. + pub picking_passthrough: bool, } /// A node within the UI Graph. @@ -231,9 +231,13 @@ impl Graph { .find(|&&visitable| { match visitable { Visitable::Widget(idx) => { - if let Some(visible_rect) = self.visible_area(idx) { - if visible_rect.is_over(xy) { - return true + if let Some(&Node::Widget(ref container)) = dag.node_weight(idx) { + if !container.picking_passthrough { + if let Some(visible_rect) = self.visible_area(idx) { + if visible_rect.is_over(xy) { + return true + } + } } } }, @@ -442,7 +446,7 @@ impl Graph { pub fn pre_update_cache(&mut self, widget: widget::PreUpdateCache) { let widget::PreUpdateCache { kind, idx, maybe_parent_idx, maybe_positioned_relatively_idx, rect, depth, kid_area, - drag_state, maybe_floating, maybe_scrolling, mouse_passthrough, + drag_state, maybe_floating, maybe_scrolling, picking_passthrough, } = widget; // Construct a new `Container` to place in the `Graph`. @@ -455,7 +459,7 @@ impl Graph { kid_area: kid_area, maybe_floating: maybe_floating, maybe_scrolling: maybe_scrolling, - mouse_passthrough: mouse_passthrough, + picking_passthrough: picking_passthrough, maybe_element: None, element_has_changed: false, is_updated: true, diff --git a/src/widget/button.rs b/src/widget/button.rs index f5c0a4468..b176613b8 100644 --- a/src/widget/button.rs +++ b/src/widget/button.rs @@ -153,7 +153,7 @@ impl<'a, F> Widget for Button<'a, F> where F: FnMut() { /// Update the state of the Button. fn update(self, args: widget::UpdateArgs) { - let widget::UpdateArgs { maybe_parent_idx, idx, state, style, rect, mut ui, .. } = args; + let widget::UpdateArgs { idx, state, style, rect, mut ui, .. } = args; let Button { enabled, maybe_label, mut maybe_react, .. } = self; let maybe_mouse = ui.input().maybe_mouse; @@ -183,13 +183,13 @@ impl<'a, F> Widget for Button<'a, F> where F: FnMut() { // FramedRectangle widget. let rectangle_idx = state.view().maybe_rectangle_idx .unwrap_or_else(|| ui.new_unique_node_index()); - let (xy, dim) = rect.xy_dim(); + let dim = rect.dim(); let frame = style.frame(ui.theme()); let color = new_interaction.color(style.color(ui.theme())); let frame_color = style.frame_color(ui.theme()); FramedRectangle::new(dim) - .point(xy) - .parent(Some(idx)) + .middle_of(idx) + .picking_passthrough(true) .color(color) .frame(frame) .frame_color(frame_color) @@ -202,10 +202,10 @@ impl<'a, F> Widget for Button<'a, F> where F: FnMut() { let color = style.label_color(ui.theme()); let font_size = style.label_font_size(ui.theme()); Label::new(label) - .point(xy) + .middle_of(rectangle_idx) + .picking_passthrough(true) .color(color) .font_size(font_size) - .parent(Some(idx)) .set(label_idx, &mut ui); label_idx }); diff --git a/src/widget/mod.rs b/src/widget/mod.rs index d1a27a02f..17c1148fd 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -169,6 +169,12 @@ pub struct CommonBuilder { pub is_floating: bool, /// Builder data for scrollable widgets. pub scrolling: scroll::Scrolling, + /// Whether or not the widget should be considered when picking the topmost widget at a + /// position. + /// + /// This is useful to indicate whether or not the widget should block the mouse from + /// interacting with widgets underneath it. + pub picking_passthrough: bool, } /// A wrapper around a **Widget**'s unique **Widget::State**. @@ -252,7 +258,7 @@ pub struct PreUpdateCache { /// Scrolling data for the **Widget** if there is some. pub maybe_scrolling: Option, /// Indicates whether the widget blocks the mouse from interacting with widgets underneath it. - pub mouse_passthrough: bool, + pub picking_passthrough: bool, } /// **Widget** data to be cached after the **Widget::update** call in the **set_widget** @@ -309,7 +315,7 @@ impl<'a, C> UiRefMut for UiCell<'a, C> where C: CharacterCache { /// /// Methods that can be optionally overridden: /// - draw -/// - mouse_passthrough +/// - picking_passthrough /// - default_position /// - default_width /// - default_height @@ -403,15 +409,6 @@ pub trait Widget: Sized { ::elmesque::element::empty() } - /// Indicates whether or not the **Widget** should block the mouse from interacting with - /// widgets underneath it. - /// - /// It may be useful to override this for widgets that simply act as graphical elements, - /// allowing the mouse to pass through itself and interact with the underlying widget. - fn mouse_passthrough(&self) -> bool { - false - } - /// The default Position for the widget. /// This is used when no Position is explicitly given when instantiating the Widget. fn default_position(&self, _theme: &Theme) -> Position { @@ -464,6 +461,8 @@ pub trait Widget: Sized { // None of the following methods should require overriding. Perhaps they should be split off // into a separate trait which is impl'ed for W: Widget to make this clearer? + // Most of them would benefit by some sort of field inheritance as they are mainly just used to + // set sommon data. /// Set the parent widget for this Widget by passing the WidgetId of the parent. @@ -476,6 +475,16 @@ pub trait Widget: Sized { self } + /// Whether or not the widget should be considered when picking the topmost widget at a + /// position. + /// + /// This is useful to indicate whether or not the widget should block the mouse from + /// interacting with widgets underneath it. + fn picking_passthrough(mut self, passthrough: bool) -> Self { + self.common_mut().picking_passthrough = passthrough; + self + } + /// Set whether or not the widget is floating (the default is `false`). /// A typical example of a floating widget would be a pop-up or alert window. /// @@ -768,7 +777,7 @@ fn set_widget<'a, C, W>(widget: W, idx: Index, ui: &mut Ui) where kid_area: kid_area, maybe_floating: maybe_floating, maybe_scrolling: maybe_new_scrolling, - mouse_passthrough: widget.mouse_passthrough(), + picking_passthrough: widget.common().picking_passthrough, }); } @@ -967,6 +976,7 @@ impl CommonBuilder { maybe_v_align: None, maybe_depth: None, maybe_parent_idx: MaybeParent::Unspecified, + picking_passthrough: false, is_floating: false, scrolling: scroll::Scrolling::new(), } From 54d5b7a379b2cf59226e902bd763330f8ce08731 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 12 Nov 2015 18:08:02 +1100 Subject: [PATCH 013/124] Update all_widgets to use conrod Circle rather than from graphics crate. Remove unnecessary graphics and vecmath crates. --- examples/all_widgets.rs | 133 ++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 67 deletions(-) diff --git a/examples/all_widgets.rs b/examples/all_widgets.rs index 189f4f068..9944748c2 100644 --- a/examples/all_widgets.rs +++ b/examples/all_widgets.rs @@ -1,25 +1,22 @@ //! -//! -//! A demonstration of all widgets available in Conrod. +//! A demonstration of all non-primitive widgets available in Conrod. //! //! //! Don't be put off by the number of method calls, they are only for demonstration and almost all //! of them are optional. Conrod supports `Theme`s, so if you don't give it an argument, it will //! check the current `Theme` within the `Ui` and retrieve defaults from there. //! -//! #[macro_use] extern crate conrod; extern crate find_folder; extern crate glutin_window; -extern crate graphics; extern crate opengl_graphics; extern crate piston; -extern crate vecmath; use conrod::{ Button, + Circle, Color, Colorable, DropDownList, @@ -115,7 +112,7 @@ impl DemoApp { "Blue".to_string()], ddl_color: purple(), selected_idx: None, - circle_pos: [560.0, 310.0], + circle_pos: [-50.0, 110.0], envelopes: vec![(vec![ [0.0, 0.0], [0.1, 17000.0], [0.25, 8000.0], @@ -152,7 +149,7 @@ fn main() { let theme = Theme::default(); let glyph_cache = GlyphCache::new(&font_path).unwrap(); let mut ui = Ui::new(glyph_cache, theme); - let mut demo = DemoApp::new(); + let mut app = DemoApp::new(); for event in event_iter { ui.handle_event(&event); @@ -163,7 +160,7 @@ fn main() { // At the moment conrod requires that we set our widgets in the Render loop, // however soon we'll add support so that you can set your Widgets at any arbitrary // update rate. - set_widgets(&mut ui, &mut demo); + set_widgets(&mut ui, &mut app); // Draw our Ui! // @@ -174,11 +171,6 @@ fn main() { // // If instead you need to re-draw your conrod GUI every frame, use `Ui::draw`. ui.draw_if_changed(c, gl); - - // Draw the circle that's controlled by our XYPad. - graphics::Ellipse::new(demo.ddl_color.to_fsa()) - .draw([demo.circle_pos[0], demo.circle_pos[1], 30.0, 30.0], - &c.draw_state, c.transform, gl); }); } } @@ -192,36 +184,36 @@ fn main() { /// the `Ui` at their given indices. Every other time this get called, the `Widget`s will avoid any /// allocations by updating the pre-existing cached state. A new graphical `Element` is only /// retrieved from a `Widget` in the case that it's `State` has changed in some way. -fn set_widgets(ui: &mut Ui, demo: &mut DemoApp) { +fn set_widgets(ui: &mut Ui, app: &mut DemoApp) { // Normally, `Split`s can be used to describe the layout of `Canvas`ses within a window (see // the canvas.rs example for a demonstration of this). However, when only one `Split` is used // (as in this case) a single `Canvas` will simply fill the screen. // We can use this `Canvas` as a parent Widget upon which we can place other widgets. - Split::new(CANVAS).frame(demo.frame_width).color(demo.bg_color).scrolling(true).set(ui); + Split::new(CANVAS).frame(app.frame_width).color(app.bg_color).scrolling(true).set(ui); // Calculate x and y coords for title (temporary until `Canvas`es are implemented, see #380). - let title_x = demo.title_pad - (ui.win_w / 2.0) + 185.0; + let title_x = app.title_pad - (ui.win_w / 2.0) + 185.0; let title_y = (ui.win_h / 2.0) - 50.0; // Label example. Label::new("Widget Demonstration") .xy(title_x, title_y) .font_size(32) - .color(demo.bg_color.plain_contrast()) + .color(app.bg_color.plain_contrast()) .parent(Some(CANVAS)) .set(TITLE, ui); - if demo.show_button { + if app.show_button { // Button widget example button. Button::new() .dimensions(200.0, 50.0) .xy(140.0 - (ui.win_w / 2.0), title_y - 70.0) .rgb(0.4, 0.75, 0.6) - .frame(demo.frame_width) + .frame(app.frame_width) .label("PRESS") - .react(|| demo.bg_color = color::random()) + .react(|| app.bg_color = color::random()) .set(BUTTON, ui) } @@ -230,7 +222,7 @@ fn set_widgets(ui: &mut Ui, demo: &mut DemoApp) { else { // Create the label for the slider. - let pad = demo.title_pad as i16; + let pad = app.title_pad as i16; let pad_string = pad.to_string(); let label = { let mut text = "Padding: ".to_string(); @@ -243,31 +235,31 @@ fn set_widgets(ui: &mut Ui, demo: &mut DemoApp) { .dimensions(200.0, 50.0) .xy(140.0 - (ui.win_w / 2.0), title_y - 70.0) .rgb(0.5, 0.3, 0.6) - .frame(demo.frame_width) + .frame(app.frame_width) .label(&label) .label_color(white()) - .react(|new_pad: f32| demo.title_pad = new_pad as f64) + .react(|new_pad: f32| app.title_pad = new_pad as f64) .set(TITLE_PAD_SLIDER, ui); } // Clone the label toggle to be drawn. - let label = demo.toggle_label.clone(); + let label = app.toggle_label.clone(); // Keep track of the currently shown widget. - let shown_widget = if demo.show_button { BUTTON } else { TITLE_PAD_SLIDER }; + let shown_widget = if app.show_button { BUTTON } else { TITLE_PAD_SLIDER }; // Toggle widget example toggle(value). - Toggle::new(demo.show_button) + Toggle::new(app.show_button) .dimensions(75.0, 75.0) .down(20.0) .rgb(0.6, 0.25, 0.75) - .frame(demo.frame_width) + .frame(app.frame_width) .label(&label) .label_color(white()) .react(|value| { - demo.show_button = value; - demo.toggle_label = match value { + app.show_button = value; + app.toggle_label = match value { true => "ON".to_string(), false => "OFF".to_string() } @@ -287,9 +279,9 @@ fn set_widgets(ui: &mut Ui, demo: &mut DemoApp) { // Grab the value of the color element. let value = match i { - 0 => demo.bg_color.red(), - 1 => demo.bg_color.green(), - _ => demo.bg_color.blue(), + 0 => app.bg_color.red(), + 1 => app.bg_color.green(), + _ => app.bg_color.blue(), }; // Create the label to be drawn with the slider. @@ -299,41 +291,41 @@ fn set_widgets(ui: &mut Ui, demo: &mut DemoApp) { // Slider widget examples. slider(value, min, max) if i == 0 { Slider::new(value, 0.0, 1.0).down(25.0) } else { Slider::new(value, 0.0, 1.0).right(20.0) } - .dimensions(40.0, demo.v_slider_height) + .dimensions(40.0, app.v_slider_height) .color(color) - .frame(demo.frame_width) + .frame(app.frame_width) .label(&label) .label_color(white()) .react(|color| match i { - 0 => demo.bg_color.set_red(color), - 1 => demo.bg_color.set_green(color), - _ => demo.bg_color.set_blue(color), + 0 => app.bg_color.set_red(color), + 1 => app.bg_color.set_green(color), + _ => app.bg_color.set_blue(color), }) .set(COLOR_SLIDER + i, ui); } // Number Dialer widget example. (value, min, max, precision) - NumberDialer::new(demo.v_slider_height, 25.0, 250.0, 1) + NumberDialer::new(app.v_slider_height, 25.0, 250.0, 1) .dimensions(260.0, 60.0) .right_from(shown_widget, 30.0) - .color(demo.bg_color.invert()) - .frame(demo.frame_width) + .color(app.bg_color.invert()) + .frame(app.frame_width) .label("Height (px)") - .label_color(demo.bg_color.invert().plain_contrast()) - .react(|new_height| demo.v_slider_height = new_height) + .label_color(app.bg_color.invert().plain_contrast()) + .react(|new_height| app.v_slider_height = new_height) .set(SLIDER_HEIGHT, ui); // Number Dialer widget example. (value, min, max, precision) - NumberDialer::new(demo.frame_width, 0.0, 15.0, 2) + NumberDialer::new(app.frame_width, 0.0, 15.0, 2) .dimensions(260.0, 60.0) .down(20.0) - .color(demo.bg_color.invert().plain_contrast()) - .frame(demo.frame_width) - .frame_color(demo.bg_color.plain_contrast()) + .color(app.bg_color.invert().plain_contrast()) + .frame(app.frame_width) + .frame_color(app.bg_color.plain_contrast()) .label("Frame Width (px)") - .label_color(demo.bg_color.plain_contrast()) - .react(|new_width| demo.frame_width = new_width) + .label_color(app.bg_color.plain_contrast()) + .react(|new_width| app.frame_width = new_width) .set(FRAME_WIDTH, ui); // A demonstration using widget_matrix to easily draw @@ -356,22 +348,22 @@ fn set_widgets(ui: &mut Ui, demo: &mut DemoApp) { // You can return any type that implements `Widget`. // The returned widget will automatically be positioned and sized to the matrix // element's rectangle. - let elem = &mut demo.bool_matrix[col][row]; + let elem = &mut app.bool_matrix[col][row]; Toggle::new(*elem) .rgba(r, g, b, a) - .frame(demo.frame_width) + .frame(app.frame_width) .react(move |new_val: bool| *elem = new_val) }) .set(TOGGLE_MATRIX, ui); // A demonstration using a DropDownList to select its own color. - let mut ddl_color = demo.ddl_color; - DropDownList::new(&mut demo.ddl_colors, &mut demo.selected_idx) + let mut ddl_color = app.ddl_color; + DropDownList::new(&mut app.ddl_colors, &mut app.selected_idx) .dimensions(150.0, 40.0) .right_from(SLIDER_HEIGHT, 30.0) // Position right from widget 6 by 50 pixels. .max_visible_items(3) .color(ddl_color) - .frame(demo.frame_width) + .frame(app.frame_width) .frame_color(ddl_color.plain_contrast()) .label("Colors") .label_color(ddl_color.plain_contrast()) @@ -387,39 +379,45 @@ fn set_widgets(ui: &mut Ui, demo: &mut DemoApp) { }; }) .set(COLOR_SELECT, ui); - demo.ddl_color = ddl_color; + app.ddl_color = ddl_color; // Draw an xy_pad. - XYPad::new(demo.circle_pos[0], 550.0, 700.0, // x range. - demo.circle_pos[1], 320.0, 170.0) // y range. + XYPad::new(app.circle_pos[0], -75.0, 75.0, // x range. + app.circle_pos[1], 95.0, 245.0) // y range. .dimensions(150.0, 150.0) .right_from(TOGGLE_MATRIX, 30.0) .align_bottom() // Align to the bottom of the last TOGGLE_MATRIX element. .color(ddl_color) - .frame(demo.frame_width) + .frame(app.frame_width) .frame_color(white()) .label("Circle Position") .label_color(ddl_color.plain_contrast().alpha(0.5)) .line_width(2.0) .react(|new_x, new_y| { - demo.circle_pos[0] = new_x; - demo.circle_pos[1] = new_y; + app.circle_pos[0] = new_x; + app.circle_pos[1] = new_y; }) .set(CIRCLE_POSITION, ui); + // Draw a circle at the app's circle_pos. + Circle::fill(15.0) + .relative_to(CIRCLE_POSITION, app.circle_pos) + .color(app.ddl_color) + .set(CIRCLE, ui); + // Draw two TextBox and EnvelopeEditor pairs to the right of the DropDownList flowing downward. for i in 0..2 { - let &mut (ref mut env, ref mut text) = &mut demo.envelopes[i]; + let &mut (ref mut env, ref mut text) = &mut app.envelopes[i]; // Draw a TextBox. text_box(&mut String, FontSize) if i == 0 { TextBox::new(text).right_from(COLOR_SELECT, 30.0) } else { TextBox::new(text) } .font_size(20) .dimensions(320.0, 40.0) - .frame(demo.frame_width) - .frame_color(demo.bg_color.invert().plain_contrast()) - .color(demo.bg_color.invert()) + .frame(app.frame_width) + .frame_color(app.bg_color.invert().plain_contrast()) + .color(app.bg_color.invert()) .react(|_string: &mut String|{}) .set(ENVELOPE_EDITOR + (i * 2), ui); @@ -431,11 +429,11 @@ fn set_widgets(ui: &mut Ui, demo: &mut DemoApp) { .down(10.0) .dimensions(320.0, 150.0) .skew_y(env_skew_y) - .color(demo.bg_color.invert()) - .frame(demo.frame_width) - .frame_color(demo.bg_color.invert().plain_contrast()) + .color(app.bg_color.invert()) + .frame(app.frame_width) + .frame_color(app.bg_color.invert().plain_contrast()) .label(&text) - .label_color(demo.bg_color.invert().plain_contrast().alpha(0.5)) + .label_color(app.bg_color.invert().plain_contrast().alpha(0.5)) .point_radius(6.0) .line_width(2.0) .react(|_points: &mut Vec, _idx: usize|{}) @@ -465,5 +463,6 @@ widget_ids! { TOGGLE_MATRIX, COLOR_SELECT, CIRCLE_POSITION, + CIRCLE, ENVELOPE_EDITOR with 4 } From 594655c970e360002c8a2d27b2d3ae67f76575c3 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 14 Nov 2015 00:08:57 +1100 Subject: [PATCH 014/124] Fix bug where scroll offset would behave incorrectly if one widget was both positional and depth parent to another. Simplify other graph module functions --- src/graph/mod.rs | 131 +++++++++++++++++++++++----------------------- src/widget/mod.rs | 1 + 2 files changed, 67 insertions(+), 65 deletions(-) diff --git a/src/graph/mod.rs b/src/graph/mod.rs index c1f24b7f4..9558fa20d 100644 --- a/src/graph/mod.rs +++ b/src/graph/mod.rs @@ -1,5 +1,6 @@ +use Scalar; use daggy::{self, Walker}; use elmesque::Element; use elmesque::element::layers; @@ -139,7 +140,7 @@ impl Container { { self.maybe_state.take().map(|any_state| { any_state.downcast().ok() - .expect("Failed to downcast from Box to required UniqueWidgetState") + .expect("Failed to downcast from `Box` to the required UniqueWidgetState") }) } @@ -293,39 +294,50 @@ impl Graph { None => return offset, }; + // Check the widget at the given index for any scroll offset and add it to our total offset. + let add_to_offset = |offset: &mut [Scalar; 2], idx: NodeIndex| { + if let Some(&Node::Widget(ref container)) = dag.node_weight(idx) { + if let Some(ref scrolling) = container.maybe_scrolling { + let scroll_offset = scrolling.kids_pos_offset(); + offset[0] += scroll_offset[0].round(); + offset[1] += scroll_offset[1].round(); + } + } + }; + // We know that our graph shouldn't cycle at all, so we can safely use loop to traverse all // parent widget nodes and return when there are no more. - 'child_edge_traversal: loop { + 'parent_depth_edge_traversal: loop { - // We only need to worry about calculating any offset if there is some parent widget. + // We only need to calculate any offset if there is some parent depth widget. if let Some((_, parent_idx)) = maybe_parent_depth_edge(dag, idx) { // Recursively check all nodes with incoming `Position` edges for a parent that - // matches our own parent. If any match, then we don't need to calculate any additional - // offset as the widget we are being positioned relatively to has already applied the - // necessary scroll offset. + // matches our own parent. If any match, then we don't need to calculate any + // additional offset as the widget we are being positioned relatively to has + // already applied the necessary scroll offset. let mut current_node = idx; - 'relative_position_edge_traversal: loop { + 'parent_position_edge_traversal: loop { match maybe_parent_position_edge(dag, current_node) { - Some((_, node)) => match maybe_parent_depth_edge(dag, node) { - Some((_, parent_node)) if parent_node == parent_idx => return offset, - _ => current_node = node, + Some((_, parent_position_node)) => if parent_position_node == parent_idx { + // If our parent depth edge is also the parent position edge, we're + // add offset for that parent - otherwise we're done. + add_to_offset(&mut offset, parent_idx); + return offset; + } else { + match maybe_parent_depth_edge(dag, parent_position_node) { + Some((_, parent_depth_node)) if parent_depth_node == parent_idx => + return offset, + _ => current_node = parent_position_node, + } }, - None => break 'relative_position_edge_traversal, + None => break 'parent_position_edge_traversal, } } // Set the parent as the new current idx and continue traversing. idx = parent_idx; - - // Check the current widget for any scroll offset. - if let Some(&Node::Widget(ref container)) = dag.node_weight(idx) { - if let Some(ref scrolling) = container.maybe_scrolling { - let scroll_offset = scrolling.kids_pos_offset(); - offset[0] += scroll_offset[0].round(); - offset[1] += scroll_offset[1].round(); - } - } + add_to_offset(&mut offset, idx); // Otherwise if there are no more parent widgets, we're done calculating the offset. } else { @@ -371,7 +383,7 @@ impl Graph { /// Set's an `Edge::Position` from a to b. This edge represents the fact that b is /// positioned relatively to a's position. - fn set_relative_position_edge(&mut self, a: A, b: B) where + fn set_position_edge(&mut self, a: A, b: B) where A: GraphIndex, B: GraphIndex, { @@ -382,23 +394,6 @@ impl Graph { } - /// Remove the incoming relative position edge (if there is one) to the widget at the given - /// index. - fn remove_incoming_relative_position_edge(&mut self, idx: I) { - let Graph { ref mut dag, ref index_map, .. } = *self; - let node_idx = idx.to_node_index(index_map).expect(NO_MATCHING_NODE_INDEX); - let mut parents = dag.parents(node_idx); - while let Some((in_edge_idx, _)) = parents.next(dag) { - if let Edge::Position = dag[in_edge_idx] { - dag.remove_edge(in_edge_idx); - // Note that we only need to check for *one* edge as there can only ever be one - // incoming relative position edge per node. - break; - } - } - } - - /// The Rect that bounds the kids of the widget with the given ID. pub fn kids_bounding_box(&self, idx: I) -> Option { idx.to_node_index(&self.index_map) @@ -518,17 +513,16 @@ impl Graph { // Now that we've updated the widget's cached data, we need to check if we should add an // `Edge::Position`. if let Some(relative_idx) = maybe_positioned_relatively_idx { - self.set_relative_position_edge(relative_idx, idx); + self.set_position_edge(relative_idx, idx); // Otherwise if the widget is not positioned relatively to any other widget, we should // ensure that there are no incoming `Position` edges. } else { - self.remove_incoming_relative_position_edge(idx); + self.remove_parent_position_edge(idx); } } - /// Cache some `PostUpdateCache` widget data into the graph. /// /// This is called (via the `ui` module) from within the `widget::set_widget` function after @@ -561,6 +555,16 @@ impl Graph { } + /// Remove the parent position edge from the widget at the given index. + fn remove_parent_position_edge(&mut self, idx: I) { + let Graph { ref mut dag, ref index_map, .. } = *self; + let node_idx = idx.to_node_index(index_map).expect(NO_MATCHING_NODE_INDEX); + if let Some((edge_idx, _)) = maybe_parent_position_edge(dag, node_idx) { + dag.remove_edge(edge_idx); + } + } + + /// Return an `elmesque::Element` containing all widgets within the entire Graph. /// /// The order in which we will draw all widgets will be a akin to a depth-first search, where @@ -717,6 +721,17 @@ impl Graph { } +/// A predicate to pass to the `Walker::filter` method. +fn is_position_edge(dag: &Dag, e: EdgeIndex, _: NodeIndex) -> bool { + dag[e] == Edge::Position +} + +/// A predicate to pass to the `Walker::filter` method. +fn is_depth_edge(dag: &Dag, e: EdgeIndex, _: NodeIndex) -> bool { + dag[e] == Edge::Depth +} + + /// The box that bounds the widget with the given ID as well as all its widget kids. /// /// Bounds are given as (max y, min y, min x, max x) from the given target xy position. @@ -731,7 +746,7 @@ impl Graph { /// are the first widget in the recursion, and we will use ourselves as the deepest_parent_idx. fn bounding_box(graph: &Graph, include_self: bool, - target_xy: Option, + maybe_target_xy: Option, use_kid_area: bool, idx: NodeIndex, maybe_deepest_parent_idx: Option) -> Option @@ -763,7 +778,7 @@ fn bounding_box(graph: &Graph, // Determine our bounds relative to the target_xy position. let (xy, dim) = rect.xy_dim(); - let target_xy = target_xy.unwrap_or(xy); + let target_xy = maybe_target_xy.unwrap_or(xy); let relative_target_xy = ::vecmath::vec2_sub(xy, target_xy); let relative_bounds = || Rect::from_xy_dim(relative_target_xy, dim); @@ -771,14 +786,12 @@ fn bounding_box(graph: &Graph, let deepest_parent_idx = maybe_deepest_parent_idx.or(Some(idx)); // An iterator yielding the bounding_box returned by each of our children. - let mut kids_bounds = dag.children(idx).iter(&dag).nodes() - .filter_map(|kid_idx| dag.find_edge(idx, kid_idx).and_then(|kid_edge_idx| { - if let Edge::Depth = dag[kid_edge_idx] { - bounding_box(graph, true, Some(target_xy), false, kid_idx, deepest_parent_idx) - } else { - None - } - })); + let mut kids_bounds = dag.children(idx) + .filter(|g, e, _| g[e] == Edge::Depth) + .iter(dag).nodes() + .filter_map(|n| { + bounding_box(graph, true, Some(target_xy), false, n, deepest_parent_idx) + }); // Work out the initial bounds to use for our max_bounds fold. let init_bounds = if include_self { @@ -889,13 +902,7 @@ fn set_edge(dag: &mut Dag, a: NodeIndex, b: NodeIndex, edge: Edge) { fn maybe_parent_position_edge(dag: &Dag, idx: NodeIndex) -> Option<(EdgeIndex, NodeIndex)> { - let mut parents = dag.parents(idx); - while let Some((in_edge_idx, in_node_idx)) = parents.next(dag) { - if let Edge::Position = dag[in_edge_idx] { - return Some((in_edge_idx, in_node_idx)); - } - } - None + dag.parents(idx).find(dag, is_position_edge) } /// Return the incoming child edge (and the attached parent Node) if one exists. @@ -904,13 +911,7 @@ fn maybe_parent_position_edge(dag: &Dag, idx: NodeIndex) fn maybe_parent_depth_edge(dag: &Dag, idx: NodeIndex) -> Option<(EdgeIndex, NodeIndex)> { - let mut parents = dag.parents(idx); - while let Some((in_edge_idx, in_node_idx)) = parents.next(dag) { - if let Edge::Depth = dag[in_edge_idx] { - return Some((in_edge_idx, in_node_idx)); - } - } - None + dag.parents(idx).find(dag, is_depth_edge) } diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 17c1148fd..bf46a9439 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -760,6 +760,7 @@ fn set_widget<'a, C, W>(widget: W, idx: Index, ui: &mut Ui) where { // Some widget to which this widget is relatively positioned (if there is one). let maybe_positioned_relatively_idx = match pos { + Position::Place(_, maybe_idx) | Position::Relative(_, _, maybe_idx) | Position::Direction(_, _, maybe_idx) => maybe_idx.or(maybe_prev_widget_idx), _ => None, From 323856b654755e4eecbc64fa99e82a282334d419 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 14 Nov 2015 00:10:32 +1100 Subject: [PATCH 015/124] Simplify all_widgets example using piston_window --- examples/all_widgets.rs | 111 ++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 62 deletions(-) diff --git a/examples/all_widgets.rs b/examples/all_widgets.rs index 9944748c2..5b55d948a 100644 --- a/examples/all_widgets.rs +++ b/examples/all_widgets.rs @@ -10,9 +10,7 @@ #[macro_use] extern crate conrod; extern crate find_folder; -extern crate glutin_window; -extern crate opengl_graphics; -extern crate piston; +extern crate piston_window; use conrod::{ Button, @@ -38,22 +36,16 @@ use conrod::{ XYPad, }; use conrod::color::{self, rgb, white, black, red, green, blue, purple}; -use glutin_window::GlutinWindow; -use opengl_graphics::{GlGraphics, OpenGL}; -use opengl_graphics::glyph_cache::GlyphCache; -use piston::event_loop::{Events, EventLoop}; -use piston::input::{RenderEvent}; -use piston::window::{WindowSettings, Size}; - - -type Ui = conrod::Ui>; - -/// This struct holds all of the variables used to demonstrate -/// application data being passed through the widgets. If some -/// of these seem strange, that's because they are! Most of -/// these simply represent the aesthetic state of different -/// parts of the GUI to offer visual feedback during interaction -/// with the widgets. +use piston_window::{Glyphs, PistonWindow, WindowSettings}; + + +type Ui = conrod::Ui; + + +/// This struct holds all of the variables used to demonstrate application data being passed +/// through the widgets. If some of these seem strange, that's because they are! Most of these +/// simply represent the aesthetic state of different parts of the GUI to offer visual feedback +/// during interaction with the widgets. struct DemoApp { /// Background color (for demonstration of button and sliders). bg_color: Color, @@ -129,50 +121,46 @@ impl DemoApp { fn main() { - let opengl = OpenGL::V3_2; - let window: GlutinWindow = - WindowSettings::new( - "Hello Conrod".to_string(), - Size { width: 1100, height: 550 } - ) - .opengl(opengl) - .exit_on_esc(true) - .samples(4) - .build() - .unwrap(); - let event_iter = window.events().ups(60).max_fps(60); - let mut gl = GlGraphics::new(opengl); - - let assets = find_folder::Search::KidsThenParents(3, 5) - .for_folder("assets").unwrap(); - let font_path = assets.join("fonts/NotoSans/NotoSans-Regular.ttf"); - let theme = Theme::default(); - let glyph_cache = GlyphCache::new(&font_path).unwrap(); - let mut ui = Ui::new(glyph_cache, theme); + + // Construct the window. + let window: PistonWindow = + WindowSettings::new("All The Widgets!", [1100, 550]) + .exit_on_esc(true).build().unwrap(); + + // construct our `Ui`. + let mut ui = { + let assets = find_folder::Search::KidsThenParents(3, 5) + .for_folder("assets").unwrap(); + let font_path = assets.join("fonts/NotoSans/NotoSans-Regular.ttf"); + let theme = Theme::default(); + let glyph_cache = Glyphs::new(&font_path, window.factory.borrow().clone()); + Ui::new(glyph_cache.unwrap(), theme) + }; + + // Our dmonstration app that we'll control with our GUI. let mut app = DemoApp::new(); - for event in event_iter { + // Poll events from the window. + for event in window { ui.handle_event(&event); - if let Some(args) = event.render_args() { - gl.draw(args.viewport(), |c, gl| { - - // We'll set all our widgets in a single function called `set_widgets`. - // At the moment conrod requires that we set our widgets in the Render loop, - // however soon we'll add support so that you can set your Widgets at any arbitrary - // update rate. - set_widgets(&mut ui, &mut app); - - // Draw our Ui! - // - // The `draw_if_changed` method only re-draws the GUI if some `Widget`'s `Element` - // representation has changed. Normally, a `Widget`'s `Element` should only change - // if a Widget was interacted with in some way, however this is up to the `Widget` - // designer's discretion. - // - // If instead you need to re-draw your conrod GUI every frame, use `Ui::draw`. - ui.draw_if_changed(c, gl); - }); - } + event.draw_2d(|c, g| { + + // We'll set all our widgets in a single function called `set_widgets`. + // At the moment conrod requires that we set our widgets in the Render loop, + // however soon we'll add support so that you can set your Widgets at any arbitrary + // update rate. + set_widgets(&mut ui, &mut app); + + // Draw our Ui! + // + // The `draw_if_changed` method only re-draws the GUI if some `Widget`'s `Element` + // representation has changed. Normally, a `Widget`'s `Element` should only change + // if a Widget was interacted with in some way, however this is up to the `Widget` + // designer's discretion. + // + // If instead you need to re-draw your conrod GUI every frame, use `Ui::draw`. + ui.draw_if_changed(c, g); + }); } } @@ -285,8 +273,7 @@ fn set_widgets(ui: &mut Ui, app: &mut DemoApp) { }; // Create the label to be drawn with the slider. - let mut label = value.to_string(); - if label.len() > 4 { label.truncate(4); } + let label = format!("{:.*}", 2, value); // Slider widget examples. slider(value, min, max) if i == 0 { Slider::new(value, 0.0, 1.0).down(25.0) } From 11b2ddd277aa05a080a96b5ebfb47f4edb939c7c Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 14 Nov 2015 00:10:48 +1100 Subject: [PATCH 016/124] Clean up counter example --- examples/counter.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/counter.rs b/examples/counter.rs index 333ae4eea..6a8598d9c 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -2,18 +2,18 @@ extern crate find_folder; extern crate piston_window; -use conrod::{Colorable, Labelable, Sizeable, Theme, Ui, Widget}; -use piston_window::*; +use conrod::{Labelable, Positionable, Sizeable, Theme, Ui, Widget}; +use piston_window::{Glyphs, PistonWindow, WindowSettings}; fn main() { // Construct the window. - let window: PistonWindow = WindowSettings::new("Click me!", [200, 100]) + let window: PistonWindow = WindowSettings::new("Click me!", [200, 200]) .exit_on_esc(true).build().unwrap(); // construct our `Ui`. let mut ui = { - let assets = find_folder::Search::ParentsThenKids(3, 3) + let assets = find_folder::Search::KidsThenParents(3, 5) .for_folder("assets").unwrap(); let font_path = assets.join("fonts/NotoSans/NotoSans-Regular.ttf"); let theme = Theme::default(); @@ -28,15 +28,15 @@ fn main() { ui.handle_event(&event); event.draw_2d(|c, g| { - // Set the background color to use for clearing the screen. - conrod::Background::new().rgb(0.2, 0.25, 0.4).set(&mut ui); - // Generate the ID for the Button COUNTER. - widget_ids!(COUNTER); + widget_ids!(CANVAS, COUNTER); + + // Create a background canvas upon which we'll place the button. + conrod::Split::new(CANVAS).pad(40.0).set(&mut ui); // Draw the button and increment `count` if pressed. conrod::Button::new() - .color(conrod::color::red()) + .middle_of(CANVAS) .dimensions(80.0, 80.0) .label(&count.to_string()) .react(|| count += 1) From aeca37f0012726fd5ee3fe51681e7530865e22f8 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 15 Nov 2015 16:55:30 +1100 Subject: [PATCH 017/124] Change Theme to use hashmap for storing unique style defaults for widgets. Closes #506. Add Sizeable methods for setting a widget's dimensions as the size of some other widget. Made a start on a TitleBar widget. --- README.md | 17 ++- src/lib.rs | 4 +- src/position.rs | 161 ++++++++++++++++++++++------- src/theme.rs | 144 ++++++++++++++++---------- src/ui.rs | 39 ++++--- src/widget/button.rs | 35 +++---- src/widget/canvas.rs | 69 +++++++------ src/widget/drop_down_list.rs | 58 ++++++----- src/widget/envelope_editor.rs | 56 +++++----- src/widget/matrix.rs | 42 +++++--- src/widget/mod.rs | 145 ++++++++++++++++++++------ src/widget/number_dialer.rs | 52 ++++++---- src/widget/primitive/label.rs | 13 ++- src/widget/primitive/line.rs | 11 +- src/widget/primitive/point_path.rs | 9 +- src/widget/slider.rs | 51 +++++---- src/widget/tabs.rs | 29 ++++-- src/widget/text_box.rs | 48 +++++---- src/widget/title_bar.rs | 144 ++++++++++++++++++++++++++ src/widget/toggle.rs | 51 +++++---- src/widget/xy_pad.rs | 55 +++++----- 21 files changed, 850 insertions(+), 383 deletions(-) create mode 100644 src/widget/title_bar.rs diff --git a/README.md b/README.md index 24b0191f0..8dcecbbdd 100644 --- a/README.md +++ b/README.md @@ -53,14 +53,29 @@ Features Available Widgets ----------------- +### Primitives + +- Shapes: + - Circle + - Rectangle + - Oval + - Polygon + - FramedRectangle +- Label +- Line +- PointPath + +### Interactive + - [Button](http://docs.piston.rs/conrod/conrod/struct.Button.html) - [Canvas](http://docs.piston.rs/conrod/conrod/struct.Canvas.html) (Can be positioned manually or by using the [Split](http://docs.piston.rs/conrod/conrod/struct.Split.html) or [Tabs](http://docs.piston.rs/conrod/conrod/struct.Tabs.html) wrappers for auto-layout) - [DropDownList](http://docs.piston.rs/conrod/conrod/struct.DropDownList.html) - [EnvelopeEditor](http://docs.piston.rs/conrod/conrod/struct.EnvelopeEditor.html) -- [Label](http://docs.piston.rs/conrod/conrod/struct.Label.html) +- WidgetMatrix - [NumberDialer](http://docs.piston.rs/conrod/conrod/struct.NumberDialer.html) - [Slider](http://docs.piston.rs/conrod/conrod/struct.Slider.html) - [TextBox](http://docs.piston.rs/conrod/conrod/struct.TextBox.html) +- TitleBar - [Toggle](http://docs.piston.rs/conrod/conrod/struct.Toggle.html) - [XYPad](http://docs.piston.rs/conrod/conrod/struct.XYPad.html) - Custom: Conrod also provides a [Widget trait](http://docs.piston.rs/conrod/conrod/trait.Widget.html) for designing and implementing custom widgets. You can find an annotated demonstration of designing a custom widget implementation [here](https://github.com/PistonDevelopers/conrod/blob/master/examples/custom_widget.rs). All [internal widgets](https://github.com/PistonDevelopers/conrod/blob/master/src/widget) also use this same trait so they should make for decent examples. If you feel like your widget is useful enough to be included within the internal widget library, feel free to add them in a pull request :) diff --git a/src/lib.rs b/src/lib.rs index aac32f4a6..bb8d5ba8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,8 +77,8 @@ pub use mouse::Scroll as MouseScroll; pub use position::{align_left_of, align_right_of, align_bottom_of, align_top_of}; pub use position::{middle_of, top_left_of, top_right_of, bottom_left_of, bottom_right_of, mid_top_of, mid_bottom_of, mid_left_of, mid_right_of}; -pub use position::{Corner, Depth, Direction, Dimensions, Horizontal, HorizontalAlign, Margin, - Padding, Place, Point, Position, Positionable, Range, Rect, Sizeable, +pub use position::{Corner, Depth, Direction, Dimension, Dimensions, Horizontal, HorizontalAlign, + Margin, Padding, Place, Point, Position, Positionable, Range, Rect, Sizeable, Vertical, VerticalAlign}; pub use position::Matrix as PositionMatrix; pub use theme::{Align, Theme}; diff --git a/src/position.rs b/src/position.rs index ab218a6a5..cb114b6cc 100644 --- a/src/position.rs +++ b/src/position.rs @@ -1,7 +1,5 @@ -use graphics::character::CharacterCache; -use theme::Theme; -use ui::GlyphCache; +use {CharacterCache, Theme, Ui}; use widget; pub use graphics::math::Scalar; @@ -17,7 +15,15 @@ pub type Dimensions = [Scalar; 2]; /// General use 2D spatial point. pub type Point = [Scalar; 2]; -/// A cached widget's position for rendering. +/// The **Position** argument used to represent the positioning of a **Widget**. +/// +/// A **Position** is stored internally within the **widget::CommonBuilder** type, allowing all +/// widgets to be positioned in a variety of different ways. +/// +/// See the [**Positionable**](./trait.Positionable) trait for methods that allow for setting the +/// **Position** in various ways. +/// +/// Note that **Positionable** is implemented for *all* types that implement **Widget**. #[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] pub enum Position { /// A specific position. @@ -30,6 +36,23 @@ pub enum Position { Place(Place, Option), } +/// The length of a **Widget** over either the *x* or *y* axes. +/// +/// This type is used to represent the different ways in which a dimension may be sized. +/// +/// See the [**Sizeable**](./trait.Sizeable) trait for methods that allow for setting the +/// `x` and `y` **Dimension**s in various ways. +/// +/// Note that **Sizeable** is implemented for *all* types that implement **Widget**. +#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)] +pub enum Dimension { + /// Some specific length has been given. + Absolute(Scalar), + /// The dimension should match that of the widget at the given index. + Of(widget::Index), +} + + impl Position { /// The default widget Position. pub fn default() -> Position{ @@ -37,6 +60,7 @@ impl Position { } } + /// Directionally positioned, relative to another widget. #[derive(Copy, Clone, Debug, RustcEncodable, RustcDecodable, PartialEq, Eq)] pub enum Direction { @@ -104,6 +128,11 @@ pub enum Place { } /// Widgets that are positionable. +/// +/// A **Position** is stored internally within the **widget::CommonBuilder** type, allowing all +/// widgets to be positioned in a variety of different ways. +/// +/// Thus, **Positionable** can be implemented for *all* types that implement **Widget**. pub trait Positionable: Sized { /// Set the Position. @@ -353,17 +382,45 @@ pub trait Positionable: Sized { /// Widgets that support different dimensions. pub trait Sizeable: Sized { - /// Set the width for the widget. - fn width(self, width: Scalar) -> Self; + // Required implementations. - /// Set the height for the widget. - fn height(self, height: Scalar) -> Self; + /// Set the length along the x axis. + fn x_dimension(self, x: Dimension) -> Self; - /// Get the width of the widget. - fn get_width(&self, theme: &Theme, glyph_cache: &GlyphCache) -> Scalar; + /// Set the length along the y axis. + fn y_dimension(self, x: Dimension) -> Self; - /// Get the height of the widget. - fn get_height(&self, theme: &Theme) -> Scalar; + /// The widget's length along the x axis as a Dimension. + fn get_x_dimension(&self, ui: &Ui) -> Dimension; + + /// The widget's length along the y axis as a Dimension. + fn get_y_dimension(&self, ui: &Ui) -> Dimension; + + // Provided defaults. + + /// Set the absolute width for the widget. + #[inline] + fn width(self, w: Scalar) -> Self { + self.x_dimension(Dimension::Absolute(w)) + } + + /// Set the absolute height for the widget. + #[inline] + fn height(self, h: Scalar) -> Self { + self.y_dimension(Dimension::Absolute(h)) + } + + /// Set the width as the width of the widget at the given index. + #[inline] + fn width_of>(self, idx: I) -> Self { + self.x_dimension(Dimension::Of(idx.into())) + } + + /// Set the height as the height of the widget at the given index. + #[inline] + fn height_of>(self, idx: I) -> Self { + self.y_dimension(Dimension::Of(idx.into())) + } /// Set the dimensions for the widget. #[inline] @@ -377,11 +434,40 @@ pub trait Sizeable: Sized { self.dim([width, height]) } + /// Set the dimensions as the dimensions of the widget at the given index. + #[inline] + fn dim_of + Copy>(self, idx: I) -> Self { + self.width_of(idx).height_of(idx) + } + + /// Set the dimensions as the dimensions of the widget at the given index. + #[inline] + fn dimension_of + Copy>(self, idx: I) -> Self { + self.dim_of(idx) + } + + /// Get the absolute width of the widget as a Scalar value. + #[inline] + fn get_width(&self, ui: &Ui) -> Option { + match self.get_x_dimension(ui) { + Dimension::Absolute(width) => Some(width), + Dimension::Of(idx) => ui.width_of(idx), + } + } + + /// Get the height of the widget. + #[inline] + fn get_height(&self, ui: &Ui) -> Option { + match self.get_y_dimension(ui) { + Dimension::Absolute(height) => Some(height), + Dimension::Of(idx) => ui.height_of(idx), + } + } + /// The dimensions for the widget. - fn get_dimensions(&self, theme: &Theme, glyph_cache: &GlyphCache) - -> Dimensions - { - [self.get_width(theme, glyph_cache), self.get_height(theme)] + #[inline] + fn get_dim(&self, ui: &Ui) -> Option { + self.get_width(ui).and_then(|w| self.get_height(ui).map(|h| [w, h])) } } @@ -1004,11 +1090,12 @@ pub fn is_over_rect(rect_xy: Point, rect_dim: Dimensions, xy: Point) -> bool { pub mod matrix { - use ::{CharacterCache, GlyphCache}; + use {CharacterCache, Dimension}; use super::{Depth, Dimensions, HorizontalAlign, VerticalAlign, Point, Position, Positionable, Scalar, Sizeable}; use theme::Theme; use ui::{self, Ui}; + use widget; pub type WidgetNum = usize; pub type ColNum = usize; @@ -1024,8 +1111,8 @@ pub mod matrix { cols: usize, rows: usize, maybe_position: Option, - maybe_width: Option, - maybe_height: Option, + maybe_x_dimension: Option, + maybe_y_dimension: Option, maybe_h_align: Option, maybe_v_align: Option, cell_pad_w: Scalar, @@ -1040,8 +1127,8 @@ pub mod matrix { cols: cols, rows: rows, maybe_position: None, - maybe_width: None, - maybe_height: None, + maybe_x_dimension: None, + maybe_y_dimension: None, maybe_h_align: None, maybe_v_align: None, cell_pad_w: 0.0, @@ -1064,7 +1151,7 @@ pub mod matrix { use utils::map_range; let pos = self.get_position(&ui.theme); - let dim = self.get_dimensions(&ui.theme, &ui.glyph_cache); + let dim = self.get_dim(ui).unwrap_or([0.0, 0.0]); let (h_align, v_align) = self.get_alignment(&ui.theme); // If we can infer some new current parent from the position, set that as the current @@ -1073,7 +1160,7 @@ pub mod matrix { ui::set_current_parent_idx(ui, id); } - let xy = ui.get_xy(None, pos, dim, h_align, v_align); + let xy = ui.calc_xy(None, pos, dim, h_align, v_align); let (half_w, half_h) = (dim[0] / 2.0, dim[1] / 2.0); let widget_w = dim[0] / self.cols as f64; let widget_h = dim[1] / self.rows as f64; @@ -1136,29 +1223,29 @@ pub mod matrix { impl Sizeable for Matrix { #[inline] - fn width(mut self, w: f64) -> Self { - self.maybe_width = Some(w); + fn x_dimension(mut self, w: Dimension) -> Self { + self.maybe_x_dimension = Some(w); self } #[inline] - fn height(mut self, h: f64) -> Self { - self.maybe_height = Some(h); + fn y_dimension(mut self, h: Dimension) -> Self { + self.maybe_y_dimension = Some(h); self } #[inline] - fn get_width(&self, theme: &Theme, _: &GlyphCache) -> f64 { - const DEFAULT_WIDTH: Scalar = 256.0; - self.maybe_width.or_else(|| { - theme.maybe_matrix.as_ref() - .map(|default| default.common.maybe_width.unwrap_or(DEFAULT_WIDTH)) + fn get_x_dimension(&self, ui: &Ui) -> Dimension { + const DEFAULT_WIDTH: Dimension = Dimension::Absolute(256.0); + self.maybe_x_dimension.or_else(|| { + ui.theme.widget_style::(widget::matrix::KIND) + .map(|default| default.common.maybe_x_dimension.unwrap_or(DEFAULT_WIDTH)) }).unwrap_or(DEFAULT_WIDTH) } #[inline] - fn get_height(&self, theme: &Theme) -> f64 { - const DEFAULT_HEIGHT: Scalar = 256.0; - self.maybe_height.or_else(|| { - theme.maybe_matrix.as_ref() - .map(|default| default.common.maybe_height.unwrap_or(DEFAULT_HEIGHT)) + fn get_y_dimension(&self, ui: &Ui) -> Dimension { + const DEFAULT_HEIGHT: Dimension = Dimension::Absolute(256.0); + self.maybe_y_dimension.or_else(|| { + ui.theme.widget_style::(widget::matrix::KIND) + .map(|default| default.common.maybe_y_dimension.unwrap_or(DEFAULT_HEIGHT)) }).unwrap_or(DEFAULT_HEIGHT) } } diff --git a/src/theme.rs b/src/theme.rs index 3cde53747..f08b0b860 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -4,16 +4,19 @@ use Scalar; use color::{Color, black, white}; -use json_io; +//use json_io; use position::{Margin, Padding, Position, Horizontal, HorizontalAlign, Vertical, VerticalAlign}; use rustc_serialize::Encodable; +use std::any::Any; +use std::collections::HashMap; use std::error::Error; -use std::path::Path; +// use std::fmt::Debug; +// use std::path::Path; use widget; /// A serializable collection of canvas and widget styling defaults. -#[derive(Debug, Clone, RustcEncodable, RustcDecodable)] +// #[derive(Debug, Clone, RustcEncodable, RustcDecodable)] pub struct Theme { /// A name for the theme used for identification. pub name: String, @@ -41,37 +44,38 @@ pub struct Theme { pub font_size_medium: u32, /// A default "small" font size. pub font_size_small: u32, - - //pub widget_styling: HashMap<'static str, widget::Style - - /// Optional style defaults for a Button widget. - pub maybe_button: Option>, - /// Optional style defaults for a Canvas widget. - pub maybe_canvas: Option>, - /// Optional style defaults for a DropDownList. - pub maybe_drop_down_list: Option>, - /// Optional style defaults for an EnvelopeEditor. - pub maybe_envelope_editor: Option>, - /// Optional style defaults for a Line. - pub maybe_line: Option>, - /// Optional style defaults for a Matrix. - pub maybe_matrix: Option>, - /// Optional style defaults for a NumberDialer. - pub maybe_number_dialer: Option>, - /// Optional style defaults for a PointPath. - pub maybe_point_path: Option>, /// Optional style defaults for a Scrollbar. pub maybe_scrollbar: Option, - /// Optional style defaults for a Slider. - pub maybe_slider: Option>, - /// Optional style defaults for a Tabs widget. - pub maybe_tabs: Option>, - /// Optional style defaults for a TextBox. - pub maybe_text_box: Option>, - /// Optional style defaults for a Toggle. - pub maybe_toggle: Option>, - /// Optional style defaults for an XYPad. - pub maybe_xy_pad: Option>, + + /// Unique styling for each widget, index-able by the **Widget::kind**. + pub widget_styling: HashMap<&'static str, WidgetDefault>, + + // /// Optional style defaults for a Button widget. + // pub maybe_button: Option>, + // /// Optional style defaults for a Canvas widget. + // pub maybe_canvas: Option>, + // /// Optional style defaults for a DropDownList. + // pub maybe_drop_down_list: Option>, + // /// Optional style defaults for an EnvelopeEditor. + // pub maybe_envelope_editor: Option>, + // /// Optional style defaults for a Line. + // pub maybe_line: Option>, + // /// Optional style defaults for a Matrix. + // pub maybe_matrix: Option>, + // /// Optional style defaults for a NumberDialer. + // pub maybe_number_dialer: Option>, + // /// Optional style defaults for a PointPath. + // pub maybe_point_path: Option>, + // /// Optional style defaults for a Slider. + // pub maybe_slider: Option>, + // /// Optional style defaults for a Tabs widget. + // pub maybe_tabs: Option>, + // /// Optional style defaults for a TextBox. + // pub maybe_text_box: Option>, + // /// Optional style defaults for a Toggle. + // pub maybe_toggle: Option>, + // /// Optional style defaults for an XYPad. + // pub maybe_xy_pad: Option>, } /// The alignment of an element's dimensions with another's. @@ -84,18 +88,25 @@ pub struct Align { } /// The defaults for a specific widget. -#[derive(Debug, Clone, RustcEncodable, RustcDecodable)] -pub struct WidgetDefault { +pub struct WidgetDefault { /// The unique style of a widget. - pub style: T, + pub style: Box, /// The attributes commonly shared between widgets. pub common: widget::CommonBuilder, } +/// A **WidgetDefault** downcast to a **Widget**'s unique **Style** type. +#[derive(Copy, Clone, Debug)] +pub struct UniqueDefault<'a, T: 'a> { + /// The unique style for the widget. + pub style: &'a T, + /// Attributes that are common to between all widgets. + pub common: &'a widget::CommonBuilder, +} -impl WidgetDefault { +impl WidgetDefault { /// Constructor for a WidgetDefault. - pub fn new(style: T) -> WidgetDefault { + pub fn new(style: Box) -> WidgetDefault { WidgetDefault { style: style, common: widget::CommonBuilder::new(), @@ -136,31 +147,50 @@ impl Theme { font_size_medium: 18, font_size_small: 12, maybe_scrollbar: None, - maybe_button: None, - maybe_canvas: None, - maybe_drop_down_list: None, - maybe_envelope_editor: None, - maybe_line: None, - maybe_matrix: None, - maybe_number_dialer: None, - maybe_point_path: None, - maybe_slider: None, - maybe_tabs: None, - maybe_text_box: None, - maybe_toggle: None, - maybe_xy_pad: None, + widget_styling: HashMap::new(), + + // maybe_button: None, + // maybe_canvas: None, + // maybe_drop_down_list: None, + // maybe_envelope_editor: None, + // maybe_line: None, + // maybe_matrix: None, + // maybe_number_dialer: None, + // maybe_point_path: None, + // maybe_slider: None, + // maybe_tabs: None, + // maybe_text_box: None, + // maybe_toggle: None, + // maybe_xy_pad: None, } } - /// Load a theme from file. - pub fn load(path: &Path) -> Result { - json_io::load(path) + /// Retrieve the unique default styling for a widget. + /// + /// Attempts to cast the `Box` to the **Widget**'s unique style **T**. + pub fn widget_style(&self, kind: &'static str) -> Option> + where T: widget::Style, + { + self.widget_styling.get(kind).and_then(|boxed_default| { + boxed_default.style.downcast_ref().map(|style| { + let common = &boxed_default.common; + UniqueDefault { + style: style, + common: common, + } + }) + }) } - /// Save a theme to file. - pub fn save(&self, path: &Path) -> Result<(), json_io::Error> { - json_io::save(path, self) - } + // /// Load a theme from file. + // pub fn load(path: &Path) -> Result { + // json_io::load(path) + // } + + // /// Save a theme to file. + // pub fn save(&self, path: &Path) -> Result<(), json_io::Error> { + // json_io::save(path, self) + // } } diff --git a/src/ui.rs b/src/ui.rs index bc37a9380..76f93a4ed 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,10 +1,9 @@ +use {CharacterCache, FontSize, Scalar}; use color::Color; use elmesque::Element; use graph::Graph; use graphics::{Context, Graphics}; -use graphics::character::CharacterCache; -use label::FontSize; use mouse::{self, Mouse}; use input; use input::{ @@ -162,19 +161,35 @@ impl Ui { } } + /// The absolute width of the widget at the given index. + /// + /// Returns `None` if there is no widget for the given index. + pub fn width_of>(&self, idx: I) -> Option { + let idx: widget::Index = idx.into(); + self.widget_graph.get_widget(idx).map(|widget| widget.rect.w()) + } - /// Return the dimensions of a widget. - pub fn widget_size(&self, id: widget::Id) -> Dimensions { - self.widget_graph[id].rect.dim() + /// The absolute height of the widget at the given index. + /// + /// Returns `None` if there is no widget for the given index. + pub fn height_of>(&self, idx: I) -> Option { + let idx: widget::Index = idx.into(); + self.widget_graph.get_widget(idx).map(|widget| widget.rect.h()) } + /// The absolute dimensions for the widget at the given index. + /// + /// Returns `None` if there is no widget for the given index. + pub fn dim_of>(&self, idx: I) -> Option { + let idx: widget::Index = idx.into(); + self.widget_graph.get_widget(idx).map(|widget| widget.rect.dim()) + } /// An index to the previously updated widget if there is one. pub fn maybe_prev_widget(&self) -> Option { self.maybe_prev_widget_idx } - /// Handle game events and update the state. pub fn handle_event(&mut self, event: &E) { @@ -274,12 +289,12 @@ impl Ui { /// Get the centred xy coords for some given `Dimension`s, `Position` and alignment. /// If getting the xy for a widget, its ID should be specified so that we can also consider the /// scroll offset of the scrollable parent widgets. - pub fn get_xy(&self, - maybe_idx: Option, - position: Position, - dim: Dimensions, - h_align: HorizontalAlign, - v_align: VerticalAlign) -> Point + pub fn calc_xy(&self, + maybe_idx: Option, + position: Position, + dim: Dimensions, + h_align: HorizontalAlign, + v_align: VerticalAlign) -> Point { use vecmath::vec2_add; diff --git a/src/widget/button.rs b/src/widget/button.rs index b176613b8..5469864be 100644 --- a/src/widget/button.rs +++ b/src/widget/button.rs @@ -1,14 +1,13 @@ -use {Label, NodeIndex, FramedRectangle, Scalar}; +use {Label, NodeIndex, FramedRectangle, Ui}; use color::{Color, Colorable}; use elmesque::Element; use frame::Frameable; use graphics::character::CharacterCache; use label::{FontSize, Labelable}; use mouse::Mouse; -use position::Positionable; +use position::{Dimension, Positionable}; use theme::Theme; -use ui::GlyphCache; use widget::{self, Widget}; @@ -46,6 +45,9 @@ pub struct State { interaction: Interaction, } +/// Unique kind for the widget. +pub const KIND: widget::Kind = "Button"; + /// Represents an interaction with the Button widget. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Interaction { @@ -121,7 +123,7 @@ impl<'a, F> Widget for Button<'a, F> where F: FnMut() { &mut self.common } - fn unique_kind(&self) -> &'static str { + fn unique_kind(&self) -> widget::Kind { "Button" } @@ -137,18 +139,12 @@ impl<'a, F> Widget for Button<'a, F> where F: FnMut() { self.style.clone() } - fn default_width(&self, theme: &Theme, _: &GlyphCache) -> Scalar { - const DEFAULT_WIDTH: Scalar = 64.0; - theme.maybe_button.as_ref().map(|default| { - default.common.maybe_width.unwrap_or(DEFAULT_WIDTH) - }).unwrap_or(DEFAULT_WIDTH) + fn default_x_dimension(&self, ui: &Ui) -> Dimension { + widget::default_dimension(self, ui).unwrap_or(Dimension::Absolute(64.0)) } - fn default_height(&self, theme: &Theme) -> Scalar { - const DEFAULT_HEIGHT: Scalar = 64.0; - theme.maybe_button.as_ref().map(|default| { - default.common.maybe_height.unwrap_or(DEFAULT_HEIGHT) - }).unwrap_or(DEFAULT_HEIGHT) + fn default_y_dimension(&self, ui: &Ui) -> Dimension { + widget::default_dimension(self, ui).unwrap_or(Dimension::Absolute(64.0)) } /// Update the state of the Button. @@ -266,7 +262,6 @@ impl<'a, F> Widget for Button<'a, F> where F: FnMut() { } - impl Style { /// Construct the default Style. @@ -282,35 +277,35 @@ impl Style { /// Get the Color for an Element. pub fn color(&self, theme: &Theme) -> Color { - self.maybe_color.or(theme.maybe_button.as_ref().map(|default| { + self.maybe_color.or(theme.widget_style::(KIND).map(|default| { default.style.maybe_color.unwrap_or(theme.shape_color) })).unwrap_or(theme.shape_color) } /// Get the frame for an Element. pub fn frame(&self, theme: &Theme) -> f64 { - self.maybe_frame.or(theme.maybe_button.as_ref().map(|default| { + self.maybe_frame.or(theme.widget_style::(KIND).map(|default| { default.style.maybe_frame.unwrap_or(theme.frame_width) })).unwrap_or(theme.frame_width) } /// Get the frame Color for an Element. pub fn frame_color(&self, theme: &Theme) -> Color { - self.maybe_frame_color.or(theme.maybe_button.as_ref().map(|default| { + self.maybe_frame_color.or(theme.widget_style::(KIND).map(|default| { default.style.maybe_frame_color.unwrap_or(theme.frame_color) })).unwrap_or(theme.frame_color) } /// Get the label Color for an Element. pub fn label_color(&self, theme: &Theme) -> Color { - self.maybe_label_color.or(theme.maybe_button.as_ref().map(|default| { + self.maybe_label_color.or(theme.widget_style::(KIND).map(|default| { default.style.maybe_label_color.unwrap_or(theme.label_color) })).unwrap_or(theme.label_color) } /// Get the label font size for an Element. pub fn label_font_size(&self, theme: &Theme) -> FontSize { - self.maybe_label_font_size.or(theme.maybe_button.as_ref().map(|default| { + self.maybe_label_font_size.or(theme.widget_style::(KIND).map(|default| { default.style.maybe_label_font_size.unwrap_or(theme.font_size_medium) })).unwrap_or(theme.font_size_medium) } diff --git a/src/widget/canvas.rs b/src/widget/canvas.rs index a67c5a379..bdb3f09dd 100644 --- a/src/widget/canvas.rs +++ b/src/widget/canvas.rs @@ -1,15 +1,13 @@ - -use Scalar; +use {Scalar, Ui}; use time::precise_time_ns; use color::Color; use elmesque::element::Element; use graphics::character::CharacterCache; use label::FontSize; use mouse::Mouse; -use position::{self, Dimensions, Horizontal, Margin, Padding, Place, Point, Position, Rect}; +use position::{self, Dimension, Dimensions, Horizontal, Margin, Padding, Place, Point, Position, Rect}; use theme::Theme; use widget::{self, Widget}; -use ui::GlyphCache; /// A widget designed to be a parent for other widgets. @@ -32,6 +30,9 @@ pub struct State { /// The padding between the edge of the title bar and the title bar's label. const TITLE_BAR_LABEL_PADDING: f64 = 4.0; +/// Unique kind for the widget type. +pub const KIND: widget::Kind = "Canvas"; + /// State of the title bar. #[derive(Clone, Debug, PartialEq)] pub struct TitleBar { @@ -203,9 +204,18 @@ impl<'a> Widget for Canvas<'a> { type State = State; type Style = Style; - fn common(&self) -> &widget::CommonBuilder { &self.common } - fn common_mut(&mut self) -> &mut widget::CommonBuilder { &mut self.common } - fn unique_kind(&self) -> &'static str { "Canvas" } + fn common(&self) -> &widget::CommonBuilder { + &self.common + } + + fn common_mut(&mut self) -> &mut widget::CommonBuilder { + &mut self.common + } + + fn unique_kind(&self) -> widget::Kind { + KIND + } + fn init_state(&self) -> State { State { interaction: Interaction::Normal, @@ -213,6 +223,7 @@ impl<'a> Widget for Canvas<'a> { maybe_title_bar: None, } } + fn style(&self) -> Style { self.style.clone() } @@ -221,18 +232,12 @@ impl<'a> Widget for Canvas<'a> { Position::Place(Place::Middle, None) } - fn default_width(&self, theme: &Theme, _: &GlyphCache) -> Scalar { - const DEFAULT_WIDTH: Scalar = 160.0; - theme.maybe_button.as_ref().map(|default| { - default.common.maybe_width.unwrap_or(DEFAULT_WIDTH) - }).unwrap_or(DEFAULT_WIDTH) + fn default_x_dimension(&self, ui: &Ui) -> Dimension { + widget::default_dimension(self, ui).unwrap_or(Dimension::Absolute(64.0)) } - fn default_height(&self, theme: &Theme) -> Scalar { - const DEFAULT_HEIGHT: Scalar = 80.0; - theme.maybe_button.as_ref().map(|default| { - default.common.maybe_height.unwrap_or(DEFAULT_HEIGHT) - }).unwrap_or(DEFAULT_HEIGHT) + fn default_y_dimension(&self, ui: &Ui) -> Dimension { + widget::default_dimension(self, ui).unwrap_or(Dimension::Absolute(80.0)) } /// The title bar area at which the Canvas can be clicked and dragged. @@ -453,28 +458,28 @@ impl Style { /// Get the color for the Canvas' Element. pub fn color(&self, theme: &Theme) -> Color { - self.maybe_color.or(theme.maybe_canvas.as_ref().map(|default| { + self.maybe_color.or(theme.widget_style::