-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A zoom container which isn't a viewport
This commit adds components::ZoomContainer which is an object which collaborates with a component that can internalize its draws based on scale and starting position. Defacto this will let us make the shortcircuit sample zoom way faster because we can just know start points and end points. The mouse gestures are, I think right. For a mac trackpad they are glorious. But we will need to adjust them for win/lin mice I'm sure.
- Loading branch information
Showing
3 changed files
with
342 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
/* | ||
* sst-jucegui - an open source library of juce widgets | ||
* built by Surge Synth Team. | ||
* | ||
* Copyright 2023-2024, various authors, as described in the GitHub | ||
* transaction log. | ||
* | ||
* sst-jucegui is released under the MIT license, as described | ||
* by "LICENSE.md" in this repository. This means you may use this | ||
* in commercial software if you are a JUCE Licensee. If you use JUCE | ||
* in the open source / GPL3 context, your combined work must be | ||
* released under GPL3. | ||
* | ||
* All source in sst-jucegui available at | ||
* https://github.com/surge-synthesizer/sst-jucegui | ||
*/ | ||
|
||
#ifndef SSTJUCEGUI_EXAMPLES_COMPONENT_DEMO_ZOOMCONTAINERDEMO_H | ||
#define SSTJUCEGUI_EXAMPLES_COMPONENT_DEMO_ZOOMCONTAINERDEMO_H | ||
|
||
#include <sst/jucegui/components/ZoomContainer.h> | ||
#include <sst/jucegui/components/NamedPanel.h> | ||
#include <sst/jucegui/components/WindowPanel.h> | ||
#include <sst/jucegui/util/DebugHelpers.h> | ||
#include "ExampleUtils.h" | ||
#include <cassert> | ||
|
||
struct ZoomContainerDemo : public sst::jucegui::components::WindowPanel | ||
{ | ||
static constexpr const char *name = "ZoomContainer"; | ||
|
||
template <bool DO_H, bool DO_V> | ||
struct PaintGrid : juce::Component, sst::jucegui::components::ZoomContainerClient | ||
{ | ||
juce::Component *associatedComponent() override { return this; } | ||
bool supportsVerticalZoom() const override { return DO_V; } | ||
bool supportsHorizontalZoom() const override { return DO_H; } | ||
float hZoomStart{0.f}, hZoomScale{1.f}; | ||
float vZoomStart{0.f}, vZoomScale{1.f}; | ||
void setHorizontalZoom(float pctStart, float zoomFactor) override | ||
{ | ||
assert(DO_H); | ||
hZoomStart = pctStart; | ||
hZoomScale = zoomFactor; | ||
repaint(); | ||
} | ||
void setVerticalZoom(float pctStart, float zoomFactor) override | ||
{ | ||
assert(DO_V); | ||
vZoomStart = pctStart; | ||
vZoomScale = zoomFactor; | ||
repaint(); | ||
} | ||
|
||
void paint(juce::Graphics &g) override | ||
{ | ||
g.fillAll(juce::Colour(40, 40, 70)); | ||
|
||
if (DO_H) | ||
{ | ||
// q = (i-zoomStart) * scale | ||
// q == 0 -> i = zoomStart | ||
// q = 1 -> i = 1/scale + zoomStart | ||
// but q is stepping in 0.25 so if we start at q=0 we | ||
// quantize. So we want to start at the 0.025 below | ||
// zoomstart | ||
auto start = std::floor(hZoomStart * 20) / 20; | ||
auto end = 1.0 / hZoomScale + hZoomStart + 0.025; | ||
bool first{true}; | ||
for (float i = start; i < end; i += 0.025) | ||
{ | ||
auto q = (i - hZoomStart) * hZoomScale; | ||
g.setColour(juce::Colours::red); | ||
g.drawVerticalLine(q * getWidth(), 0, getHeight()); | ||
g.setFont(12); | ||
g.setColour(juce::Colours::white); | ||
g.drawText(std::to_string((int)(i / 0.025)), q * getWidth(), 0, 60, 20, | ||
juce::Justification::centredLeft); | ||
} | ||
} | ||
|
||
if (DO_V) | ||
{ | ||
// q = (i-zoomStart) * scale | ||
// q == 0 -> i = zoomStart | ||
// q = 1 -> i = 1/scale + zoomStart | ||
// but q is stepping in 0.25 so if we start at q=0 we | ||
// quantize. So we want to start at the 0.025 below | ||
// zoomstart | ||
auto start = std::floor(vZoomStart * 20) / 20; | ||
auto end = 1.0 / vZoomScale + vZoomStart + 0.025; | ||
bool first{true}; | ||
for (float i = start; i < end; i += 0.025) | ||
{ | ||
auto q = (i - vZoomStart) * vZoomScale; | ||
g.setColour(juce::Colours::green); | ||
g.drawHorizontalLine(q * getHeight(), 0, getWidth()); | ||
g.setFont(12); | ||
g.setColour(juce::Colours::white); | ||
g.drawText(std::to_string((int)(i / 0.025)), 0, q * getHeight(), 60, 20, | ||
juce::Justification::centredLeft); | ||
} | ||
} | ||
g.setColour(juce::Colours::yellow); | ||
g.drawRect(getLocalBounds()); | ||
} | ||
}; | ||
|
||
ZoomContainerDemo() | ||
{ | ||
panelOne = std::make_unique<sst::jucegui::components::NamedPanel>("ZoomThis"); | ||
panelContents = std::make_unique<sst::jucegui::components::ZoomContainer>( | ||
std::make_unique<PaintGrid<true, false>>()); | ||
lowerPanelContents = std::make_unique<sst::jucegui::components::ZoomContainer>( | ||
std::make_unique<PaintGrid<true, true>>()); | ||
addAndMakeVisible(*panelOne); | ||
panelOne->addAndMakeVisible(*panelContents); | ||
panelOne->addAndMakeVisible(*lowerPanelContents); | ||
} | ||
|
||
void resized() override | ||
{ | ||
panelOne->setBounds(getLocalBounds().reduced(10)); | ||
auto plb = panelOne->getLocalBounds().reduced(20); | ||
|
||
panelContents->setBounds(plb.withHeight(plb.getHeight() / 2).reduced(10)); | ||
lowerPanelContents->setBounds( | ||
plb.withHeight(plb.getHeight() / 2).translated(0, plb.getHeight() / 2).reduced(10)); | ||
} | ||
|
||
std::unique_ptr<sst::jucegui::components::NamedPanel> panelOne; | ||
std::unique_ptr<sst::jucegui::components::ZoomContainer> panelContents; | ||
std::unique_ptr<sst::jucegui::components::ZoomContainer> lowerPanelContents; | ||
}; | ||
|
||
#endif // SST_JUCEGUI_ZOOMCONTAINER_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
/* | ||
* sst-jucegui - an open source library of juce widgets | ||
* built by Surge Synth Team. | ||
* | ||
* Copyright 2023-2024, various authors, as described in the GitHub | ||
* transaction log. | ||
* | ||
* sst-jucegui is released under the MIT license, as described | ||
* by "LICENSE.md" in this repository. This means you may use this | ||
* in commercial software if you are a JUCE Licensee. If you use JUCE | ||
* in the open source / GPL3 context, your combined work must be | ||
* released under GPL3. | ||
* | ||
* All source in sst-jucegui available at | ||
* https://github.com/surge-synthesizer/sst-jucegui | ||
*/ | ||
|
||
#ifndef INCLUDE_SST_JUCEGUI_COMPONENTS_ZOOMCONTAINER_H | ||
#define INCLUDE_SST_JUCEGUI_COMPONENTS_ZOOMCONTAINER_H | ||
|
||
#include <memory> | ||
#include <juce_gui_basics/juce_gui_basics.h> | ||
#include "ScrollBar.h" | ||
|
||
namespace sst::jucegui::components | ||
{ | ||
struct ZoomContainerClient | ||
{ | ||
virtual ~ZoomContainerClient() = default; | ||
virtual juce::Component *associatedComponent() = 0; | ||
virtual bool supportsVerticalZoom() const = 0; | ||
virtual bool supportsHorizontalZoom() const = 0; | ||
virtual void setHorizontalZoom(float pctStart, float zoomFactor) {} | ||
virtual void setVerticalZoom(float pctStart, float zoomFactor) {} | ||
}; | ||
struct ZoomContainer : juce::Component, juce::ScrollBar::Listener | ||
{ | ||
std::unique_ptr<ScrollBar> vScroll, hScroll; | ||
std::unique_ptr<ZoomContainerClient> contents; | ||
ZoomContainer(std::unique_ptr<ZoomContainerClient> &&c) : contents(std::move(c)) | ||
{ | ||
if (contents->supportsHorizontalZoom()) | ||
{ | ||
hScroll = std::make_unique<ScrollBar>(false); | ||
hScroll->setAutoHide(false); | ||
hScroll->setRangeLimits(0.0, 1.0, juce::NotificationType::dontSendNotification); | ||
hScroll->addListener(this); | ||
addAndMakeVisible(*hScroll); | ||
} | ||
if (contents->supportsVerticalZoom()) | ||
{ | ||
vScroll = std::make_unique<ScrollBar>(true); | ||
vScroll->setAutoHide(false); | ||
vScroll->setRangeLimits(0.0, 1.0, juce::NotificationType::dontSendNotification); | ||
vScroll->addListener(this); | ||
addAndMakeVisible(*vScroll); | ||
} | ||
addAndMakeVisible(*(contents->associatedComponent())); | ||
} | ||
|
||
static constexpr int sbw{6}; | ||
|
||
void resized() override | ||
{ | ||
auto bx = getLocalBounds(); | ||
|
||
if (hScroll && vScroll) | ||
{ | ||
auto hb = bx.withTrimmedTop(bx.getHeight() - sbw).withTrimmedRight(sbw); | ||
auto vb = bx.withTrimmedLeft(bx.getWidth() - sbw).withTrimmedBottom(sbw); | ||
bx = bx.withTrimmedBottom(sbw).withTrimmedRight(sbw); | ||
hScroll->setBounds(hb); | ||
vScroll->setBounds(vb); | ||
} | ||
else if (hScroll) | ||
{ | ||
auto hb = bx.withTrimmedTop(bx.getHeight() - sbw); | ||
bx = bx.withTrimmedBottom(sbw); | ||
hScroll->setBounds(hb); | ||
} | ||
else if (vScroll) | ||
{ | ||
auto vb = bx.withTrimmedLeft(bx.getWidth() - sbw); | ||
bx = bx.withTrimmedRight(sbw); | ||
vScroll->setBounds(vb); | ||
} | ||
contents->associatedComponent()->setBounds(bx); | ||
} | ||
|
||
void mouseMagnify(const juce::MouseEvent &event, float scaleFactor) override | ||
{ | ||
if (event.mods.isShiftDown()) | ||
{ | ||
adjustVerticalZoom(event.position, scaleFactor); | ||
} | ||
else | ||
{ | ||
adjustHorizontalZoom(event.position, scaleFactor); | ||
} | ||
Component::mouseMagnify(event, scaleFactor); | ||
} | ||
|
||
void mouseWheelMove(const juce::MouseEvent &event, | ||
const juce::MouseWheelDetails &wheel) override | ||
{ | ||
if (event.mods.isShiftDown() && event.mods.isAltDown()) | ||
{ | ||
// HZOM by -Delta Y | ||
if (contents->supportsHorizontalZoom()) | ||
{ | ||
adjustHorizontalZoom(event.position, 1.0 - wheel.deltaY); | ||
} | ||
return; | ||
} | ||
if (event.mods.isShiftDown()) | ||
{ | ||
// VZoom by delta Y | ||
if (contents->supportsVerticalZoom()) | ||
{ | ||
adjustVerticalZoom(event.position, 1.0 - wheel.deltaY); | ||
} | ||
return; | ||
} | ||
|
||
// Assume a 2D mouse so do what you think. This is just a joy on | ||
// a mac trackpad! | ||
if (fabs(wheel.deltaX) > fabs(wheel.deltaY)) | ||
{ | ||
if (hScroll) | ||
{ | ||
auto dy = wheel.deltaX; | ||
auto rs = hScroll->getCurrentRangeStart(); | ||
rs = std::clamp(rs + dy, 0., 1.); | ||
hScroll->setCurrentRangeStart(rs); | ||
} | ||
} | ||
else | ||
{ | ||
if (vScroll) | ||
{ | ||
auto dy = wheel.deltaY; | ||
auto rs = vScroll->getCurrentRangeStart(); | ||
rs = std::clamp(rs + dy, 0., 1.); | ||
vScroll->setCurrentRangeStart(rs); | ||
} | ||
} | ||
} | ||
|
||
void scrollBarMoved(juce::ScrollBar *scrollBarThatHasMoved, double newRangeStart) override | ||
{ | ||
if (scrollBarThatHasMoved == hScroll.get()) | ||
{ | ||
contents->setHorizontalZoom(newRangeStart, 1.0 / hScroll->getCurrentRangeSize()); | ||
} | ||
if (scrollBarThatHasMoved == vScroll.get()) | ||
{ | ||
contents->setVerticalZoom(newRangeStart, 1.0 / vScroll->getCurrentRangeSize()); | ||
} | ||
} | ||
void adjustVerticalZoom(const juce::Point<float> &p, float scaleFactor) | ||
{ | ||
auto rs = vScroll->getCurrentRangeStart(); | ||
auto re = vScroll->getCurrentRangeSize(); | ||
|
||
auto nre = re / scaleFactor; | ||
auto dre = re - nre; | ||
re = nre; | ||
|
||
auto mfac = std::clamp(p.getY() / (getHeight() - (hScroll ? sbw : 0)), 0.f, 1.f); | ||
|
||
rs += dre * mfac; | ||
|
||
rs = std::clamp(rs, 0., 1.); | ||
re = std::clamp(re, 0., 1.); | ||
|
||
contents->setVerticalZoom(rs, 1.0 / re); | ||
|
||
vScroll->setCurrentRange(rs, re, juce::NotificationType::dontSendNotification); | ||
vScroll->repaint(); | ||
} | ||
void adjustHorizontalZoom(const juce::Point<float> &p, float scaleFactor) | ||
{ | ||
auto rs = hScroll->getCurrentRangeStart(); | ||
auto re = hScroll->getCurrentRangeSize(); | ||
|
||
auto nre = re / scaleFactor; | ||
auto dre = re - nre; | ||
re = nre; | ||
|
||
auto mfac = std::clamp(p.getX() / (getWidth() - (vScroll ? sbw : 0)), 0.f, 1.f); | ||
|
||
rs += dre * mfac; | ||
|
||
rs = std::clamp(rs, 0., 1.); | ||
re = std::clamp(re, 0., 1.); | ||
|
||
contents->setHorizontalZoom(rs, 1.0 / re); | ||
|
||
hScroll->setCurrentRange(rs, re, juce::NotificationType::dontSendNotification); | ||
hScroll->repaint(); | ||
} | ||
}; | ||
} // namespace sst::jucegui::components | ||
#endif // SST_JUCEGUI_ZOOMCONTAINER_H |