Pad/internal/ui/unit.go
Greg Pomerantz 7240b62a61 WIP baseline: Termux open-file bridge + word-wrap-aware viewport/scroll
- JNI: open_file_in_termux via ACTION_SEND intent (text/plain + file:// uri),
  global context ref kept from registerFragment
- impl_android.go: OpenFile(path) attaches current thread if needed
- NewLogic takes openfunc; State.open + ui.OpenFile now func(string)
- ChunkedBuffer.VisibleByteRange: word-wrap path using
  GlyphLayout.VisualLineStarts (+byteOffset, lineHeight, visual index param)
- GlyphLayout gains LineHeight; drawWrappedText records VisualLineStarts
- WordWrap default true; State.ByteOffset tracks first visible line
- types: VisualLineIndex
- scroll_fix_test.go (new)
- debug prints left in place (WIP; cleanup in later phase)
2026-08-16 00:31:26 -04:00

116 lines
3.7 KiB
Go

package ui
import (
"gioui.org/unit"
"pad/internal/io/pool/types"
)
// Dp represents device-independent pixels. Use for all element positions,
// sizes, and spacing in the logic/layout layer.
type Dp unit.Dp
// Px represents physical device pixels. Use only when interfacing with
// Gio's layout.Context (gtx.Constraints, gtx.Dp(), etc.).
type Px int
// ToDp converts physical pixels to device-independent pixels using the
// given scale factor (pixels per DP). Typically from gtx.Metric.PxPerDp.
func ToDp(px Px, scale float32) Dp {
return Dp(float32(px) / scale)
}
// ToPx converts device-independent pixels to physical pixels using the
// given scale factor (pixels per DP).
func ToPx(dp Dp, scale float32) Px {
return Px(float32(dp) * scale)
}
// PxPerDp returns the scale factor: how many physical pixels per DP.
// Use this to convert between Dp and Px.
func PxPerDp(scale float32) float32 { return scale }
// --- Gio interop helpers ---
// DpToPx converts a gioui unit.Dp to our Px using the scale factor.
func DpToPx(d unit.Dp, scale float32) Px {
return Px(float32(d) * scale)
}
// PxToDp converts our Px to a gioui unit.Dp using the scale factor.
func PxToDp(p Px, scale float32) unit.Dp {
return unit.Dp(float32(p) / scale)
}
// RegionPx is a region in physical pixels. Used for Gio interop only.
type RegionPx struct {
X, Y Px
W, H Px
}
// ToDp converts a RegionPx to a Region (Dp) using the scale factor.
func (r RegionPx) ToDp(scale float32) Region {
return Region{
X: ToDp(r.X, scale),
Y: ToDp(r.Y, scale),
W: ToDp(r.W, scale),
H: ToDp(r.H, scale),
}
}
// FromDp converts a Region (Dp) to a RegionPx using the scale factor.
func FromDp(r Region, scale float32) RegionPx {
return RegionPx{
X: ToPx(r.X, scale),
Y: ToPx(r.Y, scale),
W: ToPx(r.W, scale),
H: ToPx(r.H, scale),
}
}
// GlyphLayout holds per-glyph layout data captured during text shaping.
// Each index i represents one glyph (one rune in the source string).
// ByteOffsets[i] is the byte position in the buffer, (X[i], Y[i]) is the
// glyph's screen location in Dp (X relative to text region origin, Y is the
// shaper baseline), and Advance[i] is the glyph's width in Dp.
// LineHeight is the shaper's actual baseline-to-baseline line height in Dp,
// derived from consecutive lines' Y values.
type GlyphLayout struct {
ByteOffsets []int // byte offset of each glyph in the buffer
X []Dp // screen X (Dp) of each glyph, relative to text region origin
Y []Dp // screen Y (Dp) baseline of each glyph (shaper value)
Advance []Dp // advance width (Dp) of each glyph
LineHeight Dp // shaper's actual baseline-to-baseline line height in Dp
VisualLineStarts []int // byte offsets where each visual line starts (for word wrap)
VisualLineIndex *types.VisualLineIndex // Optional: pre-computed visual line index for this layout
}
// VisualLineOffsets returns the byte offset of each visual line start.
// Uses pre-captured VisualLineStarts if available, otherwise computes from glyph data.
func (gl GlyphLayout) VisualLineOffsets() []int32 {
if len(gl.VisualLineStarts) > 0 {
// Use pre-captured visual line starts (more accurate for word wrap)
offsets := make([]int32, len(gl.VisualLineStarts))
for i, v := range gl.VisualLineStarts {
offsets[i] = int32(v)
}
return offsets
}
// Fallback: compute from glyph data (less accurate for word wrap)
var offsets []int32
if len(gl.ByteOffsets) == 0 {
return offsets
}
// The first line always starts at byte 0
offsets = append(offsets, int32(gl.ByteOffsets[0]))
// Add byte offset for each new Y coordinate
for i := 1; i < len(gl.Y); i++ {
if gl.Y[i] != gl.Y[i-1] {
offsets = append(offsets, int32(gl.ByteOffsets[i]))
}
}
return offsets
}