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
|
||||
windows.workspace = true
|
||||
windows-core = "0.58"
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
path = "examples/hello_world.rs"
|
||||
|
@ -221,18 +220,22 @@ path = "examples/hello_world.rs"
|
|||
name = "image"
|
||||
path = "examples/image/image.rs"
|
||||
|
||||
[[example]]
|
||||
name = "set_menus"
|
||||
path = "examples/set_menus.rs"
|
||||
|
||||
[[example]]
|
||||
name = "window_shadow"
|
||||
path = "examples/window_shadow.rs"
|
||||
|
||||
[[example]]
|
||||
name = "input"
|
||||
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]]
|
||||
name = "shadow"
|
||||
path = "examples/shadow.rs"
|
||||
|
@ -245,10 +248,10 @@ path = "examples/svg/svg.rs"
|
|||
name = "text_wrapper"
|
||||
path = "examples/text_wrapper.rs"
|
||||
|
||||
[[example]]
|
||||
name = "opacity"
|
||||
path = "examples/opacity.rs"
|
||||
|
||||
[[example]]
|
||||
name = "uniform_list"
|
||||
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 {
|
||||
Solid = 0,
|
||||
LinearGradient = 1,
|
||||
PatternSlash = 2,
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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 {
|
||||
BackgroundTag::Solid => self.solid.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 {
|
||||
// 0u is Solid
|
||||
// 1u is LinearGradient
|
||||
// 2u is PatternSlash
|
||||
tag: u32,
|
||||
// 0u is sRGB linear 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 {
|
||||
var result = GradientColor();
|
||||
|
||||
if (tag == 0u) {
|
||||
if (tag == 0u || tag == 2u) {
|
||||
result.solid = hsla_to_rgba(solid);
|
||||
} else if (tag == 1u) {
|
||||
// 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;
|
||||
|
|
|
@ -26,7 +26,7 @@ float blur_along_x(float x, float y, float sigma, float corner,
|
|||
float2 half_size);
|
||||
float4 over(float4 below, float4 above);
|
||||
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);
|
||||
|
||||
struct GradientColor {
|
||||
|
@ -34,7 +34,7 @@ struct GradientColor {
|
|||
float4 color0;
|
||||
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 {
|
||||
uint quad_id [[flat]];
|
||||
|
@ -71,7 +71,7 @@ vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
|
|||
quad.content_mask.bounds);
|
||||
float4 border_color = hsla_to_rgba(quad.border_color);
|
||||
|
||||
GradientColor gradient = prepare_gradient_color(
|
||||
GradientColor gradient = prepare_fill_color(
|
||||
quad.background.tag,
|
||||
quad.background.color_space,
|
||||
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 center = float2(quad.bounds.origin.x, quad.bounds.origin.y) + half_size;
|
||||
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);
|
||||
|
||||
// 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);
|
||||
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.color_space,
|
||||
sprite.color.solid,
|
||||
|
@ -520,7 +520,7 @@ fragment float4 path_sprite_fragment(
|
|||
float mask = 1. - abs(1. - fmod(sample.r, 2.));
|
||||
PathSprite sprite = sprites[input.sprite_id];
|
||||
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);
|
||||
color.a *= mask;
|
||||
return color;
|
||||
|
@ -794,10 +794,10 @@ float4 over(float4 below, float4 above) {
|
|||
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) {
|
||||
GradientColor out;
|
||||
if (tag == 0) {
|
||||
if (tag == 0 || tag == 2) {
|
||||
out.solid = hsla_to_rgba(solid);
|
||||
} else if (tag == 1) {
|
||||
out.color0 = hsla_to_rgba(color0);
|
||||
|
@ -815,7 +815,13 @@ GradientColor prepare_gradient_color(uint tag, uint color_space, Hsla solid,
|
|||
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,
|
||||
Bounds_ScaledPixels bounds,
|
||||
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_to_point = position - center;
|
||||
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)) {
|
||||
t = (t + half_size.x) / bounds.size.width;
|
||||
} else {
|
||||
|
@ -867,6 +873,24 @@ float4 gradient_color(Background background,
|
|||
}
|
||||
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;
|
||||
|
|
|
@ -582,6 +582,7 @@ impl Style {
|
|||
.first()
|
||||
.map(|stop| stop.color)
|
||||
.unwrap_or_default(),
|
||||
BackgroundTag::PatternSlash => color.solid,
|
||||
},
|
||||
None => Hsla::default(),
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue