Files
8bitMyGoUI/pixel/settings.go
T
ZacharyZhang-NY 5c3c883b7d Add overlay components 176-195; pixel Harbor and Kit.Cue
Dialogs on DialogBase with focusable panels that harbor parked focus,
popovers on PopoverBase, bubbles, drawers, sheets, a lightbox, an RPG
dialogue box, notifications, a pause menu and a dithered title card.
Kit.Cue moves from feedback to pixel.
2026-10-11 23:11:46 -04:00

149 lines
3.6 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 pixel
import (
"fmt"
"log/slog"
"time"
"github.com/egoist/mygo/ui"
)
type Locale uint8
const (
En Locale = iota
Zh
)
type Settings struct {
Palette *Palette
// Dot is DIPs per dot, 1–4; zero means 2.
Dot int
Locale Locale
// Now replaces the frame clock, for tests.
Now func() time.Time
// Inert controls take no input, as under a busy veil.
Inert bool
}
// Kit is one frame's settings, captured by components.
type Kit struct {
pal *Palette
dot float32
locale Locale
now time.Time
clock func() time.Time
reduce bool
}
func (k Kit) Palette() *Palette { return k.pal }
func (k Kit) C(s Slot) ui.Color { return k.pal.Colors[s] }
func (k Kit) Dot() float32 { return k.dot }
func (k Kit) D(n int) float32 { return float32(n) * k.dot }
func (k Kit) Locale() Locale { return k.locale }
func (k Kit) Now() time.Time { return k.now }
func (k Kit) Reduce() bool { return k.reduce }
type frame struct {
cur Settings
at time.Time
typing bool
// harbor holds the focus of controls turning off.
harbor *ui.Element
}
type frameKey struct{}
// Use must run once per frame, before any component.
func Use(c *ui.Context, s Settings) {
s = s.valid()
f := frameOf(c)
if f.cur.Palette != s.Palette {
slog.Debug("8bit: palette", "name", s.Palette.Name)
}
f.cur, f.at, f.typing, f.harbor = s, c.Now(), false, nil
c.SetTheme(theme(s.Palette))
}
// Typing reports a focused Editor built so far this frame.
func Typing(c *ui.Context) bool { return current(c).typing }
// Current returns the settings in force, for Scope to change.
func Current(c *ui.Context) Settings { return current(c).cur }
// Scope builds fn under other settings.
func Scope(c *ui.Context, s Settings, fn func()) {
f := current(c)
prev := f.cur
f.cur = s.valid()
fn()
f.cur = prev
}
// Lazy keeps this scope for rows MyGo builds during layout.
func Lazy[T any](c *ui.Context, fn func(T)) func(T) {
s := current(c).cur
return func(v T) { Scope(c, s, func() { fn(v) }) }
}
func K(c *ui.Context) Kit {
s := current(c).cur
now := c.Now()
if s.Now != nil {
now = s.Now()
}
return Kit{pal: s.Palette, dot: float32(s.Dot), locale: s.Locale, now: now, clock: s.Now, reduce: c.Preferences().ReduceMotion}
}
func frameOf(c *ui.Context) *frame {
return ui.Local(c.Root(), frameKey{}, func() frame { return frame{} })
}
func current(c *ui.Context) *frame {
f := frameOf(c)
if !f.at.Equal(c.Now()) {
panic("pixel: Use was not called in this frame")
}
return f
}
func (s Settings) valid() Settings {
if s.Palette == nil {
panic("pixel: Settings.Palette is nil")
}
if s.Dot == 0 {
s.Dot = 2
}
if s.Dot < 1 || s.Dot > 4 {
panic(fmt.Sprintf("pixel: Settings.Dot %d outside 1–4", s.Dot))
}
if s.Locale > Zh {
panic(fmt.Sprintf("pixel: unknown Locale %d", s.Locale))
}
return s
}
// theme keeps MyGo's drawing in the palette; row highlights clear.
func theme(p *Palette) *ui.Theme {
t := ui.LightTheme()
c := p.Colors
t.Dark = p.Night
t.Background, t.Surface, t.SurfaceHover, t.SurfacePressed = c[Back], c[Face], ui.Transparent, c[Shade]
t.Border, t.Text, t.TextMuted = c[Edge], c[Ink], c[Faint]
t.Accent, t.AccentHover, t.AccentPressed, t.AccentText = ui.Transparent, c[AccentLit], c[Accent], c[AccentInk]
t.Danger, t.Warning, t.Success = c[Bad], c[Warn], c[Good]
t.Selection, t.Focus, t.Scrollbar, t.Radius = c[AccentLit], c[Accent], ui.Transparent, 0
return t
}
// Or is v, or def for zero; negatives panic.
func Or(name string, v, def int) int {
if v < 0 {
panic(fmt.Sprintf("pixel: %s %d is negative", name, v))
}
if v == 0 {
return def
}
return v
}