From d32262adffca3848f466651029758e31b0f47fd7 Mon Sep 17 00:00:00 2001 From: JetSetIlly Date: Sun, 7 Apr 2024 09:22:29 +0100 Subject: [PATCH] simplified how gui implements and handles notifications debugger no longer sends play, pause notifications to the gui. the gui polls for that information as required govern package now has SubState type to complement the State type. StateIntegrity() function enforces combinations of State and SubState, called from debugger.setState() function playmode notifications reworked and contained in a single playmode_overlay.go file. this includes the FPS and screen detail preference value sdlimgui.playmode.fpsOverlay replaced with sdlimgui.playmode.fpsDetail. still toggled with F7 key coproc icon moved to top-left corner of playmode overlay and only visible when FPS detail is showing when FPS detail is showing multiple (small) icons care shown. when it is not showing, a single (large) icon is shown according to the priority of the icon. eg. pause indicator has higher priority than the mute indicator --- debugger/commands.go | 42 +- debugger/debugger.go | 110 ++--- debugger/deeppoke.go | 2 +- debugger/govern/state.go | 63 ++- debugger/loop_catchup.go | 2 +- debugger/loop_debugger.go | 8 +- debugger/push.go | 4 +- debugger/rewind.go | 76 ++- gui/fonts/fonts.go | 4 +- gui/gui.go | 10 +- gui/sdlimgui/playscr.go | 119 +---- gui/sdlimgui/playscr_notifications.go | 381 --------------- gui/sdlimgui/playscr_overlay.go | 481 +++++++++++++++++++ gui/sdlimgui/preferences.go | 15 +- gui/sdlimgui/requests.go | 29 +- gui/sdlimgui/sdlimgui.go | 8 +- gui/sdlimgui/service_keyboard.go | 6 +- gui/sdlimgui/win_prefs.go | 10 +- hardware/memory/cartridge/plusrom/plusrom.go | 8 +- hardware/run.go | 2 +- notifications/doc.go | 12 + notifications/notifications.go | 44 +- prefs/defunct.go | 2 + 23 files changed, 709 insertions(+), 729 deletions(-) delete mode 100644 gui/sdlimgui/playscr_notifications.go create mode 100644 gui/sdlimgui/playscr_overlay.go 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.