Update specs to match Element interface implementation
- Element is an interface with Region() and Visible() methods - Concrete types have unexported region/visible/id fields - Constructors (NewLabel, NewListView) replace embedded Element struct - Theme simplified to FontSize only - Removed OnSelect/OnPress callback IDs from element types - Added internal/ui/ to architecture in spec.md Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
6720359360
commit
dfc112f5d4
|
|
@ -28,7 +28,7 @@ Pad is a minimal, high-performance plain text editor for Android. It is configur
|
||||||
|
|
||||||
```
|
```
|
||||||
cmd/
|
cmd/
|
||||||
pad/ # Mobile entry point
|
pad/ # Mobile entry point (Gioui window + frame loop)
|
||||||
|
|
||||||
internal/
|
internal/
|
||||||
browser/ # Directory listing, pagination, search, alphabetical index
|
browser/ # Directory listing, pagination, search, alphabetical index
|
||||||
|
|
@ -38,6 +38,7 @@ internal/
|
||||||
conflict/ # External change reconciliation, merge strategies
|
conflict/ # External change reconciliation, merge strategies
|
||||||
state/ # App state persistence, per-file cursor map, restore logic
|
state/ # App state persistence, per-file cursor map, restore logic
|
||||||
undo/ # Undo operation log, context anchoring, rebasing
|
undo/ # Undo operation log, context anchoring, rebasing
|
||||||
|
ui/ # Element types, renderer, Gioui drawing
|
||||||
```
|
```
|
||||||
|
|
||||||
## 4. File Browser
|
## 4. File Browser
|
||||||
|
|
|
||||||
|
|
@ -33,23 +33,33 @@ Consistent with Gioui's `unit` package:
|
||||||
|
|
||||||
The logic layer works exclusively in `Dp` (positions/sizes) and `Sp` (fonts). The renderer converts to raw pixels using the `Metric` provided by Gioui's transaction context.
|
The logic layer works exclusively in `Dp` (positions/sizes) and `Sp` (fonts). The renderer converts to raw pixels using the `Metric` provided by Gioui's transaction context.
|
||||||
|
|
||||||
|
### 2.3 Element Interface
|
||||||
|
|
||||||
|
All UI elements implement the `Element` interface, which exposes the region and visibility:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Element interface {
|
||||||
|
Region() Region
|
||||||
|
Visible() bool
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Concrete elements are plain Go structs with unexported fields for `region`, `visible`, and `id`. Constructors (`NewLabel`, `NewListView`, etc.) set these fields, keeping the API clean and preventing external mutation.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Region defines a screen area in device-independent pixels (Dp).
|
|
||||||
type Region struct {
|
type Region struct {
|
||||||
X, Y unit.Dp
|
X, Y unit.Dp
|
||||||
W, H unit.Dp
|
W, H unit.Dp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Element is the base of all UI elements.
|
type Label struct { /* unexported region, visible, id + exported Text, Align, ... */ }
|
||||||
type Element struct {
|
func NewLabel(text string, fontSize unit.Sp, region Region) Label
|
||||||
ID string // unique identifier for input routing
|
func (l Label) Region() Region
|
||||||
Region Region // where to draw (computed by logic/layout pass, in Dp)
|
func (l Label) Visible() bool
|
||||||
Visible bool // false = skip rendering
|
func (l Label) ID() string
|
||||||
}
|
|
||||||
```
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Each concrete element embeds `Element` and adds its own fields.
|
The renderer accepts `[]Element` and dispatches via type switches. This keeps the logic layer testable — tests assert on concrete element values without any framework or interface indirection in the test code.
|
||||||
|
|
||||||
## 3. Element Catalog
|
## 3. Element Catalog
|
||||||
|
|
||||||
|
|
@ -57,7 +67,7 @@ Each concrete element embeds `Element` and adds its own fields.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Label struct {
|
type Label struct {
|
||||||
Element
|
/* region, visible, id — unexported */
|
||||||
Text string
|
Text string
|
||||||
Align TextAlign // start, center, end
|
Align TextAlign // start, center, end
|
||||||
FontSize unit.Sp // 0 = theme default
|
FontSize unit.Sp // 0 = theme default
|
||||||
|
|
@ -74,7 +84,7 @@ Used for: headers, status bar text, file sizes, line numbers.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type TextField struct {
|
type TextField struct {
|
||||||
Element
|
/* region, visible, id — unexported */
|
||||||
Value string
|
Value string
|
||||||
Placeholder string
|
Placeholder string
|
||||||
Focused bool // true = show cursor, accept keyboard input
|
Focused bool // true = show cursor, accept keyboard input
|
||||||
|
|
@ -97,7 +107,7 @@ The editor's `TextField` only contains lines visible in the current viewport. Th
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Cursor struct {
|
type Cursor struct {
|
||||||
Element // region is the cursor rectangle within the text field
|
/* region, visible, id — unexported */
|
||||||
Line int // line number (0-indexed into VisibleLines)
|
Line int // line number (0-indexed into VisibleLines)
|
||||||
Column int // character offset within the line
|
Column int // character offset within the line
|
||||||
Blinking bool // current blink state
|
Blinking bool // current blink state
|
||||||
|
|
@ -116,11 +126,10 @@ Rendered as an overlay inside a `TextField`. The logic layer computes cursor pos
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type ListView struct {
|
type ListView struct {
|
||||||
Element
|
/* region, visible, id — unexported */
|
||||||
Items []ListItem
|
Items []ListItem
|
||||||
ScrollOffset int // index of the first visible item
|
ScrollOffset int // index of the first visible item
|
||||||
Selected int // index of selected item (-1 = none)
|
Selected int // index of selected item (-1 = none)
|
||||||
OnSelect string // callback ID for routing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListItem struct {
|
type ListItem struct {
|
||||||
|
|
@ -136,10 +145,9 @@ Used for: directory browser. Only contains items for the current viewport.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type AlphaIndex struct {
|
type AlphaIndex struct {
|
||||||
Element
|
/* region, visible, id — unexported */
|
||||||
Letters []string // visible letters (e.g., ["A", "B", "C", ...])
|
Letters []string // visible letters (e.g., ["A", "B", "C", ...])
|
||||||
ActiveLetter string // currently pressed letter (for highlighting)
|
ActiveLetter string // currently pressed letter (for highlighting)
|
||||||
OnTap string // callback ID
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -149,11 +157,10 @@ Used for: quick navigation in the directory browser.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Button struct {
|
type Button struct {
|
||||||
Element
|
/* region, visible, id — unexported */
|
||||||
Text string
|
Text string
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Primary bool // true = emphasized style (e.g., filled background)
|
Primary bool // true = emphasized style (e.g., filled background)
|
||||||
OnPress string // callback ID
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -163,7 +170,7 @@ Used for: merge resolution (ours/theirs/both), dismiss, apply.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type MergeHunk struct {
|
type MergeHunk struct {
|
||||||
Element
|
/* region, visible, id — unexported */
|
||||||
HunkNumber int // N of M
|
HunkNumber int // N of M
|
||||||
TotalHunks int
|
TotalHunks int
|
||||||
LineRange string // display text, e.g., "Lines 142–148"
|
LineRange string // display text, e.g., "Lines 142–148"
|
||||||
|
|
@ -182,9 +189,9 @@ Used for: conflict resolution UI. The logic layer produces one `MergeHunk` eleme
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type StatusBar struct {
|
type StatusBar struct {
|
||||||
Element
|
/* region, visible, id — unexported */
|
||||||
Left string // e.g., "Ln 10, Col 5"
|
Left string // e.g., "Ln 10, Col 5"
|
||||||
Right string // e.g., "1024 / 50000 bytes"
|
Right string // e.g., "1024 / 50000 bytes"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -192,7 +199,7 @@ type StatusBar struct {
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Toast struct {
|
type Toast struct {
|
||||||
Element
|
/* region, visible, id — unexported */
|
||||||
Text string
|
Text string
|
||||||
Timeout time.Duration // auto-dismiss after this duration
|
Timeout time.Duration // auto-dismiss after this duration
|
||||||
}
|
}
|
||||||
|
|
@ -204,7 +211,7 @@ Used for: "undo skipped (text changed)", "file saved", "conflict detected".
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Spacer struct {
|
type Spacer struct {
|
||||||
Element
|
/* region, visible, id — unexported */
|
||||||
// Region.H defines the spacer height
|
// Region.H defines the spacer height
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -276,24 +283,15 @@ Elements are rendered in slice order. Later elements draw on top of earlier ones
|
||||||
|
|
||||||
## 7. Theme
|
## 7. Theme
|
||||||
|
|
||||||
Minimal theming via a `Theme` struct passed to the layout pass:
|
Minimal theming via a `Theme` struct passed to the renderer:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Theme struct {
|
type Theme struct {
|
||||||
ScreenWidth unit.Dp
|
FontSize unit.Sp
|
||||||
ScreenHeight unit.Dp
|
|
||||||
FontSize unit.Sp
|
|
||||||
HeaderH unit.Dp
|
|
||||||
StatusBarH unit.Dp
|
|
||||||
Padding unit.Dp
|
|
||||||
TextColor Color
|
|
||||||
BgColor Color
|
|
||||||
AccentColor Color
|
|
||||||
Metric unit.Metric // PxPerDp, PxPerSp — for renderer use
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Elements that don't specify explicit colors/sizes use theme defaults. The theme is part of the app state, not a global.
|
Elements that don't specify explicit colors/sizes use theme defaults. The theme is part of the app state, not a global. Screen dimensions, header/status bar heights, and padding are layout constants known to the logic layer's layout functions, not theme fields.
|
||||||
|
|
||||||
## 8. Page Compositions
|
## 8. Page Compositions
|
||||||
|
|
||||||
|
|
@ -335,32 +333,35 @@ Elements that don't specify explicit colors/sizes use theme defaults. The theme
|
||||||
|
|
||||||
## 9. Testing
|
## 9. Testing
|
||||||
|
|
||||||
Tests import the logic layer, call render functions, and assert on the resulting `[]Element`:
|
Tests import the logic layer, call layout functions, and assert on the resulting `[]Element`. Since `Element` is an interface, tests type-assert to concrete types:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func TestBrowserFiltersBySearch(t *testing.T) {
|
func TestBrowserFiltersBySearch(t *testing.T) {
|
||||||
state := BrowserState{Entries: allFiles, Search: "foo"}
|
state := BrowserState{Entries: allFiles, Search: "foo"}
|
||||||
elems := BrowserLayout(state, theme)
|
elems := BrowserLayout(state, screen)
|
||||||
|
|
||||||
list := elems[2].(ListView)
|
list, ok := elems[2].(ListView)
|
||||||
|
assert.True(t, ok)
|
||||||
assert.Equal(t, 3, len(list.Items))
|
assert.Equal(t, 3, len(list.Items))
|
||||||
assert.Equal(t, "foo.txt", list.Items[0].Text)
|
assert.Equal(t, "foo.txt", list.Items[0].Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEditorCursorPosition(t *testing.T) {
|
func TestEditorCursorPosition(t *testing.T) {
|
||||||
state := EditorState{Cursor: 1024, Buffer: buffer}
|
state := EditorState{Cursor: 1024, Buffer: buffer}
|
||||||
elems := EditorLayout(state, theme)
|
elems := EditorLayout(state, screen)
|
||||||
|
|
||||||
cursor := elems[1].(Cursor)
|
cursor, ok := elems[1].(Cursor)
|
||||||
|
assert.True(t, ok)
|
||||||
assert.Equal(t, 42, cursor.Line)
|
assert.Equal(t, 42, cursor.Line)
|
||||||
assert.Equal(t, 10, cursor.Column)
|
assert.Equal(t, 10, cursor.Column)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeHunkDisplay(t *testing.T) {
|
func TestMergeHunkDisplay(t *testing.T) {
|
||||||
state := MergeState{CurrentHunk: 2, Hunks: hunks}
|
state := MergeState{CurrentHunk: 2, Hunks: hunks}
|
||||||
elems := MergeLayout(state, theme)
|
elems := MergeLayout(state, screen)
|
||||||
|
|
||||||
hunk := elems[2].(MergeHunk)
|
hunk, ok := elems[2].(MergeHunk)
|
||||||
|
assert.True(t, ok)
|
||||||
assert.Equal(t, 3, hunk.HunkNumber)
|
assert.Equal(t, 3, hunk.HunkNumber)
|
||||||
assert.Equal(t, 7, hunk.TotalHunks)
|
assert.Equal(t, 7, hunk.TotalHunks)
|
||||||
assert.Equal(t, Unresolved, hunk.Resolution)
|
assert.Equal(t, Unresolved, hunk.Resolution)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user