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

I found a strategy for complex UI programming / organization #247

Open
elanhickler opened this issue Oct 3, 2018 · 6 comments
Open

I found a strategy for complex UI programming / organization #247

elanhickler opened this issue Oct 3, 2018 · 6 comments

Comments

@elanhickler
Copy link
Contributor

elanhickler commented Oct 3, 2018

  1. Create a .h and .cpp dedicated to resources as static members of a class called EditorResources() or whatever.

    • For example, create Font() and Image() static members for any fonts and images and svg etc that you will be using.
    • Now when you create custom painters (simply overriding paint() on RSlider or whatever), you do something like class MyFontWidget(Font desiredFont) : public RSlider instead of doing over and over again class MyFontWidget(const char data, const size_t size) filling out BinaryData::tekoFont, BinaryData::tekoFontSize
  2. Create a .h and .cpp dedicated to custom classes overriding paint.

    • You will not actually use painters. You will override the paint() method in RSlider or ModulatableSlider rather than use a painter since the mouse handling cannot be customized.
    • The only code that I seem to need to duplicate is the mouse handling, so if we figure out a good strategy to setup mouse handling this would be perfect.
  3. Every unique widget, and I mean every one, create a new class and override RSlider or ModulatableSlider. The only time you should reuse a class is if you're just changing stuff like color, size, font style, rotation, etc. OTHERWISE you should create reusable graphic/path functions that help you do the repetition of creating the paint() method in every new class.

    • for example, my knobs require setting up various arcs of different margins, sizes, thicknesses. I didn't do this yet, but I could create an "arc designer" function that I can reuse in my various paint methods to create the various arc knob styles
      image
    • Another example of when not to reuse a class: Don't create a mega customizeable button class. Need a button that doesn't change with user interaction? Make a static button. Need a button with two states? Make a two image button. Need a button that blinks? Make a blink button. Need a button that highlights on mouseover? Make a highlight on mouseover button. Do not reuse! Lesson learned.
    • Basically the goal is that you are creating new classes where most of the code is in paint() method. If you're doing code outside of paint method, make another class that helps complete your painting tasks.
  4. When viable, from your various UI classes, create a reusable UI object/template, like one knob that you use multiple times in the UI that has the same color, same size, same everything, so you can just change that one template to quickly affect all ui elements that are exact copies.

  5. Tip: Because you are rapidly creating multiple small UI element classes, it is best not to go crazy and create functions for everything like setFontSize(), setFontColor(), setBackgroundColor(). Instead, get used to accessing the members directly, and use members that are easy to manipulate. font.setHeight(), fontColor = Colour({x,x,x}), backgroundColor = Colour({x,x,x}). Or you could create a custom font class to do stuff like font.setColor(), font.setSize(), and finally use font.draw(g) in your paint methods. Only create functions if you need to do something like call repaint when you change something or if you need
    special functionality like void doBlink(bool v).

@elanhickler elanhickler changed the title I found a strategy for complex UI organized programming I found a strategy for complex UI programming/strategy/organization Oct 3, 2018
@elanhickler elanhickler changed the title I found a strategy for complex UI programming/strategy/organization I found a strategy for complex UI programming/organization Oct 3, 2018
@elanhickler elanhickler changed the title I found a strategy for complex UI programming/organization I found a strategy for complex UI programming / organization Oct 3, 2018
@elanhickler
Copy link
Contributor Author

elanhickler commented Oct 3, 2018

Note: We will differentiate between paint and draw. Paint implies repaint() may be called and is a method found in juce::Component. Draw implies this function is called inside a paint method. Resource classes should implement draw()

Basic resources are things like images, fonts, svg that you intend to call draw() inside a paint method.

  • Image
  • Font
  • SVG (I made my own SVG resource class to hold some ugly code)

Resource classes are classes that hold one or more basic resources for a higher level purpose

  • Background image: contains image member and perhaps an opacity setting, convenient because images are by default set to draw in an undesirable way for background images.

Resources cannot be interacted with, but may be intended as a paint routine for a Component subclass. Components are meant to be added to parent components (aka editors) so that the parent manages its visibility, repainting, resizing, etc. What you don't want to do is make a component subclass just to call its paint method in the parent component's paint method. A component is for adding to a parent usually via addAndMakeVisible(). I didn't understand this stuff a few days ago. Based on the user interaction of the component you can manipulate the paint method to then change how said resource is drawn to create user interaction with one or more resources.

@RobinSchmidt
Copy link
Owner

RobinSchmidt commented Oct 3, 2018

phew - that's a lot to absorb. ...need to read it again... what strikes me a bit odd is that you seem to somehow conflate painting with mouse-handling when you say things like:

You will not actually use painters. You will override the paint() method in RSlider or ModulatableSlider rather than use a painter since the mouse handling cannot be customized.

but i actually think that painting and mouse-handling are two very separate issues that should not be conflated

The only code that I seem to need to duplicate is the mouse handling, so if we figure out a good strategy to setup mouse handling this would be perfect.

i actually have an idea how to do that. client code would then do things like:

mySlider->setPainter(&mySliderPainter);
mySlider->setMouseHandler(&mySliderMouseHandler);

so client code could customize both aspects independently

@elanhickler
Copy link
Contributor Author

elanhickler commented Oct 3, 2018

You are right Robin, mouseHandler/Painter sounds like a good strategy. I wrote this more to see if it makes sense. Still figuring out if what I wrote it makes sense. If you skimmed through what I wrote, you probably read enough of it!

@elanhickler
Copy link
Contributor Author

I have a situation in my plugin where I have both sliders and knobs and it would be confusing for the user to have to drag horizontal for just a few sliders when it's vertical for the majority of the controls. I could make all knobs and sliders respond to just vertical or just horizontal, but that is equally confusing if you are used to one way versus the other.

This solves the issue of having to code vertical or horizontal dragging. This allows for controls to respond to both horizontal and vertical seamlessly. This is how some plugins do it and probably it will become the standard in the future.

if (!e.mods.isRightButtonDown() && !e.mods.isCommandDown())
{
    int newDragDistance = e.getDistanceFromDragStartY() - e.getDistanceFromDragStartX();

    int dragDelta = newDragDistance - oldDragDistance;
    oldDragDistance = newDragDistance;
    dragValue += scale * dragDelta;

    double y = normalizedValueOnMouseDown;
    y -= dragValue;
    y = clip(y, 0, 1);

    setNormalizedValue(y);
}

@RobinSchmidt
Copy link
Owner

RobinSchmidt commented Oct 25, 2018

so, the main difference to my RSlider::mouseDrag is this:
newDragDistance = e.getDistanceFromDragStartY() - e.getDistanceFromDragStartX();
vs this:
newDragDistance = e.getDistanceFromDragStartX();
hmmm...so it would respond equally to both drag dimensions...but - maybe i'm confused - it looks like it reverses the response to horizontal drag due to the minus sign in front of the x-value? shouldn't it be + instead of -?

@elanhickler
Copy link
Contributor Author

Yeah you can swap around some variables/signs to make the math look better. But the math I gave you is sound.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants