## 1. Overview

`ctrl-ing` is a tiny HTML custom element (18.4 kB uncompressed) used to interactively control your Web-App parameters or JavaScript/JSON object values in a comfortable way with a pleasing GUI [1].

Fig. 1: Controlling an Animation.

The interactive menu for this example was created via:

    <ctrl-ing ref="app" darkmode>
      [ {"sec":"hdr","text":"Parameters"},
        {"sec":"num","label":"a","min":0,"max":10,"step":1,"path":"$['a']","unit":"[-]"},
        {"sec":"num","label":"b","min":0,"max":10,"step":1,"path":"$['b']","unit":"[-]"},
        {"sec":"hdr","text":"Animation"},
        {"sec":"chk","label":"run","path":"$['run']"},
        {"sec":"rng","label":"vel","min":1,"max":10,"step":1,"path":"$['vel']"},
        {"sec":"hdr","text":"Style"},
        {"sec":"col","label":"Stroke","path":"$['ls']"},
        {"sec":"col","label":"Fill","path":"$['fs']"}
      ]
    </ctrl-ing>

Listing 1: Structure of custom HTML element `ctrl-ing`.

Beside implementing your web application, all you need to do for creating an appealing GUI, is inserting a `<ctrl-ing>` element to your HTML document (see Listing 1). Its content is compact JSON text, representing an array of section objects. Each section corresponds to a single line in the grid-like view structure of the `<ctrl-ing>` element's shadow DOM and is associated to either

- input controlling application parameters.
- output monitoring values.
- structuring elements.

All section objects are generating plain native HTML (form) elements in the background (shadow DOM).

## 2. Getting Started

Load the `ctrling.js` script somewhere into your web document locally or from CDN.

    <script src="path/to/tweakpane-3.1.1.min.js"></script>
    or
    <script src="https://cdn.jsdelivr.net/npm/tweakpane@3.1.1/dist/tweakpane.min.js"></script>

Here is a minimal web page using `ctrling`.

    <!doctype html>
    <html>
    <head>
        <meta charset='utf-8'>
        <title>Getting Started</title>
        <script src="./ctrling.js"></script>
    </head>
    <body>
        <h2>Getting Started</h2>
        <ctrl-ing ref="obj">
            [ {"sec":"hdr","text":"Getting Started"},
              {"sec":"chk","label":"Toggle","path":"$['toggle']"},
              {"sec":"out","label":"const obj = ","path":"$"}
            ]
        </ctrl-ing>
        <script>
            const obj = {
                toggle: false
            }
        </script>
        </body>
    </html>

Listing 2: Minimalistic example using `ctrl-ing` element.

## 2. Implementation Aspects

The connection from JSON sections to application parameter values is established via their section object's `path` members, whose values obey the syntax of Normalized Paths according to Internet standard JSONPath (IETF). Herein the root identifier `$` will be replaced by the object variable name indicated by the `<ctrl-ing>` element's `ref` attribute.

Each JSON section creates a HTML `<section>` element at initialization time. Let's take a simple example

[ {"sec":"hdr","text":"Toggle Example"}, {"sec":"chk","label":"Toggle","path":"$['toggle']"} ]

which, by using

    <ctrl-ing ref="obj">
        [ {"sec":"hdr","text":"Toggle Example"},
          {"sec":"chk","label":"Toggle","path":"$['toggle']"}
        ]
    </ctrl-ing>

will create a GUI menu with a simple shadow DOM structure represented as

    <main>
      <section class="hdr">Toggle Example</section>
      <section class="chk">
        <label>Toggle<input type="checkbox"></label>
      </section>
    </main>

Thanks to web component's encapsulation that markup is hidden and separated from other code on the page — avoiding code collisions [1].

So for each JSON section there is a HTML `<section>` element containing either plain visually representative or interactive form elements.

Type | Class | HTML | Task
--- | --- | --- | ---
`btn` | Input | `<button>` | Perform an action by calling a parameterless function or object method.
`chk` | Input | `<input type="checkbox">` | Display a checkbox for setting Boolean parameter values.
`col` | Input | `<input type="color">` | Display a color menu for setting RGB color parameter values.
`mtr` | Input | Display a color menu for setting RGB color parameter values. |

## 3. Sections

### Button (`btn`)

The `btn` section is used to trigger an action of some kind, so it supports the invocation of a parameterless object method or a global function. You can specify a `label` and/or a `text` property. If one of them is missing, the other one is taken as the button text.

    <ctrl-ing ref="btn" darkmode>
      [ {"sec":"hdr","text":"Buttons"},
        {"sec":"btn","label":"Object method","text":"call","path":"$['method']"},
        {"sec":"btn","label":"Global function","text":"call","path":"window['fnc']"}
      ]
    </ctrl-ing>

    function fnc() { alert("'function' called."); }
    var btn = {
      method() { alert("'method' called."); }
    }

[ {"sec":"hdr","text":"Buttons"}, {"sec":"btn","label":"Object method","text":"call","path":"$['method']"}, {"sec":"btn","label":"Global function","text":"call","path":"window['fnc']"} ]

Properties

`btn` | Default | Comment
--- | --- | ---
`label` | - | Label text.
`text` | - | Button text.
`path` | - | Location of method within reference object or global function.
`[disabled]` | - | Disable button.

HTML in Shadow DOM

    <section class="btn">
      Object method
      <button type="button">call</button>
    </section>

### Checkbox (`chk`)

The `chk` section is usually assigned to a boolean property of the object to be controlled.

    <ctrl-ing ref="chk" darkmode>
      [ {"sec":"hdr","text":"Checkbox"},
        {"sec":"chk","label":"Checkbox","path":"$['toggle']"}
      ]
    </ctrl-ing>

[ {"sec":"hdr","text":"Checkbox"}, {"sec":"chk","label":"Checkbox","path":"$['toggle']"} ]

Properties

`chk` | Default | Comment
--- | --- | ---
`label` | - | Label text.
`path` | - | Location of reference value within reference object.
`[value]` | reference value | Only used as value, if reference value is not available.
`[disabled]` | - | Disable input element.

HTML in Shadow DOM

    <section class="chk">
      <label>
        Toggle
        <input type="checkbox" checked>
      </label>
    </section>

### Color (`col`)

The `col` section provides a user interface to assign a RGB color to an object property.

    <ctrl-ing ref="chk" darkmode>
      [ {"sec":"hdr","text":"Color"},
        {"sec":"col","label":"Color","path":"$['color']"}
      ]
    </ctrl-ing>

[ {"sec":"hdr","text":"Color"}, {"sec":"col","label":"Color","path":"$['color']"} ]

Properties

`col` | Default | Comment
--- | --- | ---
`label` | - | Label text.
`path` | - | Location of reference value within reference object.
`[value]` | `#000000` | Used as value, if reference value is not available.
`[disabled]` | - | Disable input element.

HTML in Shadow DOM

    <section class="col">
      Color
      <span>
        <input type="color" value="#456789">
        <output>#456789</output>
      </span>
    </section>

### Header (`hdr`)

    <ctrl-ing darkmode>
      [ {"sec":"hdr","text":"Header Only"} ]
    </ctrl-ing>

[ {"sec":"hdr","text":"Header Only"} ]

HTML in Shadow DOM

    <section class="hdr">
      Header Only
    </section>

We can use multiple headers for visually combining sections.

### Meter (`mtr`)

The `mtr` section provides a graphic view of a scalar object property value within a known range. It displays the value in its initial state and is not aware of value changes, unless either/or the

- `<ctrl-ing>`'s `autoupdate` property is set.
- API method `update()` is called.

    <ctrl-ing ref="mtr" darkmode>
      [ {"sec":"hdr","text":"Meter"},
        {"sec":"mtr","label":"Volumn","path":"$['volumn']","min":20,"max":80,
                     "low":45,"high":70,"optimum":40,"value":50,"unit":"m&sup3;"}
      ]
    </ctrl-ing>

[ {"sec":"hdr","text":"Meter"}, {"sec":"mtr","label":"Volumn","path":"$['volumn']","min":20,"max":80, "low":45,"high":70,"optimum":40,"value":50,"unit":"m³"} ]

Properties

`mtr` | Default | Comment
--- | --- | ---
`label` | - | Label text.
`path` | - | Location of scalar reference value within reference object.
`[value]` | [`min` | 0] | Used as value, if reference value is not available.
`[min]` | 0 | Minimum value.
`[max]` | 1 | Maximum value.
`[low]` | `min` | Lower limit value (`low` ≥ `min`). Meter may change color when below.
`[high]` | `max` | Upper limit value (`high` ≤ `max`). Meter may change color when above.
`[optimum]` | - | Optimal numeric value (`min` ≤ `optimum` ≤ `max`); e.g., if it lies between `min` and `low`, that region is colored as the preferrable one.
`[unit]` | - | Append unit string.

HTML in Shadow DOM

    <section class="mtr">
      Volumn
      <span>
        <meter value="40" min="20" max="80" low="45" high="70" optimum="30"></meter>
        <output>40</output>
        <span>m&sup2;</span>
      </span>
    </section>

### Number (`num`)

The `num` section provides a user interface to enter a numerical value and assign it to an object property. It uses the HTML `<input type="number">` element and supports its `min`, `max` and `step` attributes.

Example

    <ctrl-ing ref="num" darkmode>
      [ {"sec":"hdr","text":"Number"},
        {"sec":"num","label":"Number","min":1,"max":2,"step":0.2,
                     "path":"$['number']","unit":"[s]"}
      ]
    </ctrl-ing>

[ {"sec":"hdr","text":"Number"}, {"sec":"num","label":"Number","min":1,"max":2,"step":0.2, "path":"$['number']","unit":"[s]"} ]

HTML in Shadow DOM

    <section class="num">
        <label>
            Number
            <span>
                <input type="number" value="0" min="1" max="2" step="0.2">
                <span>[-]</span>
            </span>
        </label>
    </section>

Properties

`num` | Default | Comment
--- | --- | ---
`label` |  | Label text.
`[min]` | - | Minimum value.
`[max]` | - | Maximum value.
`[step]` | `1` | Step size.
`[fractions]` | - | Number of decimal digits.
`[value]` | reference value | Only used as value, if reference value is not available.
`path` |  | Location of reference value within reference object.
`[unit]` | - | Unit symbol of value.
`[disabled]` | - | Disabled input.

### Output (`out`)

The `out` section displays an object property value in its initial state. It is not aware of value changes, unless either/or the

- `<ctrl-ing>`'s `autoupdate` property is set.
- API method `update()` is called.

The property value is mutated to JSON text for display. So only primitive object properties and getters are shown in case of structured property values.

    <ctrl-ing ref="mtr" darkmode>
      [ {"sec":"hdr","text":"Output"},
        {"sec":"out","label":"sub","path":"$['sub']"}
      ]
    </ctrl-ing>

[ {"sec":"hdr","text":"Output"}, {"sec":"out","label":"Point","path":"$['pnt']","unit":"[mm]"} ]

Properties

`out` | Default | Comment
--- | --- | ---
`label` | - | Label text.
`path` | - | Location of reference value.
`[value]` | reference value | Only used as value, if reference value is not available.
`[unit]` | - | Append unit string.

HTML in Shadow DOM

    <section class="out">
      Point
      <span>
        <output>{
          "x":5,
          "y":7
        }</output>
        <span>[mm]</span>
      </span>
    </section>

### Selection (`sel`)

The `sel` section provides a drop down menu of options. It uses the HTML `<select>` element. Options can be represented either by object members or array elements. Then object member names are displayed in the drop down menu and member values are inserted as property values. Array elements (strings or numbers) are used as drop down items as well as target object property values.

Please note that the HTML `<selects>`'s `multiple` attribute and `<optgroup>` elements are not supported.

    <ctrl-ing ref="sel" darkmode>
      [ {"sec":"hdr","text":"Select"},
        {"sec":"sel","label":"Linestyle","path":"$['lineStyle']",
         "options":["solid","dashed","dotted"]},
        {"sec":"sel","label":"Thickness","path":"$['thickness']",
         "options":{"thin":1,"medium":2,"thick":3}}
      ]
    </ctrl-ing>

[ {"sec":"hdr","text":"Output"}, {"sec":"sel","label":"Linestyle","options":["solid","dashed","dotted"],"path":"$['lineStyle']"}, {"sec":"sel","label":"Thickness","options":{"thin":1,"medium":2,"thick":3},"path":"$['thickness']"} ]

Properties

`sel` | Default | Comment
--- | --- | ---
`label` | - | Label text.
`[options]` | - | Object as member name/value pairs or array elements.
`[value]` | reference value | Only used as value, if reference value is not available.
`path` | - | Location of reference value.
`[disabled]` | - | Disabled selection.

### Separator (`sep`)

The `sep` section displays a separation line using the HTML `<hr>` element. It has no properties.

    <ctrl-ing darkmode>
      [ {"sec":"hdr","text":"Separator"},
        {"sec":"sep"}
      ]
    </ctrl-ing>

[ {"sec":"hdr","text":"Output"}, {"sec":"sep"} ]

HTML in Shadow DOM

    <section class="sep">
      <hr>
    </section>

### Text (`txt`)

The `txt` section provides a user interface to enter a text string and assign it to an object property. It uses the HTML `<input type="text">` element.

Please note that multiline text is not supported.

Example

    <ctrl-ing ref="txt" darkmode>
      [ {"sec":"hdr","text":"Text"},
        {"sec":"txt","label":"Text","path":"$['text']"}
      ]
    </ctrl-ing>

[ {"sec":"hdr","text":"Text"}, {"sec":"txt","label":"Text","path":"$['text']"} ]

HTML in Shadow DOM

    <section class="txt">
      <label>
        Text
        <input type="text" value="something ...">
      </label>
    </section>

Properties

`txt` | Default | Comment
--- | --- | ---
`label` |  | Label text.
`path` |  | Location of reference value within reference object.
`[value]` | reference value | Only used as value, if reference value is not available.
`[disabled]` | - | Disabled input.

### Vector (`vec`)

The `vec` section provides a user interface for object/array structures. It offers for each member/element an individual input field using HTML `<input type="text">` elements in grid-like structure. The `path` member in this section is an array holding multiple pathes, rather than a singular path.

Size of the input fields might be controlled by `width` member accepting CSS units. Input elements on the grid lines are wrapped onto a new line in case of overflow. They are always right aligned.

Example

    <ctrl-ing ref="vec" darkmode>
      [ {"sec":"hdr","text":"Vector"},
        {"sec":"vec","label":"Array",
         "path":["$['arr'][0]","$['arr'][1]","$['arr'][2]","$['arr'][3]"]},
        {"sec":"vec","label":"Object (x,y,z)",
         "path":["$['vec']['x']","$['vec']['y']","$['vec']['z']"],
         "unit":"[mm]"}
      ]
    </ctrl-ing>

[ {"sec":"hdr","text":"Vector"}, {"sec":"vec","label":"Array", "path":["$['arr'][0]","$['arr'][1]","$['arr'][2]","$['arr'][3]"]}, {"sec":"vec","label":"Object (x,y,z)", "path":["$['vec']['x']","$['vec']['y']","$['vec']['z']"], "unit":"[mm]"} ]

HTML in Shadow DOM

    <section class="vec">
      Vector
      <span>
        <input type="text" value="1">
        <input type="text" value="2">
        <input type="text" value="3">
      </span>
      <span>[mm]</span>
    </section>

Properties

`vec` | Default | Comment
--- | --- | ---
`label` |  | Label text.
`path` | - | Location of reference value within reference object.
`[width]` | - | Input fields width in CSS units.
`[unit]` | - | Unit string.
`[disabled]` | - | Disabled input fields.

## 2. How to Use ?

    <ctrl-ing ref="obj1" darkmode>
      [ {"sec":"hdr","text":"Demo-1"},
        {"sec":"num","label":"Numbers","path":"$['numbers'][0]","unit":"[-]"},
        {"sec":"chk","label":"Toggle","path":"$['toggle']"}
      ]
    </ctrl-ing>

Listing 1: Constructor – cartesian coordinates with origin centered.

[ {"sec":"hdr","text":"Demo-1"}, {"sec":"num","label":"Numbers[0]","path":"$['numbers'][0]","unit":"[-]"}, {"sec":"chk","label":"Toggle","path":"$['toggle']"} ]

View coordinates provided by events can be controlled in the constructor by an additional view argument `{x=0,y=0,scl=1,cartesian=false}` beside the canvas `RenderingContext2D` object.

- `x,y` … view's origin location.
- `scl` … view's scaling.
- `cartesian` … cartesian coordinate system (y-axis up).

## 3. Handling Events

Each `canvasInteractor` instance handles DOM pointer events and some custom events.

Table 1: Supported Events

Event | Comment
--- | ---
`pointermove` | Pointer moved.
`pointerdown` | Pointer device button pressed.
`pointerup` | Pointer device button released.
`pointerenter` | Pointer enters canvas.
`pointerleave` | Pointer leaves canvas.
`pointercancel` | DOM event forwarded.
`click` | Pointer device button pressed and released at the same location.
`wheel` | Pointer device wheel event.
`tick` | Throttled timer event. At most every 60 milliseconds.
`pan` | Pan by pointer device. Occuring only with (left) button pressed.
`drag` | Drag by pointer device. Occuring only with (left) button pressed and something was signalled as 'hit'.

Instead of registering events via well known `addEventListener`, an application registers events to a `canvasInteractor` instance using thats `on` method.

    // ...
    interactor.on('pointermove', (e) => { /* do stuff */ })
              .on('tick', (e) => { /* do other stuff */ })
              .on('pan',  (e) => { /* do yet other stuff */ })
              .startTimer();

Listing 2: Registering events and starting tick timer.

The `pointermove` event in combination with (left) pointer device button pressed also notifies the application of a `drag` or `pan` event, whether an underlying element is `hit` or not. See the example how to control that behavior.

Callback functions registered via `on` recieve an extended event object `e`.

Table 2: Event object properties

Property | Type | Comment
--- | --- | ---
`type` | `string` | Event type.
`x`, `y` | `number` | Canvas coordinates with respect to upper left or lower left (`cartesian`) corner.
`dx`, `dy` | `number` | Pointer location displacement from previous position.
`xusr`, `yusr` | `number` | Coordinates with respect to user defined view origin and scaling.
`dxusr`, `dyusr` | `number` | Pointer location displacement with respect to user defined view scaling.
`btn` | `number` | Pointer device button identifier on button press (left: `1`, right: `2`, middle: `4`).
`dbtn` | `number` | Pointer device button difference on button release (left: `-1`, right: `-2`, middle: `-4`).
`eps` | `number` | Some pixel tolerance for selecting/hitting (default = `5`).
`inside` | `boolean` | Is pointer currently inside canvas.
`delta` | `number` | Wheel delta.
`hit` | `boolean` | Needs to be set by application within `pointermove` event. Can be treated then within `tick` event.

## 4. Example

The example shows how to use `canvasInteractor`. Rectangles can be `drag`ged, whereas the origin symbol can not, which induces the `pan` event. `zoom`ing is done by the pointer device' wheel operation.

fps: - | cartesian | zoom-scale: 1 | pos: ./. | state: - |

### Example Code

    <!doctype html>
    <html>
    <head>
        <title>canvasInteractor example</title>
    </head>

    <body>
        <h1>canvasInteractor example</h1>
        <canvas id="c" width="601" height="301"
                style="border:1px solid black;background-color:snow"></canvas>

        <script src="https://cdn.jsdelivr.net/gh/goessner/canvasinteractor/canvasInteractor.js"></script>
        <script>
        const ctx = document.getElementById('c').getContext('2d');
        const interactor = canvasInteractor.create(ctx, {x:200,
                                                         y:100,
                                                         scl:1,
                                                         cartesian:true});
        const rec1 = {x:50,y:50,b:80,h:60,fs:'orange',lw:4};
        const rec2 = {x:150,y:50,b:100,h:40,fs:'cyan',lw:4};

        function render() {
            // ...
        }

        function hitRec(x,y,rec) {
            return (x > rec.x && x < rec.x + rec.b && y > rec.y && y < rec.y + rec.h);
        }
        interactor
            .on('tick', (e) => {
                render();
            })
            .on('pointermove', (e) => {
                rec1.sel = hitRec(e.xusr,e.yusr,rec1) ? true : false;
                rec2.sel = hitRec(e.xusr,e.yusr,rec2) ? true : false;
                e.hit = rec1.sel || rec2.sel;
            })
            .on('wheel',  (e) => {   // zooming about pointer location ...
                interactor.view.x = e.x + e.dscl*(interactor.view.x - e.x);
                interactor.view.y = e.y + e.dscl*(interactor.view.y - e.y);
                interactor.view.scl *= e.dscl;
            })
            .on('pan',  (e) => {
                interactor.view.x += e.dx;
                interactor.view.y += e.dy;
            })
            .on('drag', (e) => {
                if (rec1.sel) { rec1.x += e.dxusr; rec1.y += e.dyusr; }
                if (rec2.sel) { rec2.x += e.dxusr; rec2.y += e.dyusr; }
            })
            .startTimer();
        </script>
    </body>
    </html>

Listing 3: canvasInteractor example.

## 5. Conclusion

`canvasInteractor` is a tiny JavaScript library enhancing and extending HTML canvas' event handling for performant animation and geometrical interaction.

A global event loop (singleton) based on `requestAnimationFrame` provides assistance with event throttling via its custom `tick` event. Cartesian coordinates preferred in scientific and engineering applications are supported. Pan, drag and zoom based on user defined origins can be done.

## References

[1] Canvas API, https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
[2] D. Corbacho, Debouncing and Throttling Explained Through Examples
https://css-tricks.com/debouncing-throttling-explained-examples/
[3] S. Goessner, `canvas-area`, https://github.com/goessner/canvas-area
[4] S. Goessner, Make your HTML canvas Interactive, Researchgate, DOI: 10.13140/RG.2.2.31978.39367