Board index FlightGear Development Canvas

Drop-down ("Combo") class  Topic is solved

Canvas is FlightGear's new fully scriptable 2D drawing system that will allow you to easily create new instruments, HUDs and even GUI dialogs and custom GUI widgets, without having to write C++ code and without having to rebuild FlightGear.

Drop-down ("Combo") class

Postby Philosopher » Mon Jul 08, 2013 2:58 pm

I'm making a Canvas dialog that is "conventional" -- i.e. uses elements like in PUI -- but is made directly from Nasal (specifically by some files for my joystick). I've been working on a Combo class and my current problem is that I'm displaying two of these with vertical alignment -- which means that opening one goes on top of the other. Is there a way to give one priority over the other, so that it's background covers the other's text? For the record I am using version 2.10. Here's my class (it's in gui-elements.nas, in the namespace "elements"):

Code: Select all
# A combo element like that in PUI
var Combo = {
   # Make a new one off of a Canvas group
   #
   # @param group The Canvas group to make this a child of
   # @param values The list of values to start out with
   new: func(group, values=nil, default=nil) {
      var me = {
         parents:[Combo, group.createChild("group")],
         values: values==nil?[]:values, #vector<string> of values: should this be in the property tree?
         _values: [], #vector<canvas.Text>: handles to canvas.Text objects to manipulate each value
         state: 0, #-1: show all; other: index of selected value
         listener: func{}, #a function that gets called when clicked
         spacing: 18, #parameter: y increment between text elements
         border: 4, #parameter: spacing around various elements
         font_sz: 14,
         width: 170, #width of this box
      };
      if (default != nil) me.state = me.findValue(default);
      me.addEventListener("click", func(e) {
         var y=me._getTf().f.getValue();
         if (me.state == -1) {
            y-=me.spacing/2; #correct for the center alignment of the text
            var s = nil;
            for (var i=1; i<=size(me.values); i+=1)
               if (e.clientY < y+me.spacing*i) {var s = me.state = i-1; break}
            if (s != nil)
               me.listener(me.values[s], s, e);
            else
               die("Invalid mouse click");
         } else {
            me.state = -1;
            me.listener(nil, -1, e);
         }
         me._drawValues();
      });
      me.fill = me.createChild("path");
      me._makeValues();
      me._drawValues();
      return me;
   },
   # Private; make text objects out of each value
   _makeValues: func() {
      foreach (var v; me._values) v.del();
      me._values = setsize([], size(me.values));
      forindex (var i; me.values) {
         me._values[i] = me.parents[1].createChild("text")
            .setText(me.values[i])
            .setTranslation(0, 0)
            .setAlignment("left-center")
            .setFontSize(me.font_sz)
            .setFont("LiberationFonts/LiberationSans-Bold.ttf")
            .setColor(1,1,1)
            .hide();
      }
      if (me.state < size(me.values))
         me._values[me.state].show();
   },
   # Private: position the values based upon me.state
   _drawValues: func() {
      if (size(me._values) != size(me.values)) print("Sizes differ!");
      #var x=me._getTf().e.getValue();
      #var y=me._getTf().f.getValue();
      if (me.state >= 0) {
         foreach (var v; me._values) v.hide().setTranslation(0,0);
         if (me.state < size(me._values))
            me._values[me.state].show();
         me.fill.reset()
            .moveTo(-me.border,-me.font_sz/2-me.border)
            .horizTo(me.width)
            .vertTo(me.font_sz/2+me.border)
            .horizTo(-me.border)
            .close()
            .setColorFill(0,0,0);
      } else {
         var incr = -me.spacing;
         foreach (var v; me._values) {
            v.show().setTranslation(0,incr+=me.spacing);
         }
         me.fill.reset()
            .moveTo(-me.border,-me.font_sz/2-me.border)
            .horizTo(me.width)
            .vertTo(me.font_sz/2+me.border+incr)
            .horizTo(-me.border)
            .close()
            .setColorFill(0,0,0);
      }
   },
   # Add a new value into the list at the specified position
   #
   # @param value The value (string)
   # @param idx The index to add it in at; default: the end
   addValue: func(value, idx=-1) {
      if (!size(me.values) and (idx == 0 or idx == -1)) {
         me.values = [value];
         me._makeValues();
         return me;
      }
      if (idx < 0) idx += size(me.values);
      if (idx < 0 or idx >= size(me.values)) return me;
      if (idx == size(me.values)-1)
         me.values = me.values[:idx]~[value];
      else
         me.values = me.values[:idx]~[value]~me.values[idx+1:];
      me._makeValues();
      return me;
   },
   # Remove a value from the list
   #
   # @param value Either the value itself or its index
   removeValue: func(value) {
      var idx = me.findValue(value);
      if (idx == nil or idx < 0 or idx >= size(me.values)) return me;
      if (idx == size(me.values)-1)
         me.values = me.values[:idx-1];
      elsif (idx == 0)
         me.values = me.values[idx+1:];
      else
         me.values = me.values[:idx-1]~me.values[idx+1:];
      me._makeValues();
      return me;
   },
   # Set the listener for this box. Gets called when a new value is
   # selected with the value and index as arguments
   setListener: func(listener_fn) {
      me.listener = listener_fn;
      return me;
   },
   # Find a value in the list
   #
   # @param value Either the value itself or its index
   findValue: func(value) {
      if (num(value) != nil) return value;
      forindex (var i; me.values)
         if (me.values[i] == value) return i;
      return nil;
   },
   # Set the direction for this to open
   #
   # @param dir 1 for up, -1 for down
   setDir: func(dir=1) {
      me.set("direction", dir);
   },
   #_getTf: func() {
   #   me.parents[1].parents[1].parents[1]._getTf();
   #},
};


And my surrounding code (you'll have to modify the io.load_nasal usage to use outside of my joystick :oops:):

Code: Select all
io.load_nasal(Joystick.Root ~ "/gui-elements.nas", "elements"); #the Combo class

var state = {};

var mode_editor = nil;
var make_window = func {
   mode_editor = getprop("/sim/version/flightgear") == "2.10.0" ? canvas.Dialog.new([400,300]) : canvas.Window.new([400,300]);
   var my_canvas = mode_editor.createCanvas()
                             .setColorBackground(0,0,0,0);
   var root = my_canvas.createGroup();
   var title_bar = root.createChild("group");
   title_bar.addEventListener("drag", func(e) { mode_editor.move(e.deltaX, e.deltaY); });
   var x = 0;
   var y = 0;
   var rx = 8;
   var ry = 8;
   var w = 400;
   var h = 20;
   title_bar.createChild("path")
      .moveTo(x + w - rx, y)
      .arcSmallCWTo(rx, ry, 0, x + w, y + ry)
      .vertTo(y + h)
      .horizTo(x)
      .vertTo(y + ry)
      .arcSmallCWTo(rx, ry, 0, x + rx, y)
      .close()
      .setColorFill(0.25,0.24,0.22)
      .setStrokeLineWidth(0);
   y = 20;
   h = 280;
   root.createChild("path")
      .moveTo(x + w, y)
      .vertTo(y + h)
      .horizTo(x)
      .vertTo(y)
      .setColorFill(1,1,1)
      .setColor(0,0,0);
   x = 8;
   y = 5;
   w = 10;
   h = 10;
   title_bar.createChild("path", "icon-close")
      .moveTo(x, y)
      .lineTo(x + w, y + h)
      .moveTo(x + w, y)
      .lineTo(x, y + h)
      .setColor(1,0,0)
      .setStrokeLineWidth(3)
      .addEventListener("click", func {mode_editor.del(); mode_editor = nil});
   title_bar.createChild("text", "dialog-caption")
      .setText("Joystick Mode Editor")
      .setTranslation(x + w + 8, 4)
      .setAlignment("left-top")
      .setFontSize(14)
      .setFont("LiberationFonts/LiberationSans-Bold.ttf")
      .setColor(1,1,1);
   var body = root.createChild("group");
      x = 4;
      y = 14;
      body.createChild("text")
         .setText("Selected joystick:")
         .setTranslation(x, 20+y+2)
         .setAlignment("left-center")
         .setFontSize(14)
         .setFont("LiberationFonts/LiberationSans-Bold.ttf")
         .setColor(0,0,0);
      x += 150;
      var joystick_list = elements.Combo.new(body, [], 0)
         .setTranslation(x, 20+y)
         .setListener(func gui.popupTip("Joystick is "~(arg[0]==nil?"being selected":arg[0]~" at index "~arg[1])));
      x = 4;
      y = 40;
      body.createChild("text")
         .setText("Selected mode:")
         .setTranslation(x, 20+y)
         .setAlignment("left-center")
         .setFontSize(14)
         .setFont("LiberationFonts/LiberationSans-Bold.ttf")
         .setColor(0,0,0);
      x += 150;
      var mode_list = elements.Combo.new(body, [], 0)
         .setTranslation(x, 20+y)
         .setListener(func gui.popupTip("Mode is "~(arg[0]==nil?"being selected":arg[0]~" at index "~arg[1])));
      var w = 400;
      body.createChild("path")
         .moveTo(4,75)
         .horizTo(w-4)
         .setColor(0,0,0);
   joystick_list.addValue("[none]").addValue("Cyborg-X").addValue("Missing").addValue("One?");
   #foreach (var js; Joystick.getParent().getChildren("js"))
   #   joystick_list.addValue(js.getNode("name").getValue());
   foreach (var mode; Joystick.getChildren("mode"))
      mode_list.addValue(mode.getNode("name").getValue());
   state = caller(0)[0]; #make this visible as more than a temporary namespace to make debugging possible
};

var toggle_window = func {
   if (mode_editor != nil) {mode_editor.del(); mode_editor = nil}
   else {
      io.load_nasal(Joystick.Root ~ "/gui.nas", namespace);
      make_window();
   }
};


P.S. One thing that I noticed is that text, based upon it's content, has a different bounding box -- and that screws up the alignment, as text looks best if it is aligned by its baseline. That means text with parenthesis gets a higher baseline than text without those characters.
P.P.S. Thanks Thomas for all your work on Canvas!!! :D
Thanks,
Philosopher
(inactive but lurking occasionally...)
Philosopher
 
Posts: 1590
Joined: Sun Aug 12, 2012 6:29 pm
Location: Stuck in my head...
Callsign: AFTI
Version: Git
OS: Mac OS X 10.7.5

Re: Drop-down ("Combo") class

Postby Hooray » Mon Jul 08, 2013 3:13 pm

Philosopher wrote in Mon Jul 08, 2013 2:58 pm:Is there a way to give one priority over the other, so that it's background covers the other's text?

canvas windows can be stacked: http://www.youtube.com/watch?&v=llVaasTEf44
For groups, you'll probably want to use the z-index property instead, to affect rendering precedence:
Code: Select all
group1.set("z-index", 1);
group2.set("z-index", 2);
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 11437
Joined: Tue Mar 25, 2008 8:40 am

Re: Drop-down ("Combo") class

Postby Philosopher » Mon Jul 08, 2013 3:49 pm

Hm, isn't working for me. It seems to affect events precedence, but not rendering precedence.
Thanks,
Philosopher
(inactive but lurking occasionally...)
Philosopher
 
Posts: 1590
Joined: Sun Aug 12, 2012 6:29 pm
Location: Stuck in my head...
Callsign: AFTI
Version: Git
OS: Mac OS X 10.7.5

Re: Drop-down ("Combo") class

Postby TheTom » Mon Jul 08, 2013 9:24 pm

Philosopher wrote in Mon Jul 08, 2013 3:49 pm:It seems to affect events precedence, but not rendering precedence.

Ok, it seems like this is a problem only with text elements. I will have a look at it...
TheTom
 
Posts: 321
Joined: Sun Oct 09, 2011 10:20 am

Re: Drop-down ("Combo") class  

Postby TheTom » Fri Jul 12, 2013 2:22 pm

I've just pushed a fix. Now all elements should be drawn in the correct order.
TheTom
 
Posts: 321
Joined: Sun Oct 09, 2011 10:20 am

Re: Drop-down ("Combo") class

Postby Hooray » Fri Jul 12, 2013 4:49 pm

which means probably that P. will have to wait for 2.12 ? :D
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 11437
Joined: Tue Mar 25, 2008 8:40 am

Re: Drop-down ("Combo") class

Postby Philosopher » Fri Jul 12, 2013 4:57 pm

Actually, I might try a nightly from Jenkins, but if I don't like that, I've already cloned the FG repos, so if I get the necessary permissions I might try compiling soon :D.

Thanks Thomas for fixing these things!
Thanks,
Philosopher
(inactive but lurking occasionally...)
Philosopher
 
Posts: 1590
Joined: Sun Aug 12, 2012 6:29 pm
Location: Stuck in my head...
Callsign: AFTI
Version: Git
OS: Mac OS X 10.7.5

Re: Drop-down ("Combo") class

Postby Hooray » Fri Jul 12, 2013 5:23 pm

Compiling should be fairly straightforward on Macs as far as I've heard, and I think there are 1-2 core developers who are OSX-based. Overall, it should be much easier than building on Windows, because it's also pretty *nix-ish - but if you need any specific help, let us know.
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 11437
Joined: Tue Mar 25, 2008 8:40 am


Return to Canvas

Who is online

Users browsing this forum: No registered users and 1 guest