Requirements

Gui Wrapper

  • supporting cegui, win32gui and gtkmm gui backends
  • bindings to yake.reflection (which reflects the gui functionality to lua or python)
  • forwarding and converting gui backend events
  • improved and simplified syntax
  • mostly generic data structures, which should make loading cegui layouts with win32gui possible
  • should be suitable for in-game menus and yakes' online platform (similiar to valves' steam technology)

Gui Editor

  • possibility of importing existing layouts (created by 3rd party editors e.g. scriptkiddies cegui editor)
  • creating/modifiying gui layouts in-game
  • serializing windows and creating the corresponding lua/python code for it
  • all reflected properties of a widget should be accessible
  • instantiation of new widgets at runtime

Evolution

Syntax Simplification

The first initial step in the direction of providing a graphical user interface was made with the concept of a home-baked implementation. Mainly we headed this way, because the existing gui libraries were not satisfactory. At that time we came up with a solid concept, which had a couple of neat features and was quite well designed. The main idea was to separate the graphical representation from the 2d picking or intersection layer. The approach is very familiar to that one used by physics libraries like ODE. Each graphical item has to register it's intersection polygon to a data-structure and keeps the form up to date. So basically there's an abstraction layer between the 'bounding polygon' and the actual representation of the widget, which would open possibilities like bringing 2d/3d widget togehther.

After some discussions or you may call it analysis we came to the conclusion, that the implementation, support and maintenance would be too time consuming and actually we are focussing on using/wrapping third party software instead of coming up with low-level libraries - finally we decided to go with cegui.

The first steps with cegui indicated that it would be nice to have a more robust and simpler syntax, the result looked like this:

   create_root() 
      .frame_window( "Demo7/Window" ) 
         .min_size( 0.2f, 0.2f ) 
         .max_size( 0.8f, 0.8f ) 
         .pos( 0.25f, 0.4f ) 
         .size( 0.5f, 0.5f ) 
         .caption( "Demo 7 - Window" ) 
         .button( "Demo7/Window1/Quit" ) 
            .caption( "quit" ) 
            .pos( 0.02f, 0.1f ) 
            .size( 0.2f, 0.05f ) 
            .end() 
         .button( "Demo7/Window1/YakeClick" ) 
            .caption( "fire event" ) 
            .pos( 0.26f, 0.1f ) 
            .size( 0.2f, 0.05f ) 
            .end() 
         .button( "Demo7/Window1/SoftWire" ) 
            .caption( "wire" ) 
            .pos( 0.5f, 0.1f ) 
            .size( 0.2f, 0.05f ) 
            .end() 
         .end() 
      .end();

This improved process of defining widget constellations in c++ essentially.

A Wrapper Was Born

The next steps were done shortly after simplifying ceguis' api. Instead of just providing an uitility for widget instantiation new functionality were added, an abstraction layer, the widget info classes, lots of helpers and other useful things. The reflection system (in its' second iteration) was coming along smoothly and the event bindings were somewhat clumsy but working. The first issue on the way to a reflected gui was event adapting (converting cegui events to events of the reflection system). The concept behind it is pretty simple, we just attach the event-adaptor to ceguis' event as handler and the adaptor accepts and forwards the 'calls'.

But there were another issue. The appearance of the previous was neat, but the concrete widget interface should not look like this ('title()' in comparsion with 'set_title()'), the solution is simple, just create another small class with functions like 'title()' and pass this class to the cobstructor of the concrete widget class.

The outcome:

button_base * but = NULL; 
win = create<window_base>( "form, james form" ) 
  << button() 
     .color(green) 
     .label("click") 
     .bind(but) 
  << button() 
     .label("click2") 
     .color(blue);

Finals

Somewhat later the third iteration of the reflection system was born and it affected the gui wrapper as well. Polished event and property-bindings improved the code, the possibility of having dynamic lua bindings brought some new powerful elements into the game. Now it was easier and cleaner to use lua instead of c++, reflected properties looked like real member variables, it was possible to bind lua functions without some kind of handler-conversion (like it was needed for the second iteration of the reflection library) and even chaining was available in lua.

Having this toy under the hood was a good proof-of-concept (for both concepts: reflection and gui) and opened a new way of solving gui related problems. With this clean lua integration we decided to generate lua code (in terms of persistence) instead of serializing the widgets and implementing a simple widget editor using reflected properties and the capability of code generation went like a charm!

function lua_clicked_handler(widget)
  print('lua_clicked_handler(): LUA here I am!')
  but = safe_dynamic_cast_to_button(widget) --or: assert(dynamic_cast_to_button(widget)) where assert returns the ptr
  --or: but.absolute_position = point(250, 100)
  but.pos.y = but.pos.y + 0.05
end

function create_win(id)
   window_base('win: ' .. id)
     :add(button('but' .. id):caption('but: ' .. id))
     :add(button():pos(0.4, 0.1):clicked(lua_clicked_handler))
     :run()
end

create_win('this_id')

Design Issues

Problems Of Generalization

Of course accomplishing a perfect generalizing abstraction of the given set of gui backends is not possible. Consequently the client can still access some backend specific functionality (other specific data like cegui widget naming is hidden within the plugin), but on the whole this wrapper is worthwhile, because the client can define a constellation of widgets regardless of the actual backend. This gives the possibility of defining a chat window in lua and creating instances for win32 (win32gui), linux (gtkmm) or opengl (ogre3d). Primarily this could be useful for an online platform (having the same friends/serverlist/mods windows in-game and in-os without defining them twice) and other tools like configuration utilities.

Making Widget Constellations Persistent

The possibility of creating graphical interfaces without the limitation of having just one backend is pretty useful in some cases. But you want more, you want to serialize/save and deserialize/load your creations, otherwise the undertaking would be senseless. Letting the gui backend handle this might be the easiest solution - unfortunately it's not, because most of the available gui libraries don't support serialization. Consequently we have to come up with our own solution(s).

As you know all properties of a widget are reflected using yake.reflection which makes you think 'serialize them!', this would works and at the time you are reading this it could be implemented as well. The approach has its' bad sides too, normally you don't create an interface only, but also event handlers. Serializing events resp. their handlers is more complicated, because you cannot store the function offset, so you need an serializable identifier for each function (yake.reflection offers this functionality).

Finally there's a simple solution for our issue, we are manually serializing each child widget (its' reflected properties) of a window (and of course the window itself) to lua or python, well you may prefer to call it 'code generation' rather than 'serialization'. This approach has some handy advantages:

  • the user can modify the gui without restarting the application
  • we can make usage of inheritance, which means that every defined constellation of widgets is a new class
  • it is possible to add event handlers and whatever the user wants to the constructor
  • it's trendy to outsource the logic code using lua, having the gui code at the same place can't be bad
  • importing existing third party layout files is possible

Simplifying Things

FIXME

cegui url strings ⇒ typos ceguis' bloated syntax being lightweight ⇒ widget infos

Structural Overview

yake registry for outsourcing ⇒ abstract widget factory for themes/backends ⇒ reflection which allows:

  • code generation
  • script bindings
  • serialization
  • event wiring

event adaptors editor ⇒ edit_widget

FIXME

Interfaces

FIXME

Examples

Simple Window

// creating a simple crazy eddie window and adding some widgets

win1 = create<gui_base>("yake.gui.cegui.taharez")->create<window_base>()
  .title("control window")
  .pos(0.1f, 0.2f)
  .size(0.5f, 0.5f)
  .sizing_enabled(false);

assert(std::string(win1.get_title()) == std::string("control window"));

win1 << static_text("cout:")
          .bg_enabled(false)
          .frame_enabled(false)
          .pos(0.02f, 0.1f)
          .size(0.1f, 0.1f)
     << multi_line_edit_box("redirected cout output goes here")
          .pos(0.02f, 0.2f)
          .size(0.96f, 0.4f)
          .bind(global_cout)
     << button("Quit")
          .pos(0.02f, 0.65f)
          .size(0.2f, 0.05f)
          .clicked(handle_quit, this);
 
component/yappgui.txt · Last modified: 2008/02/21 21:54 (external edit)
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki