Creating components from templates: Rotary Knob

With this template you can add a new Rotary Knob component to your project. Rotary Knob is a widget the user can interact with in order to change a number value by touching and dragging a knob (performing one finger rotation gesture). Each time the user interacts with a rotary knob, the component sends signals to associated slot methods where your particular implementation is executed. If the rotary knob is focused the component can also be controlled by pressing keys on the keyboard or by using hardware buttons.

Components created with this template are intended to be adapted to your particular design expectations. After adding the new Rotary Knob you should edit the component, change its appearance and if desired also its behavior. Once you have adapted the component, you can embed instances of this Rotary Knob wherever you need in your GUI project. Because it serves as template, it is intentionally kept very simple. Nevertheless, Rotary Knobs created by the template are working widgets. If desired, they can already be used as they are. The following figure demonstrates the default appearance of the Rotary Knob created by using the here described component template:

The approach with component templates has two functions. Primarily the templates should simplify the development of new components. Instead of creating the Rotary Knob from scratch you can use the available template. The second function is more educative. The template implements fully working Rotary Knob component you can investigate and learn about the corresponding programming aspects. The template is well documented. It contains annotations and inline comments with instructions helping you to understand how the component works and how it can be adapted to your particular needs.

This chapter provides an overview how the Rotary Knob component template is used within your own application and how you adapt this component according to your particular needs. You will find here also further details concerning the internal implementation of the Rotary Knob component.

Component templates versus ready-to-use widgets

Besides the here described Rotary Knob component template, Embedded Wizard provides also a ready to use Rotary Knob widget. It is finished implemented and ready to be used as it is. Within a certain limits, the appearance and partially also the behavior of the Rotary Knob widget can be configured. The advantage of the approach is, that to customize it you don't need to write a single line of code. The flexibility of the provided widget, however, is limited by its implementation.

With the component template you are not limited. But you have to take care of the adaptation of the so created component. You have to write code. More deep understanding is required. Summarized, the ready to use widgets follow a convenient, simple but also limited approach where you don't need to write a single line of code. With the here described component templates you have to write code by which you are flexible to create very individual and sophisticated widgets. Which of the both approaches applies to you does depend on your application case. By understanding the differences in the both concepts you can select the optimal one.

Add new Rotary Knob component

To create a new Rotary Knob component from a template you simply Drag & Drop it between the Templates window and the Composer with an opened unit. This is important in that by using the component templates you add in fact a new class to your project. Classes, in turn, can exist within units only. The following are the typical steps to create a new Rotary Knob component from a template:

First switch to the Composer page for the respective unit, where you want to add the new Rotary Knob component.

Then ensure that the Templates window is visible.

In Templates window switch to the folder Component Templates.

In the folder locate the Rotary Knob template.

Drag & Drop the template into the Composer window:

Eventually name the new added component.

The new created Rotary Knob component appears accompanied by annotation providing helpful tips how to proceed. If undesired, you can select and delete the annotation.

Use the Rotary Knob component

Once you have created the Rotary Knob component, you can use it to assemble more complex components. Technically seen, you embed an instance of the Rotary Knob class in-place within some superior GUI component. At the runtime, the superior GUI component takes care of the correct initialization and the displaying of all embedded components, so they appear similarly as you have composed them at the design time.

Step 1. Add new Rotary Knob instance

The following are the typical steps to create a new instance of an already existing Rotary Knob component:

First switch to the Composer page for the respective GUI component, where you want to add the new Rotary Knob.

Then ensure that the Browser window is visible.

Within the Browser locate the class of the previously created Rotary Knob. This can be done easily with Browser's own filter function.

Select the found class in the Browser window.

Drag & Drop the selected class into the Composer area.

Eventually name the new instance according to its function within the GUI component (e.g. MicrophoneVolume).

Component templates are intended to create widgets which can be modified and adapted to your particular design expectations. In the following sections you will learn how to do this. Originally, if not yet modified, the Rotary Knob appears composed of an image representing the knob and a border surrounding the component. When the user interacts with the component, the image is rotated. Our intention is to keep the component templates as minimalistic as possible so they don't distract you with less imporntant design details.

Step 2. Inspect the Rotary Knob instance

As long as the Rotary Knob is selected you can inspect and modify its properties conveniently in the Inspector window as demonstrated with the property Bounds in the screenshot below. This is in so far worth mentioning as diverse features of the Rotary Knob are controlled by the corresponding properties. If you are not familiar with the concept of a property and the usage of Inspector window, please read first the preceding chapter Compositing component appearance.

The Rotary Knob component descends from the Mosaic class Core::Group. Consequently, most of the properties listed in the above screenshot are inherited from this base class. Particular to the Rotary Knob are only few following properties:

Property

Description

CurrentValue

The property CurrentValue stores the momentary value of the Rotary Knob. This value corresponds to the actual rotation of the knob and it is limited automatically to the range specified by the properties MinValue and MaxValue.

MaxValue

The property MaxValue defines the maximum value for the widget's property CurrentValue. This corresponds to the value at the right end of the knob's rotation range.

MinValue

The property MinValue defines the minimum value for the widget's property CurrentValue. This corresponds to the value at the left end of the knob's rotation range.

OnApply

The property OnApply can refer to a slot method, which will receive a signal as soon as the user has released the widget after rotating the knob. It means, the method is signaled when the user has finalized the interaction with the Rotary Knob. Thereupon the method's logic will be executed. In the associated slot method you can evaluate the widget's current value CurrentValue.

OnChange

The property OnChange can refer to a slot method, which will receive a signal each time the user rotates the knob. It means, the method is signaled repeatedly while the user interacts with the Rotary Knob. Thereupon the method's logic will be executed. In the associated slot method you can evaluate the widget's current value CurrentValue.

Outlet

The property Outlet can refer to any other int32 property the widget should remain synchronized with. When the user rotates the knob, the affected property is automatically updated to reflect the widget's current value. On the other hand, when the referred property is modified by another one, the Rotary Knob is automatically notified to remain in sync with the property.

This approach follows the model-view-controller (MVC) programming paradigm, where the Rotary Knob has the function of the view and controller and the property referred via Outlet serves as model. See also Outlet properties.

Step 3. Arrange the Rotary Knob within the superior component

Once added to the component, you can freely move the Rotary Knob instance, or you simply grab one of its corners and resize it in this way. You can control the position and the size of the component also by directly modifying its property Bounds. If you want the Rotary Knob to appear behind other views you can reorder it explicitly.

Step 4. Determine the Rotary Knob's value range and its current value

The Rotary Knob is intended to allow the user to conveniently change an integer value. When the user touches and drags on the Rotary Knob, the value changes. This alternation is reflected in the widget's property CurrentValue. By evaluating this property you can simply query the value which is actually set in the affected widget. Accordingly, when you modify the property CurrentValue, the affected Rotary Knob will implicitly update the position (the rotation) of its knob. For example:

// Evaluate the current position of the Rotary Knob. if ( RotaryKnob.CurrentValue > 80 ) ... [...] // Change the position of the Rotary Knob. RotaryKnob.CurrentValue = 50;

The possible value range for the property CurrentValue is determined by the both properties MinValue and MaxValue, whereby the value specified in the property MinValue corresponds to the leftmost position of the knob and the value of the property MaxValue to the rightmost position. Thus, the value of the property CurrentValue lies always between MinValue and MaxValue. When the user rotates the knob, the new value is interpolated within this range. The following figure demonstrates the relations between the three properties:

Step 5. Implement Rotary Knob's slot method

While the user interacts with the Rotary Knob, the component sends signals to associated slot methods. Within the slot method your particular implementation can react and process the event. If the Rotary Knob is controlled by keyboard events, the component can also be controlled by using the keys Plus or Minus. This is the default behavior implemented in the Rotary Knob template.

The slot methods are connected to the Rotary Knob by simply storing them in the for this purpose available properties OnChange and OnApply. The slot method connected to property OnChange is signaled each time the user rotates the knob. It means, while the user interacts with the Rotary Knob. In turn, the slot method connected to property OnApply is signaled when the user has released the widget after rotating the knob. It means, when the user has finalized the interaction. You can initialize the properties OnApply and OnChange with already existing slot methods or you add a new one and implement it as desired. The following steps describe how to do this:

First add a new slot method to your GUI component.

Assign the slot method to the property OnChange of the affected Rotary Knob if you want to handle the events while the user drags the knob. If you want to handle the event when the user has finalized the interaction, use the property OnApply.

Open the slot method for editing.

In the Code Editor implement your desired operation to execute when the event occurs. Especially your implementation can evaluate the widget's property CurrentValue to determine the actual position of the knob. For example:

SetMicrophoneVolume( RotaryKnob.CurrentValue );

When you start the Prototyper now, you can interact with the component. Thereupon the Rotary Knob will trigger the associated slot method.

Step 6. Connect the Rotary Knob with a data provider

To simplify the development of GUI applications, the Rotary Knob implements a technique permitting you to connect it directly to a data provider. Once connected, the widget will remain in sync with the value stored actually in this provider. Similarly, if the user rotates the knob, the associated value changes automatically. This technique corresponds to the model-view-controller (MVC) programming paradigm, where the Rotary Knob has the function of the view and controller and the associated data provider serves as model. If you associate in your application several Rotary Knobs to one and the same data provider value, the underlying mechanisms will ensure, that after interacting with one component all other affected Rotary Knobs do update their state automatically.

The connection between the Rotary Knob and the data provider is established by assigning to the Rotary Knob's property Outlet a reference to a property existing within the data provider and storing the interesting value. Since Rotary Knob is intended to deal with integer values, the property suitable to be connected via reference to the Rotary Knob has to be declared with int32 as its data type. Accordingly, the value of the referenced property corresponds to the Rotary Knob's current value.

Summarized, after assigning a reference to an existing int32 property to the Rotary Knob's own property Outlet, the widget adapts its own state to automatically correspond to the actual value of the associated property. When the user drags the knob, the associated property is modified. You don't need to write a single line of code to benefit from this mechanisms. The aspects underlying this technique are explained in the sections Outlet properties and Notifications and Observer.

Open the component for editing

Component templates are intended to create widgets which can be adapted and enhanced to your particular design expectations. For this purpose, once you have added a new Rotary Knob component to your project, you can open the component class for editing. Thereupon the implementation of the component appears in a new Composer page:

Originally, if not yet modified, the Rotary Knob appears composed of an image (representing the knob) and a border surrounding the component. When the user interacts with the Rotary Knob the image is rotated. Our intention is to keep the component templates as minimalistic as possible so they don't distract you with less important design details. The Rotary Knob can react to touch, mouse and keyboard inputs. The color and position of the knob image as well as the color of the border reflect the actual state of the widget. Especially the rotation of the knob image corresponds to widget's CurrentValue property. When the widget is focused, the border appears thick.

This default functionality is implemented by following members belonging to the Rotary Knob. These members are explicitly intended to be modified. Understanding this internal structure is thus the first step before you start to adapt the Rotary Knob to your particular needs:

Icon

Member

Description

CurrentValue

The property CurrentValue stores the momentary value of the Rotary Knob. This value corresponds to the actual rotation of the knob and it is limited automatically to the range specified by the properties MinValue and MaxValue.

MinValue

The property MinValue defines the minimum value for the widget's property CurrentValue. This corresponds to the value at the left end of the knob's rotation range.

MaxValue

The property MaxValue defines the maximum value for the widget's property CurrentValue. This corresponds to the value at the right end of the knob's rotation range.

OnSetCurrentValue

The onset method belongs to the property CurrentValue. Each time the value of the property is changed, the code implemented in the method is executed to update the rotation of the knob.

OnGetCurrentValue

The onget method belongs to the property CurrentValue. Each time the value of the property is evaluated within an expression, the code implemented in the method is executed to ensure that the returned value lies within the range specified in the properties MinValue and MaxValue.

OnSetMinValue

The onset method belongs to the property MinValue. Each time the value of the property is changed, the code implemented in the method is executed to update the rotation of the knob.

OnSetMaxValue

The onset method belongs to the property MaxValue. Each time the value of the property is changed, the code implemented in the method is executed to update the rotation of the knob.

Outlet

The property Outlet can refer to any other int32 property the widget should remain synchronized with. When the user rotates the knob, the affected property is automatically updated to reflect the widget's current value. On the other hand, when the referred property is modified by another one, the Rotary Knob is automatically notified to remain in sync with the property.

This approach follows the model-view-controller (MVC) programming paradigm, where the Rotary Knob has the function of the view and controller and the property referred via Outlet serves as model. See also Outlet properties.

OnSetOutlet

The onset method belongs to the property Outlet. Each time a new property reference is assigned to Outlet, the code implemented in the method is executed to register the Rotary Knob as observer for the referred property.

OnChange

The property OnChange can refer to a slot method, which will receive a signal each time the user rotates the knob. It means, the method is signaled repeatedly while the user interacts with the Rotary Knob. Thereupon the method's logic will be executed. In the associated slot method you can evaluate the widget's current value CurrentValue.

OnApply

The property OnApply can refer to a slot method, which will receive a signal as soon as the user has released the widget after rotating the knob. It means, the method is signaled when the user has finalized the interaction with the Rotary Knob. Thereupon the method's logic will be executed. In the associated slot method you can evaluate the widget's current value CurrentValue.

onPressTouch

This internal slot method is called when the user touches the widget's knob area (when the rotation interaction begins). This slot method is connected to the member TouchHandler.

onReleaseTouch

This internal slot method is called when the user releases the touch screen after touching and evtl. rotating the knob (when the rotation interaction ends). This slot method is connected to the member TouchHandler.

onRotateTouch

This internal slot method is called when the user drags the finger while pressing the knob. The method calculates from the made rotation the alternation of the widget's value and updates the widget accordingly. This slot method is connected to the member TouchHandler.

touchStartValue

This variable stores the widget's value at the moment when the user begun to rotate the knob. It is used to calculate the new value from the knob rotation.

KeyPlusHandler

This key handler reacts to key press events. When the user presses the key specified in the property Filter of the key handler, the handler is activated and the method onPressKey is called. When the user releases the key again, the method onReleaseKey is called.

KeyMinusHandler

This key handler reacts to key press events. When the user presses the key specified in the property Filter of the key handler, the handler is activated and the method onPressKey is called. When the user releases the key again, the method onReleaseKey is called.

onPressKey

This internal slot method is called when either the KeyPlusHandler or KeyMinusHandler has been activated (when the user has pressed the key specified in the property Filter of the respective key handler). Thereupon, the method increments or decrements the Rotary Knob's current value.

onReleaseKey

This internal slot method is called when either the KeyPlusHandler or KeyMinusHandler receives a key up event. With this the alternation of the Rotary Knob's value by using keys ends. The method has the job to notify the owner of the widget that the user has finished the interaction and update the appearance of the Rotary Knob.

keyStartValue

This variable stores the Rotary Knob's value valid at the moment when the user pressed a key handled by the KeyPlusHandler or KeyMinusHandler. It is used to detect the modification of the widget's value when the user releases the key again.

onOutlet

This internal slot method will receive a signal, if the value of the property referred by Outlet has been changed by another widget or by the application logic. In response to this notification, the Rotary Knob will update its own CurrentValue property to reflect the new value of the referred property.

Background

This Filled Rectangle view displays in the template the background of the Rotary Knob. The background is white per default.

Border

This Border view displays in the template the border of the Rotary Knob. Depending on the widget's actual state variables enabled, selected and rotating the border is either black, gray and the thickness of the border changes.

Knob

This Warp Image view represents the knob the user can touch and rotate with a finger. The image is rotated about its center position. The rotation angle corresponds to CurrentValue within the range determined by the properties MinValue and MaxValue. Depending on the widget's actual state variables enabled, rotating and selected the image's color changes between gray and red.

TouchHandler

This Rotate Touch Handler reacts to touch and mouse events and invokes the associated slot methods: onPressTouch, onReleaseTouch and onRotateTouch.

UpdateViewState

This method is invoked automatically after the state of the component has been changed. In case of the Rotary Knob, the method updates the views Knob and Border so they reflect the widget's current state.

enabled

This variable stores the current state of the Rotary Knob. It is true if the widget is allowed to handle user inputs (the component is enabled). See also Control the Enabled state of nested components. If the widget is actually disabled, the variable is consequently false.

selected

This variable stores the current state of the Rotary Knob. It is true if the widget is actually focused for keyboard inputs (more precisely, the widget is selected within its owner component). The user can thereupon control the widget via keyboard.

rotating

This variable stores the current state of the Rotary Knob. It is true if the widget should appear pressed. This is usually the case when the user touches the widget actively or presses a key intended to control the widget (the user is rotating the knob).

Understand the state management of the component

During its lifetime, the Rotary Knob will assume different states as direct effect of user interactions and as result of other superior state alternations within the GUI application. To track its current state the Rotary Knob manages the above mentioned state variables: enabled, selected and rotating. The variables are evaluated and updated within the implementation of the method UpdateViewState. This method evaluates the common component states (Enabled and Selected) and verifies whether the user is actually interacting with the component via touch screen or keyboard (the user is rotating the knob). At the end of the method the estimated states are stored in the state variables:

// Estimate the new state var bool isEnabled = aState.contains( Core::ViewState[ Enabled ]); var bool isSelected = aState.contains( Core::ViewState[ Selected ]); var bool isRotating = TouchHandler.Down || KeyPlusHandler.Down || KeyMinusHandler.Down; [...] // Remember the new state enabled = isEnabled; selected = isSelected; rotating = isRotating;

For example, the local variable isRotating will become true if the user is actually touching within the area of the knob (the variable Down of the TouchHandler is true) or the user is pressing a key destined to increment or decrement the widgets's value (the variable Down of the KeyPlusHandler or KeyMinusHandler is true). Being in this state, the widget should assume the appearance indicating that the user is actively rotating it.

When common component states (Enabled or Selected) have changed, the method UpdateViewState is invoked automatically. You don't need to worry about it. Any other state alternations need to explicitly request an invocation of the UpdateViewState method. This is especially true for state alternations caused by touch handlers, keyboard handlers or timer objects. When you modify the Rotary Knob to handle other user inputs or perform other animations always ensure that each state alternation (each event) does request an invocation of UpdateViewState. In your implementation you use for this purpose the method InvalidateViewState.

For example, the slot method onPressTouch is associated to the TouchHandler and it is invoked when the user presses the finger within the widget's knob area. In response to this, the rotating state of the widget has to be updated. The method onPressTouch requests thus the invocation of UpdateViewState:

// Touching the widget may affect its appearance to change. Request the // UpdateViewState() method to be called in order to refresh the widget's // appearance. InvalidateViewState();

Depending on your design expectations the default state management may require adaptations. Generally, you are free to modify the method UpdateViewState, add further state variables to your Rotary Knob or even remove the existing functionality if it is not needed in your case.

For example, the default implementation of the Rotary Knob template manages the state selected. This state indicates that the widget is actually selected within the superior component. The widget could thus receive keyboard events. However, whether the Rotary Knob is really able to receive events does depend on the selection status of its owner component and all other superior components. Only when the widget lies on the focus path, it will receive keyboard events. If your application case requires the Rotary Knob to distinguish this additional state, you do following:

Add new variable to your Rotary Knob component.

Rename the variable to e.g. focused.

Configure the data-type of the variable to bool.

In the UpdateViewState method adapt the implementation as follows:

// Estimate the new state var bool isEnabled = aState.contains( Core::ViewState[ Enabled ]); var bool isSelected = aState.contains( Core::ViewState[ Selected ]); var bool isRotating = TouchHandler.Down || KeyPlusHandler.Down || KeyMinusHandler.Down; var bool isFocused = aState.contains( Core::ViewState[ Focused ]); [...] // Remember the new state enabled = isEnabled; selected = isSelected; rotating = isRotating; focused = isFocused;

Adapt the appearance of the component

Originally, if not yet modified, the Rotary Knob appears composed of a rectangle, an image and a border. The image represents the knob. Our intention is to keep the component templates as minimalistic as possible so they don't distract you with less important design details. It's up to you to adapt the widget to have the expected appearance. The available possibilities are explained in the following sections. Please note, that you can combine the different approaches according to your application case:

1. Modify existing views

The Rotary Knob template contains per default the views Background, Knob and Border. If desired, you can modify the views. For example, you can display them with other colors depending on the actual state of the widget. For this purpose you change the implementation of the above explained method UpdateViewState responsible for tracking the state alternations within the Rotary Knob.

When you open the method UpdateViewState you will see that it does not only update the state variables but also updates the views existing within the component. Accordingly, depending on the actual state of the Rotary Knob the view Knob is displayed with other colors and the view Border with other line thickness. By simply modifying this implementation you change the appearance. For example, you change the knob color from red to blue when the user actively rotates on it:

if ( !isEnabled ) { Background.Color = #AAAAAAFF; Border.Color = #CCCCCCFF; Border.Width = 1; Knob.Color = #CCCCCCFF; } else if ( isRotating ) { Background.Color = #FFFFFFFF; Border.Color = #444444FF; Border.Width = 3; Knob.Color = #0000FFFF; // <-- blue color } else if ( isSelected ) { Background.Color = #FFFFFFFF; Border.Color = #444444FF; Border.Width = 3; Knob.Color = #000000FF; } // Enabled but not rotating nor selected. else { Background.Color = #FFFFFFFF; Border.Color = #444444FF; Border.Width = 1; Knob.Color = #000000FF; }

In case of the Rotary Knob component the implementation of the UpdateViewState method also calculates the rotation of the view Knob from to widget's CurrentValue property and the value range determined by the properties MinValue and MaxValue. Per default, the knob can rotate between 210° and -30° degree about the center of the component:

// The following variables determine the lower and the upper end of the // rotation area and are expressed in degree. var float minAngle = 210.0; var float maxAngle = -30.0; var float newRotationAngle = minAngle; // Convert the widget's current value to a rotation angle within the range // specified by minAngle .. maxAngle. if ( MaxValue != MinValue ) newRotationAngle = ( float( CurrentValue - MinValue ) * ( maxAngle - minAngle )) / float( MaxValue - MinValue ) + minAngle; // Calculate the position where to pin the knob view within the widget area. // Per default, the center of the widget is assumed. var point dstPos = point( Bounds.w / 2, Bounds.h / 2 ); // Let the knob appear at the calculated position with the calculated // rotation angle. Knob.RotateAndScale( dstPos, newRotationAngle - 90.0, 1.0, 1.0 );

If desired, this implementation can be modified and the position of the knob can be calculated in a different manner (for example, with other angles). Since such adaptation affects the implementation of other methods found in the Rotary Knob component, this task is explained separately in the section Understand the calculation of knob's position.

In its original version, the Knob view displays the bitmap Templates::DefaultRotaryKnob. It is a very simple designed ALPHA8 bitmap which can be colorized at the runtime. The easiest way to modify the appearance of the Rotary Knob is thus providing another bitmap. This can be even a bitmap imitating the appearance of a real knob. Following steps explain this task:

By using a graphic editor (e.g. Photoshop Elements, GIMP, Affinity Designer, etc.) design an image of the desired knob. Store it as PNG file.

Add the image file to your project as new bitmap resource.

Open the Rotary Knob component for editing.

Select the view Knob.

Assign the bitmap resource to the view's property Bitmap.

Modify the view's property SourceAnchor. It determines the pivot point within the bitmap around which the bitmap is rotated.

Eventually remove the constant - 90.0 or change it to another value in the following line of the UpdateViewState method. This constant determines an additional rotation to apply on the image, so it appears correctly aligned. Alternative approach would be to take in account the correct rotation already at the design time when you create the image file in the graphic editor:

// Let the knob appear at the calculated position with the calculated // rotation angle. Knob.RotateAndScale( dstPos, newRotationAngle - 90.0, 1.0, 1.0 );

Adapt the calculation of the variable dstPos. This is the position within the Rotary Knob component around which the knob will rotate. Per default, the position is centered:

var point dstPos = point( Bounds.w / 2, Bounds.h / 2 );

2. Remove existing views

If not needed, you can delete the per default existing views Background, Knob and Border. Doing this, please don't forget to also remove all references to the deleted views from the implementation of the UpdateViewState method. Otherwise you will get error messages next time the Composer contents is reloaded. To avoid this error message we recommend to perform the steps in following order:

In the UpdateViewState method adapt the implementation to not refer anymore to the undesired view. For example, if you decided to delete the Border view, simply remove all corresponding code lines. The remaining implementation looks then like this:

if ( !isEnabled ) { Background.Color = #AAAAAAFF; Knob.Color = #CCCCCCFF; } else if ( isRotating ) { Background.Color = #FFFFFFFF; Knob.Color = #FF0000FF; } else if ( isSelected ) { Background.Color = #FFFFFFFF; Knob.Color = #000000FF; } // Enabled but not rotating nor selected. else { Background.Color = #FFFFFFFF; Knob.Color = #000000FF; }

Now you can delete the affected view.

3. Add further views to the component

You can add further views to your Rotary Knob. For example, you can add a Text view to display some caption. Or you add an Image view to display an icon within the widget. The appearance of the views (e.g. colors) can be fixed, predetermined at the design time. Or it can change according to the actual state of the Rotary Knob. For example, if the user is actually rotating the knob, the text size can change or the icon can appear slightly shifted. Following steps explain the workflow:

First add the desired view to the component. For example add a Text view.

Name the just added view according to its function in the component. For example CaptionText.

In order to arrange the view within the Rotary Knob, move the view, or you simply grab one of its corners and resize it in this way. If you want the view to appear behind other views you can reorder it explicitly.

In Inspector window configure the properties of the just added view. For example, in case of the Text view you can select the font to render the text.

In the UpdateViewState method adapt the implementation to update the properties of the view according to the widget's current state. This can be, for example, the color of the Text view depending on the actual enabled state of the widget. For this purpose you could add following code to the method:

// Update the appearance of the CaptionText view. if ( !isEnabled ) CaptionText.Color = #CCCCCCFF; // light gray color // Enabled, evtl. selected or pressed. else CaptionText.Color = #000000FF; // black color

4. Replace existing views

Also possible, you can replace the existing views by other views. For example, in the original version, the Rotary Knob displays the knob as a rotated image. To make the component more sophisticated, you could replace the here used Warp Image view by e.g. Filled Path view and so raster the knob as vector graphic. For this purpose:

Think about which view is adequate as replacement for the old view existing per default in the component. An overview of existing views is found in the section Using Views.

Delete the old view.

Add the desired view to the component.

Name the just added view to have the name of the previously removed view.

Eventually move the view, or you simply grab one of its corners and resize it in this way. If you want the view to appear behind other views you can reorder it explicitly.

If necessary, in Inspector window configure the properties of the just added view.

If necessary, modify the implementation of other methods existing in the component if these evaluate/modify the properties of the replaced view.

Configure the layout of the component

The initial size of the Rotary Knob is determined by the thick blue border surrounding the Canvas area. It corresponds to the size that all instances of this Rotary Knob component will have by default. If desired, you can adjust the Canvas area and change this default size accordingly. This could be for example the case when you plan to create a large Rotary Knob. For this purpose you click and drag the edges of the surrounding border (see also Resize the Canvas area). Once the size is changed, you can then adapt (move, resize) the views existing within the component. For example, to make the Rotary Knob wider you adjust its right edge:

The resulting size of the Rotary Knob, however, is not necessarily fixed. It can be adjusted for each component instance individually when the instance is arranged within a superior GUI component or it can change dynamically at the runtime. The GUI application can thus contain multiple instances of the Rotary Knob, each with another size. From this arises the question, how the Rotary Knob will react to its size alternation?

In case of the views existing per default in the Rotary Knob template (Background and Border), the views are automatically adjusted to fill the area of the widget. All other views you have eventually added later to the Rotary Knob are not adjusted automatically.

To control the adjustment you have to explicitly configure for each view its Layout property. (see also Configure component layout). Let's assume, in order to display some caption text you have added a Text view to the Rotary Knob. Then you have arranged the view within the Canvas area according to your design expectation. If you want now that the view grows and shrinks according to size changes of the Rotary Knob, you enable in the property Layout of this view following settings:

Now the Rotary Knob knows how to react to its size changes. Each widget instance can have individual size and the enclosed views are automatically arranged within the available area:

Implement the interface of the component

When creating your own Rotary Knob component you should ensure that instances of the widget can be configured to control all the features implemented in it. For example, if you have enhanced the component to display some caption text, you should allow this text to be specified individually for each instance. In this way several Rotary Knob instances can exist at the same time, each displaying another caption.

To control the features in your component you use properties. A property can be understood as variable where the corresponding setting is stored, e.g. the caption text to display in the Rotary Knob. When the value of the property changes, the component can react to it and update its appearance accordingly. The properties reflect thus the settings within your widget. Together they form the interface of the component.

Usually, each particular setting within a component is represented by the corresponding property. Thus, a Rotary Knob where color and icon can be configured will require two properties: one for the color and one for the icon bitmap. In its original version, the Rotary Knob contains already six properties CurrentValue, MinValue, MaxValue, Outlet, OnApply and OnChange. It allows the component instance to configure its actual state (the value and the value range) or to exchange data with the component. In order to enhance this interface by your own properties, following steps are necessary:

Add a new property to the Rotary Knob component.

Name the property according to the setting it should represent. For example, the property intended to store the caption text could be named Caption.

Determine the data type of the property. For example, the property intended to store the caption text will store a string.

Determine the initialization value of the property. This value should correspond to the widget's default state. For example, the property intended to store the caption text should be initialized with exact the string the widget will display if no other text is specified (e.g. "Text").

The property is accompanied by its onget method. Except particular cases, this method is not needed and can be deleted now.

The property is accompanied by its onset method. Open this method for editing.

Adapt the implementation of the onset method so it updates the Rotary Knob according to its new value. For example, in case of the property intended to store the caption text, you will probably update some Text view where the caption is displayed:

// The value doesn't change - nothing to do. if ( pure Caption == value ) return; // Remember the property's new value. pure Caption = value; // Update the view to display the just modified caption. CaptionText.String = value;

That is all. Now when you deal with instances of the Rotary Knob component, you can evaluate and modify the properties similarly to how you access variables. Especially, when an instance is selected, you see in Inspector window the property and can change it there. The modification is immediately visible in the Composer window:

If desired, the properties can also be modified at the runtime of your application. The caption, for example, can be adapted to provide useful information for the user concerning the corresponding operation. For this purpose you access and modify the affected property directly within Chora code:

var string speakerName = ... // Let the Rotary Knob display concrete details to the associated value. RotaryKnob.Caption = "Speaker: " + speakerName;

Understand the handling of touch inputs

The Rotary Knob is an interactive component. It can be controlled via touch screen or by the mouse device. For this purpose the template contains the Rotate Touch Handler object named TouchHandler. Per default, this handler occupies the complete area of the component - thus the entire component is touch sensitive. To this handler are associated three slot methods: onPressTouch, onReleaseTouch and onRotateTouch. As their names imply the methods are invoked at the beginning of the interaction (press), at its end (release) and repeatedly during the interaction when the user drags the finger (mouse pointer):

The methods take care of two tasks. As first they request an invocation of the UpdateViewState method if due to the performed touch (mouse) interaction the visual appearance of the widget does need the update. The second task is the calculation from the recent touched position the corresponding Rotary Knob's value and sending signals to slot methods associated to its properties OnChange and OnApply if due to the interaction the value has been changed.

In order to be able to detect value alternations, just in the moment when the user starts the touch (mouse) interaction with the Rotary Knob, the property CurrentValue is stored in the internal variable touchStartValue. The variable provides thus a reference value for further comparisations with CurrentValue. This is implemented in the slot method onPressTouch:

// Touching the widget may affect its appearance to change. Request the // UpdateViewState() method to be called in order to refresh the widget's // appearance. InvalidateViewState(); // Remember which was the widget's value at the beginning of the knob drag // operation. It is used in the slot method 'onRotateTouch' to calculate the // value from the rotation the user made on the screen. touchStartValue = CurrentValue;

Later, when the user finalizes the touch (mouse) interaction, the property CurrentValue is compared with the internal variable touchStartValue. If the value has changed, a signal is sent to the slot method stored in the property OnApply. The owner of the widget can thereupon react to this notification. Consequently, this functionality is implemented in the slot method onReleaseTouch:

// When the touch interaction ends, the appearance of the widget may change. // Request the UpdateViewState() method to be called in order to refresh the // widget's appearance. InvalidateViewState(); // Did the user really changed the widget's value? If yes, notify the owner. if ( CurrentValue != touchStartValue ) postsignal OnApply;

The slot method onRotateTouch is invoked when the user drags the finger while touching within the area of the component. The implementation of the method calculates from the new touch position (from the made rotation gesture) the corresponding CurrentValue and sends signals to the slot method stored in the property OnChange. The owner of the component can thereupon react to this notification:

var int32 minAngle = 210; var int32 maxAngle = -30; var int32 oldValue = CurrentValue; var int32 newValue = oldValue; // Calculate from the rotation the alternation of the widget's value. // Knowing the value at the beginning of the rotation interaction -> // get the new value. newValue = ( TouchHandler.Relative * ( MaxValue - MinValue )) / ( maxAngle - minAngle ) + touchStartValue; // Store the calculated value CurrentValue = newValue; // The rotation was too small to change the widget's value or the knob // has already reached the min/max position. if ( oldValue == CurrentValue ) return; // Notify the owner of the widget that the value has been changed. postsignal OnChange; // If a property is associated to the widget, update it accordingly and // notify other widgets also associated to this property. if ( Outlet != null ) { Outlet^ = CurrentValue; notifyobservers Outlet; }

Usually you will not need to edit the implementation of the methods. It corresponds already to whatever typical Rotary Knobs do. Nevertheless, you are free to change this default functionality if you want some particular behavior to be implemented in your component. For example, in the default implementation, the knob can rotate between the angle 210° and -30°. If desired, this can be modified and the position of the knob can be calculated in a different manner. Since such adaptation affects the implementation of other methods found in the component, this task is explained separately in the section Understand the calculation of knob's position.

Per default the TouchHandler is configured to handle all touch events. If necessary you can change this behavior and restrict the Rotary Knob to react only when the user performs a particular touch interaction. The TouchHandler provides for this purpose diverse configuration properties like NoOfFingers and LimitToFinger. For example, if you want the Rotary Knob to react when the user has touched it with two fingers, set the handler's properties NoOfFingers to the value 2. For more details, see Configure the filter condition.

If your device does not contain any touch screen nor mouse, the per default existing TouchHandler and its associated slot methods are unnecessary ballast. You can remove them in such case. We recommend to do this in following order to avoid eventual error message because of broken references between still existing and the already removed members:

First edit the UpdateViewState method and remove all expressions accessing TouchHandler.

Edit the onPressKey method and remove the expressions accessing TouchHandler.

Edit the onReleaseKey method and remove the expressions accessing TouchHandler.

Using the Inspector window delete the TouchHandler object.

Delete the three slot methods: onPressTouch, onReleaseTouch and onRotateTouch.

Delete the internal variable: touchStartValue.

Finally delete the annotation grouping the slot methods.

Understand the handling of keyboard inputs

The Rotary Knob is an interactive component. It can be controlled via keyboard or hardware buttons. For this purpose the template contains two Key Press Handler objects named KeyPlusHandler and KeyMinusHandler. To the both handlers are associated the slot methods: onPressKey and onReleaseKey. The methods are thus invoked when the user presses or releases a key matching the filter condition specified in one of the handler objects. These are per default the keys Plus and Minus:

The methods take care of two tasks. As first they request an invocation of the UpdateViewState method if due to the performed interaction the visual appearance of the component does need the update. The second task is the increment/decrement of the Rotary Knob's value and sending signals to slot methods associated to its properties OnChange and OnApply if due to the interaction the value has been changed.

In order to be able to detect value alternations, just in the moment when the user starts the keyboard interaction with the widget, the property CurrentValue is stored in the internal variable keyStartValue. The variable provides thus a reference value for further comparisations with CurrentValue. This is implemented in the slot method onPressKey:

// Pressing a key may affect the appearance of the widget to change. Request // the UpdateViewState() method to be called in order to refresh the widget's // appearance. InvalidateViewState(); var int32 oldValue = CurrentValue; // With the first key press event, remember which was the widget's value at // the beginning of the interaction. Later when the user releases the key, // this remembered value is compared with the current value to decide whether // notify or not the owner about an alternation of the value. if ( !((Core::KeyPressHandler)sender).Repetition ) keyStartValue = CurrentValue; // Depending on which key handler has fired, increment or decrement the // widget's current value. if ( sender == KeyPlusHandler ) CurrentValue = oldValue + 1; if ( sender == KeyMinusHandler ) CurrentValue = oldValue - 1; // The knob has already reached the min/max position. if ( oldValue == CurrentValue ) return; // Notify the owner of the widget that the value has been changed. postsignal OnChange; // If a property is associated to the widget, update it accordingly and notify // other widgets also associated to this property. if ( Outlet != null ) { Outlet^ = CurrentValue; notifyobservers Outlet; }

Later, when the user finalizes the keyboard interaction, the property CurrentValue is compared with the internal variable keyStartValue. If the value has changed, a signal is sent to the slot method stored in the property OnApply. The owner of the component can thereupon react to this notification. Consequently, this functionality is implemented in the slot method onReleaseKey:

// Releasing a key may affect the appearance of the widget to change. Request // the UpdateViewState() method to be called in order to refresh the widget's // appearance. InvalidateViewState(); // Did the user really changed the widget's value? If yes, notify the owner. if ( CurrentValue != keyStartValue ) postsignal OnApply;

Usually you will not need to edit the implementation of those methods. It corresponds already to whatever typical Rotary Knobs do. Nevertheless, you are free to change this default functionality if you want some particular behavior to be implemented in your component. For example, you can change the increment/decrement step so the Rotary Knob accelerates when the user continues pressing the key. In such case you could modify the onPressKey method as shown in the code below. Here the increment/decrement depends on the counter RepetitionCount of the respective key handler. The longer the user holds the key pressed, the bigger the counter:

// Depending on which key handler has fired, increment or decrement the // widget's current value. Note, the longer the user presses the key, // the bigger the increment/decrement. if ( sender == KeyPlusHandler ) CurrentValue = oldValue + 1 + ( KeyPlusHandler. RepetitionCount / 10 ); if ( sender == KeyMinusHandler ) CurrentValue = oldValue - 1 - ( KeyMinusHandler.RepetitionCount / 10 );

In some application cases, the handling of keyboard repetitions is not desired and the Rotary Knob is expected to react on the press event only. In such case, you can add following condition to the onPressKey method. In this version, the component increments/decrements its value only in the moment when the user presses the key:

// Pressing a key may affect the appearance of the widget to change. Request // the UpdateViewState() method to be called in order to refresh the widget's // appearance. InvalidateViewState(); // Ignore repetitions. if (((Core::KeyPressHandler)sender).Repetition ) return; [...]

Per default the key handlers are configured to accept the keys Plus and Minus. If necessary you can change this behavior and adapt the Rotary Knob to react to other keys or key groups. The key handler provides for this purpose the configuration property Filter. For example, if you want the component to react when the user presses the Left and Right keys, set the property Filter of the object KeyPlusHandler to the value Core::KeyCode.Right and the of the object KeyMinusHandler to the value Core::KeyCode.Left. For more details, see Configure the filter condition.

If your device does not contain any keyboard nor hardware buttons, the per default existing key handler and their associated slot methods are unnecessary ballast. You can remove them in such case. We recommend to do this in following order to avoid eventual error messages because of broken references between still existing and the already removed members:

First remove all expressions accessing KeyPlusHandler and KeyMinusHandler from the UpdateViewState method.

Similarly, remove all expressions accessing KeyPlusHandler and KeyMinusHandler from the onPressTouch method.

Similarly, remove all expressions accessing KeyPlusHandler and KeyMinusHandler from the onReleaseTouch method.

Then delete the KeyPlusHandler and KeyMinusHandler objects.

Then delete the slot methods: onPressKey and onReleaseKey.

Delete the internal variable: keyStartValue.

Finally delete the annotation grouping the handler and the slot method.

Understand the calculation of knob's position

The position (the rotation) of the knob correlates directly with the value of the property CurrentValue and the range specified by the properties MinValue and MaxValue. If CurrentValue is equal MinValue, the knob is rotated to the left end position. If CurrentValue is equal MaxValue, the knob is rotated to the right end position. All other intermediate values result in the knob rotation angle being interpolated linearly within this range of motion. The code responsible to adjust the knob's position is implemented in the above explained method UpdateViewState:

// The following variables determine the lower and the upper end of the // rotation area and are expressed in degree. var float minAngle = 210.0; var float maxAngle = -30.0; var float newRotationAngle = minAngle; // Convert the widget's current value to a rotation angle within the range // specified by minAngle .. maxAngle. if ( MaxValue != MinValue ) newRotationAngle = ( float( CurrentValue - MinValue ) * ( maxAngle - minAngle )) / float( MaxValue - MinValue ) + minAngle; // Calculate the position where to pin the knob view within the widget area. // Per default, the center of the widget is assumed. var point dstPos = point( Bounds.w / 2, Bounds.h / 2 ); // Let the knob appear at the calculated position with the calculated // rotation angle. Knob.RotateAndScale( dstPos, newRotationAngle - 90.0, 1.0, 1.0 );

In this default implementation, the knob can rotate in the range between 210° and -30°. You can modify this default implementation and, for example, calculate with other angles. In such case, however, you should also adapt the counterpart of this calculation found in the method onRotateTouch.

Each time, the user touches within the component's area and drags the finger thereupon, the method onRotateTouch is invoked. As explained in the section Understand the handling of touch inputs the implementation of this method calculates from the made rotation gesture and the variable touchStartValue a new widget's value. The variable touchStartValue contains a copy of the CurrentValue property just at the beginning of the touch interaction. Consequently, changes resulting from a drag operation are calculated relative to this value:

var int32 minAngle = 210; var int32 maxAngle = -30; var int32 oldValue = CurrentValue; var int32 newValue = oldValue; // Calculate from the rotation the alternation of the widget's value. // Knowing the value at the beginning of the rotation interaction -> // get the new value. newValue = ( TouchHandler.Relative * ( MaxValue - MinValue )) / ( maxAngle - minAngle ) + touchStartValue; // Store the calculated value CurrentValue = newValue;

Let's assume you want to limit the rotation range of the knob to 90° between 135° and 45°. In such case, you would need to make following modifications in the Rotary Knob component:

In the method UpdateViewState change the initialization of the local variable minAngle and maxAngle according to the desired range:

var int32 minAngle = 135; var int32 maxAngle = 45;

Do the same adaptation in the method onRotateTouch.

In the default version, the component reacts to touch events when the user hit elsewhere within the widget's area. This behavior is common for Rotary Knobs. In some cases, however, you might expect to restrict the touch sensitive area to some visual aspect of the Rotary Knob, for example to the black dot displayed in the default version of this component. To interact with the widget the user has to touch exactly within this sensitive area otherwise the component will ignore the touch interaction. If you require this behavior, following are the necessary adaptations:

Select the object TouchHandler.

Change the handler's property Enabled to the value false. The handler should not react to any touch events by default.

Add a new Simple Touch Handler to the Rotary Knob component.

Move and resize the Simple Touch Handler to correspond to the desired touch sensitive area. For example, to the of the black dot in the default version of the component:

Add a new slot method to the component.

Assign the slot method to the SimpleTouchHandler's property OnPress.

Edit the slot method and implement following code:

// Redirect the touch interaction to the original TouchHandler. rootthis.DeflectCursor( TouchHandler, <0,0>);

With the above modifications the Rotary Knob reacts when the user hit within the area of the Simple Touch Handler. The handler thereupon deflects (relays) the interaction to the original TouchHandler which thereupon continues handling the interaction. Since we have disabled TouchHandler, touching directly within its area will not have any effect. The remaining step in this application case is the arrangement of the Simple Touch Handler to overlays the touch sensitive area within the component. In other words, the Simple Touch Handler has to follow the rotation of the Knob.

In the method UpdateViewState add following code:

// Calculate the position for the center of the touch sensitive area. Here we assume // the area will lie 50 pixel away from the center of the Knob. var int32 radius = 50; var point pos = point( int32( radius * math_sin( newRotationAngle + 90 )), int32( radius * math_cos( newRotationAngle + 90 ))); // Move the Simple Touch Handler to the just calculated position. SimpleTouchHandler.MoveView( dstPos + pos - SimpleTouchHandler.GetExtent().center, false );

Understand the Outlet updates

The Rotary Knob can be connected via its property Outlet with any other int32 property the widget should remain synchronized with. When the user rotates the knob, the affected property is automatically updated to reflect the widget's current value. On the other hand, when the referred property is modified by another one, the component is automatically notified to remain in sync with the property. This approach follows the model-view-controller (MVC) programming paradigm, where the Rotary Knob has the function of the view and controller and the property referred via Outlet serves as model. See also Outlet properties.

When the state of the Rotary Knob changes due to a user interaction, the component updates the value of the referred property. This is, for example, the case in the slot method onRotateTouch. The implementation found there simply assigns the actual value of the Rotary Knob's CurrentValue property to the property referred via Outlet and broadcasts a notification to all other widgets connected to the same property so they also can update their state accordingly:

// If a property is associated to the widget, update it accordingly and notify // other widgets also associated to this property. if ( Outlet != null ) { Outlet^ = CurrentValue; notifyobservers Outlet; }

In order to react to alternations of the referred property (to receive notifications broadcasted by other widgets connected with the same property), the Rotary Knob implements the method onOutlet. Each time the referred property is notified, this slot method is automatically invoked and following code is executed:

// Read the current value of the associated property and update accordingly // the state of the widget. if ( Outlet != null ) CurrentValue = Outlet^;

In order to be invoked, the method onOutlet needs to be registered with the referred property as so-called observer. The corresponding code is implemented in the onset method OnSetOutlet. Thus, just in the moment when a property reference is assigned to the Rotary Knob's Outlet property, following code is executed:

// Check if the new outlet differs from the currently used outlet if ( pure Outlet == value ) return; // Detach from the previous outlet if ( pure Outlet != null ) detachobserver onOutlet, pure Outlet; // Store the new outlet ... pure Outlet = value; // ... and attach to the new one if ( value != null ) attachobserver onOutlet, value; // Finally, retrieve the current state of the property associated via 'Outlet'. if ( value != null ) postsignal onOutlet;

Perform state changes with animations

In the section Adapt the appearance of the component you learned how state alternations within the Rotary Knob are processed and how views existing in the component are updated in order to reflect the actual state. Accordingly, when the user touches the widget (starts the rotation gesture), the widget does appear pressed. When the user releases the widget again (ends the touch interaction), the widget restores its released appearance. The default implementation performs such appearance updates instantly, just in the moment when the respective interaction took place.

If desired, you can modify the Rotary Knob to update its appearance with animations. For example, instead of instantly switching between the pressed and released appearance, such Rotary Knob can fade-in/out the views, modulate their colors or move the views within the component. For this purpose you use the Animation Effects. With an Animation Effect you can animate a property of a view existing in the component. Following are the steps how to do this:

Depending on the data type of the property to animate, add the appropriate Animation Effect to the Rotary Knob. For example, to animate a color property, add a Pulse color effect.

Connect the effect object with the property to animate. For example, if you want to animate the color of the Knob view, connect the Pulse color effect with the property Color of the Knob view.

Configure the duration and eventually the desired timing (easing) for the animation.

Once started, the effect will animate the property endlessly. In our case, however, the animation should stop at its end. For this purpose set the effect's property NoOfCycles to the value 1.

Configure the key values for the animation. In case of the pulse color effect, it is the start and the end color. For example, to animate the color between black and red, configure the effect's property Value1 with the #000000FF (black) and the property Value2 with #FF0000FF (red). This step is appropriate if the key values are fixed, known at the design time. Otherwise, the key values must be set at runtime shortly before the effect is triggered.

Open the method UpdateViewState and modify its implementation to trigger the effect object when the component switches between the states rotating and not rotating. For example in case of the above mentioned pulse color effect to animate the color of the Knob, following implementation could be adequate:

// Estimate the new state var bool isEnabled = aState.contains( Core::ViewState[ Enabled ]); var bool isSelected = aState.contains( Core::ViewState[ Selected ]); var bool isRotating = TouchHandler.Down || KeyPlusHandler.Down || KeyMinusHandler.Down; if ( !isEnabled ) { Background.Color = #AAAAAAFF; Border.Color = #CCCCCCFF; Border.Width = 1; Knob.Color = #CCCCCCFF; } else if ( isRotating ) { Background.Color = #FFFFFFFF; Border.Color = #444444FF; Border.Width = 3; } else if ( isSelected ) { Background.Color = #FFFFFFFF; Border.Color = #444444FF; Border.Width = 3; } // Enabled but not rotating nor selected. else { Background.Color = #FFFFFFFF; Border.Color = #444444FF; Border.Width = 1; } // Switching from not rotating -> rotating state. Start the animation. if ( isEnabled && isRotating && !rotating ) { ColorEffect.Reversed = false; ColorEffect.Enabled = true; } // Switching from rotating -> not rotating state. Start the animation. else if ( isEnabled && !isRotating && rotating ) { ColorEffect.Reversed = true; ColorEffect.Enabled = true; } [...] // Remember the new state enabled = isEnabled; selected = isSelected; rotating = isRotating;

Since the properties of the affected views are now modified with animations, you will eventually need to adapt their initial values to correspond to the default state of the widget. For example, you might need to set the property Color of the view Knob to the value #000000FF (default color is black).