Re-arrange top bar icons, move byte position to bottom bar

StatusBar (top bar):
- Icons re-arranged: [Cut] [Copy] [Paste] on left, [Conf] [Search] on right
- Removed Right/BytePosition field (moved to BottomBar)

BottomBar:
- Added BytePos field (e.g., "1024 / 50000 bytes")
- Word wrap is now a button (tap to toggle On/Off)
- Layout: CursorPos (left) | BytePos (center) | WordWrap button (right)

Element changes:
- StatusBar: removed Right field, added Search field
- BottomBar: added BytePos field, WordWrap is now a button

Interaction:
- Word wrap tap sends InputEvent{ElementID: "word_wrap"} to Logic
- Logic toggles word_wrap_enabled state field

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Greg Pomerantz 2026-05-08 15:22:12 -04:00
parent 8b09697e6b
commit 9f36806a4f
2 changed files with 49 additions and 33 deletions

View File

@ -215,26 +215,25 @@ type StatusBar struct {
/* region, visible, id — unexported */ /* region, visible, id — unexported */
Filename string // e.g., "notes.txt" (truncated with ellipsis if too long) Filename string // e.g., "notes.txt" (truncated with ellipsis if too long)
FilenameExp bool // true = show full filename (multi-line), false = truncated FilenameExp bool // true = show full filename (multi-line), false = truncated
Right string // e.g., "1024 / 50000 bytes"
CutCopy bool // true = show cut icon in left slot CutCopy bool // true = show cut icon in left slot
Copy bool // true = show copy icon in left slot (next to cut) Copy bool // true = show copy icon in left slot (next to cut)
Search bool // true = show search icon (toggle search bar) Paste bool // true = show paste icon in left slot (next to copy)
Paste bool // true = show paste icon in right slot ConflictIcon bool // true = show conflict warning icon (right side)
ConflictIcon bool // true = show conflict warning icon (rightmost) Search bool // true = show search icon (right side, next to conflict)
} }
``` ```
The status bar appears at the top of the editor page. It has a **two-line layout**: The status bar appears at the top of the editor page. It has a **two-line layout**:
- **Line 1**: Filename (truncated with ellipsis if too long). Tapping the ellipsis toggles between truncated and full multi-line view. - **Line 1**: Filename (truncated with ellipsis if too long). Tapping the ellipsis toggles between truncated and full multi-line view.
- **Line 2**: Action icons (Cut, Copy, Search, Paste) + file size + conflict icon. - **Line 2**: Action icons (Cut, Copy, Paste) on the left + conflict icon + search icon on the right.
**Fixed icon slots** (24×24 DP each, fixed positions): **Fixed icon slots** (24×24 DP each, fixed positions):
- **Cut icon**: leftmost action icon - **Cut icon**: leftmost action icon
- **Copy icon**: second from left (36 DP from Cut icon) - **Copy icon**: second from left (36 DP from Cut icon)
- **Search icon**: third from left (36 DP from Copy icon) - **Paste icon**: third from left (36 DP from Copy icon)
- **Paste icon**: fourth from left (36 DP from Search icon) - **Conflict icon**: right side (36 DP from Search icon)
- **Conflict icon**: rightmost - **Search icon**: rightmost (36 DP from Conflict icon)
Icons appear/disappear without reflowing other elements. The StatusBar's `Region.H` is computed dynamically based on whether the filename is truncated (2 lines) or expanded (3+ lines). Icons appear/disappear without reflowing other elements. The StatusBar's `Region.H` is computed dynamically based on whether the filename is truncated (2 lines) or expanded (3+ lines).
@ -246,21 +245,23 @@ When a sync conflict is detected for the active file, `ConflictIcon` is set to t
type BottomBar struct { type BottomBar struct {
/* region, visible, id — unexported */ /* region, visible, id — unexported */
CursorPos string // e.g., "Ln 10, Col 5" CursorPos string // e.g., "Ln 10, Col 5"
WordWrap bool // true = word wrap is enabled (shown for reference) BytePos string // e.g., "1024 / 50000 bytes"
WordWrap bool // true = word wrap is enabled (button: tap to toggle)
} }
``` ```
The bottom bar appears at the bottom of the editor page. It displays cursor position and editor state information. It is always visible (no icons, just text). The bottom bar appears at the bottom of the editor page. It displays cursor position, byte position, and word wrap status. It is always visible.
**Layout**: **Layout**:
``` ```
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ Ln 10, Col 5 Word Wrap: On │ Ln 10, Col 5 1024 / 50000 [Word Wrap: On]
└──────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────┘
``` ```
- **Left**: Cursor position (line number, column number) - **Left**: Cursor position (line number, column number)
- **Right**: Word wrap status (On/Off) - **Center**: Byte position (current byte / total file bytes)
- **Right**: Word wrap button (tap to toggle On/Off)
The BottomBar's `Region.H` is fixed (24 DP). It sits below the editor content. The BottomBar's `Region.H` is fixed (24 DP). It sits below the editor content.
@ -381,13 +382,12 @@ Elements that don't specify explicit colors/sizes use theme defaults. The theme
[]Element{ []Element{
StatusBar{ StatusBar{
Filename: "notes.txt", FilenameExp: false, Filename: "notes.txt", FilenameExp: false,
Right: "1024 / 50000", CutCopy: true, Copy: false, Paste: true, ConflictIcon: false, Search: false,
CutCopy: true, Copy: false, Search: false, Paste: true, ConflictIcon: false,
}, },
SearchBar{Query: "foo", Match: 1, Total: 3}, // search (when active) SearchBar{Query: "foo", Match: 1, Total: 3}, // search (when active)
TextField{Multiline: true, VisibleLines: [...], ScrollOffset: 420, WordWrap: true}, TextField{Multiline: true, VisibleLines: [...], ScrollOffset: 420, WordWrap: true},
Cursor{Line: 5, Column: 12, Selection: &Selection{...}}, Cursor{Line: 5, Column: 12, Selection: &Selection{...}},
BottomBar{CursorPos: "Ln 47, Col 12", WordWrap: true}, BottomBar{CursorPos: "Ln 47, Col 12", BytePos: "1024 / 50000", WordWrap: true},
} }
``` ```
@ -395,12 +395,13 @@ The search bar is only present when the user has activated search. When visible,
The StatusBar contains: The StatusBar contains:
- **Filename** on line 1 (truncated with ellipsis if too long) - **Filename** on line 1 (truncated with ellipsis if too long)
- **Action icons** (Cut, Copy, Search, Paste) on line 2, fixed positions - **Action icons** (Cut, Copy, Paste) on line 2, left side
- **File size** between Paste and Conflict icons - **Conflict icon** and **Search icon** on line 2, right side
The BottomBar contains: The BottomBar contains:
- **Cursor position** on the left (e.g., "Ln 47, Col 12") - **Cursor position** on the left (e.g., "Ln 47, Col 12")
- **Word wrap status** on the right (e.g., "Word Wrap: On") - **Byte position** in the center (e.g., "1024 / 50000")
- **Word wrap button** on the right (tap to toggle On/Off)
### 8.3 Merge Page ### 8.3 Merge Page

View File

@ -303,25 +303,24 @@ The StatusBar (top bar) has **fixed icon slots** for cut, copy, and paste. Icons
``` ```
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ filename.txt │ ← Line 1: filename (truncated with ellipsis) │ filename.txt │ ← Line 1: filename (truncated with ellipsis)
│ [Cut] [Copy] [Search] [Paste] 1024 / 50000 [Conf] │ ← Line 2: action icons + file size │ [Cut] [Copy] [Paste] [Conf] [Search] │ ← Line 2: action icons (left/right)
└──────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────┘
StatusBar (top bar) StatusBar (top bar)
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ TextField (editor content) │ │ TextField (editor content) │
└──────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ Ln 10, Col 5 Word Wrap: On │ ← BottomBar │ Ln 10, Col 5 1024 / 50000 [Word Wrap: On] │ ← BottomBar
└──────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────┘
``` ```
**Fixed slot positions** (in Dp, from the edges): **Fixed slot positions** (in Dp, from the edges):
- **Cut icon**: leftmost action icon (24×24 DP, fixed position) - **Cut icon**: leftmost action icon (24×24 DP, fixed position)
- **Copy icon**: second from left (24×24 DP, fixed position, 36 DP from Cut icon) - **Copy icon**: second from left (24×24 DP, fixed position, 36 DP from Cut icon)
- **Search icon**: third from left (24×24 DP, fixed position, 36 DP from Copy icon) - **Paste icon**: third from left (24×24 DP, fixed position, 36 DP from Copy icon)
- **Paste icon**: fourth from left (24×24 DP, fixed position, 36 DP from Search icon) - **Conflict icon**: right side (24×24 DP, fixed position, 36 DP from Search icon)
- **Conflict icon**: rightmost (24×24 DP, fixed position) - **Search icon**: rightmost (24×24 DP, fixed position)
- **Filename**: top line, truncated with ellipsis if too long - **Filename**: top line, truncated with ellipsis if too long
- **File size**: between Paste and Conflict icons
**Visibility rules**: **Visibility rules**:
@ -329,9 +328,9 @@ The StatusBar (top bar) has **fixed icon slots** for cut, copy, and paste. Icons
|---|---| |---|---|
| **Cut** | `selection_start != selection_end` | | **Cut** | `selection_start != selection_end` |
| **Copy** | `selection_start != selection_end` | | **Copy** | `selection_start != selection_end` |
| **Search** | Always visible (toggle search bar) |
| **Paste** | `clipboard != ""` | | **Paste** | `clipboard != ""` |
| **Conflict** | sync conflict detected for active file | | **Conflict** | sync conflict detected for active file |
| **Search** | Always visible (toggle search bar) |
Icons are rendered as `Button` elements with fixed sizes. The StatusBar's `Region` height is computed dynamically based on whether the filename line is visible (2 lines when filename is shown, 1 line when in merge/search mode). Icons are rendered as `Button` elements with fixed sizes. The StatusBar's `Region` height is computed dynamically based on whether the filename line is visible (2 lines when filename is shown, 1 line when in merge/search mode).
@ -433,31 +432,32 @@ This field is **not** persisted to disk — it is reset on app restart (same as
In the truncated view, the StatusBar has 2 lines: In the truncated view, the StatusBar has 2 lines:
``` ```
Line 1: filename.txt... Line 1: filename.txt...
Line 2: [Cut] [Copy] [Search] [Paste] 1024 / 50000 [Conf] Line 2: [Cut] [Copy] [Paste] [Conf] [Search]
``` ```
In the full view, the StatusBar has 3+ lines (depending on filename length): In the full view, the StatusBar has 3+ lines (depending on filename length):
``` ```
Line 1: very_long_filename_that_does_not_fit_on_a_single_line.txt Line 1: very_long_filename_that_does_not_fit_on_a_single_line.txt
Line 2: [Cut] [Copy] [Search] [Paste] 1024 / 50000 [Conf] Line 2: [Cut] [Copy] [Paste] [Conf] [Search]
``` ```
The `Region.H` of the StatusBar is computed dynamically based on the number of lines. The BottomBar is always at the bottom of the screen (24 DP fixed height). The `Region.H` of the StatusBar is computed dynamically based on the number of lines. The BottomBar is always at the bottom of the screen (24 DP fixed height).
## 5.8 Bottom Bar ## 5.8 Bottom Bar
The Bottom Bar appears at the bottom of the editor page. It displays cursor position and editor state information. It is always visible (no icons, just text). The Bottom Bar appears at the bottom of the editor page. It displays cursor position, byte position, and word wrap status. It is always visible.
### 5.8.1 Layout ### 5.8.1 Layout
``` ```
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ Ln 10, Col 5 Word Wrap: On │ Ln 10, Col 5 1024 / 50000 [Word Wrap: On]
└──────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────┘
``` ```
- **Left**: Cursor position (e.g., "Ln 10, Col 5") - **Left**: Cursor position (e.g., "Ln 10, Col 5")
- **Right**: Word wrap status (e.g., "Word Wrap: On") - **Center**: Byte position (e.g., "1024 / 50000")
- **Right**: Word wrap button (tap to toggle On/Off)
The BottomBar's `Region.H` is fixed at 24 DP. It sits below the editor content. The BottomBar's `Region.H` is fixed at 24 DP. It sits below the editor content.
@ -468,13 +468,28 @@ The cursor position is computed from the current byte offset:
2. The `CursorPos` field is set to `"Ln {line}, Col {column}"`. 2. The `CursorPos` field is set to `"Ln {line}, Col {column}"`.
3. The field is updated on every cursor movement (tap, arrow keys, backspace, etc.). 3. The field is updated on every cursor movement (tap, arrow keys, backspace, etc.).
### 5.8.3 Word Wrap Status ### 5.8.3 Byte Position
The word wrap status is a simple boolean field (`word_wrap_enabled`): The byte position shows the current byte offset and total file size:
- **Format**: `"current / total"` (e.g., "1024 / 50000")
- **Current**: byte offset of the cursor in the file
- **Total**: total file size in bytes
- The field is updated on every cursor movement.
### 5.8.4 Word Wrap Button
The word wrap button toggles word wrap on/off:
- `true` → "Word Wrap: On" - `true` → "Word Wrap: On"
- `false` → "Word Wrap: Off" - `false` → "Word Wrap: Off"
This field is persisted to `.pad/state.json` and restored on app start. Toggling word wrap is done via a settings screen (not yet implemented). **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.
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.
## 6. Scroll Momentum ## 6. Scroll Momentum