Checkpoint: underlines

This commit is contained in:
Antonio Scandurra 2023-10-06 15:34:37 +02:00
parent 65c7765c07
commit ca6eb5511c
9 changed files with 495 additions and 155 deletions

View file

@ -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(),

View file

@ -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,62 +252,6 @@ impl MetalRenderer {
drawable.present();
}
fn draw_quads(
&mut self,
quads: &[Quad],
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if quads.is_empty() {
return;
}
align_offset(offset);
command_encoder.set_render_pipeline_state(&self.quads_pipeline_state);
command_encoder.set_vertex_buffer(
QuadInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
QuadInputIndex::Quads as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_fragment_buffer(
QuadInputIndex::Quads as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
QuadInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
let quad_bytes_len = mem::size_of::<Quad>() * quads.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len);
}
let next_offset = *offset + quad_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
quads.len() as u64,
);
*offset = next_offset;
}
fn draw_shadows(
&mut self,
shadows: &[Shadow],
@ -350,6 +312,122 @@ impl MetalRenderer {
*offset = next_offset;
}
fn draw_quads(
&mut self,
quads: &[Quad],
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if quads.is_empty() {
return;
}
align_offset(offset);
command_encoder.set_render_pipeline_state(&self.quads_pipeline_state);
command_encoder.set_vertex_buffer(
QuadInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
QuadInputIndex::Quads as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_fragment_buffer(
QuadInputIndex::Quads as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
QuadInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
let quad_bytes_len = mem::size_of::<Quad>() * quads.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len);
}
let next_offset = *offset + quad_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
quads.len() as u64,
);
*offset = next_offset;
}
fn draw_underlines(
&mut self,
underlines: &[Underline],
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if underlines.is_empty() {
return;
}
align_offset(offset);
command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state);
command_encoder.set_vertex_buffer(
UnderlineInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
UnderlineInputIndex::Underlines as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_fragment_buffer(
UnderlineInputIndex::Underlines as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
UnderlineInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
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(
underlines.as_ptr() as *const u8,
buffer_contents,
quad_bytes_len,
);
}
let next_offset = *offset + quad_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
underlines.len() as u64,
);
*offset = next_offset;
}
fn draw_monochrome_sprites(
&mut self,
texture_id: AtlasTextureId,
@ -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,
}

View file

@ -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);

View file

@ -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,19 +222,6 @@ impl<'a> Iterator for BatchIterator<'a> {
};
match batch_kind {
PrimitiveKind::Quad => {
let quads_start = self.quads_start;
let mut quads_end = quads_start;
while self
.quads_iter
.next_if(|quad| quad.order <= max_order)
.is_some()
{
quads_end += 1;
}
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;
@ -218,6 +237,34 @@ impl<'a> Iterator for BatchIterator<'a> {
&self.shadows[shadows_start..shadows_end],
))
}
PrimitiveKind::Quad => {
let quads_start = self.quads_start;
let mut quads_end = quads_start;
while self
.quads_iter
.next_if(|quad| quad.order <= max_order)
.is_some()
{
quads_end += 1;
}
self.quads_start = quads_end;
Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
}
PrimitiveKind::Underline => {
let underlines_start = self.underlines_start;
let mut underlines_end = underlines_start;
while self
.underlines_iter
.next_if(|underline| underline.order <= max_order)
.is_some()
{
underlines_end += 1;
}
self.underlines_start = underlines_end;
Some(PrimitiveBatch::Underlines(
&self.underlines[underlines_start..underlines_end],
))
}
PrimitiveKind::MonochromeSprite => {
let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.monochrome_sprites_start;
@ -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 {

View file

@ -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)]

View file

@ -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,

View file

@ -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(())

View file

@ -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>,

View file

@ -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"),
),
),
)
}