JIVE JUCE Modules

repository·main·Indexed 41 days ago

https://github.com/imjimmi/jive

A collection of JUCE modules that brings a modern, web-inspired declarative UI development workflow to C++. JIVE separates UI structure via ValueTrees, styling via JSON/DynamicObjects, and logic via C++. It includes jive_layouts for creating GUIs using declarative markup and jive_style_sheets for styling juce::Component objects with CSS-like properties.

Tokens
19.8K
Snippets
40
Records
53
Agent score
45%

What's inside JIVE

  1. Overview of JIVE Layouts

    main
    The jive_layouts module allows you to create Graphical User Interfaces (GUIs) by describing them using declarative markup. Instead of imperative code, you define your UI hierarchy using juce::ValueTree structures, which the library then translates into functional components.
  2. What is JIVE?

    main

    JIVE is a bundle of JUCE modules designed to provide a modern, declarative approach to UI development in C++. It draws inspiration from web front-end technologies (HTML, CSS, and JavaScript) to simplify the creation of complex GUIs.

    JIVE uses three layers to manage the UI:

    1. Markup (HTML-like): Uses juce::ValueTree to define the hierarchy and structure of components (what is on the screen).
    2. Styling (CSS-like): Uses juce::var and juce::DynamicObject via JSON documents to define visual properties like colors, fonts, and borders.
    3. Logic (JavaScript-like): Uses standard C++ to dynamically update the UI at runtime.

    This approach encourages the Model-View-Presenter (MVP) pattern and helps separate UI concerns from business logic.

  3. How JIVE Layouts work

    main

    The jive_layouts module handles the hierarchy and layout of your GUI. Instead of writing imperative JUCE Component code to position elements, you describe the UI structure using a juce::ValueTree. This allows you to define what components exist (e.g., Windows, Components, Buttons, Text) and their properties (e.g., id, align-items, display) in a declarative way.

    <Window width="640" height="400" align-items="centre">
        <Component id="header" align-items="centre">
            <Text id="title">Welcome to JIVE!</Text>
            <Text id="subtitle">The ultimate JUCE extension for building GUIs.</Text>
        </Component>
    
        <Component id="nav" display="grid" template-columns="1fr 1fr 1fr" template-rows="1fr">
            <Button>
                <Text>Home</Text>
            </Button>
    
            <Button>
                <Text>About</Text>
            </Button>
    
            <Button>
                <Text>Contact</Text>
            </Button>
        </Component>
    </Window>
  4. The JIVE GUI workflow

    main

    JIVE follows a declarative approach to GUI construction. The core workflow consists of three steps:

    1. Describe: Define your UI structure using a juce::ValueTree.
    2. Interpret: Use jive::Interpreter to transform that juce::ValueTree into a jive::GuiItem (the actual UI components).
    3. Update: Modify the juce::ValueTree at runtime to trigger dynamic, automatic updates to the visible UI.
  5. How GUI Items work in JIVE Layouts

    main

    The core of JIVE Layouts is the jive::GuiItem class. It acts as a wrapper around a juce::Component and automatically applies properties defined in a corresponding juce::ValueTree.

    Categories of GUI Items

    • Container Items: Implement layout patterns like Flex or Grid.
    • Widget Items: Implement specific widget behaviors (e.g., buttons, sliders).

    Implementation Rules for Custom Items

    If you need to create custom jive::GuiItem derivatives, follow these rules:

    1. Use the Decorator Pattern: Prefer deriving from jive::GuiItemDecorator rather than jive::GuiItem directly. A decorator should both derive from the decorator base class and hold a jive::GuiItem.
    2. Chain Virtual Method Calls: When overriding virtual methods, always call the same method on the base class to ensure other decorators in the chain can execute their logic.
    3. Avoid Order Dependency: Do not rely on the specific order in which decorators are added.

    Important: Avoid constructing GUI items manually; always use the jive::Interpreter to create them.

    // Concept: Decorator Pattern for custom items
    class MyCustomDecorator : public jive::GuiItemDecorator {
        // ... implementation
    };
  6. How JIVE Style Sheets work

    main

    The jive_style_sheets module allows you to style juce::Component objects using CSS-like properties defined in JSON.

    To apply styles, you attach a jive::StyleSheet to a juce::Component along with a juce::ValueTree. The stylesheet reads from the style property of that ValueTree.

    Key Concepts:

    • Heredity: Styles are hereditary. If a component doesn't define a property, it inherits the style from its parent.
    • Dynamic Updates: While you can set the style property as a JSON string, it is highly recommended to use a jive::Object (which derives from juce::DynamicObject). This allows for efficient runtime modifications and enables the use of listeners to react to style changes.
    ```cpp
    juce::Component component;
    juce::ValueTree tree{ "Component" };
    
    // 1. Parse JSON into a jive::Object (via juce::var)
    auto styleValue = jive::parseJSON(R
  7. Query current interpolated values during a transition

    main

    When a property is animating, jive::Property::get() returns the target value, while jive::Property::calculateCurrent() returns the current interpolated value based on elapsed time.

    To track animation progress, use the jive::Property::onTransitionProgressed() callback, which is called repeatedly during the transition. This is the ideal place to call calculateCurrent() to update your UI.

    Note: jive::Property::onValueChanged() is only invoked once when the target value changes.

    jive::Property<float> value{ state, "value" };
    
    // Target value
    value = 350.0f; 
    
    // Current interpolated value (e.g., halfway through 500ms)
    float current = value.calculateCurrent(); 
  8. How the JIVE Layout Interpreter works

    main

    The Interpreter is the core engine that converts a juce::ValueTree hierarchy into a hierarchy of jive::GuiItem objects.

    To customize how the interpreter behaves, you can use the following mechanisms:

    1. Custom Components: Register new component types by adding entries to the interpreter's jive::ComponentFactory. This allows the interpreter to construct specific custom component types based on a juce::ValueTree type name.
    2. Custom GUI Items: You can specify decorators that the interpreter will apply to items wrapping juce::ValueTree elements of a specific type.
    3. Aliases: You can define aliases to reuse complex UI structures. When the interpreter encounters a juce::ValueTree element with an alias name, it replaces that element with a predefined replacement juce::ValueTree structure.
    <!-- Example of an Alias expansion -->
    <SearchGroup/>
    
    <!-- expands to... -->
    
    <Component id="search-group">
        <Text id="search-label" margin="0 0 5 0">Search:</Text>
    
        <Component display="flex" flex-direction="row">
            <TextBox id="search-box" flex-grow="1"></TextBox>
            <Button id="search-submit"><Text>Go</Text></Button>
        </Component>
    </Component>
  9. Configure custom FlexBox layouts

    main

    JIVE containers use a FlexBox layout by default with a column direction (top-to-bottom). You can control positioning and spacing by adding properties to the juce::ValueTree representing your container.

    Common layout properties include:

    • justify-content: Controls alignment on the main axis (e.g., centre).
    • align-items: Controls alignment on the cross axis (e.g., centre).
    • margin: Sets spacing around an item using CSS-style syntax: "top right bottom left" (e.g., "0 0 20 0").
    • padding: Sets internal spacing around an item (e.g., "7 20").
    juce::ValueTree topLevel(const juce::String &greeting) {
        return juce::ValueTree{
            "Window",
            {
                { "justify-content", "centre" }, // Centre on the main-axis (vertically)
                { "align-items", "centre" },     // Centre on the cross-axis (horizontally)
            },
            // ...
        };
    }
  10. Set up jive_style_sheets for graphical styling

    main

    To apply graphical styles like colors and fonts, use the jive_style_sheets module.

    To integrate it with jive_layouts so that jive::GuiItems automatically receive a jive::StyleSheet, you must:

    1. Add the jive_style_sheets module to your project (via CMake or Projucer).
    2. Set the JIVE_GUI_ITEMS_HAVE_STYLE_SHEETS flag to 1.
  11. Define multiple transitions for a single source

    main

    Transitions can be defined as a comma-separated list within the `

    juce::ValueTree state{
        "MyState",
        {
            { "value", 100 },
            { "alpha", 0.3 },
            { "transition", "value 500ms ease-in, alpha 4s 1s" },
        },
    };
  12. Create a basic view with jive::View

    main

    While JIVE can use pure juce::ValueTree objects to define a UI, jive::View allows you to attach additional behavior to a view without losing control or needing to manually pass pointers/references.

    To create a custom view, inherit from jive::View and override the initialise() method to return the juce::ValueTree that defines the view's initial state. You can then use jive::makeView<T>() to convert your custom view type into a juce::ValueTree that the jive::Interpreter can process.

    class MyView : public jive::View
    {
    protected:
        juce::ValueTree initialise() final
        {
            return juce::ValueTree{
                "Component",
                {
                    { "width", 300 },
                    { "height", 200 },
                },
            };
        }
    };
    
    // Construct a ValueTree from our custom View type
    const juce::ValueTree view = jive::makeView<MyView>();
    
    jive::Interpreter interpreter;
    const auto guiItem = interpreter.interpret(view);