Howto:Making a Canvas Menubar

From FlightGear wiki
Jump to navigation Jump to search
Canvas GUI/Menubar
Started in 09/2014
Description A native Canvas menu bar
Maintainer(s) Hooray
Contributor(s) Jabberwocky[2], omega95[3]
Status Under active development as of 09/2014


This article or section contains out-of-date information

Please help improve this article by updating it. There may be additional information on the talk page.

Demonstrate how to create a Canvas menubar and how to populate it by parsing $FG_ROOT/gui/menubar.xml using the readxml() API in io.nas and getValues() in props.nas.

We'll be prototyping the whole thing using the Nasal Console.

Early Canvas menubar prototype providing better performance than our native PUI menubar, even on very underpowered platforms (25ms/@55fps)
Screen shot showing Jabberwocky's Canvas-based menubar [1]


  • Creating a Canvas Window Done Done
  • Loading $FG_ROOT/gui/dialogs/menubar.xml vio readxml() from io.nas Done Done
  • Processing the menubar as a Nasal hash via the pops.nas getValues() API Done Done
  • Creating a function for creating menus and sub menus (see Omega95's code) Not done Not done
  • Adding support for mouse handling using Canvas Event Handling 40}% completed
  • Adding support for bindings (fgcommands/nasal) Not done Not done
  • Full localization support Not done Not done
  • Use the Canvas Layout engine Not done Not done
  • Support widgets (checkboxes) in menus Not done Not done
  • Support adding menubar to an existing Canvas (for instruments/MFDs) Not done Not done
  • Support sub-menus Not done Not done
  • Add support for an optional tooltip tag for each menu/entry Not done Not done
  • Make the menu reloadable at run-time Not done Not done
  • Support multiple instances of a menubar 70}% completed
  • Support multiple UI/menubar modes [4] Not done Not done
  • ...
  • Add support for legacy styling system ? Not done Not done


Cquote1.png I still have to find a way to move the menu more to the right ( center ) because in my current screen layout it is now in an invisble area (the center screen is higher than the screens left & right)
— pommesschranke (Sun Feb 23). Re: Multi screen configuration not working.
(powered by Instant-Cquotes)
Cquote1.png The upcoming GUI is much more flexible - basically ANYTHING can be rendered - omega95 already created popup menus with sub menus.

Given Tom's recent work on canvas/keyboard support there shouldn't be too much missing to add a canvas window to the area of the menubar and use a few lines of Nasal to parse our existing menubar.xml file into submenus like those that omega95 created.

— Hooray (Tue Sep 09). Re: Just a little thing in the menu - ruler.
(powered by Instant-Cquotes)
Cquote1.png Changing the GUI/menubar would be extremely straightforward still - people can have their own menubars and even GUI styles, and the upcoming Canvas-based GUI post 3.2 is going to be even better style-able.
Cquote1.png The menubar and all options there are 100% XML-configurable using a PropertyList-dialect, much less sophisticated than any of the FDM/XML that experienced JSBSim developers -like yourself- are familiar with - so there's no reason why you shouldn't be able to edit $FG_ROOT/gui/menubar.xml accordingly.

AFAIK, the menubar can also be reloaded at run-time (the upcoming Canvas-based menubar certainly will be reloadable) - which means, we could probably support different menubars with different "options" for different kinds of users/contributors/developers.

Cquote1.png I don't think there's anything missing in order to replace our native menubar with a Canvas-based implementation, it should be pretty straightforward, TheTom & Zakalawe have mentioned that a few times - and meanwhile, the Canvas is fairly feature-complete to support events and GUI stuff, we would just need to come up with a "menubar" widget that's based on popup menus. So basically, two new widgets that would need to be created, and the PUI menubar could go.
— Hooray (Sun Feb 23). Re: Multi screen configuration not working.
(powered by Instant-Cquotes)
Cquote1.png Yeah, the current problem is that the menu at is still handled in C++, and AFAIK the translated strings aren't exposed anywhere... I tried adding an extension function for the translation strings, but I was having problems with compiling/updating (and some other things). It's basically a one-line interface to globals->getLocale()->getTranslatedString(...). Otherwise the menubar is stored in the property tree - /sim/menubar/

But I think we can disable the PUI-bar?

— Philosopher (Sun Feb 23). Re: Multi screen configuration not working.
(powered by Instant-Cquotes)
Cquote1.png the menubar can be simply hidden (F10) - while it is drawn via C++, all the strings/structure are PropertyList XML in menubar.xml - so we would just need to:
foreach(var menu; var io.read_properties("menubar.xml").getValues().menu) {
 # create new popup menu entry for
 foreach(var item; menu.item) {
 # create new item/entry
 # addEventListener for each binding
names/strings would need to be resolved by using translations as an overlay
— Hooray (Sun Feb 23). Re: Multi screen configuration not working.
(powered by Instant-Cquotes)


Note  In its current form, this is is to be copied and pasted into the Nasal Console for faster prototyping/testing. Eventually, this may be turned into a dedicated Nasal submodule or Canvas widget instead. For now, this ensures that the barrier to entry is kept sufficiently low.

To place all menus next to each other, we'd normally have to look up the bounding box for each previously added menu - but for the sake of simplicity we can also simply use the new Canvas Layout engine to arrange everything in a left-aligned horizontal box (hbox). Likewise, menu items in the popup menu can be arranged using a vbox layout. Prototyping a Canvas menubar

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.
# Parent class for managing a Canvas

var CanvasApplication = {
 # constructor
 new: func(x=300, y=200) {
  var m = { parents: [CanvasApplication] };
  m.window =[x,y],"dialog");
  m.canvas = m.window.createCanvas().setColorBackground(1,1,1,1);
  m.root = m.canvas.createGroup();
  return m;
 }, # new
init: func() {

}, #end of init

}; # end of CanvasApplication

# helper function for creating event handlers with a closure

var make_handler = func(group, menu, event, action) {
group.addEventListener(event, func() {
  action(group, menu);
}; # make_handler()

# Helper function for creating text nodes
# and setting some defaults
var setupTextNode = func(group) {
return group.createChild("text")
.setDrawMode(canvas.Text.TEXT + canvas.Text.BOUNDINGBOX)

var setupMenuGroup = func(menu) {
 # now check for sub menus
 foreach(var submenu; menu.item) {
 if (submenu['enabled'] != 'false')
 print("==> found new submenu item:",;
  } # get out items 
}; # setupMenuGroup()

# Helper class for managing a menu bar

var CanvasMenubar = {
 new: func(x,y, filename='gui/dialogs/menubar.xml') {
  var m = {parents: [ CanvasMenubar,,y) ], _filename:filename };
  return m;

 init: func() {

 del: func() {

 load: func() {
  var fgroot_path = getprop("/sim/fg-root");
  var path = fgroot_path ~ '/' ~ me._filename;
  # next, load menubar.xml using read_properties() (see io.nas) :
  var menubar = io.read_properties( path );

  # now, turn the whole thing into a Nasal hash for dead-simple processing
  me.menubar = menubar.getValues();

 build: func() { 
 var maxX=0;

 foreach(var menu; {
 print("Found new menu:",;

 # add some text to our menubar:
 var myMenu = setupTextNode(me.root)
  .setTranslation(maxX, 2); # hack (due to layout engine issue)

maxX += myMenu.getBoundingBox()[2]+20;

# set up a mouseover listener for the test object:

# change the text color based on mouse events:
make_handler(group:myMenu, menu:menu, event:"mouseenter", action: func(group) {group.setColor(1,0,0);} );
make_handler(group:myMenu, menu:menu, event:"mouseleave", action: func(group) {group.setColor(0,0,0);} );


# respond to clicks
make_handler(group:myMenu, menu:menu, event:"click", action: func(group, menu) {
  print("Menu clicked:",;

} # foreach menu

}, # build()


# support different kinds of menubars
var myMenubar =	x:getprop("/sim/startup/xsize"), 
					y:40, filename:'gui/menubar.xml');