99#include " RuntimeFont.h"
1010#include " SkinModel.h"
1111#include " SurgeGUIEditor.h"
12+ #include " SurgeJUCEHelpers.h"
1213#include " SurgeGUIEditorTags.h"
1314#include " dsp/effects/ConditionerEffect.h"
1415#include " dsp/effects/ConvolutionEffect.h"
@@ -60,28 +61,127 @@ struct ConvolutionButton : public juce::Component
6061
6162 void mouseDown (const juce::MouseEvent &event) override
6263 {
64+ hideTooltip ();
65+
6366 if (event.mods .isMiddleButtonDown () && sge)
6467 {
6568 sge->frame ->mouseDown (event);
6669 return ;
6770 }
6871
72+ if (!event.mods .isPopupMenu ())
73+ {
74+ if (leftJog.contains (event.position ))
75+ {
76+ jogIR (-1 );
77+ return ;
78+ }
79+ else if (rightJog.contains (event.position ))
80+ {
81+ jogIR (1 );
82+ return ;
83+ }
84+ }
85+
6986 showIRMenu ();
7087 }
7188
72- bool isMousedOver = false ;
89+ bool isMousedOver{false };
90+ bool isJogLHovered{false }, isJogRHovered{false }, isNameHovered{false };
91+ juce::Rectangle<float > leftJog, rightJog, irNameRect;
92+
93+ int tooltipCountdown{-1 };
94+ bool tooltipShowing{false };
95+
96+ bool isNameTruncated () const
97+ {
98+ if (!sge)
99+ return false ;
100+ auto ft = sge->currentSkin ->fontManager ->getLatoAtSize (9 );
101+ auto textW = SST_STRING_WIDTH_FLOAT (ft, juce::String (irname));
102+ return textW > irNameRect.getWidth () - 2 ;
103+ }
104+
105+ void hideTooltip ()
106+ {
107+ tooltipCountdown = -1 ;
108+ if (sge && tooltipShowing)
109+ {
110+ sge->hideIRNameTooltip ();
111+ }
112+ tooltipShowing = false ;
113+ }
114+
115+ void shouldTooltip ()
116+ {
117+ if (tooltipCountdown < 0 )
118+ return ;
119+
120+ tooltipCountdown--;
121+
122+ if (tooltipCountdown == 0 )
123+ {
124+ tooltipCountdown = -1 ;
125+ if (sge && isNameTruncated ())
126+ {
127+ auto b = sge->frame ->getLocalArea (this , getLocalBounds ());
128+ sge->showIRNameTooltip (irname, b);
129+ tooltipShowing = true ;
130+ }
131+ }
132+ else
133+ {
134+ juce::Timer::callAfterDelay (100 , Surge::GUI::makeSafeCallback<ConvolutionButton>(
135+ this , [](auto *that) { that->shouldTooltip (); }));
136+ }
137+ }
73138
74139 void mouseEnter (const juce::MouseEvent &event) override
75140 {
76141 isMousedOver = true ;
77142 repaint ();
78143 }
144+
79145 void mouseExit (const juce::MouseEvent &event) override
80146 {
81147 isMousedOver = false ;
148+ isJogLHovered = false ;
149+ isJogRHovered = false ;
150+ isNameHovered = false ;
151+ hideTooltip ();
82152 repaint ();
83153 }
84154
155+ void mouseMove (const juce::MouseEvent &event) override
156+ {
157+ auto njl = leftJog.contains (event.position );
158+ auto njr = rightJog.contains (event.position );
159+ auto nnm = irNameRect.contains (event.position );
160+
161+ if (njl != isJogLHovered || njr != isJogRHovered || nnm != isNameHovered)
162+ {
163+ isJogLHovered = njl;
164+ isJogRHovered = njr;
165+ isNameHovered = nnm;
166+ repaint ();
167+ }
168+
169+ if (nnm)
170+ {
171+ if (tooltipCountdown < 0 && !tooltipShowing)
172+ {
173+ tooltipCountdown = 3 ;
174+ juce::Timer::callAfterDelay (100 ,
175+ Surge::GUI::makeSafeCallback<ConvolutionButton>(
176+ this , [](auto *that) { that->shouldTooltip (); }));
177+ }
178+ }
179+ else
180+ {
181+ hideTooltip ();
182+ }
183+ }
184+
85185 void paint (juce::Graphics &g) override
86186 {
87187 if (!fx)
@@ -97,16 +197,48 @@ struct ConvolutionButton : public juce::Component
97197 auto fgframeHov = skin->getColor (Colors::Effect::Grid::Scene::BackgroundHover);
98198 auto fgtextHov = skin->getColor (Colors::Effect::Grid::Scene::TextHover);
99199
100- auto irr = getLocalBounds ();
200+ auto irr = getLocalBounds ().toFloat ();
201+ auto h = irr.getHeight ();
101202
102- bool focused = isMousedOver || hasKeyboardFocus (true );
103- g.setColour (focused ? fgcolHov : fgcol);
104- g.fillRect (irr);
105- g.setColour (focused ? fgframeHov : fgframe);
203+ leftJog = irr.withRight (h);
204+ rightJog = irr.withLeft (irr.getWidth () - h);
205+ irNameRect = irr.withTrimmedLeft (h).withTrimmedRight (h);
206+
207+ float triO = 3 ;
208+
209+ // Fill each region individually for per-region hover.
210+ g.setColour (isJogLHovered ? fgcolHov : fgcol);
211+ g.fillRect (leftJog);
212+ g.setColour (isJogRHovered ? fgcolHov : fgcol);
213+ g.fillRect (rightJog);
214+ bool nameFocused = isNameHovered || hasKeyboardFocus (true );
215+ g.setColour (nameFocused ? fgcolHov : fgcol);
216+ g.fillRect (irNameRect);
217+
218+ // Single outer border.
219+ g.setColour (fgframe);
106220 g.drawRect (irr);
107- g.setColour (focused ? fgtextHov : fgtext);
221+
222+ // Left jog arrow
223+ g.setColour (isJogLHovered ? fgtextHov : fgtext);
224+ auto triL = juce::Path ();
225+ triL.addTriangle (leftJog.getTopRight ().translated (-triO, triO),
226+ leftJog.getBottomRight ().translated (-triO, -triO),
227+ leftJog.getCentre ().withX (leftJog.getX () + triO));
228+ g.fillPath (triL);
229+
230+ // Right jog arrow
231+ g.setColour (isJogRHovered ? fgtextHov : fgtext);
232+ auto triR = juce::Path ();
233+ triR.addTriangle (rightJog.getTopLeft ().translated (triO, triO),
234+ rightJog.getBottomLeft ().translated (triO, -triO),
235+ rightJog.getCentre ().withX (rightJog.getX () + rightJog.getWidth () - triO));
236+ g.fillPath (triR);
237+
238+ // IR name text
239+ g.setColour (nameFocused ? fgtextHov : fgtext);
108240 g.setFont (skin->fontManager ->getLatoAtSize (9 ));
109- g.drawText (juce::String (irname), irr , juce::Justification::centred);
241+ g.drawText (juce::String (irname), irNameRect , juce::Justification::centred);
110242 }
111243
112244 void showIRMenu ()
@@ -293,6 +425,40 @@ struct ConvolutionButton : public juce::Component
293425 sge->synth ->load_fx_needed = true ;
294426 }
295427
428+ void jogIR (int dir)
429+ {
430+ if (!storage || storage->irOrdering .empty ())
431+ return ;
432+
433+ // Find current IR's position in the display ordering by name.
434+ int currentPos = -1 ;
435+ for (int i = 0 ; i < (int )storage->irOrdering .size (); i++)
436+ {
437+ if (storage->ir_list [storage->irOrdering [i]].name == irname)
438+ {
439+ currentPos = i;
440+ break ;
441+ }
442+ }
443+
444+ int newPos;
445+ if (currentPos < 0 )
446+ {
447+ // Current IR not in the list, start from beginning or end.
448+ newPos = (dir > 0 ) ? 0 : (int )storage->irOrdering .size () - 1 ;
449+ }
450+ else
451+ {
452+ newPos = currentPos + dir;
453+ if (newPos < 0 )
454+ newPos = (int )storage->irOrdering .size () - 1 ;
455+ if (newPos >= (int )storage->irOrdering .size ())
456+ newPos = 0 ;
457+ }
458+
459+ loadIR (storage->irOrdering [newPos]);
460+ }
461+
296462 void loadIR (int id)
297463 {
298464 if (id >= 0 && id < storage->ir_list .size () && sge)
0 commit comments