Sign in to follow this  
MaulingMonkey

GUI: Seperating GUI data from implementation

Recommended Posts

In the past, I have been fustrated in attempting to figure out "the perfect design" for a C++ GUI toolkit, as I had a hard time figuring out a good scheme for attempting to meld both implementation that could work within a multitude of contexts (Windows API, X11, OpenGL, etc) and the data of the GUI, as I followed in the steps of so many GUI kits that do so. However, I have realized the melding of these two is probably exactly what I should be avoiding!! My fustration in attempting to generate the desired level of flexibility (a window class should be useable both to access system windowing, as well as be able to provide a window rendered in OpenGL as a subwindow, or as a fullscreen app) stemmed from the attempting to meld multiple implementations, as well as the data. In my new method, classes such as "window" and "font" are simple data (although I will probably include an appropriately wrapped internal pointers allowing classes to store widget-specific cached data), which are then "handled" by implementations. Instead of "gui::button" having a render() function, there is instead a handler that takes care of all implementation details - the equivilant to button.render() would be something like these functions: opengl_context::gui_handler< gui::button >::render( button ); x11r6_context::gui_handler< gui::button >::render( button ); console_context::gui_handler< gui::button >::render( button ); This has nifty features such as allowing, say, a window to be moved between different contexts seamlessly - if managed right anyways. I'm working on building a prototype of this, with the following constraints: 1) Widgets can only be "mounted" once in the GUI heiarchy. The same button can not appear in two seperate windows. This constraint allows me to have a single "context cache" per widget. 2) The first version will only have an OpenGL + FreeType + CSS implementation to start with. I may attempt to port to SDL as well, for the sake of attempting to make sure my implementation truely seperates API from data. 3) Two seperate child lists will be maintained for widgets that have children: Managed and Unmanaged. Managed will be designed for a lifespan that is completely widget-local (the exit button of a window for example) - which will be created directly through template functions of the widget, aka: my_window.create_child< button >( /*...*/ ); my_window.destroy_child( reference_to_button ); This is done to ensure propper new/delete pairing. The data is considered "owned" by the parent, and will be destroyed when the parent widget is destroyed, unless explicitly destroyed earlier via the destroy_child (or equivilant) function. A transfer_child will likely at least be prototyped, to compare against the functionality provided by the second list: A seperate list of "non-owned" (not tied to parent) "children" will be mantained. They will have similar API: my_window.register_child( reference_to_button ); my_window.unregister_child( reference_to_button ); This is done to allow widgets to be displayed that have no fixed owner. A good example would be a sub-window of a program that can be docked. It would be switched from being registered with the window to being registered with one of the "docking groups" of the window. Feedback, links to similar GUI implementations (of which I am aware of none), and critique appreciated. -Mike

Share this post


Link to post
Share on other sites
Current prototype class list:


gui::widget
|-- gui::container_widget
| |-- gui::context
| | |-- gui::opengl_context
| | | |-- gui::glut_context
| | |-- gui::system_context
| |-- gui::window
|-- gui::test_widget


Descriptions

== gui::widget
Base widget from all to inherit from. Currently contains almost no data, except for a dirty flag.

class widget
{
bool m_dirty;
public:
bool is_dirty( void ) { return m_dirty; }
void dirty( void ) { m_dirty = true; }
void clean( void ) { m_dirty = false; }
widget() : m_dirty(false)
{
}
virtual ~widget()
{
}
};





== gui::container_widget (inherits from gui::widget)
This superclass mantains a std::set of pointers to managed children, and another of unmanaged.

class container_widget : public widget
{
//No, I'm not using namespace std; into the global namespace, I've used it into the namespace "industry" which all this is contained in.
set< widget * > managed_children;
set< widget * > unmanaged_children;
public:
virtual ~container_widget()
{
for_each( managed_children.begin() , managed_children.end() , util::deletor<widget *> );
assert( unmanaged_children.empty() );
}
template < typename FunctorT > void for_each_managed_child( FunctorT functor )
{
for_each( managed_children.begin() , managed_children.end() , functor );
}
template < typename FunctorT > void for_each_unmanaged_child( FunctorT functor )
{
std::for_each( unmanaged_children.begin() , unmanaged_children.end() , functor );
}
template < typename FunctorT > void for_each_child( FunctorT functor )
{
for_each_managed_child( functor );
for_each_unmanaged_child( functor );
}
template < typename WidgetT > WidgetT * create( void )
{
WidgetT * w = new WidgetT();
managed_children.insert( static_cast< widget * >(w) );
return w;
}
template < typename WidgetT > void destroy( WidgetT * w_arg )
{
widget * w = static_cast< widget * >( w_arg );
assert( managed_children.find( w ) != managed_children.end() );
managed_children.erase( w );
delete w;
}
void mount( widget * w )
{
assert( managed_children.find( w ) == managed_children.end() );
unmanaged_children.insert( w );
}
void unmount( widget * w )
{
assert( managed_children.find( w ) != managed_children.end() );
unmanaged_children.erase( w );
}
};




== gui::context (inherits from gui::container_widget)
Here is were some of the compiler mojo starts to get ugly. It defines a virtual, TEMPLATIZED function, which has no body. It is intentionally not a pure virtual, as not all contexts will be able to render all widgets. I'm worried about wheither or not the compiler will generate sufficient virtual tables. Worst comes to worst I'll probably go over and create my own psuedo-virtual tables.

class context : public container_widget
{
public:
template < typename WidgetT > virtual void render( const WidgetT & w );
};




== gui::opengl_context (inherits from gui::widget)
More compiler mojo. It implements specific widgets. Rendering of widgets in contexts that don't have rendering functions should compile (since It's valid C++), but generate link time errors (since they won't have an instantiation). Hopefully this will be workable such that a user can provide a function like so:

template <> void gui::opengl_context::render< my_widget > render( const my_widget & w );


class opengl_context : public contexet
{
public:
template < typename WidgetT > virtual void render( const WidgetT & w );
};




== gui::glut_context (inherits from gui::opengl_context)
This is the first of the almost-OS level contexts. This is still in the realm of userland data only - this context gets "rendered" by gui::system_context. This isn't implemented yet, but will be shortly.

== gui::system_context (inherits from gui::context)
This is a very special context. User instantiation of this class will be prohibited. This context "renders" other contexts. That is, this is where the mojo to provide a context is provided. It makes the calls to make the glut window sync up with the gui::glut_context data, resizing the window, rendering it, etc, as needed. Unimplemented as of yet.

Feedback appreciated.

-Mike

edit: tabs fixed
edit: Frogot these classes:

== gui::window (inherits from gui::container_widget)
Standarish widget. Pretty self explanitory - it represents a window.

== gui::test_widget (inherits from gui::widget)
This is a widget to be used to aid in the testing of the library. Pretty much, this should be rendered by a context somehow - dosn't matter really. A context could render this as a green square, or an implementation of gl gears.

edit: I've also fixed the classes found in the gui namespace to all have virtual dtors.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
I went thru a similar dilema and in the end decided to keep it simple and use a tried and proven approach, C++ becomes a headache when trying to do cross compatible template classes.

I use a C interface on my GUI library, but internally I do use STL here and there, the API mimics that of Windows, and is cross platform thru the use of Interfaces.

an Interface is a dynamic library (dll) which implements the required functions for each particular platform, so there is an SDL interface, OpenGL Interface, and I plan to add a D3D interface, but I've never done D3D, so I am leaving that for later.

Latelly I have been thinking about using a separate interface for input as well.

Anyway, my library is LGPL, you can get the code from here or thru my public CVS using :pserver:anonymous@cvs.aeongames.com:/cvsrepo as CVSROOT, just enter for password and module glitch.

Its work in progress, but you may find it useful. [smile]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this