Files
8bitMyGoUI/desk/appwindow.go
T
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

110 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package desk
import (
"fmt"
"log/slog"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
type AppWindowOptions struct {
// The body's Width and Height are dots; zeros mean 160×100.
Width, Height int
// Room is its area in dots; zero leaves it open.
Room [2]int
Disabled bool
}
type AppWindowResult struct {
Element *ui.Element
// Closed is the close box; Moved, a drag or arrow.
Closed, Moved bool
}
type dragKey struct{}
// AppWindow floats at x, y; drag or arrow its bar.
func AppWindow(c *ui.Context, title string, x, y *int, o AppWindowOptions, content func()) AppWindowResult {
k := pixel.K(c)
w, h := pixel.Or("AppWindow Width", o.Width, 160), pixel.Or("AppWindow Height", o.Height, 100)
// The bar needs room for a grip and close box.
if w < 40 {
panic(fmt.Sprintf("desk: AppWindow Width %d is under 40", w))
}
// The window is its body, bar and frame.
ww, wh := w+6, h+pixel.LineHeight+10
if o.Room[0] > 0 && o.Room[0] < ww || o.Room[1] > 0 && o.Room[1] < wh {
panic(fmt.Sprintf("desk: AppWindow %d×%d does not fit its Room %v", ww, wh, o.Room))
}
ox, oy := *x, *y
keep := func() {
if o.Room[0] > 0 {
*x = min(*x, o.Room[0]-ww)
}
if o.Room[1] > 0 {
*y = min(*y, o.Room[1]-wh)
}
*x, *y = max(0, *x), max(0, *y)
}
keep()
win := ui.Column(c).Absolute().Width(k.D(ww)).Padding(k.D(3)).Gap(k.D(2)).Role(ui.RoleGroup).Label(title)
// rest keeps a drag's part-dot for the next frame.
rest := ui.Local(win, dragKey{}, func() [2]float32 { return [2]float32{} })
active := win.FocusWithin()
if o.Disabled {
win.Description(k.Msg("disabled"))
}
res := AppWindowResult{Element: win}
win.Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
g.Frame(0, 0, g.W, g.H, pixel.Raised, pixel.Face)
}).Children(func() {
ui.Row(c).FillWidth().Gap(k.D(2)).AlignItems(ui.Center).Children(func() {
shell, base, st := pixel.Shell(c, func() *ui.Element {
return ui.Box(c).Focusable().Role(ui.RoleGroup).Label(title).Description(k.Msg("arrows_move")).Disabled(o.Disabled)
})
if !base.IsDisabled() {
if dx, dy, ok := base.Dragged(); ok {
rest[0], rest[1] = rest[0]+dx/k.Dot(), rest[1]+dy/k.Dot()
*x, *y = *x+int(rest[0]), *y+int(rest[1])
rest[0], rest[1] = rest[0]-float32(int(rest[0])), rest[1]-float32(int(rest[1]))
}
for _, m := range []struct {
key ui.Key
dx, dy int
}{{ui.KeyLeft, -4, 0}, {ui.KeyRight, 4, 0}, {ui.KeyUp, 0, -4}, {ui.KeyDown, 0, 4}} {
if base.Shortcut(0, m.key) {
*x, *y = *x+m.dx, *y+m.dy
}
}
}
shell.Grow(1).Height(k.D(pixel.LineHeight + 2)).Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
barLook(g, title, active)
g.Finish(st)
})
res.Closed = input.IconButton(c, icon.Close, k.Msg("close"), input.IconButtonOptions{Size: input.Small, Disabled: o.Disabled}).Clicked
})
// Off, the body's controls are off too, and veiled.
in := pixel.Current(c)
in.Inert = in.Inert || o.Disabled
ui.Box(c).Size(k.D(w), k.D(h)).Clip().Children(func() { pixel.Scope(c, in, content) }).DrawOver(func(p *ui.Painter, r ui.Rect) {
if o.Disabled {
g := k.Canvas(p, r)
g.Checker(0, 0, g.W, g.H, pixel.Face)
}
})
})
keep()
win.Left(k.D(*x)).Top(k.D(*y))
res.Moved = *x != ox || *y != oy
if res.Moved || res.Closed {
slog.Debug("8bit: app window", "title", title, "x", *x, "y", *y, "closed", res.Closed)
}
return res
}