Howto:Processing legacy PUI dialogs using Canvas
Note This discusses an outdated, but functional, prototype of a pui2canvas Nasal module originally drafted back in 2015[1] [2] [3], in response to talks on the devel list at the time, discussing various pros&cons of different approaches. In the meantime, since mid 2022, there's now an actual pui2canvas module being implemented by core developers, available in $FG_ROOT/Nasal/gui/XMLDialog.nas - therefore, this article is primarily useful for future reference, or to look up some of the original ideas/approaches. For recent developments, please refer to PUI#2022. |
The FlightGear forum has a subforum related to: Canvas |
As of 05/2017, James is still very much undecided about which technology to use for in-sim GUI, he is somewhat inclined towards using the Canvas, because it avoids some rendering issues (but exposes a few more, + some performance ones) but the problem is James is fairly unhappy with the GUI / widget API in the Canvas right now - it does not satisfy his ideas about how simple + robust such an API should be, James needs to evaluate if the current API can be improved or needs to be drastically changed. The other issue is to use QtQuick rendered into OpenGL, which has a very nice robust and well-designed API, but adds some dependencies and complicates the rendering architecture, which makes him nervous about multi-window setups and other more esoteric OSG configs.[4]
FlightGear's rudimentary gui (PUI) is written all in opengl so it works fine in full screen and doesn't need tricky interactions with the underlying OS (the details of which often vary wildly from one OS or window system to the next.) Qt has taken great strides in lowering this barrier and James is a Qt expert, but it's still a massive amount of work with a lot of build/dependency implications to convert a whole gui system (and test all the edge cases.)[5]
Improving the frame-rate and modernised 3D rendering, can’t be worked on until the PUI code, 2D panels and Shiva are removed, but doing so is a frustrating slow path[6]
Given that with many graphics drivers PUI doesn't render correctly when higher shader quality is on, many people are convinced PUI needs to be replaced.[7]
While most people seem to agree PUI needs to be replaced, it sounds as if the fallout from doing so [using Qt5] would be more painful (cumulatively) than the pain its existence causes.[8]
Obviously, it’s most important that people feel able to use and contribute to the project, far more so that whether a particular feature gets implemented or not.[9]
If you plan a solution to a problem, but it might get ready in two years (or not), we still may want/need something now. So could create some quick hack filling the gap with the expectation that the hack gets replaced once a proper solution is there [10]
pui2canvas.nas is a viable replacement - once finished, it will render every existing dialog, not pretty but working - 95% of the pain for maintainers is gone because no immediate action is required, everything still works, PUI can go[11]
The point is that the parser can parse/translate a subset of PUI xml and make a viable dialog composed of canvas elements out of it. Any graphics artist can now make better elements, changing colors and fonts is down to a .setColor() or .setFont(), so the whole thing is as configurable as any canvas element.[12]
James would be delighted if someone is serious about working on the Canvas-based UI, replacing PUI and so on. If there’s people really interested in that, he would much rather help them out, than duplicate their efforts in his limited spare time. [13]
In 01/2018, James said that he would not be especially invested in a QQ2 UI - it’s his ‘day job’ work - but obviously he also knows the technology. What he doesn't want to feel, is that he is forcing his approach on the community if people think a Canvas-based solution (or a Phi-based one or anything else) would be easier and also deliver a long-term basis for our UI. And he would be much happier if the Ui was a collaborative effort, since there are other areas he would much sooner focus on than 'more of his day job' [14]
Furthermore stating, that his basic feeling is to step back and say, let’s see if a pure Canvas UI will work[15]
James also said that his impression was that if someone makes a Canvas UI option, the Qt based solution will be still-born almost by default - because he won’t be able to create any enthusiasm or interest around it. But for sure Canvas based approaches attract a lot more support and man-power very easily (than a Qt5/QQ2 option). And his guess each person who works on the Canvas based Ui is one fewer who might ever help out with the Qt based one. (That is not probably 100% true, but there is some correlation) [16]
The basic thinking being let’s say the Qt UI is 1/4 of James' time per week spent on FG (might be a little more, but he's got lots of other things to work on), vs Thorsten’s time and 4 or 8 enthusiastic people he can recruit - Qt5/QQ2 is obviously going to fall massively behind in comparison - within three or six months they might build a complete replacement UI. [17]
It is also worth noting that we don't really need a 1:1 mapping between PUI and Canvas, because we can usually emulate/approximate most PUI widgets rather easily using similar workarounds - as a matter of fact, the whole PUI widget set is rather archaic, and we really only need 2-3 additional Canvas widgets to approximate the full set of supported widgets without much of an effort. For instance, there are a number of hard-coded widgets, such as the airport-list, waypoint-list or the property-browser - under the hood however, all of these end up just being combo/select boxes with items that respond to clicks[18]
Addon
See Addon for the main article about this subject. |
This section documents the necessary changes to turn the pui2canvas module into an addon using Torsten's addon framework.
Specifically, this involves the following changes:
- creating a pui2canvas addon
- providing a property for enabling/disabling this via
--prop:
- registering a listener to easily enable/disable the module at runtime
- adding a blacklist/whitelist of supported/working dialogs/widgets (think exit.xml, about.xml for starters)
- provide an in-sim UI to easily enable/disable pui2canvas for certain dialogs using checkboxes with the userarchive attribute set
- provide a menubar parser Howto:Making a Canvas Menubar
- provide an option to re-instate the Canvas aircraft center [1]
Whitelist
List of dialogs that are simple enough to be displayed "as is" using the pui2canvas parser:
- flightgear/fgdata/next/gui/dialogs/about.xml
- flightgear/fgdata/next/gui/dialogs/exit.xml
- flightgear/fgdata/next/gui/dialogs/scenario.xml
- flightgear/fgdata/next/gui/dialogs/view.xml
- flightgear/fgdata/next/gui/dialogs/logging.xml
Objective
This article is intended to demonstrate how existing PUI dialogs can be easily processed using FlightGear scripting (via Nasal) and the Canvas 2D rendering API, and be turned into Canvas widgets and dialogs.
This will enable the removal of PUI-related OpenGL code, which is known for using inefficient legacy OpenGL code that is not suitable to be used in conjunction with OSG, and also let existing dialogs be supported without having to manually update/port, or separately maintain, them.
In its current form, this is not intended to be a completely functional alternative to PUI, but just intended to document the necessary steps for supporting the existing PUI dialogs (and widgets), without having to adopt an external UI library like Qt5.
Simple dialogs are likely to work, while more sophisticated dialogs may require some additional work, especially when it comes to layouting and styling (including PUI themes).
This extra work would typically involve extending or creating custom Canvas widgets for wrapping PUI widgets, and integrating those with the parser shown below.
The main motivation here is to ensure that there are no regressions introduced and to ensure that existing dialogs continue to work, no matter if they live inside $FG_ROOT or not, but also because it is generally not a good idea to rewrite working systems from scratch[19][20][21].
- ↑ https://forum.flightgear.org/viewtopic.php?f=71&t=25087&&start=30#p260327
- ↑ https://forum.flightgear.org/viewtopic.php?f=71&t=25087&&start=30#p260484
- ↑ https://forum.flightgear.org/viewtopic.php?f=71&t=29789
- ↑ James Turner (May 28th, 2017). Re: [Flightgear-devel] KBOS runway list? are there 10 or are there 12? .
- ↑ curt (Oct 6th, 2016). Re: Nasal must go .
- ↑ James Turner (Jan 24th, 2017). Re: [Flightgear-devel] canvas non svg-elements broken .
- ↑ Thorsten Renk (Jan 25th, 2017). Re: [Flightgear-devel] canvas non svg-elements broken .
- ↑ James Turner (Jan 24th, 2017). Re: [Flightgear-devel] canvas non svg-elements broken .
- ↑ James Turner (Jan 24th, 2017). Re: [Flightgear-devel] canvas non svg-elements broken .
- ↑ Thorsten Renk (Jan 28th, 2017). Re: [Flightgear-devel] Fwd: Canvas-svg parsing breakage .
- ↑ Thorsten Renk (Jan 25th, 2017). Re: [Flightgear-devel] canvas non svg-elements broken .
- ↑ Thorsten (Dec 5th, 2016). Re: More realistic Blackout/Redout .
- ↑ James Turner (Jun 10th, 2016). Re: [Flightgear-devel] Aircraft center .
- ↑ James Turner (Jan 18th, 2018). Re: [Flightgear-devel] QtQuick UI - current status + plans .
- ↑ James Turner (Jan 20th, 2018). Re: [Flightgear-devel] QtQuick UI - current status + plans .
- ↑ James Turner (Jan 22nd, 2018). Re: [Flightgear-devel] QtQuick UI - current status + plans .
- ↑ James Turner (Jan 22nd, 2018). Re: [Flightgear-devel] QtQuick UI - current status + plans .
- ↑ Hooray (Jan 4th, 2017). Re: GUI Combo: How to control so it drops down not up .
- ↑ http://www.joelonsoftware.com/articles/fog0000000069.html
- ↑ http://vibratingmelon.com/2011/06/10/why-you-should-almost-never-rewrite-code-a-graphical-guide/
- ↑ http://onstartups.com/tabid/3339/bid/2596/Why-You-Should-Almost-Never-Rewrite-Your-Software.aspx
Gallery & Status
we have a demo up and running which can be refined and extended within days/weeks. Presumably, if we really wanted and would focus efforts, PUI could go by the next release and we'd have a rough replacement.[1]
Given this, probably nobody objects against removing PUI and replace it by CUI, keeping the old syntax. At least for a while. Dropping PUI would be a huge step forward.[2]
As of mid 2016, it is pretty safe to say that it's less than a handful of widgets that are now missing to be able to approximate most PUI widgets/dialogs - it goes without saying that this won't look as good as a Qt5 UI, but it also won't have the costs (amount of work) associated with it - i.e. you could say, the pui2canvas approach is merely a simple fasttrack concept to get rid of PUI completely, without having to manually port any of the existing resources necessarily.[3]
if the xml format changes (which is reasonable if you want to make use of Qt capabilities), it should be documented. Based on this documentation, the maintainer(s) of the parser make the required changes. It's not rocket science because it's a minimal solution based on fairly simple Nasal code (~500 LOC). Since Qt is envisioned to be the default solution and Canvas a minimal fallback, that is the proper way to do it.[4]
As far as aircraft dialogs are concerned [...] the parser has to be backward-compatible no matter what unless someone actually wants to go manually through old aircraft and fix. Also - what's the idea with Phi for aircraft access? A separate set of special dialogs aircraft-side? It's basically the same challenge.[5]
As of 06/2016, the built-in Canvas-based Aircraft Center has been entirely removed from the FlightGear GUI, and replaced with the Qt5-based GUI, the commit message saying[6]
https://sourceforge.net/p/flightgear/fgdata/ci/654a343bbb7eb51b387060515e3415e152d12c2a/
Needs a lot of testing, but aircraft can be installed / changed and location adjusted from within the sim. After some number of times the sim will crash.
Note that this means that Aircraft Center functionality will no longer be available to people if they don't also happen to use Qt5 and/or Phi (not sure if package manager functionality is provided there?).
Note when adding new/updated screenshots, please be sure to show the corresponding PUI dialog side-by-side for comparison. |
AI fgdata/gui/dialogs/scenario.xml dialog parsed/processed by pui2canvas converter
Screen shot showing the procedurally generated Canvas dialog, based on parsing a PUI subset of fgdata/gui/dialogs/view.xml
fgdata/gui/dialogs/jetways.xml dialog parsed/processed by the pui2canvas parser (no layouting applied)
Screenshot showing the Nasal Console GUI dialog processed by the pui2canvas parser (with functional bindings)
approximation of the FlightGear Time dialog via pui2canvas parser
Earthview PUI/XML dialog rendered via the pui2canvas parser (with table support)
Procedurally created wildfire UI dialog that is rendered using the pui2canvas parser
side-by-side comparison pui vs. pui2canvas for view.xml dialog [7]
FlightGear PUI XML dialog rendered via Pui2canvas and served via httpd to a running firefox instance.
As of 10/2015, a simple framework is being prototyped to serve as a proof-of-concept and demonstrate how existing PUI dialogs can be processed by a parser written in Nasal, which turns PUI/XML markups into the corresponding Canvas widgets/properties.
At the time of writing, it isn't clear if this approach is going to be actually used anywhere-but that doesn't really matter, because the parser is fairly straightforward and simple, which makes it an excellent stress test to exercise the underlying Canvas/C++ code.
So running the parser and benchmarking/profiling FlightGear is a pretty good way to determine bottlenecks and come up with ideas for extending/optimizing the Canvas C++ code a little, which will be beneficial to unrelated efforts (including any aircraft/dialogs using Canvas/MapStructure based displays), but also the long-term idea to help unify the 2D rendering back-end by porting the HUD/2D panels code to use Canvas.
Creating a Canvas property tree hierarchy for even simple PUI/XML dialogs will easily cause the creation of dozens of layout instances and hundreds of Canvas groups in the tree, many of which may have attached listeners/timers for event handling purposes. The Nasal/Property Tree overhead is fairly remarkable, and also noticeable on older systems.
For the time being, the parser correctly supports:
- embedded nasal/close blocks (code executed prior to dialog creation, and cleanup code executed when closing the dialog)
- bindings in the form of fgcommands and Nasal scripts
- C-format style labels/legends
- dynamically patched fgcommands to transparently dispatch PUI fgcomands to their Canvas equivalents (e.g. dialog-close)
Upcoming features will include:
- support for table layouts (heavily used by most PUI/XML dialogs)
- PUI widget visibility controlled via props.nas (conditions)
- integration with the
canvas
tag to fully support recursion
Widget Matrix
See Canvas Widget Matrix for the main article about this subject. |
Before this gets committed ...
For this to be committed, even just as an option, a few things should happen first of all:
- the whole thing should be turned into an optional Nasal submodule
- loading it should be configurable via preferences.xml
- it would make sense to introduce a $FG_ROOT/pui2canvas.xml file included via preferences.xml, which contains script specific settings
- such settings could be a list of dialogs/widgets known to work well enough already (think about.xml, view.xml, scenarios.xml etc)
- menubar.xml could be edited to replace
dialog-show
entries with agui.dialog-show
entry - this could transparently let the user decide whether he wants to use PUI, Qt5 or the pui2canvas parser (think Aircraft Center)
- provide an in-sim option to easily toggle between all options, set the userarchive attribute
- provide an option to unload/reload the module at runtime (for people wanting to help troubleshoot/develop this without having to exit/restart)
- testing should take place with a focus on complex dialogs (joysticks, tutorials, checklists), and functionality of the parser should probably be reviewed by Stuart (who created all those dialogs)
Why Nasal and Canvas ?
Let's uppose we get super-fancy weather dialog. But the current weather dialog also works - because at the end of the day, what the dialogs do is set properties, execute FGCommands or start Nasal scripts. The fact that you have created a super-fancy dialog to do these tasks doesn't mean the simple one stops working. While the visuals of the dialogs can be really complicated, how the dialog communicates with FG is very well defined. It's like saying Phi will stop working because there's Qt - it won't, because internally it also sets properties, executes FGCommands or scripts. New widgets don't equal new ways to interact with FG for your module - how you interact with FG is determined by what the subsystems understand, not what your dialog does. The same way as a launcher is just a fancy way of assembling a commandline, and... you can just skip it and type the commandline.[8]
there are different components available in FlightGear - as in an existing "Canvas GUI" SGSubsystem that runs inside the main loop, which all Canvas code can leverage - aside from that, there are widgets, and a framework to easily create new widgets - including a SVG parser that can reuse JavaScript/HTML based widgets.
Apart from that, there is the pui2canvas parser that maps a subset of the legacy PUI markup to this existing infrastructure.[9]
Any UI talks to FG in terms of setting properties, calling FGcommands and Nasal scripts. Pretty much anyone who has done a glass cockpit for FG has solved the task.
In fact, the Shuttle UI with the combination of MEDS/DPS pages, the keypad entry, the various keyboard to IDP to screen mappings and the major function/major mode constraints is much more complicated than FGs PUI. It'd be trivial (and amusing) to let some fictional Shuttle DPS pages control FG settings - it'd be straighforward to do because the DPS does what any GUI does - it communicates with FG via properties, FGCommands and scripts.
So what some may call a 'mammoth' task (providing a graphical representation for communication with the rest of FG) is in fact exactly what canvas is for and what it does in every aircraft that uses a more advanced glass cockpit. Without any major problems really... Really all that is needed is the xml parser and the widgets - the rest is all there and has been used for years.[10]
that also means that the pool of experienced Nasal/Canvas contributors (i.e. those who have done some Nasal/Canvas hacking for their aircraft), and thus potential UI contributors is much larger in the FlightGear community compared to, say, people familiar with the FlightGear/SimGear code base, and Qt5 development in particular. Like Thorsten said, the bottleneck is creating a parser that supports a sufficiently complete subset of PUI/XML as it is used in FlightGear, and coming up with the corresponding widgets provided/used by FlightGear's PUI/XML interface.
Bottom line, the Canvas UI SGSubsystem is there, it's existing C++ code, that has been integrated years ago - it's used every day - whenever you're seeing a tooltip, a Canvas GUI dialog or a CanvasWidget, it's all there and just works.
If people find it challenging to render a webkit or Qt5 widget into FlightGear, they'd be well-advised to simply come up with a custom Canvas::Element child class - which I also understand how to do, and James has previously done that, too (he implemented/prototyped Canvas::Image for handling raster images), so he understaands perfectly how to extend the Canvas system to support other "elements".
This path is unrelated to PUI or pui2canvas - it's just a way of rendering into the FligthGear window, without having to do much homework - it'll just work, which includes event handling etc - Tom implemented that almost half a decade ago, indeed at the encouragement of James and others back then ...[12]
replacing the PUI menubar with a Canvas-based one is trivial - the code on the wiki actually worked, i.e. is parsing menubar.xml and creates buttons that create popups with the corresponding entries.[13]
you don't even need to "entirely strip out PUI from any source code" to see that this works, because you can use the corresponding fgcommands to remove the entire PUI subsystem at runtime - e.g. using what you referred to as "segfault heaven": Howto:Reset/re-init Troubleshooting
In other words, pui2canvas can be solely implemented in fgdata space, and it can even remove PUI at runtime ...[15]
FAQs
The new Qt5 functionality is seeing improvements in areas for which there is no mapping outside Qt5, namely no PUI/Canvas or Phi bindings to bring features like the package manager up to par with the Qt5 code.[16]
It seems increasingly likely that the Qt5 launcher will continue to developed, and it may even make built-in features obsolete, so it is a good idea to be proactive whenever you notice anything missing/broken or incomplete, because all the ongoing Qt5 work is likely to be the foundation for what FG is going to look like a few years from now - to see that that's the case, you only need to look at new developments that are solely integrated with the Qt5 front-end - thus, sooner or later, certain desirable functionality will be tied to Qt5, and you should not expect fgrun to support all features that the Qt5 launcher does/will support.[17]
As of mid 2016, it is becoming clear that the Qt5 launcher and the way it is integrated, is having ramifications for features that are otherwise unrelated to the launcher, and Qt5 as a whole. For example, the reason end-users have an empty aircraft list in the Aircraft Center seems to be tied to the fact they do not use the Qt Launcher.[18]
People have to get the built-in launcher to setup the catalog. The canvas Aircraft center works fine, it just depend on the catalog being setup first, which is done by the build-in launcher.
The package manager is the back-end built into fgfs - but it's connecting to a website (see the xml file I mentioned above) hosted byakalawe - and previously, that xml file (and related data) hasn't been updated in a while[20]
This is pretty unfortunate, because one of the main reasons for encouraging the Qt5-based development of a launcher was indeed that it would remain 100% optional, and that it would NOT have ramifications for unrelated features, i.e. those not requiring/using Qt5 - accordingly, we might as well dispense with the fiction that this is not a dependency.[21]
Qt5 is not intended to be required for building FG, even though useful functionality is going to be increasingly tied to it (think launcher, in-sim UI, package manager, AircraftCenter UI).[22]
Yet another GUI effort ?
Nope, this is not a separate GUI, it is primarily a module to translate existing GUI files to use the integrated Canvas 2D drawing system. Also, to be absolutely fair, the whole Canvas UI debate took place several years prior to the development of a Phi/Qt5 based UI, and it was in fact inspired and encouraged by a number of core developers back then (as you can see below).
What about PUI ?
Another option is to move /all/ the remaining pieces of PLIB we use (actually just FNT, PUI and JS) into 3rdparty, and drop our dependency on external PLIB. — James Turner (Mar 9th, 2016). [Flightgear-devel] Joystick support code location.
(powered by Instant-Cquotes) |
Rolling the portions of plib that we still use into our own code base is a topic worth discussing. With a small bit of thought, I don't see a downside. Plib development/support ended long ago. — Curtis Olson (Mar 9th, 2016). Re: [Flightgear-devel] Joystick support code location.
(powered by Instant-Cquotes) |
PUI is scheduled to disappear anyway, I don't think anyone will invest much time trying to fix it. — Thorsten (Dec 16th, 2015). Re: AMD Radeon R9 – Massive text flickering again.
(powered by Instant-Cquotes) |
At least for the time being, PUI doesn't seem to become obsolete anytime soon, according to James Turner's continuing preference to hack/extend and maaintain custom PUI widgets:
http://sourceforge.net/p/flightgear/mailman/message/34747786/ This observation is also in line with numerous PUI related commits that he made despite his ongoing Qt5 work: http://wiki.flightgear.org/Canvas_GUI#PUI_Widgets For a more recent example, look at the waypointlist commits that are using neither Qt5, Phi or Canvas at all: http://sourceforge.net/p/flightgear/flightgear/ci/846fd2— Hooray (Jan 9th, 2016). Re: Can't install FGRun package (+ rant about Qt5 launcher).
(powered by Instant-Cquotes) |
I am strongly against using a very non-standard UI (a text box + slider) to achieve that interaction. I’d much rather we add a spin-box to PUI, which is not especially hard, and give it the ‘click and drag’ mouse interaction which some spinboxes in other apps have, to give similar UI to the sliders without the visual clutter. — James Turner (Jan 7th, 2016). Re: [Flightgear-devel] Speed-up vs time-warp.
(powered by Instant-Cquotes) |
I frantically code up a tweaked PUI text-input/spin-box with press-and-drag to adjust, effectively a slider’s mouse interaction — James Turner (Jan 13th, 2016). Re: [Flightgear-devel] Speed-up vs time-warp.
(powered by Instant-Cquotes) |
- ↑ Thorsten Renk (Jun 14th, 2016). Re: [Flightgear-devel] GUI options (Was: Aircraft center) .
- ↑ Torsten Dreyer (Jun 14th, 2016). Re: [Flightgear-devel] GUI options (Was: Aircraft center) .
- ↑ Hooray (Jun 21st, 2016). Re: Aircraft Center | pui2canvas parser (devel-list follow-u .
- ↑ Thorsten Renk (Jun 14th, 2016). Re: [Flightgear-devel] GUI options (Was: Aircraft center) .
- ↑ Thorsten Renk (Jun 14th, 2016). Re: [Flightgear-devel] GUI options (Was: Aircraft center) .
- ↑ Hooray (Jun 9th, 2016). Re: How do I enable aircraft center? .
- ↑ https://forum.flightgear.org/viewtopic.php?f=71&p=289322#p289321
- ↑ Thorsten (Jun 17th, 2016). Re: Aircraft Center | pui2canvas parser (devel-list follow-u .
- ↑ Hooray (Jun 18th, 2016). Re: Aircraft Center | pui2canvas parser (devel-list follow-u .
- ↑ Thorsten (Jun 18th, 2016). Re: Aircraft Center | pui2canvas parser (devel-list follow-u .
- ↑ Hooray (Jun 19th, 2016). Re: Aircraft Center | pui2canvas parser (devel-list follow-u .
- ↑ Hooray (Jun 18th, 2016). Re: Aircraft Center | pui2canvas parser (devel-list follow-u .
- ↑ Hooray (Jun 18th, 2016). Re: Aircraft Center | pui2canvas parser (devel-list follow-u .
- ↑ Hooray (Jun 18th, 2016). Re: Aircraft Center | pui2canvas parser (devel-list follow-u .
- ↑ Hooray (Jun 18th, 2016). Re: Aircraft Center | pui2canvas parser (devel-list follow-u .
- ↑ Hooray (Apr 10th, 2016). Re: OSGearth as default scenery?.
- ↑ Hooray (Feb 19th, 2016). Re: FG 2016,1.1.
- ↑ jaxsin (May 7th, 2016). Re: Tutorial: How to get Aircraft Center to work.
- ↑ Necolatis (May 7th, 2016). Re: Tutorial: How to get Aircraft Center to work.
- ↑ Hooray (Feb 17th, 2016). Re: Aircraft Center.
- ↑ Thorsten Renk (Oct 13th, 2015). Re: [Flightgear-devel] GUI questions (again).
- ↑ Hooray (Oct 14th, 2015). Re: New Canvas GUI.
What is it really ?
It is a parser/convertor (written in Nasal) to deal with existing PUI/XML files, which are dynamically translated to Canvas widgets, so that a corresponding dialog can be shown.
the idea of replacing the PUI GUI with a Canvas-based GUI. — bugman (Nov 17th, 2015). Re: version checking for matching fgfs and -set.xml files.
(powered by Instant-Cquotes) |
How is this any different from Qt5/Phi?
So far, the discussions revolving around Qt5/Phi were mainly about coming up with a new GUI from scratch, which would be a lot of work, and probably introduce a bunch of regressions along the way. Parsing a sufficiently large subset of PUI using Nasal/Canvas however is not so much work in comparison.
It's most likely a problem with the PUI (the current GUI), and the plan is to discontinue using it. There are experimental canvas GUIs which don't show the flickering (look around in the forum for some pointers), there's Phi as external GUI option and James' plan is to create a new GUI using Qt5.
So it's not really that no one cares, GUI is about the most active part of core development at the moment. — Thorsten (Dec 16th, 2015). Re: AMD Radeon R9 – Massive text flickering again.
(powered by Instant-Cquotes) |
Also, there are some key features that simply rely on the way PUI/XML and Nasal work together, such as Aircraft Checklists and Tutorials, as well as a plethora of aircraft-specific dialogs, many of which contain huge blobs with embedded Nasal code that needs to just work.
It is simply not feasible to support/rewrite all these dialogs from scratch, the only feasible approach is parsing PUI/XML and translating this at run-time (no matter if that is done via Canvas, Phi or Qt5).
Does it work ?
Yes, in fact it does - even pretty well, despite the Canvas system not having been designed/developed with this particular use-case in mind.
Isn't this tons of work ?
Actually, the parser started out at 150 LOC, and is now ~300 LOC - in comparison to the dialogs that it can process, it is pretty straightforward-even trivial. Keep in mind that a typical PUI/XML dialog is several thousands lines in size, and often has tons of custom Nasal code in embedded sections.
In other words, literally "porting" an existing dialog manually to Phi/Qt5 (think airports.xml) is likely to be much more work than coming up with a parser, which can be refined over time to support other widgets/dialogs or layouting directives.
The parser in its current form was prototyped by single person over the course of just a few days of spare-time hacking, with most time spent creating/maintaining this wiki article. At no point in time did the FlightGear binary (fgfs) have to be rebuilt or any libraries changed/added.
Now, on contrast to this, look at the Qt5/Phi efforts: two of the most active FlightGear core developers (James and Torsten) are now both involved in developing, very much overlapping, but entirely optional components (separately) that will not even be executed normally, using two incompatible technology stacks.
So, the increasing interest in revamping the FlightGear UI is likely to drain resources from other core development efforts (just look at the track record, i.e. plethora of components, touched by both, James & Torsten prior to their interest in moderniing the UI).
With Canvas/Nasal, there is not much needed in terms of C++ level changes, it requires mainly Nasal code, i.e. roughly 300-500 lines of additional code could make PUI obsolete so that it can be phased out, without any core developers having to review C++ level changes.
What's wrong with Phi/Qt5 ?
Nothing. Both solutions just satisfy different use-cases, including use-cases that don't cover certain Canvas use-cases (e.g. rendering UI widgets to a MFD and vice versa), as well as supporting use-cases that Canvas cannot cover, i.e. providing a remote UI (Phi) or providing a rich UI experience without requiring OpenGL/FBO support, including tons of excellent tools and support/documentation (Qt5).
While a parser, similar to this one, could indeed be implemented for Phi/Qt5, the staggering amount of Nasal code is complicating matters for any external solution, and in the Qt5 case there still would be the issue of an "optional" dependency being required for an in-sim UI. While Phi would, for the time being, not be able to satisfy the in-sim use case, unless people are serious about adding a Webkit dependency to FlightGear, so that Phi can also serve as the built-in UI.
there is Phi if you can have a second window open or have a second screen - that works via html interface.
Or you can wait for Qt5 to come along, but that seems to have run into problems even with just the launcher, so it's hard to guess when it will be there. — Thorsten (Dec 16th, 2015). Re: AMD Radeon R9 – Massive text flickering again.
(powered by Instant-Cquotes) |
What are the pros & cons ?
The major drawback is obviously that it is using Nasal and Canvas, both could be said to be home-grown niche solutions only used/available in FlightGear.
So, this approach cannot realistically "compete" with HTML5/JavaScript (Phi) or Qt5. In particular, this comes to bear when creating/extending widgets, because for JavaScript/Qt5 there are literally thousands of existing solutions widely available. With Canvas, we typically have to create widgets from scratch, and also continue developing/maintaining those.
The other major issue is performance obviously, i.e. the parser being implemented in Nasal and using Canvas, does have a certain overhead.
Equally, Qt5/Phi would work for most hardware, while any Canvas-based solution would inevitably require FBO (RTT) support, i.e. a Canvas UI will only ever work for OSG/OpenGL windows.
The main advantage really is that a simple fallback UI can be provided in Nasal space, so that the underlying UI files can be directly used "as is" -so that these can be incrementally optimized, i.e. to identify huge custom Nasal blobs and come up with leaner dialogs during the transition to a more powerful UI solution using Qt5.
However, a Nasal/Canvas based approach is likely to just work for most people that are able to run FG (that is, if their GPU provides FBO support).
Equally, people interested in modern avionics (MFDs on airliners, bizjets, spacecraft) will need a way to render UI widgets to a MFD, and would also greatly benefit from being able to treat their MFDs as UI widgets, e.g. to show avionics in GUI dialogs - possibly as part of a tutorial, or post-flight evaluation. Such use-cases are only supported by true recursion, i.e. a widget being a Canvas and a Canvas being a widget.
Nasal overhead
it is true that there are PUI widgets that are not currently available via Canvas and which would need to be implemented, which would usually mean additional Nasal overhead (mainly timers and listeners).
However, that is not inevatibly the case - before the Canvas UI effort got declared dead, we talked about using Torsten's autopilot/property-rule subsystems to dynamically instantiate "property controllers" on-demand as part of the /canvas sub-tree (per Canvas or per element) to help update/animate Canvas elements - this would mean that the degree of added Nasal overhead would be kept to an absolute minimum that would only come to bear during initialiation of the corresponding C++ data structures, but afterwards everything would be pure C++ code The main thing that prevented us from adding more widgets at the time was the degree of artwork needed, which is why Tom ended up borrowing artwork from Ubuntu, and we contemplated using a JavaScript/HTML widget set using SVG artwork, so that we would not have to draw dozens of widgets ...[1]
the main loop/async point remains valid, but it would be possible for the Nasal/Canvas route to learn from Phi and use the same mongoose/httpd layer to run its property I/O and fgcommands asynchronously, at the mere cost of requiring the httpd service to be up and running for the UI to work in async mode. After all, all Nasal http I/O is already async anyway... The best way to reduce Nasal overhead is expose more SimGear/FlighGear subsystems to scripting/Canvas space, so that e.g. property rules and state machines can be used for updating/animating Canvas elements - at that point, there's really no added Nasal overhead at all. Equally, it would be possible to not just support event handlers in the form of Nasal callbacks, but just fgcommands - which work without Nasal entirely. The major benefit this would have is that Canvas is modern OpenGL/OSG code, and that it's integrated in an OSG--aware fashion, while also being property-tree enabled, which means that remote interaction scenarios can be easily implemented, which includes remote replication of UIs/MFDs etc.[2]
Moving GUI functionality back into C++
In addition, there is the issue that the existing UI scheme can be mostly maintained by fgdata developers, i.e. people don't need to be proficient in C++ to add/extend existing GUI dialogs:
Why should I care ?
Extending the Canvas system so that a PUI/XML parser can be provided in Nasal space can help address a number of long-standing issues. For instance, particularly people with AMD/ATI hardware are likely to greatly benefit from this effort, because ATI/AMD drivers are particularly prone to be incompatible with much of the existing legacy OpenGL code that exists today in FlightGear:
I suspect something related to the way that the built in bitmap font is built or rendered - maybe an interaction with PUI. — Richard (Dec 13th, 2015). Re: AMD Radeon R9 – Massive text flickering again.
(powered by Instant-Cquotes) |
it has to be something related to the OpenGL usage within FlightGear or a fault within the implementation in the driver — Richard (Dec 13th, 2015). Re: AMD Radeon R9 – Massive text flickering again.
(powered by Instant-Cquotes) |
It's most likely a problem with the PUI (the current GUI), and the plan is to discontinue using it. There are experimental canvas GUIs which don't show the flickering — Thorsten (Dec 16th, 2015). Re: AMD Radeon R9 – Massive text flickering again.
(powered by Instant-Cquotes) |
While FlightGear is using OpenSceneGraph these days, there is tons of legacy OpenGL code accumulated over the course of almost two decades of development. Unfortunately, this legacy code has to run within the fgfs main loop, and it is complicating matters for OpenSceneGraph/OpenGL - not just for buggy drivers, but also more lenient drivers that may provide much worse performance due to all this legacy OpenGL code.
While an alternate GUI could indeed be just as well provided by Phi/Qt5, that would not help address ATI/AMD issues existing elsewhere, such as compatibility with other legacy code (splash screen, 2D panels, HUD, MFDs).
Canvas can however help unify related features, so that these can be re-implemented and maintained in scripting space (fgdata), and legacy C++ code can be removed.
While it is true that future ATI/AMD driver updates may make drivers more lenient, extending the Canvas system will still be beneficial for other reasons, such as anybody interested in MFDs (airliner, biz jets).
Is this going to be committed ?
It depends. In its current form this is a proof-of-concept, and it would definitely need some TLC to be ready for prime time. Equally, getting more people involved would be helpful, but most contributors are hesitant to get involved in something like this due to the Phi/Qt5 efforts, which could quickly make this whole thing obsolete.
As of mid 2016, the general consensus seems that if it is true that this could help deprecating PUI, probably nobody would object against removing PUI and replace it by CUI, keeping the old syntax. At least for a while. Dropping PUI would be a huge step forward [3], i.e. the status is that since there has not been a fundamental objection against this on the mailing list and several people have said they can live with the option as long as it remains an option, ThorstenR offered to commit pui2canvas to FGData in the form of a Nasal module that gets loaded 'on demand' dependent on a set property (similar to AW). We'll then see whether people find it more widely useful etc. and whether FG more generally relies on it for fixing certain PUI bugs/issues or not.[4]
As far as I am concerned, most of the people that I have been in touch with about this, are very much hesitant to adopt, let alone get involved in, this - because the ongoing Phi/Qt5 efforts are considered to possibly make the Nasal/Canvas based parser obsolete sooner or later.
I have yet to see any definite statement about the future of Qt5 adoption in FlightGear, and if/how this will affect overlapping efforts that are considered "dead-ends" by other core developers. Thus, for understandable reasons, I am not prioritiing the whole thing currently. But should the situation regarding segfaults related to the Qt5 integration layer in FlightGear not improve anytime soon, we may want to revisit the whole parser approach. In the meantime, the wiki article contains all the info, pointers and code snippets to extend this, should the need arise.— Hooray (Dec 16th, 2015). Re: AMD Radeon R9 – Massive text flickering again.
(powered by Instant-Cquotes) |
In summary, the pui2canvas effort does not need to be used, supported, endorsed or even just reviewed/committed by any core developers, because it's just a tiny, and optional, Nasal/Canvas module that can be added to fgdata by fgdata contributors - or even just distributed as a self-contained Nasal module that can be dropped into $FG_HOME/Nasal - and it just works, so there really is no "disconnect" or "communication barrier" here - that debate took place years ago, and James himself came up with most of the ideas related to this, we're really just implementing his original proposal - regardless of his Qt5 work, and regardless of his current priorities - he not only came up with these ideas, but approved them and encouraged this work, just like he encouraged the development of the Canvas based aircraft center[5]
How can this be helpful to Phi/Qt5 ?
Modernizing the UI is long overdue, but our existing GUI dialogs are frankly a huge mess. It is far from trivial to just "port" the UI to a different framework.
Realistically, we cannot easily phase out PUI anytime soon, because we need to maintain parity with existing features support by the combination of PUI/XML and Nasal.
The main challenge is the excessive amount of embedded Nasal code in dialogs, as well as property/fgcommand-based hacks to work around PUI limitations (e.g. tabs implemented as buttons hiding widgets using SGConditions).
Thus, writing/maintaining a parser is the only feasible thing to do, and it would allow us to identify problematic dialogs, and incrementally rewrite those to reduce the amount of Nasal code and "widget hacks", so that dialogs would later become much leaner over time, which would make it much easier for Qt5/Phi to support such dialogs.
In the meantime, the parser could help provide an integrated non-PUI solution while transitioning to a new scheme, i.e. a "fallback" scheme that does not require tons of efforts/dependencies.
How is this useful regardless of the UI ?
It's a pretty awesome way to exercise, stress-test and benchmark the Canvas engine, especially in conjunction with running the Built-in Profiler-which is to say that Canvas engine may be further optimized based on the findings made through this parser, which may eventually translate into better performance for other Canvas-related features (e.g. MFDs).
Furthermore, a few core developers have expressed an interest in phasing out other modules using legacy OpenGL code, e.g. the HUD and 2D panels - which could be equally parsed/re-implemented using a Nasal/Canvas approach.
regardless of the outcome of this, this work is fairly self-contained and instructional for other reasons, i.e. helping unify the 2D rendering back-end (2D panels, HUD, splash screen, osgText animation) for better OpenGL compatibility, as per James' original proposal: Unifying the 2D rendering backend via canvas In other words, the whole approach could be adapted to also parse legacy 2D panels and HUD XML files to use modern OSG-aware OpenGL code, and any improvements to the lower-level C++ code would obviously not be specific to just the UI use-case.[6]
Development
In general, people familiar with basic Nasal skills should have no problems picking up the concepts introduced here. In particular, people will want to read up on:
Property Processing
- XML parsing/processing using io.read_properties() fgdata/Nasal/io.nas
- fgdata/Nasal/props.nas (Nasal module wrapping the Property Tree APIs)
PLIB PUI
- Howto:Editing and creating GUI dialogs in FlightGear
- $FG_ROOT/Docs/README.gui
- Bindings
- $FG_ROOT/Docs/README.layout (flightgear/src/GUI/layout.cxx and flightgear/src/GUI/layout-props.cxx)
- fgdata/gui/dialogs (folder containing existing PUI/XML dialogs)
Canvas Widgets
- flightgear/src/GUI/FGPUIDialog.cxx (line 853) (the PUI widget factory)
- Canvas
- Canvas Snippets
- Howto:Creating a Canvas GUI Widget (recently received a major update by Red Leader)
- fgdata/Nasal/canvas/gui/Widget.nas (this is the baseclass that needs be inherited from for each widget)
- fgdata/Nasal/canvas/widgets (and fgdata/Nasal/canvas/dialogs for existing examples)
- Canvas Event Handling (to map mouse/keyboard events to the Canvas system)
- Canvas Layout System (this will be needed to map PUI layouts to Canvas layouts, e.g. a table consists of rows and columns)
Nasal Internals
- Nasal APIs like
call()
,compile()
,bind()
(specifically useful to deal with embedded Nasal sections, e.g. load/close sections)
So far, this represents roughly 1-2 hours of hacking, with most of the time spent documenting the process for people wanting to adopt/extend the concept.
The parser will be extended with a focus on:
- supporting localized dialogs using a propertylist xml overlay from $FG_ROOT/Translations
- identifying extensive use of Nasal, to introduce helper widgets/tags (e.g. a multiplayer pilot list)
- supporting versioning (e.g. to easily be able to extend the underlying PUI/XML subset)
- benchmarking and stress-testing different aspects of Canvas (Nasal, Property Tree, Canvas)
- supporting dynamic PUI dialogs and the corresponding fgcommands (which may need to be patched at run-time)
- supporting Nasal-based (procedural) PUI dialogs (think checklists/tutorials)
- identifying opportunities for optimizing Canvas further
Roadmap
- support
<live>
properties - implement support for table layouts
- map alignments to layouts and widgets Not done
- extend/fix up the parser for any dialogs that are breaking it for the time being
- implement fgcommand patching for PUI related fgcommands
- adapt the parser to work for aircraft dialogs, i.e those outside $FG_ROOT/gui/dialogs (easy) Not done
- implement missing widgets (radio, combo, slider/dial, various lists, e.g. file-picker) Not done
- review/implement use of
/sim/gui/dialogs
idiom - implement support for conditions Not done
- stress-test dialogs with embedded Nasal code (make bind/compile work correctly) Not done
- fgcommand/script blocks must be bound to the dialog's nasal/open block
- implement support for the
<canvas>
tag - review gui.nas and screen.nas uses of
dialog-show
,dialog-new
etcgui.showHelpDialog()
gui.propertyBorwser()
- seapport
- wildfire
- multiplayer/pilot list
- performance monitor
- review Tutorials and Aircraft Checklists Not done
- joystick config dialog
- the loglist widget APIs needs to be exposed via cppbind so that the widget can be recreated
- implement support for the
<color>
tag and a pui-to-canvas font mapping Not done - implement styling/theming (not sure...?)
Milestones
- come up with a io.read_properties() based parser framework to process PUI/XML files Done
- create a widget factory that maps common PUI/XML tags to existing Canvas widgets Done
- begin supporting PUI vbox/hbox layouts by mapping those to Canvas layouts Done
- review what's needed for supporting a minimal subset of PUI/XML and widgets Done
- come up with missing widgets
Issues
Motivation
I'm worried that we end up with a situation in which developers on the latest Linux distros happily write code utiliing the newest of the newest and people on systems 2-3 years behind (like myself for reasons elaborated elsewhere) have to acquire random stuff to make a particular aircraft / feature work despite being able to compile and having a running FG.
Basically I want FG to work out of the box once it's compiled, and talk about a huge collection of libraries to be utilized worries me. — Thorsten (Dec 31st, 2015). Re: FGPython an propose for Python as an nasal alternative.
(powered by Instant-Cquotes) |
There is 100% agreement that FlightGear will never require Qt, the only question is if the GUI requires Qt — James Turner (Oct 11th, 2015). [Flightgear-devel] GUI questions (again).
(powered by Instant-Cquotes) |
note that Qt is supposed to be optional - so what do we do if Qt isn't added at compile time? — Thorsten Renk (Oct 13th, 2015). Re: [Flightgear-devel] GUI questions (again).
(powered by Instant-Cquotes) |
making Qt5 an optional dependency - de-facto it's not really as there's no alternative provided and very few people will write their own or have a two-monitor setup and use Phi. — Thorsten (Dec 31st, 2015). Re: FGPython an propose for Python as an nasal alternative.
(powered by Instant-Cquotes) |
Again, there is /no/ problem at all making a Qt runtime that supports the current dialog XML syntax, with some enhancements to make richer widgets work. My problem is that doesn’t solve the ‘non-Qt’ setup, and I agree [...] that this use case is worth worrying about for aircraft dialogs. — James Turner (Oct 13th, 2015). Re: [Flightgear-devel] GUI questions (again).
(powered by Instant-Cquotes) |
building a QT based GUI is not in any way meant to obstruct the further development of a Canvas based GUI. — Durk Talsma (Jun 11th, 2015). Re: [Flightgear-devel] Policy Document and V4.X Roadmap.
(powered by Instant-Cquotes) |
the design goal of Phi was never to be a fully featured replacement for the PUI GUI and the windowing system — Edward d'Auvergne (Oct 13th, 2015). Re: [Flightgear-devel] GUI questions (again).
(powered by Instant-Cquotes) |
I agree that separate user interface windows would be nice in several interesting use cases, but I don't think the default single-screen case is one of them. — Tim Moore (Jul 25th, 2012). Re: [Flightgear-devel] Switching from PUI to osgWidget.
(powered by Instant-Cquotes) |
I doubt that using Phi as the interface to control a Space Shuttle will ever take off, and that a WebUI would ever be comparable to a true GUI. — Edward d'Auvergne (Oct 13th, 2015). Re: [Flightgear-devel] GUI questions (again).
(powered by Instant-Cquotes) |
Personally I find a separate browser instance rather ugly and unfortunate - I want a single FlightGear window running fullscreen and having to switch desktops to use UI is not good for me. — James Turner (Oct 12th, 2015). Re: [Flightgear-devel] GUI questions (again).
(powered by Instant-Cquotes) |
That is a valid point and for this we need an embedded UI. — Torsten Dreyer (Oct 12th, 2015). Re: [Flightgear-devel] GUI questions (again).
(powered by Instant-Cquotes) |
I'm most worried about aircraft-side dialogs in this scenario. All sim-specific dialogs can be taken care of by the respective GUI creators, and for instance it doesn't really matter how the detailed implementation of, say the weather selection, is in Phi vs. Qt as long as they do the same thing. But aircraft-specific dialogs are not under the control of the GUI creators. — Thorsten Renk (Oct 12th, 2015). Re: [Flightgear-devel] GUI questions (again).
(powered by Instant-Cquotes) |
even if we manage to come up with a way to define dialogs such that they can be parsed and executed in both Phi and Qt, someone would have to do this for possibly unmaintained aircraft as well and bring them up to speed. Makes a lite GUI which shows an unassuming version of the current xml definitions somewhat more attractive perhaps. — Thorsten Renk (Oct 12th, 2015). Re: [Flightgear-devel] GUI questions (again).
(powered by Instant-Cquotes) |
If we have to provide customied dialog definitions for every possible GUI module, it's not going to make me happy because dialog creation isn't my favorite activity. At which point an aircraft-side completely canvas-based GUI starts to look like an attractive option because it's going to run whatever module takes care of the GUI otherwise. — Thorsten Renk (Oct 13th, 2015). Re: [Flightgear-devel] GUI questions (again).
(powered by Instant-Cquotes) |
Any in-sim GUI using Qt will exist in parallel to the PLIB/PUI one for at least one major release, although this may mean maintaining two sets of dialogs, unless the XML-translation-tool is good enough that we can use it at run-time for aircraft dialogs. — James Turner (Oct 11th, 2015). [Flightgear-devel] GUI questions (again).
(powered by Instant-Cquotes) |
One concern I have though, from the discussions over the last year, is that the PUI->Qt migration plan is not a direct one-to-one migration. I.e. to have a main Qt window providing the main FlightGear window by containing a top menu bar followed by a QtWindow GL widget underneath, in which the FG main execution loop is run as a thread from within the Qt main loop. My impression was that the GUI will be a secondary window or dialog that you have to switch to. But one problem I see with this design is that all modern GUI toolkits (Qt, GTK, metro, Cacoa, Carbon, etc.) suffer from racing and segfaulting issues if the GUI main thread or main loop/event handler is not thread number 1. — Edward d'Auvergne (Oct 13th, 2015). Re: [Flightgear-devel] GUI questions (again).
(powered by Instant-Cquotes) |
In comparison Canvas GUI, even in its current state is perhaps easier to handle for many purposes. |
Given the state of plans as outlined on the mailing list, I think this would be most useful to have, if only for a transition period. |
The whole point of this is to provide a sane migration path for our existing PUI/PropertyList-based XML dialogs. Regardless of the GUI solution that will be in use 5+ years from now, we will need a way to deal with our existing UI once PUI is gone.
It has been made clear by the Qt5/Phi developers that the large share of custom Nasal code is complicating porting efforts.
Ironically, the most straightforward dialogs to "port" are exactly those using "just" PUI markup, without any Nasal code at all.
However, none of the discussions revolving around Qt5/Phi have addressed the fact that a large share of FlightGear dialogs that exist today, are in fact using Nasal for creating semi-dynamic, or even procedural dialogs.
This can be easily seen by running cd $FG_ROOT/gui/dialogs && grep -nr cmdarg *
as well as cd $FG_ROOT/Nasal && grep -nr dialog-show *
. In other words, these dialogs cannot be easily converted to a different UI framework short of rewriting them from scratch, or introducing support for embedded Nasal (Qt5)/JavaScript (Phi) code, too.
Looking at the staggering amounts of Nasal code in existing dialog files (referring just to $FG_ROOT), it is neither feasible to automatically port all those dialogs, nor to port them manually. And even if someone should volunteer their time to do so, this would very likely take at least 2-3 release cycles, or introduce major regressions in the meantime.
Background
At the end of 2015, it became clear that a number of core developers were contemplating to phase out our integrated legacy GUI engine (PLIB PUI) in favor of external solutions using Qt5 and Phi (browser-based).
Subsequently, it was also made clear that there were plans being discussed not to provide any built-in GUI solution at all, and to phase out existing PUI dialogs, too.
According to discussions in 10/2015, there was no plan in place to deal with existing dialogs, short of rewriting them from scratch or converting them to the new GUI scheme using a scripted converter.
Which means that the primary means for interacting with the simulator would then either be a Qt5-enabled build of FlightGear, or a web-browser using Phi.
It is foreseeable that given the different technology stacks involved in the Qt5/Phi efforts (but also the added maintenance overhead due to having to maintain separate front-ends), FlightGear UI capabilities will increasingly begin to diverge over time, with certain features and use-cases being possibly only supported by one of the two main alternatives at some point.
Just like the functionality of the plethora of existing external FlightGear GUI front-ends is diverging strongly.
However, Canvas -our hardware-accelerated 2D rendering API- is there to stay:
There's no serious 'no canvas' mode forseen as far as I know - so 'no canvas' pretty much is 'no rendering'. — Thorsten (Nov 17th, 2015). Re: version checking for matching fgfs and -set.xml files.
(powered by Instant-Cquotes) |
So it makes sense to explore using Canvas for providing a Nasal/Canvas parser than can deal with existing PUI dialogs (without having to modify those) to procedurally interpret them and turn those into Canvas Widgets, and GUI dialogs - using modern OSG code, that is directly integrated with the scene graph.
The intention is not to conflict or compete with the Qt5/Phi efforts, but to provide an alternative for those end-users, and aircraft developers, who may still want to run an internal UI, without having to link against the Qt5 dependency, and without having to use a browser to interact with the simulator.
For the time being, it isn't clear if the approach described and used below will be too well embraced, or even just supported - let alone committed to the base package. So this serves primarily as a reference for those wanting to use existing dialog files using Canvas.
The approach detailed below is sufficiently losely coupled, so that it does not depend on core developers having to review/approve of this technique. It provides a straightforward mechanism for people wanting to get rid of PUI, without necessarily having to use an external GUI like Phi or Qt5.
Original Discussion
FlightGear uses a gui widget set that is implemented on top of OpenGL. This has many advantages from a portability standpoint and from the standpoint of integrating with window systems. "Pui" doesn't have every feature under the sun, but it was never meant to. It's relatively small, lean, mean, and written on top of OpenGL which makes life *much* easier for us. — Curtis Olson (Jan 29th, 2010). Re: [Flightgear-devel] GUI dialogs suck.
(powered by Instant-Cquotes) |
If you know something about gui systems, something about portability of code across all our supported platforms, and something about flightgear. Then post a proposal for a change. Better yet, post patches with a new gui system that doesn't suck, runs efficiently, supports all platforms, integrates cleanly with FlightGear, doesn't add a nightmare of new library dependencies, isn't chock full of bugs, does everything the current system does, and does everything you think a gui system should do, etc. etc. — Curtis Olson (Jan 29th, 2010). Re: [Flightgear-devel] GUI dialogs suck.
(powered by Instant-Cquotes) |
the current solution based on PUI really needs to go away for plenty reasons, especially the horrible OpenGL code it runs. — James Turner (Oct 11th, 2015). [Flightgear-devel] GUI questions (again).
(powered by Instant-Cquotes) |
Using the Canvas also for the GUI would give us the advantage of a unified rendering backend for any type of GUI/text rendering and also the ability to use the same widgets everywhere - eg. use them also inside aircrafts for CDU GUIs or other displays... — Thomas Geymayer (Jul 24th, 2012). Re: [Flightgear-devel] Switching from PUI to osgWidget.
(powered by Instant-Cquotes) |
Keeping the current XML format is really a requirement - improving that format, especially handling of layouts, is another task, but there's too many existing dialogs to really break compatibility. — James Turner (Jul 24th, 2012). Re: [Flightgear-devel] Switching from PUI to osgWidget.
(powered by Instant-Cquotes) |
of course it should be possible to support the current XML syntax using any toolkit, it just's a question of how hard it is. — James Turner (Jul 24th, 2012). Re: [Flightgear-devel] Switching from PUI to osgWidget.
(powered by Instant-Cquotes) |
so long as you keep XML compatibility most of the current Nasal interaction with the GUI will work. There is great scope to make /better/ Nasal APIs for items such as combo-boxes and pickers, especially ICAO and radio frequency pickers, but that's all 'improving the GUI' work than can happen once we've ditched PLIB and have something hackable. — James Turner (Jul 24th, 2012). Re: [Flightgear-devel] Switching from PUI to osgWidget.
(powered by Instant-Cquotes) |
The long term idea is to eventually port some other 2D elements to this backend (eg the HUD and 2D panels) so they use OSG (and osgText) natively, and hence reduce the amount of C++ code we have for these jobs. (And increase our chances of working with never OpenGL versions that forbid old style GL calls) — James Turner (May 30th, 2012). Re: [Flightgear-devel] Canvas (2D drawing API) - Testing needed.
(powered by Instant-Cquotes) |
I'd also like to see / understand how we manage XML / property-list file processing in a nice way, to support the various formats we want to create canvas elements from - GUI dialogs, 2D panels and HUDs. — James Turner (Jul 25th, 2012). Re: [Flightgear-devel] Switching from PUI to osgWidget.
(powered by Instant-Cquotes) |
In particular I'm interested to see what aspects of XML -> building canvas elements are common between 2D panels, the GUI dialogs and HUD code. — James Turner (Jul 25th, 2012). Re: [Flightgear-devel] Switching from PUI to osgWidget.
(powered by Instant-Cquotes) |
I'm even more convinced now that we should move the 2D panel and HUD rendering over to this approach, since that would get rid of all the legacy OpenGL code besides the GUI. — James Turner (Jul 24th, 2012). Re: [Flightgear-devel] Switching from PUI to osgWidget.
(powered by Instant-Cquotes) |
We could for example just add some more parseXXX functions (like parsesvg) which parse a dialog/hud/whathever file and create a canvas from it. So we would just have to modify eg. the show-dialog command to create a canvas and call the parser. — Thomas Geymayer (Jul 25th, 2012). Re: [Flightgear-devel] Switching from PUI to osgWidget.
(powered by Instant-Cquotes) |
We need to keep the existing way of specifying GUI files via XML - it's a nice, declarative way of building the dialogs. Switching to an imperative system would be a step backwards. I do like the idea of a gui/widget/widgetname.nas structure so we can easily create a factory function and hack / add widgets. — James Turner (Jul 26th, 2012). Re: [Flightgear-devel] Switching from PUI to osgWidget.
(powered by Instant-Cquotes) |
I didn't mean to replace the existing way, but instead only changing the implementation to use the Canvas system in Nasal space. Just mapping the existing dialog-show command to a Nasal function which creates a window and parses a GUI xml file. This wouldn't change anything on how dialogs are specified and shown. — Thomas Geymayer (Jul 26th, 2012). Re: [Flightgear-devel] Switching from PUI to osgWidget.
(powered by Instant-Cquotes) |
I think the best place would be doing it with Nasal, as there is already all the information needed available. C++ doesn't know anything about widgets... — Thomas Geymayer (Jul 26th, 2012). Re: [Flightgear-devel] Switching from PUI to osgWidget.
(powered by Instant-Cquotes) |
As you say, so long as we're going through show-dialog it should be fine. — James Turner (Jul 26th, 2012). Re: [Flightgear-devel] Switching from PUI to osgWidget.
(powered by Instant-Cquotes) |
Of course, it’s great if Canvas is as the point that we can actually consider such things, and hopefully the actual arrangement of screens + widgets is easy to evaluate and adjust. — James Turner (Nov 19th, 2014). Re: [Flightgear-devel] Features for 3.4 please.
(powered by Instant-Cquotes) |
I don't want to spend too much work in getting PUI and Canvas dialogs working at the same time. The layout and widget systems is now maturing, so I think after 3.2 I can start with porting PUI. Just one major component is missing, namely keyboard input and the according input focus. I guess this will take one or two more releases. — Thomas Geymayer (Jun 13th, 2014). Re: [Flightgear-devel] Notes on Aircraft Center....
(powered by Instant-Cquotes) |
Implementation
Our existing PUI/XML files can be broadly classified to support the following types of tags:
- layouting related (group, frame, layout, hbox, vbox etc)
- widgets - (checkbox, combo, text etc) these are actual PUI widgets with their associated properties and attributes (as per README.gui)
- bindings - these are "actions" that are triggered
- conditions (used to conditionally hide/show widgets)
- embedded scripts - used for pretty much everything else, e.g. to manipulate the dialog tree before it is shown
- styling/theming related (not discussed here)
In general, the parser is a foreach-loop that uses the props.nas APIs to get a list of XML tags and it will recursively check a map (Nasal hash) with PUI tags and then invoke the callback registered there - as the "context", it will pass the canvas/layout/group etc. to be modified/updated.
So, adding support for a new tag would require navigating to the "WidgetFactory" hash, which contains key/value pairs - each key is a valid PUI tag, and the value is a callback that will be invoked once the tag is encountered in the xml dialog.
Some of the more tricky things are "bindings", fgcommands and layouting (table layouts), and conditions - the latter need to be mapped to the props.nas stuff.
Approach
Note This is using exactly the approach originally discussed on the devel mailing list, and agreed to be used by the core developers involved in that discussion. |
All PUI/XML dialogs are currently handled by a handful of fgcommands, it has meanwhile become possible to also register/override fgcommands via Nasal code. Which in turn makes it possible to prototype new fgcommands in Nasal space, but also to completely override hard-coded fgcommands once the prototype works sufficiently well (at that point, there would be no patching of XML files required anymore).
For prototyping purposes, we will be registering a custom fgcommand in Nasal using the addcommand()
API and patch menubar.xml to change dialog-show
fgcommands (as per $FG_ROOT/Docs/README.commands) into canvas-dialog-show
for each dialog whose widgets are supported/ported.
For starters, we will focus on simple dialogs, such as fgdata/gui/dialogs/about.xml and fgdata/gui/dialogs/exit.xml — however, the framework will be sufficiently generic to also support other widgets/dialogs (as per README.gui).
Once a sufficiently-large subset of PUI widgets are supported by the parser, it would also be possible to override the dialog-show
command directly to circumvent the underlying PUI C++ code, which could then also be disabled, phased out/removed in its entirety.
In theory, the same approach could be used for writing Nasal parsers that turn our existing HUD and 2D panel files into their Canvas equivalents, so that we can get rid of other legacy OpenGL code in the process, too.
Challenges
I have played a little with a PUI-XML interpreter for Phi. It worked pretty well for simple and basic dialogs. For complex dialogs like the weather dialog, this approach failed completely as there is too much Nasal involved which I can't (read: don't want to) port over to javascript. My proposal is to drop all existing PUI dialogs and recreate them from scratch. — Torsten Dreyer (Oct 12th, 2015). Re: [Flightgear-devel] GUI questions (again).
(powered by Instant-Cquotes) |
The staggering amount of custom Nasal code sections (read:hacks) in some dialog files is astonishing and a challenge not just for the Phi/Qt5 efforts, but also not exactly thrilling for people wanting to interpret dialogs using Nasal/Canvas.
Equally, many PUI Widgets can be trivially ported/re-implemented using Nasal/Canvas, while others are more sophisticated - e.g. the airport-list and property-list widgets. Also, a number of PUI widgets are unfortunately not even documented in README.gui, but still used by some dialogs.
While that does apply to the <canvas> tag, too - that is fortunately straightforward to support using a Nasal/Canvas parser.
Otherwise, it is obviously true that this Nasal/Canvas-based approach will add to the Property Tree/Nasal overhead. However, the number of PUI widgets is comparatively small. So supporting a safe subset of PUI, and adapting a few existing dialogs accordingly should be relatively straightforward.
Nevertheless, it would make sense to modify the parser so that it can analyse each dialog and dump some stats to the console, e.g. to identify dialogs with lots of embedded Nasal blocks (or conditionally-shown widgets), which is typically the case for dialogs that are working around PUI limitations. Which means that it would make sense to identify the underlying use-case/scenario (such as missing support for tabs) and introduce custom tags that are mapped to a Canvas equivalent, to incrementally phase out huge Nasal sections in favor of custom widgets that are implemented elsewhere, e.g. a pilot-list widget (and which could also be wrapped much more easily by Phi/Qt5).
Ideally, the parser would dump a number of metrics to the console, such as:
- number of layouts created
- number of widgets
- number of Nasal callbacks (timers/listeners)
- size of embedded code blocks
First, we will patch menubar.xml for the dialog that we want to display using Canvas (this could be done during startup to the in-memory copy of menubar.xml):
diff --git a/gui/menubar.xml b/gui/menubar.xml
index e0a1bf5..6050429 100644
--- a/gui/menubar.xml
+++ b/gui/menubar.xml
@@ -89,7 +89,7 @@
<name>exit</name>
<key>Esc</key>
<binding>
- <command>dialog-show</command>
+ <command>canvas-dialog-show</command>
<dialog-name>exit</dialog-name>
</binding>
</item>
This will ensure that the corresponding dialog will be passed to a custom fgcommand.
Next, we need to register a new fgcommand that will be invoked:
diff --git a/Nasal/canvas/gui/../pui-wrapper.nas b/Nasal/canvas/gui/../pui-wrapper.nas
new file mode 100644
index 0000000..009d172
--- /dev/null
+++ b/Nasal/canvas/gui/../pui-wrapper.nas
@@ -0,0 +1,9 @@
+var PUI = {
+showDialog: func(node) {
+ print("Custom PUI/Canvas translator");
+ props.dump(node);
+},
+
+};
+
+addcommand("canvas-dialog-show", PUI.showDialog);
This adds a new file to $FG_ROOT/Nasal/canvas, named pui-wrapper.nas, which will be loaded into the canvas namespace. Next, it declares a sub-namespace named PUI, and implements a showDialog() function that merely dumps the fgcommand arguments to the console using the props.nas helpers.
Finally, it uses the addcommand API to register this function as a new fgcommand.
In its current form, this is not yet particularly useful, but it demonstrates how to map the handling of legacy GUI dialogs to the Canvas system. For now, the output will look like this:
Custom PUI/Canvas translator binding[9] {NONE} = nil binding[9]/command {UNSPECIFIED} = canvas-dialog-show binding[9]/dialog-name {UNSPECIFIED} = exit
As can be seen, each fgcommand receives a full Property Tree node containing its arguments (command, dialog-name in this case).
Parsing the dialog node
First of all, we will need to parse the dialog-name
property and append the .xml suffix so that we can build a file name and load the dialog file from the base package:
diff --git a/Nasal/canvas/gui/../pui-wrapper.nas b/Nasal/canvas/gui/../pui-wrapper.nas
new file mode 100644
index 0000000..26703b2
--- /dev/null
+++ b/Nasal/canvas/gui/../pui-wrapper.nas
@@ -0,0 +1,19 @@
+var PUI = {
+showDialog: func(node) {
+ var filename = node.getNode('dialog-name').getValue() or die("Missing file name");
+ # append .xml suffix:
+ filename ~= ".xml";
+
+ # build path relative to $FG_ROOT
+ var path = getprop("/sim/fg-root")~"/gui/dialogs/"~filename;
+
+ print("Custom PUI/Canvas translator for dialog:", filename);
+ # load the dialog from the base package
+ var dlgNode = io.read_properties(path);
+ # dump it to the console
+ props.dump(dlgNode);
+},
+
+};
+
+addcommand("canvas-dialog-show", PUI.showDialog);
We can now also easily turn the dlgNode into a hash using the getValues() API provided by props.nas, and use that to e.g. read out the name/title of the dialog and use that to actually create a corresponding Canvas dialog:
diff --git a/Nasal/canvas/gui/../pui-wrapper.nas b/Nasal/canvas/gui/../pui-wrapper.nas
new file mode 100644
--- /dev/null
+++ b/Nasal/canvas/gui/../pui-wrapper.nas
@@ -0,0 +1,24 @@
+# beginning of canvas.PUI namespace
+var PUI = {
+showDialog: func(node) {
+ var filename = node.getNode('dialog-name').getValue() or die("Missing file name");
+ # append .xml suffix:
+ filename ~= ".xml";
+
+ # build path relative to $FG_ROOT
+ var path = getprop("/sim/fg-root")~"/gui/dialogs/"~filename;
+
+ print("Custom PUI/Canvas translator for dialog:", filename);
+ # load the dialog from the base package
+ var dlgNode = io.read_properties(path);
+ # dump it to the console
+ # props.dump(dlgNode);
+
+ dlgHash = dlgNode.getValues();
+ var window = canvas.Window.new([200,150],"dialog")
+ .set('title',dlgHash.name);
+}, # showDialog
+
+}; # end of PUI namespace
+
+addcommand("canvas-dialog-show", PUI.showDialog);
Parsing widgets
Next, we need to take a look at exit.xml to see what widgets/tags are used there, so that these can be incrementally added to our custom, Canvas-based, dialog handler:
<?xml version="1.0"?>
<PropertyList>
<name>exit</name>
<modal>false</modal>
<layout>vbox</layout>
<text>
<label>Exit FlightGear?</label>
</text>
<group>
<layout>hbox</layout>
<halign>fill</halign>
<default-padding>10</default-padding>
<empty><stretch>true</stretch></empty>
<button>
<legend>Exit</legend>
<default>true</default>
<equal>true</equal>
<binding>
<command>exit</command>
</binding>
<binding>
<command>dialog-close</command>
</binding>
</button>
<empty><stretch>true</stretch></empty>
<button>
<legend>Cancel</legend>
<equal>true</equal>
<key>Esc</key>
<binding>
<command>dialog-close</command>
</binding>
</button>
<empty><stretch>true</stretch></empty>
</group>
</PropertyList>
Coming up with a widget factory
Processing tags is pretty straightforward, as is mapping them to those Canvas widgets that exist already, as can be seen below.
Basically, this is just a lookup table (in the form of a hash) that contains keys (PUI widget names) that are mapped to callbacks for instantiating the corresponding Canvas Widgets.
Our parser then merely needs to process the PropertyNode object and traverse all child nodes to look for supported tags, and call the corresponding callback then, which will be responsible for coming up with a Canvas equivalent of the corresponding PUI widget.
For the time being, the majority of these callbacks are just empty placeholders, because we still need to populate them - but the underlying idea can be seen in the implementations of the button/text and label widgets respectively.
Supporting Recursion
We also need to make sure that our parser can recursively call the factory helpers, i.e. for dealing with groups/frames and other layouting directives that may have arbitrary widgets as child nodes, including other groups/frames.
In this case, we need to register a group/frame helper that recursively processes any other widgets it encounters, to ensure that the buttons are properly aligned:
Layouting
The main layouting mechanism supported by Canvas are the vbox/hbox layouts, so there is currently no direct support for the table layout supported by PUI. Obviously, we can still implement a table layout using a vector of vbox/hbox layouts. Equally, layouting using absolute positions must be supported (i.e. translations).
Supporting Bindings
Next, we need to make sure that we also support bindings, i.e., commands that may be triggered, for example, when clicking on a button. An arbitrary number of bindings can be added, so this needs to be be a vector of callbacks in Nasal. Bindings come in two forms: Fgcommands and Nasal scripts. Both can be easily supported.
setupCommandBinding: func(node){
var command = node.getNode("binding/command").getValue();
if (command == "nasal") return PUI.setupScriptBinding(node);
# TODO: fgcommands relating to PUI must be dynamically patched/overridden (e.g. dialog-close)
return func(){
fgcommand(command, node);
};
},
setupScriptBinding: func(node) {
var code = node.getNode("binding/script").getValue();
var codeObj = compile(code, "a binding"); # FIXME: should use file name, line number, binding name
return codeObj;
},
# returns a vector of bindings (fgcommands and nasal script)
setupBindings: func(node) {
var callbacks = [];
foreach(var binding; node.getChildren("binding")) {
append(callbacks, PUI.setupCommandBinding(node));
}
return callbacks;
},
There is a handful of fgcommands that are PUI related, those need to be dynamically patched to be mapped to their Canvas equivalents. Specifically, these are:
dialog-apply
dialog-update
dialog-close
(trivial}dialog-show
(trivial)gui-redraw
[7]
fgdata/gui/dialogs/airports.xml
<input>
<name>input</name>
<pref-width>120</pref-width>
<halign>fill</halign>
<stretch>true</stretch>
<property>/sim/gui/dialogs/airports/list</property>
<binding>
<command>dialog-apply</command>
<object-name>input</object-name>
</binding>
<binding>
<command>dialog-update</command>
<object-name>airport-list</object-name>
</binding>
</input>
With dialog-apply and dialog-update being the most important ones, because they are using an object-name child to address other widgets on the same dialog, i.e. quoting $FG_ROOT/Docs/README.gui:
airport-list ------------ like "list", but fills the list automatically with all airports known to FlightGear. Calls bindings on airport selection and returns the selected entry in <property> on dialog-apply. Interprets <property> as search term on dialog-update.
/sim/gui/dialogs
Conditions
PUI/XML markup is using a few helpers (via SGCondition, see $FG_ROOT/Docs/README.condtions) to decide if a widget is to be shown/hidden, this needs to be wrapped using the corresponding APIs in props.nas, e.g. compileCondition()
At the Canvas level, this would merely need to be wrapped to call the .hide() .show() methods respectively for the corresponding Canvas widget/group, so is straightforward to do.
Embedded Nasal
(open/close tags may be used to manipulate the dialog tree, so take precedence, i.e. must be processed first, cmdarg() must be overridden via bind, fgcommand() must be wrapped too, to ensure that GUI related fgcommands (dialog-close) can be patched in bindings.)
Nasal code embedded in nasal/open sections needs to be executed first, with cmdarg() wrapped to return the props.Node object, so that the tree can be manipulated - i.e. something along the lines of:
##
# handles non-widget aspects, i.e. dialog header and embedded Nasal (must take precedence!)
#
MetaTags: {
'nasal-open': func(node) {
print("nasal open!");
var ns = {cmdarg: func node};
var source=node.getNode("nasal/open").getValue();
var code=compile(source, node.getNode("name").getValue() ~ ".xml: (nasal/open)");
call(code,[],nil,ns,var err=[]);
}, # nasal open tag
'nasal-close': func(node) {
print("nasal close!");
}, # nasal close tag
}; # MetaTags
# ...
if(dlgNode.getNode("nasal/open")!=nil)
PUI.MetaTags["nasal-open"] (dlgNode);
Embedded Canvas
Some dialogs making use of embedded CanvasWidget widgets are:
- fgdata/gui/dialogs/airports.xml
- fgdata/gui/dialogs/map-canvas.xml
- Howto:Prototyping_a_new_NavDisplay_Style#Code
We will need to add a raster image child to the layout, and execute the nasal/load section, while overriding cmdarg() to return .getPath() accordingly. The only dialog using this is the airports.xml file
So we only need to populate our factory/canvas stub to read out the relevant tags, create a new Canvas, wrap the cmdarg() arg API and add the raster image child:
'canvas': func(root, layout, node) {
var tmp=node.getValues();
var (w,h) = (tmp['pref-width'], tmp['pref-height']);
var newCanvas = canvas.new({
"name": "embedded",
"size": [512, 512],
"view": [512, 512],
});
# need to wrap cmdarg to return the node for the Canvas (which is currently hard-coded)
var cmdarg = func newCanvas._node;
# run the nasal/load block, must override cmdarg()
PUI.MetaTags["nasal-open"](node:node, cmdarg: cmdarg, tag:"nasal/load", filename:"canvas/nasal/load block");
# add a label widget to serve as the container for an embedded canvas
var label = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0});
label.setImage( newCanvas.getPath() );
label.setFixedSize(256,256);
layout.addItem(label);
}, # canvas
To see if the parser can correctly deal with an embedded canvas, we can use the PUI/XML dialog from Howto:Adding a canvas to a GUI dialog:
Supporting Translations
This article is a stub. You can help the wiki by expanding it. |
Our existing GUI is fairly simple, but it all goes through XML and the/a property tree, which is an advantage here.This was previously discussed on the issue tracker a while ago: So with some work, anything that uses PropertyList/XML can still be localied by using "post-processing hooks", i.e. you first load the actual file (e.g. about.xml) and then load a localized version of the same file (with just the strings replaced) into the same props.Node/SGPropertyNode. Given James' work on Nasal-based fgcommands, we can trivially overwrite fgcommands like show-dialog with our own implementation (wrapping the original fgcommand, or using a custom show-localized-dialog) that basically:
- load a GUI dialog like about.xml
- check if there is a matching XML file in $FG_ROOT/Translations/it/gui/dialogs/about.xml
- if so, load it into the same tree, basically replacing those original English strings
Et voila, you'll end up with a very simple localization method without having to edit any C++ code
Alternatively, we could use the include/params construct to overwrite/replace default strings with stuff from $FG_ROOT/Translations
I don't even think that it requires much in terms of Nasal, it's probably under 100 lines of code to do it this way.[9]
$FG_ROOT/Translations contains references that are looked up by the menubar - so the menubar merely contains property references, not the strings - the translated strings are then looked up using those properties - e.g. see $FG_ROOT/Translations/en/menu.xml (?)
Next, check preferences.xml for the <intl> tag that includes Translations/locale.xml
At that point, you should understand how the existing scheme works, and how you can use the same mechanism to also localie Canvas dialogs accordingly, even without hard-coding any language into the code.
there should be some kind of /sim/intl and /locale tree somewhere in the global tree, have you checked if that's the case or not?
There should also be a vector with <lang> nodes for each supported locale. The whole locale handling stuff is fairly old code, but I do remember that most strings read during startup are mapped that way, which is why I suggested to do a reverse lookup to obtain the information that you need, i.e the translated strings should be also available there. And even if something should be missing all those files are PropertyList files that can be read/processed using 10 lines of Nasal code (via io.read_properties)[11]
you don't need much more than 20 lines of Nasal code to get a vector with files/directories from $FG_ROOT/Translations via the directory() API, and then use the io.read_properties() API to processs such files, and use the .getValues() method to obtain a corresponding hash that you can then traverse using a foreach() loop.[12]
The safest way for frontends like fgrun/fgX and friends would be to process a file from $FG_ROOT that details possible startup options (options.xml) and simply provides a UI on top of these, so that there would be no tight coupling at all: http://sourceforge.net/p/fgrun/feature-requests/22/ options.xml and $FG_ROOT/Translations would need to be extended a little - but otherwise that could even be useful for the Phi/Qt5 efforts, i.e. a front-end agnostic mechanism for describing startup, and run-time, options that supports localiations, and that has otherwise no tight integration with the underlying binaries (i.e. no hard-coded assumptions). Short of doing something along these lines, it is extremely likely that with the increasing plethora of FG front-ends, the functionality/features and use-cases supported by each will be more and more diverging over time. Basically, options.xml would need to be extended to classify startup/run-time options, and to encode compatibility/exclusivity of options (think YaSim vs. JSBSim, AW vs. BW, ALS vs. Rembrandt) so that the UI front-end (Phi, Qt, Canvas, fgX, fgrun etc) would simply process an XML file and procedurally create a UI based on a PropertyList-encoded list of supported features/options and heuristics/rules. The underlying control logic would ideally not use any custom Nasal blocks at all, but merely resort to property rules and XML state machine rules. That way, the UI would no longer matter.[13]
Regression Testing
We can do a simple form of regression testing by adding a new dialog to $FG_ROOT/gui/dialogs called pui2canvas-test.xml
and loading that as a self-test while executing pui2canvas.nas. This would work by rendering everything to an invisible Canvas (one with no window or dialog associated with it) and check for any runtime errors using the Nasal call()
API:
<?xml version="1.0"?>
<PropertyList>
<name>pui2canvas-test</name>
<modal>false</modal>
<layout>vbox</layout>
<nasal>
<open><![CDATA[
var x = 100;
# TODO: check bindings/fgcommands
]]></open>
<close><![CDATA[
if (x != 200) die("pui2canvas: Embedded Nasal broken");
]]></close>
</nasal>
<canvas>
<nasal>
<load><![CDATA[
if (x != 100) die ("pui2canvas: Embedded Nasal broken");
x = 200;
]]></load>
</nasal>
</canvas>
</PropertyList>
The same thing can be accomplished by creating the dialog procedurally (in-memory) using the props.nas helpers:
var testDlg = props.Node.new({ .... });
Procedural Dialogs
Styling & Themes
Layout Support
Work in progress This article or section will be worked on in the upcoming hours or days. See history for the latest developments. |
The most important layouting directive that needs to be supported is the table <layout>
- the least complex dialog to see tables at work is the gui/dialos/timeofday.xml dialog, this is a rather simple dialog with just a few static buttons and a few dynamic labels (well, the most recent version actually contains a few dropdown menus for picking a date).
var dialog = 'timeofday';
fgcommand("canvas-dialog-show", props.Node.new({'dialog-name':dialog}) );
As can be seen, a typical table layout will look like this:
<group>
<layout>table</layout>
<halign>center</halign>
<text>
<row>0</row>
<col>0</col>
<halign>left</halign>
<default-padding>0</default-padding>
<label>UTC</label>
</text>
<text>
<row>0</row>
<col>1</col>
<halign>left</halign>
<default-padding>0</default-padding>
<label>00:00:00</label>
<live>true</live>
<property>/sim/time/gmt-string</property>
</text>
<text>
<row>1</row>
<col>0</col>
<halign>left</halign>
<default-padding>0</default-padding>
<label>Local</label>
</text>
<text>
<row>1</row>
<col>1</col>
<halign>left</halign>
<default-padding>0</default-padding>
<label>00:00</label>
<live>true</live>
<property>/instrumentation/clock/local-short-string</property>
</text>
</group>
This means that groups with a table layout must be pre-processes to pre-allocate the corresponding Canvas layout structures (hbox/vbox).
foreach(var child; tableGroup ) {
# get the row/col of each child
# set up vector of vbox/box layouts
}
Dialog Support
Table of existing PUI dialogs, detailing degree of support (missing/incomplete features).
Implementing missing Widgets
A few required PUI widgets are currently not yet implemented for Canvas, so we need to provide our own implementation. To do this, we can look at existing widgets and see how these are implemented, i.e. by looking at $FG_ROOT/Nasal/canvas/gui/widgets. All widgets inherit from the superclass in $FG_ROOT/Nasal/canvas/Widget.nas
As can be seen by looking at our WidgetFactory, we are also missing some fairly simple widgets, like the <vrule/>
and <hrule/>
widgets supported by PUI, which are fairly straightforward to implement by inheriting from the Widget superclass and wrapping OpenVG path/line drawing accordingly, honoring $FG_ROOT/Nasal/canvas/gui/DefaultStyle.nas
Use Case: airports.xml
For the time being, fgdata/gui/dialogs/airports.xml is easily one of the most sophisticated/complex dialogs to parse/process, mainly because of the following features it is using:
- table-based layouting, using property conditions to hide/show widgets
- embedded Nasal sections (open/close) that must be executed while opening/closing the dialog
- Nasal code that dynamically modifies the dialog tree prior to being created
- fgcommands that must be bound to the Nasal code in the open block
- unsupported widgets (e.g. airport-list, radio button, combo)
- an embedded Canvas with its own nasal/load section that must be bound to the Nasal/open section of the whole dialog to be functional
- PUI related fgcommands that must be dynamically patched to be mapped to their Canvas equivalents
In other words, a parser that can successfully parse/process and render fgdata/gui/dialogs/airports.xml can be considered to be pretty functional already, and should be easy to extend for other dialogs.
Coming up with an airport-list widget
We can use/extend the ScrollArea widget in $FG_ROOT/Nasal/canvas/gui/widgets/ScrollArea.nas, and simply add a vbox layout to it, with each button representing an airport - i.e. instead of the splash screens added to the ScrollArea example shown in the example on the right, we will be adding buttons to the layout, each button representing one airport:
var vbox = canvas.VBoxLayout.new();
myCanvas.setLayout(vbox);
var scroll = canvas.gui.widgets.ScrollArea.new(root, canvas.style, {size: [96, 128]}).move(20, 100);
vbox.addItem(scroll, 1);
var scrollContent =
scroll.getContent()
.set("font", "LiberationFonts/LiberationSans-Bold.ttf")
.set("character-size", 16)
.set("alignment", "left-center");
var list = canvas.VBoxLayout.new();
scroll.setLayout(list);
var airports = findAirportsWithinRange(25, "KSFO");
# debug.dump(airports);
foreach(var apt; airports) {
var AirportButton = canvas.gui.widgets.Button.new(scrollContent, canvas.style, {})
.setText(apt.id)
.setFixedSize(50, 25);
list.addItem(AirportButton);
}
In its current form, this isn't even a widget yet - it is simply using the existing ScrollArea widget and adds a few buttons to it.
Bindings-wise, this is straightforward to hook up to the parser, because each button will merely have a Nasal callback that sets the corresponding PUI property:
Equally, this approach can be used to come up with a simple property-list equivalent.
Coming up with a property-list
One of the most common hard-coded widgets implemtented in C++ are custom list widgets, namely:
- list
- property-list
- airport-list
- waypointlist
- loglist
Thus, it makes sense to provide a common Canvas widget that can be reused for all these purposes. We can easily implement this by reusing the ScrollArea widget we have already, and by adding a vertical box layout to it with buttons/labels for each list entry.
For dialogs like the property browser (property-browser.xml), we need to implement the hard-coded property-list widget.
From the Canvas standpoint, it really doesn't matter what kind of list we're displaying - all that matters is having a container that implements a vertical scroll area, with buttons added for each entry, and a corresponding binding for applying the selected button - thus, we can adapt the airport-list code to become the foundation for arbitrary PUI list widgets.
For each button's label, we merely need to call the getPath() method for each property in the currently shown tree, and add it to the scroll area, which can be implemented using roughly 20 lines of code:
'property-list': func(window, root, layout, node) {
var vbox = canvas.VBoxLayout.new();
layout.addItem(vbox);
var scroll = canvas.gui.widgets.ScrollArea.new(root, canvas.style, {size: [96, 128]}).move(20, 100);
vbox.addItem(scroll, 1);
var scrollContent =
scroll.getContent()
.set("font", "LiberationFonts/LiberationSans-Bold.ttf")
.set("character-size", 16)
.set("alignment", "left");
var list = canvas.VBoxLayout.new();
scroll.setLayout(list);
var updateList = func(node) {
# TODO: should add ./.. first
foreach(var n; node.getChildren() ) {
# TODO: should probably use label instead here ?
var PropertyButton = canvas.gui.widgets.Button.new(scrollContent, canvas.style, {})
.setText(n.getPath() )
.setFixedSize(200, 25);
# save the node in a closure for later use
(func() {
var n = n;
var bindings = PUI.setupBindings(window, node);
# FIXME: this needs to use the patched fgcommand bindings ... for dialog-apply etc
PropertyButton.listen("clicked", PUI.makeBindingsCallback(bindings));
PropertyButton._view._root.addEventListener("click", func() {
print("click event:",n.getPath() );
});
# this will clear the list and call the function with the selected new node
PropertyButton._view._root.addEventListener("dblclick", func() {
print("double click event",n.getPath() );
# clear list
list.clear();
updateList( n );
});
}()); # call the anonymous function
list.addItem(PropertyButton);
} # foreach child
}
updateList( props.getNode("") );
}, # property-list
For that, we need a generic List widget inherited from Canvas.Widget, which can be populated with buttons.
Source Code
The code shown below is fairly simple (under 300 LOC), and lacking some of the enhancements shown/discussed above (e.g. embedded Canvas support, table layouts, custom widgets etc), but it can already be used to display dialogs like view.xml, scenario.xml or about.xml - for example:
Caution While standard fgcommands are likely to work "as is", most PUI related fgcommands need special treatment, which isn't implemented in the code shown below. For starters, you may want to try the about dialog, refer to the screenshots/gallery section above for other working examples |
fgcommand('canvas-dialog-show', props.Node.new({'dialog-name':'view'}) );
PUI Findings
The way PUI is used, and "enhanced" through tons of custom Nasal code embedded in PUI/XML dialogs, makes it obvious that it would be a good idea not just to support legacy dialogs, but to also introduce support for specialized widgets to get rid of all the Nasal spaghetti code, especially:
- airport-list (airports.xml)
- airport-info (airports.xml)
- pilot-list (multiplayer)
...
Ideally, these would be gradually re-implemented using a simple Canvas wrapper, so that the corresponding Nasal/PUI markup can be phased out in favor of dedicated widgets.
Canvas Menubar
See Howto:Making a Canvas Menubar for the main article about this subject. |
Preliminary Findings (core Canvas/C++)
Writing a parser in Nasal to convert PUI/XML PropertyList-encoded XML to Canvas widgets/dialogs is possible, and not even very involved, however there also some obvious issues/challenges currently - because the underlying C++ code was never designed with this use-case in mind.
This section is intended to gather data points and conclusions to identify bottlenecks and potential opportunities for optimizing Canvas, unrelated to the UI use-case, but with a focus on shaping APIs that may be useful for similar efforts, such as e.g. porting the HUD/2D panels code to use a similar approach:
- for testing purposes it would make sense to come up with a dialog showing an embedded Canvas to open/run arbitrary dialogs on demand (including aircraft specific ones)
- at least on old hardware, instantiating a Canvas-based PUI dialog is a fairly noticeable process that may take 50-120ms
- wrapping key props.nas APIs via cppbind would certainly help speed-up dialog parsing/processing
- it may make sense to investigate adding a widget/dialog cache (parts of this could be in Nasal, others would need C++ changes)
- using vbox/hbox layouts to emulate tables is unnecessarily heavy, i.e. causing tons of property/Nasal overhead - adding a Table layout (and exposing some related attributes, as per README.layout) would make sense and should alleviate most layouting overhead (most PUI dialogs make heavy use of tables).
- to process PUI/XML layouts, it would be much easier not to go through Nasal at all, and expose layouts at the property level directly, e.g. by treating a layout as a canvas element and automatically aligning child nodes according to the parent layout (vbox,hbox, table)
- scrolling/event-handling seems unnecessarily slow/unresponsive (need to investigate)
- animations should not be handled using timers or listeners, but by using OSG data structures directly
- Equally, README.gui covers a few common use-cases, such as "live" properties, and printf-style formatting - as well as using properties as input/output for a widget. Mapping those concepts directly to Canvas using C++ code, should make most related Nasal code unnecessary. Obviously, with Canvas everything is "live" in some way - but we still need to map Nasal callbacks to implement live/printf-style behavior, while that could also be done using property flags. So that Nasal listeners/polling can be avoided to emulate PUI behavior, i.e. by encoding update semantics (polling per frame, update-on-change etc)
- For the same reason it would make sense to expose the SGCondition code using cppbind, so that the props.nas helpers can be circumvented using native code.
- while there are a number of PUI widgets that are not currently implemented for Canvas, those are fairly straightforward to provide. However, there also is a handful of undocumented custom PUI widgets that need to be ported-some of which are using data that is currently not accessible, e.g. the loglist widget (Nasal Console, shaders/terrasync), which means that the logstream APIs would need to be exposed via Nasal/CppBind
References
References
|