Clean up debug statements, restore proper icons, document multi-line text spacing

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Greg Pomerantz 2026-05-09 14:48:12 -04:00
parent f67cca1f4b
commit 9fd6239129
3 changed files with 101 additions and 3 deletions

View File

@ -476,12 +476,110 @@ Look for:
- **Text off-screen**: Wrong offset calculation - **Text off-screen**: Wrong offset calculation
- **Text clipped**: Clip region doesn't include text bounds - **Text clipped**: Clip region doesn't include text bounds
## 9. Multi-line Text Spacing
When laying out multiple lines of text at different positions, you need to understand how Gio calculates baseline spacing.
### 9.1 Gio's Line Height Calculation
In `gio/text/gotext.go` (`calculateYOffsets` and `LayoutRunes`):
```go
// First line baseline starts at the ascent height
currentY := lines[0].ascent.Ceil()
// Subsequent baselines are spaced by lineHeight
for i := range lines {
if i > 0 {
currentY += lines[i].lineHeight.Round()
}
lines[i].yOffset = currentY
}
// lineHeight = max(ascent + descent) * LineHeightScale
// Default LineHeightScale = 1.2
if params.LineHeight != 0 {
maxHeight = params.LineHeight
}
if params.LineHeightScale == 0 {
params.LineHeightScale = 1.2
}
maxHeight = floatToFixed(fixedToFloat(maxHeight) * params.LineHeightScale)
```
**Key points**:
- **First line baseline**: `ascent.Ceil()` (not 0)
- **Baseline-to-baseline spacing**: `lineHeight * LineHeightScale`
- **Default LineHeightScale**: 1.2 (adds 20% extra padding)
- **LineHeightScale 1.0**: tightest spacing, no extra padding
### 9.2 Positioning Multiple Lines
When drawing multiple lines at different positions (e.g., filename + icons in StatusBar), use the baseline spacing as your guide:
```go
// Line 1: Filename at origin
filenameLineY := reg.Y
// Line 2: Icons — baseline spacing ≈ font size with LineHeightScale=1.0
// For 16SP font: baseline-to-baseline ≈ 20DP
iconsLineY := filenameLineY + unit.Dp(20)
```
**Why 20DP?** For a 16SP font on a typical display:
- `PxPerEm = 16 * 1.25 = 20` device pixels (2x display)
- `ascent + descent ≈ PxPerEm = 20`
- With `LineHeightScale = 1.0`: baseline spacing = 20DP
### 9.3 Controlling Spacing
You have two options to control line spacing:
**Option A: Set `LineHeightScale` in LayoutString**
```go
shp.LayoutString(text.Parameters{
PxPerEm: fixed.I(gtx.Sp(size)),
MinWidth: 0,
MaxWidth: availableWidth,
MaxLines: 1,
LineHeightScale: 1.0, // Tight spacing, no extra padding
}, str)
```
**Option B: Set `LineHeight` to a specific value**
```go
shp.LayoutString(text.Parameters{
PxPerEm: fixed.I(gtx.Sp(size)),
MinWidth: 0,
MaxWidth: availableWidth,
MaxLines: 1,
LineHeight: fixed.I(gtx.Sp(18)), // Fixed 18SP baseline spacing
}, str)
```
### 9.4 Common Mistakes
**Mistake**: Adding arbitrary spacing between lines without considering baseline positioning.
**Fix**: Remember that the shaper's Y value is the **baseline**, not the top of the text. The visual top of the text is at `baseline - ascent`. So the visual gap between two lines is:
```
visualGap = (line2Baseline - line1Baseline) - (ascent1 + ascent2)
= lineHeight - (ascent1 + ascent2)
```
With `LineHeightScale = 1.0`: `visualGap ≈ 0` (lines touch)
With `LineHeightScale = 1.2`: `visualGap ≈ 0.2 * lineHeight` (20% padding)
## 10. Summary ## 10. Summary
- **Always** use `fixed.I(gtx.Sp(size))` for `PxPerEm` - **Always** use `fixed.I(gtx.Sp(size))` for `PxPerEm`
- **Always** set `MinWidth`, `MaxWidth`, `MaxLines` in `LayoutString` parameters - **Always** set `MinWidth`, `MaxWidth`, `MaxLines` in `LayoutString` parameters
- **Always** offset by `(x + first.X, y + first.Y)` in `drawLine` - **Always** offset by `(x + first.X, y + first.Y)` in `drawLine`
- **Always** wrap glyph drawing in `op.Record`/`m.Stop()` for clipping - **Always** wrap glyph drawing in `op.Record`/`m.Stop()` for clipping
- **Baseline spacing** = `lineHeight * LineHeightScale` (default 1.2)
- **For tight multi-line layouts**, use `LineHeightScale: 1.0` or set explicit `LineHeight`
- **Visual gap** between lines = `baselineSpacing - (ascent1 + ascent2)`
- **Avoid** using `material.Label()` when you need precise glyph positions - **Avoid** using `material.Label()` when you need precise glyph positions
- **Reuse** glyph data for measurement, hit testing, and rendering - **Reuse** glyph data for measurement, hit testing, and rendering
- **Follow** Gio's `paintGlyph` as the reference implementation - **Follow** Gio's `paintGlyph` as the reference implementation

View File

@ -90,7 +90,6 @@ func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar) {
// Line 1: Filename (truncated with ellipsis if needed) // Line 1: Filename (truncated with ellipsis if needed)
filenameLineY := reg.Y filenameLineY := reg.Y
filenameLineH := unit.Dp(24)
// Truncate filename only if it doesn't fit // Truncate filename only if it doesn't fit
displayFilename := sb.Filename displayFilename := sb.Filename
@ -117,8 +116,9 @@ func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar) {
// Layout filename, measure widths, and draw in a single pass // Layout filename, measure widths, and draw in a single pass
r.drawTruncatedText(gtx, th.Shaper, displayFilename, r.theme.FontSize, reg.X+unit.Dp(8), filenameLineY, availableWidth, spaceForText, color.NRGBA{R: 0, G: 0, B: 0, A: 255}) r.drawTruncatedText(gtx, th.Shaper, displayFilename, r.theme.FontSize, reg.X+unit.Dp(8), filenameLineY, availableWidth, spaceForText, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
// Line 2: Icons // Line 2: Icons — positioned 20DP below filename baseline.
iconsLineY := filenameLineY + filenameLineH // This matches Gio's default line spacing (ascent+descent with LineHeightScale=1.0).
iconsLineY := filenameLineY + unit.Dp(20)
// Left side: Cut, Copy, Paste icons // Left side: Cut, Copy, Paste icons
leftX := reg.X + unit.Dp(8) leftX := reg.X + unit.Dp(8)

BIN
pad

Binary file not shown.