Add code components 366-385; ScrollArea Follow, editor sync

A terminal with history, a Go-colored code block, a log viewer that
follows, a hex viewer, a memory map, a JSON tree, a line diff, a GAME
OVER crash screen, an FPS meter, profiler bars, breakpoints,
variables, command history, a shortcut list, a debug grid, a pixel
editor that exports string art, a palette editor, a sprite sheet, a
glyph map and a 6502 register view.

layout.ScrollArea takes Follow, and ChatLog now uses it. data.TreeNode
takes a Value drawn in its Slot. pixel gains Has, and Sprite.Art
writes MustSprite art. pixel.Editor takes the caller's value at each
input event, so a value changed between frames is never typed onto.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-12 07:18:30 -04:00
1 parent 0e21870b64
commit 51af14709d
42 files changed
+3415 -37

No files matched your search

+52
View File
@@ -0,0 +1,52 @@
package layout_test
import (
"testing"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/layout"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
func TestScrollAreaFollowsItsEndWhileThere(t *testing.T) {
s, n, follow := ui.ScrollState{}, 10, true
tt := ui.NewTester(func(c *ui.Context) {
pixel.Use(c, pixel.Settings{Palette: pixel.SpringDay})
layout.ScrollArea(c, &s, layout.ScrollAreaOptions{Label: "Log", Follow: follow}, func() {
for range n {
ui.Box(c).Height(32)
}
}).Element.Size(200, 100)
}, 300, 200)
tt.Frame()
if s.MaxY == 0 || s.Y != s.MaxY {
t.Fatalf("a following view opened at %v of %v", s.Y, s.MaxY)
}
n++
tt.Frame()
if s.Y != s.MaxY {
t.Fatalf("at the end, a new row left the view at %v of %v", s.Y, s.MaxY)
}
// Scrolled up it stays; back at the end, it follows.
s.Y = 0
n++
tt.Frame()
if s.Y != 0 {
t.Fatalf("scrolled up, the view jumped to %v", s.Y)
}
s.Y = s.MaxY
tt.Frame()
n++
tt.Frame()
if s.Y != s.MaxY {
t.Fatalf("back at the end, the view stopped following at %v of %v", s.Y, s.MaxY)
}
// Without Follow, growth leaves the view alone.
follow, s = false, ui.ScrollState{}
n++
tt.Frame()
if s.Y != 0 {
t.Fatalf("a plain view followed to %v", s.Y)
}
}
+25 -7
View File
@@ -25,7 +25,9 @@ var (
type ScrollAreaOptions struct {
// Label names the region for assistive technology.
Label string
Label string
// Follow keeps the view at the end while there.
Follow bool
Disabled bool
}
@@ -37,17 +39,32 @@ type ScrollAreaResult struct {
type scrollKey struct{}
// area is the last view and the end last reached.
type area struct {
last, end float32
}
// ScrollArea scrolls children vertically beside a pixel scrollbar.
func ScrollArea(c *ui.Context, s *ui.ScrollState, o ScrollAreaOptions, children func()) ScrollAreaResult {
k := pixel.K(c)
// The bar comes first: TrackScroll reads s when called.
row := ui.Row(c).AlignItems(ui.Stretch).Reverse()
last := ui.Local(row, scrollKey{}, func() float32 { return s.Y })
st := ui.Local(row, scrollKey{}, func() area { return area{last: s.Y} })
view := row.Bounds().H
// Off freezes the view: any move is undone before paint.
inert := pixel.Current(c).Inert
if o.Disabled || inert {
s.Y = *last
off := o.Disabled || inert
if off {
s.Y = st.last
}
// Content grew under a view at its old end: follow.
grew := func() bool { return o.Follow && s.MaxY > st.end && s.Y >= st.end }
// Off, the end waits until it wakes.
if !off {
if grew() {
s.Y = s.MaxY
}
st.end = s.MaxY
}
var v *ui.Element
row.Children(func() {
@@ -69,7 +86,8 @@ func ScrollArea(c *ui.Context, s *ui.ScrollState, o ScrollAreaOptions, children
})
focus := v.FocusVisible()
row.Draw(func(p *ui.Painter, r ui.Rect) {
if r.H != view {
// Layout has just measured; resize or follow next frame.
if r.H != view || !off && grew() {
c.Invalidate()
}
}).DrawOver(func(p *ui.Painter, r ui.Rect) {
@@ -82,8 +100,8 @@ func ScrollArea(c *ui.Context, s *ui.ScrollState, o ScrollAreaOptions, children
g.Checker(w-1, 0, 1, g.H, pixel.Accent)
}
})
res := ScrollAreaResult{Element: row, Changed: s.Y != *last}
*last = s.Y
res := ScrollAreaResult{Element: row, Changed: s.Y != st.last}
st.last = s.Y
if res.Changed {
slog.Debug("8bit: scrollarea scrolled", "y", s.Y)
}