Howto:Canvas dialog examples

From FlightGear wiki
Jump to navigation Jump to search

This page is for Canvas dialog Examples and their explanations. At the moment it has 1:

Extra 500 - failure dialog - Fuel

Extra500 failure dialog

Introduction

This dialog is the 'front end' of the extra500 failure system. It uses Canvas only and has several pages and interactive elements. All pages are .svg designed. We will look at the code and try to explain what is does.

  • Setting up the namespace "Dialogs"
  • Making a menu entry for the dialog
  • Setting up a class and making one instance of it
  • Setting up the Canvas
  • What to do when opening the dialog like:
  1. Making the window (frame)
  2. Creating a canvas group related to that window frame
  3. Parsing (and displaying) .svg files
  4. Linking elements of the .svg file to the canvas group (giving them a name so we can change them)
  5. Initiate the listeners to make certain elements interactive
  • Making the listeners and linking them to canvas/.svg elements (howto make clicking on it make it do something)

Namespace and Menu entry

The dialog is set-up like a normal nasal namespace in the -set file:

<nasal>
    <Dialogs>
      <file>Aircraft/extra500/Dialogs/failuredialog.nas</file>
    </Dialogs>
</nasal>

And called (opened) though a menu entry in /Dialogs/extra500-menu.xml just like any other classical menu entry.

<item>
   <name>Failure-dialog</name>
   <label>Failure Dialog</label>
   <binding>
      <command>nasal</command>
      <script><![CDATA[
         Dialogs.Failuredialog.openDialog();
      ]]></script>
   </binding>
</item>

This means there needs to be a loaded Class called "Failuredialog" in the namespace "Dialogs" with an internal function called "openDialog".

File structure

The file is located in the /extra500/Dialog folder and is called failuredialog.nas After the license part, it starts up with defining a vector that basically holds all the colors of the .svg elements that are changed. The color definition, for instance "#00ff004a" is used in the .svg files directly and can be looked up in Inkscape or any other .svg file editor. The rest holds the definition of "FailureClass{}" and in the end of the file, one instance of this Class is invoked:

var FailureClass = {
...
...
};
var Failuredialog = FailureClass.new();

Thus now we created Dialogs.Failuredialog and the internal functions as defined in the FailureClass Class. We are calling the internal function "new" at initialization. All the internal functions can be called using Dialog.Failuredialog.<InternalFunctionName>

Other files used are the .svg files, which are located in the same folder. We will be looking at the MenuFaildialog.svg and GearFaildialog.svg files in this example.

FailureClass.new

With the last line of code above, we invoke the following:

new : func(){
  var m = {parents:[FailureClass]};
  m._title = 'Extra500 Failure Dialog';
  m._gfd 	= nil;
  m._canvas	= nil;
  m._timer 	= maketimer(1.0,m,FailureClass._timerf );
  return m;
 },

So we make the Class, set a title, make a canvas and even set a timer. In principle nothing happens except in the background the Class is set up.

openDialog

The next function hold all the things we want to do if we open the window. We are making the (empty) window:

me._gfd = canvas.Window.new([750,512],"dialog");
me._gfd._onClose = func(){Failuredialog._onClose();}
me._gfd.set('title',me._title);
me._canvas = me._gfd.createCanvas().set("background", canvas.style.getColor("bg_color"));
me._root = me._canvas.createGroup();

After this we pre-load all the .svg files. This is not necessary, but a design choice. It makes the switching between window tabs faster (as you don't need to load the .svg every time). Another design choice is that each page in the dialog has its own .svg file. This is not necessary, but we found it a bit more structured.

me._filename = "/Dialogs/MenuFaildialog.svg";
me._svg_menu = me._root.createChild('group');
canvas.parsesvg(me._svg_menu, me._filename);

The first line point to where the .svg is located, the second sets up a group and gives it a name. And finally the .svg is parsed (loaded) and linked to the group name. In this example we will only look at the "menu" and the "gear" tab. Their names are: me._svg_menu and me._svg_gear respectively.

In the next section all the elements from the individual .svg files are linked, declared and named. This is only necessary for elements which actually do something. Like you want to make it interactive (click on it) or change its color or hide it etc. etc.

So for the menu we have the gear tab which has 3 elements:

  • A colored field (of which we will change the color): me._gear
  • A text field (which just sits there and does nothing, so we won't declare it)
  • A field that indicates if there is an active failure on the gear: me._gear_active
me._gear = me._svg_menu.getElementById("field_gear");
me._gear_active = me._svg_menu.getElementById("gear_active").hide();

So the elements in the .svg file are declared and linked to their names. "field_gear" and "gear_active" are the element ID-s as set in Inkscape (Object - Object Properties). Note that the gear_active field is set invisible at startup by using .hide() at the end.

For the gear page, we do the same:

me._LHgear = me._svg_gear.getElementById("LHgear");
etc.

Here the .svg element "LHgear" of the parsed .svg file called me._svg_gear is linked to the name me._LHgear. This enables us to do all kind of things to it like hiding, showing, change color etc. This is both true for complete parsed .svg files as their elements

For instance, a bit further down in the file:

 me._svg_menu.show();

Which shows the menu page. So we can show or hide whole pages (individual parsed .svg-s) with .show() and .hide() respectively.

We also initiate all listeners here:

me.setListeners(instance = me);

Which happens in the setListeners function.

Listeners: things are happening

Now we can add (event-)listeners to the different elements in the setListeners function:

 me._gear.addEventListener("click",func(){me._onGearClick();});

Here we tell to execute the function me._onGearClick() when the element me._gear is clicked. We can do this for all declared elements:

me._LHgear.addEventListener("click",func(){me._onGeneralClick("/systems/gear/LMG-free",0,"LMG_jammed","gear");});

This one looks a bit more complicated, but the principle is the same. The function me._onGeneralClick just gets some arguments passed to it. You can replace "click" with "wheel" or "drag" in your listener for mouse-wheel or mouse-dragging operation. For "wheel" and "drag" you will need to pass on the amount you want to change like so:

me._fielddelay.addEventListener("wheel",func(e){me._onRandomDelayChange(e);});

and the function that is called:

_onRandomDelayChange : func(e){
 var delay = getprop("/extra500/failurescenarios/randommaxdelay") + e.deltaY;
 delay = math.clamp(delay,0,60);
 setprop("/extra500/failurescenarios/randommaxdelay",delay);
 me._welcome_update();
},

Operations

Let us look at some operations. For instance in the _onGearClick() function. One of the lines is:

me._gear.setColorFill(COLORfd["menuse"]);

Here we set the color of element me._gear to COLORfd["menuse"]. This is a defined in the vector at the beginning of the file and is color "#0055d432".

Another line is:

me._svg_gear.show();

Which makes the gear page visible. The opposite is .hide()

Dialog design

The structure of the dialog is pretty complicated, but is all just logical nasal stuff and has nothing to do with the canvas - nasal interaction. In principle, all pages are parsed separately so they can be turned on and off easily. In general, when a tab is pressed, all pages are hidden (except for the menu) and the proper page shown.

 
_onGearClick : func() {
  me._menuReset();
  me._gear.setColorFill(COLORfd["menuse"]);
  me._frame.setColorFill(COLORfd["menuse"]);
  me._hideAll();
  me._svg_lmenu.show();
  me._gear_active.show();
  me._svg_gear.show();
  me._gearButtons_update();
 },

This happens when the GEAR tab in the menu is pressed. The menu is re-setted and then the gear tab is changed color to indicate it is selected. All the other pages are hidden, the gear page is show and finally all gear-buttons are updated. The status of the buttons depends on fg-properties. They have no listeners, but are only updated when the page is show. This is a bit easier and saves a lot of listeners. The disadvantage is that the buttons (they change color when a pressed and a failure is initiated) are not updated automatically. Maybe not ideal for a 'front end'...