This commit is contained in:
Sunli 2025-08-26 20:10:42 +08:00 committed by GitHub
commit 46b8ebbe46
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 123 additions and 49 deletions

View file

@ -3,8 +3,8 @@ use std::time::Duration;
use anyhow::Result;
use gpui::{
Animation, AnimationExt as _, App, Application, AssetSource, Bounds, Context, SharedString,
Transformation, Window, WindowBounds, WindowOptions, black, bounce, div, ease_in_out,
percentage, prelude::*, px, rgb, size, svg,
Transformation, Window, WindowBounds, WindowOptions, bounce, div, ease_in_out, percentage,
prelude::*, px, size, svg,
};
struct Assets {}
@ -37,37 +37,66 @@ struct AnimationExample {}
impl Render for AnimationExample {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().flex().flex_col().size_full().justify_around().child(
div().flex().flex_row().w_full().justify_around().child(
div()
.flex()
.flex_col()
.size_full()
.bg(gpui::white())
.text_color(gpui::black())
.justify_around()
.child(
div()
.flex()
.bg(rgb(0x2e7d32))
.size(px(300.0))
.justify_center()
.items_center()
.shadow_lg()
.text_xl()
.text_color(black())
.child("hello")
.flex_col()
.size_full()
.justify_around()
.child(
svg()
.size_8()
.path(ARROW_CIRCLE_SVG)
.text_color(black())
.with_animation(
"image_circle",
Animation::new(Duration::from_secs(2))
.repeat()
.with_easing(bounce(ease_in_out)),
|svg, delta| {
svg.with_transformation(Transformation::rotate(percentage(
delta,
)))
},
div()
.id("content")
.flex()
.flex_col()
.h(px(150.))
.overflow_y_scroll()
.w_full()
.flex_1()
.justify_center()
.items_center()
.text_xl()
.gap_4()
.child("Hello Animation")
.child(
svg()
.size_20()
.overflow_hidden()
.path(ARROW_CIRCLE_SVG)
.text_color(gpui::black())
.with_animation(
"image_circle",
Animation::new(Duration::from_secs(2))
.repeat()
.with_easing(bounce(ease_in_out)),
|svg, delta| {
svg.with_transformation(Transformation::rotate(
percentage(delta),
))
},
),
),
)
.child(
div()
.flex()
.h(px(64.))
.w_full()
.p_2()
.justify_center()
.items_center()
.border_t_1()
.border_color(gpui::black().opacity(0.1))
.bg(gpui::black().opacity(0.05))
.child("Other Panel"),
),
),
)
)
}
}

View file

@ -141,6 +141,12 @@ fn distance_from_clip_rect(unit_vertex: vec2<f32>, bounds: Bounds, clip_bounds:
return distance_from_clip_rect_impl(position, clip_bounds);
}
fn distance_from_clip_rect_transformed(unit_vertex: vec2<f32>, bounds: Bounds, clip_bounds: Bounds, transform: TransformationMatrix) -> vec4<f32> {
let position = unit_vertex * vec2<f32>(bounds.size) + bounds.origin;
let transformed = transpose(transform.rotation_scale) * position + transform.translation;
return distance_from_clip_rect_impl(transformed, clip_bounds);
}
// https://gamedev.stackexchange.com/questions/92015/optimized-linear-to-srgb-glsl
fn srgb_to_linear(srgb: vec3<f32>) -> vec3<f32> {
let cutoff = srgb < vec3<f32>(0.04045);
@ -1117,7 +1123,7 @@ fn vs_mono_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index
out.tile_position = to_tile_position(unit_vertex, sprite.tile);
out.color = hsla_to_rgba(sprite.color);
out.clip_distances = distance_from_clip_rect(unit_vertex, sprite.bounds, sprite.content_mask);
out.clip_distances = distance_from_clip_rect_transformed(unit_vertex, sprite.bounds, sprite.content_mask, sprite.transformation);
return out;
}

View file

@ -18,6 +18,8 @@ float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
constant Size_DevicePixels *atlas_size);
float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
Bounds_ScaledPixels clip_bounds);
float4 distance_from_clip_rect_transformed(float2 unit_vertex, Bounds_ScaledPixels bounds,
Bounds_ScaledPixels clip_bounds, TransformationMatrix transformation);
float corner_dash_velocity(float dv1, float dv2);
float dash_alpha(float t, float period, float length, float dash_velocity,
float antialias_threshold);
@ -599,13 +601,14 @@ struct MonochromeSpriteVertexOutput {
float4 position [[position]];
float2 tile_position;
float4 color [[flat]];
float clip_distance [[clip_distance]][4];
float4 clip_distance;
};
struct MonochromeSpriteFragmentInput {
float4 position [[position]];
float2 tile_position;
float4 color [[flat]];
float4 clip_distance;
};
vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
@ -620,8 +623,8 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
MonochromeSprite sprite = sprites[sprite_id];
float4 device_position =
to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation, viewport_size);
float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
sprite.content_mask.bounds);
float4 clip_distance = distance_from_clip_rect_transformed(unit_vertex, sprite.bounds,
sprite.content_mask.bounds, sprite.transformation);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
float4 color = hsla_to_rgba(sprite.color);
return MonochromeSpriteVertexOutput{
@ -635,6 +638,10 @@ fragment float4 monochrome_sprite_fragment(
MonochromeSpriteFragmentInput input [[stage_in]],
constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
if (any(input.clip_distance < float4(0.0))) {
return float4(0.0);
}
constexpr sampler atlas_texture_sampler(mag_filter::linear,
min_filter::linear);
float4 sample =
@ -1096,6 +1103,23 @@ float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
clip_bounds.origin.y + clip_bounds.size.height - position.y);
}
float4 distance_from_clip_rect_transformed(float2 unit_vertex, Bounds_ScaledPixels bounds,
Bounds_ScaledPixels clip_bounds, TransformationMatrix transformation) {
float2 position =
unit_vertex * float2(bounds.size.width, bounds.size.height) +
float2(bounds.origin.x, bounds.origin.y);
float2 transformed_position = float2(0, 0);
transformed_position[0] = position[0] * transformation.rotation_scale[0][0] + position[1] * transformation.rotation_scale[0][1];
transformed_position[1] = position[0] * transformation.rotation_scale[1][0] + position[1] * transformation.rotation_scale[1][1];
transformed_position[0] += transformation.translation[0];
transformed_position[1] += transformation.translation[1];
return float4(transformed_position.x - clip_bounds.origin.x,
clip_bounds.origin.x + clip_bounds.size.width - transformed_position.x,
transformed_position.y - clip_bounds.origin.y,
clip_bounds.origin.y + clip_bounds.size.height - transformed_position.y);
}
float4 over(float4 below, float4 above) {
float4 result;
float alpha = above.a + below.a * (1.0 - above.a);

View file

@ -54,7 +54,10 @@ impl SvgRenderer {
}
}
pub(crate) fn render(&self, params: &RenderSvgParams) -> Result<Option<Vec<u8>>> {
pub(crate) fn render(
&self,
params: &RenderSvgParams,
) -> Result<Option<(Size<DevicePixels>, Vec<u8>)>> {
anyhow::ensure!(!params.size.is_zero(), "can't render at a zero size");
// Load the tree.
@ -65,30 +68,33 @@ impl SvgRenderer {
let pixmap = self.render_pixmap(&bytes, SvgSize::Size(params.size))?;
// Convert the pixmap's pixels into an alpha mask.
let size = Size::new(
DevicePixels(pixmap.width() as i32),
DevicePixels(pixmap.height() as i32),
);
let alpha_mask = pixmap
.pixels()
.iter()
.map(|p| p.alpha())
.collect::<Vec<_>>();
Ok(Some(alpha_mask))
Ok(Some((size, alpha_mask)))
}
pub fn render_pixmap(&self, bytes: &[u8], size: SvgSize) -> Result<Pixmap, usvg::Error> {
let tree = usvg::Tree::from_data(bytes, &self.usvg_options)?;
let size = match size {
SvgSize::Size(size) => size,
SvgSize::ScaleFactor(scale) => crate::size(
DevicePixels((tree.size().width() * scale) as i32),
DevicePixels((tree.size().height() * scale) as i32),
),
let svg_size = tree.size();
let scale = match size {
SvgSize::Size(size) => size.width.0 as f32 / svg_size.width(),
SvgSize::ScaleFactor(scale) => scale,
};
// Render the SVG to a pixmap with the specified width and height.
let mut pixmap = resvg::tiny_skia::Pixmap::new(size.width.into(), size.height.into())
.ok_or(usvg::Error::InvalidSize)?;
let mut pixmap = resvg::tiny_skia::Pixmap::new(
(svg_size.width() * scale) as u32,
(svg_size.height() * scale) as u32,
)
.ok_or(usvg::Error::InvalidSize)?;
let scale = size.width.0 as f32 / tree.size().width();
let transform = resvg::tiny_skia::Transform::from_scale(scale, scale);
resvg::render(&tree, transform, &mut pixmap.as_mut());

View file

@ -2996,22 +2996,31 @@ impl Window {
let Some(tile) =
self.sprite_atlas
.get_or_insert_with(&params.clone().into(), &mut || {
let Some(bytes) = cx.svg_renderer.render(&params)? else {
let Some((size, bytes)) = cx.svg_renderer.render(&params)? else {
return Ok(None);
};
Ok(Some((params.size, Cow::Owned(bytes))))
Ok(Some((size, Cow::Owned(bytes))))
})?
else {
return Ok(());
};
let content_mask = self.content_mask().scale(scale_factor);
let svg_bounds = Bounds {
origin: bounds.center()
- Point::new(
ScaledPixels(tile.bounds.size.width.0 as f32 / SMOOTH_SVG_SCALE_FACTOR / 2.),
ScaledPixels(tile.bounds.size.height.0 as f32 / SMOOTH_SVG_SCALE_FACTOR / 2.),
),
size: tile
.bounds
.size
.map(|value| ScaledPixels(value.0 as f32 / SMOOTH_SVG_SCALE_FACTOR)),
};
self.next_frame.scene.insert_primitive(MonochromeSprite {
order: 0,
pad: 0,
bounds: bounds
.map_origin(|origin| origin.floor())
.map_size(|size| size.ceil()),
bounds: svg_bounds,
content_mask,
color: color.opacity(element_opacity),
tile,