Density (pure hi-DPI) was already scale-free: all bookkeeping is in density-dp and the scale enters only at the px<->dp boundary. But Android also has a second axis, the user font-size setting (PxPerSp = fontScale * PxPerDp), and the shaper draws baselines in sp. At a non-default font scale the rendered line pitch is 16.8*fontScale dp while every logic-side consumer used the raw 16.8 dp: taps would misplace by up to (fontScale-1) viewportfuls of lines and scroll clamping would stop short of the bottom. - ScaleEvent.FontScale + Frame.FontScale closed loop (main reads gtx.Metric, logic tracks it in State.fontScale). - EffectiveLineHeight()/EffectiveLineHeightAt(): the font-scale-applied line height, now used by every consumer (window start, sub-line remainder, tap mapping, scroll clamp, page size, cursor vertical move, menu position, chunk-prefetch fallbacks). - Renderer: GlyphLayout.LineHeight, caret, selection handles, and highlight all use the scaled ascent/line-height from gtx.Metric. - font_scale_test.go: 2000-pair tap property test at fontScale 1.3 with the glyph layout fabricated at the scaled pitch (independent ground truth), plus EffectiveLineHeight unit test. - On-device: tap markers landed on exactly the tapped line at font_scale 1.3 (fsline060/080/081) and 0.8 (fsline039); rendered pitch measured 57/35/44 px at 1.3/0.8/1.0 (matches 16.8*fs*2.625); settled-position window start k = floor(s/lh_eff) verified against the visible top line. - Docs: architecture.md 6.2 font-scale axis, README two-scale note + profiler 2s flush staleness note, development plan v10 Phase 11.
70 lines
2.3 KiB
Go
70 lines
2.3 KiB
Go
package editor
|
|
|
|
import (
|
|
"time"
|
|
|
|
"pad/internal/ui"
|
|
)
|
|
|
|
// Frame is the unit of handoff from the logic goroutine to the frame
|
|
// receiver. Per architecture.md §9, the frame is the ONLY cross-goroutine
|
|
// state carrier: the main goroutine must never read *State directly, it
|
|
// reads the snapshot stored here by the frame receiver (under the frame
|
|
// mutex).
|
|
//
|
|
// Elems is the computed element tree for the next draw.
|
|
// The remaining fields are the view-state snapshot that the main goroutine
|
|
// needs to route events and forward input:
|
|
//
|
|
// - Scale: current px-per-Dp, so the main goroutine can detect scale
|
|
// changes and pass the scale to the renderer's Draw call.
|
|
// - FocusedElementID: which registered element receives key/edit events.
|
|
// - Query: the search query the logic goroutine is currently filtering
|
|
// with; the main goroutine compares it against the (main-owned) search
|
|
// widget's text and forwards changes via SearchQueryChan.
|
|
type Frame struct {
|
|
Elems []ui.Element
|
|
Scale float32
|
|
FontScale float32 // user font-size setting the logic bookkeeping used
|
|
FocusedElementID string
|
|
Query string
|
|
}
|
|
|
|
// frameOf wraps a computed element tree with the current view-state
|
|
// snapshot. Must be called on the logic goroutine.
|
|
func (l *Logic) frameOf(elems []ui.Element) Frame {
|
|
return Frame{
|
|
Elems: elems,
|
|
Scale: l.state.scale,
|
|
FontScale: l.state.fontScale,
|
|
FocusedElementID: l.state.FocusedElementID,
|
|
Query: l.state.Browser.Query,
|
|
}
|
|
}
|
|
|
|
// inspectReq is a test-only request to run fn on the logic goroutine.
|
|
// It preserves the single-owner invariant (architecture.md §1): the fn
|
|
// executes on the owner, not on the caller. fn must not block on sends to
|
|
// logic channels.
|
|
type inspectReq struct {
|
|
fn func(st *State) any
|
|
resp chan any
|
|
}
|
|
|
|
// Inspect runs fn on the logic goroutine and returns its result. It is
|
|
// intended for tests; production code must use the regular channels.
|
|
func (l *Logic) Inspect(fn func(st *State) any) (any, bool) {
|
|
req := &inspectReq{fn: fn, resp: make(chan any, 1)}
|
|
select {
|
|
case l.inspectChan <- req:
|
|
case <-time.After(5 * time.Second):
|
|
return nil, false
|
|
}
|
|
select {
|
|
case v := <-req.resp:
|
|
return v, true
|
|
case <-time.After(5 * time.Second):
|
|
return nil, false
|
|
}
|
|
}
|