Add search button to top bar, move cursor info to bottom bar

StatusBar (top bar):
- Added Search icon (always visible, toggles search bar)
- Removed cursor position from top bar (moved to bottom bar)
- Fixed icon slots: Cut, Copy, Search, Paste (left to right)
- File size between Paste and Conflict icons

BottomBar (new element):
- Always visible at bottom of editor page (24 DP fixed height)
- Left: cursor position (Ln X, Col Y)
- Right: word wrap status (On/Off)
- Cursor position updated on every cursor movement
- Word wrap status persisted to .pad/state.json

Search button:
- Tap toggles search bar visible/invisible
- When active: shows SearchBar element, focuses text field, shows keyboard
- When inactive: hides SearchBar, clears query, hides keyboard
- Primary search activation method on Android (Ctrl+F still works as secondary)

Layout:
- Top: StatusBar (filename + action icons)
- Middle: SearchBar (when active) + TextField (editor)
- Bottom: BottomBar (cursor position + word wrap status)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Greg Pomerantz 2026-05-08 15:13:16 -04:00
parent c210f9a2f1
commit 8b09697e6b
2 changed files with 105 additions and 17 deletions

View File

@ -215,10 +215,10 @@ 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
Left string // e.g., "Ln 10, Col 5"
Right string // e.g., "1024 / 50000 bytes" 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 right slot Paste bool // true = show paste icon in right slot
ConflictIcon bool // true = show conflict warning icon (rightmost) ConflictIcon bool // true = show conflict warning icon (rightmost)
} }
@ -227,19 +227,44 @@ type StatusBar struct {
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, Paste) + line/col info + file size + conflict icon. - **Line 2**: Action icons (Cut, Copy, Search, Paste) + file size + conflict icon.
**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)
- **Paste icon**: second from right - **Search icon**: third from left (36 DP from Copy icon)
- **Paste icon**: fourth from left (36 DP from Search icon)
- **Conflict icon**: rightmost - **Conflict icon**: rightmost
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).
When a sync conflict is detected for the active file, `ConflictIcon` is set to true. Tapping the icon navigates to the merge resolution page. The icon persists across process death — if a conflict file exists for the active file on restore, the icon appears. When a sync conflict is detected for the active file, `ConflictIcon` is set to true. Tapping the icon navigates to the merge resolution page. The icon persists across process death — if a conflict file exists for the active file on restore, the icon appears.
### 3.10 Toast / Notification ### 3.10 Bottom Bar
```go
type BottomBar struct {
/* region, visible, id — unexported */
CursorPos string // e.g., "Ln 10, Col 5"
WordWrap bool // true = word wrap is enabled (shown for reference)
}
```
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).
**Layout**:
```
┌──────────────────────────────────────────────────────────────┐
│ Ln 10, Col 5 Word Wrap: On │
└──────────────────────────────────────────────────────────────┘
```
- **Left**: Cursor position (line number, column number)
- **Right**: Word wrap status (On/Off)
The BottomBar's `Region.H` is fixed (24 DP). It sits below the editor content.
### 3.11 Toast / Notification
```go ```go
type Toast struct { type Toast struct {
@ -318,11 +343,11 @@ Elements are rendered in slice order. Later elements draw on top of earlier ones
``` ```
1. Background (full screen) 1. Background (full screen)
2. Header / Label 2. Status bar (top)
3. Search bar or main content 3. Search bar or main content
4. List or text area 4. List or text area
5. Overlay elements (cursor, selection highlight, toast) 5. Overlay elements (cursor, selection highlight)
6. Status bar (bottom) 6. Bottom bar
``` ```
## 7. Theme ## 7. Theme
@ -356,12 +381,13 @@ 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,
Left: "Ln 47, Col 12", Right: "1024 / 50000", Right: "1024 / 50000",
CutCopy: true, Copy: false, Paste: true, ConflictIcon: 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},
} }
``` ```
@ -369,10 +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, Paste) on line 2, fixed positions - **Action icons** (Cut, Copy, Search, Paste) on line 2, fixed positions
- **Line/col info** between Cut/Copy and Paste icons
- **File size** between Paste and Conflict icons - **File size** between Paste and Conflict icons
The BottomBar contains:
- **Cursor position** on the left (e.g., "Ln 47, Col 12")
- **Word wrap status** on the right (e.g., "Word Wrap: On")
### 8.3 Merge Page ### 8.3 Merge Page
``` ```

View File

@ -303,17 +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] Ln 10, Col 5 [Paste] 1024 / 50000 [Conf] │ ← Line 2: action icons + info │ [Cut] [Copy] [Search] [Paste] 1024 / 50000 [Conf] │ ← Line 2: action icons + file size
└──────────────────────────────────────────────────────────────┘
StatusBar (top bar)
┌──────────────────────────────────────────────────────────────┐
│ TextField (editor content) │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Ln 10, Col 5 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)
- **Paste icon**: second from right (24×24 DP, fixed position) - **Search 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**: rightmost (24×24 DP, fixed position) - **Conflict 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
- **Line/col info**: between Cut/Copy and Paste icons
- **File size**: between Paste and Conflict icons - **File size**: between Paste and Conflict icons
**Visibility rules**: **Visibility rules**:
@ -322,6 +329,7 @@ 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 |
@ -344,6 +352,25 @@ Icons are rendered as `Button` elements with fixed sizes. The StatusBar's `Regio
4. Selection is **not** cleared (text remains in buffer). 4. Selection is **not** cleared (text remains in buffer).
5. Logic produces a new frame (Cut/Copy icons still visible, Paste icon appears). 5. Logic produces a new frame (Cut/Copy icons still visible, Paste icon appears).
### 5.6.4a Search Button
The Search button toggles the search bar visible/invisible.
1. User taps the Search icon.
2. Logic toggles `search_active` state field.
3. If `search_active` is true:
- Show `SearchBar` element below the StatusBar.
- Set focus to the search bar's text field.
- Show the soft keyboard (`key.SoftKeyboardOp(true)`).
4. If `search_active` is false:
- Hide `SearchBar` element.
- Clear search query.
- Remove focus from search bar.
- Hide soft keyboard (`key.SoftKeyboardOp(false)`).
5. Logic produces a new frame.
The Search button is always visible in the StatusBar (unlike Cut/Copy/Paste which are conditional). It serves as the primary way to activate search on Android (where Ctrl+F may not be available).
### 5.6.5 Paste Operation ### 5.6.5 Paste Operation
1. User taps the Paste icon. 1. User taps the Paste icon.
@ -406,16 +433,48 @@ 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] Ln 10, Col 5 [Paste] 1024 / 50000 [Conf] Line 2: [Cut] [Copy] [Search] [Paste] 1024 / 50000 [Conf]
``` ```
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] Ln 10, Col 5 [Paste] 1024 / 50000 [Conf] Line 2: [Cut] [Copy] [Search] [Paste] 1024 / 50000 [Conf]
``` ```
The `Region.H` of the StatusBar is computed dynamically based on the number of lines. 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
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).
### 5.8.1 Layout
```
┌──────────────────────────────────────────────────────────────┐
│ Ln 10, Col 5 Word Wrap: On │
└──────────────────────────────────────────────────────────────┘
```
- **Left**: Cursor position (e.g., "Ln 10, Col 5")
- **Right**: Word wrap status (e.g., "Word Wrap: On")
The BottomBar's `Region.H` is fixed at 24 DP. It sits below the editor content.
### 5.8.2 Cursor Position
The cursor position is computed from the current byte offset:
1. Logic converts byte offset to (line, column) using the **Line Index**.
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.).
### 5.8.3 Word Wrap Status
The word wrap status is a simple boolean field (`word_wrap_enabled`):
- `true` → "Word Wrap: On"
- `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).
## 6. Scroll Momentum ## 6. Scroll Momentum