Knob / slider animation

From FlightGear wiki
Jump to navigation Jump to search

The new (in 2.11) knob animation and slider animation are helpers to reduce repetition in XML files, and provide a separation point for behaviour versus user interface. The idea is to specify a single animation element representing the knob, dial or slider, with bindings for various actions, and allow the GUI to decide which user interactions map to those behaviours. In particular the mapping could be adjusted by platform (Mac vs Linux), device type (laptop, trackpad, trackball or touch-screen) or a GUI preference. All objects using the animation would hence have standard user-interaction across all aircraft, which is not the case at present.

Rotate / Translate animation

The core animation values are identical to the standard 'rotate' (for a knob) and 'translate' (for a slider) animations. They can be omitted without problem if you want to keep the animations separate for some reason, e.g. different pick objects to animated objects in the model. This also means the knob could be used with more complex compound animations.

Binding Actions

Using the <action> group, a single set of bindings are run for movement of the knob/slider in either direction. An 'offset' property is exposed, with a value of either 1 or -1. For many common cases, such as a property-adjust command, this will work for both directions of movement, with suitable scale and min/max limits.

If you need different behaviours for increase vs decrease, add bindings to the <increase> and <decrease> groups instead. Note both systems can be used in parallel - all the bindings fire.

A <release> binding is available which fires when the button is released, for spring-loaded knobs (commonly used for engine starters) which return to a position after selection.

Note that if you are using a property-adjust binding, you must use <factor> to control the numerical change, since this scales the <offset> parameter. If you use <step>, bidirectional control via the same binding will not work. If for some reason your actions are asymmetric, then you will need separate increase and decrease bindings as described above. However, an asymmetric instrument might not be a good choice for these animations, for the sake of end-user understanding!

Shifted Mode

By default, in shifted mode, the normal bindings above are repeated. The default repeat is 10, but this can be changed with the <shift-repeat> property. This is deliberately coded to behave exactly the same as clicking the button a number of times, so the 'offset' in the action bindings is still 1 or -1. This is to simplify code for bindings having to deal with steps at the end of the range.

If the above mechanism is too simple, or you wish shifted mode to do something different to unshifted mode, you can specify explicit bindings instead. As soon as you specify an explicit shifted binding, the default 'repeat' behaviour is skipped. Examples of alternate shifted mode would be many MCP or GPS knobs, where pushed/pulled mode might change a totally different setting, eg fractional vs integer part of a radio frequency, or bank limit instead of selected heading.

The group names are <shift-action>, <shift-increase> and <shift-decrease>

Dragging

Dragging the object generates value changes, based on the direction of mouse movement. The mapping from pixel movement to offset values is controlled by the <drag-scale-px> property, which divides the pixel movement. Hence, smaller values mean faster movement (harder to hit a precise value accurately) and larger values mean slower movement (easier to hit a particular value, but more mouse movement needed). The default value is 10px to cause a change.

By default the drag direction is left-right (since most people have better mobility in that direction, and screens are wider than tall), but each slider or knob can over-ride this. For example, for throttle or mixture controls built-into a panel, up-down movement will feel more natural. Set <drag-direction> to 'vertical' or 'horizontal'.

Compatibility

Do not refer to modifier-state or button-state properties in your bindings. This violates the control separation which is the aim of these animations. If you include such references in your bindings, you make it very likely your knobs/sliders will break for some future user with a different platform- or input- configuration. You can't assume how many mouse buttons they have, or how their modifier keys are mapped, so the only safe thing is not to make any assumptions at all.

This might mean creating multiple in-cockpit animations for a single real-world knob in the worst case, in which case design with great care. For example the KLN-89B has a 'dual' knob (outer and inner rings) and the inner knob can be pulled out. (Some Airbus MCP panels have something similar, but the knob can be pushed or pulled). In the case of the KLN-89, the solution would be two separate knob animations, with the outer one probably having a large surrounding pick-proxy object to ensure it can be hit comfortably in a front-on view. Shifted mode on the inner knob then correspond to 'pulled out' mode in real-life.

Another option (which would also work for the Airbus) would be to make the centre of the knob pick-able separately, to drive pushing/pulling the knob, and then have the knob animation pick cover the outer circumference. This is not ideal but still much more usable than adding conditional bindings on alternate mouse buttons or modifiers! Good use of tooltips will also help here, i.e. the tooltip for the knob would include some text like 'click here to pull out'.

Examples

Basic usage:

Cycles the property between 0..3 and rotates the knob object in steps of 60° (property value x factor). The knobs 3D model must be positioned at the '0' position in order to move in sync.

 <?xml version="1.0" encoding="utf8"?>
 <PropertyList>
    <animation>
        <name>Wiper Left</name>
        <type>knob</type>
        <object-name>wiper.left</object-name>
        <property>controls/switches/wiper-left</property>
        <factor>60</factor>
        <axis>
            <object-name>wiper.left.axis</object-name>
        </axis>
        <action>
            <binding>
                <command>property-cycle</command>
                <property>controls/switches/wiper-left</property>
                <value>0</value>
                <value>1</value>
                <value>2</value>
                <value>3</value>
                <wrap>false</wrap>
            </binding>
        </action>
    </animation>
 </PropertyList>

Another basic example:

 <?xml version="1.0" encoding="utf8"?>
 <PropertyList>
  <animation>
        <type>knob</type>
        <object-name>ALT-SEL</object-name>
        <value>0.0</value> <!-- or property as usual for rotate animation -->
        <center>
            <x-m>0.0</x-m><y-m>0.027</y-m><z-m>0.0238</z-m>
        </center>
        <axis>
            <x>-1</x><y>0</y><z>0</z>
        </axis>
        
        <action>
            <binding>
                <command>property-adjust</command>
                <property>/autopilot/settings/target-altitude-ft</property>
                <factor>100</factor>
                <min>0</min>
                <max>40000</max>                
                <wrap>false</wrap>
            </binding>
        </action>
        <!-- explicit increase binding, action binding still runs too -->
        <increase>
            <binding>
                <command>nasal</command>
                <script>debug.dump('was increased', cmdarg().getNode('offset').getValue());</script>
           </binding>
        </increase>
        <!-- explicit decrease binding, action binding still runs too -->
        <decrease>
            <binding>....</binding>
        </decrease>
    </animation>
 </PropertyList>

Custom shifted-usage

 <?xml version="1.0" encoding="utf8"?>
 <PropertyList>
  <animation>
        <type>knob</type>
        <object-name>Radio.TUNE</object-name>
        <value>0.0</value>
        <center>...</center>
        <axis>...</axis>
        
        <action>
            <binding>
                <command>property-adjust</command>
                <property>/instrumentation/nav[0]/standby-frequency-mhz</property>
                <factor>1</factor>
                <min>108</min>
                <max>122</max>                
                <wrap>true</wrap>
            </binding>
        </action>
        
        <shift-action>
            <binding>
                <command>property-adjust</command>
                <property>/instrumentation/nav[0]/standby-frequency-mhz</property>
                <factor>0.05</factor>
            </binding>
        </shift-action>

    </animation>
 </PropertyList>

Spring-loaded position:

 <?xml version="1.0" encoding="utf8"?>
 <PropertyList>
  <animation>
        <type>knob</type>
        <object-name>APU-Start</object-name>
        <property>/controls/apu/rotary-start</property>
        <!-- center, axis, factor, ... -->

        <!-- Normal action, positions 0, 1 and 2 -->
        <action>
            <binding>
                <command>property-adjust</command>
                <property>/controls/apu/rotary-start</property>
                <min>0</min>
                <max>2</max>
            </binding>
        </action>

        <!-- Release action, limit to 0 and 1. 2 is spring loaded starter position -->
        <release>
            <binding>
                <command>property-adjust</command>
                <property>/controls/apu/rotary-start</property>
                <step>0</step>
                <min>0</min>
                <max>1</max>
            </binding>
        </release>
    </animation>
 </PropertyList>

Slider (good for breakers):

 <?xml version="1.0" encoding="utf8"?>
 <PropertyList>
    <animation>
        <type>slider</type>
        <object-name>brk-ku-elec</object-name>
        <property>/fdm/jsbsim/systems/mechanical/ku-antenna-elec-cb</property>
        <factor>0.006</factor>
        <axis>
            <x>0</x>
            <y>1</y>
            <z>0</z>
        </axis>
        <action>
            <binding>
                <command>property-toggle</command>
                <property>/fdm/jsbsim/systems/mechanical/ku-antenna-elec-cb</property>
            </binding>
        </action>
    </animation>
 </PropertyList>

Slider used as a "Spring/contact" switch (reverts to 0 on exit):

 <?xml version="1.0" encoding="utf8"?>
 <PropertyList>    <animation>
        <type>slider</type>
        <object-name>a6-att-ref</object-name>
        <property>/fdm/jsbsim/systems/adi/att-ref</property>
        <factor>0.002</factor>
        <axis>
            <x>1</x>
            <y>0</y>
            <z>0</z>
        </axis>
        <action>
            <binding>
                <command>property-assign</command>
                <property>/fdm/jsbsim/systems/adi/att-ref</property>
                <value>1</value>
            </binding>
            <binding>
                <command>nasal</command>
                <script>
                    SpaceShuttle.coas_att_ref();
                    SpaceShuttle.click("avionics");
                </script>
            </binding>
        </action>
        <release>
            <binding>
                <command>property-adjust</command>
                <property>/fdm/jsbsim/systems/adi/att-ref</property>
                <step>0</step>
                <min>0</min>
                <max>0</max>
            </binding>
        </release>
        <hovered>
            <binding>
                <command>set-tooltip</command>
                <tooltip-id>a6-att-ref</tooltip-id>
                <label>Attitude Reference</label>
            </binding>
        </hovered>
    </animation>
 </PropertyList>

Note: it has been reported that especially with a low framerate, you need to hold down a knob / slider with a <release> binding using the property-assign or property-adjust some half second instead of just clicking it, to actually make the bindings run. To work around this, use property-interpolate with a time of 0.01 or so - this works always. Example:

<action>
    <binding>
        <command>property-assign</command>
        <property>instrumentation/nav/swap-button-pressed</property>
        <value>1</value>
    </binding>
</action>
<release>
    <binding>
        <command>property-interpolate</command>
        <property>instrumentation/nav/swap-button-pressed</property>
        <value>0</value>
        <time>0.01</time>
    </binding>
</release>

Related content