Skip to content

Commit

Permalink
A zoom container which isn't a viewport
Browse files Browse the repository at this point in the history
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
baconpaul committed Aug 31, 2024
1 parent cada9ff commit 5b806fa
Show file tree
Hide file tree
Showing 3 changed files with 342 additions and 0 deletions.
2 changes: 2 additions & 0 deletions examples/component-demo/SSTJuceGuiDemo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "TextPushButtonDemo.h"
#include "SevenSegmentDemo.h"
#include "VUMeterDemo.h"
#include "ZoomContainerDemo.h"

struct SSTJuceGuiDemo : public juce::JUCEApplication
{
Expand Down Expand Up @@ -131,6 +132,7 @@ struct SSTJuceGuiDemo : public juce::JUCEApplication
mk<SevenSegmentDemo>();
mk<VUMeterDemo>();
mk<GlyphDemo>();
mk<ZoomContainerDemo>();
}
void paint(juce::Graphics &g) override { g.fillAll(juce::Colours::black); }
void resized() override
Expand Down
136 changes: 136 additions & 0 deletions examples/component-demo/ZoomContainerDemo.h
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
204 changes: 204 additions & 0 deletions include/sst/jucegui/components/ZoomContainer.h
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

0 comments on commit 5b806fa

Please sign in to comment.