gpui: Add support for slash pattern fills (///
) (#23576)
TODO: - [x] Add BackgroundTag::PatternSlash - [x] Support metal slash pattern fills - [x] Support blade slash pattern fills --- Adds support for a new background type in gpui, `pattern_slash`. Usage: ```rust div().size(px(56.0)).bg(pattern_slash(gpui::red())) ``` This will create a 56px square with a red slash pattern fill. You can run the pattern example with `cargo run -p gpui --example pattern`:  --- After talking with @as-cii at length about how we want to support patterns in gpui, we decided for now we'll simply add a new BackgroundTag specific to this pattern. It isn't the best long term plan however – we'll likely want to introduce the concept of a `Fill` at some point so we can have `Fill::Solid`, `Fill::Gradient(LinearGradient)`, etc in the future. The pattern is designed to seamlessly tile vertically for elements of the same height. For example, for use in editor line backgrounds:  --- Release Notes: (do we do gpui release notes?) - Adds support for slash pattern fills in `gpui`. --------- Co-authored-by: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
parent
070890d361
commit
23672987ff
6 changed files with 183 additions and 24 deletions
|
@ -212,7 +212,6 @@ flume = "0.11"
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
windows.workspace = true
|
windows.workspace = true
|
||||||
windows-core = "0.58"
|
windows-core = "0.58"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "hello_world"
|
name = "hello_world"
|
||||||
path = "examples/hello_world.rs"
|
path = "examples/hello_world.rs"
|
||||||
|
@ -221,18 +220,22 @@ path = "examples/hello_world.rs"
|
||||||
name = "image"
|
name = "image"
|
||||||
path = "examples/image/image.rs"
|
path = "examples/image/image.rs"
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "set_menus"
|
|
||||||
path = "examples/set_menus.rs"
|
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "window_shadow"
|
|
||||||
path = "examples/window_shadow.rs"
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "input"
|
name = "input"
|
||||||
path = "examples/input.rs"
|
path = "examples/input.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "opacity"
|
||||||
|
path = "examples/opacity.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "pattern"
|
||||||
|
path = "examples/pattern.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "set_menus"
|
||||||
|
path = "examples/set_menus.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "shadow"
|
name = "shadow"
|
||||||
path = "examples/shadow.rs"
|
path = "examples/shadow.rs"
|
||||||
|
@ -245,10 +248,10 @@ path = "examples/svg/svg.rs"
|
||||||
name = "text_wrapper"
|
name = "text_wrapper"
|
||||||
path = "examples/text_wrapper.rs"
|
path = "examples/text_wrapper.rs"
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "opacity"
|
|
||||||
path = "examples/opacity.rs"
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "uniform_list"
|
name = "uniform_list"
|
||||||
path = "examples/uniform_list.rs"
|
path = "examples/uniform_list.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "window_shadow"
|
||||||
|
path = "examples/window_shadow.rs"
|
||||||
|
|
103
crates/gpui/examples/pattern.rs
Normal file
103
crates/gpui/examples/pattern.rs
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
use gpui::{
|
||||||
|
div, linear_color_stop, linear_gradient, pattern_slash, prelude::*, px, rgb, size, App,
|
||||||
|
AppContext, Bounds, ViewContext, WindowBounds, WindowOptions,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PatternExample;
|
||||||
|
|
||||||
|
impl Render for PatternExample {
|
||||||
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_3()
|
||||||
|
.bg(rgb(0xffffff))
|
||||||
|
.size(px(600.0))
|
||||||
|
.justify_center()
|
||||||
|
.items_center()
|
||||||
|
.shadow_lg()
|
||||||
|
.text_xl()
|
||||||
|
.text_color(rgb(0x000000))
|
||||||
|
.child("Pattern Example")
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.border_1()
|
||||||
|
.border_color(gpui::blue())
|
||||||
|
.child(div().w(px(54.0)).h(px(18.0)).bg(pattern_slash(gpui::red())))
|
||||||
|
.child(div().w(px(54.0)).h(px(18.0)).bg(pattern_slash(gpui::red())))
|
||||||
|
.child(div().w(px(54.0)).h(px(18.0)).bg(pattern_slash(gpui::red())))
|
||||||
|
.child(div().w(px(54.0)).h(px(18.0)).bg(pattern_slash(gpui::red()))),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.border_1()
|
||||||
|
.border_color(gpui::blue())
|
||||||
|
.bg(gpui::green().opacity(0.16))
|
||||||
|
.child("Elements the same height should align")
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w(px(256.0))
|
||||||
|
.h(px(56.0))
|
||||||
|
.bg(pattern_slash(gpui::red())),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w(px(256.0))
|
||||||
|
.h(px(56.0))
|
||||||
|
.bg(pattern_slash(gpui::green())),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w(px(256.0))
|
||||||
|
.h(px(56.0))
|
||||||
|
.bg(pattern_slash(gpui::blue())),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w(px(256.0))
|
||||||
|
.h(px(26.0))
|
||||||
|
.bg(pattern_slash(gpui::yellow())),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.border_1()
|
||||||
|
.border_color(gpui::blue())
|
||||||
|
.w(px(240.0))
|
||||||
|
.h(px(40.0))
|
||||||
|
.bg(gpui::red()),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.border_1()
|
||||||
|
.border_color(gpui::blue())
|
||||||
|
.w(px(240.0))
|
||||||
|
.h(px(40.0))
|
||||||
|
.bg(linear_gradient(
|
||||||
|
45.,
|
||||||
|
linear_color_stop(gpui::red(), 0.),
|
||||||
|
linear_color_stop(gpui::blue(), 1.),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new().run(|cx: &mut AppContext| {
|
||||||
|
let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx);
|
||||||
|
cx.open_window(
|
||||||
|
WindowOptions {
|
||||||
|
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
|cx| cx.new_view(|_cx| PatternExample),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
cx.activate(true);
|
||||||
|
});
|
||||||
|
}
|
|
@ -553,6 +553,7 @@ impl<'de> Deserialize<'de> for Hsla {
|
||||||
pub(crate) enum BackgroundTag {
|
pub(crate) enum BackgroundTag {
|
||||||
Solid = 0,
|
Solid = 0,
|
||||||
LinearGradient = 1,
|
LinearGradient = 1,
|
||||||
|
PatternSlash = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A color space for color interpolation.
|
/// A color space for color interpolation.
|
||||||
|
@ -606,6 +607,15 @@ impl Default for Background {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a hash pattern background
|
||||||
|
pub fn pattern_slash(color: Hsla) -> Background {
|
||||||
|
Background {
|
||||||
|
tag: BackgroundTag::PatternSlash,
|
||||||
|
solid: color,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a LinearGradient background color.
|
/// Creates a LinearGradient background color.
|
||||||
///
|
///
|
||||||
/// The gradient line's angle of direction. A value of `0.` is equivalent to to top; increasing values rotate clockwise from there.
|
/// The gradient line's angle of direction. A value of `0.` is equivalent to to top; increasing values rotate clockwise from there.
|
||||||
|
@ -683,6 +693,7 @@ impl Background {
|
||||||
match self.tag {
|
match self.tag {
|
||||||
BackgroundTag::Solid => self.solid.is_transparent(),
|
BackgroundTag::Solid => self.solid.is_transparent(),
|
||||||
BackgroundTag::LinearGradient => self.colors.iter().all(|c| c.color.is_transparent()),
|
BackgroundTag::LinearGradient => self.colors.iter().all(|c| c.color.is_transparent()),
|
||||||
|
BackgroundTag::PatternSlash => self.solid.is_transparent(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ struct LinearColorStop {
|
||||||
struct Background {
|
struct Background {
|
||||||
// 0u is Solid
|
// 0u is Solid
|
||||||
// 1u is LinearGradient
|
// 1u is LinearGradient
|
||||||
|
// 2u is PatternSlash
|
||||||
tag: u32,
|
tag: u32,
|
||||||
// 0u is sRGB linear color
|
// 0u is sRGB linear color
|
||||||
// 1u is Oklab color
|
// 1u is Oklab color
|
||||||
|
@ -285,7 +286,7 @@ fn prepare_gradient_color(tag: u32, color_space: u32,
|
||||||
solid: Hsla, colors: array<LinearColorStop, 2>) -> GradientColor {
|
solid: Hsla, colors: array<LinearColorStop, 2>) -> GradientColor {
|
||||||
var result = GradientColor();
|
var result = GradientColor();
|
||||||
|
|
||||||
if (tag == 0u) {
|
if (tag == 0u || tag == 2u) {
|
||||||
result.solid = hsla_to_rgba(solid);
|
result.solid = hsla_to_rgba(solid);
|
||||||
} else if (tag == 1u) {
|
} else if (tag == 1u) {
|
||||||
// The hsla_to_rgba is returns a linear sRGB color
|
// The hsla_to_rgba is returns a linear sRGB color
|
||||||
|
@ -357,6 +358,22 @@ fn gradient_color(background: Background, position: vec2<f32>, bounds: Bounds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case 2u: {
|
||||||
|
let base_pattern_size = bounds.size.y / 5.0;
|
||||||
|
let width = base_pattern_size * 0.5;
|
||||||
|
let slash_spacing = 0.89;
|
||||||
|
let radians = M_PI_F / 4.0;
|
||||||
|
let rotation = mat2x2<f32>(
|
||||||
|
cos(radians), -sin(radians),
|
||||||
|
sin(radians), cos(radians)
|
||||||
|
);
|
||||||
|
let relative_position = position - bounds.origin;
|
||||||
|
let rotated_point = rotation * relative_position;
|
||||||
|
let pattern = (rotated_point.x / slash_spacing) % (base_pattern_size * 2.0);
|
||||||
|
let distance = min(pattern, base_pattern_size * 2.0 - pattern) - width;
|
||||||
|
background_color = sold_color;
|
||||||
|
background_color.a *= saturate(0.5 - distance);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return background_color;
|
return background_color;
|
||||||
|
|
|
@ -26,7 +26,7 @@ float blur_along_x(float x, float y, float sigma, float corner,
|
||||||
float2 half_size);
|
float2 half_size);
|
||||||
float4 over(float4 below, float4 above);
|
float4 over(float4 below, float4 above);
|
||||||
float radians(float degrees);
|
float radians(float degrees);
|
||||||
float4 gradient_color(Background background, float2 position, Bounds_ScaledPixels bounds,
|
float4 fill_color(Background background, float2 position, Bounds_ScaledPixels bounds,
|
||||||
float4 solid_color, float4 color0, float4 color1);
|
float4 solid_color, float4 color0, float4 color1);
|
||||||
|
|
||||||
struct GradientColor {
|
struct GradientColor {
|
||||||
|
@ -34,7 +34,7 @@ struct GradientColor {
|
||||||
float4 color0;
|
float4 color0;
|
||||||
float4 color1;
|
float4 color1;
|
||||||
};
|
};
|
||||||
GradientColor prepare_gradient_color(uint tag, uint color_space, Hsla solid, Hsla color0, Hsla color1);
|
GradientColor prepare_fill_color(uint tag, uint color_space, Hsla solid, Hsla color0, Hsla color1);
|
||||||
|
|
||||||
struct QuadVertexOutput {
|
struct QuadVertexOutput {
|
||||||
uint quad_id [[flat]];
|
uint quad_id [[flat]];
|
||||||
|
@ -71,7 +71,7 @@ vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
|
||||||
quad.content_mask.bounds);
|
quad.content_mask.bounds);
|
||||||
float4 border_color = hsla_to_rgba(quad.border_color);
|
float4 border_color = hsla_to_rgba(quad.border_color);
|
||||||
|
|
||||||
GradientColor gradient = prepare_gradient_color(
|
GradientColor gradient = prepare_fill_color(
|
||||||
quad.background.tag,
|
quad.background.tag,
|
||||||
quad.background.color_space,
|
quad.background.color_space,
|
||||||
quad.background.solid,
|
quad.background.solid,
|
||||||
|
@ -96,7 +96,7 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
|
||||||
float2 half_size = float2(quad.bounds.size.width, quad.bounds.size.height) / 2.;
|
float2 half_size = float2(quad.bounds.size.width, quad.bounds.size.height) / 2.;
|
||||||
float2 center = float2(quad.bounds.origin.x, quad.bounds.origin.y) + half_size;
|
float2 center = float2(quad.bounds.origin.x, quad.bounds.origin.y) + half_size;
|
||||||
float2 center_to_point = input.position.xy - center;
|
float2 center_to_point = input.position.xy - center;
|
||||||
float4 color = gradient_color(quad.background, input.position.xy, quad.bounds,
|
float4 color = fill_color(quad.background, input.position.xy, quad.bounds,
|
||||||
input.background_solid, input.background_color0, input.background_color1);
|
input.background_solid, input.background_color0, input.background_color1);
|
||||||
|
|
||||||
// Fast path when the quad is not rounded and doesn't have any border.
|
// Fast path when the quad is not rounded and doesn't have any border.
|
||||||
|
@ -491,7 +491,7 @@ vertex PathSpriteVertexOutput path_sprite_vertex(
|
||||||
to_device_position(unit_vertex, sprite.bounds, viewport_size);
|
to_device_position(unit_vertex, sprite.bounds, viewport_size);
|
||||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||||
|
|
||||||
GradientColor gradient = prepare_gradient_color(
|
GradientColor gradient = prepare_fill_color(
|
||||||
sprite.color.tag,
|
sprite.color.tag,
|
||||||
sprite.color.color_space,
|
sprite.color.color_space,
|
||||||
sprite.color.solid,
|
sprite.color.solid,
|
||||||
|
@ -520,7 +520,7 @@ fragment float4 path_sprite_fragment(
|
||||||
float mask = 1. - abs(1. - fmod(sample.r, 2.));
|
float mask = 1. - abs(1. - fmod(sample.r, 2.));
|
||||||
PathSprite sprite = sprites[input.sprite_id];
|
PathSprite sprite = sprites[input.sprite_id];
|
||||||
Background background = sprite.color;
|
Background background = sprite.color;
|
||||||
float4 color = gradient_color(background, input.position.xy, sprite.bounds,
|
float4 color = fill_color(background, input.position.xy, sprite.bounds,
|
||||||
input.solid_color, input.color0, input.color1);
|
input.solid_color, input.color0, input.color1);
|
||||||
color.a *= mask;
|
color.a *= mask;
|
||||||
return color;
|
return color;
|
||||||
|
@ -794,10 +794,10 @@ float4 over(float4 below, float4 above) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
GradientColor prepare_gradient_color(uint tag, uint color_space, Hsla solid,
|
GradientColor prepare_fill_color(uint tag, uint color_space, Hsla solid,
|
||||||
Hsla color0, Hsla color1) {
|
Hsla color0, Hsla color1) {
|
||||||
GradientColor out;
|
GradientColor out;
|
||||||
if (tag == 0) {
|
if (tag == 0 || tag == 2) {
|
||||||
out.solid = hsla_to_rgba(solid);
|
out.solid = hsla_to_rgba(solid);
|
||||||
} else if (tag == 1) {
|
} else if (tag == 1) {
|
||||||
out.color0 = hsla_to_rgba(color0);
|
out.color0 = hsla_to_rgba(color0);
|
||||||
|
@ -815,7 +815,13 @@ GradientColor prepare_gradient_color(uint tag, uint color_space, Hsla solid,
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
float4 gradient_color(Background background,
|
float2x2 rotate2d(float angle) {
|
||||||
|
float s = sin(angle);
|
||||||
|
float c = cos(angle);
|
||||||
|
return float2x2(c, -s, s, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 fill_color(Background background,
|
||||||
float2 position,
|
float2 position,
|
||||||
Bounds_ScaledPixels bounds,
|
Bounds_ScaledPixels bounds,
|
||||||
float4 solid_color, float4 color0, float4 color1) {
|
float4 solid_color, float4 color0, float4 color1) {
|
||||||
|
@ -842,7 +848,7 @@ float4 gradient_color(Background background,
|
||||||
float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
|
float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
|
||||||
float2 center_to_point = position - center;
|
float2 center_to_point = position - center;
|
||||||
float t = dot(center_to_point, direction) / length(direction);
|
float t = dot(center_to_point, direction) / length(direction);
|
||||||
// Check the direct to determine the use x or y
|
// Check the direction to determine whether to use x or y
|
||||||
if (abs(direction.x) > abs(direction.y)) {
|
if (abs(direction.x) > abs(direction.y)) {
|
||||||
t = (t + half_size.x) / bounds.size.width;
|
t = (t + half_size.x) / bounds.size.width;
|
||||||
} else {
|
} else {
|
||||||
|
@ -867,6 +873,24 @@ float4 gradient_color(Background background,
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 2: {
|
||||||
|
// This pattern is full of magic numbers to make it line up perfectly
|
||||||
|
// when vertically stacked. Make sure you know what you are doing
|
||||||
|
// if you change this!
|
||||||
|
|
||||||
|
float base_pattern_size = bounds.size.height / 5;
|
||||||
|
float width = base_pattern_size * 0.5;
|
||||||
|
float slash_spacing = .89;
|
||||||
|
float radians = M_PI_F / 4.0;
|
||||||
|
float2x2 rotation = rotate2d(radians);
|
||||||
|
float2 relative_position = position - float2(bounds.origin.x, bounds.origin.y);
|
||||||
|
float2 rotated_point = rotation * relative_position;
|
||||||
|
float pattern = fmod(rotated_point.x / slash_spacing, base_pattern_size * 2.0);
|
||||||
|
float distance = min(pattern, base_pattern_size * 2.0 - pattern) - width;
|
||||||
|
color = solid_color;
|
||||||
|
color.a *= saturate(0.5 - distance);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return color;
|
return color;
|
||||||
|
|
|
@ -582,6 +582,7 @@ impl Style {
|
||||||
.first()
|
.first()
|
||||||
.map(|stop| stop.color)
|
.map(|stop| stop.color)
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
|
BackgroundTag::PatternSlash => color.solid,
|
||||||
},
|
},
|
||||||
None => Hsla::default(),
|
None => Hsla::default(),
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue