Fifteen file components: FileExplorer, FileGrid, FileIcon, PathBar, MemoryCard, DiskUsage, UploadQueue, Trash, RecentFiles, FilePreview, ArchiveViewer, FileDetails, SharePanel, SyncStatus and QuotaMeter. FileIcon draws six kinds as hand-placed 12x12 pages and a folder. The browsers build on data.Tree, List, GridView and Table; ArchiveViewer owns its sort and keeps the caller's index on its entry. UploadQueue keys rows by name so focus stays on its row. SharePanel never logs the link. SyncStatus turns its arrows on Busy steps, so syncing moves under Reduce Motion. data.Tree now opens the closed branches above a chosen path it knows, so a caller's starting path survives the first frame.
174 lines
6.2 KiB
Go
174 lines
6.2 KiB
Go
package gallery
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/files"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
|
)
|
|
|
|
var filesEntries = []Entry{
|
|
{406, "FileExplorer", "文件管理器", "files", "Folders on the left, their files on the right. Double click opens.", demoFileExplorer},
|
|
{407, "FileGrid", "文件网格", "files", "Big icons over short names. Arrows walk the grid; Enter opens.", demoFileGrid},
|
|
{408, "FileIcon", "文件图标", "files", "Six kinds, placed dot by dot: folder, text, image, audio, code, archive.", demoFileIcon},
|
|
{409, "PathBar", "路径栏", "files", "Where you are, folder by folder. Click one to climb back up.", demoPathBar},
|
|
{410, "MemoryCard", "记忆卡", "files", "Fifteen blocks, like the grey card of old. DELETE frees one.", demoMemoryCard},
|
|
{411, "DiskUsage", "磁盘占用", "files", "Where the room went, stacked in one bar.", demoDiskUsage},
|
|
{412, "UploadQueue", "上传队列", "files", "Files on their way up. ✕ calls one back.", demoUploadQueue},
|
|
{413, "Trash", "回收站", "files", "A bin that looks full when it is. EMPTY takes it out.", demoTrash},
|
|
}
|
|
|
|
// disk is the demo's drive, from C: down.
|
|
var disk = files.Dir{Name: "C:", Files: []files.File{{Name: "AUTOEXEC.BAT", Kind: files.Code, Size: 512}}, Dirs: []files.Dir{
|
|
{Name: "GAMES", Dirs: []files.Dir{{Name: "HERO QUEST", Files: []files.File{
|
|
{Name: "SAVE1.SAV", Kind: files.Archive, Size: 8192},
|
|
{Name: "MAP.PNG", Kind: files.Image, Size: 48_000},
|
|
{Name: "THEME.MID", Kind: files.Audio, Size: 21_504},
|
|
{Name: "CHEATS.TXT", Kind: files.Text, Size: 1_337},
|
|
}}}, Files: []files.File{{Name: "README.TXT", Kind: files.Text, Size: 2_048}}},
|
|
{Name: "MUSIC", Files: []files.File{
|
|
{Name: "BOSS FIGHT.MID", Kind: files.Audio, Size: 30_720},
|
|
{Name: "CREDITS.MID", Kind: files.Audio, Size: 12_288},
|
|
}},
|
|
}}
|
|
|
|
// path is the demo path's folder names, root first.
|
|
var path = []string{"C:", "GAMES", "HERO QUEST", "SAVES"}
|
|
|
|
type filesDemo struct {
|
|
dir string
|
|
file, grid, block, trash, at int
|
|
saves []files.Save
|
|
ups []files.Upload
|
|
started time.Time
|
|
}
|
|
|
|
func filesState(c *ui.Context) *filesDemo {
|
|
return ui.Local(c.Root(), demoKey("files"), func() filesDemo {
|
|
return filesDemo{dir: "0/0/0", file: -1, grid: -1, trash: 3, at: len(path) - 1, saves: []files.Save{
|
|
{Title: "HERO QUEST", Icon: icon.Sword},
|
|
{Title: "SPACE RACE", Icon: icon.Star, Slot: pixel.Warn},
|
|
{Title: "MONSTER FARM", Icon: icon.Heart, Slot: pixel.Bad},
|
|
{Title: "PUZZLE TOWER", Icon: icon.Gem, Slot: pixel.Magic},
|
|
{Title: "FISHING DAY", Icon: icon.Drop, Slot: pixel.Info},
|
|
}, ups: []files.Upload{
|
|
{Name: "SPEEDRUN.ZIP", Kind: files.Archive, Size: 9 << 20},
|
|
{Name: "BOSS.PNG", Kind: files.Image, Size: 3 << 20},
|
|
{Name: "LEVEL 9.MAP", Kind: files.Code, Size: 1 << 20, Sent: 1 << 19, Failed: true},
|
|
}}
|
|
})
|
|
}
|
|
|
|
func demoFileExplorer(c *ui.Context, log func(string)) {
|
|
s := filesState(c)
|
|
r := files.FileExplorer(c, "Explorer", disk, &s.dir, &s.file, files.FileExplorerOptions{})
|
|
if r.Changed {
|
|
log(fmt.Sprintf("folder %s, file %d", s.dir, s.file))
|
|
}
|
|
if r.Opened {
|
|
log(fmt.Sprintf("open file %d", s.file))
|
|
}
|
|
}
|
|
|
|
func demoFileGrid(c *ui.Context, log func(string)) {
|
|
s := filesState(c)
|
|
all := slices.Concat(disk.Dirs[0].Dirs[0].Files, []files.File{{Name: "SECRET LEVEL", Kind: files.Folder}})
|
|
r := files.FileGrid(c, "Files", all, &s.grid, files.FileGridOptions{Height: 150})
|
|
if r.Changed {
|
|
log("chose " + all[s.grid].Name)
|
|
}
|
|
if r.Opened {
|
|
log("open " + all[s.grid].Name)
|
|
}
|
|
}
|
|
|
|
func demoFileIcon(c *ui.Context, log func(string)) {
|
|
k := pixel.K(c)
|
|
kinds := []files.Kind{files.Folder, files.Text, files.Image, files.Audio, files.Code, files.Archive}
|
|
ui.Column(c).Gap(k.D(6)).Children(func() {
|
|
for _, scale := range []int{1, 2} {
|
|
ui.Row(c).Gap(k.D(6)).Children(func() {
|
|
for _, kind := range kinds {
|
|
files.FileIcon(c, kind, files.FileIconOptions{Scale: scale})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func demoPathBar(c *ui.Context, log func(string)) {
|
|
s := filesState(c)
|
|
// Climbing up trims the path; the reset walks back down.
|
|
r := files.PathBar(c, "Path", path[:s.at+1], files.PathBarOptions{})
|
|
if r.Clicked >= 0 {
|
|
s.at = r.Clicked
|
|
log("up to " + path[s.at])
|
|
}
|
|
if s.at < len(path)-1 && input.Button(c, "BACK DOWN", input.ButtonOptions{Size: input.Small}).Clicked {
|
|
s.at = len(path) - 1
|
|
log("down to " + path[s.at])
|
|
}
|
|
}
|
|
|
|
func demoMemoryCard(c *ui.Context, log func(string)) {
|
|
s := filesState(c)
|
|
r := files.MemoryCard(c, "Memory card 1", s.saves, &s.block, files.MemoryCardOptions{})
|
|
if r.Changed {
|
|
log(fmt.Sprintf("block %d", s.block+1))
|
|
}
|
|
if r.Deleted >= 0 {
|
|
log("deleted " + s.saves[r.Deleted].Title)
|
|
s.saves = append(s.saves[:r.Deleted:r.Deleted], s.saves[r.Deleted+1:]...)
|
|
}
|
|
}
|
|
|
|
func demoDiskUsage(c *ui.Context, log func(string)) {
|
|
files.DiskUsage(c, "Disk", []files.Use{
|
|
{Name: "GAMES", Bytes: 41 << 30}, {Name: "SCREENSHOTS", Bytes: 6 << 30},
|
|
{Name: "MUSIC", Bytes: 12 << 30}, {Name: "SAVES", Bytes: 300 << 20},
|
|
}, 128<<30, files.DiskUsageOptions{Width: 220})
|
|
}
|
|
|
|
func demoUploadQueue(c *ui.Context, log func(string)) {
|
|
k := pixel.K(c)
|
|
s := filesState(c)
|
|
if s.started.IsZero() {
|
|
s.started = k.Now()
|
|
}
|
|
// The demo sends a megabyte a second, row by row.
|
|
sent := int64(k.Since(c, s.started, 4, 60)) << 18
|
|
ups := make([]files.Upload, len(s.ups))
|
|
for i, u := range s.ups {
|
|
if !u.Failed {
|
|
u.Sent = min(u.Size, sent)
|
|
sent = max(0, sent-u.Size)
|
|
}
|
|
ups[i] = u
|
|
}
|
|
if r := files.UploadQueue(c, "Uploads", ups, files.UploadQueueOptions{}); r.Canceled >= 0 {
|
|
log("canceled " + s.ups[r.Canceled].Name)
|
|
s.ups = append(s.ups[:r.Canceled:r.Canceled], s.ups[r.Canceled+1:]...)
|
|
}
|
|
}
|
|
|
|
func demoTrash(c *ui.Context, log func(string)) {
|
|
k := pixel.K(c)
|
|
s := filesState(c)
|
|
ui.Column(c).Gap(k.D(6)).Children(func() {
|
|
if files.Trash(c, "Trash", s.trash, files.TrashOptions{}).Emptied {
|
|
log(fmt.Sprintf("emptied %d", s.trash))
|
|
s.trash = 0
|
|
}
|
|
if input.Button(c, "THROW AWAY", input.ButtonOptions{Size: input.Small}).Clicked {
|
|
s.trash++
|
|
log(fmt.Sprintf("threw one away: %d", s.trash))
|
|
}
|
|
})
|
|
}
|