Friday, January 9, 2009

BUI - refactoring, configuration, timers

During this week I spent significant amount of time restructuring and especially refactoring BUI. I also have initial implementation of timers and event manager ready. I just hooked up the event manager to current PyOpenGL frontend today so you can set up key events and assign handlers to them just today. Both parts probably could use at least some sort of rewrite though but at least they work minimally and can be easily extended.

In this and upcoming posts I aim to provide more "user friendly" information. I will try to be more verbose about the basic concepts and possibly reiterate some information that has been already mentioned earlier in the blog. Also to get concrete view on how to actually write scripts using BUI I will likely provide some sort of "recipes" or similar. Probably converting the essential information from this blog into some sort of manual would be a worthwhile effort later.

As a part of refactoring effort I extracted a concept of node from the architecture. A node is something that has references to other children and parent nodes. Analogously you can think them as inputs and outputs. I treat each part of the user interface as a node. A node can be either a layout, an element or even a window.

Layouts (used to be Containers) contain other layouts and elements and define the way elements are placed inside it. At the moment there are three layouts available: free, horizontal and vertical.

Free layout means that all nodes contained within have full freedom to determine their location, width and height. In case of horizontal and vertical layout location cannot be predefined. However hints about width and height can be given. The system figures out the missing bits in as sensible way as possible. Note that default settings defined globally in a configuration can be also overwritten at layouts also. This means that their children will use that local setting instead of globally defined one.

It is important to note that a node width/height has a mode (ie. width_mode), minimum (ie. min_width) and maximum (ie. max_width) attributes. By default width/height is set to auto(matic) mode unless width/height is explicitly given at the user interface definition. This means that it tries to match width/height to the default value (ie. default_node_width). It also takes parent width/height in count and finds minima of default value and parent's value. Note that during evaluation of an "auto" value, set value minimum and maximum are taken in count should they have been defined.

Other modes available are absolute and relative (should rename this as proportional for clarity?). If width/height is given a value explicitly in the user interface definition it's considered absolute by default. This value overrides the default value and is clamped to minimum, maximum and parent's value just like automatic option before.

Currently the remaining option left, relative, scales width/height in relation to its parent's value. At the moment it doesn't take minimum and maximum in count but it probably should.

Next I will cover the current configuration system briefly. To get straight to the business a configuration can look like something like this (taken from example clock.py):


configuration = '''
label: Clock test
width: 1000
height: 200
start_timers: True
hotkeys: hotkeys
structure: root_structure
#default_node_height: 20
'''


I started to use label attribute actively everywhere to signify the part of text that is displayed on the user interface. So if an element has some visible text in the user interface you can expect to use label attribute to access it. This convention is used in PyGTK also. Previously I used "name" attribute for the same purpose. Now however name refers to node's internal name that can be used to find certain node(s) for instance.

width and height refer to the default window's width and height. start_timers can be set True to determine that as the application is executed its timers will start at the same time too. hotkeys and structure refer to default hotkey map and user interface structure used (again candidates for renaming :) ). Those are defined in classes passed onto WindowManager. Other options, such as default_node_width or height, can be set as well.

At later time it should be possible to set up multiple windows and subwindows using configuration. In case of subwindow definition it might be interesting to reuse current layout system so that windows, being nodes themselves, just take place of elements. For time being it's limited to one only. In case of Blender I have to provide limited configuration as Blender's Scripts Window is the window itself and sets already certain attributes.

The last topic of this post, as mentioned in the title, is the timer system. Following snippet contains a simple definition of a timer (yet again from clock.py):


class Timers(AllMethodsStatic):
def update_clock(root_layout, timer, timers):
''' interval=0.5 '''
current_time = get_current_time_as_formatted_string()
clock = root_layout.find_child(name='current_time')
clock.label = current_time


As you can see a timer itself is quite simple. Once a timer is started it will run indefinitely till stopped. To create a timer you need to determine certain kind of function signature, set up interval and write some code. In function signature root_layout, timer itself and a collection of all timers are provided. Note that the signature is bound to change as new requirements arise. :)

root_layout can be used to find nodes of the user interface definition and alter them. In example above current time is set to a label. A timer can change its own status (interval for instance) and stop/pause itself if needed. A timer can also alter the status of other timers.

The pace of development work will probably slow down a bit in the coming weeks as I resume my studies. I hope to find nice excuses to work on the project though.

There are still some crucial aspects (finalize basic systems, make Blender frontend work again) to handle and bugs to fix before first release can be made. Also there are not that many user interface elements available for PyOpenGL frontend yet... I also want to write more test applications to properly test the limits of the library.

No comments: