Gopher2600/gui/sdlimgui/win_cart_cdf_streams.go
JetSetIlly 17e6d70f2f added colour reflection on the bevel
the inside of the bevel nearest the screen reflects the colour from the
screen. it's not perfect but it's a nice effect and can form the basis
for something better

screen centering inside solid state bevel moved very slightly to the
right
2025-01-13 19:42:20 +00:00

446 lines
13 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"
"image"
"image/color"
"github.com/inkyblackness/imgui-go/v4"
"github.com/jetsetilly/gopher2600/gui/fonts"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/cdf"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
"github.com/jetsetilly/gopher2600/hardware/television/signal"
"github.com/jetsetilly/gopher2600/hardware/television/specification"
)
const winCDFStreamsID = "CDF Streams"
// keeps track of whether the datastream drag and drop is active and which
// datastreams are involved (ie. source and target)
type datastreamDragAndDrop struct {
active bool
src int
tgt int
previousSrc int
}
type winCDFStreams struct {
debuggerWin
img *SdlImgui
streamPixels [cdf.NumDatastreams]*image.RGBA
streamTextures [cdf.NumDatastreams]texture
pixelsSize image.Point
detailTexture texture
colouriser datastreamDragAndDrop
colourSource [cdf.NumDatastreams]int
trackScreen bool
scanlines int32
optionsHeight float32
}
func newWinCDFStreams(img *SdlImgui) (window, error) {
win := &winCDFStreams{
img: img,
scanlines: specification.AbsoluteMaxScanlines,
trackScreen: true,
}
win.clearColours()
for i := range win.streamTextures {
win.streamTextures[i] = img.rnd.addTexture(shaderColor, false, false, nil)
win.streamPixels[i] = image.NewRGBA(image.Rect(0, 0, 8, specification.AbsoluteMaxScanlines))
}
win.detailTexture = img.rnd.addTexture(shaderColor, false, false, nil)
win.pixelsSize = win.streamPixels[0].Bounds().Size()
for y := 0; y < win.pixelsSize.Y; y++ {
for x := 0; x < win.pixelsSize.X; x++ {
for i := range win.streamPixels {
win.streamPixels[i].SetRGBA(x, y, color.RGBA{R: 0, G: 0, B: 0, A: 255})
}
}
}
win.render()
return win, nil
}
func (win *winCDFStreams) init() {
}
func (win *winCDFStreams) id() string {
return winCDFStreamsID
}
func (win *winCDFStreams) updateStreams(regs cdf.Registers, static mapper.CartStatic) {
// keep track of scanlines
frameInfo := win.img.cache.TV.GetFrameInfo()
scanlines := frameInfo.VisibleBottom - frameInfo.VisibleTop
if !win.trackScreen {
scanlines = int(win.scanlines)
} else {
win.scanlines = int32(scanlines)
}
fg := color.RGBA{100, 100, 100, 255}
bg := color.RGBA{10, 10, 10, 255}
unused := color.RGBA{10, 10, 10, 100}
spec := win.img.cache.TV.GetFrameInfo().Spec
// draw pixels
for i := range regs.Datastream {
for y := 0; y < win.pixelsSize.Y; y++ {
// pixel data
v := regs.Datastream[i].Peek(y, static)
// colour source
col := fg
if win.colouriser.active && win.colouriser.tgt == i {
s := regs.Datastream[win.colouriser.src].Peek(y, static)
col = spec.GetColor(signal.ColorSignal(s))
} else if win.colourSource[i] > -1 {
s := regs.Datastream[win.colourSource[i]].Peek(y, static)
col = spec.GetColor(signal.ColorSignal(s))
}
// plot pixels
for x := 0; x < 8; x++ {
if y <= scanlines {
if (v<<x)&0x80 == 0x80 {
win.streamPixels[i].SetRGBA(x, y, col)
} else {
win.streamPixels[i].SetRGBA(x, y, bg)
}
} else {
win.streamPixels[i].SetRGBA(x, y, unused)
}
}
}
}
win.render()
}
func (win *winCDFStreams) render() {
for i := range win.streamTextures {
win.streamTextures[i].markForCreation()
win.streamTextures[i].render(win.streamPixels[i])
}
}
func (win *winCDFStreams) debuggerDraw() bool {
if !win.debuggerOpen {
return false
}
// do not open window if there is no valid cartridge debug bus available
bus := win.img.cache.VCS.Mem.Cart.GetRegistersBus()
if bus == nil {
return false
}
regs, ok := bus.GetRegisters().(cdf.Registers)
if !ok {
return false
}
staticBus := win.img.cache.VCS.Mem.Cart.GetStaticBus()
if staticBus == nil {
return false
}
static := staticBus.GetStatic()
imgui.SetNextWindowPosV(imgui.Vec2{X: 100, Y: 100}, imgui.ConditionFirstUseEver, imgui.Vec2{X: 0, Y: 0})
imgui.SetNextWindowSizeV(imgui.Vec2{X: 920, Y: 554}, imgui.ConditionFirstUseEver)
win.img.setReasonableWindowConstraints()
if imgui.BeginV(win.debuggerID(win.id()), &win.debuggerOpen, imgui.WindowFlagsHorizontalScrollbar) {
win.draw(regs, static)
}
win.debuggerGeom.update()
imgui.End()
return true
}
func (win *winCDFStreams) draw(regs cdf.Registers, static mapper.CartStatic) {
scaling := float32(win.img.prefs.guiFontSize.Get().(int)) / 10
if imgui.BeginChildV("##stream", imgui.Vec2{Y: imguiRemainingWinHeight() - win.optionsHeight}, false, imgui.WindowFlagsNone) {
win.updateStreams(regs, static)
// disable preview color. it will be turned on if drag and drop is being used this frame.
win.colouriser.active = false
for i := 0; i < len(win.streamTextures); i++ {
imgui.BeginGroup()
// styling for datastream buttons )including the image button)
imgui.PushStyleColor(imgui.StyleColorButton, win.img.cols.Transparent)
imgui.PushStyleColor(imgui.StyleColorButtonActive, win.img.cols.Transparent)
imgui.PushStyleColor(imgui.StyleColorButtonHovered, win.img.cols.Transparent)
imgui.PushStyleColor(imgui.StyleColorDragDropTarget, win.img.cols.Transparent)
imgui.PushStyleVarVec2(imgui.StyleVarFramePadding, imgui.Vec2{})
// using button for labelling
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.DataStreamNumLabel)
imgui.ButtonV(fmt.Sprintf("%02d", i), imgui.Vec2{X: float32(win.pixelsSize.X) * (scaling + 1)})
imgui.PopStyleColor()
// position of ImageButton() we'll use this to measure the position of
// the mouse in relation to the top/left of the button
pos := imgui.CursorScreenPos()
// Have to use ImageButton() rather than Image() because we want to use
// drag and drop
imgui.ImageButton(imgui.TextureID(win.streamTextures[i].getID()), imgui.Vec2{
X: float32(win.pixelsSize.X) * (scaling + 1),
Y: float32(win.pixelsSize.Y) * scaling,
})
if imgui.IsItemHovered() {
// quickly repeat previous drag and drop with a double click
if imgui.IsMouseDoubleClicked(0) {
win.colourSource[i] = win.colouriser.previousSrc
win.updateStreams(regs, static)
}
// clear assignment of color datastream
if imgui.IsMouseClicked(1) {
win.colourSource[i] = -1
win.updateStreams(regs, static)
}
}
// the name of the drag and drop rendezvous
const dragDropName = "DATASTREAM"
// data stream image can be dragged. the drag image is a paintbrush
imgui.PushStyleVarFloat(imgui.StyleVarPopupBorderSize, 0.0)
imgui.PushStyleColor(imgui.StyleColorPopupBg, win.img.cols.Transparent)
if imgui.BeginDragDropSource(imgui.DragDropFlagsNone) {
imgui.SetDragDropPayload(dragDropName, []byte{byte(i)}, imgui.ConditionAlways)
imgui.PushFont(win.img.fonts.largeFontAwesome)
imgui.Text(string(fonts.PaintBrush))
imgui.PopFont()
imgui.EndDragDropSource()
// drag and drop is active
win.colouriser.active = true
win.colouriser.src = i
}
imgui.PopStyleColor()
imgui.PopStyleVar()
// each datastream image can also be dropped onto
if imgui.BeginDragDropTarget() {
// drag and drop is hovering over a legitimate target
payload := imgui.AcceptDragDropPayload(dragDropName, imgui.DragDropFlagsAcceptPeekOnly)
if payload != nil {
// drag and drop is active. note that we may see the drop
// target before we see the drop source, so setting active here
// is required
win.colouriser.active = true
win.colouriser.tgt = i
win.updateStreams(regs, static)
}
// drag and drop has ended on a legitimate target
payload = imgui.AcceptDragDropPayload(dragDropName, imgui.DragDropFlagsNone)
if payload != nil {
win.colourSource[i] = int(payload[0])
win.colouriser.previousSrc = win.colouriser.src
win.updateStreams(regs, static)
}
imgui.EndDragDropTarget()
}
imgui.PopStyleVar()
imgui.PopStyleColorV(4)
win.img.imguiTooltip(func() {
imgui.Text("Datastream ")
imgui.SameLine()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.DisasmLocation)
imgui.Text(fmt.Sprintf("%d", i))
imgui.PopStyleColor()
imgui.Spacing()
imgui.Separator()
imgui.Spacing()
imgui.Text("Pointer:")
imgui.SameLine()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.DisasmAddress)
imgui.Text(fmt.Sprintf("%08x", regs.Datastream[i].AfterCALLFN))
imgui.PopStyleColor()
imgui.Text("Increment:")
imgui.SameLine()
imgui.PushStyleColor(imgui.StyleColorText, win.img.cols.DisasmAddress)
imgui.Text(fmt.Sprintf("%08x", regs.Datastream[i].Increment))
imgui.PopStyleColor()
// mouse position is used to decide which values in the stream
// to peek/show
p := imgui.MousePos()
p = p.Minus(pos)
const numOfAdditionalPeeks = 3
y := int(p.Y / scaling)
yTop := y - numOfAdditionalPeeks
if yTop < 0 {
yTop = 0
}
yBot := y + numOfAdditionalPeeks
if yBot >= int(win.scanlines) {
yBot = int(win.scanlines)
}
// test if mouse position intersects with the active part of
// the texture
if y >= 0 && y < int(win.scanlines) {
imgui.Spacing()
imgui.Separator()
imgui.Spacing()
// list of values
imgui.BeginGroup()
imgui.PushStyleVarFloat(imgui.StyleVarAlpha, 0.5)
for yy := yTop; yy < y; yy++ {
v := regs.Datastream[i].Peek(yy, static)
imgui.Text(fmt.Sprintf("%03d %c %02x", yy, fonts.CaretRight, v))
}
imgui.PopStyleVar()
v := regs.Datastream[i].Peek(y, static)
imgui.Text(fmt.Sprintf("%03d %c %02x", y, fonts.CaretRight, v))
imgui.PushStyleVarFloat(imgui.StyleVarAlpha, 0.5)
for yy := y + 1; yy <= yBot; yy++ {
v := regs.Datastream[i].Peek(yy, static)
imgui.Text(fmt.Sprintf("%03d %c %02x", yy, fonts.CaretRight, v))
}
imgui.PopStyleVar()
imgui.EndGroup()
// detail texture
imgui.SameLineV(0, 20)
// small offset to help center the detail with the "list of
// values" above
p := imgui.CursorScreenPos()
p.Y += imgui.CurrentStyle().FramePadding().Y
imgui.SetCursorScreenPos(p)
imgui.BeginGroup()
// crop the pixels from the underlying stream texture
detailCrop := image.Rect(0, y-numOfAdditionalPeeks, win.pixelsSize.X, y+numOfAdditionalPeeks+1)
detailPixels := win.streamPixels[i].SubImage(detailCrop).(*image.RGBA)
sz := detailPixels.Bounds().Size()
win.detailTexture.markForCreation()
win.detailTexture.render(detailPixels)
// height of image matches the height of the "list of
// values" above
h := imgui.FontSize() + (imgui.CurrentStyle().FramePadding().Y)
imgui.Image(imgui.TextureID(win.detailTexture.getID()), imgui.Vec2{
X: float32(sz.X) * h * 1.25,
Y: float32(sz.Y) * h,
})
imgui.EndGroup()
}
if win.colourSource[i] != -1 {
imgui.Spacing()
imgui.Separator()
imgui.Spacing()
imgui.Text(fmt.Sprintf("%c from datastream %d", fonts.PaintBrush, win.colourSource[i]))
}
}, true)
imgui.EndGroup()
imgui.SameLine()
}
}
imgui.EndChild()
win.optionsHeight = imguiMeasureHeight(func() {
imgui.Spacing()
imgui.Spacing()
imguiLabel("Stream length")
if win.trackScreen {
imgui.PushItemFlag(imgui.ItemFlagsDisabled, true)
imgui.PushStyleVarFloat(imgui.StyleVarAlpha, disabledAlpha)
}
imgui.PushItemWidth(200)
imgui.SliderInt("##streamlength", &win.scanlines, 100, specification.AbsoluteMaxScanlines)
imgui.PopItemWidth()
if win.trackScreen {
imgui.PopItemFlag()
imgui.PopStyleVar()
}
imgui.SameLineV(0, 20)
imgui.Checkbox("Track Screen Size", &win.trackScreen)
// clear colours button is sometimes disabled
imgui.SameLineV(0, 20)
enableClearColours := false
for _, v := range win.colourSource {
if v != -1 {
enableClearColours = true
break
}
}
if !enableClearColours {
imgui.PushItemFlag(imgui.ItemFlagsDisabled, true)
imgui.PushStyleVarFloat(imgui.StyleVarAlpha, disabledAlpha)
}
if imgui.Button("Clear Colours") {
win.clearColours()
}
if !enableClearColours {
imgui.PopStyleVar()
imgui.PopItemFlag()
}
})
}
func (win *winCDFStreams) clearColours() {
for i := range win.colourSource {
win.colourSource[i] = -1
}
}