GUI: display C64/AY/SAA wave macros differently

hopefully better
This commit is contained in:
tildearrow 2022-01-21 01:56:30 -05:00
parent e06911258e
commit 9307a14a1d
4 changed files with 199 additions and 10 deletions

View file

@ -699,6 +699,14 @@ const char* ssgEnvTypes[8]={
"Down Down Down", "Down.", "Down Up Down Up", "Down UP", "Up Up Up", "Up.", "Up Down Up Down", "Up DOWN" "Down Down Down", "Down.", "Down Up Down Up", "Down UP", "Up Up Up", "Up.", "Up Down Up Down", "Up DOWN"
}; };
const char* c64ShapeBits[5]={
"triangle", "saw", "pulse", "noise", NULL
};
const char* ayShapeBits[4]={
"tone", "noise", "envelope", NULL
};
#define P(x) if (x) { \ #define P(x) if (x) { \
modified=true; \ modified=true; \
e->notifyInsChange(curIns); \ e->notifyInsChange(curIns); \
@ -893,8 +901,9 @@ void FurnaceGUI::drawInsEdit() {
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
if (ins->type!=DIV_INS_FM) if (ImGui::BeginTabItem("Macros")) { if (ins->type!=DIV_INS_FM) if (ImGui::BeginTabItem("Macros")) {
float asFloat[128]; float asFloat[256];
float loopIndicator[128]; int asInt[256];
float loopIndicator[256];
// volume macro // volume macro
ImGui::Separator(); ImGui::Separator();
@ -1062,25 +1071,42 @@ void FurnaceGUI::drawInsEdit() {
} }
// wave macro // wave macro
int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?7:63; int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?3:63;
bool bitMode=false;
if (ins->type==DIV_INS_TIA) waveMax=15; if (ins->type==DIV_INS_TIA) waveMax=15;
if (ins->type==DIV_INS_C64) waveMax=8; if (ins->type==DIV_INS_C64) waveMax=4;
if (ins->type==DIV_INS_SAA1099) waveMax=3; if (ins->type==DIV_INS_SAA1099) waveMax=2;
if (waveMax>0) { if (waveMax>0) {
ImGui::Separator(); ImGui::Separator();
ImGui::Text("Waveform Macro"); ImGui::Text("Waveform Macro");
for (int i=0; i<ins->std.waveMacroLen; i++) { for (int i=0; i<ins->std.waveMacroLen; i++) {
asFloat[i]=ins->std.waveMacro[i]; asFloat[i]=ins->std.waveMacro[i];
if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930) {
asInt[i]=ins->std.waveMacro[i]+1;
} else {
asInt[i]=ins->std.waveMacro[i];
}
loopIndicator[i]=(ins->std.waveMacroLoop!=-1 && i>=ins->std.waveMacroLoop); loopIndicator[i]=(ins->std.waveMacroLoop!=-1 && i>=ins->std.waveMacroLoop);
} }
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f));
ImGui::PlotHistogram("##IWaveMacro",asFloat,ins->std.waveMacroLen,0,NULL,0,waveMax,ImVec2(400.0f*dpiScale,200.0f*dpiScale)); ImVec2 areaSize=ImVec2(400.0f*dpiScale,200.0f*dpiScale);
if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) {
areaSize=ImVec2(400.0f*dpiScale,waveMax*32.0f*dpiScale);
PlotBitfield("##IWaveMacro",asInt,ins->std.waveMacroLen,0,(ins->type==DIV_INS_C64)?c64ShapeBits:ayShapeBits,waveMax,areaSize);
bitMode=true;
} else {
ImGui::PlotHistogram("##IWaveMacro",asFloat,ins->std.waveMacroLen,0,NULL,0,waveMax,areaSize);
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
macroDragStart=ImGui::GetItemRectMin(); macroDragStart=ImGui::GetItemRectMin();
macroDragAreaSize=ImVec2(400.0f*dpiScale,200.0f*dpiScale); macroDragAreaSize=areaSize;
macroDragMin=0; macroDragMin=0;
macroDragMax=waveMax; macroDragMax=waveMax;
macroDragBitOff=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0;
macroDragBitMode=bitMode;
macroDragInitialValueSet=false;
macroDragInitialValue=false;
macroDragLen=ins->std.waveMacroLen; macroDragLen=ins->std.waveMacroLen;
macroDragActive=true; macroDragActive=true;
macroDragTarget=ins->std.waveMacro; macroDragTarget=ins->std.waveMacro;
@ -3402,10 +3428,34 @@ void FurnaceGUI::processDrags(int dragX, int dragY) {
int x=(dragX-macroDragStart.x)*macroDragLen/macroDragAreaSize.x; int x=(dragX-macroDragStart.x)*macroDragLen/macroDragAreaSize.x;
if (x<0) x=0; if (x<0) x=0;
if (x>=macroDragLen) x=macroDragLen-1; if (x>=macroDragLen) x=macroDragLen-1;
int y=round(macroDragMax-((dragY-macroDragStart.y)*(double(macroDragMax-macroDragMin)/(double)macroDragAreaSize.y))); int y;
if (macroDragBitMode) {
y=(int)(macroDragMax-((dragY-macroDragStart.y)*(double(macroDragMax-macroDragMin)/(double)macroDragAreaSize.y)));
} else {
y=round(macroDragMax-((dragY-macroDragStart.y)*(double(macroDragMax-macroDragMin)/(double)macroDragAreaSize.y)));
}
if (y>macroDragMax) y=macroDragMax; if (y>macroDragMax) y=macroDragMax;
if (y<macroDragMin) y=macroDragMin; if (y<macroDragMin) y=macroDragMin;
macroDragTarget[x]=y; if (macroDragBitMode) {
if (macroDragLastX!=x || macroDragLastY!=y) {
macroDragLastX=x;
macroDragLastY=y;
if (macroDragInitialValueSet) {
if (macroDragInitialValue) {
macroDragTarget[x]=(((macroDragTarget[x]+macroDragBitOff)&((1<<macroDragMax)-1))&(~(1<<y)))-macroDragBitOff;
} else {
macroDragTarget[x]=(((macroDragTarget[x]+macroDragBitOff)&((1<<macroDragMax)-1))|(1<<y))-macroDragBitOff;
}
} else {
macroDragInitialValue=(((macroDragTarget[x]+macroDragBitOff)&((1<<macroDragMax)-1))&(1<<y));
macroDragInitialValueSet=true;
macroDragTarget[x]=(((macroDragTarget[x]+macroDragBitOff)&((1<<macroDragMax)-1))^(1<<y))-macroDragBitOff;
}
macroDragTarget[x]&=(1<<macroDragMax)-1;
}
} else {
macroDragTarget[x]=y;
}
} }
} }
if (macroLoopDragActive) { if (macroLoopDragActive) {
@ -3471,6 +3521,11 @@ bool FurnaceGUI::loop() {
case SDL_MOUSEBUTTONUP: case SDL_MOUSEBUTTONUP:
if (macroDragActive || macroLoopDragActive || waveDragActive) modified=true; if (macroDragActive || macroLoopDragActive || waveDragActive) modified=true;
macroDragActive=false; macroDragActive=false;
macroDragBitMode=false;
macroDragInitialValue=false;
macroDragInitialValueSet=false;
macroDragLastX=-1;
macroDragLastY=-1;
macroLoopDragActive=false; macroLoopDragActive=false;
waveDragActive=false; waveDragActive=false;
if (selecting) { if (selecting) {
@ -4133,6 +4188,12 @@ FurnaceGUI::FurnaceGUI():
macroDragLen(0), macroDragLen(0),
macroDragMin(0), macroDragMin(0),
macroDragMax(0), macroDragMax(0),
macroDragLastX(-1),
macroDragLastY(-1),
macroDragBitOff(0),
macroDragBitMode(false),
macroDragInitialValueSet(false),
macroDragInitialValue(false),
macroDragActive(false), macroDragActive(false),
nextScroll(-1.0f), nextScroll(-1.0f),
nextAddScroll(0.0f), nextAddScroll(0.0f),

View file

@ -254,6 +254,11 @@ class FurnaceGUI {
int* macroDragTarget; int* macroDragTarget;
int macroDragLen; int macroDragLen;
int macroDragMin, macroDragMax; int macroDragMin, macroDragMax;
int macroDragLastX, macroDragLastY;
int macroDragBitOff;
bool macroDragBitMode;
bool macroDragInitialValueSet;
bool macroDragInitialValue;
bool macroDragActive; bool macroDragActive;
ImVec2 macroLoopDragStart; ImVec2 macroLoopDragStart;

View file

@ -1,4 +1,5 @@
#include "plot_nolerp.h" #include "plot_nolerp.h"
#include "imgui.h"
#ifndef IMGUI_DEFINE_MATH_OPERATORS #ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS
#endif #endif
@ -19,6 +20,21 @@ static float Plot_ArrayGetter(void* data, int idx)
return v; return v;
} }
struct FurnacePlotIntArrayGetterData
{
const int* Values;
int Stride;
FurnacePlotIntArrayGetterData(const int* values, int stride) { Values = values; Stride = stride; }
};
static int Plot_IntArrayGetter(void* data, int idx)
{
FurnacePlotIntArrayGetterData* plot_data = (FurnacePlotIntArrayGetterData*)data;
const int v = *(const int*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
return v;
}
int PlotNoLerpEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size) int PlotNoLerpEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
@ -145,3 +161,109 @@ void PlotNoLerp(const char* label, const float* values, int values_count, int va
FurnacePlotArrayGetterData data(values, stride); FurnacePlotArrayGetterData data(values, stride);
PlotNoLerpEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); PlotNoLerpEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
} }
int PlotBitfieldEx(const char* label, int (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char** overlay_text, int bits, ImVec2 frame_size)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return -1;
const ImGuiStyle& style = g.Style;
const ImGuiID id = window->GetID(label);
const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
if (frame_size.x == 0.0f)
frame_size.x = ImGui::CalcItemWidth();
if (frame_size.y == 0.0f)
frame_size.y = label_size.y + (style.FramePadding.y * 2);
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));
ImGui::ItemSize(total_bb, style.FramePadding.y);
if (!ImGui::ItemAdd(total_bb, 0, &frame_bb))
return -1;
const bool hovered = ImGui::ItemHoverable(frame_bb, id);
ImGui::RenderFrame(frame_bb.Min, frame_bb.Max, ImGui::GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
const int values_count_min = 1;
int idx_hovered = -1;
if (values_count >= values_count_min)
{
int res_w = ImMin((int)frame_size.x, values_count);
int item_count = values_count;
// Tooltip on hover
if (hovered && inner_bb.Contains(g.IO.MousePos))
{
const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
const int v_idx = (int)(t * item_count);
IM_ASSERT(v_idx >= 0 && v_idx < values_count);
//const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
//ImGui::SetTooltip("%d: %8.4g", v_idx, v0);
idx_hovered = v_idx;
}
const float t_step = 1.0f / (float)res_w;
float t0 = 0.0f;
ImVec2 tp0 = ImVec2( t0, 0.0f ); // Point in the normalized space of our target rectangle
const ImU32 col_base = ImGui::GetColorU32(ImGuiCol_PlotHistogram);
const ImU32 col_hovered = ImGui::GetColorU32(ImGuiCol_PlotHistogramHovered);
for (int n = 0; n < res_w; n++)
{
const float t1 = t0 + t_step;
const int v1_idx = (int)(t0 * item_count + 0.5f);
IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
const int v1 = values_getter(data, (v1_idx + values_offset) % values_count);
ImVec2 tp1 = ImVec2( t1, 0.0f );
for (int o = 0; o < bits; o++) {
tp0.y=float(bits-o)/float(bits);
tp1.y=float(bits-o-1)/float(bits);
// NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU.
ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, tp1);
if (pos1.x >= pos0.x + 2.0f)
pos1.x -= 1.0f;
if (pos1.y <= pos0.y - 2.0f)
pos1.y += 1.0f;
if (v1&(1<<o)) {
window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
}
}
tp0 = tp1;
t0 = t1;
}
}
// Text overlay
if (overlay_text) {
float lineHeight=ImGui::GetTextLineHeight()/2.0;
for (int i=0; i<bits && overlay_text[i]; i++) {
ImGui::PushStyleColor(ImGuiCol_Text,ImVec4(0,0,0,1.0f));
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x-1, frame_bb.Min.y-lineHeight-1), ImVec2(frame_bb.Max.x-1,frame_bb.Max.y+lineHeight-1), overlay_text[i], NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x-1, frame_bb.Min.y-lineHeight+1), ImVec2(frame_bb.Max.x-1,frame_bb.Max.y+lineHeight+1), overlay_text[i], NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x+1, frame_bb.Min.y-lineHeight-1), ImVec2(frame_bb.Max.x+1,frame_bb.Max.y+lineHeight-1), overlay_text[i], NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x+1, frame_bb.Min.y-lineHeight+1), ImVec2(frame_bb.Max.x+1,frame_bb.Max.y+lineHeight+1), overlay_text[i], NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
ImGui::PopStyleColor();
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y-lineHeight), ImVec2(frame_bb.Max.x,frame_bb.Max.y+lineHeight), overlay_text[i], NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
}
}
if (label_size.x > 0.0f)
ImGui::RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
// Return hovered index or -1 if none are hovered.
// This is currently not exposed in the public API because we need a larger redesign of the whole thing, but in the short-term we are making it available in PlotEx().
return idx_hovered;
}
void PlotBitfield(const char* label, const int* values, int values_count, int values_offset, const char** overlay_text, int bits, ImVec2 graph_size, int stride)
{
FurnacePlotIntArrayGetterData data(values, stride);
PlotBitfieldEx(label, &Plot_IntArrayGetter, (void*)&data, values_count, values_offset, overlay_text, bits, graph_size);
}

View file

@ -1,3 +1,4 @@
#include "imgui.h" #include "imgui.h"
void PlotNoLerp(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float)); void PlotNoLerp(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float));
void PlotBitfield(const char* label, const int* values, int values_count, int values_offset = 0, const char** overlay_text = NULL, int bits = 8, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float));