Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Begin GUI globals, component library, design system #38

Open
4 of 5 tasks
Tracked by #69 ...
nathanjhood opened this issue Nov 30, 2024 · 8 comments
Open
4 of 5 tasks
Tracked by #69 ...

WIP: Begin GUI globals, component library, design system #38

nathanjhood opened this issue Nov 30, 2024 · 8 comments
Assignees
Labels
duplicate This issue or pull request already exists

Comments

@nathanjhood
Copy link
Member

nathanjhood commented Nov 30, 2024

Chore

Describe the chore

I have started the blank panels which will encompass the eventual 0.1 release. Immediately, I'm finding things that I want to set globally (probably from plugin.hpp) so that I can enact global themes and such on the GUI front.

Here's a function which calls some colors - a black and a white - and paints the BG of the module widget:

void ::StoneyDSP::VCVRack::HP1Widget::draw(const ::StoneyDSP::VCVRack::Widget::DrawArgs &args)
{

    ::nvgBeginPath(args.vg);
    ::nvgRect(args.vg, 0.0, 0.0, box.size.x, box.size.y);
    ::NVGcolor bg = ::rack::settings::preferDarkPanels ? ::nvgRGB(42, 42, 42) : ::nvgRGB(235, 235, 235);
    ::nvgFillColor(args.vg, bg);
    ::nvgFill(args.vg);
    ::StoneyDSP::VCVRack::Widget::draw(args);
}

I don't want to update those vars in a million places when I decide to make adjustments to my theme.

This type of thing will crop up a lot - my intention is to perhaps also add a component library for things like themed screws... There will probably be DSP templates and stuff also.

The blank panel modules are an excellent starting point, as I can resolve lots of concerns that will affect the entire library, before getting deep into DSP and weird ideas.

Additional context

I'll start a branch when the time is right, but while the module count is low, a branch isn't a priority at this time.

Tasks

@nathanjhood nathanjhood added enhancement New feature or request build system Changes or improvements to the project build system labels Nov 30, 2024
@nathanjhood nathanjhood self-assigned this Nov 30, 2024
@nathanjhood nathanjhood moved this to Todo in StoneyDSP Nov 30, 2024
@nathanjhood nathanjhood moved this from Todo to In Progress in StoneyDSP Nov 30, 2024
This was linked to pull requests Nov 30, 2024
@github-project-automation github-project-automation bot moved this from In Progress to Done in StoneyDSP Nov 30, 2024
@nathanjhood nathanjhood reopened this Nov 30, 2024
@nathanjhood nathanjhood moved this from Done to In Progress in StoneyDSP Nov 30, 2024
@nathanjhood
Copy link
Member Author

This one is right near the top in terms of priority... I can't/won't officially increment any versions and cut a release until I've done a real deep dive on the panels and widgets.

Any visitors building our modules will find some interesting semi-transparent, perspex-like behaviour, which is due to missing resources but kinda has it's own charm :D totally aware of it, and must start a discovery branch for the panels and widgets ASAP.

This was unlinked from pull requests Dec 5, 2024
@nathanjhood
Copy link
Member Author

nathanjhood commented Dec 5, 2024

learn how to subscribe to runtime-level "onThemeChanged" events, if possible, to mark framebuffer widgets as dirty()

I guess ThemedScrew does it... let's check the course code:

struct ThemedScrew : app::ThemedSvgScrew {
	ThemedScrew() {
		setSvg(Svg::load(asset::system("res/ComponentLibrary/ScrewSilver.svg")), Svg::load(asset::system("res/ComponentLibrary/ScrewBlack.svg")));
	}
};

So, I'm guessing then that setSVG() helpfully has two arguments; a "light-mode" resource, and a "dark-mode" resource... no need to subscribe to any events manually, in this case.

This bodes real well for my intentions; really appreciating the solid GUI framework in the Rack API the more I look into it. I even prefer it over the web component style appearing elsewhere.

So, we can make a ton of themed panel BG's, widgets, and such forth. We can define SVG-alike codeblobs using NanoSVG which encapsulate things like screws, gradients, etc. The Svg::load() method can also load resources; there is asset::system for resources from Core (do we need a CMake target to import those recource files...?) and asset::plugin for project-local resources, presumably prefixed from the source directory (??).

EDIT: Here's the parent class:

struct ThemedSvgScrew : SvgScrew {
	std::shared_ptr<window::Svg> lightSvg;
	std::shared_ptr<window::Svg> darkSvg;

	void setSvg(std::shared_ptr<window::Svg> lightSvg, std::shared_ptr<window::Svg> darkSvg) {
		this->lightSvg = lightSvg;
		this->darkSvg = darkSvg;
		SvgScrew::setSvg(settings::preferDarkPanels ? darkSvg : lightSvg);
	}

	void step() override {
		SvgScrew::setSvg(settings::preferDarkPanels ? darkSvg : lightSvg);
		SvgScrew::step();
	}
};

Hmmm... so it's just setSvg() inside the step() function override, with a per-step() comparison against settings::preferDarkPanels...

This right here is the clue to how to work with themes, I think:

settings::preferDarkPanels ? darkSvg : lightSvg

@nathanjhood nathanjhood added duplicate This issue or pull request already exists and removed enhancement New feature or request build system Changes or improvements to the project build system labels Dec 21, 2024
@nathanjhood
Copy link
Member Author

nathanjhood commented Dec 21, 2024

Solved on #263 #259

void ::StoneyDSP::StoneyVCV::HP1::HP1ModuleWidget::step()
{
    if(this->lastPrefersDarkPanels != ::rack::settings::preferDarkPanels) {
        this->hp1ModuleWidgetFrameBuffer->setDirty();
        this->lastPrefersDarkPanels = ::rack::settings::preferDarkPanels;
    }

    return ::rack::Widget::step();
}

@github-project-automation github-project-automation bot moved this from In Progress to Done in StoneyDSP Dec 21, 2024
@nathanjhood nathanjhood reopened this Dec 22, 2024
@nathanjhood
Copy link
Member Author

nathanjhood commented Dec 22, 2024

Solved on #263 #259

Not quite.

I've gotten confident with nanovg/nanosvg, as well as subscribing to Rack events; meanwhile, I really do not want to use Inkscape or Figma on this project.

I'm a front-end web developer... I don't want to mix the day job in with my "fun" stuff. And those Inkscape SVG files are a nightmare, as is their GUI...

So, I'm considering in terms of design system:

  • Make a "gradient" widget
  • Paint the BG color on the top-most Widget, and add the gradient widget as a child
  • Could be a free function, or static class method, which "adds" the gradient layer, using a passed-in Widget (which ModuleWidget inherits anyway) and calling addChild and getSize on that passed-in Widget, to set things correctly
  • Do the same with screws and lines

As an aside, I'm entirely skeptical about the widespread use of SVG's and setPanel - with no unit tests, no debugger, etc - and the Rack manual's suggestions to use n*5.08mm x 128.5mm for dimensions. If you do exactly this, then run Rack with a debugger attached, it warns you that your module's size is too small on the Y axis... and what if Inkscape overwrites the file and sets arbitrary values on important properties (which it does freely) - so you think you fixed the widget size, open Inkscape to do a quick check, and rebuild. Congrats, Inkscape broke your fix.

@nathanjhood
Copy link
Member Author

nathanjhood commented Dec 22, 2024

Let's do some psuedo-code, before-I-forget style:

void addScrewsToWidget(rack::widget::Widget* widget)
{
    rack::componentlibrary::ThemedScrew* screws [] = {
::rack::createWidgetCentered<::rack::componentlibrary::ThemedScrew>(/** Top Left */),
::rack::createWidgetCentered<::rack::componentlibrary::ThemedScrew>(/** Top Right */),
      // etc...
    };
    for(auto screw : screws) {
        widget->addChild(screw);
    }
}

We can grub some additional logic by querying box.size from the passed-in Widget;

  • if the passed-in Widget is only 1HP wide but 3U tall, set one screw each on the top and bottom
  • if the passed-in Widget is only 2HP wide but 3U tall, set one screw each on the top-left, top-right, bottom-left, and bottom-right
  • if the passed-in widget is "wide" and "tall" enough, also add the lines around it (optional with a boolean param, maybe?)

Another, less weildy approach is to use class inheritance;

  • create a new base class which inherits from ModuleWidget
  • add all the screws, lines, trimmings etc logic and code in this base class
  • replace our ModuleWidgets inheritance with this new class

I'm more a fan of the first option, but may experiment with the latter to observe what benefits this approach may have, as well as drawbacks.

EDIT: thoughts on making a function vs. making a base class

  • the base class is a nice idea so long as our inheritance chain plays well with rack::createModel(); it would mean that we assume every module wants to have the screws and lines and trimmings, and adds those without us needing to ever think about it; this is the whole point of design system
  • On the other hand, the function allows us to set screws etc on things which might only represent a smaller part of the panel, like a panel border around a section
  • We could even make both... the function gets used in the base class, as well as being available outside it...

@nathanjhood
Copy link
Member Author

nathanjhood commented Dec 22, 2024

Maybe we could template it:

template <type T>
void addCenteredChildToWidget(rack::widget::Widget* widget)
{
    T* children [] = {
::rack::createWidgetCentered<T>(/** Top Left */),
::rack::createWidgetCentered<T>(/** Top Right */),
      // etc...
    };
    for(auto child : children) {
        widget->addChild(child);
    }
}

A constraint would be needed to ensure that T either is a rack::widget, or is derived from it.

@nathanjhood
Copy link
Member Author

Ok, latest:

  • sub-classed GUI components with themes is now live (panels and ports, currently)
  • screws and lines are happy living in a frame buffer widget which gets setDirty() when the theme changes (only VCA, currently)

About to start the themed knobs (the text labels and ringed line around the widget will respond to theme changes). I found a gotcha:

There are too many, conflicting, types of knob widgets in the Rack factory component library. Sizes named "big", "large", and "huge" don't really give a clear idea of hierarchy, to boot.

I've found that I've been using a size that is not present on any Core or Fundamental v2 modules - the "big" size. Thus, I cannot find any matching instances of the knob I've used on my VCA in the factory modules, and therefore don't have a reference for dimensions and other requirements I'd need to put the ring and label on it.

Using the "Large" size, which is smaller than "big", looks kinda small on the VCA panel and makes me wonder if I should make my module less wide. The "Huge" one is just too big for such a basic module. I still really like using a slider, but this takes even less horizontal space and again makes me wonder if I should make my VCA thinner along x.

In any case, I'll bounce straight on to the LFO and then other modules, where even more components will be needed. But I'll need to be aware which knob sizes not to use, if I plan on using factory modules as references.

@nathanjhood
Copy link
Member Author

Image
Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
duplicate This issue or pull request already exists
Projects
Status: Done
Development

No branches or pull requests

1 participant