Files
MujicaUI/media/media_controls.go
T
ZacharyZhang-NYandClaude Opus 5.5 bcce34cda1 Upgrade MyGo to v0.5.5
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>
2026-10-17 01:37:54 -04:00

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
}