Add desk components 466-485 and icon.Bomb

Twenty pieces of a retro desktop: Desktop, DesktopIcon, Taskbar,
StartMenu, AppWindow, Calculator, Notepad, Minesweeper, ControlPanel,
TrayIcons, ShutdownScreen, BootScreen, Screensaver, Snake, Dock,
RunDialog, DiskDefrag, WallpaperPicker, ClipboardPanel and LockScreen.

The calculator really adds up and calls division by zero GAME OVER;
Minesweeper and Snake are playable, first click always safe; START
cascades its submenus without moving the rows already open; windows
drag by whole dots; the screensaver's logo changes color at each wall
and sparkles in a corner; the defragmenter is oddly soothing. Clocks
that step back hold at zero, and typed or copied text never reaches
the log.

icon.Bomb is the new 12x12 mine.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-12 13:13:44 -04:00
1 parent d60d53ef90
commit 7800003418
39 files changed
+4120 -2

No files matched your search

+186
View File
@@ -0,0 +1,186 @@
package gallery
import (
"fmt"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/desk"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
var deskEntries = []Entry{
{466, "Desktop", "桌面", "desk", "A dithered wallpaper with icons down the left, as desktops had.", demoDesktop},
{467, "DesktopIcon", "桌面图标", "desk", "Click to choose and it inverts; double-click or Enter opens it.", demoDesktopIcon},
{468, "Taskbar", "任务栏", "desk", "START, a button for each open window, the tray and the clock.", demoTaskbar},
{469, "StartMenu", "开始菜单", "desk", "Programs in cascading menus. Arrows walk in and out; Enter runs one.", demoStartMenu},
{470, "AppWindow", "应用窗口", "desk", "Drag a window by its title bar, or focus the bar and use the arrows.", demoAppWindow},
{471, "Calculator", "计算器", "desk", "It really adds up. Divide by zero for a GAME OVER; C continues.", demoCalculator},
{472, "Notepad", "记事本", "desk", "A text area under FILE and EDIT. TIME/DATE stamps the moment.", demoNotepad},
{473, "Minesweeper", "扫雷", "desk", "Open with a click or Enter, flag with a right click or F. The face starts over.", demoMinesweeper},
{474, "ControlPanel", "控制面板", "desk", "Settings as an icon grid. Arrows choose; Enter opens.", demoControlPanel},
{475, "TrayIcons", "托盘图标", "desk", "Small icons by the clock. Hover or focus one for its tip.", demoTrayIcons},
}
type deskDemo struct {
chosen, active, wx, wy int
note string
shut bool
}
func deskState(c *ui.Context) *deskDemo {
return ui.Local(c.Root(), demoKey("desk"), func() deskDemo {
return deskDemo{chosen: -1, wx: 70, wy: 12, note: "Dear diary,\ntoday I defragged."}
})
}
// icons sit on the demo desktops.
var icons = []struct {
name string
sp *pixel.Sprite
}{{"MY COMPUTER", icon.Gamepad}, {"RECYCLE BIN", icon.Trash}, {"NOTES.TXT", icon.FileText}, {"GAMES", icon.Folder}}
// programs fill the demo START menus.
var programs = []desk.Program{
{Name: "PROGRAMS", Icon: icon.Folder, Programs: []desk.Program{
{Name: "ACCESSORIES", Icon: icon.Folder, Programs: []desk.Program{{Name: "NOTEPAD", Icon: icon.FileText}, {Name: "CALCULATOR", Icon: icon.Grid}}},
{Name: "GAMES", Icon: icon.Gamepad, Programs: []desk.Program{{Name: "MINESWEEPER", Icon: icon.Flag}, {Name: "SNAKE", Icon: icon.Bolt}}},
}},
{Name: "DOCUMENTS", Icon: icon.FileText},
{Name: "SETTINGS", Icon: icon.Settings, Programs: []desk.Program{{Name: "CONTROL PANEL", Icon: icon.Settings}, {Name: "WALLPAPER", Icon: icon.Image}}},
{Name: "RUN...", Icon: icon.Terminal},
{Name: "SHUT DOWN", Icon: icon.Power},
}
func demoDesktop(c *ui.Context, log func(string)) {
s := deskState(c)
desk.Desktop(c, "Desktop", desk.DesktopOptions{Width: 300, Height: 150}, func() {
for i, it := range icons {
r := desk.DesktopIcon(c, it.name, it.sp, desk.DesktopIconOptions{Chosen: s.chosen == i})
if r.Clicked {
s.chosen = i
log("chose " + it.name)
}
if r.Opened {
log("opened " + it.name)
}
}
})
}
func demoDesktopIcon(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := deskState(c)
ui.Row(c).Gap(k.D(4)).Children(func() {
for i, it := range icons[:3] {
r := desk.DesktopIcon(c, it.name, it.sp, desk.DesktopIconOptions{Chosen: s.chosen == i})
if r.Clicked {
s.chosen = i
log("chose " + it.name)
}
if r.Opened {
log("opened " + it.name)
}
}
})
}
// tray sits by the demo taskbars' clocks.
var tray = []desk.TrayIcon{{Icon: icon.Wifi, Tip: "ONLINE"}, {Icon: icon.Volume, Tip: "VOLUME 80%"}, {Icon: icon.Battery, Tip: "BATTERY 99%"}}
func demoTaskbar(c *ui.Context, log func(string)) {
s := deskState(c)
windows := []string{"NOTEPAD", "CALC", "MINES"}
r := desk.Taskbar(c, "Taskbar", windows, &s.active, desk.TaskbarOptions{Programs: programs, Tray: tray})
if r.Changed {
log(fmt.Sprintf("active window %d", s.active))
}
if r.Launched != "" {
log("launched " + r.Launched)
}
if r.Tray >= 0 {
log("tray: " + tray[r.Tray].Tip)
}
}
func demoStartMenu(c *ui.Context, log func(string)) {
if r := desk.StartMenu(c, "Start", programs, desk.StartMenuOptions{}); r.Launched != "" {
log("launched " + r.Launched)
}
}
func demoAppWindow(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := deskState(c)
ui.Column(c).Gap(k.D(4)).Children(func() {
desk.Desktop(c, "Desktop", desk.DesktopOptions{Wallpaper: desk.Dots, Width: 320, Height: 170}, func() {
if !s.shut {
r := desk.AppWindow(c, "HELLO.TXT", &s.wx, &s.wy, desk.AppWindowOptions{Width: 120, Height: 48, Room: [2]int{320, 170}}, func() {
pixel.Text(c, "Drag me around!", pixel.TextOptions{})
})
if r.Moved {
log(fmt.Sprintf("window at %d, %d", s.wx, s.wy))
}
if r.Closed {
s.shut = true
log("window closed")
}
}
})
if s.shut && input.Button(c, "REOPEN", input.ButtonOptions{Size: input.Small}).Clicked {
s.shut = false
log("window reopened")
}
})
}
func demoCalculator(c *ui.Context, log func(string)) {
if desk.Calculator(c, "Pocket calculator", desk.CalculatorOptions{}).Changed {
log("key pressed")
}
}
func demoNotepad(c *ui.Context, log func(string)) {
s := deskState(c)
r := desk.Notepad(c, "Notepad", &s.note, desk.NotepadOptions{})
if r.Changed {
log(fmt.Sprintf("%d runes", len([]rune(s.note))))
}
if r.Saved {
log("saved")
}
}
func demoMinesweeper(c *ui.Context, log func(string)) {
r := desk.Minesweeper(c, "Minesweeper", desk.MinesweeperOptions{})
if r.Flagged {
log("flag")
}
if r.Restarted {
log("new game")
}
if r.Won {
log("cleared the field")
}
if r.Lost {
log("boom")
}
}
func demoControlPanel(c *ui.Context, log func(string)) {
applets := []desk.Applet{
{Name: "DISPLAY", Icon: icon.Image}, {Name: "SOUND", Icon: icon.Volume}, {Name: "CLOCK", Icon: icon.Clock}, {Name: "NETWORK", Icon: icon.Wifi},
{Name: "POWER", Icon: icon.Power}, {Name: "USERS", Icon: icon.Users}, {Name: "FONTS", Icon: icon.FileText}, {Name: "GAMEPAD", Icon: icon.Gamepad},
}
if i := desk.ControlPanel(c, "Control panel", applets, desk.ControlPanelOptions{}).Opened; i >= 0 {
log("opened " + applets[i].Name)
}
}
func demoTrayIcons(c *ui.Context, log func(string)) {
if i := desk.TrayIcons(c, "Tray", tray, desk.TrayIconsOptions{}).Clicked; i >= 0 {
log("clicked " + tray[i].Tip)
}
}
+160
View File
@@ -0,0 +1,160 @@
package gallery
import (
"fmt"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/desk"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
var retroEntries = []Entry{
{476, "ShutdownScreen", "关机画面", "desk", "The kindest words a computer ever said.", demoShutdownScreen},
{477, "BootScreen", "开机自检", "desk", "Memory counts up, POST lines roll by, the logo waits.", demoBootScreen},
{478, "Screensaver", "屏保", "desk", "A logo bounces round the screen. Any key, click or move wakes it. Watch the corners.", demoScreensaver},
{479, "Snake", "贪吃蛇", "desk", "Arrows steer, food grows you. The wall is not your friend.", demoSnake},
{480, "Dock", "程序坞", "desk", "A shelf of apps. The one you point at or focus rises.", demoDock},
{481, "RunDialog", "运行", "desk", "Type a program's name. OK runs it; CANCEL or Escape walks away.", demoRunDialog},
{482, "DiskDefrag", "磁盘整理", "desk", "Scattered blocks march into order. Oddly soothing.", demoDiskDefrag},
{483, "WallpaperPicker", "壁纸选择", "desk", "Dither patterns to dress the desktop.", demoWallpaperPicker},
{484, "ClipboardPanel", "剪贴板", "desk", "Recent clips. Click one and it is yours to paste again.", demoClipboardPanel},
{485, "LockScreen", "锁屏", "desk", "The time, the date, and PRESS ANY KEY.", demoLockScreen},
}
type retroDemo struct {
saver, locked, defrag bool
boots, disks int
run string
paper desk.Wallpaper
}
func retroState(c *ui.Context) *retroDemo {
return ui.Local(c.Root(), demoKey("retro"), func() retroDemo { return retroDemo{run: "notepad"} })
}
func demoShutdownScreen(c *ui.Context, log func(string)) {
desk.ShutdownScreen(c, desk.ShutdownScreenOptions{Width: 300, Height: 120})
}
func demoBootScreen(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := retroState(c)
ui.Column(c).Gap(k.D(4)).Children(func() {
// A new key is a new screen: REBOOT starts over.
ui.Box(c).Key(s.boots).Children(func() {
if desk.BootScreen(c, "8BIT BIOS V4.51", []string{
"CPU: PIXEL 8086 AT 4.77 MHZ", "FLOPPY A: 1.44 MB", "KEYBOARD... OK", "BOOTING FROM C:",
}, desk.BootScreenOptions{Width: 300, Height: 140}).Done {
log("booted")
}
})
if input.Button(c, "REBOOT", input.ButtonOptions{Size: input.Small}).Clicked {
s.boots++
log("rebooting")
}
})
}
func demoScreensaver(c *ui.Context, log func(string)) {
s := retroState(c)
if input.Button(c, "START SAVER", input.ButtonOptions{}).Clicked {
s.saver = true
log("saver on")
}
if desk.Screensaver(c, "8BIT", &s.saver, desk.ScreensaverOptions{}).Woke {
log("woke up")
}
}
func demoSnake(c *ui.Context, log func(string)) {
r := desk.Snake(c, "Snake pit", desk.SnakeOptions{})
if r.Started {
log("go!")
}
if r.Over {
log(fmt.Sprintf("game over at %d", r.Score))
}
}
func demoDock(c *ui.Context, log func(string)) {
apps := []desk.DockApp{
{Name: "FILES", Icon: icon.Folder}, {Name: "MAIL", Icon: icon.Mail}, {Name: "MUSIC", Icon: icon.Music},
{Name: "CAMERA", Icon: icon.Camera}, {Name: "TERMINAL", Icon: icon.Terminal}, {Name: "TRASH", Icon: icon.Trash},
}
if i := desk.Dock(c, "Dock", apps, desk.DockOptions{}).Launched; i >= 0 {
log("launched " + apps[i].Name)
}
}
func demoRunDialog(c *ui.Context, log func(string)) {
s := retroState(c)
r := desk.RunDialog(c, "Run", &s.run, desk.RunDialogOptions{})
if r.Changed {
log(fmt.Sprintf("%d letters typed", len([]rune(s.run))))
}
if r.Submitted {
log(fmt.Sprintf("running a %d-letter program", len([]rune(s.run))))
}
if r.Canceled {
log("canceled")
}
}
func demoDiskDefrag(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := retroState(c)
ui.Column(c).Gap(k.D(4)).Children(func() {
ui.Box(c).Key(s.disks).Children(func() {
if desk.DiskDefrag(c, "Drive C:", s.defrag, desk.DiskDefragOptions{Seed: 7}).Done {
s.defrag = false
log("drive in order")
}
})
ui.Row(c).Gap(k.D(4)).Children(func() {
label := "START"
if s.defrag {
label = "PAUSE"
}
if input.Button(c, label, input.ButtonOptions{Size: input.Small}).Clicked {
s.defrag = !s.defrag
log(fmt.Sprintf("defrag running %v", s.defrag))
}
if input.Button(c, "NEW DISK", input.ButtonOptions{Size: input.Small}).Clicked {
s.disks, s.defrag = s.disks+1, false
log("a fresh mess")
}
})
})
}
func demoWallpaperPicker(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := retroState(c)
ui.Row(c).Gap(k.D(8)).AlignItems(ui.Start).Children(func() {
if desk.WallpaperPicker(c, "Wallpaper", &s.paper, desk.WallpaperPickerOptions{}).Changed {
log(fmt.Sprintf("wallpaper %d", s.paper))
}
desk.Desktop(c, "Preview", desk.DesktopOptions{Wallpaper: s.paper, Width: 120, Height: 90}, func() {})
})
}
func demoClipboardPanel(c *ui.Context, log func(string)) {
clips := []string{"git push gitea main", "IT'S NOW SAFE TO TURN OFF", "UP UP DOWN DOWN LEFT RIGHT", "8bitmygoui.workers.dev"}
if i := desk.ClipboardPanel(c, "Clipboard", clips, desk.ClipboardPanelOptions{Width: 240}).Copied; i >= 0 {
log(fmt.Sprintf("copied clip %d", i+1))
}
}
func demoLockScreen(c *ui.Context, log func(string)) {
s := retroState(c)
if input.Button(c, "LOCK", input.ButtonOptions{}).Clicked {
s.locked = true
log("locked")
}
if desk.LockScreen(c, "Lock screen", &s.locked, desk.LockScreenOptions{}).Unlocked {
log("unlocked")
}
}
+43
View File
@@ -0,0 +1,43 @@
package gallery
import (
"strings"
"testing"
)
func TestDeskDemosLogTheirActions(t *testing.T) {
// Focusing, opening a menu or moving a light logs nothing.
quiet := map[string]bool{"key:Tab": true, "Start": true, "key:Up": true, "FILE": true, "OPEN:": true}
for _, tc := range []struct {
path string
steps []string
}{
{"/c/466-desktop", []string{"MY COMPUTER"}}, {"/c/473-minesweeper", []string{"NEW GAME"}},
{"/c/479-snake", []string{"Snake pit"}}, {"/c/481-rundialog", []string{"OPEN:", "type:ls"}},
{"/c/467-desktopicon", []string{"MY COMPUTER", "key:Enter"}}, {"/c/468-taskbar", []string{"ONLINE"}},
{"/c/469-startmenu", []string{"Start", "key:Up", "key:Enter"}}, {"/c/470-appwindow", []string{"CLOSE", "REOPEN"}},
{"/c/471-calculator", []string{"Pocket calculator"}}, {"/c/472-notepad", []string{"FILE", "SAVE"}},
{"/c/474-controlpanel", []string{"Control panel"}}, {"/c/475-trayicons", []string{"ONLINE"}},
{"/c/477-bootscreen", []string{"REBOOT"}}, {"/c/478-screensaver", []string{"START SAVER", "key:A"}},
{"/c/480-dock", []string{"MAIL"}}, {"/c/481-rundialog", []string{"OK", "CANCEL"}},
{"/c/482-diskdefrag", []string{"START", "NEW DISK"}}, {"/c/483-wallpaperpicker", []string{"Wallpaper"}},
{"/c/484-clipboardpanel", []string{"git push gitea main"}}, {"/c/485-lockscreen", []string{"LOCK", "key:A"}},
} {
a, tt := open(tc.path)
slug := tc.path[3:]
for _, step := range tc.steps {
before := len(a.logs[slug])
if k, ok := strings.CutPrefix(step, "key:"); ok {
tt.Key(0, letters[k])
tt.Frame()
} else if s, ok := strings.CutPrefix(step, "type:"); ok {
tt.Type(s)
} else {
click(t, tt, step)
}
if len(a.logs[slug]) <= before && !quiet[step] {
t.Errorf("%s: %s logged nothing: %q", slug, step, a.logs[slug])
}
}
}
}
+1 -1
View File
@@ -56,7 +56,7 @@ var Categories = []Category{
{"tabletop", "Tabletop", "桌游", icon.Crown},
}
var entries = slices.Concat(foundationEntries, layoutEntries, textEntries, displayEntries, inputEntries, controlEntries, feedbackEntries, navigationEntries, dataEntries, recordEntries, overlayEntries, popupEntries, datetimeEntries, timeEntries, chartEntries, plotEntries, gameEntries, rpgEntries, screenEntries, lootEntries, fxEntries, screenFxEntries, chatEntries, mailEntries, mediaEntries, studioEntries, codeEntries, debugEntries, accountEntries, playerEntries, filesEntries, storageEntries, projectEntries, teamEntries, shopEntries, marketEntries, agentEntries, guildEntries)
var entries = slices.Concat(foundationEntries, layoutEntries, textEntries, displayEntries, inputEntries, controlEntries, feedbackEntries, navigationEntries, dataEntries, recordEntries, overlayEntries, popupEntries, datetimeEntries, timeEntries, chartEntries, plotEntries, gameEntries, rpgEntries, screenEntries, lootEntries, fxEntries, screenFxEntries, chatEntries, mailEntries, mediaEntries, studioEntries, codeEntries, debugEntries, accountEntries, playerEntries, filesEntries, storageEntries, projectEntries, teamEntries, shopEntries, marketEntries, agentEntries, guildEntries, deskEntries, retroEntries)
func Entries() []Entry { return entries }