Make the editor top and bottom bars ~30% taller for bigger tap targets
The top bar's back/search icons (24dp in a 32dp bar) and the bottom bar's wrap toggle (a text label in a 24dp bar) were fiddly to hit. Top bar: 32 -> 42dp (new TopBarHeight const, which buildFindBar and the editor text area already hang off); its icons 24 -> 31dp (new TopBarIconSize; the find bar keeps the smaller ui.IconSize). Bottom bar: BottomBarHeight 24 -> 31dp, labels re-centred. While verifying, a relaunch into an app-unreadable saved directory (/storage/emulated is media_rw:media_rw 0750 on device; only /storage/emulated/0 is app-exposed) showed an empty browser: os.Stat passes for such dirs, so the restore guard now probes with a real os.ReadDir — the same operation the browser index does. Test expectations that hard-coded the old 32dp top bar now use TopBarHeight. Verified on emulator and phone: back/search/wrap all tappable, find bar still docks under the taller top bar.
This commit is contained in:
parent
e96464ec79
commit
ef54486683
|
|
@ -120,10 +120,14 @@ func run(w *app.Window) error {
|
||||||
if sess, ok := loadSession(sessPath); ok {
|
if sess, ok := loadSession(sessPath); ok {
|
||||||
if sess.InBrowser {
|
if sess.InBrowser {
|
||||||
log.Printf("restoring session: browser path=%q", sess.BrowserPath)
|
log.Printf("restoring session: browser path=%q", sess.BrowserPath)
|
||||||
// The saved directory may have vanished (deleted, synced away):
|
// The saved directory may have vanished (deleted, synced away) or
|
||||||
// fall back to the startup directory rather than pointing the
|
// be unreadable by this app (stat succeeds but the permission bits
|
||||||
// browser at a dead path.
|
// deny the read — e.g. /storage/emulated itself, which is
|
||||||
if fi, err := os.Stat(sess.BrowserPath); err == nil && fi.IsDir() {
|
// media_rw:media_rw 0750; only /storage/emulated/0 is app-exposed).
|
||||||
|
// Probe with a real directory read — the same operation the
|
||||||
|
// browser's index does — rather than os.Stat, which passes for
|
||||||
|
// unreadable dirs. Fall back to the startup directory otherwise.
|
||||||
|
if _, err := os.ReadDir(sess.BrowserPath); err == nil {
|
||||||
logic.BeginBrowserRestore(sess.BrowserPath)
|
logic.BeginBrowserRestore(sess.BrowserPath)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ func TestSelectionMenu_FollowsTextAcrossScroll(t *testing.T) {
|
||||||
// The menu sits ABOVE the anchor's line: lineTop(line 5) = reg.Y + 5*lh
|
// The menu sits ABOVE the anchor's line: lineTop(line 5) = reg.Y + 5*lh
|
||||||
// (reg.Y = 32: the top bar is margin-free, the anchor's visual line
|
// (reg.Y = 32: the top bar is margin-free, the anchor's visual line
|
||||||
// index is 5).
|
// index is 5).
|
||||||
wantY0 := ui.Dp(32) + ui.Dp(5*lh) - menuH - 8
|
wantY0 := TopBarHeight + ui.Dp(5*lh) - menuH - 8
|
||||||
if !dpeq(y0, wantY0) {
|
if !dpeq(y0, wantY0) {
|
||||||
t.Fatalf("initial menu Y = %v, want %v (above the anchor line)", y0, wantY0)
|
t.Fatalf("initial menu Y = %v, want %v (above the anchor line)", y0, wantY0)
|
||||||
}
|
}
|
||||||
|
|
@ -109,19 +109,20 @@ func TestSelectionMenu_ClampsWhenTextLeavesView(t *testing.T) {
|
||||||
}
|
}
|
||||||
yTop := e.MenuRect.Y // above line 5
|
yTop := e.MenuRect.Y // above line 5
|
||||||
|
|
||||||
// Scroll 10 lines: line 5 is 5 lines ABOVE the viewport top. Both ends
|
// Scroll 11 lines: line 5 is 6 lines ABOVE the viewport top. Both ends
|
||||||
// of the selection stay inside the shaped window (small file = whole
|
// of the selection stay inside the shaped window (small file = whole
|
||||||
// buffer) so the menu tracks: the preferred above-placement overflows
|
// buffer) so the menu tracks: the preferred above-placement overflows
|
||||||
// (the line is off-screen top) and the menu flips below the line, which
|
// (the line is off-screen top) and the menu flips below the line, which
|
||||||
// lands just under the window top (lineTop + lh + handleDropDp + 8).
|
// overflows the window top too (lineTop + lh + handleDropDp + 8 = 0 < 8)
|
||||||
TheState.ScrollOffset = ui.Dp(10 * lh)
|
// and clamps to it.
|
||||||
|
TheState.ScrollOffset = ui.Dp(11 * lh)
|
||||||
EditorLayout(ui.Dp(1000), ui.Dp(2000), false)
|
EditorLayout(ui.Dp(1000), ui.Dp(2000), false)
|
||||||
|
|
||||||
if !e.MenuVisible {
|
if !e.MenuVisible {
|
||||||
t.Fatal("menu hidden, but the selection is still in the shaped window")
|
t.Fatal("menu hidden, but the selection is still in the shaped window")
|
||||||
}
|
}
|
||||||
// Below-placement: lineTop(32) + 5*lh - 10*lh + lh + handleDropDp + 8
|
// Below-placement: lineTop(TopBarHeight) + 5*lh - 11*lh + lh + handleDropDp + 8
|
||||||
// = 6.8 < 8, so the menu clamps to the window top.
|
// = 0 < 8, so the menu clamps to the window top.
|
||||||
if got := e.MenuRect.Y; !dpeq(got, 8) {
|
if got := e.MenuRect.Y; !dpeq(got, 8) {
|
||||||
t.Fatalf("menu Y = %v, want 8 (below-placement clamped to the window top)", got)
|
t.Fatalf("menu Y = %v, want 8 (below-placement clamped to the window top)", got)
|
||||||
}
|
}
|
||||||
|
|
@ -171,9 +172,10 @@ func TestSelectionMenu_FlipsBelowOnFirstLine(t *testing.T) {
|
||||||
if !e.MenuVisible {
|
if !e.MenuVisible {
|
||||||
t.Fatal("menu not shown")
|
t.Fatal("menu not shown")
|
||||||
}
|
}
|
||||||
// Line 0 top = reg.Y = 32; above would be 32-52-8 = -28 < 8, so the menu
|
// Line 0 top = reg.Y = TopBarHeight; above would be TopBarHeight-52-8 =
|
||||||
// flips below the selection HANDLES: line bottom + handleDropDp + 8.
|
// -18 < 8, so the menu flips below the selection HANDLES: line bottom +
|
||||||
wantY := ui.Dp(32) + EffectiveLineHeight() + ui.Dp(handleDropDp) + 8
|
// handleDropDp + 8.
|
||||||
|
wantY := TopBarHeight + EffectiveLineHeight() + ui.Dp(handleDropDp) + 8
|
||||||
if !dpeq(e.MenuRect.Y, wantY) {
|
if !dpeq(e.MenuRect.Y, wantY) {
|
||||||
t.Fatalf("menu Y = %v, want %v (flipped below the first line, clear of the handles)", e.MenuRect.Y, wantY)
|
t.Fatalf("menu Y = %v, want %v (flipped below the first line, clear of the handles)", e.MenuRect.Y, wantY)
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +222,7 @@ func TestSelectionMenu_AnchorsToStableEnd(t *testing.T) {
|
||||||
t.Fatal("menu not shown")
|
t.Fatal("menu not shown")
|
||||||
}
|
}
|
||||||
// Above line 3 (the stable start), NOT above line 5 (the moving end).
|
// Above line 3 (the stable start), NOT above line 5 (the moving end).
|
||||||
wantY := ui.Dp(32) + ui.Dp(3*lh) - menuH - 8
|
wantY := TopBarHeight + ui.Dp(3*lh) - menuH - 8
|
||||||
if !dpeq(e.MenuRect.Y, wantY) {
|
if !dpeq(e.MenuRect.Y, wantY) {
|
||||||
t.Fatalf("menu Y = %v, want %v (above the selection's TOP line)", e.MenuRect.Y, wantY)
|
t.Fatalf("menu Y = %v, want %v (above the selection's TOP line)", e.MenuRect.Y, wantY)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2176,6 +2176,15 @@ func markDirty() {
|
||||||
// (which shrinks the text area to make room) both use it.
|
// (which shrinks the text area to make room) both use it.
|
||||||
const FindBarHeight = ui.Dp(40)
|
const FindBarHeight = ui.Dp(40)
|
||||||
|
|
||||||
|
// TopBarHeight is the editor top bar's height (buildFindBar hangs off it
|
||||||
|
// too); TopBarIconSize is that bar's icon size — both ~30% larger than the
|
||||||
|
// old 32dp/24dp pair so the back/search icons are a comfortable tap
|
||||||
|
// target. The find bar keeps the smaller ui.IconSize.
|
||||||
|
const (
|
||||||
|
TopBarHeight = ui.Dp(42)
|
||||||
|
TopBarIconSize = ui.Dp(31)
|
||||||
|
)
|
||||||
|
|
||||||
// buildFindBar lays out the in-file search bar: [input][counter][prev][next]
|
// buildFindBar lays out the in-file search bar: [input][counter][prev][next]
|
||||||
// [clear-X], full screen width, directly below the margin-free top bar (the
|
// [clear-X], full screen width, directly below the margin-free top bar (the
|
||||||
// bars merge, so this one does too; inner content keeps the margin). The
|
// bars merge, so this one does too; inner content keeps the margin). The
|
||||||
|
|
@ -2186,7 +2195,7 @@ func buildFindBar(screenWidth ui.Dp) ui.Element {
|
||||||
margin := ui.Dp(10)
|
margin := ui.Dp(10)
|
||||||
gap := ui.Dp(8)
|
gap := ui.Dp(8)
|
||||||
h := FindBarHeight
|
h := FindBarHeight
|
||||||
topY := ui.Dp(32) // top bar height (see EditorLayout)
|
topY := TopBarHeight // top bar height (see EditorLayout)
|
||||||
|
|
||||||
// Right-hand button column: prev, next, clear (X).
|
// Right-hand button column: prev, next, clear (X).
|
||||||
iconW := ui.IconSize
|
iconW := ui.IconSize
|
||||||
|
|
@ -2235,7 +2244,7 @@ func EditorLayout(screenWidth, screenHeight ui.Dp, wordWrap bool) []ui.Element {
|
||||||
statusBarRegion := ui.Region{
|
statusBarRegion := ui.Region{
|
||||||
X: 0, Y: 0,
|
X: 0, Y: 0,
|
||||||
W: screenWidth,
|
W: screenWidth,
|
||||||
H: ui.Dp(32),
|
H: TopBarHeight,
|
||||||
}
|
}
|
||||||
statusBarW := statusBarRegion.W
|
statusBarW := statusBarRegion.W
|
||||||
filename := TheState.Editor.Filename
|
filename := TheState.Editor.Filename
|
||||||
|
|
@ -2246,10 +2255,10 @@ func EditorLayout(screenWidth, screenHeight ui.Dp, wordWrap bool) []ui.Element {
|
||||||
statusBarRegion,
|
statusBarRegion,
|
||||||
ui.Color{R: 230, G: 230, B: 230, A: 255},
|
ui.Color{R: 230, G: 230, B: 230, A: 255},
|
||||||
[]ui.Element{
|
[]ui.Element{
|
||||||
ui.NewIcon("back", ui.Region{X: margin, Y: ui.Dp(4), W: ui.IconSize, H: ui.IconSize}, 0,
|
ui.NewIcon("back", ui.Region{X: margin, Y: ui.Dp(5), W: TopBarIconSize, H: TopBarIconSize}, 0,
|
||||||
[]ui.Interaction{{Gesture: ui.Tap, Handler: GoToBrowser}}),
|
[]ui.Interaction{{Gesture: ui.Tap, Handler: GoToBrowser}}),
|
||||||
ui.NewLabel(filename, 14, ui.Region{X: ui.Dp(32) + margin, Y: ui.Dp(6), W: statusBarW - ui.Dp(32) - margin - ui.IconSize - ui.Dp(8), H: ui.Dp(20)}, ui.AlignStart, "", nil),
|
ui.NewLabel(filename, 14, ui.Region{X: margin + TopBarIconSize + ui.Dp(8), Y: ui.Dp(11), W: statusBarW - margin - TopBarIconSize - ui.Dp(8) - margin - TopBarIconSize, H: ui.Dp(20)}, ui.AlignStart, "", nil),
|
||||||
ui.NewIcon("search", ui.Region{X: screenWidth - margin - ui.IconSize, Y: ui.Dp(4), W: ui.IconSize, H: ui.IconSize}, 0,
|
ui.NewIcon("search", ui.Region{X: screenWidth - margin - TopBarIconSize, Y: ui.Dp(5), W: TopBarIconSize, H: TopBarIconSize}, 0,
|
||||||
[]ui.Interaction{{Gesture: ui.Tap, Handler: ToggleFind}}),
|
[]ui.Interaction{{Gesture: ui.Tap, Handler: ToggleFind}}),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -2291,9 +2300,9 @@ func EditorLayout(screenWidth, screenHeight ui.Dp, wordWrap bool) []ui.Element {
|
||||||
bottomBarRegion,
|
bottomBarRegion,
|
||||||
ui.Color{R: 230, G: 230, B: 230, A: 255},
|
ui.Color{R: 230, G: 230, B: 230, A: 255},
|
||||||
[]ui.Element{
|
[]ui.Element{
|
||||||
ui.NewLabel(statusText, 12, ui.Region{X: margin, Y: ui.Dp(2), W: bottomBarW - margin, H: ui.Dp(20)}, ui.AlignStart, "", nil),
|
ui.NewLabel(statusText, 12, ui.Region{X: margin, Y: ui.Dp(5), W: bottomBarW - margin, H: ui.Dp(20)}, ui.AlignStart, "", nil),
|
||||||
ui.NewLabel(cursorPosText, 12, ui.Region{X: 0, Y: ui.Dp(2), W: bottomBarW, H: ui.Dp(20)}, ui.AlignCenter, "", nil),
|
ui.NewLabel(cursorPosText, 12, ui.Region{X: 0, Y: ui.Dp(5), W: bottomBarW, H: ui.Dp(20)}, ui.AlignCenter, "", nil),
|
||||||
ui.NewLabel(wrapText, 12, ui.Region{X: 0, Y: ui.Dp(2), W: bottomBarW - margin, H: ui.Dp(20)}, ui.AlignEnd, "wrap", []ui.Interaction{
|
ui.NewLabel(wrapText, 12, ui.Region{X: 0, Y: ui.Dp(5), W: bottomBarW - margin, H: ui.Dp(20)}, ui.AlignEnd, "wrap", []ui.Interaction{
|
||||||
{Gesture: ui.Tap, Handler: ToggleWordWrap},
|
{Gesture: ui.Tap, Handler: ToggleWordWrap},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -108,10 +108,10 @@ func TestRealFile_FindBarAndNavigation(t *testing.T) {
|
||||||
t.Fatalf("focus %q, want find_bar", focus)
|
t.Fatalf("focus %q, want find_bar", focus)
|
||||||
}
|
}
|
||||||
// The text area must shrink from the top while the find bar is open so
|
// The text area must shrink from the top while the find bar is open so
|
||||||
// the first line is not covered: region top = top bar (32) + FindBarHeight.
|
// the first line is not covered: region top = TopBarHeight + FindBarHeight.
|
||||||
reg, _ := h.Inspect(func(st *editor.State) any { return st.EditorRegion })
|
reg, _ := h.Inspect(func(st *editor.State) any { return st.EditorRegion })
|
||||||
if got := reg.(ui.Region); got.Y != ui.Dp(32)+editor.FindBarHeight {
|
if got := reg.(ui.Region); got.Y != editor.TopBarHeight+editor.FindBarHeight {
|
||||||
t.Fatalf("editor region top %d, want %d (find bar must not cover text)", int(got.Y), int(ui.Dp(32)+editor.FindBarHeight))
|
t.Fatalf("editor region top %d, want %d (find bar must not cover text)", int(got.Y), int(editor.TopBarHeight+editor.FindBarHeight))
|
||||||
}
|
}
|
||||||
// The editor field reports UNFOCUSED while the find bar is open (the
|
// The editor field reports UNFOCUSED while the find bar is open (the
|
||||||
// renderer hides its caret for unfocused fields; main hands key focus to
|
// renderer hides its caret for unfocused fields; main hands key focus to
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ const (
|
||||||
StatusBarLineHeight = Dp(24)
|
StatusBarLineHeight = Dp(24)
|
||||||
StatusBarFilenameLine = Dp(24)
|
StatusBarFilenameLine = Dp(24)
|
||||||
StatusBarIconsLine = Dp(24)
|
StatusBarIconsLine = Dp(24)
|
||||||
BottomBarHeight = Dp(24)
|
BottomBarHeight = Dp(31) // ~30% taller than the old 24dp: the bar's only tap target (the wrap toggle) is a text label, so the bar doubles as its hit area
|
||||||
IconSize = Dp(24)
|
IconSize = Dp(24)
|
||||||
IconGap = Dp(36)
|
IconGap = Dp(36)
|
||||||
Padding = Dp(8)
|
Padding = Dp(8)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user