Elements are now pass-scoped value handles: tests that read bounds after a frame bind a ui.Handle. pixel tells frames apart by the build pass's root, since the Tester clock no longer moves on its own. Fix the regressions the upgrade brought: - Clipboard reads in input handlers go through c.Services(). - MyGo notes an overlay's opener after building it, so AutoFocus inside lost it; pixel.AutoFocus focuses through a Handle, and archtest bans the raw call. Dialogs hand the focus back again. - Elements around a press stay hovered: Tooltip tells a press-given focus by focus arriving under the pointer. - The web host moves the Tester clock with real time, at most 500 ms a call, and gives components the real clock. The gallery's source index also accepts CRLF checkouts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
92 lines
2.2 KiB
Go
92 lines
2.2 KiB
Go
// Package media plays, shows and records sound and pictures.
|
||
package media
|
||
|
||
import (
|
||
"fmt"
|
||
"time"
|
||
|
||
"github.com/egoist/mygo/ui"
|
||
|
||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
|
||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||
)
|
||
|
||
// Track is a song: title, artist and length.
|
||
type Track struct {
|
||
Title, Artist string
|
||
Length time.Duration
|
||
}
|
||
|
||
// mmss prints d as m:ss, or h:mm:ss past an hour.
|
||
func mmss(d time.Duration) string {
|
||
s := int(d / time.Second)
|
||
if s >= 3600 {
|
||
return fmt.Sprintf("%d:%02d:%02d", s/3600, s/60%60, s%60)
|
||
}
|
||
return fmt.Sprintf("%d:%02d", s/60, s%60)
|
||
}
|
||
|
||
// prev is Next turned around.
|
||
var prev = icon.Next.Mirror()
|
||
|
||
// scale is s, or def for zero; above 4 panics.
|
||
func scale(name string, s, def int) int {
|
||
s = pixel.Or(name+" Scale", s, def)
|
||
if s > 4 {
|
||
panic(fmt.Sprintf("media: %s Scale %d outside 1–4", name, s))
|
||
}
|
||
return s
|
||
}
|
||
|
||
// art is a named picture w by h dots.
|
||
func art(c *ui.Context, k pixel.Kit, label string, w, h int) ui.Element {
|
||
if label == "" {
|
||
panic("media: a picture needs a name")
|
||
}
|
||
return ui.Box(c).Size(k.D(w), k.D(h)).Label(label).Role(ui.RoleImage)
|
||
}
|
||
|
||
// dark is the darkest slot and a line on it.
|
||
func dark(k pixel.Kit) (body, line pixel.Slot) {
|
||
if k.Palette().Night {
|
||
return pixel.Well, pixel.Face
|
||
}
|
||
return pixel.Ink, pixel.Faint
|
||
}
|
||
|
||
// sameSize panics unless every sprite matches the first.
|
||
func sameSize(name string, sps []*pixel.Sprite) {
|
||
if len(sps) == 0 {
|
||
panic("media: " + name + " needs pictures")
|
||
}
|
||
for _, sp := range sps {
|
||
if sp.W != sps[0].W || sp.H != sps[0].H {
|
||
panic(fmt.Sprintf("media: %s mixes %d×%d and %d×%d pictures", name, sps[0].W, sps[0].H, sp.W, sp.H))
|
||
}
|
||
}
|
||
}
|
||
|
||
type turnKey struct{}
|
||
|
||
// turn is steps taken, their first frame, and play.
|
||
type turn struct {
|
||
held, from int
|
||
playing bool
|
||
}
|
||
|
||
// turned counts steps while playing; stopped, it holds still.
|
||
func turned(c *ui.Context, k pixel.Kit, e ui.Element, playing bool, fps int) int {
|
||
s := ui.Local(e, turnKey{}, func() turn { return turn{} })
|
||
on := playing && !k.Reduce()
|
||
if on {
|
||
n := k.Tick(c, fps)
|
||
// A clock stepping back rebases, so the count never falls.
|
||
if !s.playing || n-s.from < s.held {
|
||
s.from = n - s.held
|
||
}
|
||
s.held = n - s.from
|
||
}
|
||
s.playing = on
|
||
return s.held
|
||
}
|