MyGo v0.5.5 returns ui.Element as a checked value handle instead of a
pointer. Migrated with `mygo migrate-ui`, then fixed what the codemod
leaves to review:
- Element nil checks become .Valid(); absent elements are ui.Element{}.
- Input handlers and paint callbacks run outside the build pass, where
elements expire and Context methods are inert: read IsDisabled into a
local during the build, keep a ui.Handle for Focus, use c.Services()
for clipboard access, create ui.Local state during the build.
- Add core.AutoFocus: v0.5.5 records an overlay's opener after its
content builds, so same-frame autofocus lost focus restore on close.
It focuses one frame later; cascader rows defer their focus likewise.
- core.OverlayLayer identifies build passes by the root element, not
c.Now(), which repeats across passes and under the Tester clock.
- Context menus survive the opening press counted as PressedOutside;
split button tooltip no longer reorders its open menu; disabled
menubar rows pass presses to the panel; Form defers blur-started
async checks so the growing message no longer moves Submit away
from the release; Combobox drops its disabled copy now that MyGo
blocks typing under a disabled wrapper (canary flipped to a guard).
- The Tester clock is virtual: tests use Advance/Now instead of
time.Sleep/time.Now and read element state inside the view. The web
mirror advances the clock to wall time before each frame and input.
- AGENTS.md and docs describe the value Element API and the new pin.
Snapshot goldens are darwin-only and were not re-verified here.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
80 lines
2.7 KiB
Go
80 lines
2.7 KiB
Go
package media
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/core"
|
|
)
|
|
|
|
// LoopMode is how playback repeats. The zero value repeats nothing.
|
|
type LoopMode int
|
|
|
|
const (
|
|
LoopOff LoopMode = iota
|
|
LoopAll
|
|
LoopOne
|
|
)
|
|
|
|
// next cycles off, all, one.
|
|
func (m LoopMode) next() LoopMode { return (m + 1) % 3 }
|
|
|
|
// MediaControlsState is what the caller's playback reports.
|
|
type MediaControlsState struct {
|
|
Playing bool
|
|
Loop LoopMode
|
|
Shuffle bool
|
|
// NoPrevious and NoNext disable those buttons at the ends.
|
|
NoPrevious, NoNext bool
|
|
}
|
|
|
|
// MediaControlsOptions are the callbacks; every one is required.
|
|
type MediaControlsOptions struct {
|
|
OnPrevious, OnPlay, OnPause, OnNext func()
|
|
OnLoop func(LoopMode)
|
|
OnShuffle func(bool)
|
|
Disabled bool
|
|
}
|
|
|
|
// MediaControls are previous, play, next, loop and shuffle buttons.
|
|
func MediaControls(c *ui.Context, st MediaControlsState, opts MediaControlsOptions) ui.Element {
|
|
if opts.OnPrevious == nil || opts.OnPlay == nil || opts.OnPause == nil || opts.OnNext == nil || opts.OnLoop == nil || opts.OnShuffle == nil {
|
|
panic("mujica: MediaControls needs every callback")
|
|
}
|
|
if st.Loop < LoopOff || st.Loop > LoopOne {
|
|
panic(fmt.Sprintf("mujica: unknown LoopMode %d", st.Loop))
|
|
}
|
|
row := ui.Row(c).Gap(core.Density(c).Unit()).AlignItems(ui.Center).FocusGroup(ui.Horizontal).Role(ui.RoleToolbar).
|
|
Label(core.Msg(c, "media_controls.label")).Disabled(opts.Disabled)
|
|
row.Children(func() {
|
|
shuffle := MediaToggleAction(c, core.MediaShuffle, core.Msg(c, "media_controls.shuffle"), st.Shuffle)
|
|
if shuffle.Clicked() && !shuffle.IsDisabled() {
|
|
slog.Debug("mujica: media_controls shuffle", "on", !st.Shuffle)
|
|
opts.OnShuffle(!st.Shuffle)
|
|
}
|
|
prev := core.IconAction(c, core.MediaPrev, core.Msg(c, "media_controls.previous")).Disabled(st.NoPrevious)
|
|
if prev.Clicked() && !prev.IsDisabled() {
|
|
slog.Debug("mujica: media_controls previous")
|
|
opts.OnPrevious()
|
|
}
|
|
mediaPlayToggle(c, st.Playing, opts.OnPlay, opts.OnPause)
|
|
next := core.IconAction(c, core.MediaNext, core.Msg(c, "media_controls.next")).Disabled(st.NoNext)
|
|
if next.Clicked() && !next.IsDisabled() {
|
|
slog.Debug("mujica: media_controls next")
|
|
opts.OnNext()
|
|
}
|
|
icon := core.MediaRepeat
|
|
if st.Loop == LoopOne {
|
|
icon = core.MediaRepeatOne
|
|
}
|
|
loop := MediaToggleAction(c, icon, core.Msg(c, []string{"media_controls.loopOff", "media_controls.loopAll", "media_controls.loopOne"}[st.Loop]), st.Loop != LoopOff)
|
|
if loop.Clicked() && !loop.IsDisabled() {
|
|
slog.Debug("mujica: media_controls loop", "mode", st.Loop.next())
|
|
opts.OnLoop(st.Loop.next())
|
|
}
|
|
})
|
|
return row
|
|
}
|