mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
realtime audio mixers, like SDL audio implementation, are expected to self-regulate. this makes more sense and the Regulate() idea didn't work as expected SDL audio now repeats or drops short bursts of samples as they are received, in order keep the queue inside a generous window. this seems to work very well and far better than the previous efforts at regulation there could maybe be more thought around how many samples should be repeated or dropped such that it isn't audible the difference between a realtime mixer and a regular mixer is now just the SetSpec() function. I'll consider adding the SetSpec() function to the regular mixer interface and dropping the realtime mixer altogether
638 lines
20 KiB
Go
638 lines
20 KiB
Go
// 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 <https://www.gnu.org/licenses/>.
|
|
|
|
package sdlimgui
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"strings"
|
|
"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/gui/sdlaudio"
|
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
|
|
"github.com/jetsetilly/gopher2600/notifications"
|
|
)
|
|
|
|
type overlayLatch int
|
|
|
|
const (
|
|
overlayLatchPinned = -1
|
|
overlayLatchOff = 0
|
|
overlayLatchBrief = 30
|
|
overlayLatchShort = 60
|
|
overlayLatchLong = 90
|
|
)
|
|
|
|
func (ct *overlayLatch) forceExpire() {
|
|
*ct = overlayLatchOff
|
|
}
|
|
|
|
// reduces the duration value. returns false if count has expired. if the
|
|
// duration has been "pinned" then value will return true
|
|
func (ct *overlayLatch) tick() bool {
|
|
if *ct == overlayLatchOff {
|
|
return false
|
|
}
|
|
if *ct == overlayLatchPinned {
|
|
return true
|
|
}
|
|
*ct = *ct - 1
|
|
return true
|
|
}
|
|
|
|
// returns true if duration is not off or pinned
|
|
func (ct *overlayLatch) expired() bool {
|
|
return *ct != overlayLatchPinned && *ct == overlayLatchOff
|
|
}
|
|
|
|
type playscrOverlay struct {
|
|
img *SdlImgui
|
|
playscr *playScr
|
|
|
|
fps string
|
|
refreshRate string
|
|
|
|
renderAlert int
|
|
|
|
memStatsTicker *time.Ticker
|
|
memStats runtime.MemStats
|
|
|
|
// 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
|
|
stateLatch overlayLatch
|
|
|
|
// events are user-activated events and require immediate feedback
|
|
event notifications.Notice
|
|
eventLatch overlayLatch
|
|
|
|
// 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
|
|
cartridgeLatch overlayLatch
|
|
|
|
// bottom-left corner of the overlay
|
|
leftPort plugging.PeripheralID
|
|
leftPortLatch overlayLatch
|
|
|
|
// bottom-right corner of the overlay
|
|
rightPort plugging.PeripheralID
|
|
rightPortLatch overlayLatch
|
|
|
|
// visibility of icons is set from the preferences once per draw()
|
|
visibility float32
|
|
}
|
|
|
|
const overlayPadding = 10
|
|
|
|
func (ovly *playscrOverlay) set(v any, args ...any) {
|
|
switch n := v.(type) {
|
|
case plugging.PortID:
|
|
switch n {
|
|
case plugging.PortLeft:
|
|
ovly.leftPort = args[0].(plugging.PeripheralID)
|
|
ovly.leftPortLatch = overlayLatchShort
|
|
case plugging.PortRight:
|
|
ovly.rightPort = args[0].(plugging.PeripheralID)
|
|
ovly.rightPortLatch = overlayLatchShort
|
|
}
|
|
case notifications.Notice:
|
|
switch n {
|
|
case notifications.NotifySuperchargerSoundloadStarted:
|
|
ovly.cartridge = n
|
|
ovly.cartridgeLatch = overlayLatchPinned
|
|
case notifications.NotifySuperchargerSoundloadEnded:
|
|
ovly.cartridge = n
|
|
ovly.cartridgeLatch = overlayLatchShort
|
|
case notifications.NotifySuperchargerSoundloadRewind:
|
|
return
|
|
|
|
case notifications.NotifyPlusROMNetwork:
|
|
ovly.cartridge = n
|
|
ovly.cartridgeLatch = overlayLatchShort
|
|
|
|
case notifications.NotifyScreenshot:
|
|
ovly.event = n
|
|
ovly.eventLatch = overlayLatchShort
|
|
|
|
default:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ovly *playscrOverlay) draw() {
|
|
imgui.PushStyleColor(imgui.StyleColorWindowBg, ovly.img.cols.Transparent)
|
|
imgui.PushStyleColor(imgui.StyleColorBorder, ovly.img.cols.Transparent)
|
|
defer imgui.PopStyleColorV(2)
|
|
|
|
imgui.PushStyleVarVec2(imgui.StyleVarWindowPadding, imgui.Vec2{})
|
|
defer imgui.PopStyleVarV(1)
|
|
|
|
imgui.SetNextWindowPos(imgui.Vec2{X: 0, Y: 0})
|
|
|
|
winw, winh := ovly.img.plt.windowSize()
|
|
imgui.SetNextWindowSize(imgui.Vec2{X: winw, Y: winh})
|
|
|
|
imgui.BeginV("##playscrOverlay", nil, imgui.WindowFlagsAlwaysAutoResize|
|
|
imgui.WindowFlagsNoScrollbar|imgui.WindowFlagsNoTitleBar|
|
|
imgui.WindowFlagsNoDecoration|imgui.WindowFlagsNoSavedSettings|
|
|
imgui.WindowFlagsNoBringToFrontOnFocus)
|
|
defer imgui.End()
|
|
|
|
ovly.visibility = float32(ovly.img.prefs.notificationVisibility.Get().(float64))
|
|
|
|
ovly.drawTopLeft()
|
|
ovly.drawTopRight()
|
|
ovly.drawBottomLeft()
|
|
ovly.drawBottomRight()
|
|
}
|
|
|
|
func (ovly *playscrOverlay) updateRefreshRate() {
|
|
fps, refreshRate := ovly.img.dbg.VCS().TV.GetActualFPS()
|
|
if fps == 0 {
|
|
ovly.fps = "waiting"
|
|
} else {
|
|
ovly.fps = fmt.Sprintf("%03.2f fps", fps)
|
|
}
|
|
if refreshRate == 0 {
|
|
ovly.refreshRate = "waiting"
|
|
} else {
|
|
ovly.refreshRate = fmt.Sprintf("%03.2fhz", refreshRate)
|
|
}
|
|
}
|
|
|
|
// 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 (ovly *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 ovly.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()
|
|
|
|
ovly.updateRefreshRate()
|
|
|
|
select {
|
|
case <-ovly.memStatsTicker.C:
|
|
runtime.ReadMemStats(&ovly.memStats)
|
|
default:
|
|
}
|
|
|
|
imgui.Textf("Emulation: %s", ovly.fps)
|
|
r := imgui.CurrentIO().Framerate()
|
|
if r == 0.0 {
|
|
imgui.Text("Rendering: waiting")
|
|
} else {
|
|
imgui.Textf("Rendering: %03.2f fps", r)
|
|
}
|
|
|
|
imguiSeparator()
|
|
|
|
if coproc := ovly.img.cache.VCS.Mem.Cart.GetCoProc(); coproc != nil {
|
|
clk := float32(ovly.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", ovly.playscr.scaling))
|
|
imgui.Text(fmt.Sprintf("%d total scanlines", ovly.playscr.scr.crit.frameInfo.TotalScanlines))
|
|
|
|
imguiSeparator()
|
|
|
|
// this construct (spacing followed by a same-line directive) is only
|
|
// necessary so that the extreme left pixel of the VBLANKtop icon is not
|
|
// chopped off. it's a very small detail but worth doing
|
|
imgui.Spacing()
|
|
imgui.SameLineV(0, 1)
|
|
|
|
vblankBounds := fmt.Sprintf("%c %d %c %d",
|
|
fonts.VBLANKtop,
|
|
ovly.playscr.scr.crit.frameInfo.VBLANKtop,
|
|
fonts.VBLANKbottom,
|
|
ovly.playscr.scr.crit.frameInfo.VBLANKbottom)
|
|
vblankBounds = strings.ReplaceAll(vblankBounds, "-1", "-")
|
|
imgui.Text(vblankBounds)
|
|
if ovly.playscr.scr.crit.frameInfo.VBLANKunstable {
|
|
imgui.SameLineV(0, 5)
|
|
imgui.Text(string(fonts.Bug))
|
|
}
|
|
if ovly.playscr.scr.crit.frameInfo.AtariSafe() {
|
|
imgui.SameLineV(0, 15)
|
|
imgui.Text(string(fonts.VBLANKatari))
|
|
}
|
|
|
|
imgui.Spacing()
|
|
if ovly.playscr.scr.crit.frameInfo.FromVSYNC {
|
|
imgui.Text(fmt.Sprintf("VSYNC %d+%d", ovly.playscr.scr.crit.frameInfo.VSYNCscanline,
|
|
ovly.playscr.scr.crit.frameInfo.VSYNCcount))
|
|
if ovly.playscr.scr.crit.frameInfo.VSYNCunstable {
|
|
imgui.SameLineV(0, 5)
|
|
imgui.Text(string(fonts.Bug))
|
|
}
|
|
} else {
|
|
imgui.Text(fmt.Sprintf("VSYNC %c", fonts.Bug))
|
|
}
|
|
|
|
imguiSeparator()
|
|
imgui.Text(ovly.img.screen.crit.frameInfo.Spec.ID)
|
|
|
|
imgui.SameLine()
|
|
imgui.Text(ovly.refreshRate)
|
|
|
|
if ovly.img.prefs.frameQueueMeterInOverlay.Get().(bool) {
|
|
imguiSeparator()
|
|
|
|
imgui.PushStyleColor(imgui.StyleColorText, ovly.img.cols.FrameQueueSlackActive)
|
|
for _ = range ovly.playscr.scr.frameQueueSlack {
|
|
imgui.Text(string(fonts.MeterSegment))
|
|
imgui.SameLineV(0, 0)
|
|
}
|
|
|
|
imgui.PushStyleColor(imgui.StyleColorText, ovly.img.cols.FrameQueueSlackInactive)
|
|
for _ = range ovly.playscr.scr.crit.frameQueueLen - ovly.playscr.scr.frameQueueSlack {
|
|
imgui.Text(string(fonts.MeterSegment))
|
|
imgui.SameLineV(0, 0)
|
|
}
|
|
imgui.Text("")
|
|
|
|
imgui.PopStyleColorV(2)
|
|
|
|
imgui.Spacing()
|
|
imgui.Textf("%2.2fms/frame", float32(ovly.img.plt.renderAvgTime.Nanoseconds())/1000000)
|
|
if ovly.img.plt.renderAlert {
|
|
ovly.renderAlert = 60
|
|
} else if ovly.renderAlert > 0 {
|
|
ovly.renderAlert--
|
|
}
|
|
if ovly.renderAlert > 0 {
|
|
imgui.SameLineV(0, 5)
|
|
imgui.Text(string(fonts.RenderTime))
|
|
}
|
|
}
|
|
|
|
if ovly.img.prefs.audioQueueMeterInOverlay.Get().(bool) {
|
|
// draw separator if there is no frame queue meter
|
|
if !ovly.img.prefs.frameQueueMeterInOverlay.Get().(bool) {
|
|
imguiSeparator()
|
|
} else {
|
|
imgui.Spacing()
|
|
}
|
|
|
|
if ovly.img.audio.QueuedBytes == 0 {
|
|
imgui.PushStyleColor(imgui.StyleColorText, ovly.img.cols.AudioQueueInactive)
|
|
imgui.Text(string(fonts.MeterSegment))
|
|
imgui.SameLineV(0, 0)
|
|
imgui.Text(string(fonts.MeterSegment))
|
|
imgui.SameLineV(0, 0)
|
|
imgui.Text(string(fonts.MeterSegment))
|
|
imgui.SameLineV(0, 0)
|
|
imgui.PopStyleColor()
|
|
} else if ovly.img.audio.QueuedBytes < sdlaudio.QueueOkay {
|
|
imgui.PushStyleColor(imgui.StyleColorText, ovly.img.cols.AudioQueueActive)
|
|
imgui.Text(string(fonts.MeterSegment))
|
|
imgui.SameLineV(0, 0)
|
|
imgui.PushStyleColor(imgui.StyleColorText, ovly.img.cols.AudioQueueInactive)
|
|
imgui.Text(string(fonts.MeterSegment))
|
|
imgui.SameLineV(0, 0)
|
|
imgui.Text(string(fonts.MeterSegment))
|
|
imgui.SameLineV(0, 0)
|
|
imgui.PopStyleColorV(2)
|
|
} else if ovly.img.audio.QueuedBytes < sdlaudio.QueueWarning {
|
|
imgui.PushStyleColor(imgui.StyleColorText, ovly.img.cols.AudioQueueActive)
|
|
imgui.Text(string(fonts.MeterSegment))
|
|
imgui.SameLineV(0, 0)
|
|
imgui.Text(string(fonts.MeterSegment))
|
|
imgui.SameLineV(0, 0)
|
|
imgui.PushStyleColor(imgui.StyleColorText, ovly.img.cols.AudioQueueInactive)
|
|
imgui.Text(string(fonts.MeterSegment))
|
|
imgui.SameLineV(0, 0)
|
|
imgui.PopStyleColorV(2)
|
|
} else {
|
|
imgui.PushStyleColor(imgui.StyleColorText, ovly.img.cols.AudioQueueActive)
|
|
imgui.Text(string(fonts.MeterSegment))
|
|
imgui.SameLineV(0, 0)
|
|
imgui.Text(string(fonts.MeterSegment))
|
|
imgui.SameLineV(0, 0)
|
|
imgui.Text(string(fonts.MeterSegment))
|
|
imgui.SameLineV(0, 0)
|
|
imgui.PopStyleColor()
|
|
}
|
|
|
|
imgui.Spacing()
|
|
if !ovly.img.prefs.audioMutePlaymode.Get().(bool) {
|
|
imgui.Textf("%dkb audio queue", ovly.img.audio.QueuedBytes/1024)
|
|
}
|
|
}
|
|
|
|
if ovly.img.prefs.memoryUsageInOverlay.Get().(bool) {
|
|
imguiSeparator()
|
|
imgui.Textf("Used = %v MB\n", ovly.memStats.Alloc/1048576)
|
|
imgui.Textf("Reserved = %v MB\n", ovly.memStats.Sys/1048576)
|
|
imgui.Textf("GC Sweeps = %v", ovly.memStats.NumGC)
|
|
imgui.Textf("GC CPU %% = %.2f%%", ovly.memStats.GCCPUFraction*100)
|
|
}
|
|
|
|
// 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
|
|
ovly.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(ovly.img.fonts.veryLargeFontAwesome)
|
|
defer imgui.PopFont()
|
|
|
|
// add visibility adjustment if there is no FPS overlay
|
|
imgui.PushStyleColor(imgui.StyleColorText, imgui.Vec4{X: ovly.visibility, Y: ovly.visibility, Z: ovly.visibility, W: ovly.visibility})
|
|
defer imgui.PopStyleColor()
|
|
}
|
|
|
|
// start a new icons queue
|
|
ovly.iconQueue = ovly.iconQueue[:0]
|
|
|
|
// mute is likely to be the icon visible the longest so has the lowest priority
|
|
if ovly.img.prefs.audioMutePlaymode.Get().(bool) && ovly.img.prefs.audioMuteNotification.Get().(bool) {
|
|
ovly.iconQueue = append(ovly.iconQueue, fonts.AudioMute)
|
|
}
|
|
|
|
// the real current state as set by the emulation is used to decide what
|
|
// state to use for the overlay icon
|
|
state := ovly.img.dbg.State()
|
|
subState := ovly.img.dbg.SubState()
|
|
|
|
switch state {
|
|
case govern.Paused:
|
|
// handling the pause state is the trickiest to get right. we want to
|
|
// prioritise the pause icon in some cases but not in others
|
|
switch ovly.state {
|
|
case govern.Rewinding:
|
|
// if the previous state was the rewinding state a pause icon will
|
|
// show if the pause sub-state is not normal or if the
|
|
// previous state latch has expired
|
|
if subState != govern.Normal || ovly.stateLatch.expired() {
|
|
ovly.state = state
|
|
ovly.subState = subState
|
|
ovly.stateLatch = overlayLatchPinned
|
|
}
|
|
default:
|
|
ovly.state = state
|
|
ovly.subState = subState
|
|
ovly.stateLatch = overlayLatchPinned
|
|
}
|
|
case govern.Running:
|
|
if state != ovly.state {
|
|
ovly.state = state
|
|
ovly.subState = subState
|
|
ovly.stateLatch = overlayLatchShort
|
|
}
|
|
case govern.Rewinding:
|
|
ovly.state = state
|
|
ovly.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)
|
|
ovly.stateLatch = overlayLatchBrief
|
|
}
|
|
|
|
// the state duration is ticked and the icon is shown unless the tick has
|
|
// expired (returns false)
|
|
if ovly.stateLatch.tick() {
|
|
switch ovly.state {
|
|
case govern.Paused:
|
|
switch ovly.subState {
|
|
case govern.PausedAtStart:
|
|
ovly.iconQueue = append(ovly.iconQueue, fonts.EmulationPausedAtStart)
|
|
case govern.PausedAtEnd:
|
|
ovly.iconQueue = append(ovly.iconQueue, fonts.EmulationPausedAtEnd)
|
|
default:
|
|
ovly.iconQueue = append(ovly.iconQueue, fonts.EmulationPause)
|
|
}
|
|
case govern.Running:
|
|
ovly.iconQueue = append(ovly.iconQueue, fonts.EmulationRun)
|
|
case govern.Rewinding:
|
|
switch ovly.subState {
|
|
case govern.RewindingBackwards:
|
|
ovly.iconQueue = append(ovly.iconQueue, fonts.EmulationRewindBack)
|
|
case govern.RewindingForwards:
|
|
ovly.iconQueue = append(ovly.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 ovly.eventLatch.tick() {
|
|
switch ovly.event {
|
|
case notifications.NotifyScreenshot:
|
|
ovly.iconQueue = append(ovly.iconQueue, fonts.Camera)
|
|
}
|
|
}
|
|
|
|
// draw only the last (ie. most important) icon unless the icon queue flag
|
|
// has been set
|
|
if !useIconQueue {
|
|
if len(ovly.iconQueue) > 0 {
|
|
imgui.Text(string(ovly.iconQueue[len(ovly.iconQueue)-1]))
|
|
}
|
|
return
|
|
}
|
|
|
|
// draw icons in order of priority
|
|
for _, i := range ovly.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 (ovly *playscrOverlay) drawTopRight() {
|
|
if !ovly.cartridgeLatch.tick() {
|
|
return
|
|
}
|
|
|
|
var icon string
|
|
var secondaryIcon string
|
|
|
|
switch ovly.cartridge {
|
|
case notifications.NotifySuperchargerSoundloadStarted:
|
|
if ovly.img.prefs.superchargerNotifications.Get().(bool) {
|
|
icon = fmt.Sprintf("%c", fonts.Tape)
|
|
secondaryIcon = fmt.Sprintf("%c", fonts.TapePlay)
|
|
}
|
|
case notifications.NotifySuperchargerSoundloadEnded:
|
|
if ovly.img.prefs.superchargerNotifications.Get().(bool) {
|
|
icon = fmt.Sprintf("%c", fonts.Tape)
|
|
secondaryIcon = fmt.Sprintf("%c", fonts.TapeStop)
|
|
}
|
|
case notifications.NotifySuperchargerSoundloadRewind:
|
|
if ovly.img.prefs.superchargerNotifications.Get().(bool) {
|
|
icon = fmt.Sprintf("%c", fonts.Tape)
|
|
secondaryIcon = fmt.Sprintf("%c", fonts.TapeRewind)
|
|
}
|
|
case notifications.NotifyPlusROMNetwork:
|
|
if ovly.img.prefs.plusromNotifications.Get().(bool) {
|
|
icon = fmt.Sprintf("%c", fonts.Wifi)
|
|
}
|
|
default:
|
|
return
|
|
}
|
|
|
|
pos := imgui.WindowContentRegionMax()
|
|
pos.X -= ovly.img.fonts.gopher2600IconsSize + overlayPadding
|
|
pos.Y = 0
|
|
if secondaryIcon != "" {
|
|
pos.X -= ovly.img.fonts.largeFontAwesomeSize * 2
|
|
}
|
|
|
|
imgui.PushFont(ovly.img.fonts.gopher2600Icons)
|
|
defer imgui.PopFont()
|
|
|
|
imgui.PushStyleColor(imgui.StyleColorText, imgui.Vec4{X: ovly.visibility, Y: ovly.visibility, Z: ovly.visibility, W: ovly.visibility})
|
|
defer imgui.PopStyleColor()
|
|
|
|
imgui.SetCursorScreenPos(pos)
|
|
imgui.Text(icon)
|
|
|
|
if secondaryIcon != "" {
|
|
imgui.PushFont(ovly.img.fonts.largeFontAwesome)
|
|
defer imgui.PopFont()
|
|
|
|
imgui.SameLine()
|
|
pos = imgui.CursorScreenPos()
|
|
pos.Y += (ovly.img.fonts.gopher2600IconsSize - ovly.img.fonts.largeFontAwesomeSize) * 0.5
|
|
|
|
imgui.SetCursorScreenPos(pos)
|
|
imgui.Text(secondaryIcon)
|
|
}
|
|
}
|
|
|
|
func (ovly *playscrOverlay) drawBottomLeft() {
|
|
if !ovly.leftPortLatch.tick() {
|
|
return
|
|
}
|
|
|
|
if !ovly.img.prefs.controllerNotifcations.Get().(bool) {
|
|
return
|
|
}
|
|
|
|
pos := imgui.WindowContentRegionMax()
|
|
pos.X = overlayPadding
|
|
pos.Y -= ovly.img.fonts.gopher2600IconsSize + overlayPadding
|
|
|
|
imgui.SetCursorScreenPos(pos)
|
|
ovly.drawPeripheral(ovly.leftPort)
|
|
}
|
|
|
|
func (ovly *playscrOverlay) drawBottomRight() {
|
|
if !ovly.rightPortLatch.tick() {
|
|
return
|
|
}
|
|
|
|
if !ovly.img.prefs.controllerNotifcations.Get().(bool) {
|
|
return
|
|
}
|
|
|
|
pos := imgui.WindowContentRegionMax()
|
|
pos.X -= ovly.img.fonts.gopher2600IconsSize + overlayPadding
|
|
pos.Y -= ovly.img.fonts.gopher2600IconsSize + overlayPadding
|
|
|
|
imgui.SetCursorScreenPos(pos)
|
|
ovly.drawPeripheral(ovly.rightPort)
|
|
}
|
|
|
|
// drawPeripheral is used to draw the peripheral in the bottom left and bottom
|
|
// right corners of the overlay
|
|
func (ovly *playscrOverlay) drawPeripheral(peripID plugging.PeripheralID) {
|
|
imgui.PushFont(ovly.img.fonts.gopher2600Icons)
|
|
defer imgui.PopFont()
|
|
|
|
imgui.PushStyleColor(imgui.StyleColorText, imgui.Vec4{X: ovly.visibility, Y: ovly.visibility, Z: ovly.visibility, W: ovly.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))
|
|
}
|
|
}
|