diff --git a/debugger/commands.go b/debugger/commands.go
index d487dde6..6ac4713b 100644
--- a/debugger/commands.go
+++ b/debugger/commands.go
@@ -277,7 +277,7 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) error {
return fmt.Errorf("unknown STEP BACK mode (%s)", mode)
}
- dbg.setState(govern.Rewinding)
+ dbg.setState(govern.Rewinding, govern.RewindingBackwards)
dbg.unwindLoop(func() error {
dbg.catchupContext = catchupStepBack
return dbg.Rewind.GotoCoords(coords)
@@ -401,20 +401,27 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) error {
dbg.runUntilHalt = false
if arg == "LAST" {
- dbg.setState(govern.Rewinding)
+ dbg.setState(govern.Rewinding, govern.RewindingForwards)
dbg.unwindLoop(dbg.Rewind.GotoLast)
} else if arg == "SUMMARY" {
dbg.printLine(terminal.StyleInstrument, dbg.Rewind.Peephole())
} else {
frame, _ := strconv.Atoi(arg)
- dbg.setState(govern.Rewinding)
- dbg.unwindLoop(func() error {
- err := dbg.Rewind.GotoFrame(frame)
- if err != nil {
- return err
+ coords := dbg.TV().GetCoords()
+ if frame != coords.Frame {
+ if frame < coords.Frame {
+ dbg.setState(govern.Rewinding, govern.RewindingBackwards)
+ } else {
+ dbg.setState(govern.Rewinding, govern.RewindingForwards)
}
- return nil
- })
+ dbg.unwindLoop(func() error {
+ err := dbg.Rewind.GotoFrame(frame)
+ if err != nil {
+ return err
+ }
+ return nil
+ })
+ }
}
return nil
}
@@ -437,21 +444,26 @@ func (dbg *Debugger) processTokens(tokens *commandline.Tokens) error {
}
case cmdGoto:
- coords := dbg.vcs.TV.GetCoords()
+ fromCoords := dbg.vcs.TV.GetCoords()
+ toCoords := fromCoords
if s, ok := tokens.Get(); ok {
- coords.Clock, _ = strconv.Atoi(s)
+ toCoords.Clock, _ = strconv.Atoi(s)
if s, ok := tokens.Get(); ok {
- coords.Scanline, _ = strconv.Atoi(s)
+ toCoords.Scanline, _ = strconv.Atoi(s)
if s, ok := tokens.Get(); ok {
- coords.Frame, _ = strconv.Atoi(s)
+ toCoords.Frame, _ = strconv.Atoi(s)
}
}
}
- dbg.setState(govern.Rewinding)
+ if coords.GreaterThan(toCoords, fromCoords) {
+ dbg.setState(govern.Rewinding, govern.RewindingForwards)
+ } else {
+ dbg.setState(govern.Rewinding, govern.RewindingBackwards)
+ }
dbg.unwindLoop(func() error {
- err := dbg.Rewind.GotoCoords(coords)
+ err := dbg.Rewind.GotoCoords(toCoords)
if err != nil {
return err
}
diff --git a/debugger/debugger.go b/debugger/debugger.go
index 5a29969e..500d691c 100644
--- a/debugger/debugger.go
+++ b/debugger/debugger.go
@@ -87,7 +87,8 @@ type Debugger struct {
// state is an atomic value because we need to be able to read it from the
// GUI thread (see State() function)
- state atomic.Value // emulation.State
+ state atomic.Value // emulation.State
+ subState atomic.Value // emulation.RewindingSubState
// preferences for the emulation
Prefs *Preferences
@@ -333,10 +334,10 @@ func NewDebugger(opts CommandLineOptions, create CreateUserInterface) (*Debugger
readEventsPulse: time.NewTicker(1 * time.Millisecond),
}
- // emulator is starting in the "none" mode (the advangatge of this is that
- // we get to set the underlying type of the atomic.Value early before
- // anyone has a change to call State() or Mode() from another thread)
+ // set atomics to defaults values. if we don't do this we can cause panics
+ // due to the GUI asking for values before we've had a chance to set them
dbg.state.Store(govern.EmulatorStart)
+ dbg.subState.Store(govern.RewindingBackwards)
dbg.mode.Store(govern.ModeNone)
dbg.quantum.Store(govern.QuantumInstruction)
@@ -526,19 +527,26 @@ func (dbg *Debugger) State() govern.State {
return dbg.state.Load().(govern.State)
}
+// SubState implements the emulation.Emulation interface.
+func (dbg *Debugger) SubState() govern.SubState {
+ return dbg.subState.Load().(govern.SubState)
+}
+
// Mode implements the emulation.Emulation interface.
func (dbg *Debugger) Mode() govern.Mode {
return dbg.mode.Load().(govern.Mode)
}
// set the emulation state
-func (dbg *Debugger) setState(state govern.State) {
- dbg.setStateQuiet(state, false)
-}
+func (dbg *Debugger) setState(state govern.State, subState govern.SubState) {
+ // intentionally panic if state/sub-state combination is not allowed
+ if !govern.StateIntegrity(state, subState) {
+ panic(fmt.Sprintf("illegal sub-state (%s) for %s state (prev state: %s)",
+ subState, state,
+ dbg.state.Load().(govern.State),
+ ))
+ }
-// same as setState but with quiet argument, to indicate that EmulationEvent
-// should not be issued to the gui.
-func (dbg *Debugger) setStateQuiet(state govern.State, quiet bool) {
if state == govern.Rewinding {
dbg.endPlayback()
dbg.endRecording()
@@ -561,30 +569,8 @@ func (dbg *Debugger) setStateQuiet(state govern.State, quiet bool) {
}
dbg.CoProcDev.SetEmulationState(state)
- prevState := dbg.State()
dbg.state.Store(state)
-
- if !quiet && dbg.Mode() == govern.ModePlay {
- switch state {
- case govern.Initialising:
- err := dbg.gui.SetFeature(gui.ReqEmulationNotify, notifications.NotifyInitialising)
- if err != nil {
- logger.Log("debugger", err.Error())
- }
- case govern.Paused:
- err := dbg.gui.SetFeature(gui.ReqEmulationNotify, notifications.NotifyPause)
- if err != nil {
- logger.Log("debugger", err.Error())
- }
- case govern.Running:
- if prevState > govern.Initialising {
- err := dbg.gui.SetFeature(gui.ReqEmulationNotify, notifications.NotifyRun)
- if err != nil {
- logger.Log("debugger", err.Error())
- }
- }
- }
- }
+ dbg.subState.Store(subState)
}
// set the emulation mode
@@ -649,14 +635,14 @@ func (dbg *Debugger) setMode(mode govern.Mode) error {
return fmt.Errorf("debugger: %w", err)
}
} else {
- dbg.setState(govern.Running)
+ dbg.setState(govern.Running, govern.Normal)
}
case govern.ModeDebugger:
dbg.vcs.TV.AddFrameTrigger(dbg.Rewind)
dbg.vcs.TV.AddFrameTrigger(dbg.ref)
dbg.vcs.TV.AddFrameTrigger(dbg.counter)
- dbg.setState(govern.Paused)
+ dbg.setState(govern.Paused, govern.Normal)
// debugger needs knowledge about previous frames (via the reflector)
// if we're moving from playmode. also we want to make sure we end on
@@ -1098,12 +1084,12 @@ func (dbg *Debugger) Notify(notice notifications.Notice) error {
dbg.vcs.Mem.Poke(supercharger.MutliloadByteAddress, uint8(dbg.opts.Multiload))
}
- err := dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifySuperchargerSoundloadStarted)
+ err := dbg.gui.SetFeature(gui.ReqNotification, notifications.NotifySuperchargerSoundloadStarted)
if err != nil {
return err
}
case notifications.NotifySuperchargerSoundloadEnded:
- err := dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifySuperchargerSoundloadEnded)
+ err := dbg.gui.SetFeature(gui.ReqNotification, notifications.NotifySuperchargerSoundloadEnded)
if err != nil {
return err
}
@@ -1118,23 +1104,20 @@ func (dbg *Debugger) Notify(notice notifications.Notice) error {
return dbg.vcs.TV.Reset(true)
case notifications.NotifySuperchargerSoundloadRewind:
- err := dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifySuperchargerSoundloadRewind)
+ err := dbg.gui.SetFeature(gui.ReqNotification, notifications.NotifySuperchargerSoundloadRewind)
if err != nil {
return err
}
- case notifications.NotifyPlusROMInserted:
- if dbg.vcs.Env.Prefs.PlusROM.NewInstallation {
- err := dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifyPlusROMNewInstallation)
- if err != nil {
- return fmt.Errorf(err.Error())
- }
+ case notifications.NotifyPlusROMNewInstall:
+ err := dbg.gui.SetFeature(gui.ReqNotification, notifications.NotifyPlusROMNewInstall)
+ if err != nil {
+ return err
}
case notifications.NotifyPlusROMNetwork:
- err := dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifyPlusROMNetwork)
+ err := dbg.gui.SetFeature(gui.ReqNotification, notifications.NotifyPlusROMNetwork)
if err != nil {
return err
}
-
case notifications.NotifyMovieCartStarted:
return dbg.vcs.TV.Reset(true)
default:
@@ -1166,19 +1149,19 @@ func (dbg *Debugger) attachCartridge(cartload cartridgeloader.Loader) (e error)
dbg.bots.Quit()
// attching a cartridge implies the initialise state
- dbg.setState(govern.Initialising)
+ dbg.setState(govern.Initialising, govern.Normal)
// set state after initialisation according to the emulation mode
defer func() {
switch dbg.Mode() {
case govern.ModeDebugger:
if dbg.runUntilHalt && e == nil {
- dbg.setState(govern.Running)
+ dbg.setState(govern.Running, govern.Normal)
} else {
- dbg.setState(govern.Paused)
+ dbg.setState(govern.Paused, govern.Normal)
}
case govern.ModePlay:
- dbg.setState(govern.Running)
+ dbg.setState(govern.Running, govern.Normal)
}
}()
@@ -1227,23 +1210,13 @@ func (dbg *Debugger) attachCartridge(cartload cartridgeloader.Loader) (e error)
if err != nil {
logger.Logf("debugger", err.Error())
if errors.Is(err, coproc_dwarf.UnsupportedDWARF) {
- err = dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifyUnsupportedDWARF)
+ err = dbg.gui.SetFeature(gui.ReqNotification, notifications.NotifyUnsupportedDWARF)
if err != nil {
logger.Logf("debugger", err.Error())
}
}
}
- // notify GUI of coprocessor state
- if dbg.CoProcDev.HasSource() {
- err = dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifyCoprocDevStarted)
- } else {
- err = dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifyCoprocDevEnded)
- }
- if err != nil {
- logger.Logf("debugger", err.Error())
- }
-
// attach current debugger as the yield hook for cartridge
dbg.vcs.Mem.Cart.SetYieldHook(dbg)
@@ -1386,15 +1359,12 @@ func (dbg *Debugger) endComparison() {
func (dbg *Debugger) hotload() (e error) {
// tell GUI that we're in the initialistion phase
- dbg.setState(govern.Initialising)
+ dbg.setState(govern.Initialising, govern.Normal)
defer func() {
if dbg.runUntilHalt && e == nil {
- dbg.setState(govern.Running)
+ dbg.setState(govern.Running, govern.Normal)
} else {
- err := dbg.gui.SetFeature(gui.ReqEmulationNotify, notifications.NotifyPause)
- if err != nil {
- logger.Log("debugger", err.Error())
- }
+ dbg.setState(govern.Paused, govern.Normal)
}
}()
@@ -1472,14 +1442,14 @@ func (dbg *Debugger) Plugged(port plugging.PortID, peripheral plugging.Periphera
if dbg.vcs.Mem.Cart.IsEjected() {
return
}
- err := dbg.gui.SetFeature(gui.ReqPeripheralNotify, port, peripheral)
+ err := dbg.gui.SetFeature(gui.ReqPeripheralPlugged, port, peripheral)
if err != nil {
logger.Log("debugger", err.Error())
}
}
func (dbg *Debugger) reloadCartridge() error {
- dbg.setState(govern.Initialising)
+ dbg.setState(govern.Initialising, govern.Normal)
spec := dbg.vcs.TV.GetFrameInfo().Spec.ID
err := dbg.insertCartridge("")
@@ -1534,7 +1504,7 @@ func (dbg *Debugger) insertCartridge(filename string) error {
// insertCartridge() for that
func (dbg *Debugger) InsertCartridge(filename string) {
dbg.PushFunctionImmediate(func() {
- dbg.setState(govern.Initialising)
+ dbg.setState(govern.Initialising, govern.Normal)
dbg.unwindLoop(func() error {
return dbg.insertCartridge(filename)
})
diff --git a/debugger/deeppoke.go b/debugger/deeppoke.go
index 1b324840..e992e148 100644
--- a/debugger/deeppoke.go
+++ b/debugger/deeppoke.go
@@ -47,7 +47,7 @@ func (dbg *Debugger) PushDeepPoke(addr uint16, value uint8, newValue uint8, valu
}
dbg.PushFunctionImmediate(func() {
- dbg.setStateQuiet(govern.Rewinding, true)
+ dbg.setState(govern.Rewinding, govern.Normal)
dbg.unwindLoop(doDeepPoke)
})
diff --git a/debugger/govern/state.go b/debugger/govern/state.go
index 0a7c4cad..dda8fead 100644
--- a/debugger/govern/state.go
+++ b/debugger/govern/state.go
@@ -26,13 +26,7 @@ type State int
// Initialising can be used when reinitialising the emulator. for example, when
// a new cartridge is being inserted.
//
-// Values are ordered so that order comparisons are meaningful. For example,
-// Running is "greater than" Stepping, Paused, etc.
-//
-// Note that there is a sub-state of the rewinding state that we can potentially
-// think of as the "catch-up" state. This occurs in the brief transition period
-// between Rewinding and the Running or Pausing state. For simplicity, the
-// catch-up loop is part of the Rewinding state
+// Paused and Rewinding can have meaningful sub-states
const (
EmulatorStart State = iota
Initialising
@@ -63,3 +57,58 @@ func (s State) String() string {
return ""
}
+
+// SubState allows more detail for some states. NoSubState indicates that there
+// is not more information to impart about the state
+type SubState int
+
+// List of possible rewinding sub states
+const (
+ Normal SubState = iota
+ RewindingBackwards
+ RewindingForwards
+ PausedAtStart
+ PausedAtEnd
+)
+
+func (s SubState) String() string {
+ switch s {
+ case RewindingBackwards:
+ return "Backwards"
+ case RewindingForwards:
+ return "Forwards"
+ case PausedAtStart:
+ return "Paused at start"
+ case PausedAtEnd:
+ return "Paused at end"
+ }
+ return ""
+}
+
+// StateIntegrity checks whether the combination of state, sub-state makes
+// sense. The previous state is also required for a complete check.
+//
+// Rules:
+//
+// 1. NoSubState can coexist with any state
+//
+// 2. PausedAtStart and PausedAtEnd can only be paired with the Paused State
+//
+// 3. RewindingBackwards and RewindingForwards can only be paired with the
+// Rewinding state
+func StateIntegrity(state State, subState SubState) bool {
+ if subState == Normal {
+ return true
+ }
+ switch state {
+ case Rewinding:
+ if subState == RewindingBackwards || subState == RewindingForwards {
+ return true
+ }
+ case Paused:
+ if subState == PausedAtEnd || subState == PausedAtStart {
+ return true
+ }
+ }
+ return false
+}
diff --git a/debugger/loop_catchup.go b/debugger/loop_catchup.go
index 26d87d23..d5aff164 100644
--- a/debugger/loop_catchup.go
+++ b/debugger/loop_catchup.go
@@ -79,7 +79,7 @@ func (dbg *Debugger) CatchUpLoop(tgt coords.TelevisionCoords) error {
dbg.vcs.TV.SetFPSCap(fpsCap)
dbg.catchupContinue = nil
dbg.catchupEnd = nil
- dbg.setState(govern.Paused)
+ dbg.setState(govern.Paused, govern.Normal)
dbg.runUntilHalt = false
dbg.continueEmulation = dbg.catchupEndAdj
dbg.catchupEndAdj = false
diff --git a/debugger/loop_debugger.go b/debugger/loop_debugger.go
index 5047313d..8b2df38d 100644
--- a/debugger/loop_debugger.go
+++ b/debugger/loop_debugger.go
@@ -290,7 +290,7 @@ func (dbg *Debugger) inputLoop(inputter terminal.Input, nonInstructionQuantum bo
}
// set pause emulation state
- dbg.setState(govern.Paused)
+ dbg.setState(govern.Paused, govern.Normal)
// take note of current machine state if the emulation was in a running
// state and is halting just now
@@ -358,10 +358,10 @@ func (dbg *Debugger) inputLoop(inputter terminal.Input, nonInstructionQuantum bo
// stepping.
if dbg.halting.volatileTraps.isEmpty() {
if inputter.IsInteractive() {
- dbg.setState(govern.Running)
+ dbg.setState(govern.Running, govern.Normal)
}
} else {
- dbg.setState(govern.Stepping)
+ dbg.setState(govern.Stepping, govern.Normal)
}
// update comparison point before execution continues
@@ -369,7 +369,7 @@ func (dbg *Debugger) inputLoop(inputter terminal.Input, nonInstructionQuantum bo
dbg.Rewind.UpdateComparison()
}
} else if inputter.IsInteractive() {
- dbg.setState(govern.Stepping)
+ dbg.setState(govern.Stepping, govern.Normal)
}
}
diff --git a/debugger/push.go b/debugger/push.go
index 8234962a..ed560949 100644
--- a/debugger/push.go
+++ b/debugger/push.go
@@ -55,9 +55,9 @@ func (dbg *Debugger) PushSetPause(paused bool) {
case govern.ModePlay:
dbg.PushFunction(func() {
if paused {
- dbg.setState(govern.Paused)
+ dbg.setState(govern.Paused, govern.Normal)
} else {
- dbg.setState(govern.Running)
+ dbg.setState(govern.Running, govern.Normal)
}
})
case govern.ModeDebugger:
diff --git a/debugger/rewind.go b/debugger/rewind.go
index a9aee6fe..1d0e20cc 100644
--- a/debugger/rewind.go
+++ b/debugger/rewind.go
@@ -22,53 +22,37 @@ import (
"fmt"
"github.com/jetsetilly/gopher2600/debugger/govern"
- "github.com/jetsetilly/gopher2600/gui"
"github.com/jetsetilly/gopher2600/hardware/television/coords"
- "github.com/jetsetilly/gopher2600/logger"
- "github.com/jetsetilly/gopher2600/notifications"
)
// RewindByAmount moves forwards or backwards by specified frames. Negative
// numbers indicate backwards
-func (dbg *Debugger) RewindByAmount(amount int) bool {
+func (dbg *Debugger) RewindByAmount(amount int) {
switch dbg.Mode() {
case govern.ModePlay:
coords := dbg.vcs.TV.GetCoords()
tl := dbg.Rewind.GetTimeline()
- if amount < 0 && coords.Frame-1 <= tl.AvailableStart {
- dbg.setStateQuiet(govern.Paused, true)
- err := dbg.gui.SetFeature(gui.ReqEmulationNotify, notifications.NotifyRewindAtStart)
- if err != nil {
- logger.Log("debugger", err.Error())
- }
- return false
- }
-
- if amount > 0 && coords.Frame+1 >= tl.AvailableEnd {
- dbg.setStateQuiet(govern.Paused, true)
- err := dbg.gui.SetFeature(gui.ReqEmulationNotify, notifications.NotifyRewindAtEnd)
- if err != nil {
- logger.Log("debugger", err.Error())
- }
- return false
- }
-
- dbg.setStateQuiet(govern.Rewinding, true)
- dbg.Rewind.GotoFrame(coords.Frame + amount)
- dbg.setStateQuiet(govern.Paused, true)
-
- var err error
if amount < 0 {
- err = dbg.gui.SetFeature(gui.ReqEmulationNotify, notifications.NotifyRewindBack)
- } else {
- err = dbg.gui.SetFeature(gui.ReqEmulationNotify, notifications.NotifyRewindFoward)
- }
- if err != nil {
- logger.Log("debugger", err.Error())
+ if coords.Frame-1 < tl.AvailableStart {
+ dbg.setState(govern.Paused, govern.PausedAtStart)
+ return
+ }
+ dbg.setState(govern.Rewinding, govern.RewindingBackwards)
}
- return true
+ if amount > 0 {
+ if coords.Frame+1 > tl.AvailableEnd {
+ dbg.setState(govern.Paused, govern.PausedAtEnd)
+ return
+ }
+ dbg.setState(govern.Rewinding, govern.RewindingForwards)
+ }
+
+ dbg.Rewind.GotoFrame(coords.Frame + amount)
+ dbg.setState(govern.Paused, govern.Normal)
+
+ return
}
panic(fmt.Sprintf("Rewind: unsupported mode (%v)", dbg.Mode()))
@@ -107,7 +91,11 @@ func (dbg *Debugger) RewindToFrame(fn int, last bool) bool {
dbg.PushFunctionImmediate(func() {
// set state to govern.Rewinding as soon as possible (but
// remembering that we must do it in the debugger goroutine)
- dbg.setState(govern.Rewinding)
+ if fn > dbg.vcs.TV.GetCoords().Frame {
+ dbg.setState(govern.Rewinding, govern.RewindingForwards)
+ } else {
+ dbg.setState(govern.Rewinding, govern.RewindingBackwards)
+ }
dbg.unwindLoop(doRewind)
})
@@ -118,7 +106,7 @@ func (dbg *Debugger) RewindToFrame(fn int, last bool) bool {
}
// GotoCoords rewinds the emulation to the specified coordinates.
-func (dbg *Debugger) GotoCoords(coords coords.TelevisionCoords) bool {
+func (dbg *Debugger) GotoCoords(toCoords coords.TelevisionCoords) bool {
switch dbg.Mode() {
case govern.ModeDebugger:
if dbg.State() == govern.Rewinding {
@@ -130,7 +118,7 @@ func (dbg *Debugger) GotoCoords(coords coords.TelevisionCoords) bool {
// upate catchup context before starting rewind process
dbg.catchupContext = catchupGotoCoords
- err := dbg.Rewind.GotoCoords(coords)
+ err := dbg.Rewind.GotoCoords(toCoords)
if err != nil {
return err
}
@@ -143,7 +131,13 @@ func (dbg *Debugger) GotoCoords(coords coords.TelevisionCoords) bool {
dbg.PushFunctionImmediate(func() {
// set state to govern.Rewinding as soon as possible (but
// remembering that we must do it in the debugger goroutine)
- dbg.setState(govern.Rewinding)
+
+ fromCoords := dbg.vcs.TV.GetCoords()
+ if coords.GreaterThan(toCoords, fromCoords) {
+ dbg.setState(govern.Rewinding, govern.RewindingForwards)
+ } else {
+ dbg.setState(govern.Rewinding, govern.RewindingBackwards)
+ }
dbg.unwindLoop(doRewind)
})
@@ -168,7 +162,7 @@ func (dbg *Debugger) RerunLastNFrames(frames int) bool {
// if we're in between instruction boundaries therefore we need to push a
// GotoCoords() request. get the current coordinates now
correctCoords := !dbg.liveDisasmEntry.Result.Final
- coords := dbg.vcs.TV.GetCoords()
+ toCoords := dbg.vcs.TV.GetCoords()
// the function to push to the debugger/emulation routine
doRewind := func() error {
@@ -178,7 +172,7 @@ func (dbg *Debugger) RerunLastNFrames(frames int) bool {
}
if correctCoords {
- err = dbg.Rewind.GotoCoords(coords)
+ err = dbg.Rewind.GotoCoords(toCoords)
if err != nil {
return err
}
@@ -195,7 +189,7 @@ func (dbg *Debugger) RerunLastNFrames(frames int) bool {
// set state to govern.Rewinding as soon as possible (but
// remembering that we must do it in the debugger goroutine)
- dbg.setState(govern.Rewinding)
+ dbg.setState(govern.Rewinding, govern.Normal)
dbg.unwindLoop(doRewind)
})
diff --git a/gui/fonts/fonts.go b/gui/fonts/fonts.go
index d0633239..bb74d316 100644
--- a/gui/fonts/fonts.go
+++ b/gui/fonts/fonts.go
@@ -46,8 +46,8 @@ const (
EmulationRun = '\uf04b'
EmulationRewindBack = '\uf04a'
EmulationRewindForward = '\uf04e'
- EmulationRewindAtStart = '\uf049'
- EmulationRewindAtEnd = '\uf050'
+ EmulationPausedAtStart = '\uf049'
+ EmulationPausedAtEnd = '\uf050'
MusicNote = '\uf001'
VolumeRising = '\uf062'
VolumeFalling = '\uf063'
diff --git a/gui/gui.go b/gui/gui.go
index 3a311eed..9565bf25 100644
--- a/gui/gui.go
+++ b/gui/gui.go
@@ -47,16 +47,12 @@ const (
// the size of the monitor).
ReqFullScreen FeatureReq = "ReqFullScreen" // bool
- // an event generated by the emulation has occured. for example, the
- // emulation has been paused.
- ReqEmulationNotify FeatureReq = "ReqEmulationNotify" // notifications.Notify
-
// an event generated by the cartridge has occured. for example, network
// activity from a PlusROM cartridge.
- ReqCartridgeNotify FeatureReq = "ReqCartridgeNotify" // notifications.Notify
+ ReqNotification FeatureReq = "ReqNotification" // notifications.Notify
- // peripheral has changed for one of the ports.
- ReqPeripheralNotify FeatureReq = "ReqPeripheralNotify" // plugging.PortID, plugging.PeripheralID
+ // peripheral has been changed for one of the ports.
+ ReqPeripheralPlugged FeatureReq = "ReqPeripheralPlugging" // plugging.PortID, plugging.PeripheralID
// open ROM selector.
ReqROMSelector FeatureReq = "ReqROMSelector" // nil
diff --git a/gui/sdlimgui/playscr.go b/gui/sdlimgui/playscr.go
index cbd00829..2065b7c6 100644
--- a/gui/sdlimgui/playscr.go
+++ b/gui/sdlimgui/playscr.go
@@ -16,11 +16,9 @@
package sdlimgui
import (
- "fmt"
"time"
"github.com/inkyblackness/imgui-go/v4"
- "github.com/jetsetilly/gopher2600/gui/fonts"
"github.com/jetsetilly/gopher2600/hardware/television/specification"
)
@@ -52,32 +50,20 @@ type playScr struct {
// number of scanlines in current image. taken from screen but is crit section safe
visibleScanlines int
- // fps overlay
- fpsPulse *time.Ticker
- fps string
- hz string
-
- // controller notifications
- peripheralLeft peripheralNotification
- peripheralRight peripheralNotification
-
- // emulation notifications
- emulationNotice emulationEventNotification
-
- // cartridge notifications
- cartridgeNotice cartridgeEventNotification
+ // overlay for play screen
+ overlay playscrOverlay
}
func newPlayScr(img *SdlImgui) *playScr {
win := &playScr{
- img: img,
- scr: img.screen,
- fpsPulse: time.NewTicker(time.Second),
- fps: "waiting",
- peripheralRight: peripheralNotification{
- rightAlign: true,
+ img: img,
+ scr: img.screen,
+ overlay: playscrOverlay{
+ fpsPulse: time.NewTicker(time.Second),
+ fps: "waiting",
},
}
+ win.overlay.playscr = win
// set texture, creation of textures will be done after every call to resize()
// clamp is important for LINEAR filtering. not noticeable for NEAREST filtering
@@ -99,94 +85,7 @@ func (win *playScr) draw() {
dl := imgui.BackgroundDrawList()
dl.AddImage(imgui.TextureID(win.displayTexture.getID()), win.imagePosMin, win.imagePosMax)
- win.peripheralLeft.draw(win)
- win.peripheralRight.draw(win)
- win.cartridgeNotice.draw(win)
-
- if !win.drawFPS() {
- win.emulationNotice.draw(win, false)
- }
-}
-
-func (win *playScr) toggleFPS() {
- fps := win.img.prefs.fpsOverlay.Get().(bool)
- win.img.prefs.fpsOverlay.Set(!fps)
-}
-
-func (win *playScr) drawFPS() bool {
- if !win.img.prefs.fpsOverlay.Get().(bool) {
- return false
- }
-
- // update fps
- select {
- case <-win.fpsPulse.C:
- fps, hz := win.img.dbg.VCS().TV.GetActualFPS()
- win.fps = fmt.Sprintf("%03.2f fps", fps)
- win.hz = fmt.Sprintf("%03.2fhz", hz)
- default:
- }
-
- imgui.SetNextWindowPos(imgui.Vec2{0, 0})
-
- imgui.PushStyleColor(imgui.StyleColorWindowBg, win.img.cols.Transparent)
- imgui.PushStyleColor(imgui.StyleColorBorder, win.img.cols.Transparent)
-
- fpsOpen := true
- imgui.BeginV("##playscrfps", &fpsOpen, imgui.WindowFlagsAlwaysAutoResize|
- imgui.WindowFlagsNoScrollbar|imgui.WindowFlagsNoTitleBar|
- imgui.WindowFlagsNoDecoration|imgui.WindowFlagsNoSavedSettings|
- imgui.WindowFlagsNoBringToFrontOnFocus)
-
- imgui.Text(fmt.Sprintf("Emulation: %s", win.fps))
- fr := imgui.CurrentIO().Framerate()
- if fr == 0.0 {
- imgui.Text("Rendering: waiting")
- } else {
- imgui.Text(fmt.Sprintf("Rendering: %03.2f fps", fr))
- }
-
- imguiSeparator()
-
- if coproc := win.img.cache.VCS.Mem.Cart.GetCoProc(); coproc != nil {
- clk := float32(win.img.dbg.VCS().Env.Prefs.ARM.Clock.Get().(float64))
- imgui.Text(fmt.Sprintf("%s Clock: %.0f Mhz", coproc.ProcessorID(), clk))
- imguiSeparator()
- }
-
- imgui.Text(fmt.Sprintf("%.1fx scaling", win.yscaling))
- imgui.Text(fmt.Sprintf("%d total scanlines", win.scr.crit.frameInfo.TotalScanlines))
-
- imguiSeparator()
-
- imgui.Text(win.img.screen.crit.frameInfo.Spec.ID)
- imgui.SameLine()
- imgui.Text(win.hz)
- if !win.scr.crit.frameInfo.VSync {
- imgui.SameLine()
- imgui.Text(string(fonts.NoVSYNC))
- }
-
- imguiSeparator()
- imgui.Text(fmt.Sprintf("%d frame input lag", win.scr.crit.frameQueueLen))
- if win.scr.nudgeIconCt > 0 {
- imgui.SameLine()
- imgui.Text(string(fonts.Nudge))
- }
-
- // if win.img.screen.crit.frameInfo.IsAtariSafe() {
- // imguiSeparator()
- // imgui.Text("atari safe")
- // }
-
- imgui.PopStyleColorV(2)
-
- imgui.Spacing()
- win.emulationNotice.draw(win, true)
-
- imgui.End()
-
- return true
+ win.overlay.draw()
}
// resize() implements the textureRenderer interface.
diff --git a/gui/sdlimgui/playscr_notifications.go b/gui/sdlimgui/playscr_notifications.go
deleted file mode 100644
index 4a4a1850..00000000
--- a/gui/sdlimgui/playscr_notifications.go
+++ /dev/null
@@ -1,381 +0,0 @@
-// This file is part of Gopher2600.
-//
-// Gopher2600 is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Gopher2600 is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Gopher2600. If not, see .
-
-package sdlimgui
-
-import (
- "fmt"
-
- "github.com/inkyblackness/imgui-go/v4"
- "github.com/jetsetilly/gopher2600/gui/fonts"
- "github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
- "github.com/jetsetilly/gopher2600/notifications"
-)
-
-const (
- notificationDurationPeripheral = 90
- notificationDurationCartridge = 60
- notificationDurationEmulation = 60
-)
-
-// peripheralNotification is used to draw an indicator on the screen for controller change events.
-type peripheralNotification struct {
- frames int
- icon string
- rightAlign bool
-}
-
-func (ntfy *peripheralNotification) set(peripheral plugging.PeripheralID) {
- ntfy.frames = notificationDurationPeripheral
- switch peripheral {
- case plugging.PeriphStick:
- ntfy.icon = fmt.Sprintf("%c", fonts.Stick)
- case plugging.PeriphPaddles:
- ntfy.icon = fmt.Sprintf("%c", fonts.Paddle)
- case plugging.PeriphKeypad:
- ntfy.icon = fmt.Sprintf("%c", fonts.Keypad)
- case plugging.PeriphSavekey:
- ntfy.icon = fmt.Sprintf("%c", fonts.Savekey)
- case plugging.PeriphGamepad:
- ntfy.icon = fmt.Sprintf("%c", fonts.Gamepad)
- case plugging.PeriphAtariVox:
- ntfy.icon = fmt.Sprintf("%c", fonts.AtariVox)
- default:
- ntfy.icon = ""
- return
- }
-}
-
-// pos should be the coordinate of the *extreme* bottom left or bottom right of
-// the playscr window. the values will be adjusted according to whether we're
-// display an icon or text.
-func (ntfy *peripheralNotification) draw(win *playScr) {
- if ntfy.frames <= 0 {
- return
- }
- ntfy.frames--
-
- if !win.img.prefs.controllerNotifcations.Get().(bool) {
- return
- }
-
- // position window so that it is fully visible at the bottom of the screen.
- // taking special care of the right aligned window
- var id string
- var pos imgui.Vec2
- dimen := win.img.plt.displaySize()
- if ntfy.rightAlign {
- pos = imgui.Vec2{dimen[0], dimen[1]}
- id = "##rightPeriphNotification"
- pos.X -= win.img.fonts.gopher2600IconsSize * 1.35
- } else {
- pos = imgui.Vec2{0, dimen[1]}
- id = "##leftPeriphNotification"
- pos.X += win.img.fonts.gopher2600IconsSize * 0.20
- }
- pos.Y -= win.img.fonts.gopher2600IconsSize * 1.35
-
- imgui.SetNextWindowPos(pos)
- imgui.PushStyleColor(imgui.StyleColorWindowBg, win.img.cols.Transparent)
- imgui.PushStyleColor(imgui.StyleColorBorder, win.img.cols.Transparent)
- defer imgui.PopStyleColorV(2)
-
- imgui.PushFont(win.img.fonts.gopher2600Icons)
- defer imgui.PopFont()
-
- a := float32(win.img.prefs.notificationVisibility.Get().(float64))
- imgui.PushStyleColor(imgui.StyleColorText, imgui.Vec4{a, a, a, a})
- defer imgui.PopStyleColor()
-
- periphOpen := true
- imgui.BeginV(id, &periphOpen, imgui.WindowFlagsAlwaysAutoResize|
- imgui.WindowFlagsNoScrollbar|imgui.WindowFlagsNoTitleBar|
- imgui.WindowFlagsNoDecoration|imgui.WindowFlagsNoSavedSettings)
-
- imgui.Text(ntfy.icon)
-
- imgui.End()
-}
-
-// emulationEventNotification is used to draw an indicator on the screen for
-// events defined in the emulation package.
-type emulationEventNotification struct {
- open bool
- frames int
-
- event notifications.Notice
- mute bool
-}
-
-func (ntfy *emulationEventNotification) set(event notifications.Notice) {
- switch event {
- default:
- ntfy.event = event
- ntfy.frames = notificationDurationEmulation
- ntfy.open = true
- case notifications.NotifyPause:
- ntfy.event = event
- ntfy.frames = 0
- ntfy.open = true
- case notifications.NotifyMute:
- ntfy.event = event
- ntfy.frames = 0
- ntfy.open = true
- ntfy.mute = true
- case notifications.NotifyUnmute:
- ntfy.event = event
- ntfy.frames = 0
- ntfy.open = true
- ntfy.mute = false
- }
-}
-
-func (ntfy *emulationEventNotification) tick() {
- if ntfy.frames <= 0 {
- return
- }
- ntfy.frames--
-
- if ntfy.frames == 0 {
- if ntfy.mute {
- ntfy.event = notifications.NotifyMute
- ntfy.open = true
- } else {
- ntfy.open = false
- }
- }
-}
-
-func (ntfy *emulationEventNotification) draw(win *playScr, hosted bool) {
- ntfy.tick()
- if !ntfy.open {
- return
- }
-
- if !hosted {
- imgui.SetNextWindowPos(imgui.Vec2{X: 10, Y: 10})
- imgui.PushStyleColor(imgui.StyleColorWindowBg, win.img.cols.Transparent)
- imgui.PushStyleColor(imgui.StyleColorBorder, win.img.cols.Transparent)
- defer imgui.PopStyleColorV(2)
-
- a := float32(win.img.prefs.notificationVisibility.Get().(float64))
- imgui.PushStyleColor(imgui.StyleColorText, imgui.Vec4{a, a, a, a})
- defer imgui.PopStyleColor()
-
- imgui.BeginV("##emulationNotification", &ntfy.open, imgui.WindowFlagsAlwaysAutoResize|
- imgui.WindowFlagsNoScrollbar|imgui.WindowFlagsNoTitleBar|
- imgui.WindowFlagsNoDecoration|imgui.WindowFlagsNoSavedSettings|
- imgui.WindowFlagsNoBringToFrontOnFocus)
- defer imgui.End()
-
- imgui.PushFont(win.img.fonts.veryLargeFontAwesome)
- defer imgui.PopFont()
- }
-
- switch ntfy.event {
- case notifications.NotifyInitialising:
- imgui.Text("")
- case notifications.NotifyPause:
- imgui.Text(string(fonts.EmulationPause))
- case notifications.NotifyRun:
- imgui.Text(string(fonts.EmulationRun))
- case notifications.NotifyRewindBack:
- imgui.Text(string(fonts.EmulationRewindBack))
- case notifications.NotifyRewindFoward:
- imgui.Text(string(fonts.EmulationRewindForward))
- case notifications.NotifyRewindAtStart:
- imgui.Text(string(fonts.EmulationRewindAtStart))
- case notifications.NotifyRewindAtEnd:
- imgui.Text(string(fonts.EmulationRewindAtEnd))
- case notifications.NotifyScreenshot:
- imgui.Text(string(fonts.Camera))
- default:
- if ntfy.mute && win.img.prefs.audioMuteNotification.Get().(bool) {
- imgui.Text(string(fonts.AudioMute))
- }
- }
-}
-
-// cartridgeEventNotification is used to draw an indicator on the screen for cartridge
-// events defined in the mapper package.
-type cartridgeEventNotification struct {
- open bool
- frames int
-
- event notifications.Notice
- coprocDev bool
-}
-
-func (ntfy *cartridgeEventNotification) set(event notifications.Notice) {
- switch event {
- case notifications.NotifySuperchargerSoundloadStarted:
- ntfy.event = event
- ntfy.frames = 0
- ntfy.open = true
- case notifications.NotifySuperchargerSoundloadEnded:
- ntfy.event = event
- ntfy.frames = notificationDurationCartridge
- ntfy.open = true
- case notifications.NotifySuperchargerSoundloadRewind:
- ntfy.event = event
- ntfy.frames = notificationDurationCartridge
- ntfy.open = true
- case notifications.NotifyPlusROMNetwork:
- ntfy.event = event
- ntfy.frames = notificationDurationCartridge
- ntfy.open = true
- case notifications.NotifyCoprocDevStarted:
- ntfy.coprocDev = true
- ntfy.frames = 0
- ntfy.open = true
- case notifications.NotifyCoprocDevEnded:
- ntfy.coprocDev = false
- ntfy.frames = 0
- ntfy.open = false
- }
-}
-
-func (ntfy *cartridgeEventNotification) tick() {
- if ntfy.frames <= 0 {
- return
- }
- ntfy.frames--
-
- if ntfy.frames == 0 {
- // always remain open if coprocessor development is active
- if ntfy.coprocDev {
- ntfy.open = true
- } else {
- switch ntfy.event {
- case notifications.NotifySuperchargerSoundloadRewind:
- ntfy.event = notifications.NotifySuperchargerSoundloadStarted
- default:
- ntfy.open = false
- }
- }
- }
-}
-
-func (ntfy *cartridgeEventNotification) draw(win *playScr) {
- ntfy.tick()
- if !ntfy.open {
- return
- }
-
- // notifications are made up of an icon and a sub-icon. icons must be from
- // the gopher2600Icons font and the sub-icon from the largeFontAwesome font
- icon := ""
- secondaryIcon := ""
-
- useGopherFont := false
-
- plusrom := false
- supercharger := false
- coprocDev := false
-
- switch win.cartridgeNotice.event {
- case notifications.NotifySuperchargerSoundloadStarted:
- supercharger = true
- useGopherFont = true
- icon = fmt.Sprintf("%c", fonts.Tape)
- secondaryIcon = fmt.Sprintf("%c", fonts.TapePlay)
- case notifications.NotifySuperchargerSoundloadEnded:
- supercharger = true
- useGopherFont = true
- icon = fmt.Sprintf("%c", fonts.Tape)
- secondaryIcon = fmt.Sprintf("%c", fonts.TapeStop)
- case notifications.NotifySuperchargerSoundloadRewind:
- supercharger = true
- useGopherFont = true
- icon = fmt.Sprintf("%c", fonts.Tape)
- secondaryIcon = fmt.Sprintf("%c", fonts.TapeRewind)
- case notifications.NotifyPlusROMNetwork:
- plusrom = true
- useGopherFont = true
- secondaryIcon = ""
- icon = fmt.Sprintf("%c", fonts.Wifi)
- default:
- if ntfy.coprocDev {
- coprocDev = true
- useGopherFont = false
- icon = fmt.Sprintf("%c", fonts.Developer)
- } else {
- return
- }
- }
-
- // check preferences and return if the notification is not to be displayed
- if plusrom && !win.img.prefs.plusromNotifications.Get().(bool) {
- return
- }
- if supercharger && !win.img.prefs.superchargerNotifications.Get().(bool) {
- return
- }
- if coprocDev && !win.img.prefs.coprocDevNotification.Get().(bool) {
- return
- }
-
- dimen := win.img.plt.displaySize()
- pos := imgui.Vec2{dimen[0], 0}
-
- width := win.img.fonts.gopher2600IconsSize * 1.5
- if secondaryIcon != "" {
- width += win.img.fonts.largeFontAwesomeSize * 1.5
- }
-
- // position is based on which font we're using
- if useGopherFont {
- imgui.PushFont(win.img.fonts.gopher2600Icons)
- pos.X -= win.img.fonts.gopher2600IconsSize * 1.2
- if secondaryIcon != "" {
- pos.X -= win.img.fonts.largeFontAwesomeSize * 2.0
- }
- } else {
- imgui.PushFont(win.img.fonts.veryLargeFontAwesome)
- pos.X -= win.img.fonts.veryLargeFontAwesomeSize
- pos.X -= 20
- pos.Y += 10
- }
- defer imgui.PopFont()
-
- imgui.SetNextWindowPos(pos)
- imgui.PushStyleColor(imgui.StyleColorWindowBg, win.img.cols.Transparent)
- imgui.PushStyleColor(imgui.StyleColorBorder, win.img.cols.Transparent)
- defer imgui.PopStyleColorV(2)
-
- a := float32(win.img.prefs.notificationVisibility.Get().(float64))
- imgui.PushStyleColor(imgui.StyleColorText, imgui.Vec4{a, a, a, a})
- defer imgui.PopStyleColor()
-
- imgui.BeginV("##cartridgeNotification", &ntfy.open, imgui.WindowFlagsAlwaysAutoResize|
- imgui.WindowFlagsNoScrollbar|imgui.WindowFlagsNoTitleBar|imgui.WindowFlagsNoDecoration)
-
- imgui.Text(icon)
-
- imgui.SameLine()
-
- if secondaryIcon != "" {
- // position sub-icon so that it is centered vertically with the main icon
- dim := imgui.CursorScreenPos()
- dim.Y += (win.img.fonts.gopher2600IconsSize - win.img.fonts.largeFontAwesomeSize) * 0.5
- imgui.SetCursorScreenPos(dim)
-
- imgui.PushFont(win.img.fonts.largeFontAwesome)
- imgui.Text(secondaryIcon)
- imgui.PopFont()
- }
-
- imgui.End()
-}
diff --git a/gui/sdlimgui/playscr_overlay.go b/gui/sdlimgui/playscr_overlay.go
new file mode 100644
index 00000000..26e962a6
--- /dev/null
+++ b/gui/sdlimgui/playscr_overlay.go
@@ -0,0 +1,481 @@
+// This file is part of Gopher2600.
+//
+// Gopher2600 is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Gopher2600 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Gopher2600. If not, see .
+
+package sdlimgui
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/inkyblackness/imgui-go/v4"
+ "github.com/jetsetilly/gopher2600/coprocessor/developer/dwarf"
+ "github.com/jetsetilly/gopher2600/debugger/govern"
+ "github.com/jetsetilly/gopher2600/gui/fonts"
+ "github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
+ "github.com/jetsetilly/gopher2600/notifications"
+)
+
+type overlayDuration int
+
+const (
+ overlayDurationPinned = -1
+ overlayDurationOff = 0
+ overlayDurationBrief = 30
+ overlayDurationShort = 60
+ overlayDurationLong = 90
+)
+
+// reduces the duration value. returns false if count has expired. if the
+// duration has been "pinned" then value will return true
+func (ct *overlayDuration) tick() bool {
+ if *ct == overlayDurationOff {
+ return false
+ }
+ if *ct == overlayDurationPinned {
+ return true
+ }
+ *ct = *ct - 1
+ return true
+}
+
+// returns true if duration is not off or pinned
+func (ct *overlayDuration) running() bool {
+ return *ct == overlayDurationPinned || *ct > overlayDurationOff
+}
+
+type playscrOverlay struct {
+ playscr *playScr
+
+ // fps information is updated on every pule of the time.Ticker. the fps and
+ // hz string is updated at that time and then displayed on every draw()
+ fpsPulse *time.Ticker
+ fps string
+ hz string
+
+ // top-left corner of the overlay includes emulation state. if the
+ // "fpsOverlay" is active then these will be drawn alongside the FPS
+ // information
+ state govern.State
+ subState govern.SubState
+ stateDur overlayDuration
+
+ // events are user-activated events and require immediate feedback
+ event notifications.Notice
+ eventDur overlayDuration
+
+ // icons in the top-left corner of the overlay are drawn according to a
+ // priority. the iconQueue list the icons to be drawn in order
+ iconQueue []rune
+
+ // top-right corner of the overlay
+ cartridge notifications.Notice
+ cartridgeDur overlayDuration
+
+ // bottom-left corner of the overlay
+ leftPort plugging.PeripheralID
+ leftPortDur overlayDuration
+
+ // bottom-right corner of the overlay
+ rightPort plugging.PeripheralID
+ rightPortDur overlayDuration
+
+ // visibility of icons is set from the preferences once per draw()
+ visibility float32
+}
+
+const overlayPadding = 10
+
+func (oly *playscrOverlay) set(v any, args ...any) {
+ switch n := v.(type) {
+ case plugging.PortID:
+ switch n {
+ case plugging.PortLeft:
+ oly.leftPort = args[0].(plugging.PeripheralID)
+ oly.leftPortDur = overlayDurationShort
+ case plugging.PortRight:
+ oly.rightPort = args[0].(plugging.PeripheralID)
+ oly.rightPortDur = overlayDurationShort
+ }
+ case notifications.Notice:
+ switch n {
+ case notifications.NotifySuperchargerSoundloadStarted:
+ oly.cartridge = n
+ oly.cartridgeDur = overlayDurationPinned
+ case notifications.NotifySuperchargerSoundloadEnded:
+ oly.cartridge = n
+ oly.cartridgeDur = overlayDurationShort
+ case notifications.NotifySuperchargerSoundloadRewind:
+ return
+
+ case notifications.NotifyPlusROMNetwork:
+ oly.cartridge = n
+ oly.cartridgeDur = overlayDurationShort
+
+ case notifications.NotifyScreenshot:
+ oly.event = n
+ oly.eventDur = overlayDurationShort
+
+ default:
+ return
+ }
+ }
+}
+
+func (oly *playscrOverlay) draw() {
+ imgui.PushStyleColor(imgui.StyleColorWindowBg, oly.playscr.img.cols.Transparent)
+ imgui.PushStyleColor(imgui.StyleColorBorder, oly.playscr.img.cols.Transparent)
+ defer imgui.PopStyleColorV(2)
+
+ imgui.PushStyleVarVec2(imgui.StyleVarWindowPadding, imgui.Vec2{})
+ defer imgui.PopStyleVarV(1)
+
+ imgui.SetNextWindowPos(imgui.Vec2{0, 0})
+ imgui.BeginV("##playscrOverlay", nil, imgui.WindowFlagsAlwaysAutoResize|
+ imgui.WindowFlagsNoScrollbar|imgui.WindowFlagsNoTitleBar|
+ imgui.WindowFlagsNoDecoration|imgui.WindowFlagsNoSavedSettings|
+ imgui.WindowFlagsNoBringToFrontOnFocus)
+ defer imgui.End()
+
+ oly.visibility = float32(oly.playscr.img.prefs.notificationVisibility.Get().(float64))
+
+ oly.drawTopLeft()
+ oly.drawTopRight()
+ oly.drawBottomLeft()
+ oly.drawBottomRight()
+}
+
+// information in the top left corner of the overlay are about the emulation.
+// eg. whether audio is mute, or the emulation is paused, etc. it is also used
+// to display the FPS counter and other TV information
+func (oly *playscrOverlay) drawTopLeft() {
+ pos := imgui.CursorScreenPos()
+ pos.X += overlayPadding
+ pos.Y += overlayPadding
+
+ // by default only one icon is shown in the top left corner. however, if the
+ // FPS overlay is being used we use the space to draw smaller icons
+ var useIconQueue bool
+
+ // draw FPS information if it's enabled
+ if oly.playscr.img.prefs.fpsDetail.Get().(bool) {
+ // it's easier if we put topleft of overlay in a window because the window
+ // will control the width and positioning automatically. if we don't then
+ // the horizntal rules will stretch the width of the screen and each new line of
+ // text in the fps detail will need to be repositioned for horizontal
+ // padding
+ imgui.SetNextWindowPos(pos)
+ imgui.BeginV("##fpsDetail", nil, imgui.WindowFlagsAlwaysAutoResize|
+ imgui.WindowFlagsNoScrollbar|imgui.WindowFlagsNoTitleBar|
+ imgui.WindowFlagsNoDecoration|imgui.WindowFlagsNoSavedSettings|
+ imgui.WindowFlagsNoBringToFrontOnFocus)
+ defer imgui.End()
+
+ select {
+ case <-oly.fpsPulse.C:
+ fps, hz := oly.playscr.img.dbg.VCS().TV.GetActualFPS()
+ oly.fps = fmt.Sprintf("%03.2f fps", fps)
+ oly.hz = fmt.Sprintf("%03.2fhz", hz)
+ default:
+ }
+
+ imgui.Text(fmt.Sprintf("Emulation: %s", oly.fps))
+ fr := imgui.CurrentIO().Framerate()
+ if fr == 0.0 {
+ imgui.Text("Rendering: waiting")
+ } else {
+ imgui.Text(fmt.Sprintf("Rendering: %03.2f fps", fr))
+ }
+
+ imguiSeparator()
+
+ if coproc := oly.playscr.img.cache.VCS.Mem.Cart.GetCoProc(); coproc != nil {
+ clk := float32(oly.playscr.img.dbg.VCS().Env.Prefs.ARM.Clock.Get().(float64))
+ imgui.Text(fmt.Sprintf("%s Clock: %.0f Mhz", coproc.ProcessorID(), clk))
+ imguiSeparator()
+ }
+
+ imgui.Text(fmt.Sprintf("%.1fx scaling", oly.playscr.yscaling))
+ imgui.Text(fmt.Sprintf("%d total scanlines", oly.playscr.scr.crit.frameInfo.TotalScanlines))
+
+ imguiSeparator()
+ imgui.Text(oly.playscr.img.screen.crit.frameInfo.Spec.ID)
+
+ imgui.SameLine()
+ imgui.Text(oly.hz)
+ if !oly.playscr.scr.crit.frameInfo.VSync {
+ imgui.SameLine()
+ imgui.Text(string(fonts.NoVSYNC))
+ }
+
+ imguiSeparator()
+ imgui.Text(fmt.Sprintf("%d frame input lag", oly.playscr.scr.crit.frameQueueLen))
+ if oly.playscr.scr.nudgeIconCt > 0 {
+ imgui.SameLine()
+ imgui.Text(string(fonts.Nudge))
+ }
+
+ // create space in the window for any icons that we might want to draw.
+ // what's good about this is that it makes sure that the window is large
+ // enough from frame-to-frame. without this, there will be a visble
+ // delay when the window is resized
+ imgui.Spacing()
+ p := imgui.CursorScreenPos()
+ imgui.Text("")
+ imgui.SetCursorScreenPos(p)
+
+ // draw developer icon if BorrowSource() returns a non-nil value
+ oly.playscr.img.dbg.CoProcDev.BorrowSource(func(src *dwarf.Source) {
+ if src != nil {
+ imgui.Text(string(fonts.Developer))
+ imgui.SameLine()
+ }
+ })
+
+ // we can draw multiple icons if required
+ useIconQueue = true
+
+ } else {
+ // we'll only be drawing one icon so we only need to set the cursor
+ // position once, so there's no need for a window as would be the case
+ // if fps detail was activated
+ imgui.SetCursorScreenPos(pos)
+
+ // FPS overlay is not active so we increase the font size for any icons
+ // that may be drawn hereafter in this window
+ imgui.PushFont(oly.playscr.img.fonts.veryLargeFontAwesome)
+ defer imgui.PopFont()
+
+ // add visibility adjustment if there is no FPS overlay
+ imgui.PushStyleColor(imgui.StyleColorText, imgui.Vec4{oly.visibility, oly.visibility, oly.visibility, oly.visibility})
+ defer imgui.PopStyleColor()
+ }
+
+ // start a new icons queue
+ oly.iconQueue = oly.iconQueue[:0]
+
+ // mute is likely to be the icon visible the longest so has the lowest priority
+ if oly.playscr.img.prefs.audioMutePlaymode.Get().(bool) && oly.playscr.img.prefs.audioMuteNotification.Get().(bool) {
+ oly.iconQueue = append(oly.iconQueue, fonts.AudioMute)
+ }
+
+ // the real current state as set by the emulation is used to decide what
+ // state to use for the overlay icon. we need to be careful with this
+ // decision and to only update the state/substate when a suitable duration
+ // has passed
+ state := oly.playscr.img.dbg.State()
+ subState := oly.playscr.img.dbg.SubState()
+ switch state {
+ case govern.Paused:
+ if state != oly.state && !oly.stateDur.running() {
+ oly.state = state
+ oly.subState = subState
+ oly.stateDur = overlayDurationPinned
+ }
+ case govern.Running:
+ if state != oly.state {
+ oly.state = state
+ oly.subState = subState
+ oly.stateDur = overlayDurationShort
+ }
+ case govern.Rewinding:
+ oly.state = state
+ oly.subState = subState
+
+ // refresh how the hold duration on every render frame that the
+ // rewinding state is seen. this is so that the duration of the rewind
+ // icon doesn't expire causing the pause icon to appear every so often
+ //
+ // (the way rewinding is implemented in the emulation means that the
+ // rewinding state is interspersed very quickly with the paused state.
+ // that works great for internal emulation purposes but requires careful
+ // handling for UI purposes)
+ oly.stateDur = overlayDurationBrief
+ }
+
+ // the state duration is ticked and the currently decided state shown unless
+ // the tick has expired (returns false)
+ if oly.stateDur.tick() {
+ switch oly.state {
+ case govern.Paused:
+ switch oly.subState {
+ case govern.PausedAtStart:
+ oly.iconQueue = append(oly.iconQueue, fonts.EmulationPausedAtStart)
+ case govern.PausedAtEnd:
+ oly.iconQueue = append(oly.iconQueue, fonts.EmulationPausedAtEnd)
+ default:
+ oly.iconQueue = append(oly.iconQueue, fonts.EmulationPause)
+ }
+ case govern.Running:
+ oly.iconQueue = append(oly.iconQueue, fonts.EmulationRun)
+ case govern.Rewinding:
+ switch oly.subState {
+ case govern.RewindingBackwards:
+ oly.iconQueue = append(oly.iconQueue, fonts.EmulationRewindBack)
+ case govern.RewindingForwards:
+ oly.iconQueue = append(oly.iconQueue, fonts.EmulationRewindForward)
+ default:
+ }
+ }
+ }
+
+ // events have the highest priority. we can think of these as user activated
+ // events, such as the triggering of a screenshot. we therefore want to give
+ // the user confirmation feedback immediately over other icons
+ if oly.eventDur.tick() {
+ switch oly.event {
+ case notifications.NotifyScreenshot:
+ oly.iconQueue = append(oly.iconQueue, fonts.Camera)
+ }
+ }
+
+ // draw only the last (ie. most important) icon unless the icon queue flag
+ // has been set
+ if !useIconQueue {
+ if len(oly.iconQueue) > 0 {
+ imgui.Text(string(oly.iconQueue[len(oly.iconQueue)-1]))
+ }
+ return
+ }
+
+ // draw icons in order of priority
+ for _, i := range oly.iconQueue {
+ imgui.Text(string(i))
+ imgui.SameLine()
+ }
+ return
+}
+
+// information in the top right of the overlay is about the cartridge. ie.
+// information from the cartridge about what is happening. for example,
+// supercharger tape activity, or PlusROM network activity, etc.
+func (oly *playscrOverlay) drawTopRight() {
+ if !oly.cartridgeDur.tick() {
+ return
+ }
+
+ var icon string
+ var secondaryIcon string
+
+ switch oly.cartridge {
+ case notifications.NotifySuperchargerSoundloadStarted:
+ if oly.playscr.img.prefs.superchargerNotifications.Get().(bool) {
+ icon = fmt.Sprintf("%c", fonts.Tape)
+ secondaryIcon = fmt.Sprintf("%c", fonts.TapePlay)
+ }
+ case notifications.NotifySuperchargerSoundloadEnded:
+ if oly.playscr.img.prefs.superchargerNotifications.Get().(bool) {
+ icon = fmt.Sprintf("%c", fonts.Tape)
+ secondaryIcon = fmt.Sprintf("%c", fonts.TapeStop)
+ }
+ case notifications.NotifySuperchargerSoundloadRewind:
+ if oly.playscr.img.prefs.superchargerNotifications.Get().(bool) {
+ icon = fmt.Sprintf("%c", fonts.Tape)
+ secondaryIcon = fmt.Sprintf("%c", fonts.TapeRewind)
+ }
+ case notifications.NotifyPlusROMNetwork:
+ if oly.playscr.img.prefs.plusromNotifications.Get().(bool) {
+ icon = fmt.Sprintf("%c", fonts.Wifi)
+ }
+ default:
+ return
+ }
+
+ pos := imgui.Vec2{oly.playscr.img.plt.displaySize()[0], 0}
+ pos.X -= oly.playscr.img.fonts.gopher2600IconsSize + overlayPadding
+ if secondaryIcon != "" {
+ pos.X -= oly.playscr.img.fonts.largeFontAwesomeSize * 2
+ }
+
+ imgui.PushFont(oly.playscr.img.fonts.gopher2600Icons)
+ defer imgui.PopFont()
+
+ imgui.PushStyleColor(imgui.StyleColorText, imgui.Vec4{oly.visibility, oly.visibility, oly.visibility, oly.visibility})
+ defer imgui.PopStyleColor()
+
+ imgui.SetCursorScreenPos(pos)
+ imgui.Text(icon)
+
+ if secondaryIcon != "" {
+ imgui.PushFont(oly.playscr.img.fonts.largeFontAwesome)
+ defer imgui.PopFont()
+
+ imgui.SameLine()
+ pos = imgui.CursorScreenPos()
+ pos.Y += (oly.playscr.img.fonts.gopher2600IconsSize - oly.playscr.img.fonts.largeFontAwesomeSize) * 0.5
+
+ imgui.SetCursorScreenPos(pos)
+ imgui.Text(secondaryIcon)
+ }
+}
+
+func (oly *playscrOverlay) drawBottomLeft() {
+ if !oly.leftPortDur.tick() {
+ return
+ }
+
+ if !oly.playscr.img.prefs.controllerNotifcations.Get().(bool) {
+ return
+ }
+
+ pos := imgui.Vec2{0, oly.playscr.img.plt.displaySize()[1]}
+ pos.X += overlayPadding
+ pos.Y -= oly.playscr.img.fonts.gopher2600IconsSize + overlayPadding
+
+ imgui.SetCursorScreenPos(pos)
+ oly.drawPeripheral(oly.leftPort)
+}
+
+func (oly *playscrOverlay) drawBottomRight() {
+ if !oly.rightPortDur.tick() {
+ return
+ }
+
+ if !oly.playscr.img.prefs.controllerNotifcations.Get().(bool) {
+ return
+ }
+
+ d := oly.playscr.img.plt.displaySize()
+ pos := imgui.Vec2{d[0], d[1]}
+ pos.X -= oly.playscr.img.fonts.gopher2600IconsSize + overlayPadding
+ pos.Y -= oly.playscr.img.fonts.gopher2600IconsSize + overlayPadding
+
+ imgui.SetCursorScreenPos(pos)
+ oly.drawPeripheral(oly.rightPort)
+}
+
+// drawPeripheral is used to draw the peripheral in the bottom left and bottom
+// right corners of the overlay
+func (oly *playscrOverlay) drawPeripheral(peripID plugging.PeripheralID) {
+ imgui.PushFont(oly.playscr.img.fonts.gopher2600Icons)
+ defer imgui.PopFont()
+
+ imgui.PushStyleColor(imgui.StyleColorText, imgui.Vec4{oly.visibility, oly.visibility, oly.visibility, oly.visibility})
+ defer imgui.PopStyleColor()
+
+ switch peripID {
+ case plugging.PeriphStick:
+ imgui.Text(fmt.Sprintf("%c", fonts.Stick))
+ case plugging.PeriphPaddles:
+ imgui.Text(fmt.Sprintf("%c", fonts.Paddle))
+ case plugging.PeriphKeypad:
+ imgui.Text(fmt.Sprintf("%c", fonts.Keypad))
+ case plugging.PeriphSavekey:
+ imgui.Text(fmt.Sprintf("%c", fonts.Savekey))
+ case plugging.PeriphGamepad:
+ imgui.Text(fmt.Sprintf("%c", fonts.Gamepad))
+ case plugging.PeriphAtariVox:
+ imgui.Text(fmt.Sprintf("%c", fonts.AtariVox))
+ }
+}
diff --git a/gui/sdlimgui/preferences.go b/gui/sdlimgui/preferences.go
index 1882a5bd..b5cf0eca 100644
--- a/gui/sdlimgui/preferences.go
+++ b/gui/sdlimgui/preferences.go
@@ -51,7 +51,7 @@ type preferences struct {
// playmode preferences
audioMutePlaymode prefs.Bool
- fpsOverlay prefs.Bool
+ fpsDetail prefs.Bool
activePause prefs.Bool
// playmode notifications
@@ -59,7 +59,6 @@ type preferences struct {
plusromNotifications prefs.Bool
superchargerNotifications prefs.Bool
audioMuteNotification prefs.Bool
- coprocDevNotification prefs.Bool
notificationVisibility prefs.Float
// fonts
@@ -91,15 +90,13 @@ func newPreferences(img *SdlImgui) (*preferences, error) {
p.showTooltips.Set(true)
p.showTimelineThumbnail.Set(false)
p.colorDisasm.Set(true)
- p.fpsOverlay.Set(false)
+ p.fpsDetail.Set(false)
p.activePause.Set(false)
p.audioMutePlaymode.Set(false)
p.controllerNotifcations.Set(true)
p.plusromNotifications.Set(true)
p.superchargerNotifications.Set(true)
p.audioMuteNotification.Set(true)
- p.coprocDevNotification.Set(true)
- p.coprocDevNotification.Set(true)
p.notificationVisibility.Set(0.75)
p.guiFontSize.Set(13)
p.terminalFontSize.Set(12)
@@ -141,7 +138,7 @@ func newPreferences(img *SdlImgui) (*preferences, error) {
// debugger audio mute options later
// playmode options
- err = p.dsk.Add("sdlimgui.playmode.fpsOverlay", &p.fpsOverlay)
+ err = p.dsk.Add("sdlimgui.playmode.fpsDetail", &p.fpsDetail)
if err != nil {
return nil, err
}
@@ -165,10 +162,6 @@ func newPreferences(img *SdlImgui) (*preferences, error) {
if err != nil {
return nil, err
}
- err = p.dsk.Add("sdlimgui.playmode.coprocDevNotification", &p.coprocDevNotification)
- if err != nil {
- return nil, err
- }
err = p.dsk.Add("sdlimgui.playmode.notifcationVisibility", &p.notificationVisibility)
if err != nil {
return nil, err
@@ -263,7 +256,7 @@ func newPreferences(img *SdlImgui) (*preferences, error) {
if err != nil {
return nil, err
}
- err = p.saveOnExitDsk.Add("sdlimgui.playmode.fpsOverlay", &p.fpsOverlay)
+ err = p.saveOnExitDsk.Add("sdlimgui.playmode.fpsDetail", &p.fpsDetail)
if err != nil {
return nil, err
}
diff --git a/gui/sdlimgui/requests.go b/gui/sdlimgui/requests.go
index 32ad9e6c..5863538f 100644
--- a/gui/sdlimgui/requests.go
+++ b/gui/sdlimgui/requests.go
@@ -23,7 +23,6 @@ import (
"github.com/jetsetilly/gopher2600/coprocessor/developer/dwarf"
"github.com/jetsetilly/gopher2600/debugger/govern"
"github.com/jetsetilly/gopher2600/gui"
- "github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
"github.com/jetsetilly/gopher2600/notifications"
)
@@ -70,38 +69,22 @@ func (img *SdlImgui) serviceSetFeature(request featureRequest) {
img.plt.setFullScreen(request.args[0].(bool))
}
- case gui.ReqPeripheralNotify:
+ case gui.ReqPeripheralPlugged:
err = argLen(request.args, 2)
if err == nil {
- port := request.args[0].(plugging.PortID)
- switch port {
- case plugging.PortLeft:
- img.playScr.peripheralLeft.set(request.args[1].(plugging.PeripheralID))
- case plugging.PortRight:
- img.playScr.peripheralRight.set(request.args[1].(plugging.PeripheralID))
- }
+ img.playScr.overlay.set(request.args[0], request.args[1])
}
- case gui.ReqEmulationNotify:
- if img.isPlaymode() {
- err = argLen(request.args, 1)
- if err == nil {
- img.playScr.emulationNotice.set(request.args[0].(notifications.Notice))
- }
- }
-
- case gui.ReqCartridgeNotify:
+ case gui.ReqNotification:
err = argLen(request.args, 1)
if err == nil {
- notice := request.args[0].(notifications.Notice)
-
- switch notice {
- case notifications.NotifyPlusROMNewInstallation:
+ switch request.args[0].(notifications.Notice) {
+ case notifications.NotifyPlusROMNewInstall:
img.modal = modalPlusROMFirstInstallation
case notifications.NotifyUnsupportedDWARF:
img.modal = modalUnsupportedDWARF
default:
- img.playScr.cartridgeNotice.set(notice)
+ img.playScr.overlay.set(request.args[0].(notifications.Notice))
}
}
diff --git a/gui/sdlimgui/sdlimgui.go b/gui/sdlimgui/sdlimgui.go
index 70ee65d9..7bb2c671 100644
--- a/gui/sdlimgui/sdlimgui.go
+++ b/gui/sdlimgui/sdlimgui.go
@@ -27,7 +27,6 @@ import (
"github.com/jetsetilly/gopher2600/gui/sdlaudio"
"github.com/jetsetilly/gopher2600/gui/sdlimgui/caching"
"github.com/jetsetilly/gopher2600/logger"
- "github.com/jetsetilly/gopher2600/notifications"
"github.com/jetsetilly/gopher2600/prefs"
"github.com/jetsetilly/gopher2600/reflection"
"github.com/jetsetilly/gopher2600/resources"
@@ -311,7 +310,7 @@ func (img *SdlImgui) quit() {
}
// end program. this differs from quit in that this function is called when we
-// receive a ReqEnd, which *may* have been sent in reponse to a EventQuit.
+// receive a ReqEnd, which *may* have been sent in reponse to a userinput.EventQuit
func (img *SdlImgui) end() {
img.prefs.saveWindowPreferences()
}
@@ -411,11 +410,6 @@ func (img *SdlImgui) applyAudioMutePreference() {
if img.isPlaymode() {
mute = img.prefs.audioMutePlaymode.Get().(bool)
- if mute {
- img.playScr.emulationNotice.set(notifications.NotifyMute)
- } else {
- img.playScr.emulationNotice.set(notifications.NotifyUnmute)
- }
img.dbg.VCS().RIOT.Ports.MutePeripherals(mute)
} else {
mute = img.prefs.audioMuteDebugger.Get().(bool)
diff --git a/gui/sdlimgui/service_keyboard.go b/gui/sdlimgui/service_keyboard.go
index 497f4158..5f28fe49 100644
--- a/gui/sdlimgui/service_keyboard.go
+++ b/gui/sdlimgui/service_keyboard.go
@@ -140,7 +140,8 @@ func (img *SdlImgui) serviceKeyboard(ev *sdl.KeyboardEvent) {
case sdl.SCANCODE_F7:
if img.isPlaymode() {
- img.playScr.toggleFPS()
+ fps := img.prefs.fpsDetail.Get().(bool)
+ img.prefs.fpsDetail.Set(!fps)
}
case sdl.SCANCODE_F8:
@@ -164,8 +165,7 @@ func (img *SdlImgui) serviceKeyboard(ev *sdl.KeyboardEvent) {
} else {
img.screenshot(modeSingle, "")
}
-
- img.playScr.emulationNotice.set(notifications.NotifyScreenshot)
+ img.playScr.overlay.set(notifications.NotifyScreenshot)
case sdl.SCANCODE_F14:
fallthrough
diff --git a/gui/sdlimgui/win_prefs.go b/gui/sdlimgui/win_prefs.go
index 6418d498..b9446029 100644
--- a/gui/sdlimgui/win_prefs.go
+++ b/gui/sdlimgui/win_prefs.go
@@ -211,10 +211,9 @@ a television image that is sympathetic to the display kernel
of the ROM.`)
imgui.Spacing()
- if imgui.CollapsingHeader("Notifications") {
-
+ if imgui.CollapsingHeader("Notification Icons") {
controllerNotifications := win.img.prefs.controllerNotifcations.Get().(bool)
- if imgui.Checkbox("Controller Change", &controllerNotifications) {
+ if imgui.Checkbox("Controller Changes", &controllerNotifications) {
win.img.prefs.controllerNotifcations.Set(controllerNotifications)
}
@@ -233,11 +232,6 @@ of the ROM.`)
win.img.prefs.audioMuteNotification.Set(audioMuteNotification)
}
- coprocDevNotification := win.img.prefs.coprocDevNotification.Get().(bool)
- if imgui.Checkbox("Coprocessor Development", &coprocDevNotification) {
- win.img.prefs.coprocDevNotification.Set(coprocDevNotification)
- }
-
visibility := float32(win.img.prefs.notificationVisibility.Get().(float64)) * 100
if imgui.SliderFloatV("Visibility", &visibility, 0.0, 100.0, "%.0f%%", imgui.SliderFlagsNone) {
win.img.prefs.notificationVisibility.Set(visibility / 100)
diff --git a/hardware/memory/cartridge/plusrom/plusrom.go b/hardware/memory/cartridge/plusrom/plusrom.go
index e72cb310..393375dc 100644
--- a/hardware/memory/cartridge/plusrom/plusrom.go
+++ b/hardware/memory/cartridge/plusrom/plusrom.go
@@ -139,9 +139,11 @@ func NewPlusROM(env *environment.Environment, child mapper.CartMapper) (mapper.C
// log success
logger.Logf("plusrom", "will connect to %s", cart.net.ai.String())
- err := cart.env.Notifications.Notify(notifications.NotifyPlusROMInserted)
- if err != nil {
- return nil, fmt.Errorf("plusrom %w:", err)
+ if cart.env.Prefs.PlusROM.NewInstallation {
+ err := cart.env.Notifications.Notify(notifications.NotifyPlusROMNewInstall)
+ if err != nil {
+ return nil, fmt.Errorf("plusrom %w:", err)
+ }
}
return cart, nil
diff --git a/hardware/run.go b/hardware/run.go
index 7d25fe47..87cfb67a 100644
--- a/hardware/run.go
+++ b/hardware/run.go
@@ -85,7 +85,7 @@ func (vcs *VCS) Run(continueCheck func() (govern.State, error)) error {
}
case govern.Paused:
default:
- return fmt.Errorf("vcs: unsupported emulation state (%d) in Run() function", state)
+ return fmt.Errorf("vcs: unsupported emulation state (%s) in Run() function", state)
}
state, err = continueCheck()
diff --git a/notifications/doc.go b/notifications/doc.go
index eca7ac8d..6d86cc29 100644
--- a/notifications/doc.go
+++ b/notifications/doc.go
@@ -21,4 +21,16 @@
// event that has happened (eg. tape stopped, etc.) For some notifications
// however, it is appropriate for the emulation instance to deal with the
// notification invisibly.
+//
+// Finally, some notifications are not generated by the cartridge but by the
+// emulation instance, and sent to the GUI.
+//
+// We can therefore think of the flow of notifications in the following manner:
+//
+// (a) (b) (c)
+// Cartridge -----> Emulation Instance -----> GUI ----
+// 1 2 \____| 3
+//
+// The diagram also suggests that a GUI can both produce and receive a
+// notification. This is possible and sometimes convenient to do.
package notifications
diff --git a/notifications/notifications.go b/notifications/notifications.go
index 00e8ad60..c334035e 100644
--- a/notifications/notifications.go
+++ b/notifications/notifications.go
@@ -22,47 +22,27 @@ type Notice string
// List of defined notifications.
const (
- NotifyInitialising Notice = "NotifyInitialising"
- NotifyPause Notice = "NotifyPause"
- NotifyRun Notice = "NotifyRun"
- NotifyRewindBack Notice = "NotifyRewindBack"
- NotifyRewindFoward Notice = "NotifyRewindFoward"
- NotifyRewindAtStart Notice = "NotifyRewindAtStart"
- NotifyRewindAtEnd Notice = "NotifyRewindAtEnd"
- NotifyScreenshot Notice = "NotifyScreenshot"
- NotifyMute Notice = "NotifyMute"
- NotifyUnmute Notice = "NotifyUnmute"
+ // a screen shot is taking place
+ NotifyScreenshot Notice = "NotifyScreenshot"
- // If Supercharger is loading from a fastload binary then this event is
+ // notifications sent when supercharger is loading from a sound file (eg. mp3 file)
+ NotifySuperchargerSoundloadStarted Notice = "NotifySuperchargerSoundloadStarted"
+ NotifySuperchargerSoundloadEnded Notice = "NotifySuperchargerSoundloadEnded"
+ NotifySuperchargerSoundloadRewind Notice = "NotifySuperchargerSoundloadRewind"
+
+ // if Supercharger is loading from a fastload binary then this event is
// raised when the ROM requests the next block be loaded from the "tape
NotifySuperchargerFastload Notice = "NotifySuperchargerFastload"
- // If Supercharger is loading from a sound file (eg. mp3 file) then these
- // events area raised when the loading has started and ended.
- NotifySuperchargerSoundloadStarted Notice = "NotifySuperchargerSoundloadStarted"
- NotifySuperchargerSoundloadEnded Notice = "NotifySuperchargerSoundloadEnded"
+ // notifications sent by plusrom
+ NotifyPlusROMNewInstall Notice = "NotifyPlusROMNewInstall"
+ NotifyPlusROMNetwork Notice = "NotifyPlusROMNetwork"
- // tape is rewinding.
- NotifySuperchargerSoundloadRewind Notice = "NotifySuperchargerSoundloadRewind"
-
- // PlusROM cartridge has been inserted.
- NotifyPlusROMInserted Notice = "NotifyPlusROMInserted"
-
- // PlusROM network activity.
- NotifyPlusROMNetwork Notice = "NotifyPlusROMNetwork"
-
- // PlusROM new installation
- NotifyPlusROMNewInstallation Notice = "NotifyPlusROMNewInstallation"
-
- // Moviecart started
+ // moviecart has started
NotifyMovieCartStarted Notice = "NotifyMoveCartStarted"
// unsupported DWARF data
NotifyUnsupportedDWARF Notice = "NotifyUnsupportedDWARF"
-
- // coprocessor development information has been loaded
- NotifyCoprocDevStarted Notice = "NotifyCoprocDevStarted"
- NotifyCoprocDevEnded Notice = "NotifyCoprocDevEnded"
)
// Notify is used for direct communication between a the hardware and the
diff --git a/prefs/defunct.go b/prefs/defunct.go
index 9258798c..b7554c40 100644
--- a/prefs/defunct.go
+++ b/prefs/defunct.go
@@ -66,6 +66,8 @@ var defunct = []string{
"crt.syncPowerOn",
"crt.syncSpeed",
"crt.syncSensitivity",
+ "sdlimgui.playmode.coprocDevNotification",
+ "sdlimgui.playmode.fpsOverlay",
}
// returns true if string is in list of defunct values.