summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar shan <shankern@protonmail.com>2022-10-07 11:41:50 -0700
committerLibravatar shan <shankern@protonmail.com>2022-10-07 12:01:07 -0700
commit12a87c54eb68b992676060c80e518ffb29445cfc (patch)
treeb3fba16f2dd95a084cdbc3850f59a049c103c355
parentf9a6efcaa03728f43aaa105af8936c1ed4778388 (diff)
downloadiced-12a87c54eb68b992676060c80e518ffb29445cfc.tar.gz
iced-12a87c54eb68b992676060c80e518ffb29445cfc.tar.bz2
iced-12a87c54eb68b992676060c80e518ffb29445cfc.zip
Added support for relative positioning of gradient fills. Addressed some PR feedback.
-rw-r--r--examples/modern_art/src/main.rs41
-rw-r--r--examples/solar_system/src/main.rs7
-rw-r--r--glow/src/shader/common/gradient.frag3
-rw-r--r--graphics/src/gradient.rs22
-rw-r--r--graphics/src/gradient/linear.rs148
-rw-r--r--graphics/src/primitive.rs3
-rw-r--r--graphics/src/widget/canvas.rs2
-rw-r--r--graphics/src/widget/canvas/frame.rs10
8 files changed, 182 insertions, 54 deletions
diff --git a/examples/modern_art/src/main.rs b/examples/modern_art/src/main.rs
index 0601fc44..362e4ad1 100644
--- a/examples/modern_art/src/main.rs
+++ b/examples/modern_art/src/main.rs
@@ -1,5 +1,5 @@
use iced::widget::canvas::{
- self, fill, Cache, Canvas, Cursor, Fill, Frame, Geometry, Gradient,
+ self, Cache, Canvas, Cursor, Frame, Geometry, Gradient, Position, Location
};
use iced::{
executor, Application, Color, Command, Element, Length, Point, Rectangle,
@@ -76,6 +76,20 @@ impl<Message> canvas::Program<Message> for ModernArt {
}
}
+fn random_direction() -> Location {
+ match thread_rng().gen_range(0..8) {
+ 0 => Location::TopLeft,
+ 1 => Location::Top,
+ 2 => Location::TopRight,
+ 3 => Location::Right,
+ 4 => Location::BottomRight,
+ 5 => Location::Bottom,
+ 6 => Location::BottomLeft,
+ 7 => Location::Left,
+ _ => Location::TopLeft
+ }
+}
+
fn generate_box(frame: &mut Frame, bounds: Size) -> bool {
let solid = rand::random::<bool>();
@@ -87,8 +101,13 @@ fn generate_box(frame: &mut Frame, bounds: Size) -> bool {
)
};
- let gradient = |top_left: Point, bottom_right: Point| -> Gradient {
- let mut builder = Gradient::linear(top_left, bottom_right);
+ let gradient = |top_left: Point, size: Size| -> Gradient {
+ let mut builder = Gradient::linear(Position::Relative {
+ top_left,
+ size,
+ start: random_direction(),
+ end: random_direction()
+ });
let stops = thread_rng().gen_range(1..15u32);
let mut i = 0;
@@ -114,25 +133,13 @@ fn generate_box(frame: &mut Frame, bounds: Size) -> bool {
frame.fill_rectangle(
top_left,
size,
- Fill {
- style: fill::Style::Solid(random_color()),
- ..Default::default()
- },
+ random_color(),
);
} else {
frame.fill_rectangle(
top_left,
size,
- Fill {
- style: fill::Style::Gradient(&gradient(
- top_left,
- Point::new(
- top_left.x + size.width,
- top_left.y + size.height,
- ),
- )),
- ..Default::default()
- },
+ &gradient(top_left, size),
);
};
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index 6f38f480..9200391b 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -19,6 +19,7 @@ use iced::{
};
use std::time::Instant;
+use crate::canvas::Position;
pub fn main() -> iced::Result {
SolarSystem::run(Settings {
@@ -202,8 +203,10 @@ impl<Message> canvas::Program<Message> for State {
let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS);
let earth_fill = Gradient::linear(
- Point::new(-Self::EARTH_RADIUS, 0.0),
- Point::new(Self::EARTH_RADIUS, 0.0),
+ Position::Absolute {
+ start: Point::new(-Self::EARTH_RADIUS, 0.0),
+ end: Point::new(Self::EARTH_RADIUS, 0.0)
+ }
)
.add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0))
.add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47))
diff --git a/glow/src/shader/common/gradient.frag b/glow/src/shader/common/gradient.frag
index ade9c4a1..3c701af7 100644
--- a/glow/src/shader/common/gradient.frag
+++ b/glow/src/shader/common/gradient.frag
@@ -45,6 +45,9 @@ void main() {
fragColor = color_stops[0];
} else if (coord_offset > color_stops[color_stops_size - 1].x) {
fragColor = color_stops[color_stops_size - 2];
+ } else {
+ //This use case can happen if a gradient's start & end position are the same
+ fragColor = vec4(0.0, 0.0, 0.0, 0.0);
}
}
}
diff --git a/graphics/src/gradient.rs b/graphics/src/gradient.rs
index d785fb2a..e1984854 100644
--- a/graphics/src/gradient.rs
+++ b/graphics/src/gradient.rs
@@ -1,9 +1,10 @@
//! For creating a Gradient.
mod linear;
-pub use crate::gradient::linear::Linear;
+pub use crate::gradient::linear::{Linear, Position, Location};
use crate::widget::canvas::frame::Transform;
-use crate::{Point, Color};
+use crate::Color;
+use crate::widget::canvas::{Fill, fill};
#[derive(Debug, Clone, PartialEq)]
/// A fill which transitions colors progressively along a direction, either linearly, radially (TBD),
@@ -25,18 +26,27 @@ pub struct ColorStop {
impl Gradient {
/// Creates a new linear [`linear::Builder`].
- pub fn linear(start: Point, end: Point) -> linear::Builder {
- linear::Builder::new(start, end)
+ pub fn linear(position: impl Into<Position>) -> linear::Builder {
+ linear::Builder::new(position.into())
}
/// Modifies the start & end stops of the gradient to have a proper transform value.
pub(crate) fn transform(mut self, transform: &Transform) -> Self {
match &mut self {
Gradient::Linear(linear) => {
- linear.start = transform.apply_to(linear.start);
- linear.end = transform.apply_to(linear.end);
+ linear.start = transform.transform_point(linear.start);
+ linear.end = transform.transform_point(linear.end);
}
}
self
}
}
+
+impl<'a> Into<Fill<'a>> for &'a Gradient {
+ fn into(self) -> Fill<'a> {
+ Fill {
+ style: fill::Style::Gradient(self),
+ .. Default::default()
+ }
+ }
+}
diff --git a/graphics/src/gradient/linear.rs b/graphics/src/gradient/linear.rs
index 6bf69b43..a9cfd55d 100644
--- a/graphics/src/gradient/linear.rs
+++ b/graphics/src/gradient/linear.rs
@@ -1,7 +1,6 @@
//! Linear gradient builder & definition.
-
use crate::gradient::{ColorStop, Gradient};
-use crate::{Color, Point};
+use crate::{Color, Point, Size};
/// A linear gradient that can be used in the style of [`super::Fill`] or [`super::Stroke`].
#[derive(Debug, Clone, PartialEq)]
@@ -14,18 +13,115 @@ pub struct Linear {
pub color_stops: Vec<ColorStop>,
}
+#[derive(Debug)]
+/// The position of the gradient within its bounds.
+pub enum Position {
+ /// The gradient will be positioned with respect to two points.
+ Absolute {
+ /// The starting point of the gradient.
+ start: Point,
+ /// The ending point of the gradient.
+ end: Point,
+ },
+ /// The gradient will be positioned relative to the provided bounds.
+ Relative {
+ /// The top left position of the bounds.
+ top_left: Point,
+ /// The width & height of the bounds.
+ size: Size,
+ /// The start [Location] of the gradient.
+ start: Location,
+ /// The end [Location] of the gradient.
+ end: Location,
+ },
+}
+
+impl Into<Position> for (Point, Point) {
+ fn into(self) -> Position {
+ Position::Absolute {
+ start: self.0,
+ end: self.1,
+ }
+ }
+}
+
+#[derive(Debug)]
+/// The location of a relatively-positioned gradient.
+pub enum Location {
+ /// Top left.
+ TopLeft,
+ /// Top.
+ Top,
+ /// Top right.
+ TopRight,
+ /// Right.
+ Right,
+ /// Bottom right.
+ BottomRight,
+ /// Bottom.
+ Bottom,
+ /// Bottom left.
+ BottomLeft,
+ /// Left.
+ Left,
+}
+
+impl Location {
+ fn to_absolute(&self, top_left: Point, size: Size) -> Point {
+ match self {
+ Location::TopLeft => top_left,
+ Location::Top => {
+ Point::new(top_left.x + size.width / 2.0, top_left.y)
+ }
+ Location::TopRight => {
+ Point::new(top_left.x + size.width, top_left.y)
+ }
+ Location::Right => Point::new(
+ top_left.x + size.width,
+ top_left.y + size.height / 2.0,
+ ),
+ Location::BottomRight => {
+ Point::new(top_left.x + size.width, top_left.y + size.height)
+ }
+ Location::Bottom => Point::new(
+ top_left.x + size.width / 2.0,
+ top_left.y + size.height,
+ ),
+ Location::BottomLeft => {
+ Point::new(top_left.x, top_left.y + size.height)
+ }
+ Location::Left => {
+ Point::new(top_left.x, top_left.y + size.height / 2.0)
+ }
+ }
+ }
+}
+
/// A [`Linear`] builder.
#[derive(Debug)]
pub struct Builder {
start: Point,
end: Point,
- stops: Vec<(f32, Color)>,
+ stops: Vec<ColorStop>,
valid: bool,
}
impl Builder {
/// Creates a new [`Builder`].
- pub fn new(start: Point, end: Point) -> Self {
+ pub fn new(position: Position) -> Self {
+ let (start, end) = match position {
+ Position::Absolute { start, end } => (start, end),
+ Position::Relative {
+ top_left,
+ size,
+ start,
+ end,
+ } => (
+ start.to_absolute(top_left, size),
+ end.to_absolute(top_left, size),
+ ),
+ };
+
Self {
start,
end,
@@ -36,37 +132,47 @@ impl Builder {
/// Adds a new stop, defined by an offset and a color, to the gradient.
///
- /// `offset` must be between `0.0` and `1.0`.
+ /// `offset` must be between `0.0` and `1.0` or the gradient cannot be built.
+ ///
+ /// Note: when using the [Glow] backend, any color stop added after the 16th
+ /// will not be displayed.
+ ///
+ /// On [backend::Wgpu] backend this limitation does not exist (technical limit is 524,288 stops).
pub fn add_stop(mut self, offset: f32, color: Color) -> Self {
- if !(0.0..=1.0).contains(&offset) {
+ if offset.is_finite() && (0.0..=1.0).contains(&offset) {
+ match self.stops.binary_search_by(|stop| {
+ stop.offset.partial_cmp(&offset).unwrap()
+ }) {
+ Ok(_) => {
+ //the offset already exists in the gradient
+ self.valid = false;
+ }
+ Err(index) => {
+ self.stops.insert(index, ColorStop { offset, color })
+ }
+ }
+ } else {
self.valid = false;
}
-
- //TODO: can sort on insert here
- self.stops.push((offset, color));
self
}
/// Builds the linear [`Gradient`] of this [`Builder`].
///
- /// Returns `None` if no stops were added to the builder or
+ /// Returns `Err` if no stops were added to the builder or
/// if stops not between 0.0 and 1.0 were added.
- pub fn build(self) -> Option<Gradient> {
+ pub fn build(self) -> Result<Gradient, &'static str> {
if self.stops.is_empty() || !self.valid {
- return None;
+ return Err("Valid gradient conditions: \
+ 1) Must contain at least one color stop. \
+ 2) Every color stop offset must be unique. \
+ 3) Every color stop must be within the range of 0.0..=1.0");
}
- let mut stops: Vec<ColorStop> = self.stops.clone().into_iter().map(|f| ColorStop {
- offset: f.0,
- color: f.1
- }).collect();
-
- stops.sort_by(|a, b| a.offset.partial_cmp(&b.offset).unwrap());
-
- Some(Gradient::Linear(Linear {
+ Ok(Gradient::Linear(Linear {
start: self.start,
end: self.end,
- color_stops: stops
+ color_stops: self.stops,
}))
}
}
diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs
index 10c86ad2..5cb0ef23 100644
--- a/graphics/src/primitive.rs
+++ b/graphics/src/primitive.rs
@@ -2,11 +2,10 @@ use iced_native::image;
use iced_native::svg;
use iced_native::{Background, Color, Font, Rectangle, Size, Vector};
-use crate::{alignment, layer};
+use crate::{alignment, layer::mesh};
use crate::triangle;
use std::sync::Arc;
-use layer::mesh;
/// A rendering primitive.
#[derive(Debug, Clone)]
diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs
index fe0f618f..ea8af8c2 100644
--- a/graphics/src/widget/canvas.rs
+++ b/graphics/src/widget/canvas.rs
@@ -28,7 +28,7 @@ pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
pub use text::Text;
use crate::{Backend, Primitive, Renderer};
-pub use crate::gradient::Gradient;
+pub use crate::gradient::{Gradient, Position, Location};
use iced_native::layout::{self, Layout};
use iced_native::mouse;
diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs
index ccba840a..6dd0d06a 100644
--- a/graphics/src/widget/canvas/frame.rs
+++ b/graphics/src/widget/canvas/frame.rs
@@ -1,4 +1,4 @@
-use lyon::geom::euclid::Vector2D;
+use lyon::geom::euclid::Point2D;
use std::borrow::Cow;
use iced_native::{Point, Rectangle, Size, Vector};
@@ -38,11 +38,11 @@ pub(crate) struct Transform {
impl Transform {
/// Transforms the given [Point] by the transformation matrix.
- pub(crate) fn apply_to(&self, mut point: Point) -> Point {
+ pub(crate) fn transform_point(&self, mut point: Point) -> Point {
let transformed =
- self.raw.transform_vector(Vector2D::new(point.x, point.y));
- point.x = transformed.x + self.raw.m31;
- point.y = transformed.y + self.raw.m32;
+ self.raw.transform_point(Point2D::new(point.x, point.y));
+ point.x = transformed.x;
+ point.y = transformed.y;
point
}
}