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>
89 lines
2.7 KiB
Go
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
|
|
}
|