Introducing JIVE - the ultimate JUCE extension for building GUIs

JIVE is a bundle of JUCE modules centred around the desire to have a more modern approach to building GUIs in JUCE. As well as some handy utility modules, JIVE has two main constituents:

JIVE Layouts

JIVE layouts provides an API to build a hierarchy of components from declarative markup, in the form of juce::ValueTrees (XML).

Using popular CSS layout patterns like Flex and Grid, this API provides a powerful way to quickly and effectively build modern, flexible UIs by specifying common properties found in many other frameworks and tools. With support for custom components, aliases, and live-updates, jive_layouts completely removes the need to ever write a Component class ever again.

JIVE Style Sheets

JIVE Style Sheets provides an API to apply common styling properties from JSON documents, akin to CSS.

jive_style_sheets currently supports the most common styling properties like background and foreground colours/gradients, fonts, and corner radius. It also supports interaction-state selectors like mouse-hover and keyboard focus, as well as type-specific selectors to apply styles to all of a particular component type. All of this combined removes the need to ever make calls to a juce::Graphics context in the majority of components.

The Competition

This is by no means a new idea as there are several existing alternatives. However all of these have their flaws leaving a gap in the market that JIVE hopes to fill.

  • juce::ComponentBuilder - can be used to create a hierarchy of components from a juce::ValueTree. Sounds very similar to what JIVE offers, however a lot of manual work must still be done to actually build each of the individual components and, to the best of my knowledge, juce::ComponentBuilder doesn’t handle any sort of layout logic, it’s merely a tool for saving/loading UI state.
  • Plugin Gui Magic - a tool for building modern UIs through a GUI WYSIWYG interface. Although very powerful, GUI editors and the $10+/month license aren’t for everyone.
  • react-juce - taking a similar approach by borrowing from web front-end practices, react-juce provides tools for building JUCE GUIs using the React framework. Although very impressive, the dependency on other frameworks and languages outside of JUCE and C++ isn’t ideal, and it raises the question of why not just use a web-view?
  • Web View - this is certainly the way to go for larger teams who have the sparse skillset required to build a back-end in JUCE/C++ along with a front-end in HTML/CSS/JS, however for smaller teams and/or independent developers this approach is likely far more involved than necessery.
  • JIVE - has no dependencies other than JUCE itself, uses an MIT license and is 100% free to use for everyone, can easily be integrated into existing JUCE projects as well as the ideal tool for new projects.

Please see the documentation in the repo for more details, as well as some examples and getting-started guides. Feel free to use this thread to ask any question, raise any issues, etc.

The Jist

  • Describe your UI using XML/juce::ValueTree:
    <Window width="640" height="400">
        <Text>This is JIVE!</Text>
        <Button>Click Me!</Button>
    </Window>
    
  • Pass this to a jive::Interpreter to build the hierarchy of components:
    #include <jive_layouts/jive_layouts.h>
    
    juce::ValueTree windowSource{...};
    
    jive::Interpreter interpreter;
    const auto window = interpreter.interpret(windowSource);
    
  • Update properties in the original juce::ValueTree to update the UI in real time:
    windowSource.setProperty("align-items", "centre", nullptr);
    windowSource.setProperty("justify-content", "centre", nullptr);
    
  • Apply styles:
    static constexpr auto style = R"({
        "background": "#121314",
        "foreground": "#F0F1F2",
        "font-family": "Comic Sans MS",
    
        "Button": {
            "background": "#232425",
    
            "hover": {
                "background": "#313233",
            }
        }
    })";
    windowSource.setProperty("style", style, nullptr);
    

Nomenclature

It wouldn’t be a JUCE module without a terrible backronym… JIVE stands for “James’ Intuitive View Explicator”.

Or, if you prefer recursive backronyms, “JIVE is an Intuitive View Explicator”.

31 Likes

Nice work!! Declarative reloadable UI is a dream. Thanks for sharing, looking forward to checking it out in more detail. Nice docs on the layout module, btw!

Is there an example project somewhere to see what an simple plugin/app might look like?

3 Likes

I’m working on a Demo project atm, it’s on a branch that you can check out here:

https://github.com/ImJimmi/JIVE/tree/demo-runner/runners/demo-runner

3 Likes

This is really nice!

1 Like

Great work here…How mature is this codebase. Is it ready for real life projects?

It’s still early days - I’m working on a little demo project and sorting out a few odd edge-case bugs here and there. Once I’m done with that I’ll call it v1.0.

As for whether it’s ready for real-life projects I guess that’s entirely up to you! At the moment I’m mostly just keen to hear people’s feedback so if you want to give it a go in a project then I’d love to hear how you get on with it.

2 Likes

Okay I finially got round to finishing this demo project :sweat_smile: You can check it out here: https://github.com/ImJimmi/JIVE/tree/main/runners/demo-runner

There’s still lots of improvements to be made, some of which I’ve listed in the Issues on GitHub, but that’s by no means an exhaustive list! I’d love to hear feedback from anyone who fancies giving it a try!

I’ve also now slapped a Version 1 tag on it, so if you’re using CPM you can do

CPMAddPackage("gh:ImJimmi/JIVE@1.0.0")
7 Likes

Hi, this project looks fantastic. I want to know if JIVE would be any good for making UIs with lots of zooming? I have a JUCE UI where you can zoom in smoothly on any part of the canvas, and different widgets come in and out of focus on different levels. It works, but the code is terrible and I’ve been looking for ways I to simplify it. Can JIVE work with Affine transformations, or is it not built for this kind of thing?

I’m afraid I haven’t done anything specific to work with transforms just yet. It’s something I’ve been thinking about but isn’t a high priority for me right now.

jive::GuiItems are simply wrappers around components so you can still interact with those components the same way you would normally, so you could always use JIVE to build your UI and then apply the additional transforming behaviour imperitively.

juce::ValueTree someView{ ... };
jive::Interpreter interpreter;

auto topLevelGuiItem = interpreter.interpret(someView);
auto topLevelComponent = topLevelGuiItem.getComponent(); // std::shared_ptr<juce::Component>

topLevelComponent->setTransform(juce::AffineTransform::scale(1.2f));
2 Likes

I’ve made some significant performance improvements to the interpreter and the layout behaviour - the basic benchmarking tests I’ve added are showing this is now around 20-30% faster.

2 Likes

I also wanted to share, @jellebakker gave a good talk at ADC this year about using ValueTrees and in particular highlighted some useful utilities like juce::Value and juce::CachedValue. I wanted to highlight my own solution to this which is jive::Property.

It’s main features are:

  • Templated for type-safe reading and writing using juce::VariantConverter to convert to/from juce::var (much like juce::CachedValue, but without the caching).
  • std::function listener mechanism akin to JUCE widgets for easy observation of a particular property (much like juce::Value, but without the need for inheritance and the boilerplate of overridding the callbacks).
  • Two additional template arguments after the value type to specifiy:
    • The inheritance behaviour - allows for a property to inherit it’s value from an ancestor if the property isn’t specified on the target tree.
    • The accumulation behaviour - bit of a specific usecase, but allows get() to return not only the target tree’s value, but also an accumulation of any desendant trees (using operator+ - so the property’s type must support that). This is used in JIVE to concatinate nested "text" properties into a single string.
  • Allows setting a property of any type to "auto" (and provides an isAuto() query).
  • As well as assigning values of the specified type, you can also assign a std::function that returns a value of that type. This is intended to allow some more functional design patterns, but it’s still a bit experimental to be honest.

I’m also planning on adding a bind() method that will allow you to bind properties together such that whenever one changes, the other will be changed to match. This will support binding properties of different types, with the default behaviour being that their values will simply be cast before copying, but with the option to supply custom conversions and/or custom mapping.

Data binding is a common pattern used in various separated-presentation architectures, namely Model-View-ViewModel and Supervising Controller, as well as other applications like binding a widget’s value to some sort of UI property - e.g. a slider’s value to the rotation of an SVG element for rotary knobs.

5 Likes

I totally dig the CSS approach (I was there too: Proposal improve of look and feel of Juce's applications - #63 by Qfactor). Any chances of establishing a CSS standard in JUCE to override the look and feel approach?

That’s pretty much my biggest motivation for JIVE. I want to offer a solution where you’ll never have to inherit from juce::Component or juce::LookAndFeel ever again.

I’m not planning on adding full CSS support, I’m mainly using it as a reference to build declarative styling and layout APIs. Where appropriate I’ve made sure that JIVE supports CSS properties so they can simply be copy-pasted from design tools like Figma.

The biggest difference between CSS and the approach I’ve taken in JIVE is that layouts and graphics are handled separately to have a clear distinction between the geometry and hierarchy of your UI, and how your UI elements are styled.

For example, the follow HTML/CSS snippet:

// .html
<div id="xyz" class="container">
</div>

// .css
.container {
  display: flex;
  flex-direction: column;
  background-color: red;
  border: 2px solid blue;
}

In JIVE would look something like:

[[nodiscard]] auto container()
{
    return juce::ValueTree {
        "Container",
        {
            { "id", "xyz" },
            { "display", "flex" },
            { "flex-direction", "column" },
            { "border-width", 2 },
            {
                "style",
                new jive::Object {
                    { "background", "red" },
                    { "border", "blue" },
                },
            },
        },
    };
}

Layouts and styling are handled by two separate modules that don’t depend on one another so you could use jive_style_sheets to style your components but still do your own layouts, or vice versa you could use jive_layouts to build your component hierarchy but still use a custom LookAndFeel.

1 Like

The LookAndFeel serves two purposes, delegate drawing from Components away, so you can customise the drawing routines without adding an inheritance (and therefore diamond problem) to it.

The smaller part of LookAndFeel is the colour lookup, which is partly handled by the Component itself (they have their own colour map) and as other part by the LookAndFeel to allow groupings of colour choices.

I don’t know how Jive implements the colours lookup, but in PluginGuiMagic, which seems to have given a lot of inspiration to this project, the CSS properties are inherited and can be referenced via style classes, pretty similar to the original CSS.

So I doubt the LookAndFeel can go away.

Btw. I should maybe mention here, that the paid license for plugin gui magic mentioned in the OP is gone, it is now BSD-V3 free to use for everybody.

1 Like

Yep similar CSS-like approach in JIVE - it’s all handled through the jive::StyleSheet class. Elements will first check their own "style" property for a given style (background, foreground, border, etc.), if none is specified they’ll then check their parent and parent’s parent and so on until they find one.

These styles also support gradients, not just colours, so JUCE’s colour system wouldn’t work. It also supports selectors, so you can specify styles for different interaction states (mouse over, keyboard focus, etc.), as well as specify styles for ID’s, classes, and types. All of this also applies to fonts, not just colours. See https://github.com/ImJimmi/JIVE/tree/main/jive_style_sheets#jive-style-sheets for more details.

You’ll never need to use the LookAndFeel class with JIVE - there is jive::LookAndFeel but all this does is override all the default JUCE drawing to be completely blank so it doesn’t interfere with jive::StyleSheets.

If you want to do more complex graphics than what you get with the style sheets then I’d suggest using SVG, which can be embedded as a child in your ValueTrees.

auto button()
{
    return juce::ValueTree {
        "Button",
        {},
        {
            juce::ValueTree::fromXml ("<svg>...</svg>");
        },
    };
}

Good to know - I’ll update my original post!

1 Like

So you cannot chaange the drawing for Components in Jive? That’s a step backwards IMHO.

Sorry I probably didn’t explain very well…

You don’t need to supply a custom LookAndFeel or override paint() if you use JIVE’s style sheets instead. These styles apply to a canvas component that’s added as a child to the target component.

Check out the style sheets page of the demo:

Nowhere in the demo project do I inherit from juce::Component or any of its derivatives, and nowhere do I inherit from juce::LookAndFeel. The demo project never touches juce::Graphics - it’s all handled by JIVE’s style sheets.

The source for this page in particular can be found here: https://github.com/ImJimmi/JIVE/blob/main/runners/demo-runner/source/jive_demo/gui/pages/StyleSheetsPage.h

4 Likes

Also there’s nothing stopping you from still using custom components. jive::Interpreter has a map of tree types to components, e.g. <Button> will create a juce::Button. You can add your own mappings using jive::Interpreter::setComponentFactory() for your own custom widgets. So if you have an existing LookAndFeel and a bunch of components that you don’t want to rewrite, you can still use them in JIVE.

2 Likes

I should have been more specific. I meant having a CSS standard to override the “ColourId” system and provide a set of parameters (e.g. borders, paddings, font decoration) to the drawing components instead of having them hardcoded. I absolutely like the idea of the LookAndFeel as a method to override the drawing components.

Regarding the CSS standard, I use internally my own version of it in my SVG based components and when I see that Jive is also using this approach, I wonder if a general standard (property names and values) could be established in JUCE so our projects are more compatible.

2 Likes

That is a clear example of why CSS can be useful. Designers understand the language and it doesn’t force developers to use WebViews (we can have our own way of rendering the CSS in our UI custom components or lookAndFeel methods).

1 Like