- 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.
- 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.
- 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
- 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
- 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)
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- ConfigUpdate interface with apply(*State) method
- ConfigEvent and ScaleEvent both implement ConfigUpdate
- Logic.ConfigChan() accepts both types
- Type switch in Run() differentiates ConfigEvent vs ScaleEvent
- main.go sends both on same ConfigChan()
- 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