Howto:Creating a Canvas GUI Widget: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
m (Reverted edits by Hooray (talk) to last revision by Red Leader)
(Sorry, accidental)
Line 12: Line 12:
   }}</ref>
   }}</ref>


For the time being, this approach has a number of major advantages, i.e. it has proven to be able to deal with existing resources, without the original legacy UI dialogs having to be touched/modified, so that the primary thing missing to parse/process these dialogs are a handful of missing Canvas widgets.  
For the time being, this approach has a number of major advantages, i.e. it has proven to be able to deal with existing UI resources (dialogs, and [[Menubar]]), without the original legacy UI dialogs (or any C++ code) having to be touched/modified, so that the primary thing missing to parse/process these dialogs are a handful of missing Canvas widgets, which can also be implemented in scripting space usually.
 
For the time being, this approach also is the only effort that can easily support procedurally created and updated dialogs like [[Aircraft Checklists]] and [[Tutorials]].
Also, the '''pui2canvas''' approach can help address a number of long-standing PUI related issues, such as rendering arttfacts  on some ATI/AMD GPUs, but also PUI/Canvas inter-operability issues related to [[Canvas Event Handling]].


As the integrated, and fully script-able, [[Canvas GUI]] develops, new {{wikipedia|Widget (GUI)|widgets}} need to be added <ref>{{cite web
As the integrated, and fully script-able, [[Canvas GUI]] develops, new {{wikipedia|Widget (GUI)|widgets}} need to be added <ref>{{cite web
Line 33: Line 36:
For a complete list (including custom/undocumented widgets), refer to <code>FGPUIDialog::makeObject()</code> in {{flightgear file|src/GUI/FGPUIDialog.cxx|l=850}}
For a complete list (including custom/undocumented widgets), refer to <code>FGPUIDialog::makeObject()</code> in {{flightgear file|src/GUI/FGPUIDialog.cxx|l=850}}


In addition, FlightGear introduces a number of custom PUI widgets implemented in C++ space, that not even PUI itself supports directly.
In addition, FlightGear introduces a number of custom PUI widgets implemented in C++ space, that not even PUI itself supports directly:
However, many PUI widgets can be emulated/approximated by using a combination of existing widgets, and/or functionality found in existing wigets.
* {{PUI widget|airport-list}}
* {{PUI widget|waypoint-list}} (only used by the [[Route Manager]] i.e. {{Dialog file|dialog=route-manager}} 
* {{PUI widget|property-list}} (only used by the [[Property browser]] i.e. {{Dialog file|dialog=property-browser}}
 
However, many PUI widgets can be emulated/approximated by using a combination of existing widgets, and/or functionality found in existing widgets, i.e. by referring to their source code.


As of late June 2016, the following widgets are primarily needed to help getting rid of PUI (listed in ascending complexity):
As of late June 2016, the following widgets are primarily needed to help getting rid of PUI (listed in ascending complexity):
* {{PUI widget|vrule}} (not critical, could  be implemented OpenVG or simply by using a transparent image)
* {{PUI widget|vrule}} (not critical, could  be implemented OpenVG or simply by using a transparent image)
* {{PUI widget|hrule}} (not critical, could  be implemented OpenVG or simply by using a transparent image)
* {{PUI widget|hrule}} (not critical, could  be implemented OpenVG or simply by using a transparent image)
* {{PUI widget|radio}} (Canvas images using event handling)
* {{PUI widget|radio}} (Canvas images using event handling to dynamically change the image based on mouse events)
* {{PUI widget|slider}} (basically 3 buttons with the middle button supporting dragging, see also [https://forum.flightgear.org/viewtopic.php?f=71&t=19975])
* {{PUI widget|slider}} (basically 3 buttons with the middle button supporting dragging, see also [https://forum.flightgear.org/viewtopic.php?f=71&t=19975])
* {{PUI widget|dial}} (see also [https://forum.flightgear.org/viewtopic.php?f=71&t=19975])
* {{PUI widget|dial}} (see also [https://forum.flightgear.org/viewtopic.php?f=71&t=19975])
Line 46: Line 53:
The key ones really being the last 4 in that list (radio, slider, dial and combo/dropdown)
The key ones really being the last 4 in that list (radio, slider, dial and combo/dropdown)


Most other widgets currently missing can be implemented by using a combination of these widgets, e.g. a ScrollArea with buttons can be used to implement any type of list (airportlist, waypointlist, property-browser etc)
Most other widgets currently missing can be implemented by using a combination of these widgets, e.g. a ScrollArea with buttons can be used to implement any type of list (airportlist, waypointlist, property-browser etc), just by adding event handling callbacks to each entry/button that invoke the corresponding bindings:


However, once these basic widgets are supported, additional FlightGear/flight simulation specific widgets may be implemented, e.g. for picking a frequency, heading/course etc (see [[UI_Unification#Widget_tags]]).
<gallery>
Pui-airport-list-widget-in-canvas.png|airport-list widget re-implemented using Canvas ScrollArea and Buttons arranged in a VBox layout
Crude-canvas-property-parser.png|crude property-browser approximation using the pui2canvas parser (unmodified dialog), no layouts or sorting applied
</gallery>


A new Canvas widget will have two parts to it.  
This ScrollArea-based approach has the added benefit that improvements to the ScrollArea widget will automatically benefit widgets using it internally.  


The first provides the background code and the APIs for that widget. The second defines the appearance of the widget, i.e. its style.
However, once these basic widgets are supported, additional FlightGear/flight simulation specific widgets may be implemented sooner or later, e.g. for picking a frequency, heading/course etc (see [[UI_Unification#Widget_tags]]).


== Widget.nas ==
== Widget.nas ==
Line 74: Line 84:


== Part one ==
== Part one ==
A new Canvas widget will have two parts to it.
The first provides the background code and the APIs for that widget. The second defines the appearance of the widget, i.e. its style.
The first part is a Nasal file in {{fg root file|path=Nasal/canvas/gui/widgets}}, e.g., . They must follow the basic structure of the example below, replacing  
The first part is a Nasal file in {{fg root file|path=Nasal/canvas/gui/widgets}}, e.g., . They must follow the basic structure of the example below, replacing  
{{Canvas Widget Template|name=myWidget}}
{{Canvas Widget Template|name=myWidget}}
Line 114: Line 128:


== Examples ==
== Examples ==
{{Stub}}
=== Implementing a hrule widget ===
=== Implementing a hrule widget ===


Line 164: Line 180:
* <code>scrollTo()</code>
* <code>scrollTo()</code>
* ...
* ...
=== Implementing a graph widget ===
{{See also|Canvas_Snippets#Adding_OpenVG_Paths}}
[[File:Openvg-via-canvas.png|thumb]]
{{Canvas Widget Template|parent=Widget|name=graph}}
=== Map ===
{{Main article|MapStructure}}
{{WIP}}
[[File:MapStructure-map-widget.png|thumb|MapStructure Canvas widget prototyping]]
Assuming, we have access to a root group, the most minimal snippet of code to display a layered MapStructure map is the following:
<syntaxhighlight lang="nasal">
var TestMap = root.createChild("map");
TestMap.setController("Aircraft position");
TestMap.setRange(25);
TestMap.setTranslation(width, height);
    var r = func(name,vis=1,zindex=nil) return caller(0)[0];
    foreach(var type; [r('VOR'),r('APT'), r('APS') ] )
            TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name, visible: type.vis, priority: type.zindex,);
</syntaxhighlight>
Next, we need to wrap this in a Widget.nas child class:
Everything that we'd like to be able to customize at the widget level, needs to be exposed via a corresponding method added to the subclass inheriting from Widget.nas, for example:
* setController()
* setRange()
* setLayers()
* ...
To approximate the appearance of the legacy [[Map]] dialog, we will be using a hbox layout with two columns, with the left column containing a vbox layout with checkboxes added for each layer to control visibility of the layer, and the right hbox cell containing the actual MapStructure map:
Once we are finished, we can check if the widget is working properly by adding a handful of identical widgets to the same dialog, all of which should work independently, i.e. having their own checkbox/range widgets to control the corresponding map widget.
== References ==
{{Appendix}}
[[Category:Canvas GUI]]
[[Category:Canvas GUI]]

Revision as of 10:54, 3 July 2016

Originally, suggested and discussed back in early 2014 by a few FlightGear core developers, the pui2canvas approach to parsing a subset of PUI/XML has turned out to be a suitable fasttrack solution to get rid of the legacy PUI engine, without causing major regressions or tons of work. It seems, that probably nobody would object against removing PUI and replacing it by CUI, keeping the old PUI/XML syntax. At least for a while. Dropping PUI would be a huge step forward.[1]

For the time being, this approach has a number of major advantages, i.e. it has proven to be able to deal with existing UI resources (dialogs, and Menubar), without the original legacy UI dialogs (or any C++ code) having to be touched/modified, so that the primary thing missing to parse/process these dialogs are a handful of missing Canvas widgets, which can also be implemented in scripting space usually.

For the time being, this approach also is the only effort that can easily support procedurally created and updated dialogs like Aircraft Checklists and Tutorials. Also, the pui2canvas approach can help address a number of long-standing PUI related issues, such as rendering arttfacts on some ATI/AMD GPUs, but also PUI/Canvas inter-operability issues related to Canvas Event Handling.

As the integrated, and fully script-able, Canvas GUI develops, new widgets This is a link to a Wikipedia article need to be added [2]. This article shows how to create a Canvas GUI widget.

As of June 2016, there are just five widgets in the Canvas GUI:

In contrast, the PUI subsystem in FlightGear supports ~15 documented widgets (see $FG_ROOT/Docs/README.gui#l219 for a list of widgets made available to FlightGear via PUI/XML). For a complete list (including custom/undocumented widgets), refer to FGPUIDialog::makeObject() in flightgear/src/GUI/FGPUIDialog.cxx (line 850)

In addition, FlightGear introduces a number of custom PUI widgets implemented in C++ space, that not even PUI itself supports directly:

However, many PUI widgets can be emulated/approximated by using a combination of existing widgets, and/or functionality found in existing widgets, i.e. by referring to their source code.

As of late June 2016, the following widgets are primarily needed to help getting rid of PUI (listed in ascending complexity):

The key ones really being the last 4 in that list (radio, slider, dial and combo/dropdown)

Most other widgets currently missing can be implemented by using a combination of these widgets, e.g. a ScrollArea with buttons can be used to implement any type of list (airportlist, waypointlist, property-browser etc), just by adding event handling callbacks to each entry/button that invoke the corresponding bindings:

This ScrollArea-based approach has the added benefit that improvements to the ScrollArea widget will automatically benefit widgets using it internally.

However, once these basic widgets are supported, additional FlightGear/flight simulation specific widgets may be implemented sooner or later, e.g. for picking a frequency, heading/course etc (see UI_Unification#Widget_tags).

Widget.nas

This is the base class for all widgets, all widgets must inherit from it, or from any of its child classes - it can be found in $FG_ROOT/Nasal/canvas/gui/Widget.nas, its main methods are:

  • new() (constructor)
  • setFixedSize()
  • setEnabled()
  • move()
  • setSize()
  • setFocus()
  • clearFocus()
  • onRemove()
  • ...

Widget.nas is a scripting space wrapper for the lower-level NasalWidget module (see Nasal/CppBind) in simgear/layout: https://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/canvas/layout/NasalWidget.cxx The wrapper is registered and set up in $FG_SRC/Scripting/NasalCanvas.cxx: https://sourceforge.net/p/flightgear/flightgear/ci/next/tree/src/Scripting/NasalCanvas.cxx#l543

Part one

A new Canvas widget will have two parts to it.

The first provides the background code and the APIs for that widget. The second defines the appearance of the widget, i.e. its style.

The first part is a Nasal file in $FG_ROOT/Nasal/canvas/gui/widgets, e.g., . They must follow the basic structure of the example below, replacing

 gui.widgets.myWidget = {
    # our constructor
    new: func(parent, style, cfg){
        var m = gui.Widget.new(gui.widgets.myWidget);
        m._cfg = Config.new(cfg);
        m._focus_policy = m.NoFocus;
        m._setView(style.createWidget(parent, "myWidget", m._cfg));
 
        return m;
    }, # new() constructor
    # widget-specific APIs come below, e.g., setText()
 };


Notes:

  • All widgets inherit from canvas.gui.Widget (or any of its child classes).
  • The configuration hash can be used to alter the function of the widget.
  • The string "mywidget" corresponds to the name of the widget below.
  • In order the access methods in the code in next section. use me._view.<API>. MVC This is a link to a Wikipedia article

Part two

The second part is a section contained in $FG_ROOT/Nasal/canvas/gui/styles/DefaultStyle.nas.

The example below shows the minimum code needed, replace mywidget with your widget's name:

 DefaultStyle.widgets.mywidget = {
    new: func(parent, cfg){
        me._root = parent.createChild("group", "mywidget");
    },
    setSize: func(model, w, h){
        # ...
        # code for changing size
        # ...

        return me;
    },
    update: func(model){}
    # other APIs below, e.g., setBackground()
};


Notes:

  • the setSize() and update() methods must exist, although update() may be empty.
  • Some APIs from Part 1, especially ones that change the widget's appearance/size, will need to fall to this code, see note above.
  • In particular, the layout logic (e.g. hbox, vbox) need a way to notify a widget that it needs to be updated/resized, which is accomplished through these methods

Accessing colors

The colors of some parts of the Canvas GUI are loaded from $FG_ROOT/gui/styles/AmbianceClassic/style.xml (note that AmbianceClassic is, as of June 2016, the only style). You can define colors for your new widget there and access them in from the code above using, for example, me._style.getColor("<color name>");.

Background images

If your widget needs background images (e.g., the button widget), they must be placed in $FG_ROOT/gui/styles/AmbianceClassic/widgets (again, AmbianceClassic is the only style as of June 2016). You can then find their folder using me._style._dir_widgets. See the fgdata/Nasal/canvas/gui/widgets/Button.nas widget for an example.

In general, Canvas widgets will need some custom artwork - for the time being, Tom simply reused existing artwork, e.g. from Lubuntu/gtk (see the CheckBox implementation for reference) The corresponding artwork resides in $FG_ROOT/styles/AmbianceClassic: fgdata/gui/styles/AmbianceClassic/style.xml


Part three

All that remains is registering the new widget. To do this, open $FG_ROOT/Nasal/canvas/gui.nas and add a new loadWidget("myWidget"); to the section where the others are loaded (line 23 onwards), for example:

loadWidget("myWidget");

Notes:

Examples

This article is a stub. You can help the wiki by expanding it.

Implementing a hrule widget

The following example is based on adapting the line-drawing example taken from Canvas_Snippets#Adding_OpenVG_Paths. For instance, consider the following snippet (assuming there is already a root group):

var width = 640;
var height = 320;
var group = root.createChild("group");
var line = graph.createChild("path", "x-axis")
.moveTo(10, height/2)
.lineTo(width-10, height/2)
.setColor(1,0,0) # red 
.setStrokeLineWidth(3); # thickness

To adapt this to work as a widget, we need to get rid of the width/height variables and instead refer to the position vector in Widget.nas:

Implementing a slider widget

We can approximate a simple slider by using three buttons, with the middle button having a registered drag/drop listener, so that it can be repositioned accordingly

 gui.widgets.slider = {
    # our constructor
    new: func(parent, style, cfg){
        var m = gui.Widget.new(gui.widgets.slider);
        m._cfg = Config.new(cfg);
        m._focus_policy = m.NoFocus;
        m._setView(style.createWidget(parent, "slider", m._cfg));
 
        return m;
    }, # new() constructor
    # widget-specific APIs come below, e.g., setText()
 };


To support both, vertical and horizontal, sliders, it makes sense to use the config hash and add a mode attribute to it, which may contain either horizontal or vertical

For an example, we can refer to the ScrollArea.nas code which is doing this sort of thing already.

Implementing a radio button

To implement a radio buttno widget, it makes sense to look at Button.nas and CheckBox.nas - i.e. we need a button that changes its background image based on being selected/hovered - which is basically what the checkbox is doing, too:

Implementing a dropdown menu

To implement a simple dropdown/combo widget, we mainly need the following functionality:

  • a ScrollArea
  • a vbox layout
  • buttons for each entry added to the ScrollArea

Thus, instead of inheriting from gui.Widgets, we can directly inherit our combo widget from ScrollArea:

 gui.widgets.combo = {
    # our constructor
    new: func(parent, style, cfg){
        var m = gui.widgets.ScrollArea.new(gui.widgets.combo);
        m._cfg = Config.new(cfg);
        m._focus_policy = m.NoFocus;
        m._setView(style.createWidget(parent, "combo", m._cfg));
 
        return m;
    }, # new() constructor
    # widget-specific APIs come below, e.g., setText()
 };


Due to inheriting directly from the ScrollArea widget, we now have access to all its functionality:

  • setLayout()
  • getContent()
  • setColorBackgrond()
  • setSize()
  • scrollTo()
  • ...

Implementing a graph widget

Openvg-via-canvas.png
 gui.widgets.graph = {
    # our constructor
    new: func(parent, style, cfg){
        var m = gui.Widget.new(gui.widgets.graph);
        m._cfg = Config.new(cfg);
        m._focus_policy = m.NoFocus;
        m._setView(style.createWidget(parent, "graph", m._cfg));
 
        return m;
    }, # new() constructor
    # widget-specific APIs come below, e.g., setText()
 };


Map

1rightarrow.png See MapStructure for the main article about this subject.

WIP.png Work in progress
This article or section will be worked on in the upcoming hours or days.
See history for the latest developments.
MapStructure Canvas widget prototyping

Assuming, we have access to a root group, the most minimal snippet of code to display a layered MapStructure map is the following:

var TestMap = root.createChild("map");
TestMap.setController("Aircraft position");
TestMap.setRange(25);

TestMap.setTranslation(width, height);
     var r = func(name,vis=1,zindex=nil) return caller(0)[0];
     foreach(var type; [r('VOR'),r('APT'), r('APS') ] )
            TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name, visible: type.vis, priority: type.zindex,);

Next, we need to wrap this in a Widget.nas child class:

Everything that we'd like to be able to customize at the widget level, needs to be exposed via a corresponding method added to the subclass inheriting from Widget.nas, for example:

  • setController()
  • setRange()
  • setLayers()
  • ...

To approximate the appearance of the legacy Map dialog, we will be using a hbox layout with two columns, with the left column containing a vbox layout with checkboxes added for each layer to control visibility of the layer, and the right hbox cell containing the actual MapStructure map:

Once we are finished, we can check if the widget is working properly by adding a handful of identical widgets to the same dialog, all of which should work independently, i.e. having their own checkbox/range widgets to control the corresponding map widget.

References

References
  1. Torsten Dreyer  (Jun 14th, 2016).  Re: [Flightgear-devel] GUI options (Was: Aircraft center) .
  2. bugman  (Jun 21st, 2016).  Re: Aircraft Center | pui2canvas .