From bfb8a50e3ad40414f170b9e87c7f1d634986a5a9 Mon Sep 17 00:00:00 2001 From: Mario Kruselj <mario.kruselj@gmail.com> Date: Fri, 27 Aug 2021 01:22:44 +0200 Subject: [PATCH] Fix a number of step seq UI/UX regressions Addresses #4877 Also: - properly update state of Pencil button when toggling MSEG/formula overlays by clicking on the display - rename msegEditSwitch SGE member to something more universal - make elementary rects of LFO widget member variables, remove &within argument from pain functions as a consequence - step seq value is added directly on mouse down, not just on mouse move --- src/gui/SurgeGUIEditor.cpp | 46 ++- src/gui/SurgeGUIEditor.h | 2 +- src/gui/widgets/LFOAndStepDisplay.cpp | 498 +++++++++++++++++++------- src/gui/widgets/LFOAndStepDisplay.h | 22 +- 4 files changed, 418 insertions(+), 150 deletions(-) diff --git a/src/gui/SurgeGUIEditor.cpp b/src/gui/SurgeGUIEditor.cpp index 9306310a5a0..ad4a5e52539 100644 --- a/src/gui/SurgeGUIEditor.cpp +++ b/src/gui/SurgeGUIEditor.cpp @@ -889,7 +889,7 @@ void SurgeGUIEditor::openOrRecreateEditor() ** There are a collection of member states we need to reset */ polydisp = nullptr; - msegEditSwitch = nullptr; + lfoEditSwitch = nullptr; lfoNameLabel = nullptr; midiLearnOverlay = nullptr; @@ -1124,14 +1124,14 @@ void SurgeGUIEditor::openOrRecreateEditor() } case Surge::Skin::Connector::NonParameterConnection::MSEG_EDITOR_OPEN: { - msegEditSwitch = layoutComponentForSkin(skinCtrl, tag_mseg_edit); - setAccessibilityInformationByTitleAndAction(msegEditSwitch->asJuceComponent(), + lfoEditSwitch = layoutComponentForSkin(skinCtrl, tag_mseg_edit); + setAccessibilityInformationByTitleAndAction(lfoEditSwitch->asJuceComponent(), "Show MSEG Editor", "Show"); - auto msejc = dynamic_cast<juce::Component *>(msegEditSwitch); + auto msejc = dynamic_cast<juce::Component *>(lfoEditSwitch); jassert(msejc); msejc->setVisible(false); - msegEditSwitch->setValue(isAnyOverlayPresent(MSEG_EDITOR) || - isAnyOverlayPresent(FORMULA_EDITOR)); + lfoEditSwitch->setValue(isAnyOverlayPresent(MSEG_EDITOR) || + isAnyOverlayPresent(FORMULA_EDITOR)); auto q = modsource_editor[current_scene]; if ((q >= ms_lfo1 && q <= ms_lfo6) || (q >= ms_slfo1 && q <= ms_slfo6)) { @@ -4575,9 +4575,9 @@ void SurgeGUIEditor::lfoShapeChanged(int prior, int curr) if (prior != curr || prior == lt_mseg || curr == lt_mseg || prior == lt_formula || curr == lt_formula) { - if (msegEditSwitch) + if (lfoEditSwitch) { - auto msejc = dynamic_cast<juce::Component *>(msegEditSwitch); + auto msejc = dynamic_cast<juce::Component *>(lfoEditSwitch); msejc->setVisible(curr == lt_mseg || curr == lt_formula); } } @@ -4626,6 +4626,12 @@ void SurgeGUIEditor::closeMSEGEditor() { broadcastMSEGState(); dismissEditorOfType(MSEG_EDITOR); + + if (lfoEditSwitch) + { + lfoEditSwitch->setValue(0.0); + lfoEditSwitch->asJuceComponent()->repaint(); + } } } void SurgeGUIEditor::toggleMSEGEditor() @@ -4707,6 +4713,12 @@ void SurgeGUIEditor::showFormulaEditorDialog() addJuceEditorOverlay(std::move(pt), "Formula Editor", FORMULA_EDITOR, skinCtrl->getRect(), true, []() {}); + + if (lfoEditSwitch) + { + lfoEditSwitch->setValue(1.0); + lfoEditSwitch->asJuceComponent()->repaint(); + } } void SurgeGUIEditor::closeFormulaEditorDialog() @@ -4714,6 +4726,12 @@ void SurgeGUIEditor::closeFormulaEditorDialog() if (isAnyOverlayPresent(FORMULA_EDITOR)) { dismissEditorOfType(FORMULA_EDITOR); + + if (lfoEditSwitch) + { + lfoEditSwitch->setValue(0.0); + lfoEditSwitch->asJuceComponent()->repaint(); + } } } @@ -4797,17 +4815,17 @@ void SurgeGUIEditor::showMSEGEditor() auto skinCtrl = currentSkin->getOrCreateControlForConnector(conn); addJuceEditorOverlay(std::move(mse), title, MSEG_EDITOR, skinCtrl->getRect(), true, [this]() { - if (msegEditSwitch) + if (lfoEditSwitch) { - msegEditSwitch->setValue(0.0); - msegEditSwitch->asJuceComponent()->repaint(); + lfoEditSwitch->setValue(0.0); + lfoEditSwitch->asJuceComponent()->repaint(); } }); - if (msegEditSwitch) + if (lfoEditSwitch) { - msegEditSwitch->setValue(1.0); - msegEditSwitch->asJuceComponent()->repaint(); + lfoEditSwitch->setValue(1.0); + lfoEditSwitch->asJuceComponent()->repaint(); } } diff --git a/src/gui/SurgeGUIEditor.h b/src/gui/SurgeGUIEditor.h index fe00899fc2c..8591bf60074 100644 --- a/src/gui/SurgeGUIEditor.h +++ b/src/gui/SurgeGUIEditor.h @@ -505,7 +505,7 @@ class SurgeGUIEditor : public Surge::GUI::IComponentTagValue::Listener, friend struct Surge::Overlays::PatchStoreDialog; friend struct Surge::Widgets::MainFrame; - Surge::GUI::IComponentTagValue *msegEditSwitch = nullptr; + Surge::GUI::IComponentTagValue *lfoEditSwitch = nullptr; int typeinResetCounter = -1; std::string typeinResetLabel = ""; diff --git a/src/gui/widgets/LFOAndStepDisplay.cpp b/src/gui/widgets/LFOAndStepDisplay.cpp index 66eb5505850..6743489dc56 100644 --- a/src/gui/widgets/LFOAndStepDisplay.cpp +++ b/src/gui/widgets/LFOAndStepDisplay.cpp @@ -45,32 +45,39 @@ struct TimeB }; LFOAndStepDisplay::LFOAndStepDisplay() {} + +void LFOAndStepDisplay::resized() +{ + outer = getLocalBounds(); + left_panel = outer.withRight(outer.getX() + lpsize + margin); + main_panel = outer.withLeft(left_panel.getRight()); + waveform_display = main_panel.withTrimmedLeft(19 - margin).withTrimmedRight(margin); + + ss_shift_bg = main_panel.toFloat().withWidth(10).withHeight(32).translated(2, 0).withCentre( + juce::Point<float>(main_panel.getX() + 10, main_panel.getCentreY())); + ss_shift_left = ss_shift_bg.reduced(1, 1).withBottom(ss_shift_bg.getY() + 16); + ss_shift_right = ss_shift_left.translated(0, 16); +} + void LFOAndStepDisplay::paint(juce::Graphics &g) { TimeB paint("outerPaint"); - auto outer = getLocalBounds(); - auto leftPanel = outer.withRight(outer.getX() + lpsize + margin); - auto mainPanel = outer.withLeft(leftPanel.getRight()); if (ss && lfodata->shape.val.i == lt_stepseq) { - paintStepSeq(g, mainPanel); + paintStepSeq(g); } else { - paintWaveform(g, mainPanel); + paintWaveform(g); } - paintTypeSelector(g, leftPanel); + paintTypeSelector(g); } -void LFOAndStepDisplay::paintWaveform(juce::Graphics &g, const juce::Rectangle<int> &within) +void LFOAndStepDisplay::paintWaveform(juce::Graphics &g) { TimeB mainTimer("-- paintWaveform"); - auto mainPanel = within.withTrimmedLeft(19 - margin).withTrimmedRight(margin); - auto pathPanel = mainPanel.withTrimmedTop(0).withTrimmedBottom(0); - // g.setColour(juce::Colours::blue); - // g.drawRect(mainPanel, 1); juce::Path deactPath, path, eupath, edpath; bool drawBeats = isAnythingTemposynced(); @@ -138,6 +145,7 @@ void LFOAndStepDisplay::paintWaveform(juce::Graphics &g, const juce::Rectangle<i LFOStorage deactivateStorage; bool hasFullWave = false, waveIsAmpWave = false; + if (lfodata->rate.deactivated) { hasFullWave = true; @@ -179,13 +187,14 @@ void LFOAndStepDisplay::paintWaveform(juce::Graphics &g, const juce::Rectangle<i } bool drawEnvelope{true}; + if (skin->hasColor(Colors::LFO::Waveform::Background)) { g.setColour(skin->getColor(Colors::LFO::Waveform::Background)); - g.fillRect(pathPanel); + g.fillRect(waveform_display); } - int minSamples = (1 << 0) * (int)(mainPanel.getWidth()); + int minSamples = (1 << 0) * (int)(waveform_display.getWidth()); int totalSamples = std::max((int)minSamples, (int)(totalEnvTime * samplerate / BLOCK_SIZE)); float drawnTime = totalSamples * samplerate_inv * BLOCK_SIZE; @@ -196,6 +205,7 @@ void LFOAndStepDisplay::paintWaveform(juce::Graphics &g, const juce::Rectangle<i int susCountdown = -1; float priorval = 0.f, priorwval = 0.f; + for (int i = 0; i < totalSamples; i += averagingWindow) { float val = 0; @@ -205,11 +215,16 @@ void LFOAndStepDisplay::paintWaveform(juce::Graphics &g, const juce::Rectangle<i float maxval = -1000000, maxwval = -1000000; float firstval; float lastval; + for (int s = 0; s < averagingWindow; s++) { tlfo->process_block(); + if (tFullWave) + { tFullWave->process_block(); + } + if (susCountdown < 0 && tlfo->env_state == lfoeg_stuck) { susCountdown = susTime * samplerate / BLOCK_SIZE; @@ -217,8 +232,11 @@ void LFOAndStepDisplay::paintWaveform(juce::Graphics &g, const juce::Rectangle<i else if (susCountdown == 0 && tlfo->env_state == lfoeg_stuck) { tlfo->release(); + if (tFullWave) + { tFullWave->release(); + } } else if (susCountdown > 0) { @@ -226,21 +244,31 @@ void LFOAndStepDisplay::paintWaveform(juce::Graphics &g, const juce::Rectangle<i } val += tlfo->get_output(modIndex); + if (tFullWave) { auto v = tFullWave->get_output(modIndex); + minwval = std::min(v, minwval); maxwval = std::max(v, maxwval); wval += v; } + if (s == 0) + { firstval = tlfo->get_output(modIndex); + } + if (s == averagingWindow - 1) + { lastval = tlfo->get_output(modIndex); + } + minval = std::min(tlfo->get_output(modIndex), minval); maxval = std::max(tlfo->get_output(modIndex), maxval); eval += tlfo->env_val * lfodata->magnitude.get_extended(lfodata->magnitude.val.f); } + val = val / averagingWindow; wval = wval / averagingWindow; eval = eval / averagingWindow; @@ -248,21 +276,26 @@ void LFOAndStepDisplay::paintWaveform(juce::Graphics &g, const juce::Rectangle<i wval = ((-wval + 1.0f) * 0.5 * 0.8 + 0.1) * valScale; minwval = ((-minwval + 1.0f) * 0.5 * 0.8 + 0.1) * valScale; maxwval = ((-maxwval + 1.0f) * 0.5 * 0.8 + 0.1) * valScale; + float euval = ((-eval + 1.0f) * 0.5 * 0.8 + 0.1) * valScale; float edval = ((eval + 1.0f) * 0.5 * 0.8 + 0.1) * valScale; - float xc = valScale * i / totalSamples; if (i == 0) { path.startNewSubPath(xc, val); eupath.startNewSubPath(xc, euval); + if (!isUnipolar() && (lfodata->shape.val.i != lt_envelope)) + { edpath.startNewSubPath(xc, edval); + } + if (tFullWave) { deactPath.startNewSubPath(xc, wval); } + priorval = val; priorwval = wval; } @@ -274,11 +307,13 @@ void LFOAndStepDisplay::paintWaveform(juce::Graphics &g, const juce::Rectangle<i // Make sure we draw one closest to prior first. See #1438 float firstval = minval; float secondval = maxval; + if (priorval - minval < maxval - priorval) { firstval = maxval; secondval = minval; } + path.lineTo(xc - 0.1 * valScale / totalSamples, firstval); path.lineTo(xc + 0.1 * valScale / totalSamples, secondval); @@ -302,38 +337,50 @@ void LFOAndStepDisplay::paintWaveform(juce::Graphics &g, const juce::Rectangle<i } } } + if (lfodata->shape.val.i == lt_formula) { drawEnvelope = tlfo->formulastate.useEnvelope; } + tlfo->completedModulation(); + delete tlfo; + if (tFullWave) { tFullWave->completedModulation(); delete tFullWave; } - auto at = juce::AffineTransform() - .scale(pathPanel.getWidth() / valScale, pathPanel.getHeight() / valScale) - .translated(pathPanel.getTopLeft()); + auto at = + juce::AffineTransform() + .scale(waveform_display.getWidth() / valScale, waveform_display.getHeight() / valScale) + .translated(waveform_display.getTopLeft()); for (int i = -1; i < 2; ++i) { float off = i * 0.4 + 0.5; // so that/s 0.1, 0.5, 0.9 float x0 = 0, y0 = valScale * off, x1 = valScale, y1 = valScale * off; + at.transformPoint(x0, y0); at.transformPoint(x1, y1); + if (i == 0) + { g.setColour(skin->getColor(Colors::LFO::Waveform::Center)); + } else + { g.setColour(skin->getColor(Colors::LFO::Waveform::Bounds)); + } g.drawLine(x0, y0, x1, y1, 1); } auto pointColor = skin->getColor(Colors::LFO::Waveform::Dots); int nxd = 61, nyd = 9; + for (int xd = 0; xd < nxd; xd++) { float normx = 1.f * xd / (nxd - 1) * 0.99; @@ -345,7 +392,7 @@ void LFOAndStepDisplay::paintWaveform(juce::Graphics &g, const juce::Rectangle<i float normy = 1.f * yd / (nyd - 1); float dotPointX = normx * valScale, dotPointY = (0.8 * normy + 0.1) * valScale; at.transformPoint(dotPointX, dotPointY); - float esize = 0.5; + float esize = 1.f; float xoff = (xd == 0 ? esize : 0); g.setColour(pointColor); g.fillEllipse(dotPointX - esize, dotPointY - esize, esize, esize); @@ -356,7 +403,11 @@ void LFOAndStepDisplay::paintWaveform(juce::Graphics &g, const juce::Rectangle<i { g.setColour(skin->getColor(Colors::LFO::Waveform::Envelope)); g.strokePath(eupath, juce::PathStrokeType(1.f), at); - g.strokePath(edpath, juce::PathStrokeType(1.f), at); + + if (!isUnipolar()) + { + g.strokePath(edpath, juce::PathStrokeType(1.f), at); + } } if (drawBeats) @@ -503,7 +554,7 @@ void LFOAndStepDisplay::paintWaveform(juce::Graphics &g, const juce::Rectangle<i /* * In 1.8 I think we don't want to show the key release point in the simulated wave - * with the MSEG but I wrote it to debug and we may change our mid so keeping this code + * with the MSEG but I wrote it to debug and we may change our mind so keeping this code * here */ if (msegRelease && false) @@ -519,7 +570,7 @@ void LFOAndStepDisplay::paintWaveform(juce::Graphics &g, const juce::Rectangle<i } } -void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g, const juce::Rectangle<int> &mainPanel) +void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g) { auto shadowcol = skin->getColor(Colors::LFO::StepSeq::ColumnShadow); auto stepMarker = skin->getColor(Colors::LFO::StepSeq::Step::Fill); @@ -538,7 +589,7 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g, const juce::Rectangle<in // set my coordinate system so that 0,0 is at splitpoint moved over with room for jogs auto shiftTranslate = - juce::AffineTransform().translated(mainPanel.getTopLeft()).translated(19, 0); + juce::AffineTransform().translated(main_panel.getTopLeft()).translated(19, 0); { juce::Graphics::ScopedSaveState gs(g); @@ -547,7 +598,7 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g, const juce::Rectangle<in for (int i = 0; i < n_stepseqsteps; i++) { juce::Rectangle<float> rstep(1.f * i * scale, 1.f * margin, scale, - mainPanel.getHeight() - margin2), + main_panel.getHeight() - margin2), gstep; // Draw an outline in the shadow color @@ -588,6 +639,7 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g, const juce::Rectangle<in gstep = gstep.withTrimmedTop(1).withTrimmedLeft(1).withTrimmedRight( i == n_stepseqsteps - 1 ? 1 : 0); + if (maski) { fillr(gstep, gatecolor); @@ -614,6 +666,7 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g, const juce::Rectangle<in fillr(rstep.withTrimmedLeft(1).reduced(0, 1).withTrimmedRight( i == n_stepseqsteps - 1 ? 1 : 0), stepcolor); + steprect[i] = rstep; // draw the midpoint line @@ -623,9 +676,11 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g, const juce::Rectangle<in auto fr = rstep.toFloat().withTop(cy).withHeight(1.f); fillr(fr, shadowcol); } + // Now draw the value auto v = rstep; int p1, p2; + if (isUnipolar()) { auto sv = std::max(ss->steps[i], 0.f); @@ -636,9 +691,11 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g, const juce::Rectangle<in p1 = v.getBottom() - (int)((float)0.5f + v.getHeight() * (0.50f + 0.5f * ss->steps[i])); p2 = v.getCentreY(); + auto ttop = std::min(p1, p2); auto tbottom = std::max(p1, p2); - v = v.withTop(ttop).withBottom(tbottom); + + v = v.withTop(ttop).withBottom(tbottom).withTrimmedRight(1).translated(1, 0); } if (lfodata->rate.deactivated && @@ -664,8 +721,8 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g, const juce::Rectangle<in fillr(loopStartRect, grabMarker); fillr(loopEndRect, grabMarker); - g.setColour(grabMarker); + // continue drawing loop marker vertical lines using the same grabMarker color g.drawLine(loopStartRect.getX() + 1, steprect[0].getY(), loopStartRect.getX() + 1, loopStartRect.getY()); g.drawLine(loopEndRect.getRight(), steprect[0].getY(), loopEndRect.getRight(), @@ -686,50 +743,58 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g, const juce::Rectangle<in rect_steps = steprect[0].withRight(steprect[n_stepseqsteps - 1].getRight()); rect_steps_retrig = gaterect[0].withRight(gaterect[n_stepseqsteps - 1].getRight()); - ss_shift_left = mainPanel.toFloat().withWidth(10).withHeight(32).translated(2, 0).withCentre( - juce::Point<float>(mainPanel.getX() + 10, mainPanel.getCentreY())); - + // step seq shift background frame g.setColour(skin->getColor(Colors::LFO::StepSeq::Button::Border)); - g.fillRect(ss_shift_left); - ss_shift_left = ss_shift_left.reduced(1, 1).withBottom(ss_shift_left.getY() + 16); + g.fillRect(ss_shift_bg); - float triO = 2; - if (ss_shift_hover == 1) + // step seq shift left + if (stepSeqShiftHover == 0) g.setColour(skin->getColor(Colors::LFO::StepSeq::Button::Hover)); else g.setColour(skin->getColor(Colors::LFO::StepSeq::Button::Background)); + g.fillRect(ss_shift_left); + + float triO = 2; auto triL = juce::Path(); + auto triR = juce::Path(); + triL.addTriangle(ss_shift_left.getTopRight().translated(-triO, triO), ss_shift_left.getBottomRight().translated(-triO, -triO), ss_shift_left.getCentre().withX(ss_shift_left.getX() + triO)); + g.setColour(skin->getColor(Colors::LFO::StepSeq::Button::Arrow)); - if (ss_shift_hover == 1 && skin->getVersion() >= 2) + + if (stepSeqShiftHover == 0 && skin->getVersion() >= 2) { g.setColour(skin->getColor(Colors::LFO::StepSeq::Button::ArrowHover)); } g.fillPath(triL); - ss_shift_right = ss_shift_left.translated(0, 16); - if (ss_shift_hover == 2) + // step seq shift right + if (stepSeqShiftHover == 1) g.setColour(skin->getColor(Colors::LFO::StepSeq::Button::Hover)); else g.setColour(skin->getColor(Colors::LFO::StepSeq::Button::Background)); + g.fillRect(ss_shift_right); - auto triR = juce::Path(); + triR.addTriangle( ss_shift_right.getTopLeft().translated(triO, triO), ss_shift_right.getBottomLeft().translated(triO, -triO), ss_shift_right.getCentre().withX(ss_shift_right.getX() + ss_shift_right.getWidth() - triO)); + g.setColour(skin->getColor(Colors::LFO::StepSeq::Button::Arrow)); - if (ss_shift_hover == 2 && skin->getVersion() >= 2) + + if (stepSeqShiftHover == 1 && skin->getVersion() >= 2) { g.setColour(skin->getColor(Colors::LFO::StepSeq::Button::ArrowHover)); } + g.fillPath(triR); - // OK now we have everything drawn we want to draw the LFO. This is similar to the LFO + // OK now we have everything drawn we want to draw the step seq. This is similar to the LFO // code above but with very different scaling in time since we need to match the steps no // matter the rate @@ -747,6 +812,7 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g, const juce::Rectangle<in float floorrate = -1.2; // can't be const - we mod it float displayRate = lfodata->rate.val.f; const float twotofloor = powf(2.0, -1.2); // so copy value here + if (lfodata->rate.temposync) { /* @@ -811,9 +877,11 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g, const juce::Rectangle<in float maxval = -1000000; float firstval; float lastval; + for (int s = 0; s < averagingWindow; s++) { tlfo->process_block(); + if (susCountdown < 0 && tlfo->env_state == lfoeg_stuck) { susCountdown = susTime * samplerate / BLOCK_SIZE; @@ -828,39 +896,58 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g, const juce::Rectangle<in } val += tlfo->get_output(0); + if (s == 0) + { firstval = tlfo->get_output(0); + } + if (s == averagingWindow - 1) + { lastval = tlfo->get_output(0); + } + minval = std::min(tlfo->get_output(0), minval); maxval = std::max(tlfo->get_output(0), maxval); eval += tlfo->env_val * lfodata->magnitude.get_extended(lfodata->magnitude.val.f); } + val = val / averagingWindow; eval = eval / averagingWindow; if (isUnipolar()) + { val = val * 2.0 - 1.0; + } val = ((-val + 1.0f) * 0.5) * valScale; + float euval = ((-eval + 1.0f) * 0.5) * valScale; float edval = ((eval + 1.0f) * 0.5) * valScale; - float xc = valScale * i / (cycleSamples * n_stepseqsteps); + if (i == 0) { path.startNewSubPath(xc, val); eupath.startNewSubPath(xc, euval); + if (!isUnipolar()) + { edpath.startNewSubPath(xc, edval); + } } else { path.lineTo(xc, val); eupath.lineTo(xc, euval); - edpath.lineTo(xc, edval); + + if (!isUnipolar()) + { + edpath.lineTo(xc, edval); + } } } + delete tlfo; auto q = boxo; @@ -872,8 +959,13 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g, const juce::Rectangle<in auto tfpath = tf; g.setColour(skin->getColor(Colors::LFO::StepSeq::Envelope)); + g.strokePath(eupath, juce::PathStrokeType(1.0), tfpath); - g.strokePath(edpath, juce::PathStrokeType(1.0), tfpath); + + if (!isUnipolar()) + { + g.strokePath(edpath, juce::PathStrokeType(1.0), tfpath); + } g.setColour(skin->getColor(Colors::LFO::StepSeq::Wave)); g.strokePath(path, juce::PathStrokeType(1.0), tfpath); @@ -924,6 +1016,7 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g, const juce::Rectangle<in { int detailedMode = Surge::Storage::getUserDefaultValue( storage, Surge::Storage::HighPrecisionReadouts, 0); + if (detailedMode) { prec = 6; @@ -995,16 +1088,16 @@ void LFOAndStepDisplay::paintStepSeq(juce::Graphics &g, const juce::Rectangle<in } } -void LFOAndStepDisplay::paintTypeSelector(juce::Graphics &g, const juce::Rectangle<int> &within) +void LFOAndStepDisplay::paintTypeSelector(juce::Graphics &g) { if (!typeImg) { g.setColour(juce::Colours::crimson); - g.fillRect(within); + g.fillRect(left_panel); return; } - auto leftpanel = within.translated(0, margin + 2); + auto leftpanel = left_panel.translated(0, margin + 2); auto type = lfodata->shape.val.i; auto off = lfodata->shape.val.i * 76; { @@ -1026,8 +1119,8 @@ void LFOAndStepDisplay::paintTypeSelector(juce::Graphics &g, const juce::Rectang if (lfoTypeHover >= 0) { auto off = lfoTypeHover * 76; - auto dt = typeImgHover; + if (lfoTypeHover == type) { dt = typeImgHoverOn; @@ -1039,11 +1132,83 @@ void LFOAndStepDisplay::paintTypeSelector(juce::Graphics &g, const juce::Rectang auto at = juce::AffineTransform() .translated(leftpanel.getX(), leftpanel.getY()) .translated(0, -off); + g.reduceClipRegion(leftpanel.getX(), leftpanel.getY(), 51, 76); + dt->draw(g, 1.0, at); } } } + +void LFOAndStepDisplay::setStepToDefault(const juce::MouseEvent &event) +{ + for (int i = 0; i < n_stepseqsteps; ++i) + { + if (steprect[i].contains(event.position)) + { + ss->steps[i] = 0.f; + repaint(); + } + } +} + +void LFOAndStepDisplay::setStepValue(const juce::MouseEvent &event) +{ + keyModMult = 0; + + int quantStep = 12; + + if (!storage->isStandardTuning && storage->currentScale.count > 1) + { + quantStep = storage->currentScale.count; + } + + for (int i = 0; i < n_stepseqsteps; ++i) + { + auto r = steprect[i]; + + if (r.contains(event.position)) + { + draggedStep = i; + + float f; + + if (isUnipolar()) + { + f = limit01((r.getBottom() - event.position.y) / r.getHeight()); + } + else + { + f = limitpm1(2 * (r.getCentreY() - event.position.y) / r.getHeight()); + } + + if (event.mods.isShiftDown()) + { + keyModMult = quantStep; // only shift pressed + + if (event.mods.isAltDown()) + { + keyModMult = quantStep * 2; // shift+alt pressed + + f *= quantStep * 2; + f = floor(f + 0.5); + f *= (1.f / (quantStep * 2)); + } + else + { + f *= quantStep; + f = floor(f + 0.5); + f *= (1.f / quantStep); + } + } + + ss->steps[i] = f; + + repaint(); + } + } +} + void LFOAndStepDisplay::onSkinChanged() { if (!(skin && skinControl && associatedBitmapStore)) @@ -1058,8 +1223,8 @@ void LFOAndStepDisplay::onSkinChanged() void LFOAndStepDisplay::mouseDown(const juce::MouseEvent &event) { - auto sge = firstListenerOfType<SurgeGUIEditor>(); + for (int i = 0; i < n_lfo_types; ++i) { if (shaperect[i].contains(event.position.toInt())) @@ -1067,45 +1232,70 @@ void LFOAndStepDisplay::mouseDown(const juce::MouseEvent &event) if (event.mods.isPopupMenu()) { notifyControlModifierClicked(event.mods); + return; } + if (i != lfodata->shape.val.i) { auto prior = lfodata->shape.val.i; + lfodata->shape.val.i = i; + sge->refresh_mod(); sge->broadcastPluginAutomationChangeFor(&(lfodata->shape)); + repaint(); + sge->lfoShapeChanged(prior, i); } + return; } } - if (isMSEG() && sge) + if (waveform_display.contains(event.position.toInt()) && sge) { - if (event.mods.isPopupMenu()) - showMSEGPopupMenu(); - else - sge->toggleMSEGEditor(); - } + if (isMSEG()) + { + if (event.mods.isPopupMenu()) + { + showMSEGPopupMenu(); + } + else + { + sge->toggleMSEGEditor(); + } + } - if (isFormula() && sge) - { - sge->toggleFormulaEditorDialog(); + if (isFormula()) + { + sge->toggleFormulaEditorDialog(); + } } if (isStepSequencer()) { dragMode = NONE; + + if (event.mods.isCtrlDown()) + { + setStepToDefault(event); + dragMode = RESET_VALUE; + + return; + } + if (ss_shift_left.contains(event.position)) { float t = ss->steps[0]; + for (int i = 0; i < (n_stepseqsteps - 1); i++) { ss->steps[i] = ss->steps[i + 1]; assert((i >= 0) && (i < n_stepseqsteps)); } + ss->steps[n_stepseqsteps - 1] = t; ss->trigmask = (((ss->trigmask & 0x000000000000fffe) >> 1) | (((ss->trigmask & 1) << 15) & 0xffff)) | @@ -1115,16 +1305,20 @@ void LFOAndStepDisplay::mouseDown(const juce::MouseEvent &event) (((ss->trigmask & 0x100000000) << 15) & 0xffff00000000)); repaint(); + return; } + if (ss_shift_right.contains(event.position)) { float t = ss->steps[n_stepseqsteps - 1]; + for (int i = (n_stepseqsteps - 2); i >= 0; i--) { ss->steps[i + 1] = ss->steps[i]; assert((i >= 0) && (i < n_stepseqsteps)); } + ss->steps[0] = t; ss->trigmask = (((ss->trigmask & 0x0000000000007fff) << 1) | (((ss->trigmask & 0x0000000000008000) >> 15) & 0xffff)) | @@ -1132,14 +1326,18 @@ void LFOAndStepDisplay::mouseDown(const juce::MouseEvent &event) (((ss->trigmask & 0x0000000080000000) >> 15) & 0xffff0000)) | (((ss->trigmask & 0x00007fff00000000) << 1) | (((ss->trigmask & 0x0000800000000000) >> 15) & 0xffff00000000)); + repaint(); + return; } + if (loopStartRect.contains(event.position)) { dragMode = LOOP_START; return; } + if (loopEndRect.contains(event.position)) { dragMode = LOOP_END; @@ -1158,50 +1356,64 @@ void LFOAndStepDisplay::mouseDown(const juce::MouseEvent &event) } else { + setStepValue(event); dragMode = VALUES; } } } + for (int i = 0; i < n_stepseqsteps; ++i) { auto r = gaterect[i]; + if (r.contains(event.position)) { dragMode = TRIGGERS; + uint64_t maski = ss->trigmask & (UINT64_C(1) << i); uint64_t maski16 = ss->trigmask & (UINT64_C(1) << (i + 16)); uint64_t maski32 = ss->trigmask & (UINT64_C(1) << (i + 32)); - uint64_t maskOn = 0; uint64_t maskOff = 0xFFFFFFFFFFFFFFFF; - if (maski) - { - maskOn = (UINT64_C(1) << (i + 16)); - maskOff = ~maski; - dragTrigger0 = UINT64_C(1) << 16; - } - else if (maski16) - { - maskOn = (UINT64_C(1) << (i + 32)); - maskOff = ~maski16; - dragTrigger0 = UINT64_C(1) << 32; - } - else if (maski32) + + if (!event.mods.isAnyModifierKeyDown()) { - maskOn = 0; - maskOff = ~maski32; - dragTrigger0 = 0; + if (maski || maski16 || maski32) + { + maskOn = 0; + maskOff = ~maski & ~maski16 & ~maski32; + dragTrigger0 = 0; + } + else + { + maskOn = (UINT64_C(1) << (i + 0)); + maskOff = ss->trigmask; + dragTrigger0 = UINT64_C(1); + } } - else + + if (event.mods.isRightButtonDown() || event.mods.isShiftDown()) { - maskOn = (UINT64_C(1) << (i + 0)); - maskOff = ss->trigmask; - dragTrigger0 = UINT64_C(1); + if (maski || ~maski) + { + maskOn = (UINT64_C(1) << (i + 16)); + maskOff = ~maski; + dragTrigger0 = UINT64_C(1) << 16; + } + + if (maski16) + { + maskOn = (UINT64_C(1) << (i + 32)); + maskOff = ~maski16; + dragTrigger0 = UINT64_C(1) << 32; + } } ss->trigmask &= maskOff; ss->trigmask |= maskOn; + repaint(); + return; } } @@ -1211,12 +1423,15 @@ void LFOAndStepDisplay::mouseDown(const juce::MouseEvent &event) void LFOAndStepDisplay::mouseExit(const juce::MouseEvent &event) { lfoTypeHover = -1; + stepSeqShiftHover = -1; + repaint(); } void LFOAndStepDisplay::mouseMove(const juce::MouseEvent &event) { int nextHover = -1; + for (int i = 0; i < n_lfo_types; ++i) { if (shaperect[i].contains(event.position.toInt())) @@ -1224,13 +1439,39 @@ void LFOAndStepDisplay::mouseMove(const juce::MouseEvent &event) nextHover = i; } } + if (nextHover != lfoTypeHover) { lfoTypeHover = nextHover; + repaint(); + + // We hover over the type so if (lfoTypeHover >= 0) { - // We aver over the type so + return; + } + } + + if (ss_shift_left.contains(event.position)) + { + nextHover = 0; + } + + if (ss_shift_right.contains(event.position)) + { + nextHover = 1; + } + + if (nextHover != stepSeqShiftHover) + { + stepSeqShiftHover = nextHover; + + repaint(); + + // We hover over the stepseq jog so + if (stepSeqShiftHover >= 0) + { return; } } @@ -1247,23 +1488,32 @@ void LFOAndStepDisplay::mouseDrag(const juce::MouseEvent &event) if (event.mods.isPopupMenu()) { notifyControlModifierClicked(event.mods); + return; } + if (i != lfodata->shape.val.i) { auto prior = lfodata->shape.val.i; + lfodata->shape.val.i = i; + sge->refresh_mod(); sge->broadcastPluginAutomationChangeFor(&(lfodata->shape)); + repaint(); + sge->lfoShapeChanged(prior, i); } + return; } } if (!isStepSequencer()) + { return; + } if (dragMode != NONE && event.getDistanceFromDragStart() > 0) { @@ -1272,6 +1522,7 @@ void LFOAndStepDisplay::mouseDrag(const juce::MouseEvent &event) juce::Desktop::getInstance().getMainMouseSource().enableUnboundedMouseMovement(true); } } + switch (dragMode) { case NONE: @@ -1287,14 +1538,17 @@ void LFOAndStepDisplay::mouseDrag(const juce::MouseEvent &event) { int sign = dragMode == LOOP_START ? 1 : -1; int loopStart = -1; + for (int i = 0; i < n_stepseqsteps; ++i) { auto r = steprect[i]; + if (r.contains(event.position.x + sign * r.getWidth() * 0.5, r.getCentreY())) { loopStart = i; } } + if (dragMode == LOOP_START) { if (ss->loop_start != loopStart && loopStart >= 0) @@ -1313,71 +1567,42 @@ void LFOAndStepDisplay::mouseDrag(const juce::MouseEvent &event) } break; } + case RESET_VALUE: + { + if (event.mods.isCtrlDown()) + { + setStepToDefault(event); + return; + } + break; + } case TRIGGERS: { for (int i = 0; i < n_stepseqsteps; ++i) { auto r = gaterect[i]; auto otm = ss->trigmask; + if (r.contains(event.position)) { auto off = UINT64_MAX & ~(UINT64_C(1) << i) & ~(UINT64_C(1) << (i + 16)) & ~(UINT64_C(1) << (i + 32)); auto on = dragTrigger0 << i; + ss->trigmask &= off; ss->trigmask |= on; } + if (ss->trigmask != otm) + { repaint(); + } } break; } case VALUES: { - keyModMult = 0; - int quantStep = 12; - - if (!storage->isStandardTuning && storage->currentScale.count > 1) - quantStep = storage->currentScale.count; - - for (int i = 0; i < n_stepseqsteps; ++i) - { - auto r = steprect[i]; - if (r.contains(event.position)) - { - draggedStep = i; - float f; - if (isUnipolar()) - { - f = limit01((r.getBottom() - event.position.y) / r.getHeight()); - } - else - { - f = limitpm1(2 * (r.getCentreY() - event.position.y) / r.getHeight()); - } - - if (event.mods.isShiftDown()) - { - keyModMult = quantStep; // only shift pressed - - if (event.mods.isAltDown()) - { - keyModMult = quantStep * 2; // shift+alt pressed - f *= quantStep * 2; - f = floor(f + 0.5); - f *= (1.f / (quantStep * 2)); - } - else - { - f *= quantStep; - f = floor(f + 0.5); - f *= (1.f / quantStep); - } - } - ss->steps[i] = f; - repaint(); - } - } + setStepValue(event); break; } } @@ -1392,6 +1617,7 @@ void LFOAndStepDisplay::mouseUp(const juce::MouseEvent &event) juce::Desktop::getInstance().getMainMouseSource().enableUnboundedMouseMovement(false); } } + if (dragMode == ARROW) { auto rmStepStart = arrowStart; @@ -1403,17 +1629,25 @@ void LFOAndStepDisplay::mouseUp(const juce::MouseEvent &event) // find out if a microtuning is loaded and store the scale length for Alt-drag // quantization to scale degrees keyModMult = 0; + int quantStep = 12; if (!storage->isStandardTuning && storage->currentScale.count > 1) + { quantStep = storage->currentScale.count; + } for (int i = 0; i < 16; ++i) { if (steprect[i].contains(rmStepStart)) + { startStep = i; + } + if (steprect[i].contains(rmStepCurr)) + { endStep = i; + } }; int s = startStep, e = endStep; @@ -1445,6 +1679,7 @@ void LFOAndStepDisplay::mouseUp(const juce::MouseEvent &event) if (s != e) { float ds = (fs - fe) / (s - e); + for (int q = s; q <= e; q++) { float f = ss->steps[s] + (q - s) * ds; @@ -1456,6 +1691,7 @@ void LFOAndStepDisplay::mouseUp(const juce::MouseEvent &event) if (event.mods.isAltDown()) { keyModMult = quantStep * 2; // shift+alt pressed + f *= quantStep * 2; f = floor(f + 0.5); f *= (1.f / (quantStep * 2)); @@ -1478,6 +1714,7 @@ void LFOAndStepDisplay::mouseUp(const juce::MouseEvent &event) dragMode = NONE; draggedStep = -1; + repaint(); } @@ -1485,21 +1722,17 @@ void LFOAndStepDisplay::mouseDoubleClick(const juce::MouseEvent &event) { if (isStepSequencer()) { - for (int i = 0; i < n_stepseqsteps; ++i) - { - if (steprect[i].contains(event.position)) - { - ss->steps[i] = 0.f; - repaint(); - } - } + setStepToDefault(event); } } + void LFOAndStepDisplay::mouseWheelMove(const juce::MouseEvent &event, const juce::MouseWheelDetails &wheel) { if (!isStepSequencer()) + { return; + } float delta = wheel.deltaX - (wheel.isReversed ? 1 : -1) * wheel.deltaY; #if MAC @@ -1507,16 +1740,21 @@ void LFOAndStepDisplay::mouseWheelMove(const juce::MouseEvent &event, #endif if (event.mods.isShiftDown()) + { delta *= 0.1; + } if (delta == 0) + { return; + } for (int i = 0; i < n_stepseqsteps; ++i) { if (steprect[i].contains(event.position)) { auto v = ss->steps[i] + delta; + if (isUnipolar()) { v = limit01(v); @@ -1525,7 +1763,9 @@ void LFOAndStepDisplay::mouseWheelMove(const juce::MouseEvent &event, { v = limitpm1(v); } + ss->steps[i] = v; + repaint(); } } diff --git a/src/gui/widgets/LFOAndStepDisplay.h b/src/gui/widgets/LFOAndStepDisplay.h index b80376a079c..fd95a5a03b3 100644 --- a/src/gui/widgets/LFOAndStepDisplay.h +++ b/src/gui/widgets/LFOAndStepDisplay.h @@ -29,9 +29,10 @@ struct LFOAndStepDisplay : public juce::Component, public WidgetBaseMixin<LFOAnd { LFOAndStepDisplay(); void paint(juce::Graphics &g) override; - void paintWaveform(juce::Graphics &g, const juce::Rectangle<int> &within); - void paintStepSeq(juce::Graphics &g, const juce::Rectangle<int> &within); - void paintTypeSelector(juce::Graphics &g, const juce::Rectangle<int> &within); + void paintWaveform(juce::Graphics &g); + void paintStepSeq(juce::Graphics &g); + void paintTypeSelector(juce::Graphics &g); + void resized() override; float value; float getValue() const override { return value; } @@ -41,6 +42,7 @@ struct LFOAndStepDisplay : public juce::Component, public WidgetBaseMixin<LFOAnd bool isMSEG() { return lfodata->shape.val.i == lt_mseg; } bool isFormula() { return lfodata->shape.val.i == lt_formula; } bool isUnipolar() { return lfodata->unipolar.val.b; } + void invalidateIfIdIsInRange(int j) {} void invalidateIfAnythingIsTemposynced() { @@ -61,6 +63,9 @@ struct LFOAndStepDisplay : public juce::Component, public WidgetBaseMixin<LFOAnd return drawBeats; } + void setStepToDefault(const juce::MouseEvent &event); + void setStepValue(const juce::MouseEvent &event); + int tsNum{4}, tsDen{4}; void setTimeSignature(int num, int den) { @@ -102,9 +107,11 @@ struct LFOAndStepDisplay : public juce::Component, public WidgetBaseMixin<LFOAnd ARROW, LOOP_START, LOOP_END, + RESET_VALUE, TRIGGERS, - VALUES + VALUES, } dragMode{NONE}; + int draggedStep{-1}; int keyModMult; uint64_t dragTrigger0; @@ -127,10 +134,13 @@ struct LFOAndStepDisplay : public juce::Component, public WidgetBaseMixin<LFOAnd std::array<juce::Rectangle<int>, n_lfo_types> shaperect; std::array<juce::Rectangle<float>, n_stepseqsteps> steprect, gaterect; + + juce::Rectangle<int> outer, left_panel, main_panel, waveform_display; juce::Rectangle<float> loopStartRect, loopEndRect, rect_steps, rect_steps_retrig; - juce::Rectangle<float> ss_shift_left, ss_shift_right; + juce::Rectangle<float> ss_shift_bg, ss_shift_left, ss_shift_right; + int lfoTypeHover{-1}; - int ss_shift_hover{-1}; + int stepSeqShiftHover{-1}; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LFOAndStepDisplay); };