Documentation updates.
This commit is contained in:
parent
bef3929f40
commit
d06f8af0f7
|
|
@ -1,9 +1,2 @@
|
||||||
This file lists descriptions of outstanding bugs. Bugs are separated by empty lines.
|
This file lists descriptions of outstanding bugs. Bugs are separated by empty lines.
|
||||||
|
|
||||||
If I move the cursor (using the arrow keys) to the right side of the screen in a word-wrapped
|
|
||||||
line, the cursor does not move to the next line right away, instead it moves through whitespace on
|
|
||||||
the right side of the wrapped line, sometimes moving off screen. Also in a word wrapped file, moving
|
|
||||||
the cursor to the right does not move all the way down to the end of the file, it stops at some
|
|
||||||
point before the end. Also when I change the screen width to change the word wrap, the cursor does
|
|
||||||
not stay at the same position in the file but instead tries to stay in the same screen position,
|
|
||||||
not following the word wrap as it adjusts the new screen config.
|
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,11 @@ type EditorState struct {
|
||||||
### Phase 1: Basic Buffer Management & Cursor
|
### Phase 1: Basic Buffer Management & Cursor
|
||||||
- [x] Define `EditorState` (Cursor, Selection, Dirty flag). — **DONE**
|
- [x] Define `EditorState` (Cursor, Selection, Dirty flag). — **DONE**
|
||||||
- [x] Implement `GlyphLayout` capture in `drawWrappedText` and feedback via `layoutChan`. — **DONE**
|
- [x] Implement `GlyphLayout` capture in `drawWrappedText` and feedback via `layoutChan`. — **DONE**
|
||||||
- [x] Implement cursor navigation (Arrow keys: Left, Right, Up, Down). — **DONE**
|
- [x] Implement cursor navigation (Arrow keys: Left, Right, Up, Down). — **DONE** (Left/Right: byte-based ±1; Up/Down: GlyphLayout-based)
|
||||||
- [x] Implement basic buffer updates (Insert character, Delete/Backspace). — **DONE**
|
- [x] Implement basic buffer updates (Insert character, Delete/Backspace). — **DONE**
|
||||||
- [x] Implement cursor display. — **DONE** (Moved to `Renderer` in Phase 4 for latency-free rendering)
|
- [x] Implement cursor display. — **DONE** (Rendered inline in `Renderer.drawWrappedText` using `GlyphLayout`)
|
||||||
- [ ] Implement cursor blink animation. — **NOT STARTED**
|
- [ ] Implement cursor blink animation. — **NOT STARTED** (`CursorVisible` field exists but no timer drives it)
|
||||||
- [x] Update `EditorLayout` to position cursor from GlyphLayout (no fixed-width approximation). — **DONE** (Rendered directly in `Renderer` based on `GlyphLayout`)
|
- [x] Tap-to-position cursor from GlyphLayout. — **DONE** (`SetCursorFromPoint` groups glyphs by Y, finds closest X)
|
||||||
|
|
||||||
### Phase 2: File IO Integration
|
### Phase 2: File IO Integration
|
||||||
- [ ] Implement `SaveFile` handler: Dispatch `WriteFileTask` to worker pool. — **NOT STARTED**
|
- [ ] Implement `SaveFile` handler: Dispatch `WriteFileTask` to worker pool. — **NOT STARTED**
|
||||||
|
|
@ -126,87 +126,62 @@ Storing all X advances is not expensive — a single page of text is a tiny amou
|
||||||
|
|
||||||
### 8.4 Capture Point
|
### 8.4 Capture Point
|
||||||
|
|
||||||
**Current state (as of June 2026):** `drawWrappedText` iterates every glyph via `r.shp.NextGlyph()` but does **not** capture per-glyph layout data. It tracks only `lastLineY` (the baseline Y of the last drawn glyph) and `displayLineCount`. These are exposed via `Renderer.LastLineY()` and `Renderer.DisplayLineCount()`.
|
**Implemented.** `drawWrappedText` in `render.go` captures full per-glyph layout data during the shaping loop:
|
||||||
|
|
||||||
**Planned change:** `drawWrappedText` will be extended to capture layout data during the glyph iteration loop and return a `GlyphLayout`:
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (r *Renderer) drawWrappedText(...) GlyphLayout {
|
func (r *Renderer) drawWrappedText(gtx layout.Context, str string, reg Region, wrapWidth Dp, scrollOffset Dp, cursorPos int) {
|
||||||
// ... existing shaping setup ...
|
// ... shaping setup with WrapHeuristically ...
|
||||||
r.shp.LayoutString(params, str)
|
r.shp.LayoutString(params, str)
|
||||||
|
|
||||||
var layout GlyphLayout
|
var layout GlyphLayout
|
||||||
byteOffset := 0
|
byteOffset := 0
|
||||||
for g, ok := r.shp.NextGlyph(); ok; g, ok = r.shp.NextGlyph() {
|
for g, ok := r.shp.NextGlyph(); ok; g, ok = r.shp.NextGlyph() {
|
||||||
glyphX := Dp(float32(g.Xdot >> 6)) - scrollOffset
|
|
||||||
glyphY := Dp(float32(g.Ydot))
|
|
||||||
|
|
||||||
layout.ByteOffsets = append(layout.ByteOffsets, byteOffset)
|
layout.ByteOffsets = append(layout.ByteOffsets, byteOffset)
|
||||||
layout.X = append(layout.X, glyphX)
|
layout.X = append(layout.X, Dp(float32(g.X>>6)/r.scale.Scale()))
|
||||||
layout.Y = append(layout.Y, glyphY)
|
layout.Y = append(layout.Y, Dp(float32(g.Y)/r.scale.Scale()))
|
||||||
layout.Advance = append(layout.Advance, Dp(float32(g.Advance >> 6)))
|
layout.Advance = append(layout.Advance, Dp(float32(g.Advance>>6)/r.scale.Scale()))
|
||||||
|
|
||||||
byteOffset += utf8.RuneLen(runeAtThisGlyph)
|
for i := uint16(0); i < g.Runes; i++ {
|
||||||
// ... existing draw logic ...
|
_, sz := utf8.DecodeRuneInString(str[byteOffset:])
|
||||||
|
byteOffset += sz
|
||||||
|
}
|
||||||
|
// ... draw logic ...
|
||||||
|
}
|
||||||
|
// Store captured layout; derive lastLineY from it.
|
||||||
|
r.glyphLayout = layout
|
||||||
|
if len(layout.Y) > 0 {
|
||||||
|
r.lastLineY = layout.Y[len(layout.Y)-1]
|
||||||
}
|
}
|
||||||
r.lastLineY = layout.Y[len(layout.Y)-1] // derived, not separate
|
|
||||||
return layout
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`TextField.Draw` calls `drawWrappedText`, which captures the layout as a side effect and stores it on the renderer (`r.glyphLayout`). No code change is needed in `element.go` — the storage happens entirely inside `drawWrappedText` in `render.go`.
|
The layout is stored on the renderer as `r.glyphLayout`. `lastLineY` is derived from `layout.Y[len-1]`. Exposed via `Renderer.GlyphLayout()`.
|
||||||
|
|
||||||
### 8.5 Feedback Path
|
### 8.5 Feedback Path
|
||||||
|
|
||||||
**Current state (as of June 2026):** The main loop sends `lastLineY` (as `int`) on `DisplayLineChan()` after each frame. The logic goroutine stores it in `State.LastLineY`. This is the **only** layout feedback path currently in use.
|
**Implemented.** A `layoutChan` carries the full `GlyphLayout` from renderer to logic. `LastLineY` is derived from `GlyphLayout.Y[len-1]`.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Current: main.go
|
// main.go — after each FrameEvent
|
||||||
lastLineY := int(renderer.LastLineY())
|
glyphLayout := renderer.GlyphLayout()
|
||||||
logic.DisplayLineChan() <- lastLineY
|
logic.LayoutChan() <- glyphLayout
|
||||||
|
|
||||||
// Current: logic.go
|
// logic.go
|
||||||
case y := <-l.lastLineYChan:
|
case layout := <-l.layoutChan:
|
||||||
if ui.Dp(y) != l.state.LastLineY {
|
l.state.Editor.GlyphLayout = layout
|
||||||
l.state.LastLineY = ui.Dp(y)
|
var derivedLastLineY ui.Dp
|
||||||
|
if len(layout.Y) > 0 {
|
||||||
|
derivedLastLineY = layout.Y[len(layout.Y)-1]
|
||||||
|
}
|
||||||
|
if derivedLastLineY != l.state.LastLineY {
|
||||||
|
l.state.LastLineY = derivedLastLineY
|
||||||
l.frameChan <- l.state.layout(l.browserManager)
|
l.frameChan <- l.state.layout(l.browserManager)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Planned change:** A new `layoutChan` will carry the full `GlyphLayout` from renderer to logic, replacing `lastLineYChan`. `LastLineY` will be derived from `GlyphLayout.Y[len-1]`.
|
|
||||||
|
|
||||||
```go
|
|
||||||
layoutChan chan GlyphLayout // in Logic struct (replaces lastLineYChan)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 8.6 Editor State Consumption
|
### 8.6 Editor State Consumption
|
||||||
|
|
||||||
**Current state (as of June 2026):** `EditorState` does **not** yet contain a `GlyphLayout` field. Cursor positioning uses a fixed-width approximation:
|
**Implemented.** `EditorState` contains a `GlyphLayout` field. All cursor operations are GlyphLayout-based.
|
||||||
|
|
||||||
```go
|
|
||||||
// Current: state.go — EditorLayout()
|
|
||||||
line, col := byteOffsetToLineCol(TheState.Editor.Buffer, TheState.Editor.CursorPosition)
|
|
||||||
charWidth := ui.Dp(float32(EditorFontSize) * 0.6)
|
|
||||||
lineHeight := EditorLineHeight()
|
|
||||||
cursorX := editorRegion.X + ui.Dp(col)*charWidth
|
|
||||||
cursorY := editorRegion.Y + ui.Dp(line)*lineHeight - TheState.ScrollOffset
|
|
||||||
```
|
|
||||||
|
|
||||||
Cursor movement (`HandleCursorMove`) is byte-based (±1 per arrow press), not glyph-based:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Current: state.go
|
|
||||||
func HandleCursorMove(delta int) {
|
|
||||||
newPos := TheState.Editor.CursorPosition + delta
|
|
||||||
if newPos < 0 { newPos = 0 }
|
|
||||||
if newPos > len(TheState.Editor.Buffer) { newPos = len(TheState.Editor.Buffer) }
|
|
||||||
TheState.Editor.CursorPosition = newPos
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This works for monospace-like navigation but is inaccurate for proportional fonts and does not respect visual (wrapped) line boundaries.
|
|
||||||
|
|
||||||
**Planned change:** `EditorState` gains the layout field and cursor logic switches to GlyphLayout-based positioning:
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type EditorState struct {
|
type EditorState struct {
|
||||||
|
|
@ -217,50 +192,13 @@ type EditorState struct {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Cursor rendering** in `EditorLayout` (planned):
|
**Cursor rendering** — handled inline in `drawWrappedText` (`render.go`). After shaping, the renderer binary-searches `ByteOffsets` for `cursorPos` and draws a 2dp vertical bar at the computed position. No separate cursor element is needed in the element tree.
|
||||||
```go
|
|
||||||
func EditorLayout(...) []ui.Element {
|
|
||||||
layout := TheState.Editor.GlyphLayout
|
|
||||||
pos := TheState.Editor.CursorPosition
|
|
||||||
|
|
||||||
idx := sort.Search(len(layout.ByteOffsets), func(i int) bool {
|
**Cursor movement** (`HandleCursorMove`, `state.go`) — byte-based ±1 with bounds clamping. For proportional fonts this is a reasonable approximation; a glyph-index-based version could be added later.
|
||||||
return layout.ByteOffsets[i] >= pos
|
|
||||||
})
|
|
||||||
|
|
||||||
var cursorX, cursorY Dp
|
**Vertical cursor movement** (`HandleVerticalCursorMove`, `state.go`) — fully GlyphLayout-based. Finds the current glyph's Y via binary search, scans for the target line's Y, then picks the glyph with the closest X on that line.
|
||||||
if idx < len(layout.ByteOffsets) {
|
|
||||||
cursorX = editorRegion.X + layout.X[idx]
|
|
||||||
cursorY = editorRegion.Y + layout.Y[idx]
|
|
||||||
} else {
|
|
||||||
// cursor at end of buffer — place after last glyph
|
|
||||||
cursorX = editorRegion.X + layout.X[len(layout.X)-1] + layout.Advance[len(layout.Advance)-1]
|
|
||||||
cursorY = editorRegion.Y + layout.Y[len(layout.Y)-1]
|
|
||||||
}
|
|
||||||
// ... create cursor element ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Cursor movement** (`HandleCursorMove`, planned):
|
**Tap-to-position** (`SetCursorFromPoint`, `state.go`) — GlyphLayout-based. Groups glyphs by Y-baseline, identifies the target visual line from the tap Y, then finds the closest glyph by X distance on that line. Handles edge case of tapping past the rightmost glyph.
|
||||||
```go
|
|
||||||
func HandleCursorMove(delta int) {
|
|
||||||
layout := TheState.Editor.GlyphLayout
|
|
||||||
pos := TheState.Editor.CursorPosition
|
|
||||||
|
|
||||||
idx := findGlyphIndex(layout, pos) // binary search ByteOffsets
|
|
||||||
|
|
||||||
if delta > 0 {
|
|
||||||
idx++ // next glyph
|
|
||||||
} else {
|
|
||||||
idx-- // previous glyph
|
|
||||||
}
|
|
||||||
|
|
||||||
if idx >= 0 && idx < len(layout.ByteOffsets) {
|
|
||||||
TheState.Editor.CursorPosition = layout.ByteOffsets[idx]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Arrow-up / arrow-down** (future): find current glyph's Y, search for the glyph with the closest X on the target Y line.
|
|
||||||
|
|
||||||
### 8.7 Correctness Guarantees
|
### 8.7 Correctness Guarantees
|
||||||
|
|
||||||
|
|
@ -276,14 +214,14 @@ func HandleCursorMove(delta int) {
|
||||||
|
|
||||||
### 8.8 Summary of Changes
|
### 8.8 Summary of Changes
|
||||||
|
|
||||||
| File | Current State (June 2026) | Planned Change |
|
| File | Status | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `internal/ui/unit.go` | No `GlyphLayout` type defined | Add `GlyphLayout` struct |
|
| `internal/ui/unit.go` | **Done** | `GlyphLayout` struct defined with `ByteOffsets`, `X`, `Y`, `Advance` |
|
||||||
| `internal/ui/render.go` | `drawWrappedText` tracks `lastLineY` and `displayLineCount` only | Return `GlyphLayout`; derive `lastLineY` from it; render cursor inline |
|
| `internal/ui/render.go` | **Done** | `drawWrappedText` captures full `GlyphLayout`; derives `lastLineY` from it; renders cursor inline |
|
||||||
| `internal/ui/element.go` | `TextField.Draw` calls `drawWrappedText` | Pass `CursorPosition` to `drawWrappedText` |
|
| `internal/ui/element.go` | **Done** | `TextField.Draw` passes `CursorPosition` to `drawWrappedText` |
|
||||||
| `internal/editor/logic.go` | Uses `lastLineYChan` for feedback | Removed `lastLineYChan` |
|
| `internal/editor/logic.go` | **Done** | Uses `layoutChan` (full `GlyphLayout`) instead of `lastLineYChan` |
|
||||||
| `internal/editor/state.go` | `EditorLayout` computed cursor element separately | Removed separate cursor element; `TextField` now handles cursor rendering via `Renderer` |
|
| `internal/editor/state.go` | **Done** | `GlyphLayout` stored on `EditorState`; cursor rendering handled by `Renderer` |
|
||||||
| `cmd/pad/main.go` | Sends `int(renderer.LastLineY())` on `DisplayLineChan()` after each frame | Removed `lastLineYChan` usage |
|
| `cmd/pad/main.go` | **Done** | Sends `renderer.GlyphLayout()` on `LayoutChan()` after each frame |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -295,15 +233,18 @@ This section documents what has been implemented beyond the original plan, and w
|
||||||
|
|
||||||
| Component | Status | Notes |
|
| Component | Status | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `EditorState` struct | **Done** | `Buffer`, `CursorPosition`, `SelectionStart/End` (unused), `Dirty`, `CursorVisible` |
|
| `EditorState` struct | **Done** | `Buffer`, `CursorPosition`, `GlyphLayout`, `SelectionStart/End` (unused), `Dirty`, `CursorVisible` |
|
||||||
|
| `GlyphLayout` capture | **Done** | Full per-glyph capture in `drawWrappedText` (ByteOffsets, X, Y, Advance) |
|
||||||
|
| `layoutChan` feedback | **Done** | Main loop sends `GlyphLayout` after each frame; logic derives `LastLineY` from it |
|
||||||
| `HandleInsert` | **Done** | String concatenation at cursor position, advances cursor, sets `Dirty` |
|
| `HandleInsert` | **Done** | String concatenation at cursor position, advances cursor, sets `Dirty` |
|
||||||
| `HandleBackspace` | **Done** | Removes byte before cursor, decrements cursor, sets `Dirty` |
|
| `HandleBackspace` | **Done** | Removes byte before cursor, decrements cursor, sets `Dirty` |
|
||||||
| `HandleDelete` | **Done** | Removes byte after cursor, sets `Dirty` |
|
| `HandleDelete` | **Done** | Removes byte after cursor, sets `Dirty` |
|
||||||
| `HandleCursorMove` | **Done (basic)** | Byte-based ±1 movement, bounded by buffer length |
|
| `HandleCursorMove` | **Done** | Byte-based ±1 movement, bounded by buffer length |
|
||||||
| `HandleKeyDown` | **Done** | Dispatches `key.Name` (arrows, delete) and `key.EditEvent` (text input) |
|
| `HandleVerticalCursorMove` | **Done** | GlyphLayout-based: binary search for current Y, scan for target Y, find closest X |
|
||||||
| `EditorLayout` | **Done** | Full layout: status bar, text field, cursor element, bottom bar |
|
| `SetCursorFromPoint` | **Done** | GlyphLayout-based tap-to-position: groups glyphs by Y, finds closest X on target line |
|
||||||
| `Cursor` element | **Done** | Renders as 2dp × 18dp vertical bar at computed position |
|
| `HandleKeyDown` | **Done** | Dispatches `key.Name` (arrows, delete, return) and `key.EditEvent` (text input) |
|
||||||
| `byteOffsetToLineCol` | **Done** | Converts byte offset to (line, col) for cursor positioning |
|
| `EditorLayout` | **Done** | Full layout: status bar, text field, bottom bar |
|
||||||
|
| Cursor rendering | **Done** | Inline in `Renderer.drawWrappedText` — 2dp vertical bar positioned from `GlyphLayout` |
|
||||||
| File open flow | **Done** | `OpenFile` → `openFileChan` → `ReadFileTask` → worker pool → `handleWorkerResult` → `Editor.Buffer` |
|
| File open flow | **Done** | `OpenFile` → `openFileChan` → `ReadFileTask` → worker pool → `handleWorkerResult` → `Editor.Buffer` |
|
||||||
| Soft-wrap toggle | **Done** | `ToggleWordWrap` wired to bottom bar label |
|
| Soft-wrap toggle | **Done** | `ToggleWordWrap` wired to bottom bar label |
|
||||||
| Page navigation | **Done** | Browser ↔ Editor page switching via `GoToBrowser`/`GoToEditor` |
|
| Page navigation | **Done** | Browser ↔ Editor page switching via `GoToBrowser`/`GoToEditor` |
|
||||||
|
|
@ -318,16 +259,17 @@ This section documents what has been implemented beyond the original plan, and w
|
||||||
|---|---|
|
|---|---|
|
||||||
| `buffer_test.go` | `TestInsertChar` — verifies insert at end of buffer |
|
| `buffer_test.go` | `TestInsertChar` — verifies insert at end of buffer |
|
||||||
| `buffer_test.go` | `TestBackspace` — verifies backspace at end of buffer |
|
| `buffer_test.go` | `TestBackspace` — verifies backspace at end of buffer |
|
||||||
| `cursor_test.go` | `TestByteOffsetToLineCol_Logical` — verifies logical line/column conversion |
|
| `cursor_test.go` | `TestCursorPositioning_Basic` — verifies GlyphLayout-based tap-to-position |
|
||||||
|
| `cursor_test.go` | `TestCursorPositioning_EmptyDocument` — verifies empty document handling |
|
||||||
|
| `cursor_test.go` | `TestCursorPositioning_IncompleteLayout` — verifies graceful handling of partial layout |
|
||||||
|
| `cursor_test.go` | `TestHandleCursorMove_Bounds` — verifies cursor clamping at buffer start/end |
|
||||||
| `integration_test.go` | `TestOpenFileIntegration` — verifies full file open flow through worker pool |
|
| `integration_test.go` | `TestOpenFileIntegration` — verifies full file open flow through worker pool |
|
||||||
|
|
||||||
### 9.3 Known Gaps
|
### 9.3 Known Gaps
|
||||||
|
|
||||||
| Gap | Description |
|
| Gap | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| GlyphLayout capture | Not yet implemented — cursor uses fixed-width approximation |
|
|
||||||
| Cursor blink | `CursorVisible` field exists but no blink timer is driven |
|
| Cursor blink | `CursorVisible` field exists but no blink timer is driven |
|
||||||
| Up/Down arrow | `HandleKeyDown` does not handle `NameUpArrow`/`NameDownArrow` |
|
|
||||||
| Home/End/PageUp/PageDown | No handlers for these keys |
|
| Home/End/PageUp/PageDown | No handlers for these keys |
|
||||||
| Cut/Copy/Paste | Icon elements exist with `nil` handlers |
|
| Cut/Copy/Paste | Icon elements exist with `nil` handlers |
|
||||||
| Text selection | `SelectionStart`/`SelectionEnd` fields present but unused |
|
| Text selection | `SelectionStart`/`SelectionEnd` fields present but unused |
|
||||||
|
|
|
||||||
27
doc/touch.md
27
doc/touch.md
|
|
@ -179,18 +179,23 @@ The `TextField` element must be "focused" to receive keyboard events. Focus is a
|
||||||
|
|
||||||
### 5.3 Key Events
|
### 5.3 Key Events
|
||||||
|
|
||||||
| Event | Type | Name | Action |
|
| Event | Type | Gio Constant | Action |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Text insertion | `Edit` | — | Insert `event.Text` at cursor, append to undo chain |
|
| Text insertion | `key.EditEvent` | — | Insert `event.Text` at cursor, append to undo chain |
|
||||||
| Backspace | `Press` | `Backspace` | Delete character before cursor, append to delete chain |
|
| Backspace | `key.Event` (Press) | `key.NameDeleteBackward` | Delete character before cursor, append to delete chain |
|
||||||
| Enter | `Press` | `Enter` | Insert newline at cursor |
|
| Delete | `key.Event` (Press) | `key.NameDeleteForward` | Delete character after cursor |
|
||||||
| Tab | `Press` | `Tab` | Insert spaces (configurable, e.g., 4 spaces) |
|
| Enter | `key.Event` (Press) | `key.NameReturn` | Insert newline at cursor |
|
||||||
| Arrow Up/Down | `Press` | `Up`/`Down` | Move cursor up/down one line |
|
| Tab | `Press` | `key.NameTab` | Insert spaces (configurable, e.g., 4 spaces) |
|
||||||
| Arrow Left/Right | `Press` | `Left`/`Right` | Move cursor left/right one character |
|
| Arrow Up | `key.Event` (Press) | `key.NameUpArrow` | Move cursor up one visual line (GlyphLayout-based) |
|
||||||
| Ctrl+A | `Press` | `A` (with Ctrl) | Select all text |
|
| Arrow Down | `key.Event` (Press) | `key.NameDownArrow` | Move cursor down one visual line (GlyphLayout-based) |
|
||||||
| Ctrl+Z | `Press` | `Z` (with Ctrl) | Undo |
|
| Arrow Left | `key.Event` (Press) | `key.NameLeftArrow` | Move cursor left one byte |
|
||||||
| Ctrl+Y | `Press` | `Y` (with Ctrl) | Redo |
|
| Arrow Right | `key.Event` (Press) | `key.NameRightArrow` | Move cursor right one byte |
|
||||||
| Ctrl+F | `Press` | `F` (with Ctrl) | Show search bar |
|
| Ctrl+A | `Press` | `A` (with Ctrl) | Select all text (future) |
|
||||||
|
| Ctrl+Z | `Press` | `Z` (with Ctrl) | Undo (future) |
|
||||||
|
| Ctrl+Y | `Press` | `Y` (with Ctrl) | Redo (future) |
|
||||||
|
| Ctrl+F | `Press` | `F` (with Ctrl) | Show search bar (future) |
|
||||||
|
|
||||||
|
**Current implementation** (as of June 2026): `HandleKeyDown` in `state.go` dispatches `key.Name` events via a `switch` on `key.Name` constants. `key.EditEvent` is handled separately for text input. Up/Down arrow navigation uses `HandleVerticalCursorMove` which is GlyphLayout-based. Left/Right uses `HandleCursorMove` which is byte-based ±1.
|
||||||
|
|
||||||
### 5.4 IME and Composition (Pure Gioui)
|
### 5.4 IME and Composition (Pure Gioui)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user