Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Gradients for Backgrounds #1597

Closed
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repository = "https://github.com/iced-rs/iced"

[dependencies]
bitflags = "1.2"
thiserror = "1"
log = "0.4"
twox-hash = { version = "1.5", default-features = false }

[dependencies.palette]
Expand Down
33 changes: 33 additions & 0 deletions core/src/angle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use crate::{Point, Rectangle, Vector};
use std::f32::consts::PI;

#[derive(Debug, Copy, Clone, PartialEq)]
/// Degrees
pub struct Degrees(pub f32);

#[derive(Debug, Copy, Clone, PartialEq)]
/// Radians
pub struct Radians(pub f32);

impl From<Degrees> for Radians {
fn from(degrees: Degrees) -> Self {
Radians(degrees.0 * PI / 180.0)
}
}

impl Radians {
/// Calculates the line in which the [`Angle`] intercepts the `bounds`.
pub fn to_distance(&self, bounds: &Rectangle) -> (Point, Point) {
let v1 = Vector::new(f32::cos(self.0), f32::sin(self.0));

let distance_to_rect = f32::min(
f32::abs((bounds.y - bounds.center().y) / v1.y),
f32::abs(((bounds.x + bounds.width) - bounds.center().x) / v1.x),
);

let start = bounds.center() + v1 * distance_to_rect;
let end = bounds.center() - v1 * distance_to_rect;

(start, end)
}
}
14 changes: 11 additions & 3 deletions core/src/background.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use crate::Color;
use crate::{Color, Gradient};

/// The background of some element.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Background {
/// A solid color
/// A solid color.
Color(Color),
// TODO: Add gradient and image variants
/// Interpolate between several colors.
Gradient(Gradient),
// TODO: Add image variant
}

impl From<Color> for Background {
Expand All @@ -19,3 +21,9 @@ impl From<Color> for Option<Background> {
Some(Background::from(color))
}
}

impl From<Gradient> for Option<Background> {
fn from(gradient: Gradient) -> Self {
Some(Background::Gradient(gradient))
}
}
184 changes: 104 additions & 80 deletions core/src/gradient.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
//! For creating a Gradient.
bungoboingo marked this conversation as resolved.
Show resolved Hide resolved
pub mod linear;

pub use linear::Linear;

use crate::{Color, Point, Size};
use crate::{Color, Radians};

#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq)]
/// A fill which transitions colors progressively along a direction, either linearly, radially (TBD),
/// or conically (TBD).
///
/// For a gradient which can be used as a fill on a canvas, see [`iced_graphics::Gradient`].
pub enum Gradient {
/// A linear gradient interpolates colors along a direction from its `start` to its `end`
/// point.
/// A linear gradient interpolates colors along a direction at a specific [`Angle`].
Linear(Linear),
}

impl Gradient {
/// Creates a new linear [`linear::Builder`].
pub fn linear(position: impl Into<Position>) -> linear::Builder {
linear::Builder::new(position.into())
///
/// This must be defined by an angle (in [`Degrees`] or [`Radians`])
/// which will use the bounds of the widget as a guide.
pub fn linear(angle: impl Into<Radians>) -> linear::Builder {
linear::Builder::new(angle.into())
}

/// Adjust the opacity of the gradient by a multiplier applied to each color stop.
pub fn mul_alpha(mut self, alpha_multiplier: f32) -> Self {
match &mut self {
Gradient::Linear(linear) => {
for stop in linear.color_stops.iter_mut().flatten() {
stop.color.a *= alpha_multiplier;
}
}
}

self
}
}

#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Default, Clone, Copy, PartialEq)]
/// A point along the gradient vector where the specified [`color`] is unmixed.
///
/// [`color`]: Self::color
Expand All @@ -35,83 +50,92 @@ pub struct ColorStop {
pub color: Color,
}

#[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,
},
}
pub mod linear {
//! Linear gradient builder & definition.
use crate::gradient::{ColorStop, Gradient};
use crate::{Color, Radians};
use std::cmp::Ordering;

impl From<(Point, Point)> for Position {
fn from((start, end): (Point, Point)) -> Self {
Self::Absolute { start, end }
/// A linear gradient that can be used as a [`Background`].
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Linear {
/// How the [`Gradient`] is angled within its bounds.
pub angle: Radians,
/// [`ColorStop`]s along the linear gradient path.
pub color_stops: [Option<ColorStop>; 8],
}
}

#[derive(Debug, Clone, Copy)]
/// 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,
}
/// A [`Linear`] builder.
#[derive(Debug)]
pub struct Builder {
angle: Radians,
stops: [Option<ColorStop>; 8],
}

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)
impl Builder {
/// Creates a new [`Builder`].
pub fn new(angle: Radians) -> Self {
Self {
angle,
stops: [None; 8],
}
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)
}

/// Adds a new [`ColorStop`], defined by an offset and a color, to the gradient.
///
/// `offset` must be between `0.0` and `1.0` or the gradient cannot be built.
///
/// Any stop added after the 8th will be silently ignored.
pub fn add_stop(mut self, offset: f32, color: Color) -> Self {
if offset.is_finite() && (0.0..=1.0).contains(&offset) {
match self.stops.binary_search_by(|stop| match stop {
None => Ordering::Greater,
Some(stop) => stop.offset.partial_cmp(&offset).unwrap(),
}) {
Ok(index) => {
if index < 8 {
self.stops[index] =
Some(ColorStop { offset, color });
}
}
Err(index) => {
if index < 8 {
self.stops[index] =
Some(ColorStop { offset, color });
}
}
}
} else {
log::warn!(
"Gradient: ColorStop must be within 0.0..=1.0 range."
);
};

self
}

/// Adds multiple [`ColorStop`]s to the gradient.
///
/// Any stop added after the 8th will be silently ignored.
pub fn add_stops(
mut self,
stops: impl IntoIterator<Item = ColorStop>,
) -> Self {
for stop in stops.into_iter() {
self = self.add_stop(stop.offset, stop.color)
}

self
}

/// Builds the linear [`Gradient`] of this [`Builder`].
///
/// Returns `BuilderError` if gradient in invalid.
pub fn build(self) -> Gradient {
Gradient::Linear(Linear {
angle: self.angle,
color_stops: self.stops,
})
}
}
}
112 changes: 0 additions & 112 deletions core/src/gradient/linear.rs

This file was deleted.

Loading