Howto:Creating a Canvas GUI dialog file: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
m (→‎Creating the actual dialog file: magic numbers are evil ... get rid of them)
 
(21 intermediate revisions by 2 users not shown)
Line 8: Line 8:
First of all, we're going to add a  new menu item to open our new dialog. This involves adding a new translation entry, as well as the actual menubar entry itself.
First of all, we're going to add a  new menu item to open our new dialog. This involves adding a new translation entry, as well as the actual menubar entry itself.
== Editing Translations/en/menu.xml ==
== Editing Translations/en/menu.xml ==
[[File:menubar2.jpg|517px]]
Navigate to the sub-menu where you'd like to add your new dialog, e.g. "Equipment" in this case, and then add a key:
Navigate to the sub-menu where you'd like to add your new dialog, e.g. "Equipment" in this case, and then add a key:
<syntaxhighlight lang="xml">
<syntaxhighlight lang="xml">
Line 15: Line 17:


== Editing gui/menubar.xml ==
== Editing gui/menubar.xml ==
Next, open $FG_ROOT/gui/menubar.xml and navigate to the corresponding sub-menu and add a new item/binding to load your dialog:
Next, open {{Fgdata file|gui/menubar.xml}} and navigate to the corresponding sub-menu and add a new item/binding to load your dialog:
<syntaxhighlight lang="xml">
<syntaxhighlight lang="xml">
<item>
<item>
Line 29: Line 31:


== Creating the actual dialog file ==
== Creating the actual dialog file ==
{{Note|These dialog files can be edited at run-time and simply closed/re-opened, because the Canvas system will reload them from disk whenever the menu binding is triggered. So this is very convenient for quickly developing and testing your dialogs. For testing purposes, you can also paste the snippet into your [[Nasal Console]] and directly execute it.}}
<!--
Next, open/create $FG_ROOT/Nasal/canvas/gui/dialogs/CanvasDemo.nas and add some boilerplate code:
{{Note|These dialog files can be edited at run-time and simply closed/re-opened, because the Canvas system will reload them from disk whenever the menu binding is triggered. So this is very convenient for quickly developing and testing your dialogs. For testing purposes, you can also paste the snippet into your [[Nasal Console]] and directly execute it.}}-->
Next, navigate to {{Fgdata file|Nasal/canvas/gui/dialogs}} and open/create $FG_ROOT/Nasal/canvas/gui/dialogs/'''CanvasDemo'''.nas to add some boilerplate code (this should match the basename of the dialog loaded in menubar.xml):
<syntaxhighlight lang="nasal">
<syntaxhighlight lang="nasal">
var DemoDialog = {
var DemoDialog = {
Line 67: Line 70:


Finally, start FlightGear and open your new dialog
Finally, start FlightGear and open your new dialog
For a more sophisticated example, refer to [[Howto:Reset/re-init Troubleshooting]].


== Adding Widgets ==
== Adding Widgets ==
In order to keep your dialog sufficiently generic, you'll want to refrain from adding too much custom functionality to it, and instead decompose your functionality into a collection of '''widgets'''. Widgets are GUI controls like a label, button, checkbox etc - i.e. these widgets have a certain appearance and may respond to events such  as being clicked for example. Whenever you create custom widgets instead of adding all the code to a single dialog, you'll end up with reusable components that can be easily other in other dialogs. In addition, you are making sure to establish a losely-coupled design, so that your widgets are sufficient generic and do not contain any use-case specific logic.
{{Main article|Howto:Creating a Canvas GUI Widget}}
In order to keep your dialog sufficiently generic, you'll want to refrain from adding too much custom functionality to it, and instead decompose your functionality into a collection of '''widgets'''. Widgets are GUI controls like a label, button, checkbox etc that support styling - i.e. these widgets have a certain appearance and may respond to events such  as being clicked for example. Whenever you create custom widgets instead of adding all the code to a single dialog, you'll end up with reusable components that can be easily other in other dialogs. In addition, you are making sure to establish a losely-coupled design, so that your widgets are sufficient generic and do not contain any use-case specific logic (think widgets being too specific to the dialog they were originally implemented for).


This means for example that a generic '''PropertyBrowser''' could be easily reused in other dialogs/windows requiring a corresponding property browser. But this also meanns that such a property browser widget should also be modularized, i.e. by using a lower-level '''ListView''' or '''TreeView''' widget and merely parameterizing that by showing properties. This approach has the added advantage that the corresponding '''ListView/TreeView''' widgets could be re-used in other places, such as for example an aircraft list, a [[Nasal Browser|Nasal Namespace Browser]].
This means for example that a generic '''PropertyBrowser''' widget could be easily reused in other dialogs/windows requiring a corresponding property browser. But this also meanns that such a property browser widget should also be modularized, i.e. by using a lower-level '''ListView''' or '''TreeView''' widget and merely parameterizing that by showing properties. This approach has the added advantage that the corresponding '''ListView/TreeView''' widgets could be re-used in other places, such as for example an aircraft list, a [[Nasal Browser|Nasal Namespace Browser]].


Likewise, a widget-focused approach means that useful functionality like a [[Interactive Nasal REPL]] can be easily used in other places, without developers having to do any refactoring.
Likewise, a widget-focused approach means that useful functionality like an [[Interactive Nasal REPL]] can be easily used in other places, without developers having to do any refactoring.


We're also trying to make sure that Canvas Widgets can be easily used not just by GUI dialogs but also by Canvas-based instruments/MFDs - without any code duplication or Copy&Paste being required.
We're also trying to make sure that Canvas Widgets can be easily used not just by GUI dialogs but also by Canvas-based instruments/MFDs - without any code duplication or Copy&Paste being required.
== Creating MFD Dialogs ==
Sometimes you may have the need to present a GUI dialog for dealing with a MFD, potentially even showing the MFD screen itself in the GUI dialog. With Canvas, this can be accomplished by creating a dialog procedurally and adding a placement for the MFD (Canvas) that is to be shown, or even by showing an indepenent instance for your MFD ([[FGCanvas]]).
<gallery>
Dps-keypad-dialog-using-canvas.png|DPS keypad for the space shuttle using a procedurally-generated Canvas dialog
Full-dps-space-shuttle-keypad.png|Screen shot showing the DPS keypad on the space shuttle using Nasal/Canvas
Canvas-MFD-Dialog-with-embedded-Canvas.png|Screen shot with a Canvas MFD dialog including an embedded Canvas (showing a splash screen here, which could just as well reference another Canvas/MFD)
</gallery>
<syntaxhighlight lang="nasal">##
# helper function for registering events  (callbacks) with buttons (wrapped in a closure)
var createButton = func(root, label, clickAction) {
var button = canvas.gui.widgets.Button.new(root, canvas.style, {} )
        .setText(label);
button.listen("clicked", clickAction );
return button;
}
var (width,height) = (800,250);
var title = 'DPS Keypad';
# create a new window, dimensions are WIDTH x HEIGHT, using the dialog decoration (i.e. titlebar)
var window = canvas.Window.new([width,height],"dialog").set('title',title);
# adding a canvas to the new window and setting up background colors/transparency
var myCanvas = window.createCanvas().set("background", canvas.style.getColor("bg_color"));
# creating the top-level/root group which will contain all other elements/group
var root = myCanvas.createGroup();
# create a new layout for the keypad:
var myHBox = canvas.HBoxLayout.new();
# assign the layout to the Canvas
myCanvas.setLayout(myHBox);
var keypad = canvas.HBoxLayout.new();
myHBox.addItem(keypad);
var mfd = canvas.gui.widgets.Label.new(root, canvas.style, {} )
# this could also be another Canvas: http://wiki.flightgear.org/Howto:Using_raster_images_and_nested_canvases#Example:_Loading_a_Canvas_dynamically
.setImage("Textures/Splash1.png");
myHBox.addItem(mfd);
# list of all buttons we want to set up
var Buttons=[
[
'FAULT',
'GPC',
'I/O RST',
'ITEM',
'EXEC',
'OPS',
'SPEC',
'RSM',
],
[
'SYS',
'A',
'D',
'1',
'4',
'7',
'-',
'CLEAR',
],
[
'MSG',
'B',
'E',
'2',
'5',
'8',
'0',
'.',
],
[
'ACK',
'C',
'F',
'3',
'6',
'9',
'+',
'PRO',
],
];
## button setup using 4 vertical boxes added to a single hbox
foreach(var col; Buttons) {
# set up a new vertical box
var vbox = canvas.VBoxLayout.new();
# add it to the top-level hbox
keypad.addItem(vbox);
foreach(var btn; col) {
(func() {
var action=btn;
var vbox=vbox;
var button=createButton(root:root, label: btn, clickAction:func {print("button clicked:",action);});
# add the button to the vbox
vbox.addItem(button);
})(); # invoke anonymous function (closure)
}
}
</syntaxhighlight>


== Using Layouts ==
== Using Layouts ==
Line 82: Line 202:
== Using Styling ==
== Using Styling ==
{{Note|This section still needs to be written (volunteers invivted to help!)-In the meantime, please refer to $FG_ROOT/Nasal/canvas/gui/styles/DefaultStyle.nas}}
{{Note|This section still needs to be written (volunteers invivted to help!)-In the meantime, please refer to $FG_ROOT/Nasal/canvas/gui/styles/DefaultStyle.nas}}
[[Category:Canvas]]

Latest revision as of 10:39, 3 October 2017

This article is a stub. You can help the wiki by expanding it.
Screen shot showing a simple Canvas GUI dialog with a handful of Button Widgets arranged with a vertical/vbox layout.

Targeted FlightGear versions: >= 3.20

First of all, we're going to add a new menu item to open our new dialog. This involves adding a new translation entry, as well as the actual menubar entry itself.

Editing Translations/en/menu.xml

Menubar2.jpg

Navigate to the sub-menu where you'd like to add your new dialog, e.g. "Equipment" in this case, and then add a key:

<equipment>Equipment</equipment>
<canvas-demo-dialog>Canvas Demo Dialog</canvas-demo-dialog>

Editing gui/menubar.xml

Next, open fgdata/gui/menubar.xml and navigate to the corresponding sub-menu and add a new item/binding to load your dialog:

<item>
<name>canvas-demo-dialog</name>
<binding>
<command>nasal</command>
<script>
canvas.loadDialog("CanvasDemo");
</script>
</binding>
</item>

Creating the actual dialog file

Next, navigate to fgdata/Nasal/canvas/gui/dialogs and open/create $FG_ROOT/Nasal/canvas/gui/dialogs/CanvasDemo.nas to add some boilerplate code (this should match the basename of the dialog loaded in menubar.xml):

var DemoDialog = {
  new: func(width=140,height=160)
  {
    var m = {
      parents: [DemoDialog],
      _dlg: canvas.Window.new([width, height], "dialog")
                         .set("title", "VBoxLayout")
                         .set("resize", 1),
    };

    m._dlg.getCanvas(1)
          .set("background", canvas.style.getColor("bg_color"));
    m._root = m._dlg.getCanvas().createGroup();
 
    var vbox = canvas.VBoxLayout.new();
    m._dlg.setLayout(vbox);

    # this is where you can add widgets from $FG_ROOT/Nasal/canvas/gui/widgets, e.g.:
    for(var i = 0; i < 5; i += 1 )
      vbox.addItem(
        canvas.gui.widgets.Button.new(m._root, canvas.style, {})
                                 .setText("Button #" ~ i)
      );

    var hint = vbox.sizeHint();
    hint[0] = math.max(width, hint[0]);
    m._dlg.setSize(hint);

    return m;
  },
};

var demo = DemoDialog.new();

Finally, start FlightGear and open your new dialog

For a more sophisticated example, refer to Howto:Reset/re-init Troubleshooting.

Adding Widgets

1rightarrow.png See Howto:Creating a Canvas GUI Widget for the main article about this subject.

In order to keep your dialog sufficiently generic, you'll want to refrain from adding too much custom functionality to it, and instead decompose your functionality into a collection of widgets. Widgets are GUI controls like a label, button, checkbox etc that support styling - i.e. these widgets have a certain appearance and may respond to events such as being clicked for example. Whenever you create custom widgets instead of adding all the code to a single dialog, you'll end up with reusable components that can be easily other in other dialogs. In addition, you are making sure to establish a losely-coupled design, so that your widgets are sufficient generic and do not contain any use-case specific logic (think widgets being too specific to the dialog they were originally implemented for).

This means for example that a generic PropertyBrowser widget could be easily reused in other dialogs/windows requiring a corresponding property browser. But this also meanns that such a property browser widget should also be modularized, i.e. by using a lower-level ListView or TreeView widget and merely parameterizing that by showing properties. This approach has the added advantage that the corresponding ListView/TreeView widgets could be re-used in other places, such as for example an aircraft list, a Nasal Namespace Browser.

Likewise, a widget-focused approach means that useful functionality like an Interactive Nasal REPL can be easily used in other places, without developers having to do any refactoring.

We're also trying to make sure that Canvas Widgets can be easily used not just by GUI dialogs but also by Canvas-based instruments/MFDs - without any code duplication or Copy&Paste being required.

Creating MFD Dialogs

Sometimes you may have the need to present a GUI dialog for dealing with a MFD, potentially even showing the MFD screen itself in the GUI dialog. With Canvas, this can be accomplished by creating a dialog procedurally and adding a placement for the MFD (Canvas) that is to be shown, or even by showing an indepenent instance for your MFD (FGCanvas).

##
# helper function for registering events  (callbacks) with buttons (wrapped in a closure)
 
var createButton = func(root, label, clickAction) {
var button = canvas.gui.widgets.Button.new(root, canvas.style, {} )
        .setText(label);
button.listen("clicked", clickAction );
return button;
}
 
var (width,height) = (800,250);
var title = 'DPS Keypad';
 
# create a new window, dimensions are WIDTH x HEIGHT, using the dialog decoration (i.e. titlebar)
var window = canvas.Window.new([width,height],"dialog").set('title',title);
 
# adding a canvas to the new window and setting up background colors/transparency
var myCanvas = window.createCanvas().set("background", canvas.style.getColor("bg_color"));
 
# creating the top-level/root group which will contain all other elements/group
var root = myCanvas.createGroup();
 
# create a new layout for the keypad:
var myHBox = canvas.HBoxLayout.new();
# assign the layout to the Canvas
myCanvas.setLayout(myHBox);
 
var keypad = canvas.HBoxLayout.new();
myHBox.addItem(keypad);

var mfd = canvas.gui.widgets.Label.new(root, canvas.style, {} )
	# this could also be another Canvas: http://wiki.flightgear.org/Howto:Using_raster_images_and_nested_canvases#Example:_Loading_a_Canvas_dynamically
	.setImage("Textures/Splash1.png");
myHBox.addItem(mfd);

# list of all buttons we want to set up
 
var Buttons=[
[
'FAULT',
'GPC',
'I/O RST',
'ITEM',
'EXEC',
'OPS',
'SPEC',
'RSM',
],
 
[
'SYS',
'A',
'D',
'1',
'4',
'7',
'-',
'CLEAR',
],
 
[
'MSG',
'B',
'E',
'2',
'5',
'8',
'0',
'.',
],
 
[
'ACK',
'C',
'F',
'3',
'6',
'9',
'+',
'PRO',
],
];
 
## button setup using 4 vertical boxes added to a single hbox
 
foreach(var col; Buttons) {
	# set up a new vertical box
	var vbox = canvas.VBoxLayout.new();
	# add it to the top-level hbox
	keypad.addItem(vbox);
 
	foreach(var btn; col) {
 
		(func() {
		var action=btn;
		var vbox=vbox;	
		var button=createButton(root:root, label: btn, clickAction:func {print("button clicked:",action);});
		# add the button to the vbox
		vbox.addItem(button);		
	})(); # invoke anonymous function (closure)
 
	}
}

Using Layouts

Note  This section still needs to be written (volunteers invivted to help!)-In the meantime, please refer to $FG_ROOT/Nasal/canvas/gui/dialogs/AircraftCenter.nas

Using Styling

Note  This section still needs to be written (volunteers invivted to help!)-In the meantime, please refer to $FG_ROOT/Nasal/canvas/gui/styles/DefaultStyle.nas