Detailed changes
@@ -50,10 +50,12 @@ fn generate_shader_bindings() -> PathBuf {
"ScaledContentMask".into(),
"Uniforms".into(),
"AtlasTile".into(),
- "QuadInputIndex".into(),
- "Quad".into(),
"ShadowInputIndex".into(),
"Shadow".into(),
+ "QuadInputIndex".into(),
+ "Underline".into(),
+ "UnderlineInputIndex".into(),
+ "Quad".into(),
"SpriteInputIndex".into(),
"MonochromeSprite".into(),
"PolychromeSprite".into(),
@@ -1,6 +1,6 @@
use crate::{
point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite,
- Quad, Scene, Shadow, Size,
+ PrimitiveBatch, Quad, Scene, Shadow, Size, Underline,
};
use cocoa::{
base::{NO, YES},
@@ -17,8 +17,9 @@ const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decisio
pub struct MetalRenderer {
layer: metal::MetalLayer,
command_queue: CommandQueue,
- quads_pipeline_state: metal::RenderPipelineState,
shadows_pipeline_state: metal::RenderPipelineState,
+ quads_pipeline_state: metal::RenderPipelineState,
+ underlines_pipeline_state: metal::RenderPipelineState,
monochrome_sprites_pipeline_state: metal::RenderPipelineState,
polychrome_sprites_pipeline_state: metal::RenderPipelineState,
unit_vertices: metal::Buffer,
@@ -83,6 +84,14 @@ impl MetalRenderer {
MTLResourceOptions::StorageModeManaged,
);
+ let shadows_pipeline_state = build_pipeline_state(
+ &device,
+ &library,
+ "shadows",
+ "shadow_vertex",
+ "shadow_fragment",
+ PIXEL_FORMAT,
+ );
let quads_pipeline_state = build_pipeline_state(
&device,
&library,
@@ -91,12 +100,12 @@ impl MetalRenderer {
"quad_fragment",
PIXEL_FORMAT,
);
- let shadows_pipeline_state = build_pipeline_state(
+ let underlines_pipeline_state = build_pipeline_state(
&device,
&library,
- "shadows",
- "shadow_vertex",
- "shadow_fragment",
+ "underlines",
+ "underline_vertex",
+ "underline_fragment",
PIXEL_FORMAT,
);
let monochrome_sprites_pipeline_state = build_pipeline_state(
@@ -122,8 +131,9 @@ impl MetalRenderer {
Self {
layer,
command_queue,
- quads_pipeline_state,
shadows_pipeline_state,
+ quads_pipeline_state,
+ underlines_pipeline_state,
monochrome_sprites_pipeline_state,
polychrome_sprites_pipeline_state,
unit_vertices,
@@ -184,10 +194,7 @@ impl MetalRenderer {
let mut instance_offset = 0;
for batch in scene.batches() {
match batch {
- crate::PrimitiveBatch::Quads(quads) => {
- self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder);
- }
- crate::PrimitiveBatch::Shadows(shadows) => {
+ PrimitiveBatch::Shadows(shadows) => {
self.draw_shadows(
shadows,
&mut instance_offset,
@@ -195,7 +202,18 @@ impl MetalRenderer {
command_encoder,
);
}
- crate::PrimitiveBatch::MonochromeSprites {
+ PrimitiveBatch::Quads(quads) => {
+ self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder);
+ }
+ PrimitiveBatch::Underlines(underlines) => {
+ self.draw_underlines(
+ underlines,
+ &mut instance_offset,
+ viewport_size,
+ command_encoder,
+ );
+ }
+ PrimitiveBatch::MonochromeSprites {
texture_id,
sprites,
} => {
@@ -207,7 +225,7 @@ impl MetalRenderer {
command_encoder,
);
}
- crate::PrimitiveBatch::PolychromeSprites {
+ PrimitiveBatch::PolychromeSprites {
texture_id,
sprites,
} => {
@@ -234,6 +252,66 @@ impl MetalRenderer {
drawable.present();
}
+ fn draw_shadows(
+ &mut self,
+ shadows: &[Shadow],
+ offset: &mut usize,
+ viewport_size: Size<DevicePixels>,
+ command_encoder: &metal::RenderCommandEncoderRef,
+ ) {
+ if shadows.is_empty() {
+ return;
+ }
+ align_offset(offset);
+
+ command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state);
+ command_encoder.set_vertex_buffer(
+ ShadowInputIndex::Vertices as u64,
+ Some(&self.unit_vertices),
+ 0,
+ );
+ command_encoder.set_vertex_buffer(
+ ShadowInputIndex::Shadows as u64,
+ Some(&self.instances),
+ *offset as u64,
+ );
+ command_encoder.set_fragment_buffer(
+ ShadowInputIndex::Shadows as u64,
+ Some(&self.instances),
+ *offset as u64,
+ );
+
+ command_encoder.set_vertex_bytes(
+ ShadowInputIndex::ViewportSize as u64,
+ mem::size_of_val(&viewport_size) as u64,
+ &viewport_size as *const Size<DevicePixels> as *const _,
+ );
+
+ let shadow_bytes_len = mem::size_of::<Shadow>() * shadows.len();
+ let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
+ unsafe {
+ ptr::copy_nonoverlapping(
+ shadows.as_ptr() as *const u8,
+ buffer_contents,
+ shadow_bytes_len,
+ );
+ }
+
+ let next_offset = *offset + shadow_bytes_len;
+ assert!(
+ next_offset <= INSTANCE_BUFFER_SIZE,
+ "instance buffer exhausted"
+ );
+
+ command_encoder.draw_primitives_instanced(
+ metal::MTLPrimitiveType::Triangle,
+ 0,
+ 6,
+ shadows.len() as u64,
+ );
+ *offset = next_offset;
+ }
+
fn draw_quads(
&mut self,
quads: &[Quad],
@@ -290,52 +368,52 @@ impl MetalRenderer {
*offset = next_offset;
}
- fn draw_shadows(
+ fn draw_underlines(
&mut self,
- shadows: &[Shadow],
+ underlines: &[Underline],
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
- if shadows.is_empty() {
+ if underlines.is_empty() {
return;
}
align_offset(offset);
- command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state);
+ command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state);
command_encoder.set_vertex_buffer(
- ShadowInputIndex::Vertices as u64,
+ UnderlineInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
- ShadowInputIndex::Shadows as u64,
+ UnderlineInputIndex::Underlines as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_fragment_buffer(
- ShadowInputIndex::Shadows as u64,
+ UnderlineInputIndex::Underlines as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
- ShadowInputIndex::ViewportSize as u64,
+ UnderlineInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
- let shadow_bytes_len = mem::size_of::<Shadow>() * shadows.len();
+ let quad_bytes_len = mem::size_of::<Underline>() * underlines.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(
- shadows.as_ptr() as *const u8,
+ underlines.as_ptr() as *const u8,
buffer_contents,
- shadow_bytes_len,
+ quad_bytes_len,
);
}
- let next_offset = *offset + shadow_bytes_len;
+ let next_offset = *offset + quad_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
@@ -345,7 +423,7 @@ impl MetalRenderer {
metal::MTLPrimitiveType::Triangle,
0,
6,
- shadows.len() as u64,
+ underlines.len() as u64,
);
*offset = next_offset;
}
@@ -533,6 +611,13 @@ fn align_offset(offset: &mut usize) {
*offset = ((*offset + 255) / 256) * 256;
}
+#[repr(C)]
+enum ShadowInputIndex {
+ Vertices = 0,
+ Shadows = 1,
+ ViewportSize = 2,
+}
+
#[repr(C)]
enum QuadInputIndex {
Vertices = 0,
@@ -541,9 +626,9 @@ enum QuadInputIndex {
}
#[repr(C)]
-enum ShadowInputIndex {
+enum UnderlineInputIndex {
Vertices = 0,
- Shadows = 1,
+ Underlines = 1,
ViewportSize = 2,
}
@@ -193,6 +193,53 @@ fragment float4 shadow_fragment(ShadowVertexOutput input [[stage_in]],
return input.color * float4(1., 1., 1., alpha);
}
+struct UnderlineVertexOutput {
+ float4 position [[position]];
+ float4 color [[flat]];
+ uint underline_id [[flat]];
+};
+
+vertex UnderlineVertexOutput underline_vertex(
+ uint unit_vertex_id [[vertex_id]], uint underline_id [[instance_id]],
+ constant float2 *unit_vertices [[buffer(UnderlineInputIndex_Vertices)]],
+ constant Underline *underlines [[buffer(UnderlineInputIndex_Underlines)]],
+ constant Size_DevicePixels *viewport_size
+ [[buffer(ShadowInputIndex_ViewportSize)]]) {
+ float2 unit_vertex = unit_vertices[unit_vertex_id];
+ Underline underline = underlines[underline_id];
+ float4 device_position =
+ to_device_position(unit_vertex, underline.bounds,
+ underline.content_mask.bounds, viewport_size);
+ float4 color = hsla_to_rgba(underline.color);
+ return UnderlineVertexOutput{device_position, color, underline_id};
+}
+
+fragment float4 underline_fragment(UnderlineVertexOutput input [[stage_in]],
+ constant Underline *underlines
+ [[buffer(UnderlineInputIndex_Underlines)]]) {
+ Underline underline = underlines[input.underline_id];
+ if (underline.wavy) {
+ float half_thickness = underline.thickness * 0.5;
+ float2 origin =
+ float2(underline.bounds.origin.x, underline.bounds.origin.y);
+ float2 st = ((input.position.xy - origin) / underline.bounds.size.height) -
+ float2(0., 0.5);
+ float frequency = (M_PI_F * (3. * underline.thickness)) / 8.;
+ float amplitude = 1. / (2. * underline.thickness);
+ float sine = sin(st.x * frequency) * amplitude;
+ float dSine = cos(st.x * frequency) * amplitude * frequency;
+ float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
+ float distance_in_pixels = distance * underline.bounds.size.height;
+ float distance_from_top_border = distance_in_pixels - half_thickness;
+ float distance_from_bottom_border = distance_in_pixels + half_thickness;
+ float alpha = saturate(
+ 0.5 - max(-distance_from_bottom_border, distance_from_top_border));
+ return input.color * float4(1., 1., 1., alpha);
+ } else {
+ return input.color;
+ }
+}
+
struct MonochromeSpriteVertexOutput {
float4 position [[position]];
float2 tile_position;
@@ -211,8 +258,8 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
float2 unit_vertex = unit_vertices[unit_vertex_id];
MonochromeSprite sprite = sprites[sprite_id];
- // Don't apply content mask at the vertex level because we don't have time to
- // make sampling from the texture match the mask.
+ // Don't apply content mask at the vertex level because we don't have time
+ // to make sampling from the texture match the mask.
float4 device_position = to_device_position(unit_vertex, sprite.bounds,
sprite.bounds, viewport_size);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
@@ -254,8 +301,8 @@ vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
float2 unit_vertex = unit_vertices[unit_vertex_id];
PolychromeSprite sprite = sprites[sprite_id];
- // Don't apply content mask at the vertex level because we don't have time to
- // make sampling from the texture match the mask.
+ // Don't apply content mask at the vertex level because we don't have time
+ // to make sampling from the texture match the mask.
float4 device_position = to_device_position(unit_vertex, sprite.bounds,
sprite.bounds, viewport_size);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
@@ -17,8 +17,9 @@ pub type DrawOrder = u32;
pub struct Scene {
pub(crate) scale_factor: f32,
pub(crate) layers: BTreeMap<StackingOrder, LayerId>,
- pub quads: Vec<Quad>,
pub shadows: Vec<Shadow>,
+ pub quads: Vec<Quad>,
+ pub underlines: Vec<Underline>,
pub monochrome_sprites: Vec<MonochromeSprite>,
pub polychrome_sprites: Vec<PolychromeSprite>,
}
@@ -28,8 +29,9 @@ impl Scene {
Scene {
scale_factor,
layers: BTreeMap::new(),
- quads: Vec::new(),
shadows: Vec::new(),
+ quads: Vec::new(),
+ underlines: Vec::new(),
monochrome_sprites: Vec::new(),
polychrome_sprites: Vec::new(),
}
@@ -39,8 +41,9 @@ impl Scene {
Scene {
scale_factor: self.scale_factor,
layers: mem::take(&mut self.layers),
- quads: mem::take(&mut self.quads),
shadows: mem::take(&mut self.shadows),
+ quads: mem::take(&mut self.quads),
+ underlines: mem::take(&mut self.underlines),
monochrome_sprites: mem::take(&mut self.monochrome_sprites),
polychrome_sprites: mem::take(&mut self.polychrome_sprites),
}
@@ -51,13 +54,17 @@ impl Scene {
let layer_id = *self.layers.entry(layer_id).or_insert(next_id);
let primitive = primitive.into();
match primitive {
+ Primitive::Shadow(mut shadow) => {
+ shadow.order = layer_id;
+ self.shadows.push(shadow);
+ }
Primitive::Quad(mut quad) => {
quad.order = layer_id;
self.quads.push(quad);
}
- Primitive::Shadow(mut shadow) => {
- shadow.order = layer_id;
- self.shadows.push(shadow);
+ Primitive::Underline(mut underline) => {
+ underline.order = layer_id;
+ self.underlines.push(underline);
}
Primitive::MonochromeSprite(mut sprite) => {
sprite.order = layer_id;
@@ -78,15 +85,26 @@ impl Scene {
}
// Add all primitives to the BSP splitter to determine draw order
+ // todo!("reuse the same splitter")
let mut splitter = BspSplitter::new();
+
+ for (ix, shadow) in self.shadows.iter().enumerate() {
+ let z = layer_z_values[shadow.order as LayerId as usize];
+ splitter.add(shadow.bounds.to_bsp_polygon(z, (PrimitiveKind::Shadow, ix)));
+ }
+
for (ix, quad) in self.quads.iter().enumerate() {
let z = layer_z_values[quad.order as LayerId as usize];
splitter.add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix)));
}
- for (ix, shadow) in self.shadows.iter().enumerate() {
- let z = layer_z_values[shadow.order as LayerId as usize];
- splitter.add(shadow.bounds.to_bsp_polygon(z, (PrimitiveKind::Shadow, ix)));
+ for (ix, underline) in self.underlines.iter().enumerate() {
+ let z = layer_z_values[underline.order as LayerId as usize];
+ splitter.add(
+ underline
+ .bounds
+ .to_bsp_polygon(z, (PrimitiveKind::Underline, ix)),
+ );
}
for (ix, monochrome_sprite) in self.monochrome_sprites.iter().enumerate() {
@@ -111,8 +129,11 @@ impl Scene {
// We need primitives to be repr(C), hence the weird reuse of the order field for two different types.
for (draw_order, polygon) in splitter.sort(Vector3D::new(0., 0., 1.)).iter().enumerate() {
match polygon.anchor {
- (PrimitiveKind::Quad, ix) => self.quads[ix].order = draw_order as DrawOrder,
(PrimitiveKind::Shadow, ix) => self.shadows[ix].order = draw_order as DrawOrder,
+ (PrimitiveKind::Quad, ix) => self.quads[ix].order = draw_order as DrawOrder,
+ (PrimitiveKind::Underline, ix) => {
+ self.underlines[ix].order = draw_order as DrawOrder
+ }
(PrimitiveKind::MonochromeSprite, ix) => {
self.monochrome_sprites[ix].order = draw_order as DrawOrder
}
@@ -123,18 +144,22 @@ impl Scene {
}
// Sort the primitives
- self.quads.sort_unstable();
self.shadows.sort_unstable();
+ self.quads.sort_unstable();
+ self.underlines.sort_unstable();
self.monochrome_sprites.sort_unstable();
self.polychrome_sprites.sort_unstable();
BatchIterator {
- quads: &self.quads,
- quads_start: 0,
- quads_iter: self.quads.iter().peekable(),
shadows: &self.shadows,
shadows_start: 0,
shadows_iter: self.shadows.iter().peekable(),
+ quads: &self.quads,
+ quads_start: 0,
+ quads_iter: self.quads.iter().peekable(),
+ underlines: &self.underlines,
+ underlines_start: 0,
+ underlines_iter: self.underlines.iter().peekable(),
monochrome_sprites: &self.monochrome_sprites,
monochrome_sprites_start: 0,
monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
@@ -152,6 +177,9 @@ struct BatchIterator<'a> {
shadows: &'a [Shadow],
shadows_start: usize,
shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
+ underlines: &'a [Underline],
+ underlines_start: usize,
+ underlines_iter: Peekable<slice::Iter<'a, Underline>>,
monochrome_sprites: &'a [MonochromeSprite],
monochrome_sprites_start: usize,
monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
@@ -165,11 +193,15 @@ impl<'a> Iterator for BatchIterator<'a> {
fn next(&mut self) -> Option<Self::Item> {
let mut orders_and_kinds = [
- (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
(
self.shadows_iter.peek().map(|s| s.order),
PrimitiveKind::Shadow,
),
+ (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
+ (
+ self.underlines_iter.peek().map(|u| u.order),
+ PrimitiveKind::Underline,
+ ),
(
self.monochrome_sprites_iter.peek().map(|s| s.order),
PrimitiveKind::MonochromeSprite,
@@ -190,6 +222,21 @@ impl<'a> Iterator for BatchIterator<'a> {
};
match batch_kind {
+ PrimitiveKind::Shadow => {
+ let shadows_start = self.shadows_start;
+ let mut shadows_end = shadows_start;
+ while self
+ .shadows_iter
+ .next_if(|shadow| shadow.order <= max_order)
+ .is_some()
+ {
+ shadows_end += 1;
+ }
+ self.shadows_start = shadows_end;
+ Some(PrimitiveBatch::Shadows(
+ &self.shadows[shadows_start..shadows_end],
+ ))
+ }
PrimitiveKind::Quad => {
let quads_start = self.quads_start;
let mut quads_end = quads_start;
@@ -203,19 +250,19 @@ impl<'a> Iterator for BatchIterator<'a> {
self.quads_start = quads_end;
Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
}
- PrimitiveKind::Shadow => {
- let shadows_start = self.shadows_start;
- let mut shadows_end = shadows_start;
+ PrimitiveKind::Underline => {
+ let underlines_start = self.underlines_start;
+ let mut underlines_end = underlines_start;
while self
- .shadows_iter
- .next_if(|shadow| shadow.order <= max_order)
+ .underlines_iter
+ .next_if(|underline| underline.order <= max_order)
.is_some()
{
- shadows_end += 1;
+ underlines_end += 1;
}
- self.shadows_start = shadows_end;
- Some(PrimitiveBatch::Shadows(
- &self.shadows[shadows_start..shadows_end],
+ self.underlines_start = underlines_end;
+ Some(PrimitiveBatch::Underlines(
+ &self.underlines[underlines_start..underlines_end],
))
}
PrimitiveKind::MonochromeSprite => {
@@ -265,22 +312,25 @@ pub enum PrimitiveKind {
Shadow,
#[default]
Quad,
+ Underline,
MonochromeSprite,
PolychromeSprite,
}
#[derive(Clone, Debug)]
pub enum Primitive {
- Quad(Quad),
Shadow(Shadow),
+ Quad(Quad),
+ Underline(Underline),
MonochromeSprite(MonochromeSprite),
PolychromeSprite(PolychromeSprite),
}
#[derive(Debug)]
pub(crate) enum PrimitiveBatch<'a> {
- Quads(&'a [Quad]),
Shadows(&'a [Shadow]),
+ Quads(&'a [Quad]),
+ Underlines(&'a [Underline]),
MonochromeSprites {
texture_id: AtlasTextureId,
sprites: &'a [MonochromeSprite],
@@ -321,6 +371,35 @@ impl From<Quad> for Primitive {
}
}
+#[derive(Debug, Clone, Eq, PartialEq)]
+#[repr(C)]
+pub struct Underline {
+ pub order: u32,
+ pub bounds: Bounds<ScaledPixels>,
+ pub content_mask: ScaledContentMask,
+ pub thickness: ScaledPixels,
+ pub color: Hsla,
+ pub wavy: bool,
+}
+
+impl Ord for Underline {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.order.cmp(&other.order)
+ }
+}
+
+impl PartialOrd for Underline {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl From<Underline> for Primitive {
+ fn from(underline: Underline) -> Self {
+ Primitive::Underline(underline)
+ }
+}
+
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub struct Shadow {
@@ -344,7 +344,7 @@ impl Default for Style {
pub struct UnderlineStyle {
pub thickness: Pixels,
pub color: Option<Hsla>,
- pub squiggly: bool,
+ pub wavy: bool,
}
#[derive(Clone, Debug)]
@@ -416,6 +416,96 @@ pub trait StyleHelpers: Styled<Style = Style> {
self
}
+ fn text_decoration_none(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ self.text_style()
+ .get_or_insert_with(Default::default)
+ .underline = None;
+ self
+ }
+
+ fn text_decoration_color(mut self, color: impl Into<Hsla>) -> Self
+ where
+ Self: Sized,
+ {
+ let style = self.text_style().get_or_insert_with(Default::default);
+ let underline = style.underline.get_or_insert_with(Default::default);
+ underline.color = Some(color.into());
+ self
+ }
+
+ fn text_decoration_solid(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ let style = self.text_style().get_or_insert_with(Default::default);
+ let underline = style.underline.get_or_insert_with(Default::default);
+ underline.wavy = false;
+ self
+ }
+
+ fn text_decoration_wavy(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ let style = self.text_style().get_or_insert_with(Default::default);
+ let underline = style.underline.get_or_insert_with(Default::default);
+ underline.wavy = true;
+ self
+ }
+
+ fn text_decoration_0(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ let style = self.text_style().get_or_insert_with(Default::default);
+ let underline = style.underline.get_or_insert_with(Default::default);
+ underline.thickness = px(0.);
+ self
+ }
+
+ fn text_decoration_1(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ let style = self.text_style().get_or_insert_with(Default::default);
+ let underline = style.underline.get_or_insert_with(Default::default);
+ underline.thickness = px(1.);
+ self
+ }
+
+ fn text_decoration_2(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ let style = self.text_style().get_or_insert_with(Default::default);
+ let underline = style.underline.get_or_insert_with(Default::default);
+ underline.thickness = px(2.);
+ self
+ }
+
+ fn text_decoration_4(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ let style = self.text_style().get_or_insert_with(Default::default);
+ let underline = style.underline.get_or_insert_with(Default::default);
+ underline.thickness = px(4.);
+ self
+ }
+
+ fn text_decoration_8(mut self) -> Self
+ where
+ Self: Sized,
+ {
+ let style = self.text_style().get_or_insert_with(Default::default);
+ let underline = style.underline.get_or_insert_with(Default::default);
+ underline.thickness = px(8.);
+ self
+ }
+
fn font(mut self, family_name: impl Into<SharedString>) -> Self
where
Self: Sized,
@@ -134,9 +134,11 @@ impl Line {
origin.y + baseline_offset.y + (self.layout.descent * 0.618),
),
UnderlineStyle {
- color: style_run.underline.color,
+ color: Some(
+ style_run.underline.color.unwrap_or(style_run.color),
+ ),
thickness: style_run.underline.thickness,
- squiggly: style_run.underline.squiggly,
+ wavy: style_run.underline.wavy,
},
));
}
@@ -153,8 +155,12 @@ impl Line {
continue;
}
- if let Some((_underline_origin, _underline_style)) = finished_underline {
- todo!()
+ if let Some((underline_origin, underline_style)) = finished_underline {
+ cx.paint_underline(
+ underline_origin,
+ glyph_origin.x - underline_origin.x,
+ &underline_style,
+ )?;
}
if glyph.is_emoji {
@@ -171,15 +177,13 @@ impl Line {
}
}
- if let Some((_underline_start, _underline_style)) = underline.take() {
- let _line_end_x = origin.x + self.layout.width;
- // cx.scene().push_underline(Underline {
- // origin: underline_start,
- // width: line_end_x - underline_start.x,
- // color: underline_style.color,
- // thickness: underline_style.thickness.into(),
- // squiggly: underline_style.squiggly,
- // });
+ if let Some((underline_start, underline_style)) = underline.take() {
+ let line_end_x = origin.x + self.layout.width;
+ cx.paint_underline(
+ underline_start,
+ line_end_x - underline_start.x,
+ &underline_style,
+ )?;
}
Ok(())
@@ -188,7 +192,7 @@ impl Line {
pub fn paint_wrapped(
&self,
origin: Point<Pixels>,
- _visible_bounds: Bounds<Pixels>,
+ _visible_bounds: Bounds<Pixels>, // todo!("use clipping")
line_height: Pixels,
boundaries: &[ShapedBoundary],
cx: &mut WindowContext,
@@ -213,14 +217,12 @@ impl Line {
.map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
{
boundaries.next();
- if let Some((_underline_origin, _underline_style)) = underline.take() {
- // cx.scene().push_underline(Underline {
- // origin: underline_origin,
- // width: glyph_origin.x - underline_origin.x,
- // thickness: underline_style.thickness.into(),
- // color: underline_style.color.unwrap(),
- // squiggly: underline_style.squiggly,
- // });
+ if let Some((underline_origin, underline_style)) = underline.take() {
+ cx.paint_underline(
+ underline_origin,
+ glyph_origin.x - underline_origin.x,
+ &underline_style,
+ )?;
}
glyph_origin = point(origin.x, glyph_origin.y + line_height);
@@ -249,7 +251,7 @@ impl Line {
style_run.underline.color.unwrap_or(style_run.color),
),
thickness: style_run.underline.thickness,
- squiggly: style_run.underline.squiggly,
+ wavy: style_run.underline.wavy,
},
));
}
@@ -260,14 +262,12 @@ impl Line {
}
}
- if let Some((_underline_origin, _underline_style)) = finished_underline {
- // cx.scene().push_underline(Underline {
- // origin: underline_origin,
- // width: glyph_origin.x - underline_origin.x,
- // thickness: underline_style.thickness.into(),
- // color: underline_style.color.unwrap(),
- // squiggly: underline_style.squiggly,
- // });
+ if let Some((underline_origin, underline_style)) = finished_underline {
+ cx.paint_underline(
+ underline_origin,
+ glyph_origin.x - underline_origin.x,
+ &underline_style,
+ )?;
}
let text_system = cx.text_system();
@@ -298,15 +298,13 @@ impl Line {
}
}
- if let Some((_underline_origin, _underline_style)) = underline.take() {
- // let line_end_x = glyph_origin.x + self.layout.width - prev_position;
- // cx.scene().push_underline(Underline {
- // origin: underline_origin,
- // width: line_end_x - underline_origin.x,
- // thickness: underline_style.thickness.into(),
- // color: underline_style.color,
- // squiggly: underline_style.squiggly,
- // });
+ if let Some((underline_origin, underline_style)) = underline.take() {
+ let line_end_x = glyph_origin.x + self.layout.width - prev_position;
+ cx.paint_underline(
+ underline_origin,
+ line_end_x - underline_origin.x,
+ &underline_style,
+ )?;
}
Ok(())
@@ -1,10 +1,11 @@
use crate::{
- image_cache::RenderImageParams, px, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
- BorrowAppContext, Bounds, Context, Corners, DevicePixels, DisplayId, Effect, Element, EntityId,
- FontId, GlyphId, Handle, Hsla, ImageData, IsZero, LayoutId, MainThread, MainThreadOnly,
- MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference,
- RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, StackingOrder,
- Style, TaffyLayoutEngine, Task, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
+ image_cache::RenderImageParams, px, size, AnyView, AppContext, AsyncWindowContext,
+ AvailableSpace, BorrowAppContext, Bounds, Context, Corners, DevicePixels, DisplayId, Effect,
+ Element, EntityId, FontId, GlyphId, Handle, Hsla, ImageData, IsZero, LayoutId, MainThread,
+ MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point,
+ PolychromeSprite, Reference, RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene,
+ SharedString, Size, StackingOrder, Style, TaffyLayoutEngine, Task, Underline, UnderlineStyle,
+ WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
};
use anyhow::Result;
use smallvec::SmallVec;
@@ -259,6 +260,38 @@ impl<'a, 'w> WindowContext<'a, 'w> {
self.window.current_stacking_order.clone()
}
+ pub fn paint_underline(
+ &mut self,
+ origin: Point<Pixels>,
+ width: Pixels,
+ style: &UnderlineStyle,
+ ) -> Result<()> {
+ let scale_factor = self.scale_factor();
+ let height = if style.wavy {
+ style.thickness * 3.
+ } else {
+ style.thickness
+ };
+ let bounds = Bounds {
+ origin,
+ size: size(width, height),
+ };
+ let content_mask = self.content_mask();
+ let layer_id = self.current_stacking_order();
+ self.window.scene.insert(
+ layer_id,
+ Underline {
+ order: 0,
+ bounds: bounds.scale(scale_factor),
+ content_mask: content_mask.scale(scale_factor),
+ thickness: style.thickness.scale(scale_factor),
+ color: style.color.unwrap_or_default(),
+ wavy: style.wavy,
+ },
+ );
+ Ok(())
+ }
+
pub fn paint_glyph(
&mut self,
origin: Point<Pixels>,
@@ -160,7 +160,13 @@ impl Titlebar {
// .fill(theme.lowest.base.hovered.background)
// .active()
// .fill(theme.lowest.base.pressed.background)
- .child(div().text_sm().child("branch")),
+ .child(
+ div()
+ .text_sm()
+ .text_decoration_1()
+ .text_decoration_wavy()
+ .child("branch"),
+ ),
),
)
}