Refactored GradientStops to support intermediary hint offsets. Also now normalized the offsets when converting to layout.

This commit is contained in:
Samuel Guerra 2020-12-08 18:38:34 -03:00
parent 40259b4520
commit 1b364d654e
6 changed files with 368 additions and 104 deletions

1
c-build-dump.bat Normal file
View File

@ -0,0 +1 @@
cargo build > dump.log 2>&1

View File

@ -22,9 +22,9 @@ fn linear_angle() -> impl Widget {
h_stack! {
spacing: 5;
items: (
//sample("linear 90º", linear_gradient(90.deg(), (colors::RED, colors::BLUE))),
//sample("linear 45º", linear_gradient(45.deg(), (colors::GREEN, colors::BLUE))),
sample("linear 0º", linear_gradient(0.deg(), (colors::BLACK, colors::GREEN))),
//sample("linear 90º", linear_gradient(90.deg(), [colors::RED, colors::BLUE])),
//sample("linear 45º", linear_gradient(45.deg(), [colors::GREEN, colors::BLUE])),
sample("linear 0º", linear_gradient(0.deg(), [colors::BLACK, colors::GREEN])),
);
}
}
@ -35,15 +35,15 @@ fn linear_points() -> impl Widget {
items: (
//sample(
// "linear points - clamp",
// linear_gradient_pt((30, 30), (90, 90), (colors::GREEN, colors::RED), ExtendMode::Clamp)
// linear_gradient_pt((30, 30), (90, 90), [colors::GREEN, colors::RED], ExtendMode::Clamp)
//),
//sample(
// "linear points - repeat",
// linear_gradient_pt((30, 30), (90, 90), (colors::GREEN, colors::RED), ExtendMode::Repeat)
// linear_gradient_pt((30, 30), (90, 90), [colors::GREEN, colors::RED], ExtendMode::Repeat)
//),
sample(
"test",
linear_gradient_pt((90, 180), (90, 0), (colors::BLACK, colors::GREEN), ExtendMode::Repeat)
linear_gradient_pt((90, 180), (90, 0), [colors::BLACK, colors::GREEN], ExtendMode::Repeat)
),
);
}
@ -56,11 +56,11 @@ fn linear_tile() -> impl Widget {
items: (
sample(
"linear tiles",
linear_gradient_tile(45.deg(), (colors::GREEN, colors::YELLOW), (w, w), (0, 0))
linear_gradient_tile(45.deg(), [colors::GREEN, colors::YELLOW], (w, w), (0, 0))
),
sample(
"linear tiles spaced",
linear_gradient_tile(45.deg(), (colors::MAGENTA, colors::AQUA), (w + 5, w + 5), (5, 5))
linear_gradient_tile(45.deg(), [colors::MAGENTA, colors::AQUA], (w + 5, w + 5), (5, 5))
),
);

View File

@ -703,6 +703,19 @@ pub fn hue_rotate<A: Into<AngleDegree>>(angle: A) -> Filter {
Filter::default().hue_rotate(angle)
}
/// Linear interpolate between `a` and `b` by the normalized `amount`.
pub fn lerp_render_color(a: RenderColor, b: RenderColor, amount: f32) -> RenderColor {
fn lerp(a: f32, b: f32, s: f32) -> f32 {
a + (b - a) * s
}
RenderColor {
r: lerp(a.r, b.r, amount),
g: lerp(a.g, b.g, amount),
b: lerp(a.b, b.b, amount),
a: lerp(a.a, b.a, amount),
}
}
/// Named web colors
pub mod colors {
use super::Rgba;
@ -1417,6 +1430,16 @@ pub mod colors {
///
/// `rgb(0, 0, 0)`
pub const BLACK: Rgba = rgb!(0, 0, 0);
/// Transparent (`#00000000`)
///
/// `rgba(0, 0, 0, 0)`
pub const TRANSPARENT: Rgba = Rgba {
red: 0.0,
green: 0.0,
blue: 0.0,
alpha: 0.0,
};
}
#[test]

View File

@ -617,7 +617,7 @@ impl FrameBuilder {
rect: LayoutRect,
start: LayoutPoint,
end: LayoutPoint,
stops: &[crate::widgets::LayoutGradientStop],
stops: &[crate::widgets::LayoutColorStop],
extend_mode: ExtendMode,
) {
if self.cancel_widget {
@ -649,7 +649,7 @@ impl FrameBuilder {
rect: LayoutRect,
start: LayoutPoint,
end: LayoutPoint,
stops: &[crate::widgets::LayoutGradientStop],
stops: &[crate::widgets::LayoutColorStop],
tile_size: LayoutSize,
tile_spacing: LayoutSize,
) {

View File

@ -30,7 +30,7 @@ mod build_tests {
fn _basic(child: impl UiNode) -> impl UiNode {
button! {
on_click: |_,_|{};
background_gradient: 90.deg(), vec![rgb(0.0, 0.0, 0.0), rgb(1.0, 1.0, 1.0)];
background_gradient: 90.deg(), [rgb(0.0, 0.0, 0.0), rgb(1.0, 1.0, 1.0)];
content: child;
}
}
@ -43,7 +43,7 @@ mod build_tests {
background_gradient: {
angle: 90.deg(),
stops: vec![rgb(0.0, 0.0, 0.0), rgb(1.0, 1.0, 1.0)]
stops: [rgb(0.0, 0.0, 0.0), rgb(1.0, 1.0, 1.0)]
};
content: child;

View File

@ -1,34 +1,13 @@
use std::f32::consts::PI;
use crate::prelude::new_widget::*;
pub use webrender::api::ExtendMode;
/// Computed [`GradientStop`].
pub type LayoutGradientStop = webrender::api::GradientStop;
/// A color stop in a linear or radial gradient.
#[derive(Clone, Copy, Debug)]
pub struct GradientStop {
pub offset: Length,
pub color: Rgba,
}
impl GradientStop {
#[inline]
pub fn to_layout(self, available_length: LayoutLength, ctx: &LayoutContext) -> LayoutGradientStop {
LayoutGradientStop {
offset: self.offset.to_layout(available_length, ctx).get(),
color: self.color.into(),
}
}
}
struct LinearGradientNode<A: VarLocal<AngleRadian>, S: VarLocal<GradientStops>> {
angle: A,
stops: S,
render_start: LayoutPoint,
render_end: LayoutPoint,
render_stops: Vec<LayoutGradientStop>,
render_stops: Vec<LayoutColorStop>,
final_size: LayoutSize,
}
#[impl_ui_node(none)]
@ -55,9 +34,7 @@ impl<A: VarLocal<AngleRadian>, S: VarLocal<GradientStops>> UiNode for LinearGrad
self.render_start = start;
self.render_end = end;
self.render_stops.clear();
self.render_stops
.extend(self.stops.get_local().iter().map(|&s| s.to_layout(length, ctx)));
self.stops.get_local().layout(length, ctx, &mut self.render_stops);
}
fn render(&self, frame: &mut FrameBuilder) {
@ -78,7 +55,7 @@ struct LinearGradientPointsNode<A: VarLocal<Point>, B: VarLocal<Point>, S: VarLo
extend_mode: E,
render_start: LayoutPoint,
render_end: LayoutPoint,
render_stops: Vec<LayoutGradientStop>,
render_stops: Vec<LayoutColorStop>,
final_size: LayoutSize,
}
#[impl_ui_node(none)]
@ -116,9 +93,7 @@ impl<A: VarLocal<Point>, B: VarLocal<Point>, S: VarLocal<GradientStops>, E: VarL
let length = LayoutLength::new(self.render_start.distance_to(self.render_end));
self.render_stops.clear();
self.render_stops
.extend(self.stops.get_local().iter().map(|&s| s.to_layout(length, ctx)));
self.stops.get_local().layout(length, ctx, &mut self.render_stops);
}
fn render(&self, frame: &mut FrameBuilder) {
@ -140,7 +115,7 @@ struct LinearGradientTileNode<A: VarLocal<AngleRadian>, S: VarLocal<GradientStop
render_start: LayoutPoint,
render_end: LayoutPoint,
render_stops: Vec<LayoutGradientStop>,
render_stops: Vec<LayoutColorStop>,
render_tile_size: LayoutSize,
render_tile_spacing: LayoutSize,
@ -184,9 +159,7 @@ impl<A: VarLocal<AngleRadian>, S: VarLocal<GradientStops>, T: VarLocal<Size>, TS
self.render_start = start;
self.render_end = end;
self.render_stops.clear();
self.render_stops
.extend(self.stops.get_local().iter().map(|s| s.to_layout(length, ctx)));
self.stops.get_local().layout(length, ctx, &mut self.render_stops);
}
fn render(&self, frame: &mut FrameBuilder) {
@ -258,42 +231,42 @@ pub fn linear_gradient_tile(
/// Linear gradient from bottom to top.
pub fn linear_gradient_to_top(stops: impl IntoVar<GradientStops>) -> impl UiNode {
linear_gradient_pt((0, 100.pct()), (0, 0), stops)
linear_gradient_pt((0, 100.pct()), (0, 0), stops, ExtendMode::Clamp)
}
/// Linear gradient from top to bottom.
pub fn linear_gradient_to_bottom(stops: impl IntoVar<GradientStops>) -> impl UiNode {
linear_gradient_pt((0, 0), (0, 100.pct()), stops)
linear_gradient_pt((0, 0), (0, 100.pct()), stops, ExtendMode::Clamp)
}
/// Linear gradient from right to left.
pub fn linear_gradient_to_left(stops: impl IntoVar<GradientStops>) -> impl UiNode {
linear_gradient_pt((100.pct(), 0), (0, 0), stops)
linear_gradient_pt((100.pct(), 0), (0, 0), stops, ExtendMode::Clamp)
}
/// Linear gradient from left to right.
pub fn linear_gradient_to_right(stops: impl IntoVar<GradientStops>) -> impl UiNode {
linear_gradient_pt((0, 0), (100.pct(), 0), stops)
linear_gradient_pt((0, 0), (100.pct(), 0), stops, ExtendMode::Clamp)
}
/// Linear gradient from bottom-left to top-right.
pub fn linear_gradient_to_top_right(stops: impl IntoVar<GradientStops>) -> impl UiNode {
linear_gradient_pt((0, 100.pct()), (100.pct(), 0), stops)
linear_gradient_pt((0, 100.pct()), (100.pct(), 0), stops, ExtendMode::Clamp)
}
/// Linear gradient from top-left to bottom-right.
pub fn linear_gradient_to_bottom_right(stops: impl IntoVar<GradientStops>) -> impl UiNode {
linear_gradient_pt((0, 0), (100.pct(), 100.pct()), stops)
linear_gradient_pt((0, 0), (100.pct(), 100.pct()), stops, ExtendMode::Clamp)
}
/// Linear gradient from bottom-right to top-left.
pub fn linear_gradient_to_top_left(stops: impl IntoVar<GradientStops>) -> impl UiNode {
linear_gradient_pt((100.pct(), 100.pct()), (0, 0), stops)
linear_gradient_pt((100.pct(), 100.pct()), (0, 0), stops, ExtendMode::Clamp)
}
/// Linear gradient from top-right to bottom-left.
pub fn linear_gradient_to_bottom_left(stops: impl IntoVar<GradientStops>) -> impl UiNode {
linear_gradient_pt((100.pct(), 0), (0, 100.pct()), stops)
linear_gradient_pt((100.pct(), 0), (0, 100.pct()), stops, ExtendMode::Clamp)
}
struct FillColorNode<C: VarLocal<Rgba>> {
@ -327,65 +300,332 @@ pub fn fill_color(color: impl IntoVar<Rgba>) -> impl UiNode {
}
}
/// Gradient stops for linear or radial gradients.
#[derive(Debug, Clone)]
pub struct GradientStops(pub Vec<GradientStop>);
impl std::ops::Deref for GradientStops {
type Target = [GradientStop];
/// Computed [`GradientStop`].
pub type LayoutColorStop = webrender::api::GradientStop;
fn deref(&self) -> &Self::Target {
&self.0
/// A color stop in a gradient.
#[derive(Clone, Copy, Debug)]
pub struct ColorStop {
pub color: Rgba,
pub offset: Length,
}
impl ColorStop {
#[inline]
pub fn to_layout(self, length: LayoutLength, ctx: &LayoutContext) -> LayoutColorStop {
LayoutColorStop {
offset: self.offset.to_layout(length, ctx).get(),
color: self.color.into(),
}
}
}
impl_from_and_into_var! {
fn from(stops: Vec<(Rgba, Length)>) -> GradientStops {
GradientStops(stops.into_iter()
.map(|(color, offset)| GradientStop {
color,
offset,
})
.collect())
}
/// Each item contains two color stops, with the same color.
fn from(stops: Vec<(Rgba, Length, Length)>) -> GradientStops {{
let mut r = Vec::with_capacity(stops.len() * 2);
for (color, offset0, offset1) in stops {
r.push(GradientStop {
color,
offset: offset0
});
r.push(GradientStop {
color,
offset: offset1
});
impl<C: Into<Rgba>, O: Into<Length>> From<(C, O)> for ColorStop {
fn from((c, o): (C, O)) -> Self {
ColorStop {
color: c.into(),
offset: o.into(),
}
GradientStops(r)
}}
}
}
/// Gradient stops that are all evenly spaced.
fn from(stops: Vec<Rgba>) -> GradientStops {{
let point = 1. / (stops.len() as f32 - 1.);
GradientStops(stops.into_iter()
.enumerate()
.map(|(i, color)| GradientStop {
offset: ((i as f32) * point).normal().into(),
color,
})
.collect())
}}
/// A stop in a gradient.
#[derive(Clone, Copy, Debug)]
pub enum GradientStop {
/// Color stop.
Color(ColorStop),
/// Midway point between two colors.
Mid(Length),
}
/// A single two color gradient stops. The first color is at offset `0.0`,
/// the second color is at offset `1.0`.
fn from((stop0, stop1): (Rgba, Rgba)) -> GradientStops {
GradientStops(vec![
GradientStop { offset: 0.0.normal().into(), color: stop0 },
GradientStop { offset: 1.0.normal().into(), color: stop1 },
])
/// Stops in a gradient.
#[derive(Debug, Clone)]
pub struct GradientStops {
/// First color stop.
pub start: ColorStop,
/// Optional stops between start and end.
pub middle: Vec<GradientStop>,
/// Last color stop.
pub end: ColorStop,
}
#[allow(clippy::len_without_is_empty)] // cannot be empty
impl GradientStops {
/// Start a color gradient builder with the first color stop.
pub fn start(color: impl Into<Rgba>, offset: impl Into<Length>) -> GradientStopsBuilder {
GradientStopsBuilder {
start: ColorStop {
color: color.into(),
offset: offset.into(),
},
middle: vec![],
}
}
fn from(stops: Vec<GradientStop>) -> GradientStops {
GradientStops(stops)
/// Gradients stops with two colors from `start` to `end`.
pub fn start_end(start: impl Into<Rgba>, end: impl Into<Rgba>) -> Self {
GradientStops {
start: ColorStop {
color: start.into(),
offset: Length::zero(),
},
middle: vec![],
end: ColorStop {
color: end.into(),
offset: 100.pct().into(),
},
}
}
/// Gradients stops with two colors from `start` to `end` and with custom midway point.
pub fn start_mid_end(start: impl Into<Rgba>, mid: impl Into<Length>, end: impl Into<Rgba>) -> Self {
GradientStops {
start: ColorStop {
color: start.into(),
offset: Length::zero(),
},
middle: vec![GradientStop::Mid(mid.into())],
end: ColorStop {
color: end.into(),
offset: 100.pct().into(),
},
}
}
fn start_missing() -> ColorStop {
ColorStop {
color: colors::TRANSPARENT,
offset: Length::zero(),
}
}
fn end_missing() -> ColorStop {
ColorStop {
color: colors::TRANSPARENT,
offset: 100.pct().into(),
}
}
/// Gradient stops from colors spaced equally.
pub fn from_colors<C: Into<Rgba> + Copy>(colors: &[C]) -> Self {
if colors.is_empty() {
GradientStops {
start: Self::start_missing(),
middle: vec![],
end: Self::end_missing(),
}
} else if colors.len() == 1 {
GradientStops {
start: ColorStop {
color: colors[0].into(),
offset: Length::zero(),
},
middle: vec![],
end: Self::end_missing(),
}
} else {
let last = colors.len() - 1;
let mut offset = 1.0 / colors.len() as f32;
let offset_step = offset;
GradientStops {
start: ColorStop {
color: colors[0].into(),
offset: Length::zero(),
},
middle: colors[1..last]
.iter()
.map(|&c| {
GradientStop::Color(ColorStop {
color: c.into(),
offset: {
let r = offset;
offset += offset_step;
r.normal().into()
},
})
})
.collect(),
end: ColorStop {
color: colors[last].into(),
offset: 100.pct().into(),
},
}
}
}
/// Gradient stops from color stops.
pub fn from_stops<C: Into<ColorStop> + Copy>(stops: &[C]) -> Self {
if stops.is_empty() {
GradientStops {
start: Self::start_missing(),
middle: vec![],
end: Self::end_missing(),
}
} else if stops.len() == 1 {
GradientStops {
start: stops[0].into(),
middle: vec![],
end: Self::end_missing(),
}
} else {
let last = stops.len() - 1;
GradientStops {
start: stops[0].into(),
middle: stops[1..last].iter().map(|&c| GradientStop::Color(c.into())).collect(),
end: stops[last].into(),
}
}
}
/// Computes the actual color stops.
pub fn layout(&self, length: LayoutLength, ctx: &LayoutContext, render_stops: &mut Vec<LayoutColorStop>) {
// In this method we need to:
// 1 - Convert all Length values to LayoutLength.
// 2 - Adjust offsets to they are always larger or equal to the previous offset.
// 3 - Convert GradientStop::Mid to LayoutColorStop.
render_stops.clear();
let mut prev_stop = self.start.to_layout(length, ctx); // 1
let mut pending_mid = None;
render_stops.push(prev_stop);
for gs in self.middle.iter() {
match gs {
GradientStop::Color(s) => {
let mut stop = s.to_layout(length, ctx); // 1
if let Some(mid) = pending_mid.take() {
if stop.offset < mid {
stop.offset = mid; // 2
}
render_stops.push(Self::mid_to_color_stop(prev_stop, mid, stop));
// 3
} else if stop.offset < prev_stop.offset {
stop.offset = prev_stop.offset; // 2
}
render_stops.push(stop);
prev_stop = stop;
}
GradientStop::Mid(l) => {
// TODO do we care if pending_mid is some here?
let mut l = l.to_layout(length, ctx).0; // 1
if l > prev_stop.offset {
l = prev_stop.offset; // 2
}
pending_mid = Some(l);
}
}
}
let mut stop = self.end.to_layout(length, ctx); // 1
if let Some(mid) = pending_mid.take() {
if stop.offset < mid {
stop.offset = mid; // 2
}
render_stops.push(Self::mid_to_color_stop(prev_stop, mid, stop)); // 3
} else if stop.offset < prev_stop.offset {
stop.offset = prev_stop.offset; // 2
}
render_stops.push(stop);
}
fn mid_to_color_stop(prev: LayoutColorStop, mid: f32, next: LayoutColorStop) -> LayoutColorStop {
let lerp_mid = (next.offset - prev.offset) / (mid - prev.offset);
LayoutColorStop {
color: lerp_render_color(prev.color, next.color, lerp_mid),
offset: mid,
}
}
/// Number of stops.
#[inline]
pub fn len(&self) -> usize {
self.middle.len() + 2
}
}
impl<C: Into<Rgba> + Copy + 'static> From<&[C]> for GradientStops {
fn from(a: &[C]) -> Self {
GradientStops::from_colors(a)
}
}
impl<C: Into<Rgba> + Copy + 'static> IntoVar<GradientStops> for &[C] {
type Var = OwnedVar<GradientStops>;
fn into_var(self) -> Self::Var {
OwnedVar(self.into())
}
}
macro_rules! impl_from_color_arrays {
($($N:tt),+ $(,)?) => {$(
impl<C: Into<Rgba> + Copy + 'static> From<[C; $N]> for GradientStops {
fn from(a: [C; $N]) -> Self {
GradientStops::from_colors(&a)
}
}
impl<C: Into<Rgba> + Copy + 'static> IntoVar<GradientStops> for [C; $N] {
type Var = OwnedVar<GradientStops>;
fn into_var(self) -> Self::Var {
OwnedVar(self.into())
}
}
)+};
}
impl_from_color_arrays!(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32);
/// A [`GradientStops`] builder.
pub struct GradientStopsBuilder {
start: ColorStop,
middle: Vec<GradientStop>,
}
impl GradientStopsBuilder {
/// Add a color stop.
pub fn color(mut self, color: impl Into<Rgba>, offset: impl Into<Length>) -> GradientStopsBuilderWithMid {
self.middle.push(GradientStop::Color(ColorStop {
color: color.into(),
offset: offset.into(),
}));
GradientStopsBuilderWithMid(self)
}
fn mid(mut self, offset: impl Into<Length>) -> Self {
self.middle.push(GradientStop::Mid(offset.into()));
self
}
/// Finishes the gradient with the last color stop.
pub fn end(self, color: impl Into<Rgba>, offset: impl Into<Length>) -> GradientStops {
GradientStops {
start: self.start,
middle: self.middle,
end: ColorStop {
color: color.into(),
offset: offset.into(),
},
}
}
}
/// [`GradientStopsBuilder`] in a state that allows adding a midway point.
pub struct GradientStopsBuilderWithMid(GradientStopsBuilder);
impl GradientStopsBuilderWithMid {
/// Add a color stop.
pub fn color(self, color: impl Into<Rgba>, offset: impl Into<Length>) -> GradientStopsBuilderWithMid {
self.0.color(color, offset)
}
/// Add the midway points between the previous color stop and the next.
pub fn mid(self, offset: impl Into<Length>) -> GradientStopsBuilder {
self.0.mid(offset)
}
/// Finishes the gradient with the last color stop.
pub fn end(self, color: impl Into<Rgba>, offset: impl Into<Length>) -> GradientStops {
self.0.end(color, offset)
}
}