Files
ZacharyZhang-NYandClaude Opus 5.5 03e1f91706 Upgrade MyGo to v0.5.5
Elements are now pass-scoped value handles: tests that read bounds
after a frame bind a ui.Handle. pixel tells frames apart by the build
pass's root, since the Tester clock no longer moves on its own.

Fix the regressions the upgrade brought:
- Clipboard reads in input handlers go through c.Services().
- MyGo notes an overlay's opener after building it, so AutoFocus
  inside lost it; pixel.AutoFocus focuses through a Handle, and
  archtest bans the raw call. Dialogs hand the focus back again.
- Elements around a press stay hovered: Tooltip tells a press-given
  focus by focus arriving under the pointer.
- The web host moves the Tester clock with real time, at most 500 ms
  a call, and gives components the real clock.

The gallery's source index also accepts CRLF checkouts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 01:31:08 -04:00

89 lines
2.7 KiB
Go

package files
import (
"cmp"
"fmt"
"log/slog"
"math"
"slices"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/data"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
// Entry is a file packed in an archive.
type Entry struct {
Path string
// Size and Packed are bytes before and after packing.
Size, Packed int64
}
type ArchiveViewerOptions struct {
// Height is the rows' in dots; zero means 120.
Height int
Disabled bool
}
type ArchiveViewerResult struct {
Element ui.Element
// Sorted reports a header click this frame.
Changed, Sorted bool
}
type archiveKey struct{}
// archiveView is the sort and the row chosen in it.
type archiveView struct {
sort data.Sort
row int
}
// ArchiveViewer lists entries and sizes; headers sort them.
func ArchiveViewer(c *ui.Context, text string, entries []Entry, selected *int, o ArchiveViewerOptions) ArchiveViewerResult {
k := pixel.K(c)
pick("ArchiveViewer", selected, len(entries))
var total, packed int64
for _, e := range entries {
// Checked against what is left, so sums cannot overflow.
if e.Path == "" || e.Size < 0 || e.Packed < 0 || e.Size > math.MaxInt64-total || e.Packed > math.MaxInt64-packed {
panic(fmt.Sprintf("files: ArchiveViewer entry %q of %d, %d bytes", e.Path, e.Size, e.Packed))
}
total, packed = total+e.Size, packed+e.Packed
}
col := ui.Column(c).Gap(k.D(3))
st := ui.Local(col, archiveKey{}, func() archiveView { return archiveView{} })
// The view sorts; the caller's index stays on its entry.
order := make([]int, len(entries))
for i := range order {
order[i] = i
}
slices.SortStableFunc(order, func(a, b int) int {
ea, eb := entries[a], entries[b]
n := [3]int{cmp.Compare(ea.Path, eb.Path), cmp.Compare(ea.Size, eb.Size), cmp.Compare(ea.Packed, eb.Packed)}[st.sort.Column]
if st.sort.Desc {
return -n
}
return n
})
st.row = slices.Index(order, *selected)
var res ArchiveViewerResult
cols := []data.Column{{Title: k.Msg("name"), Width: 112}, {Title: k.Msg("size"), Width: 56}, {Title: k.Msg("packed"), Width: 64}}
res.Element = col.Children(func() {
t := data.Table(c, text, cols, len(entries), &st.sort, &st.row, data.TableOptions{Height: o.Height, Disabled: o.Disabled}, func(r, col int) string {
e := entries[order[r]]
return [3]string{e.Path, size(e.Size), size(e.Packed)}[col]
})
if t.Changed && st.row >= 0 {
*selected, res.Changed = order[st.row], true
}
res.Sorted = t.Sorted
pixel.Text(c, fmt.Sprintf(k.Msg("archive_total"), len(entries), size(total), size(packed)), pixel.TextOptions{Slot: pixel.Faint})
})
if res.Changed {
slog.Debug("8bit: archive viewer", "label", text, "entry", *selected)
}
return res
}