Skip to content

Commit

Permalink
Add some layout math (#144)
Browse files Browse the repository at this point in the history
Ideally this would move to sst-jucegui this week and also
get a json reader, but this is how far I got before 1.0
and I can now merge it

Basically hlist and vlist is way way easier
  • Loading branch information
baconpaul authored Jan 20, 2025
1 parent 8ec2a71 commit 79e574f
Show file tree
Hide file tree
Showing 5 changed files with 362 additions and 57 deletions.
50 changes: 30 additions & 20 deletions src/ui/dahdsr-components.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include "ui-constants.h"
#include "sst/jucegui/components/RuledLabel.h"

#include "ui/layout/Layout.h"

namespace baconpaul::six_sines::ui
{
namespace jcmp = sst::jucegui::components;
Expand Down Expand Up @@ -94,34 +96,42 @@ template <typename Comp, typename PatchPart> struct DAHDSRComponents

juce::Rectangle<int> layoutDAHDSRAt(int x, int y)
{
if (!titleLab)
if (!titleLab || !slider[0])
return {};

auto lh = uicTitleLabelHeight;
auto c = asComp();
auto h = c->getHeight() - y;
auto q = h - uicLabelHeight - uicLabelGap - lh;
auto w = uicSliderWidth;
namespace jlo = sst::jucegui::layout;

auto lo = jlo::VList()
.at(x, y)
.withHeight(asComp()->getHeight() - y)
.withWidth(nels * uicSliderWidth);

positionTitleLabelAt(x, y, nels * uicSliderWidth, titleLab);
lo.add(titleLabelLayout(titleLab));
auto sliders = jlo::HList().expandToFill();

auto bx = juce::Rectangle<int>(x, y + lh, w, h - lh);
for (int i = 0; i < nels; ++i)
{
if (!slider[i])
continue;
slider[i]->setBounds(bx.withHeight(q).withTrimmedTop(uicSliderWidth));
auto sliderStack = jlo::VList().withWidth(uicSliderWidth);

if (i == 0)
triggerButton->setBounds(bx.withHeight(uicSliderWidth).reduced(2));
if (i % 2)
{
shapes[i / 2]->setBounds(bx.withHeight(uicSliderWidth));
}
lab[i]->setBounds(bx.withTrimmedTop(q + uicLabelGap));
bx = bx.translated(uicSliderWidth, 0);
sliderStack.add(
jlo::Component(*triggerButton).withHeight(uicSliderWidth).insetBy(2));
else if (i % 2)
sliderStack.add(jlo::Component(*shapes[i / 2]).withHeight(uicSliderWidth));
else
sliderStack.addGap(uicSliderWidth);

sliderStack.add(jlo::Component(*slider[i]).expandToFill());

sliderStack.addGap(uicLabelGap);
sliderStack.add(jlo::Component(*lab[i]).withHeight(uicLabelHeight));

sliders.add(sliderStack);
}
bx = bx.translated(-uicSliderWidth, 0);
return juce::Rectangle<int>(x, y, 0, 0).withLeft(bx.getRight()).withBottom(bx.getBottom());

lo.add(sliders);
auto usedSpace = lo.doLayout();
return usedSpace.translated(usedSpace.getWidth(), 0);
}

static constexpr int nels{6}; // dahdsr
Expand Down
251 changes: 251 additions & 0 deletions src/ui/layout/Layout.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
/*
* Six Sines
*
* A synth with audio rate modulation.
*
* Copyright 2024-2025, Paul Walker and Various authors, as described in the github
* transaction log.
*
* This source repo is released under the MIT license, but has
* GPL3 dependencies, as such the combined work will be
* released under GPL3.
*
* The source code and license are at https://github.com/baconpaul/six-sines
*/

#ifndef BACONPAUL_SIX_SINES_UI_LAYOUT_LAYOUT_H
#define BACONPAUL_SIX_SINES_UI_LAYOUT_LAYOUT_H

#include <iostream>
#include <cassert>

/*
* This is an experiment to see if we can figure out something which works for more props
*/
namespace sst::jucegui::layout
{
// A#define LOLOG(...) std::cout << __VA_ARGS__ << std::endl;
#define LOLOG(...) ((void)0);
struct LayoutComponent
{
using loc_t = LayoutComponent;

loc_t at(int x, int y)
{
auto res = *this;
res.x = x;
res.y = y;
return res;
}

loc_t withHeight(int h)
{
auto res = *this;
res.h = h;
return res;
}

loc_t withWidth(int w)
{
auto res = *this;
res.w = w;
return res;
}

loc_t insetBy(int xi) { return insetBy(xi, xi); }
loc_t insetBy(int xi, int yi)
{
auto res = *this;
res.insetx = xi;
res.insety = yi;
return res;
}

loc_t expandToFill()
{
auto res = *this;
res.isFilling = true;
return res;
}

enum LayoutMechanism
{
LEAF,
VLIST,
HLIST
} mechanism;

loc_t withLayoutMechanism(LayoutMechanism m)
{
auto res = *this;
res.mechanism = m;
return res;
}

loc_t withComponent(juce::Component &c)
{
auto res = *this;
res.component = juce::Component::SafePointer(&c);
return res;
}

loc_t withAutoGap(int g)
{
assert(mechanism == VLIST || mechanism == HLIST);
auto res = *this;
res.autoGap = g;
return res;
}

void add(const loc_t &l)
{
if (autoGap > 0 && !children.empty())
{
addGap(autoGap);
}

children.push_back(l);
}

void addGap(int size)
{
if (mechanism == VLIST)
{
children.push_back(loc_t().withLayoutMechanism(LayoutComponent::LEAF).withHeight(size));
}
else if (mechanism == HLIST)
{
children.push_back(loc_t().withLayoutMechanism(LayoutComponent::LEAF).withWidth(size));
}
else
{
assert(false && "addGap only works with hlist/vlist right now");
}
}

juce::Rectangle<int> doLayout(const std::string &pfx = "+--")
{
switch (mechanism)
{
case LEAF:
{
auto res = juce::Rectangle<int>(x, y, w, h).reduced(insetx, insety);
if (component)
component->setBounds(res);
LOLOG(pfx << " LEAF " << (component ? component->getTitle() : "-gap-") << " @ "
<< res.toString());
return res;
}
break;
case HLIST:
case VLIST:
{
std::string tag = (mechanism == HLIST ? "hlist" : "vlist");

LOLOG(pfx << " " << tag << " x==" << x << " y=" << y << " w=" << w << " h=" << h);
int fillingCount{0}, unfillingSpace{0};
for (auto &c : children)
{
if (c.isFilling)
{
fillingCount++;
LOLOG(pfx << " " << tag << " Filling Element");
}
else
{
auto consumes = (mechanism == HLIST ? c.w : c.h);
unfillingSpace += consumes;
LOLOG(pfx << " " << tag << " Specified element consumes=" << consumes
<< " ufs=" << unfillingSpace);
}
}
auto leftoverSpace = (mechanism == HLIST ? w : h) - unfillingSpace;
auto fillingItemSize = fillingCount > 0 ? (leftoverSpace / fillingCount) : 0;
LOLOG(pfx << " " << tag << " fc=" << fillingCount << " fis=" << fillingItemSize);

auto pos = mechanism == VLIST ? y : x;
for (auto &c : children)
{
if (c.isFilling)
{
if (mechanism == HLIST)
{
c.w = std::min(fillingItemSize, leftoverSpace);
}
else
{
c.h = std::min(fillingItemSize, leftoverSpace);
}

leftoverSpace -= fillingItemSize;
}

if (mechanism == VLIST)
{
if (c.w == 0)
c.w = w;
c.x = x;
c.y = pos;
LOLOG(pfx << " " << tag << " - setting element to " << c.x << "," << c.y
<< " with dims=" << c.w << "," << c.h << " and pos=" << pos);
pos += c.h;
LOLOG(pfx << " " << tag << " Post set pos = " << pos);
}
else
{
if (c.h == 0)
c.h = h;
c.x = pos;
c.y = y;
LOLOG(pfx << " " << tag << " - setting element to " << c.x << "," << c.y
<< " with dums=" << c.w << "," << c.h << " and pos=" << pos);
pos += c.w;
}
c.doLayout(pfx + "|--");
}

juce::Rectangle<int> res{x, y, w, h};
LOLOG(pfx << " " << tag << " Returning " << res.toString());
return res;
}
break;
}

return {};
}

private:
// details go here
int x{0}, y{0}, w{0}, h{0};
int insetx{0}, insety{0};
bool isFilling{false};

int autoGap{0};

std::vector<loc_t> children;

juce::Component::SafePointer<juce::Component> component{nullptr};
};

inline LayoutComponent VList()
{
return LayoutComponent().withLayoutMechanism(LayoutComponent::VLIST);
}

inline LayoutComponent HList()
{
return LayoutComponent().withLayoutMechanism(LayoutComponent::HLIST);
}

inline LayoutComponent Gap()
{
return LayoutComponent().withLayoutMechanism(LayoutComponent::LEAF);
}

inline LayoutComponent Component(juce::Component &c)
{
return LayoutComponent().withLayoutMechanism(LayoutComponent::LEAF).withComponent(c);
}
} // namespace sst::jucegui::layout

#endif
54 changes: 29 additions & 25 deletions src/ui/lfo-components.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "patch-data-bindings.h"
#include "ui-constants.h"
#include "sst/jucegui/components/RuledLabel.h"
#include "ui/layout/Layout.h"

namespace baconpaul::six_sines::ui
{
Expand Down Expand Up @@ -85,31 +86,34 @@ template <typename Comp, typename Patch> struct LFOComponents
if (!titleLab)
return {};

auto c = asComp();
auto lh = uicLabelHeight;
auto h = c->getHeight() - y;
auto q = h - lh;
auto w = uicKnobSize * 1.5;

positionTitleLabelAt(x, y, w + uicMargin + uicKnobSize + extraWidth, titleLab);

auto shapeH = 2 * uicLabeledKnobHeight - uicMargin - lh + 5;
auto bx = juce::Rectangle<int>(x, y + uicTitleLabelHeight, w, h - lh);
shape->setBounds(bx.withHeight(shapeH));
tempoSync->setBounds(bx.withTrimmedTop(shapeH + uicMargin).withHeight(lh));
bipolar->setBounds(bx.withTrimmedTop(shapeH + 2 * uicMargin + lh).withHeight(lh));
isEnv->setBounds(bx.withTrimmedTop(shapeH + 2 * uicMargin + lh)
.withHeight(lh)
.translated(w + uicMargin, 0)
.withWidth(uicKnobSize));

bx = bx.translated(w + uicMargin, 0);
positionKnobAndLabel(bx.getX(), bx.getY(), rate, rateL);
positionKnobAndLabel(bx.getX(), bx.getY() + uicLabeledKnobHeight + uicMargin, deform,
deformL);
bx = bx.translated(uicKnobSize + uicMargin, 0);
return juce::Rectangle<int>(x + w + uicKnobSize + uicMargin + extraWidth, y, 0, 0)
.withBottom(bx.getBottom());
namespace jlo = sst::jucegui::layout;

auto lo = jlo::VList()
.at(x, y)
.withHeight(asComp()->getHeight() - y)
.withWidth(uicKnobSize * 2.5 + uicMargin);

lo.add(titleLabelLayout(titleLab));

auto columns = jlo::HList().expandToFill().withAutoGap(uicMargin);

auto col1 = jlo::VList().withWidth(uicKnobSize * 1.5).withAutoGap(uicMargin);
col1.add(jlo::Component(*shape).withHeight(2 * uicLabeledKnobHeight - uicLabelHeight));
col1.add(jlo::Component(*tempoSync).withHeight(uicLabelHeight));
col1.add(jlo::Component(*bipolar).withHeight(uicLabelHeight));

auto col2 = jlo::VList().withWidth(uicKnobSize).withAutoGap(uicMargin);
col2.add(labelKnobLayout(rate, rateL));
col2.add(labelKnobLayout(deform, deformL));
col2.add(jlo::Component(*isEnv).withHeight(uicLabelHeight));

columns.add(col1);
columns.add(col2);

lo.add(columns);

auto usedSpace = lo.doLayout();
return usedSpace.translated(usedSpace.getWidth(), 0);
}

std::unique_ptr<jcmp::Knob> rate, deform;
Expand Down
Loading

0 comments on commit 79e574f

Please sign in to comment.