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/
|
||||
pad/ # Mobile entry point
|
||||
pad/ # Mobile entry point (Gioui window + frame loop)
|
||||
|
||||
internal/
|
||||
browser/ # Directory listing, pagination, search, alphabetical index
|
||||
|
|
@ -38,6 +38,7 @@ internal/
|
|||
conflict/ # External change reconciliation, merge strategies
|
||||
state/ # App state persistence, per-file cursor map, restore logic
|
||||
undo/ # Undo operation log, context anchoring, rebasing
|
||||
ui/ # Element types, renderer, Gioui drawing
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
### 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
|
||||
// Region defines a screen area in device-independent pixels (Dp).
|
||||
type Region struct {
|
||||
X, Y unit.Dp
|
||||
W, H unit.Dp
|
||||
}
|
||||
|
||||
// Element is the base of all UI elements.
|
||||
type Element struct {
|
||||
ID string // unique identifier for input routing
|
||||
Region Region // where to draw (computed by logic/layout pass, in Dp)
|
||||
Visible bool // false = skip rendering
|
||||
}
|
||||
```
|
||||
type Label struct { /* unexported region, visible, id + exported Text, Align, ... */ }
|
||||
func NewLabel(text string, fontSize unit.Sp, region Region) Label
|
||||
func (l Label) Region() Region
|
||||
func (l Label) Visible() bool
|
||||
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
|
||||
|
||||
|
|
@ -57,7 +67,7 @@ Each concrete element embeds `Element` and adds its own fields.
|
|||
|
||||
```go
|
||||
type Label struct {
|
||||
Element
|
||||
/* region, visible, id — unexported */
|
||||
Text string
|
||||
Align TextAlign // start, center, end
|
||||
FontSize unit.Sp // 0 = theme default
|
||||
|
|
@ -74,7 +84,7 @@ Used for: headers, status bar text, file sizes, line numbers.
|
|||
|
||||
```go
|
||||
type TextField struct {
|
||||
Element
|
||||
/* region, visible, id — unexported */
|
||||
Value string
|
||||
Placeholder string
|
||||
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
|
||||
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)
|
||||
Column int // character offset within the line
|
||||
Blinking bool // current blink state
|
||||
|
|
@ -116,11 +126,10 @@ Rendered as an overlay inside a `TextField`. The logic layer computes cursor pos
|
|||
|
||||
```go
|
||||
type ListView struct {
|
||||
Element
|
||||
/* region, visible, id — unexported */
|
||||
Items []ListItem
|
||||
ScrollOffset int // index of the first visible item
|
||||
Selected int // index of selected item (-1 = none)
|
||||
OnSelect string // callback ID for routing
|
||||
}
|
||||
|
||||
type ListItem struct {
|
||||
|
|
@ -136,10 +145,9 @@ Used for: directory browser. Only contains items for the current viewport.
|
|||
|
||||
```go
|
||||
type AlphaIndex struct {
|
||||
Element
|
||||
Letters []string // visible letters (e.g., ["A", "B", "C", ...])
|
||||
ActiveLetter string // currently pressed letter (for highlighting)
|
||||
OnTap string // callback ID
|
||||
/* region, visible, id — unexported */
|
||||
Letters []string // visible letters (e.g., ["A", "B", "C", ...])
|
||||
ActiveLetter string // currently pressed letter (for highlighting)
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -149,11 +157,10 @@ Used for: quick navigation in the directory browser.
|
|||
|
||||
```go
|
||||
type Button struct {
|
||||
Element
|
||||
Text string
|
||||
Enabled bool
|
||||
Primary bool // true = emphasized style (e.g., filled background)
|
||||
OnPress string // callback ID
|
||||
/* region, visible, id — unexported */
|
||||
Text string
|
||||
Enabled bool
|
||||
Primary bool // true = emphasized style (e.g., filled background)
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -163,7 +170,7 @@ Used for: merge resolution (ours/theirs/both), dismiss, apply.
|
|||
|
||||
```go
|
||||
type MergeHunk struct {
|
||||
Element
|
||||
/* region, visible, id — unexported */
|
||||
HunkNumber int // N of M
|
||||
TotalHunks int
|
||||
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
|
||||
type StatusBar struct {
|
||||
Element
|
||||
Left string // e.g., "Ln 10, Col 5"
|
||||
Right string // e.g., "1024 / 50000 bytes"
|
||||
/* region, visible, id — unexported */
|
||||
Left string // e.g., "Ln 10, Col 5"
|
||||
Right string // e.g., "1024 / 50000 bytes"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -192,7 +199,7 @@ type StatusBar struct {
|
|||
|
||||
```go
|
||||
type Toast struct {
|
||||
Element
|
||||
/* region, visible, id — unexported */
|
||||
Text string
|
||||
Timeout time.Duration // auto-dismiss after this duration
|
||||
}
|
||||
|
|
@ -204,7 +211,7 @@ Used for: "undo skipped (text changed)", "file saved", "conflict detected".
|
|||
|
||||
```go
|
||||
type Spacer struct {
|
||||
Element
|
||||
/* region, visible, id — unexported */
|
||||
// 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
|
||||
|
||||
Minimal theming via a `Theme` struct passed to the layout pass:
|
||||
Minimal theming via a `Theme` struct passed to the renderer:
|
||||
|
||||
```go
|
||||
type Theme struct {
|
||||
ScreenWidth unit.Dp
|
||||
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
|
||||
FontSize unit.Sp
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -335,32 +333,35 @@ Elements that don't specify explicit colors/sizes use theme defaults. The theme
|
|||
|
||||
## 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
|
||||
func TestBrowserFiltersBySearch(t *testing.T) {
|
||||
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, "foo.txt", list.Items[0].Text)
|
||||
}
|
||||
|
||||
func TestEditorCursorPosition(t *testing.T) {
|
||||
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, 10, cursor.Column)
|
||||
}
|
||||
|
||||
func TestMergeHunkDisplay(t *testing.T) {
|
||||
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, 7, hunk.TotalHunks)
|
||||
assert.Equal(t, Unresolved, hunk.Resolution)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user