Documentation update.
This commit is contained in:
parent
1596dab72d
commit
ae04aabb70
|
|
@ -396,19 +396,95 @@ Scrollable elements (ListView, TextField multiline) contain only the visible ite
|
|||
|
||||
Scroll events from the renderer are routed back to the logic layer, which updates the offset and re-renders.
|
||||
|
||||
## 5. Input Routing
|
||||
## 5. Interaction Model
|
||||
|
||||
Interactive elements have an `ID` field. The renderer reports input events as:
|
||||
Elements declare their input behaviors at construction time via the `Interactive` interface. Each element can register one or more `Interaction`s, pairing a gesture type with a handler function.
|
||||
|
||||
### 5.1 Core Types
|
||||
|
||||
```go
|
||||
type InputType int
|
||||
|
||||
const (
|
||||
Tap InputType = iota
|
||||
DoubleTap
|
||||
)
|
||||
|
||||
// Interaction pairs a gesture type with a handler function.
|
||||
type Interaction struct {
|
||||
Gesture InputType
|
||||
Handler func(any) // called with raw Gio gesture event data
|
||||
}
|
||||
|
||||
// Interactive is implemented by elements that respond to input events.
|
||||
type Interactive interface {
|
||||
ID() string
|
||||
Interactions() []Interaction
|
||||
}
|
||||
|
||||
// InputEvent carries a handler and its raw event data.
|
||||
type InputEvent struct {
|
||||
ElementID string
|
||||
Type InputType // Tap, DoubleTap, LongPress, Scroll, KeyDown, KeyUp
|
||||
Data any // type-specific payload (key code, scroll delta, etc.)
|
||||
Handler func(any)
|
||||
Data any
|
||||
}
|
||||
```
|
||||
|
||||
The logic layer routes `InputEvent` to the appropriate handler based on `ElementID`.
|
||||
### 5.2 Declaring Interactions
|
||||
|
||||
Elements declare interactions when constructed:
|
||||
|
||||
```go
|
||||
// Wrap label: tap toggles word wrap
|
||||
wrapLabel := NewLabel("Wrap: On", 12, region, AlignEnd, "wrap",
|
||||
[]Interaction{{Gesture: Tap, Handler: ToggleWordWrap}})
|
||||
```
|
||||
|
||||
Every interactive element type (`Label`, `Icon`, `Button`, `TextField`, `ListView`, `AlphaIndex`, `MergeHunk`, `SearchBar`, `Cursor`, `Toast`, `Spacer`) has an `interactions` field and implements `Interactions() []Interaction`.
|
||||
|
||||
### 5.3 Click Area Clipping
|
||||
|
||||
The click area is **not** the element's full region. It is clipped to the actual painted content:
|
||||
|
||||
- **Text elements** (`Label`, `Button`): The click area is the bounding box of the shaped text glyphs. For `AlignEnd` labels (e.g., "Wrap: On"), only the text area at the right edge is clickable, not the full container width.
|
||||
|
||||
- **Icon elements**: The click area is the icon image's bounding box.
|
||||
|
||||
- **Text fields and lists**: The click area covers the element's full region (the entire text area or list viewport).
|
||||
|
||||
This clipping is implemented in `Renderer.drawText()`: after shaping the text, a `clip.Rect` is pushed around the text's bounding box, then `gesture.Click.Add()` is called inside that clip. Only paint ops within the clip contribute to the click area.
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Ln 10, Col 5 1024 / 50000 [Wrap: On] │
|
||||
│ ──── click area ──── ──── click area ──── click area │
|
||||
│ (full width) (full width) (text only) │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.4 Gesture Registration and Event Routing
|
||||
|
||||
Click gesture registration and event routing follow this flow:
|
||||
|
||||
1. **Registration**: In `drawElement`, the renderer checks if the element implements `Interactive`. For each `Interaction`, it stores the `*gesture.Click` and handler in `clicks map[string]clickReg`.
|
||||
2. **Click area**: During `drawText` (or `drawPng` for icons), `click.Add(gtx.Ops)` is called inside the content's clip, associating the gesture with the painted area.
|
||||
3. **Polling**: After `renderer.Draw()`, `renderer.CheckGestures()` polls all registered `gesture.Click` instances via `click.Update(q)`.
|
||||
4. **Routing**: Click events are returned as `InputEvent{Handler, Data}`. The main loop sends them to the logic goroutine via `inputChan`.
|
||||
5. **Execution**: The logic goroutine calls `evt.Handler(evt.Data)`. Handlers are static functions (e.g., `ToggleWordWrap`) that read/write global `TheState` directly, avoiding closures and circular imports.
|
||||
|
||||
### 5.5 Handler Design
|
||||
|
||||
Handlers are static functions defined in the `editor` package:
|
||||
|
||||
```go
|
||||
func ToggleWordWrap(data any) {
|
||||
state := editor.TheState.Load()
|
||||
state.Lock()
|
||||
state.WordWrap = !state.WordWrap
|
||||
state.Unlock()
|
||||
}
|
||||
```
|
||||
|
||||
They receive the raw Gio gesture event (`gesture.ClickEvent`) as `any`. They access global `TheState` directly, avoiding the need for closures or element-ID dispatch tables.
|
||||
|
||||
## 6. Rendering Order
|
||||
|
||||
|
|
|
|||
52
doc/touch.md
52
doc/touch.md
|
|
@ -6,9 +6,10 @@ This document specifies how Pad handles low-level pointer and keyboard events us
|
|||
|
||||
Pad avoids high-level widgets and uses the following low-level Gioui ops and events. Ops are submitted in the paint function; Events are returned by `w.Event()`.
|
||||
|
||||
| Op (submitted in paint) | Event (returned by w.Event()) | Use Case |
|
||||
| Op (submitted in paint) | Event / Gesture | Use Case |
|
||||
|---|---|---|
|
||||
| `pointer.InputOp` | `pointer.Event` | Defines hit-regions (editor, buttons, search bar). Captures `Press`, `Release`, `Move`, `Drag`, `Scroll`. |
|
||||
| `gesture.Click.Add` | `gesture.ClickEvent` | Tap detection for UI elements (buttons, status bar labels, icons). Click area is clipped to painted content. |
|
||||
| `pointer.InputOp` | `pointer.Event` | Editor hit-region. Captures `Press`, `Release`, `Move`, `Drag`, `Scroll` for cursor movement, selection, scrolling. |
|
||||
| `key.InputOp` | `key.Event` | Enables keyboard focus. Captures `Edit` (text insertion), `Press` (Backspace, Enter, Arrows). |
|
||||
| `key.FocusOp` | — | Requests keyboard focus for the active element. |
|
||||
| `key.SoftKeyboardOp` | — | Explicitly shows/hides the Android soft keyboard. |
|
||||
|
|
@ -17,11 +18,11 @@ Pad avoids high-level widgets and uses the following low-level Gioui ops and eve
|
|||
|
||||
Because Logic does not have access to `*op.Ops`, the Op/Event flow is split:
|
||||
|
||||
1. **Logic → Ops**: Logic computes `[]Element`. Each element carries metadata about what input ops it needs (e.g., "this TextField needs `pointer.InputOp` with `Kind: Tap` on region X").
|
||||
2. **Renderer → Ops**: During the paint function, the Renderer iterates `[]Element` and submits the appropriate `pointer.InputOp` / `key.InputOp` calls into `*op.Ops`.
|
||||
3. **Gioui → Events**: Gioui returns `pointer.Event` / `key.Event` via `w.Event()` in subsequent cycles.
|
||||
4. **Main → Logic**: The Main goroutine batches these events into `[]InputEvent` and sends them to Logic.
|
||||
5. **Logic → State**: Logic processes events, updates state, and computes a new frame.
|
||||
1. **Logic → Elements**: Logic computes `[]Element`. Interactive elements declare `Interaction{Gesture, Handler}` entries.
|
||||
2. **Renderer → Ops**: During `drawElement`, the renderer registers interactions (creates `gesture.Click` instances) and submits ops into `*op.Ops`. For text elements, `click.Add()` is called inside the text's content clip.
|
||||
3. **Gioui → Events**: Gioui returns events via `w.Event()`. Gesture events are polled from `input.Source` via `click.Update(q)`.
|
||||
4. **Main → Logic**: The Main goroutine batches gesture events into `[]InputEvent` (each carrying its own handler) and sends them to Logic via `inputChan`.
|
||||
5. **Logic → State**: Logic calls `evt.Handler(evt.Data)`. Handlers are static functions that access global `TheState` directly.
|
||||
|
||||
This ensures that hit-regions are always defined in the same frame where the elements are drawn, and Logic remains testable without the SDK.
|
||||
|
||||
|
|
@ -33,17 +34,15 @@ The editor operates in three coordinate spaces:
|
|||
2. **Text Space (Lines/Cols)**: The logical position within the text file, accounting for scroll offset and word wrap.
|
||||
3. **Byte Space (Offsets)**: The raw byte offset in the UTF-8 file buffer.
|
||||
|
||||
### 2.1 Hit-Region Tags
|
||||
### 2.1 Hit Regions
|
||||
|
||||
Gioui `pointer.InputOp` takes a `tag` (an `op.Tag`). When an event fires, `event.Tag` identifies which region was touched.
|
||||
Each interactive element registers its hit region via `gesture.Click.Add(gtx.Ops)`, called inside the element's content clip. The click area is the bounding box of the painted content (text glyphs, icon image, or full element region), not the element's declared region.
|
||||
|
||||
**Tagging Strategy**:
|
||||
- The Renderer assigns a unique tag per element (e.g., `editor`, `search_bar`, `status_bar`, `alpha_index`, `merge_hunk_accept_ours`).
|
||||
- When Logic receives a `pointer.Event`, it first checks the tag to determine which element was touched, then applies element-specific coordinate logic.
|
||||
When Logic receives a click event, the `InputEvent` carries its own handler function. No tag-based dispatch is needed — the handler knows exactly what to do.
|
||||
|
||||
### 2.2 Mapping UI → Byte Offset (Editor/TextField)
|
||||
|
||||
When a `pointer.Event` occurs at `(Ex, Ey)` on the `editor` tag:
|
||||
When a `pointer.Event` occurs at `(Ex, Ey)` on the editor hit-region:
|
||||
|
||||
1. **Adjust for Scroll**: `Y = Ey + scroll_offset`.
|
||||
2. **Identify Visual Line**: `visual_line = floor(Y / line_height)`.
|
||||
|
|
@ -380,11 +379,16 @@ The Search button is always visible in the StatusBar (unlike Cut/Copy/Paste whic
|
|||
|
||||
### 5.6.6 Input Routing
|
||||
|
||||
Cut, Copy, and Paste icons are tagged with unique `op.Tag` values. When the user taps an icon:
|
||||
Interactive elements declare their behavior at construction time via the `Interactive` interface. Each element registers `Interaction` entries pairing a gesture type (`Tap`, `DoubleTap`) with a handler function.
|
||||
|
||||
1. Main goroutine receives `pointer.Event` (Type: `Press`) with the icon's tag.
|
||||
2. Main goroutine sends `InputEvent{ElementID: "cut"}`, `InputEvent{ElementID: "copy"}`, or `InputEvent{ElementID: "paste"}` to Logic.
|
||||
3. Logic processes the event and updates state.
|
||||
**Flow**:
|
||||
1. **Registration**: `drawElement` registers interactions via `registerInteraction`. For `Tap` gestures, a `*gesture.Click` is created and stored in the renderer's `clicks` map, keyed by element ID. The handler is stored alongside it.
|
||||
2. **Click area**: `click.Add(gtx.Ops)` is called inside the element's content clip (e.g., the text bounding box for labels, the icon image area for icons). Only the painted content is clickable.
|
||||
3. **Polling**: After `renderer.Draw()`, `CheckGestures()` polls all registered `gesture.Click` instances via `click.Update(q)`.
|
||||
4. **Routing**: Click events are returned as `InputEvent{Handler, Data}`. The main loop sends them to the logic goroutine via `inputChan`.
|
||||
5. **Execution**: The logic goroutine calls `evt.Handler(evt.Data)`. Handlers are static functions (e.g., `ToggleWordWrap`, `DoCut`) that access global `TheState` directly.
|
||||
|
||||
This design eliminates element-ID dispatch tables — each click event carries its own handler, and the handler knows exactly what to do.
|
||||
|
||||
### 5.6.7 Clipboard State Machine
|
||||
|
||||
|
|
@ -410,13 +414,13 @@ The filename is displayed on a separate line at the top of the StatusBar. When t
|
|||
|
||||
1. **Tap on ellipsis**: Toggling between truncated and full views.
|
||||
2. **Tap elsewhere on filename**: No action (filename text is not interactive).
|
||||
3. **Ellipsis region**: The ellipsis (`...`) is a separate `Button` element with its own `op.Tag`. Tapping it triggers the toggle.
|
||||
3. **Ellipsis region**: The ellipsis (`...`) is a separate `Button` element with a `Tap` interaction. Tapping it triggers the toggle.
|
||||
|
||||
### 5.7.3 Input Routing
|
||||
|
||||
- The ellipsis is tagged with `op.Tag` value `"filename_toggle"`.
|
||||
- When the user taps the ellipsis, the Main goroutine sends `InputEvent{ElementID: "filename_toggle"}` to Logic.
|
||||
- Logic toggles the `filename_expanded` state field.
|
||||
- The ellipsis button declares a `Tap` interaction with handler `ToggleFilename`.
|
||||
- When the user taps the ellipsis, the handler is called via `evt.Handler(evt.Data)`.
|
||||
- The handler toggles the `filename_expanded` state field.
|
||||
- Logic produces a new frame with the updated filename display.
|
||||
|
||||
### 5.7.4 State Field
|
||||
|
|
@ -484,9 +488,9 @@ The word wrap button toggles word wrap on/off:
|
|||
|
||||
**Interaction**:
|
||||
1. User taps the word wrap button in the BottomBar.
|
||||
2. Main goroutine receives `pointer.Event` (Type: `Press`) with the button's tag.
|
||||
3. Main goroutine sends `InputEvent{ElementID: "word_wrap"}` to Logic.
|
||||
4. Logic toggles the `word_wrap_enabled` state field.
|
||||
2. Main goroutine receives a click event from `CheckGestures()`.
|
||||
3. Main goroutine sends `InputEvent{Handler: ToggleWordWrap, Data: clickEvent}` to Logic.
|
||||
4. The handler toggles the `WordWrap` field in `TheState`.
|
||||
5. Logic produces a new frame (BottomBar shows updated status).
|
||||
|
||||
The word wrap status is persisted to `.pad/state.json` and restored on app start.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user