Commit Graph

71 Commits

Author SHA1 Message Date
e11d705196 chore: remove per-event debug logging from the normal path
Drop the log.Printf/fmt.Printf traces that fired on every config/input/
click/scroll/open/worker-result during IME and tap debugging. These were
noise (and some were commented out). Error, limit, and recovery logs are
kept; the default-off profiler and gated IME debug remain untouched.
2026-08-16 20:58:49 -04:00
46383c1fc1 ui: dedup IME snippet/selection to fix rapid-commit desync
TextField.Draw re-pushed key.SnippetCmd and key.SelectionCmd every frame
while focused. Re-pushing an unchanged snippet resets the IME's composition
and caret, so commits arriving faster than a frame interleaved with those
resets and desynced the cursor (garbled/duplicated text).

Mirror widget.Editor's updateSnippet/selection gating: track the last-pushed
snippet and caret in the main-owned Renderer (keyed by field ID, reset on
focus (re)gain), and only emit the ops when they actually change. A fresh
push is forced whenever the field (re)gains focus so the IME always starts
from a known state. Also drop a leftover per-frame 'Focused:' debug print.

On-device: rapid back-to-back commits (0.08-0.1s cadence, faster than a
frame) now land cleanly - type HELLO, append+backspace, and a
browser->editor navigation round-trip all produce exact content.
go test -race ./... green.
2026-08-16 13:00:25 -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
58725a5e6c Make state single-owner and stabilize race detector
Frame now carries view state (scale/focus/query) to the main goroutine,
which reads only the frame-receiver-stored snapshot. Renderer owns Gio
widget editors (registered by ID) and the draw scale. Autosave timer
sends a token to the logic goroutine instead of touching state;
Shutdown waits for the owner to exit before FlushAll.

Tests access state only through owner-side Inspect/WithState helpers
(harness + in-package). Fixed double-Harness.Run race in two e2e tests,
made TestWorkerPool_PriorityPreemption deterministic, and raised the
lazy-loading test timeout that was too short under -race.

go test -race ./... is now green.
2026-08-16 01:32:27 -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
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
d090447f2d Show soft keyboard on editor focus 2026-06-03 22:31:49 -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
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
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
2c3702f421 fix(browser): fix directory navigation and entry tapping 2026-06-02 08:14:59 -04:00
dc80c8a8e8 Remove scroll debug statements. Add one extra element to browser listview
so that we have a partial item displayed at the bottom and not a blank space.
2026-06-01 11:58:57 -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
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
fa43bf2402 Add ShapeText/DrawShapedText: single shaper call, zero-call drawing
- New shaper.go: ShapeText() returns ShapedText with measured width +
  stored PathSpec + BitmapsCall, DrawShapedText() draws with zero shaper calls
- drawStatusBar: ShapeText for filename + right icons, positions from widths
- drawBottomBar: ShapeText for cursor/bytepos/wrap, positions by alignment
- No separate measureText helper: ShapeText.Width provides measurements
- StatusBar uses IconLeft/IconRow string slices for icon configuration
- BottomBar uses BarAlignment enum for item positioning
2026-05-12 12:06:28 -04:00
5da60ac9b3 Fix Y positioning. Expand status bar height. 2026-05-10 20:12:35 -04:00
dc4962a6c4 Fix icon Y positioning: offset by icon height to align bottom with baseline
- drawPng uses top-left corner, drawText uses baseline
- Icon bottom (Y=42) now aligns with baseline at Y=42
- Icon top is at Y=26 (42-16), occupies Y=26 to Y=42
2026-05-10 07:32:15 -04:00
1416e2db26 Move icon Y position from +20 to +32 DP below filename
- Icon was overlapping the first row of status bar
- Increased vertical spacing to avoid overlap
2026-05-10 07:14:20 -04:00
fdd1e841ae Use original iconY position (30 DP) without offset
- User confirmed icon showed up at this position
- Icon renders in second row of status bar as intended
2026-05-10 07:12:49 -04:00
28c7253622 Fix icon path and Y positioning
- Read icons/cut.png (not cut.png) to match embed directive
- Offset icon Y by iconSize so bottom aligns with text baseline
- drawText uses baseline positioning, drawPng uses top-left corner
2026-05-10 07:10:02 -04:00
f259145084 Fix icon Y positioning: offset by icon height to align bottom with baseline
- drawText positions glyphs by baseline, drawPng uses top-left corner
- Subtract iconSize from iconY so icon bottom aligns with text baseline
- Icon now renders within status bar instead of overlapping top
2026-05-10 07:03:43 -04:00
0ac2941802 Replace unicode cut icon with PNG icon in StatusBar
- Load cut.png from embedded filesystem
- drawStatusBar uses drawPng() for cut icon (16dp)
- No other files modified
2026-05-10 06:52:54 -04:00
106e91832a Add drawPng function to Renderer for PNG icon rendering
- Takes image.Image, position (x,y) in Dp, size (width,height) in Dp
- Tint color with alpha < 255 applies color overlay using clip.Rect
- Converts Dp to pixels using Renderer.toPx()
- Uses paint.NewImageOp for image rendering
2026-05-10 06:50:03 -04:00
fe3e7c3d75 Revert to last working state (68437c3) before PNG icon changes 2026-05-10 06:45:47 -04:00
1b1cf51528 Remove search/conflict icon references — only cut/copy/paste exist
- StatusBar now only draws cut, copy, paste PNG icons
- Removed code that tried to draw non-existent search.png and conflict.png
2026-05-10 06:39:22 -04:00
d2509123b0 Replace Unicode icons with PNG icons in StatusBar
- Move icons/ to internal/ui/icons/ for go:embed
- Renderer.loadIcons() loads PNGs from embedded filesystem
- drawStatusBar() uses drawPng() for cut/copy/paste/search/conflict icons
- Icons are 16dp, rendered from 48x48 PNGs (2x density)
2026-05-10 06:29:19 -04:00
14284afa73 Add drawPng function to Renderer for PNG icon rendering
- Takes image.Image, position (x,y) in Dp, size (width,height) in Dp
- Tint color with alpha < 255 applies color overlay using clip.Rect
- Converts Dp to pixels using Renderer.toPx()
- Uses paint.NewImageOp for image rendering
2026-05-10 06:26:29 -04:00
620d534454 Break import cycle: use StateReader interface instead of direct State reference
- StateReader interface in ui package (Scale(), ScreenWidth(), ScreenHeight())
- State implements StateReader
- Renderer reads scale/window size via interface, never imports editor
- State fields are private (screenWidth, screenHeight, scale) with accessor methods
- No SetScale/SetWindowSize calls from main.go — Renderer reads from State directly
2026-05-09 20:33:00 -04:00
21b4df59a3 Fix event flow: ConfigEvent sets size, FrameEvent sets scale only
- ConfigEvent: ONLY from app.ConfigEvent, sets width/height in State
- FrameEvent: gets scale from gtx.Metric.PxPerDp, sends ScaleEvent only if changed
- ScaleEvent: updates State.Scale
- Renderer reads windowW/windowH from State (SetWindowSize), not gtx.Constraints
- Renderer reads scale from State (SetScale), not gtx.Metric.PxPerDp
- main.go: ConfigEvent calls SetWindowSize, FrameEvent calls SetScale
2026-05-09 20:26:10 -04:00
68437c32a7 Store scale in State, use it everywhere instead of gtx.Metric.PxPerDp
- State.Scale stores the scale factor (default 1.0)
- ScaleEvent applies SetScale() which updates State.Scale
- Logic.Scale() accessor returns current scale
- Renderer.SetScale() updates renderer's scale from State
- main.go: uses logic.Scale() to get scale, calls renderer.SetScale()
- No more references to gtx.Metric.PxPerDp in render.go or main.go
2026-05-09 20:23:02 -04:00
b581abb691 Redesign ConfigEvent/Scale handling: ConfigEvent sets size, FrameEvent updates scale
- Logic layer has separate ConfigChan and ScaleChan
- ConfigEvent: width/height in Dp, set on ConfigEvent
- ScaleEvent: scale factor, set on FrameEvent when scale changes
- Logic has ScreenSize() accessor for layout/rendering pipeline
- Logic has SetScale() to update scale and recompute layout
- main.go: ConfigEvent stores pixels, converts to DP using current scale
- main.go: FrameEvent gets actual scale, sends ScaleEvent if changed
2026-05-09 19:59:27 -04:00
f433a992b7 Clean up debug output and fix clip push/pop pattern 2026-05-09 19:16:10 -04:00
4a9253616d Fix StatusBar background using push/pop instead of op.Record macro
The op.Record macro was leaving a clip on the stack that prevented
subsequent elements from drawing. Use clip.Rect{}.Push()/clip.Pop()
instead for immediate push/pop.
2026-05-09 19:13:33 -04:00
4893d69f4e Add light gray background to StatusBar and BottomBar
Rename shaper_usage.md to layout_rendering.md
2026-05-09 18:49:43 -04:00
b8c6878f46 Introduce distinct Dp/Px types to prevent coordinate mixing at compile time
- Add ui.Dp and ui.Px as distinct named types
- Logic layer works exclusively in Dp (positions, sizes, regions)
- Renderer converts Dp→Px at Gio interop boundaries
- Capture gtx.Constraints once at start of Draw() for consistent positioning
- main.go converts app.ConfigEvent pixels to Dp before passing to logic layer
- Update documentation with new coordinate system architecture
2026-05-09 18:40:47 -04:00
4cf8641bbd Fix BottomBar X positioning to use visible window width 2026-05-09 18:01:24 -04:00
e096341533 Draw BottomBar at visible window bottom instead of region Y which may be outside viewport 2026-05-09 17:19:14 -04:00
9476f5448a Update BottomBar to use drawText instead of material.Label
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-05-09 15:53:14 -04:00