Page 1 of 2

GUI Coding Syntax

Posted: Sun Nov 16, 2008 7:10 pm
by nullsquared
To stop derailing this thread here: http://www.ogre3d.org/phpBB2/viewtopic.php?t=44833

On the topic of interesting GUI syntax, here's my contribution once again:

Code: Select all

        (*_sheet)
            ("size", vec2(_renderer->width(), _renderer->height()))
            // debug panels
            (widget_(window, "debug")
                (widget_(panel, "0")
                    ("texture", string("renderer_gbuffer_0"))
                    ("size", vec2(128, 128))
                    ("draggable", false)
                ,widget_(panel, "1")
                    ("texture", string("renderer_gbuffer_1"))
                    ("size", vec2(128, 128))
                    ("draggable", false)
                ,widget_(panel, "2")
                    ("texture", string("renderer_gbuffer_2"))
                    ("size", vec2(128, 128))
                    ("draggable", false)
                ,widget_(panel, "3")
                    ("texture", string("renderer_gbuffer_3"))
                    ("size", vec2(128, 128))
                    ("draggable", false)
                ,widget_(panel, "4")
                    ("texture", string("renderer_shade_texture"))
                    ("size", vec2(128, 128))
                    ("draggable", false)
                )
                ("size", vec2(5 * 128, 128))
                ("autoArrange")
                ["titlebar"]
                    ["text"]
                        ("text", string("debug"))
                    .up()
                .up()

            // add in scrollbar for time control
            ,widget_(window, "timeSpeedScrollbarWin")
                ("size", vec2(128, 16))
                ("closable", false)
                ("resizable", false)
                (widget_(scrollbar, "timeSpeedScrollbar")
                    ("points", vec4(0, 0, 112, 0))
                    ("position", vec2(112, 0))
                    (EVT_SCROLL, gui::bind(&editorState::_timeSpeedScroll, this, _1))
                )
                ["titlebar"]
                    ["text"]
                        ("text", string("time speed"))
                    .up()
                .up()
            );
It's actually very intuitive to program with, there's no widget casting or anything like that. Creating custom widgets is extremely simple, too, and defining their custom behaviour is as simple as overriding an update() function (and possible the companion 'getter' attrib() function). Thoughts?

Posted: Sun Nov 16, 2008 7:27 pm
by syd
interresting. heavily operator oriented :wink:
I usualy prefer a scripted rather than hard coded layout.
The great advantage here is that you can bind events directly, with a script a mapping in the code would be required anyway.
what's inconvinient is that a designer won't be feasible with an hardcoded approach...

Posted: Sun Nov 16, 2008 8:18 pm
by nullsquared
syd wrote:interresting. heavily operator oriented :wink:
I usualy prefer a scripted rather than hard coded layout.
The great advantage here is that you can bind events directly, with a script a mapping in the code would be required anyway.
what's inconvinient is that a designer won't be feasible with an hardcoded approach...
Yeah, it can't be scripted. Would be nice if it could :D. Like you said, either way the even callbacks need to be in hard code (unless you script the callbacks, too, but then you're going very deeply into scripting).

What I like about this approach is that there is never any widget casting or any hardcoded functions like setPosition(), setClosable(), getTitlebar(), setThis(), getThat(), etc. It all Just Flows with a single update() function (operator() is a shortcut so you can chain the update calls).

Posted: Sun Nov 16, 2008 8:45 pm
by syd
yep that's an interesting approach, and actually it could be easily scripted.
see, you define attributes such as "size" using a string; this is already a scripting philisophy. then it wouldn't be hard implementing a serialize() /deserialize() methods using Ogre's script compiler :wink:
about callbacks, well even with the most advanced script, it's not possible to script any callback without a mapping in the c++ side, since you can't reference any c++ function/method from a text file.

Posted: Sun Nov 16, 2008 9:08 pm
by betajaen
syd wrote:about callbacks, well even with the most advanced script, it's not possible to script any callback without a mapping in the c++ side, since you can't reference any c++ function/method from a text file.
I came up with a solution for that. Your widgets don't use callbacks, they cause events; an event can be a string or an integer. Events are less "GUI-y" and actually mean something; start-processing, new-camera-position and so on. Events can carry some information; usually an attribute of the thing causing the event to happen.

It was demonstrated in the Chocolate code I posted; in the thread nullSquared linked to.

Posted: Sun Nov 16, 2008 9:32 pm
by nullsquared
This thread gave me the great idea of scripting my GUI with Lua :D

Posted: Sun Nov 16, 2008 9:44 pm
by syd
what I meant is that any type of event; a function callback, event delegates or event messages; at one point you have to associate it manually to some c++ routine.
This thread gave me the great idea of scripting my GUI with Lua Very Happy
that would be nice ;)
i heard squirel is faster though

Posted: Sun Nov 16, 2008 10:36 pm
by nullsquared
syd wrote:
This thread gave me the great idea of scripting my GUI with Lua Very Happy
that would be nice ;)
i heard squirel is faster though
Meh, Lua is already fully integrated into the project, no need for squirrel.

Posted: Mon Nov 17, 2008 12:05 am
by nullsquared
Comes out quite ugly in Lua. But it works:

Code: Select all

function createGUI(sheet)
    win = sheet:child(gui.window("debugWin"))
    win
        :child(gui.panel("0"))
            :update("texture", "renderer_gbuffer_0")
            :update("size", vec2(128, 128))
            :update("draggable", false)
    win
        :child(gui.panel("1"))
            :update("texture", "renderer_gbuffer_1")
            :update("size", vec2(128, 128))
            :update("draggable", false)
    win
        :child(gui.panel("2"))
            :update("texture", "renderer_gbuffer_2")
            :update("size", vec2(128, 128))
            :update("draggable", false)
    win
        :child(gui.panel("3"))
            :update("texture", "renderer_gbuffer_3")
            :update("size", vec2(128, 128))
            :update("draggable", false)
    win
        :child(gui.panel("4"))
            :update("texture", "renderer_shade_texture")
            :update("size", vec2(128, 128))
            :update("draggable", false)
    win
        :update("size", vec2(5 * 128, 128))
        :update("autoArrange")
        :child("titlebar")
            :child("text")
                :update("text", "debug")

    win = sheet:child(gui.window("timeSpeedScrollbarWin"))
    win
        :update("size", vec2(128, 16))
        :update("resizable", false)
        :update("closable", false)
        :child("titlebar")
            :child("text")
                :update("text", "time speed")
    win
        :child(gui.scrollbar("timeSpeedScrollbar"))
            :update("points", vec4(0, 0, 112, 0))
            :update("position", vec2(112, 0))

    win = sheet:child(gui.window("toolboxWin"))
    win
        :update("size", vec2(256, 128))
        :update("resizable", false)
        :child("titlebar")
            :child("text")
                :update("text", "toolbox")

    win = sheet:child(gui.window("objectPropertiesWin"))
    win
        :update("size", vec2(256, 512))
        :update("resizable", false)
        :child("titlebar")
            :child("text")
                :update("text", "object properties")
end

C++:

Code: Select all

try
        {
            luabind::call_function<void>(_lua, "createGUI", boost::shared_static_cast<widget>(_sheet));
        }
        catch(const luabind::error &e)
        {
            engine::log(e.what());
            engine::log(_lua.error());
        }

        _sheet->childInFamily("timeSpeedScrollbar")
            (EVT_SCROLL, gui::bind(&editorState::_timeSpeedScroll, this, _1));

Posted: Mon Nov 17, 2008 2:00 am
by nullsquared
Fixed up the syntax in Lua:

Code: Select all

function createGUI(sheet)
    sheet
        :update(
            gui.window("debugWin")
                :update(
                    gui.panel("0")
                        :update("texture", "renderer_gbuffer_0")
                        :update("size", vec2(128, 128))
                        :update("draggable", false)

                    ,gui.panel("1")
                        :update("texture", "renderer_gbuffer_1")
                        :update("size", vec2(128, 128))
                        :update("draggable", false)

                    ,gui.panel("2")
                        :update("texture", "renderer_gbuffer_2")
                        :update("size", vec2(128, 128))
                        :update("draggable", false)

                    ,gui.panel("3")
                        :update("texture", "renderer_gbuffer_3")
                        :update("size", vec2(128, 128))
                        :update("draggable", false)

                    ,gui.panel("4")
                        :update("texture", "renderer_shade_texture")
                        :update("size", vec2(128, 128))
                        :update("draggable", false)
                ) -- end debugWin children
                :update("size", vec2(5 * 128, 128))
                :update("autoArrange")
                :update("title", "debug")

            ,gui.window("timeSpeedScrollbarWin")
                :update("size", vec2(128, 16))
                :update("resizable", false)
                :update("closable", false)
                :update("title", "time speed")
                :update(
                    gui.scrollbar("timeSpeedScrollbar")
                        :update("points", vec4(0, 0, 112, 0))
                        :update("position", vec2(112, 0))
                ) -- end timeSpeedScrollbarWin children

            ,gui.window("toolboxWin")
                :update("size", vec2(256, 128))
                :update("resizable", false)
                :update("title", "toolbox")

            ,gui.window("objectPropertiesWin")
                :update("size", vec2(256, 512))
                :update("resizable", false)
                :update("title", "object properties")
    ) -- end sheet children
end
Now it resembles its C++ counterpart, except it's fully scriptable :D (don't even ask me how much of a hack the binding code is :oops: - but, hey, it works :D )

C++ equivalent:

Code: Select all

(*_sheet)
            ("size", vec2(_renderer->width(), _renderer->height()));
            ( // debug panels
                widget_(window, "debugWin")
                    ("visible", false)
                    (
                        widget_(panel, "0")
                            ("texture", string("renderer_gbuffer_0"))
                            ("size", vec2(128, 128))
                            ("draggable", false)
                        ,widget_(panel, "1")
                            ("texture", string("renderer_gbuffer_1"))
                            ("size", vec2(128, 128))
                            ("draggable", false)
                        ,widget_(panel, "2")
                            ("texture", string("renderer_gbuffer_2"))
                            ("size", vec2(128, 128))
                            ("draggable", false)
                        ,widget_(panel, "3")
                            ("texture", string("renderer_gbuffer_3"))
                            ("size", vec2(128, 128))
                            ("draggable", false)
                        ,widget_(panel, "4")
                            ("texture", string("renderer_shade_texture"))
                            ("size", vec2(128, 128))
                            ("draggable", false)
                    )
                    ("size", vec2(5 * 128, 128))
                    ("autoArrange")
                    ("title", string("debug"))

                // add in scrollbar for time control
                ,widget_(window, "timeSpeedScrollbarWin")
                    ("size", vec2(128, 16))
                    //("position", vec2(0, 0))
                    ("closable", false)
                    ("resizable", false)
                    (
                        widget_(scrollbar, "timeSpeedScrollbar")
                            ("points", vec4(0, 0, 112, 0))
                            ("position", vec2(112, 0))
                            (EVT_SCROLL, gui::bind(&editorState::_timeSpeedScroll, this, _1))
                    )
                    ("title", string("time speed"))

                ,widget_(window, "toolboxWin")
                    ("size", vec2(256, 128))
                    ("resizable", false)
                    ("title", string("toolbox"))

                ,widget_(window, "objectPropertiesWin")
                    ("size", vec2(256, 512))
                    ("resizable", false)
                    ("title", string("object properties"))
            );

Posted: Mon Nov 17, 2008 2:45 pm
by syd
man you made it quickly =)
the second version looks indeed better, but in lua it looks like you kept the c++ constraints. I think it would have been better if the script was more "descriptive" oriented. I would look at things such as XAML or XUL, maybe the good old Ogre scripting style ;)

I think it would be great to implement a XUL parser for major Ogre's Gui libs, just my 2 cents :)

Posted: Mon Nov 17, 2008 4:13 pm
by _tommo_
syd wrote:man you made it quickly =)
the second version looks indeed better, but in lua it looks like you kept the c++ constraints. I think it would have been better if the script was more "descriptive" oriented. I would look at things such as XAML or XUL, maybe the good old Ogre scripting style ;)

I think it would be great to implement a XUL parser for major Ogre's Gui libs, just my 2 cents :)
I quote... Lua has a feature wich makes useless all that C++ like mess :D
You can use tables to have a really much clearer code:

For example, this (not really easy to read))

Code: Select all

sheet 
        :update( 
            gui.window("debugWin") 
                :update( 
                    gui.panel("0") 
                        :update("texture", "renderer_gbuffer_0") 
                        :update("size", vec2(128, 128)) 
                        :update("draggable", false) 
Could become like

Code: Select all

sheet  = {
    gui = {
        guiPanel = {
            number = 0, 
            texture = "render_gbuffer_0",
            size = {128, 128 },
            draggable = false },
        --other panels
    }
      --other GUIs    
}

 application.createSheet( sheet )
   
This would be much easier to read and write, and on top of that, you could also declare pieces of your Sheet alone, like you do now with vertex_program_defs in Ogre:

Code: Select all

defaultPanel = {...}

defaultGUI = { a = defaultPanel,  b =defaultPanel }

defaultSheet = {...}
This way you could easily define and reuse gui components without need of rewriting everything :D

Posted: Mon Nov 17, 2008 7:44 pm
by KungFooMasta
What I like about this approach is that there is never any widget casting or any hardcoded functions like setPosition(), setClosable(), getTitlebar(), setThis(), getThat(), etc. It all Just Flows with a single update() function (operator() is a shortcut so you can chain the update calls).
Not sure why you feel the get/set methods are undesirable, they add to the visibility of what can be done, and don't require mastery of the API, such as memorizing what strings are valid in certain context. What happens if I execute this line?

Code: Select all

:update("size", false)
I guess if you're into scripting GUI in code this might be a fun library, but it seems to be very error prone by default. Adding in code to check validity of input from a user would require a lot of maintenance when adding functionality.

I guess this is the difference of 2 philosophies: In one case, the API is extremely flexible and allows for various inputs, and all the rules are not defined (easy to get unwanted behavior), and in the other the input and rules are very defined, but require use of the given APIs. (restricted in some regards)

Posted: Mon Nov 17, 2008 9:43 pm
by nullsquared
KungFooMasta wrote:
What I like about this approach is that there is never any widget casting or any hardcoded functions like setPosition(), setClosable(), getTitlebar(), setThis(), getThat(), etc. It all Just Flows with a single update() function (operator() is a shortcut so you can chain the update calls).
Not sure why you feel the get/set methods are undesirable, they add to the visibility of what can be done, and don't require mastery of the API, such as memorizing what strings are valid in certain context.
You don't have to memorize, there can be (there is) a guide.
What happens if I execute this line?

Code: Select all

:update("size", false)

Code: Select all

/* in the log file */
*-*-* boost::bad_any_cast: failed conversion using boost::any_cast
        in D:\programming\portalized\trunk\src\gui\widget.cpp
        in virtual engine::gui::widget& engine::gui::widget::update(const engine
::string&, const boost::any&)
        at line 331

Code: Select all

/* 331 */
_size = SAFE_ANY_CAST(vec2, val);
Not that useful to a regular user, I know. But if you're coding, then you'll have access to widget.cpp. And if you're scripting, then ... well, you'll find it easy enough when you see 'false' for size :). Besides, I don't like stuff ever crashing, even on error, so this is nice and useful.
Adding in code to check validity of input from a user would require a lot of maintenance when adding functionality.
Not really. It's all about remembering to use SAFE_ANY_CAST over boost::any_cast. Besides, the script is no less safe than the C++ code - it, too, only has a single update() function.
I guess this is the difference of 2 philosophies: In one case, the API is extremely flexible and allows for various inputs, and all the rules are not defined (easy to get unwanted behavior), and in the other the input and rules are very defined, but require use of the given APIs. (restricted in some regards)
It's not that either one is restricted, its just that one flows syntax-wise and the other one looks cumbersome. At least in my opinion.

Posted: Mon Nov 17, 2008 10:09 pm
by nullsquared
@ better use of Lua:

1) I suck at Lua, and yes there are better ways of doing it :)

2) using tables will add lots and lots of Lua-interfacing code, which I'm not too fond of, but will add if point 3 wasn't here

3) using tables will make it deferred - I don't want a deferred approach. I don't want any createSheet() or updateSheet() calls, or updateWidget() or anything like that.

Posted: Mon Nov 17, 2008 10:28 pm
by syd
1 :)

I'd go with an xml based script, it would be a lot more intuitive.
have a look at this xul sample (firefox only): http://www.faser.net/mab/chrome/content/mab.xul
efficient, elegant script...

Posted: Mon Nov 17, 2008 10:50 pm
by _tommo_
nullsquared wrote:@ better use of Lua:

1) I suck at Lua, and yes there are better ways of doing it :)

2) using tables will add lots and lots of Lua-interfacing code, which I'm not too fond of, but will add if point 3 wasn't here

3) using tables will make it deferred - I don't want a deferred approach. I don't want any createSheet() or updateSheet() calls, or updateWidget() or anything like that.
Yeah but... for me an important objective in scripting is ease of use and clarity... so, if you are only to have an 1:1 port of C++ code, the scripting is pretty much a performance hog, with the only advantage being that you don't need to recompile...
IMHO deferring the thing is adviced, because this way you can have a much more effective scripting...
BTW, what do you mean with createSheet() or updateSheet() calls, or updateWidget() calls?

Posted: Mon Nov 17, 2008 10:56 pm
by jacmoe
How about basing it on SimKin? It's some sort of scripted XML. :)

Or, maybe Jim (a tiny TCL derivative) ?
What's cool about TCL is that everything is a command.
TCL lends itself particularly well to init and config scripts. :wink:

Posted: Mon Nov 17, 2008 10:59 pm
by betajaen
I have been considering using the JSON format to serialize a Chocolate GUI. A JSON document is easy to create and has a few C/C++ readers. JSONcpp being my favorite.

At least then someone can design a GUI builder in as a web page (which Chocolate is inspired by), or even embed a Javascript interpreter.

[Edit]

Just looking at Spidermonkey by Mozilla.

It gave me an idea. Not only a JSON format can be used to define a GUI, it can be also be used to handle logic and events. JSON is basically a collection of associate arrays, there is no reason why it can't use functions in the middle of it. :)

Posted: Mon Nov 17, 2008 10:59 pm
by syd
BTW, what do you mean with createSheet() or updateSheet() calls, or updateWidget() calls?
i think it means scripts would be able to react to events, and modify the layout, call routines... it'd be some interpreted language layer...
that's a nice idea, it goes far.

Posted: Tue Nov 18, 2008 1:01 am
by nullsquared
_tommo_ wrote: BTW, what do you mean with createSheet() or updateSheet() calls, or updateWidget() calls?
There's no way for C++ to know that you've created a proper table that's ready for the GUI. You need to tell it - once you make the GUI, you'd need a create() or update() call. Which actually isn't so bad, now that I think about it - I can have a single update() call, definitely. Good ideas!

Posted: Tue Nov 18, 2008 2:56 am
by nullsquared
_tommo_ wrote:
syd wrote:man you made it quickly =)
the second version looks indeed better, but in lua it looks like you kept the c++ constraints. I think it would have been better if the script was more "descriptive" oriented. I would look at things such as XAML or XUL, maybe the good old Ogre scripting style ;)

I think it would be great to implement a XUL parser for major Ogre's Gui libs, just my 2 cents :)
I quote... Lua has a feature wich makes useless all that C++ like mess :D
You can use tables to have a really much clearer code:

For example, this (not really easy to read))

Code: Select all

sheet 
        :update( 
            gui.window("debugWin") 
                :update( 
                    gui.panel("0") 
                        :update("texture", "renderer_gbuffer_0") 
                        :update("size", vec2(128, 128)) 
                        :update("draggable", false) 
Could become like

Code: Select all

sheet  = {
    gui = {
        guiPanel = {
            number = 0, 
            texture = "render_gbuffer_0",
            size = {128, 128 },
            draggable = false },
        --other panels
    }
      --other GUIs    
}

 application.createSheet( sheet )
   
This would be much easier to read and write, and on top of that, you could also declare pieces of your Sheet alone, like you do now with vertex_program_defs in Ogre:

Code: Select all

defaultPanel = {...}

defaultGUI = { a = defaultPanel,  b =defaultPanel }

defaultSheet = {...}
This way you could easily define and reuse gui components without need of rewriting everything :D
Great advice!

New beginnings!

Code: Select all

sheet:update({
        debugWin = {
            type = "window",
            title = "debug",
            size = vec2(5 * 128, 128),
            panel0 = {
                type = "panel",
                size = vec2(128, 128),
                draggable = false,
                texture = "renderer_gbuffer_0"
            },
            panel1 = {
                type = "panel",
                size = vec2(128, 128),
                draggable = false,
                texture = "renderer_gbuffer_1"
            },
            panel2 = {
                type = "panel",
                size = vec2(128, 128),
                draggable = false,
                texture = "renderer_gbuffer_2"
            },
            panel3 = {
                type = "panel",
                size = vec2(128, 128),
                draggable = false,
                texture = "renderer_gbuffer_3"
            },
            panel4 = {
                type = "panel",
                size = vec2(128, 128),
                draggable = false,
                texture = "renderer_shade_texture"
            },
            autoArrange = nil
        }
    })
:D

Now, considering those panels are equal except for the texture, how would I create a 'template' panel table and reuse it, but change the texture?

(the Lua interfacing code actually isn't too bad, Luabind makes it a piece of cake :D)

Posted: Tue Nov 18, 2008 12:37 pm
by _tommo_
Wow you changed your mind quite suddenly :P

To have a template panel you could do just like this:

Code: Select all

panelDefault = { 
                type = "panel", 
                size = vec2(128, 128), 
                draggable = false, 
                texture = "UNDEFINED" 
            }, 
--then, in the code, you could "inherit" the default panel:

myPanel = defaultPanel

--you are actually copying the whole structure of defaultPanel.
--so, you can easily override some members, like the texture, doing

myPanel.texture = "overridenTexture"

--this is exactly as overriding super class members in C++, except it's at runtime
It's not that fast, because it has to copy the whole table data, but anyway, speed isn't the point in scripting :wink:

Posted: Tue Nov 18, 2008 1:11 pm
by volca
betajaen wrote:
syd wrote:about callbacks, well even with the most advanced script, it's not possible to script any callback without a mapping in the c++ side, since you can't reference any c++ function/method from a text file.
I came up with a solution for that. Your widgets don't use callbacks, they cause events; an event can be a string or an integer. Events are less "GUI-y" and actually mean something; start-processing, new-camera-position and so on. Events can carry some information; usually an attribute of the thing causing the event to happen.

It was demonstrated in the Chocolate code I posted; in the thread nullSquared linked to.
Is this about complexity of bindings or consistency? I tend to think that using named slots per component would solve the latter - with signal name->functor map. The problems seem to be how to transfer parameters of the event to the functor and how to guarantee that the particular component will have a handler.

The consistency checks seem to be useful if the gui is specified in a data file:

Code: Select all

 // pseudocode
GUIWindow* myWindow = GUIManager->createWindow("MY Window");
myWindow->addSignalHandler("OkPressed", new Functor(this, &ThisClass::onOKPressed)); // signal slot handler
// Also possible: component constraint: We need name edit box present
myWindow->addConstraint(new ComponentNeededConstraint("NameEditBox"));
myWindow->loadLayout("myWindow.window"); // at the end, this will check both code slots not in data file and in reverse, and verify all constraints are satisfied
This obviously is not about coding style :)

As for the signal handler's parameters - I tend to think Variant class implementation could be used as a universal parameter, or a map of those. I'm still unsure here.

Posted: Tue Nov 18, 2008 10:40 pm
by nullsquared
I also just realized that I could make the GUI allow scripted callbacks if it wants!

I bet I could create a proxy C++ function as so:

Code: Select all

void _callback(const gui::event &e,  const luabind::object &callback)
{
    callback(e.caller);
}

void _registerScriptedCallback(gui::widget &_this, const luabind::object &callback, const string & id)
{
    _this.callback(id, boost::bind(&_callback, _1, callback));
}

// traversing the widget update table
if (type(*i) == LUA_TFUNCTION)
{
    _registerScriptedCallback(_this, *i, object_cast<string>(key))
}

// in-lua
someWidget:update({
    onClick = (function(caller) print("clicked " .. caller:name()) end)
})
// actually I'm not sure if the syntax is right, but the functions would be possible :D
Thus, I don't see why more libraries don't do this type of boost requirement - it's not really saving anyone considering boost will be std:: soon enough (it already is in tr1, std::tr1:: )