Commit Graph

57 Commits

Author SHA1 Message Date
b24aa26446 Track Android user font scale in all line-height geometry (Phase 11)
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.
2026-08-17 10:59:34 -04:00
e761436908 Prove tap-to-position is scroll-offset independent; fix float32 decomposition bug
The screen->line tap mapping only needs the sub-line scroll remainder
(tapLocalY adds r, never the full scroll) because the visible glyph
layout is window-relative and IMEWindowStartByte re-anchors it to the
file. That holds for every scroll offset IF the window start line
k=floor(s/lh) and the sub-line remainder r stay consistent.

Property test (4000 random scroll/tap pairs, asserting against an
independent drawn-geometry ground truth, not the tap code's own math)
exposed a real bug: int(s/lh) in the Dp float32 domain rounds the
quotient to nearest and can round UP across an integer boundary while
the float64 mod still reflects the line below. In a sub-pixel-wide
band of scroll offsets the window started one line too far while the
draw shift lagged by one line - the whole rendered window (and every
tapped line) shifted by one.

Fix: one shared float64 floor decomposition (scrollDecompose) used by
the window start (visibleByteRangePrecise/Estimate), the renderer's
sub-line shift (visibleScrollOffset), the tap mapping (tapLocalY), and
chunk prefetching. Also route the shaper's line height (previously
ignored by visibleByteRangePrecise) through VisibleByteRange.

On-device cross-check: at s=4246.9 (r=13.3) and s=4210.7 (r=10.7),
taps on visually identified lines typed markers that landed on exactly
those lines in the file on disk; profiler ScrollDP, screenshot,
formula, and disk all agreed.

Docs: scroll decomposition invariant in architecture.md §6.2, pipeline
hop 2 in doc/README.md, Phase 10 in development_plan.md.
2026-08-17 09:57:21 -04:00
2a16c0017c Touch selection (v1): long-press/double-tap word selection, drag handles, floating copy/cut/paste menu
Implement the Android-native touch selection model, verified on-device:
- long-press selects the word under the finger (blank -> caret + paste-only
  menu); double-tap selects the word; drag handles resize the selection,
  drag the highlighted body to move it; floating menu offers copy/cut/paste
  (selection) or paste (bare caret), closing on any item tap.
- Renderer reports finger positions (app-local Dp) as tap/double-tap/
  long-press/selection-drag events; the logic goroutine owns all geometry
  (EditorRegion, menu rect, hit-testing, handles) and the renderer only
  draws the frame snapshot.
- Long press: 400 ms still-press on the editor, cancelled by movement
  (non-grabbing raw pointer probe) or by a scroll/handle grab. The main
  loop keeps invalidating while a press is pending (Gio renders on demand;
  a stationary finger produces no frames).
- Clipboard crosses the goroutine boundary via buffered channels
  (clipboardSetChan/pasteReqChan logic->main, pasteChan main->logic);
  main executes the Gio ops and, on Android, invalidates after ReadCmd
  because a queued transfer.DataEvent schedules no frame of its own.

Renderer fixes found while validating on-device:
- clickReg was stored by value in a map; range yielded copies so per-frame
  press bookkeeping (long-press state) was silently discarded. Now pointers.
- On Android a tap's press+release arrive in the same frame and
  gesture.Click/Drag return one event per Update call; without draining
  each gesture's queue every frame the release was lost on an idle window
  and every menu tap was swallowed (needed a second tap to 'rescue' it).
  Click and drag loops now drain to exhaustion (scroll already does).
- pointer.Filter queries must name Kinds: a zero-kinds filter matches
  nothing (the press-probe query was dead).
- Menu.Draw offsets items by the menu origin; the clippable drawElement
  branch registers SelDrag (handles now draw for the TextField).

Tests: internal/editor/touch_selection_test.go (word range, long-press,
double-tap, tap/menu guards, handle drags, menu actions, selection edits)
and internal/test/e2e/touch_selection_e2e_test.go; full suite green under
-race. Docs: spec.md §2.2 + §7, architecture.md §6.3a, development_plan.md
Phases 8-9.
2026-08-17 08:57:55 -04:00
ec11abf8f1 Add text selection, real-file e2e tests, and Android arrow-key support
Selection: shift+arrow extends a selection (absolute byte offsets,
anchor/caret model); insert/backspace/delete replace the selection; the
IME unions its reported range with the active selection; the highlight
is drawn in the TextField and the selection is pushed to the IME.

Android key input (the blocker found during on-device validation):
Gio v0.10 on Android (a) drops modifier state in the JNI bridge and
(b) wraps plain arrow-key presses in input.SystemEvent for focus
navigation, so arrow keys never reached the editor. main.go now
registers explicit named key.Filters for the four arrows (delivers the
press and suppresses the focus jump) and tracks the shift key itself.
Verified on the emulator: plain arrows move the caret, shift+arrow
shows a highlight, typing replaces the selection.

Real-file e2e tests (real on-disk files via the real FileSystem,
multi-chunk 256KB files, chunk-boundary and multi-byte edits) found
and fixed two real bugs:
  1. Line index: UpdateLineIndexAfterEdit only shifted offsets; edits
     involving newlines left it permanently inconsistent. Replaced with
     newline-aware UpdateLineIndexAfterInsert/UpdateLineIndexAfterDelete.
  2. Rune granularity: HandleBackspace/HandleDelete deleted one byte,
     corrupting multi-byte UTF-8 characters (e.g. a 2-byte char
     straddling a chunk boundary). Now rune-granular.

Also: airtight e2e harness load-wait (StatFile/ReadFile/BuildLineIndex
interleaving could satisfy the old condition early).

Full suite green under -race; on-device verified.
2026-08-17 00:32:53 -04:00
3bc3ed3530 editor: add window base offset to tap/cursor positioning
The GlyphLayout is shaped from the visible window alone, so its
ByteOffsets are relative to the window start (IMEWindowStartByte). But
SetCursorFromPoint, HandleHome, HandleEnd and HandleVerticalCursorMove
treated them as absolute file offsets. On a large file scrolled deep, the
cursor was set to a window-relative offset (near the file start), so the
next EditorLayout computed visibleCursorPos = CursorPosition - start as a
small/negative value and the IME selection snapped to the window top
(regardless of where the user tapped).

Add glyphBase() and convert at the four cursor boundaries: subtract the
base before searching the window-relative offsets, add it back before
storing the absolute CursorPosition. For small files (window == whole
file) the base is 0 and the change is a no-op.

Add TestTapToPosition_WindowBaseOffset, a regression test that sets a
non-zero IMEWindowStartByte and asserts the cursor lands at base + window
byte (fails on the pre-fix window-relative assignment).

Verified on-device: with a 6 MB file scrolled to ~line 1200, a tap now
places the cursor on the tapped line (typed text lands ~16 lines below the
window top), where before it always landed on the window top.
2026-08-16 20:59:06 -04:00
6dedffcba7 editor: fix tap-to-position-cursor clamping on large files
Tapping in the editor repositions the cursor, but it was never actually
verified (the old unit test only checked in-bounds/no-panic). On-device
verification found that on a large file scrolled deep, the cursor clamped to
the bottom of the viewport regardless of tap position.

Root cause: the tap handler computed the tap's text-local Y in content space
(pt.Y - region.Y + full ScrollOffset) and passed it to SetCursorFromPoint,
whose visualLine = y/lineHeight then produced a huge content-line number
(e.g. 91,640). But the GlyphLayout is window-relative (layout.Y==0 is the top
of the visible window), so the number far exceeded the window's line count and
clamped to the last group (bottom line). The Phase 3 windowing refactor
introduced the windowed layout without updating the tap handler.

Fix: tapLocalY() converts the tap Y to window-relative space by adding only the
sub-line remainder (ScrollOffset mod lineHeight), never the full scroll.
Extracted as a named helper so it is unit-testable. Added two regression tests
that fail on the pre-fix formula (cursor clamps to the bottom line) and pass on
the fix. Verified on-device: taps now map linearly across the viewport.

Also removed the per-tap/per-glyph log.Printf debug lines in SetCursorFromPoint.
2026-08-16 17:38:24 -04:00
be48ad8157 perf: default-off in-app profiler + debug scroll jumps; verify scroll perf & clamping
internal/perf: single-goroutine profiler (no locks). When enabled by the
/storage/emulated/0/PadPerf/enable marker it records one CSV row per logic
frame (seq, ms, frame delta, page, scroll Dp, max-scroll Dp, total lines,
visible byte range), logs a rolling ~1/s summary, and on Stop reports
nearest-rank p50/p90/p99/max. Flushes per row-batch but never fsyncs per
flush (avoids periodic hitches in the logic path).

editor: PerfRecord package-level hook (nil when off) + ProbeRecord;
Logic.emitFrame() now centralizes every frame emission so the profiler sees
each logic frame exactly once, on the owner goroutine. State gains
VisibleStart/VisibleEnd so the probe can confirm shaping stays
viewport-bounded.

logic: optional debug cmd poller (off by default) watches <dir>/cmd as a
one-shot file (top/bottom/frac <0..1>/dp <int>) and the owner applies a
clamped [0,MaxScroll] jump + frame. Enables deterministic large-offset scroll
tests without pixel taps.

main: wires the profiler when the marker file exists; logs present-fps
every 2s; stops the profiler on Destroy.

docs: README package inventory (add internal/perf); architecture §11
profiler facility; spec §5 invariant 7 (scroll always clamped to
[0,maxScroll]) + §6 measured rows; development_plan v5 + Phase 6 results.

Verified: go build/vet + full -race green. On-device 10 MB file:
logic-frame cadence flat across offsets 0.02->1.0 (no large-offset
degradation), visible byte range <=4.3 KB at every offset, clamping exact
across 2->130,955-line files, PSS plateaus ~250 MB (bounded, no leak);
profiler overhead negligible.
2026-08-16 16:53:28 -04:00
c79c142397 editor: fix whole-file shaper leak + LineIndex mismatch (Phase 3)
Root cause of the ~1GB 'Unknown' memory on large files: with word wrap
on (the default), VisibleByteRange used the previously-shaped
GlyphLayout.VisualLineStarts to bound the visible byte range. That layout
only covers the visible window (~50 lines), not the whole document, so
whenever the viewport's line count exceeded the window's visual-line count
the range fell back to end=fileLen. The text shaper then laid out the ENTIRE
file each frame; its internal line/glyph buffers grow to the largest layout
ever shaped and are never released (document.reset() keeps the backing cap),
so memory ballooned to the file size (~1.1 GB for 10 MB) and OOM-killed the
process under scroll.

Fix: always derive the visible range from the real-line LineIndex (or the
heuristic estimate before it is ready). Word wrap needs no separate path:
each real line yields >= 1 visual line, so shaping viewportHeight/lineHeight
real lines always fills the viewport, and the range can never collapse to the
whole file.

Also fixed the pre-existing LineIndex storage mismatch: EditorLayout read
TheState.Editor.LineIndex (never set; SetLineIndex had zero callers) for
maxScroll and scrollByteOffset, so it always used the huge pre-index estimate.
It now reads cb.LineIndex, the single source of truth; the dead
EditorState.LineIndex field and SetLineIndex are removed.

On-device (emulator/SwiftShader), a 10 MB file now uses ~150 MB PSS / ~230 MB
RSS at steady state and stays flat under scroll (was 1.13 GB PSS / 1.5 GB RSS,
growing, OOM-killed). go test -race ./... green.
2026-08-16 12:16:52 -04:00
3460ef3993 editor: fix viewport-on-open, chunked-buffer drift, add size guard (Phase 3 code)
- Viewport: swallow the opening tap/scroll (justOpenedAt window) and
  re-clamp ScrollOffset to [0,MaxScroll] in EditorLayout so a short file
  never opens past its content (blank viewport).
- Chunked buffer: replace the fixed i*chunkSize slot model (which drifted
  after length-changing edits and could re-read stale disk for shifted
  tail chunks) with an ordered chunk slice + prefix-sum byte offsets.
  In-range files now load fully on open (SetContent), so there is no lazy
  load and no stale-disk re-read. Edits splice only the affected chunk(s).
- Size guard: files > MaxEditableFileSize (50 MB) show a 'too large to
  edit' notice instead of loading; the browser still lists them. Edit
  handlers (KeyDown/ReplaceRange) are no-ops for too-large files.
- Tests: rewrite chunked_buffer_test.go for prefix-sum correctness (insert/
  delete across chunk boundaries, rune->byte after edit); fix the large-file
  e2e expectation to the shift-correct ground truth.
2026-08-16 10:35:10 -04:00
c9c0d47f2a editor: route file taps to in-app editor; add gated IME commit logging
- ui.OpenFile now calls the in-app editor OpenFile(path) instead of the
  external openfunc (Android Termux bridge). The old wiring sent every
  browser-row tap through the Termux bridge, which crashed on Android 7+
  with FileUriExposedException (file:// Intent URI) and bypassed the editor.
- OpenFile no longer invokes TheState.open; it is retained as a dormant
  external hook for a future 'open externally' action.
- Add imeDebugLog (off by default) verbose per-commit IME logging in
  HandleReplaceRange, plus currentEditorText helper, to aid on-device
  IME commit/cursor-sync debugging.
2026-08-16 09:44:36 -04:00
72b3c3f9c1 editor+ui: complete Android IME wiring (SelectionCmd, SnippetCmd, InputHintOp)
Finish Phase 1 items 1, 2, 4, on top of the range-handling done in 9b78219:

- TextField.Draw now emits, when focused:
  * key.InputHintOp{HintText}        (item 4: enable text keyboard/autocorrect)
  * key.SnippetCmd (the visible window as the snippet, Range {0,len})
                                              (item 2: swipe/autocorrect source)
  * key.SelectionCmd (caret, window-relative rune index)
                                              (item 1: IME selection sync)
  The snippet is the visible window (not the whole file), so the IME treats
  the window as the document and reports EditEvent.Range window-relative.

- HandleReplaceRange now resolves the window-relative range against
  IMEWindowText and offsets by IMEWindowStartByte to address the buffer
  (string and chunked paths). Falls back to the whole buffer when layout
  has not set the window (tests).

- EditorState gains IMEWindowStartByte / IMEWindowText, set during layout.

- Add runeCount helper (utf8 leading-byte scan) in the ui package.

- Fix a data race in three editor e2e/integration tests: they called
  OpenFile from the test goroutine while the logic goroutine ran layout;
  now wrapped in withState so the state write happens on the owner.

Tests: ime_range_test.go gains windowed-path coverage (string+chunked).
go build, go test, go vet, and go test -race are all green.
2026-08-16 02:43:58 -04:00
9b782190fa editor: honor key.EditEvent.Range in HandleReplaceRange (IME swipe/autocorrect)
The IME commit path (swipe-to-type, autocorrect replacement) sends a
key.EditEvent with a Range that the editor ignored, causing the replaced
region to be duplicated. Add Logic.HandleReplaceRange which deletes the
rune range [start,end) and inserts text, converting rune indices to byte
offsets (string path: utf8.DecodeRuneInString; chunked path: leading-byte
scan). HandleKeyDown now routes key.EditEvent through it.

Also fixes two pre-existing ChunkedBuffer correctness bugs the tests
exposed:
- Delete mis-computed the per-chunk end using a shrinking 'remaining'
  instead of the absolute end, corrupting multi-chunk deletes.
- FullContent derived its chunk bound solely from fileLen, truncating
  in-memory chunks grown past the old bound by an insert.

Adds ime_range_test.go covering insert/replace/delete, unicode, chunked,
and swapped bounds. go test -race ./... green.
2026-08-16 02:18:44 -04:00
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
4052769813 Fix to chunk loading policy. 2026-06-05 18:06:17 -04:00
b224bbe85a Fix backspace handling on Android. 2026-06-05 14:19:01 -04:00
615957196e feat: virtual scrolling with chunked buffer for large files
- Add ChunkedBuffer for 64KB chunked file access with dirty-chunk
  eviction protection
- Add LineIndex for precise byte-offset-to-line-number mapping
- Refactor IO task system with context cancellation, typed priorities,
  and new task types (ReadChunk, BuildLineIndex, StatFile)
- Add ReadFileAt to FileSystem interface (mock + real implementations)
- Integrate virtual scrolling into editor layout
- Add comprehensive tests for chunked buffer eviction, dirty-chunk
  safety, and full edit lifecycle
2026-06-05 09:11:32 -04:00
93a5f879f5 feat: implement atomic writes for filesystem backend
- Add WriteFile method to mock filesystem (non-atomic path, creates files)
- Implement WriteFileAtomic in mock with temp file + rename pattern
  using .tmp/ directory, matching real filesystem semantics
- Update WriteFileTask.Execute() to use WriteFileAtomic
- Update FlushAll() to use WriteFileAtomic
- Fix mock to create files on write (matching os.WriteFile behavior)
- Update tests to match new semantics (create-if-not-exists)
2026-06-04 18:01:51 -04:00
672dcbe6b3 Fix cursor movement bug in scrolled editor and add regression test 2026-06-04 13:53:42 -04:00
ca828569f7 Fix deadlock when opening files and update E2E tests 2026-06-04 13:06:30 -04:00
bef3929f40 fix: update tests and fix SetCursorFromPoint panic
- Rewrite cursor_test.go to use the current GlyphLayout-based API
  instead of the deleted byteOffsetToLineCol function
- Add guard for empty/missing layout slices in SetCursorFromPoint
  to prevent index-out-of-range panic on empty documents
- Add missing Advance field to e2e test GlyphLayout initialization
- Add cmd/pad/pad binary to .gitignore
2026-06-04 07:24:44 -04:00
979656c2d0 Fix cursor Y positioning logic in editor 2026-06-03 22:47:06 -04:00
6939550527 Fix cursor positioning and add utf8 import 2026-06-03 22:37:38 -04:00
0b727ff3b2 Fix cursor positioning for end-of-line clicks 2026-06-03 22:35:24 -04:00
bab239a38a Implement click-to-move cursor in the editor 2026-06-03 22:04:16 -04:00
62d1f827e0 Fix cursor rendering lag and implement Enter key newline insertion 2026-06-03 20:19:35 -04:00
3f53836ade Implement vertical cursor movement and reset cursor on file open 2026-06-03 19:49:27 -04:00
d33e68ab2c fix(editor): fix cursor positioning, scrolling, and clipping
- Fix cursor vertical alignment and ensure it respects ScrollOffset.
- Fix scrolling in editor page by correctly registering scroll interactions in the renderer.
- Ensure cursor is clipped to the editor text area to prevent drawing over status bars.
2026-06-03 19:19:14 -04:00
a5d1c4bee6 Compute glyphLayout after text rendering. Implement correct cursor
movement.
2026-06-03 19:01:05 -04:00
bb55b4ab81 Bug fixes. 2026-06-03 10:38:29 -04:00
0dac354f2a fix(editor): wire key events to cursor movement
- Replace gtx.Event(nil) with key.Filter{Focus: focusedID} and
  key.FocusFilter{Target: focusedID} so Gio correctly routes key
  and edit events to the focused editor text field.

- Convert EditorState.CursorPosition (byte offset) to line/column
  coordinates for accurate cursor rendering.

- Add debug logging in HandleKeyDown and HandleCursorMove.
2026-06-03 07:41:18 -04:00
57a185fa36 Fix bugs in e2e tests and remove list item selection highlight 2026-06-02 13:30:09 -04:00
ec87e95b35 fix: integrate browser-to-editor file loading and remove sample text 2026-06-02 09:33:12 -04:00
2c3702f421 fix(browser): fix directory navigation and entry tapping 2026-06-02 08:14:59 -04:00
6709f52e0f Fix browser page scrolling by initializing EntryHeight and correcting scroll handler type assertion 2026-06-01 10:57:34 -04:00
ce8dd82bb0 Remove duplicate browser state and fix sort mode toggle
- Remove currentBrowserState global and SetBrowserState from browser package
- Browser now receives sortHandler as a parameter instead of using global state
- Fix sort mode toggle: reload pages after clearing cache (was showing 'Loading...')
- Add String() methods to all UI element types for debugging
- Add e2e tests for sort mode functionality (5 tests)

The editor now owns all state; the browser is a pure function receiving
state and handlers through parameters.
2026-05-31 23:20:21 +00:00
3f1c881a06 browser: document lazy loading implementation plan
- Add detailed §15 lazy loading plan with 7 implementation steps
- Update Phase 2 checklist with specific tasks
- Add E2E test harness for browser files
- Refactor browser layout and handler structure
- Add .gitignore for common patterns

WIP: Actual lazy loading not yet implemented.
2026-05-31 18:43:46 +00:00
6a43e3c0db Add e2e test harness for testing editor logic without Gio display
- internal/test/e2e/: FrameCapture, Harness, ElementAssertions, helpers
- internal/editor/logic.go: Add done channel and Done() method for graceful shutdown
- internal/editor/mock_setup.go: Mock filesystem for tests
- internal/browser/: Browser layout and search logic
- internal/io/: Worker pool for async tasks
- Update architecture docs and spec
2026-05-31 08:56:30 -04:00
a1ef84609f Fix search update bug. 2026-05-28 20:46:54 -04:00
831f0dac81 Implement four-way sort cycling for browser listview
- Added four sort modes: Date ↓, Date ↑, Name ↑, Name ↓
- Sorting happens only when sort mode changes, not every frame
- Sorted entries are cached in SortedEntries field
- Search query changes trigger re-filtering and re-sorting
- Scroll offset resets on sort change
2026-05-28 16:42:12 -04:00
8f185341b3 Implement per-pixel smooth scrolling for ListView
- Changed BrowserScrollOffset and ListView.ScrollOffset from int (row index)
  to ui.Dp (pixel offset) for smooth per-pixel scrolling
- Updated HandleBrowserScroll to use raw Dp delta instead of row-based
  scrolling with minimum ±1 row jumps
- Fixed search not triggering new frames by detecting query changes in
  main.go FrameEvent handler
- Fixed click misalignment by indexing RowFilenames with local index i
  instead of rowGlobalIndex
- Fixed bottom clamping using actual BrowserListHeight and corrected
  formula: maxScroll = totalRows * rowHeight - BrowserListHeight
2026-05-28 16:14:11 -04:00
23fdeabad4 Fix scroll clipping: ListView now clips scroll gestures to its region
- Add clippableElement interface for elements that need their own clip region
- ListView implements NeedsClip() so drawElement pushes a clip before Draw
- RegisterScroll no longer has its own clip; relies on element clip context
- Aligns with Gio's clip-based gesture registration model
- Fix scroll events firing outside the visible list area on browser page

Also:
- Update touch.md documentation to reflect clip-based interaction model
- Remove debug logging from ListView.Draw
- Remove TODO-click-fixes.md (all items resolved)
2026-05-28 14:56:12 -04:00
ad3049dd22 Browser page: scroll + click coexist via register order fix
- Added Page state (BrowserPage/EditorPage) and SortMode
- BrowserLayout with 30 static entries, sort toggle, search placeholder
- ListView rows clickable via per-row RegisterClick
- Scroll gesture registered at START of ListView.Draw (before clicks)
  so click gestures registered later are checked first during hit test
- Unique IDs for interactive elements: editor_text, browser_list, search_bar
- ListView.Interactions() filters out Scroll (registered in Draw, not registerInteraction)
- RegisterScroll added to Renderer, called from element Draw methods
- lastLineYChan frame loop fixed: only re-layout if value changed
- Back icon added, editor status bar shows active filename
2026-05-27 12:14:25 -04:00
0246316d8f Add word-wrapped text display with two-finger scroll
- Renderer computes word wrap via single LayoutString pass with
  WrapHeuristically policy; glyphs drawn inline at FlagLineBreak
- Fixed line height (fontSize * 1.2), independent of glyph metrics
- Two-finger trackpad scroll via gesture.Scroll with vertical axis
- Display line feedback: renderer reports last glyph Y after each
  Draw; logic uses it to clamp scroll offset so last line stops
  at bottom of viewport with lineHeight/2 padding
- ScrollRange fix: {Min: -(1<<30), Max: 1<<30} ensures scroll
  delta is consumed (empty range consumes nothing via clampSplit)
- Line counting fix: only FlagLineBreak increments count; buffer
  flushes (32-glyph cap) draw but don't count
2026-05-26 20:26:20 -04:00
1596dab72d Initial InputEvent handling. Clickable "Wrap" text label. 2026-05-26 09:46:35 -04:00
50e9ca5b34 Updates to rendering pipeline and documentation. 2026-05-25 17:40:35 -04:00
22c1d5bed1 fix: correct text layout and rendering pipeline
- Fix render.go: replace broken material.Label with shaper-based text
  rendering (LayoutString + Shape + glyph iteration) per Gio's paintGlyph
- Fix render.go: only apply clip rects at container boundaries; leaf
  elements have zero-size regions that were clipping all text out
- Fix render.go: call r.drawElement recursively for container children
  so nested containers work and clips are applied correctly
- Fix render.go: add drawLineText/drawLine helpers matching Gio's
  paintGlyph offset calculation (x + first.X, y + first.Y)
- Fix element.go: Button.Draw now uses r.drawText instead of duplicate
  broken material.Label code; Label defaults to black when color is unset
- Fix state.go: container children use relative coordinates instead of
  mixed screen-space/container-relative positions
- Fix main.go: ConfigEvent carries raw pixel dimensions only; ScaleEvent
  carries scale only; layout is computed once per frame using both,
  eliminating the infinite Invalidate() loop
2026-05-18 21:03:22 -04:00
8739bae517 refactor: unified Element interface with Draw method and Container type
- Add Draw(gtx, r *Renderer) to Element interface
- Add Container type that implements Element and holds []Element children
- Leaf types (Label, Icon, Button) implement Draw themselves
- Single recursive drawElement function in Renderer — no switch/case
- Remove StatusBar/BottomBar — replaced by Container
- Remove shaper.go — no longer needed with Gio material labels
- Simplify main.go — scale via ScaleProvider interface
- 5 files changed, shaper.go deleted
2026-05-18 11:25:43 -04:00
93a7a0a0b2 fix: remove duplicate scale storage, read from State via ScaleProvider interface
- Remove Renderer.scale field and SetScale method
- Add ScaleProvider interface to avoid editor/ui import cycle
- Add Scale() method to State, rename Scale field to scale
- Renderer reads scale through r.scale.Scale()
- Fix icons.go blank import preventing embed.FS usage
- Update main.go to pass State as ScaleProvider to Renderer
2026-05-17 10:59:29 -04:00
5da60ac9b3 Fix Y positioning. Expand status bar height. 2026-05-10 20:12:35 -04:00
fe3e7c3d75 Revert to last working state (68437c3) before PNG icon changes 2026-05-10 06:45:47 -04:00