diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 79c567a1..707765e7 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -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" }; +const char* c64ShapeBits[5]={ + "triangle", "saw", "pulse", "noise", NULL +}; + +const char* ayShapeBits[4]={ + "tone", "noise", "envelope", NULL +}; + #define P(x) if (x) { \ modified=true; \ e->notifyInsChange(curIns); \ @@ -893,8 +901,9 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndTabItem(); } if (ins->type!=DIV_INS_FM) if (ImGui::BeginTabItem("Macros")) { - float asFloat[128]; - float loopIndicator[128]; + float asFloat[256]; + int asInt[256]; + float loopIndicator[256]; // volume macro ImGui::Separator(); @@ -1062,25 +1071,42 @@ void FurnaceGUI::drawInsEdit() { } // 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_C64) waveMax=8; - if (ins->type==DIV_INS_SAA1099) waveMax=3; + if (ins->type==DIV_INS_C64) waveMax=4; + if (ins->type==DIV_INS_SAA1099) waveMax=2; if (waveMax>0) { ImGui::Separator(); ImGui::Text("Waveform Macro"); for (int i=0; istd.waveMacroLen; 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); } 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)) { macroDragStart=ImGui::GetItemRectMin(); - macroDragAreaSize=ImVec2(400.0f*dpiScale,200.0f*dpiScale); + macroDragAreaSize=areaSize; macroDragMin=0; 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; macroDragActive=true; macroDragTarget=ins->std.waveMacro; @@ -3402,10 +3428,34 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { int x=(dragX-macroDragStart.x)*macroDragLen/macroDragAreaSize.x; if (x<0) x=0; 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 (yValues + (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) { ImGuiContext& g = *GImGui; @@ -145,3 +161,109 @@ void PlotNoLerp(const char* label, const float* values, int values_count, int va FurnacePlotArrayGetterData data(values, stride); 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<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 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); +} diff --git a/src/gui/plot_nolerp.h b/src/gui/plot_nolerp.h index 8e4ea4ae..ec4d5836 100644 --- a/src/gui/plot_nolerp.h +++ b/src/gui/plot_nolerp.h @@ -1,3 +1,4 @@ #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)); \ No newline at end of file +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)); \ No newline at end of file