linebender / linebender/parley

TreeBuilder-based layouts have invisible cursors when text is empty

Open
#391 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
736
Forks
120
Avg merge
1d 3h
Merged PRs (30d)
53

Description

Even when the text is empty, a layout needs to have some proper line metrics, so that for example `Cursor::geometry(&layout)` can give a cursor of the correct size. `RangedBuilder` does this correctly, but `TreeBuilder` just fills the whole `LineMetrics` with zeroes. This results in an invisible cursor for any `TreeBuilder`-based text edit box when the text is empty.

This is a minimal example:

````rust
use parley::*;

fn main() {
let mut font_cx = FontContext::new();
let mut layout_cx = LayoutContext::new();
let style: TextStyle<[u8; 4]> = TextStyle::default();

println!("RangedBuilder with empty text ");
test_cursor_geometry_ranged(&mut layout_cx, &mut font_cx, &style, "");

println!();

println!("TreeBuilder with empty text ");
test_cursor_geometry(&mut layout_cx, &mut font_cx, &style, "");
}

fn test_cursor_geometry(layout_cx: &mut LayoutContext, font_cx: &mut FontContext, style: &TextStyle<[u8; 4]>, text: &str) {
let mut builder = layout_cx.tree_builder(font_cx, 1.0, true, style);

builder.push_text(text);
let (mut layout, _) = builder.build();
layout.break_all_lines(Some(400.0));

for (i, line) in layout.lines().enumerate() {
println!("Line {}: metrics = {:?}", i, line.metrics());
}

let cursor = Cursor::from_byte_index(&layout, 0, Affinity::Downstream);
let geometry = cursor.geometry(&layout, 1.0);
println!("Cursor geometry: {:?}", geometry);
}

fn test_cursor_geometry_ranged(layout_cx: &mut LayoutContext, font_cx: &mut FontContext, style: &TextStyle<[u8; 4]>, text: &str) {
let mut builder = layout_cx.ranged_builder(font_cx, text, 1.0, true);

builder.push_default(StyleProperty::FontStack(FontStack::from(&[FontFamily::Named("system-ui".into())] as &[FontFamily])));
builder.push_default(StyleProperty::FontSize(style.font_size));

let mut layout = builder.build(text);

layout.break_all_lines(Some(400.0));

for (i, line) in layout.lines().enumerate() {
println!("Line {}: metrics = {:?}", i, line.metrics());
}

let cursor = Cursor::from_byte_index(&layout, 0, Affinity::Downstream);
let geometry = cursor.geometry(&layout, 1.0);
println!("Cursor geometry: {:?}", geometry);
}
````

Output:

````
RangedBuilder with empty text
Line 0: metrics = LineMetrics { ascent: 17.104, descent: 4.688, leading: 0.0, line_height: 21.792, baseline: 16.0, offset: 0.0, advance: 4.1600003, trailing_whitespace: 4.1600003, min_coord: -1.0, max_coord: 22.0 }
Cursor geometry: Rect { x0: 0.0, y0: -1.0, x1: 1.0, y1: 22.0 }

TreeBuilder with empty text
Line 0: metrics = LineMetrics { ascent: 0.0, descent: 0.0, leading: 0.0, line_height: 0.0, baseline: 0.0, offset: 0.0, advance: 0.0, trailing_whitespace: 0.0, min_coord: 0.0, max_coord: 0.0 }
Cursor geometry: Rect { x0: 0.0, y0: 0.0, x1: 1.0, y1: 0.0 }

````

I blindly stumbled into a "fix" that looks like this: in `TreeStyleBuilder::finish()`, right before returning, do

```
if styles.is_empty() && self.text.is_empty() {
styles.push(RangedStyle {
style: self.tree[0].style.clone(),
range: 0..0,
});
}
```
This makes the style available even when the text is empty. I don't know if this can be a proper solution.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with TreeStyleBuilder::finish() and compare how TreeBuilder and RangedBuilder produce styles and line metrics for empty text. Reproduce the minimal example and inspect Cursor::geometry(&layout). Done means an empty TreeBuilder layout has nonzero line metrics and a visible cursor, with coverage for the empty-text case.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
computer-graphics
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.