Skip to content
This repository has been archived by the owner on Nov 1, 2021. It is now read-only.

UI Object Overview

janesconference edited this page Mar 10, 2011 · 19 revisions

UI Object

Tutorial

An UI object controls the relationship between Graphic Elements and their associated DrawClass.

When you create an UI object, you can associate a Graphic Element with a wrapper and set its z-index with UI.addElement, or chain two elements (and set a filter callback between them) with UI.connectSlots. Let's see how.

I will use recursiontest as an example. In this test script, a list of three Knob element is created, along with three CanvasDrawImage HTML5 wrappers:

    var NKNOBS = 3
    for (var i=0; i < NKNOBS; i+=1) {
        knob[i] = new Knob("knob" + i, [i*100 , 100], knobArgs[i]);
        imageDisplayer[i] = new CanvasDrawImage (plugin_context);
    }

Creating an UI Object

Then, we are ready create a brand **new **UI object:

    var plugin_canvas = document.getElementById("plugin");
    ui = new UI (plugin_canvas);

Notice that we pass a canvas object to the UI constructor. When you pass a target to an UI object, the UI object adds some event listener to the target. Current UI implementation adds listeners for mousedown, _mouseup _and _mousemove _events.

Adding elements to the UI

When one of those events is triggered, UI calculates the mouse position of the event relative to the target boundaries and check if one of the graphic elements it is handling is interested to the event.
We only need to add our knobs to the UI element we just created to accomplish this:

    for (var i=0; i < NKNOBS; i+=1) {
        ui.addElement(knob[i], imageDisplayer[i] , {zIndex: 10});
    }

What does UI.addElement() do? Three things: it gives the UI a graphic element to handle (knob[i] in this case), it tells the UI what wrapper object is associated with this particular graphic element (imageDIsplayer[i]) and it passes the UI another optional object (parameter n.3).

Currently, this optional object support only one parameter: zIndex.

Adding depth: zIndex

zIndex is a value >= 0 that represents the "depth" of a particular graphic element in the UI: the bigger zIndex is, the more that particular graphic elements is "on top" (towards the user). This affects the way UI refreshes the elements when their value gets changed.

In particular, when a graphic element must be refreshed (anytime one of its slot changes value, in general), the UI object refreshes it, then it refreshes every (zIndex + 1) element too.

This is done step by step: if you have five elements with zIndex = 3, 5, 5, 10 and 50 respectively, and the lowest element (**zIndex **= 3) must be refreshed, its refresh will cause, in cascade, the two **zIndex **= 5 elements to be refreshed (in random order), then the **zIndex **= 10 element, and finally the **zIndex **= 50 element. To remember how zIndex works, keep in mind this set of rules:

  • If two elements overlap, the one with the bigger **zIndex **value will always refreshed correctly on top of the other.
  • If you assign the same **zIndex **to two overlapping elements, instead, the last to be refreshed will "win", and will be render on top of the other (but every element with a greater zIndex will refresh correctly on top of them).
  • Finally, if an element does not define its zIndex, its behaviour will skip all this logic. The element will be rendered in the "deepest" (background) layer when UI.refresh() is called, and UI will not care about overlapping elements on all the consequent refresh()es.

Since in this example the three knobs don't overlap, we can assign te same zIndex value (10) to each one of them. Since they're in top of nothing and nothing overlaps them, we could have skipped the third parameter as well.

Chaining elements in the UI

Now, we want to chain our three knobs: when one changes value, we want the others to change value as well, and we also want to tell each element how to change value in relation to the others' value.

In particular, here's what we want:

  • Everytime Knob 0 changes its knobvalue, Knob 1 has to change its knobvalue automatically. But we want Knob 1 to be the inverse of Knob 0: when Knob 0 increases, Knob 1 decreases, and vice versa. This has to be true whether the value-changing action (e.g. the user manipulating the knob) is started by Knob 0 or by Knob 1.
  • Similarly, everytime Knob 1 changes its knobvalue, Knob 2 has to change automatically. But we want Knob 2's range to be [0,0.5] instead of [0,1].
  • To close the loop, we want Knob 2 to change the value of Knob 0. This time, Knob 0's value must be the equal to Knob 2's.

Using UI.connectSlots() and the filter callback, we can accomplish this in four lines of code:

    // 0 -> 1
    ui.connectSlots("knob0", "knobvalue", "knob1", "knobvalue", {callback: function (value) {return 1-value;}});
    // 1 -> 0
    ui.connectSlots("knob1", "knobvalue", "knob0", "knobvalue", {callback: function (value) {return 1-value;}});
    // 1 -> 2
    ui.connectSlots("knob1", "knobvalue", "knob2", "knobvalue", {callback: function (value) {return value * 0.5;}});
    // 2 -> 0
    ui.connectSlots("knob2", "knobvalue", "knob0", "knobvalue", {callback: function (value) {return value;}});

UI.connectSlots()

UI.connectSlots takes five parameters. Its signature is:

    UI.connectSlots  = function (senderElement, senderSlot, receiverElement, receiverSlot, connectParameters)

**senderElement **and **senderSlot **are strings. They specify where the chain we want to add "starts".
Similarly, **receiverElement **and receiverSlot are strings, too. They specify where the chain we want to add "ends".

Calling UI.connectSlots means "When the value of slot senderSlot of the element senderElement changes, make the value of slot receiverSlot in the element receiverElement change accordingly".

The fifth parameter is an object containing optional parameters. The one that we need for now is the callback one.
callback is a callback function that acts as a filter when UI activates a particular connection. This callback simply takes the value of the senderSlot as its only parameter, and returns the value that should be put in the receiverSlot.

We used this callback to invert the knob value between Knob 0 -> Knob 1 and viceversa:

    {callback: function (value) {
        return 1-value;
        }
    };

But we can use it to make an hypothetical element with numeric values (a Knob or a Slider, for example) to automatically change a slot value of another element which handles strings (maybe a Label), or any other complex-as-you-like filter / translation you may need.

Infinite loops

Chaining slots can introduce closed loops. If Knob 0 changes Knob 1, Knob 1 changes Knob 2 and Knob 2 changes Knob 0 in turn, will an user generate an endless feedback between knobs manipulating Knob 0?

UI "breaks" these infinite chains storing the history of recursively changed element:slot pair. When an element:slot is seen for the second time in the same chain, UI knows the loop is endless and breaks the chain.

Clone this wiki locally