Files
ZacharyZhang-NY 7800003418 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.
2026-10-12 13:13:44 -04:00

285 lines
8.7 KiB
Go

package desk_test
import (
"strings"
"testing"
"time"
"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/pixel"
)
func TestDesktopFillsColumnsFirst(t *testing.T) {
tt := view(700, 400, newWorld(), func(c *ui.Context) {
desk.Desktop(c, "Desk", desk.DesktopOptions{Height: 140}, func() {
for _, name := range []string{"A", "B", "C"} {
desk.DesktopIcon(c, name, icon.File, desk.DesktopIconOptions{})
}
})
})
a, b, cc := find(t, tt, "A"), find(t, tt, "B"), find(t, tt, "C")
// Two icons fill a 140-dot column; the third starts another.
if b.X != a.X || b.Y <= a.Y || cc.X <= a.X || cc.Y != a.Y {
t.Fatalf("icons at %v, %v, %v", a, b, cc)
}
}
func TestDesktopIconChoosesOpensAndInverts(t *testing.T) {
chosen, clicks, opens, off := false, 0, 0, false
tt := view(300, 200, newWorld(), func(c *ui.Context) {
r := desk.DesktopIcon(c, "NOTES", icon.FileText, desk.DesktopIconOptions{Chosen: chosen, Disabled: off})
if r.Clicked {
clicks++
chosen = true
}
if r.Opened {
opens++
}
})
x, y, w, h := dots(find(t, tt, "NOTES"))
if count(tt.Image(), x, y, w, h, pixel.Accent) > 0 {
t.Fatal("an unchosen icon shows Accent")
}
click(t, tt, "NOTES")
if clicks != 1 || opens != 0 || count(tt.Image(), x, y, w, h, pixel.Accent) == 0 {
t.Fatalf("one click: %d clicks, %d opens, or it did not invert", clicks, opens)
}
// The second quick click is a double click: it opens.
click(t, tt, "NOTES")
tt.Key(0, ui.KeyEnter)
if opens != 2 {
t.Fatalf("double click and Enter opened %d times", opens)
}
// A key sent as it turns off still does nothing.
off = true
tt.Key(0, ui.KeyEnter)
if opens != 2 {
t.Fatal("a disabled icon opened")
}
}
func TestTaskbarSwitchesMinimizesAndTellsTheTime(t *testing.T) {
active, launched, trayed := 0, "", -1
wd := newWorld()
progs := []desk.Program{{Name: "GAMES", Icon: icon.Folder, Programs: []desk.Program{{Name: "SNAKE", Icon: icon.Bolt}}}, {Name: "RUN", Icon: icon.Terminal}}
tt := view(700, 300, wd, func(c *ui.Context) {
r := desk.Taskbar(c, "Bar", []string{"ONE", "TWO"}, &active, desk.TaskbarOptions{Programs: progs, Tray: []desk.TrayIcon{{Icon: icon.Wifi, Tip: "ONLINE"}}})
if r.Launched != "" {
launched = r.Launched
}
if r.Tray >= 0 {
trayed = r.Tray
}
})
tt.Frame()
find(t, tt, "10:30")
x, y, w, h := dots(find(t, tt, "OPEN WINDOWS"))
// The second button chooses TWO; pressing it again minimizes.
tt.ClickAt(float32(2*(x+w*3/4)), float32(2*(y+h/2)))
if active != 1 {
t.Fatalf("active %d after pressing TWO", active)
}
tt.Key(0, ui.KeyEnter)
if active != -1 {
t.Fatalf("pressing the active window left %d", active)
}
click(t, tt, "ONLINE")
if trayed != 0 {
t.Fatalf("tray click gave %d", trayed)
}
click(t, tt, "START")
tt.Key(0, ui.KeyEnter)
tt.Key(0, ui.KeyEnter)
if launched != "GAMES/SNAKE" {
t.Fatalf("START, Enter, Enter launched %q", launched)
}
}
func TestStartMenuCascades(t *testing.T) {
launched, off := "", false
progs := []desk.Program{
{Name: "PROGRAMS", Icon: icon.Folder, Programs: []desk.Program{
{Name: "GAMES", Icon: icon.Folder, Programs: []desk.Program{{Name: "SNAKE", Icon: icon.Bolt}, {Name: "MINES", Icon: icon.Flag}}},
{Name: "NOTEPAD", Icon: icon.FileText},
}},
{Name: "HELP", Icon: icon.Question},
}
tt := view(700, 500, newWorld(), func(c *ui.Context) {
if r := desk.StartMenu(c, "Start", progs, desk.StartMenuOptions{Disabled: off}); r.Launched != "" {
launched = r.Launched
}
})
click(t, tt, "Start")
if _, ok := tt.Find("PROGRAMS"); !ok {
t.Fatal("START did not open")
}
// Right goes in, Left comes back out, Enter runs.
tt.Key(0, ui.KeyRight)
tt.Key(0, ui.KeyRight)
if _, ok := tt.Find("MINES"); !ok {
t.Fatalf("two Rights did not reach GAMES: %q", tt.Texts())
}
tt.Key(0, ui.KeyLeft)
tt.Key(0, ui.KeyDown)
tt.Key(0, ui.KeyEnter)
if launched != "PROGRAMS/NOTEPAD" {
t.Fatalf("launched %q", launched)
}
if _, ok := tt.Find("PROGRAMS"); ok {
t.Fatal("the menu stayed open after a launch")
}
// Clicks: a submenu opens, a program runs.
click(t, tt, "Start")
click(t, tt, "PROGRAMS")
click(t, tt, "GAMES")
click(t, tt, "MINES")
if launched != "PROGRAMS/GAMES/MINES" {
t.Fatalf("clicks launched %q", launched)
}
// Turned off while open, it closes.
click(t, tt, "Start")
off = true
tt.Frame()
if _, ok := tt.Find("PROGRAMS"); ok {
t.Fatal("a START turned off stayed open")
}
click(t, tt, "Start")
if _, ok := tt.Find("PROGRAMS"); ok {
t.Fatal("a disabled START opened")
}
}
func TestStartMenuOpensSubmenusUnderThePointer(t *testing.T) {
progs := []desk.Program{{Name: "TOOLS", Icon: icon.Folder, Programs: []desk.Program{{Name: "HAMMER", Icon: icon.Sword}}}, {Name: "QUIT", Icon: icon.Power}}
tt := view(700, 500, newWorld(), func(c *ui.Context) { desk.StartMenu(c, "Start", progs, desk.StartMenuOptions{}) })
click(t, tt, "Start")
tt.Key(0, ui.KeyDown)
r := find(t, tt, "TOOLS")
tt.Move(r.X+r.W/2, r.Y+r.H/2)
tt.Frame()
if _, ok := tt.Find("HAMMER"); !ok {
t.Fatalf("pointing at TOOLS did not open it: %q", tt.Texts())
}
}
func TestTrayIconsAreOneTabStop(t *testing.T) {
clicked := -1
tt := view(400, 200, newWorld(), func(c *ui.Context) {
if i := desk.TrayIcons(c, "Tray", []desk.TrayIcon{{Icon: icon.Wifi, Tip: "ONLINE"}, {Icon: icon.Volume, Tip: "LOUD"}}, desk.TrayIconsOptions{}).Clicked; i >= 0 {
clicked = i
}
})
tt.Key(0, ui.KeyTab)
tt.Key(0, ui.KeyRight)
tt.Key(0, ui.KeyEnter)
if clicked != 1 || !tt.Focused("LOUD") {
t.Fatalf("Tab, Right, Enter clicked %d", clicked)
}
// Focused, the tip shows in a bubble too.
if n := strings.Count(strings.Join(tt.Texts(), "|"), "LOUD"); n < 2 {
t.Fatalf("LOUD shows %d times: %q", n, tt.Texts())
}
}
func TestDockRaisesAndLaunches(t *testing.T) {
for _, reduce := range []bool{false, true} {
wd := newWorld()
wd.reduce = reduce
launched := -1
tt := view(400, 200, wd, func(c *ui.Context) {
if i := desk.Dock(c, "Dock", []desk.DockApp{{Name: "MAIL", Icon: icon.Mail}, {Name: "MUSIC", Icon: icon.Music}}, desk.DockOptions{}).Launched; i >= 0 {
launched = i
}
})
x, y, _, _ := dots(find(t, tt, "MAIL"))
top := func() int {
for j := y; j < y+40; j++ {
if count(tt.Image(), x, j, 30, 1, pixel.Ink) > 0 {
return j - y
}
}
return -1
}
rest := top()
tt.Key(0, ui.KeyTab)
first := top()
later(tt, wd, time.Second)
up := top()
// Focused, it rises eight dots: stepping, or at once reduced.
if up != rest-8 || reduce != (first == up) {
t.Errorf("reduce %v: rest %d, first %d, risen %d", reduce, rest, first, up)
}
tt.Key(0, ui.KeyEnter)
if launched != 0 {
t.Errorf("Enter launched %d", launched)
}
}
}
func TestControlPanelOpensByKeysAndClicks(t *testing.T) {
opened := -1
applets := []desk.Applet{{Name: "SOUND", Icon: icon.Volume}, {Name: "CLOCK", Icon: icon.Clock}, {Name: "POWER", Icon: icon.Power}}
tt := view(500, 200, newWorld(), func(c *ui.Context) {
if i := desk.ControlPanel(c, "Panel", applets, desk.ControlPanelOptions{}).Opened; i >= 0 {
opened = i
}
})
tt.Key(0, ui.KeyTab)
tt.Key(0, ui.KeyEnd)
tt.Key(0, ui.KeyEnter)
if opened != 2 {
t.Fatalf("End, Enter opened %d", opened)
}
r := find(t, tt, "Panel")
tt.ClickAt(r.X+2*33, r.Y+2*20)
if opened != 0 {
t.Fatalf("a click on SOUND opened %d", opened)
}
}
func TestWallpaperPickerChooses(t *testing.T) {
paper, changed := desk.Checks, 0
tt := view(500, 300, newWorld(), func(c *ui.Context) {
if desk.WallpaperPicker(c, "Paper", &paper, desk.WallpaperPickerOptions{}).Changed {
changed++
}
})
tt.Key(0, ui.KeyTab)
tt.Key(0, ui.KeyDown)
tt.Key(0, ui.KeyEnter)
if paper != desk.Dots || changed != 1 {
t.Fatalf("Down, Enter chose %d, %d changes", paper, changed)
}
find(t, tt, "DOTS")
}
func TestStartMenuHoldsStillAsSubmenusOpen(t *testing.T) {
progs := []desk.Program{
{Name: "TOOLS", Icon: icon.Folder, Programs: []desk.Program{{Name: "SAW", Icon: icon.Sword}, {Name: "AXE", Icon: icon.Sword}, {Name: "HOE", Icon: icon.Leaf}}},
{Name: "QUIT", Icon: icon.Power},
}
for _, low := range []bool{false, true} {
launched := ""
tt := view(700, 500, newWorld(), func(c *ui.Context) {
// Low on the window, the menu opens upward.
ui.Box(c).Height(map[bool]float32{false: 0, true: 420}[low])
if r := desk.StartMenu(c, "Start", progs, desk.StartMenuOptions{}); r.Launched != "" {
launched = r.Launched
}
})
click(t, tt, "Start")
before := find(t, tt, "TOOLS")
click(t, tt, "TOOLS")
if after := find(t, tt, "TOOLS"); after != before {
t.Errorf("low %v: TOOLS moved from %v to %v", low, before, after)
}
click(t, tt, "AXE")
if launched != "TOOLS/AXE" {
t.Errorf("low %v: launched %q", low, launched)
}
}
}