Monday, September 28, 2009

Scocca - New event system

Long time, no post. Since the last post I have been busy writing programming languages, studying AI, programming PHP+jQuery (MediaWiki extensions) etc. but that doesn't mean Scocca or Cassopi have been forgotten. On the contrary.

I decided to change the design of Scocca. There are a couple of major changes:
  • The programming interface that Scocca provides is going to look literally the same for each frontend. This means that the same import statements work everywhere (just import scocca) and that the frontend used is chosen based on context (ie. it uses Blender frontend when ran inside Blender). It should be possible to choose frontend explicitly as well by either stating that in the script or by using a command line parameter.
  • The element coordinate system is going to be rewritten to use left/right/top/bottom + width/height combination system. Old x and y will still work, though. The idea is that needed layouts may be constructed based on this system. So for instance to produce a horizontal layout you might do something like "foo.left = bar.right" and "foo" object will be on the right side of "bar". It should be possible to provide offsets and combinations too. Furthermore the system should provide means to use relative values.
  • The event system has been redesigned to allow more natural way of binding, and invoking, events. I will cover this in the next code example:
from scocca import Application, keyboard, mouse, press, quit

app = Application()

@press(mouse, 'left')
def say_foo():
print 'foo'

# print 'hello you' when k is pressed. note parameter passing mechanism
@press(keyboard, 'k', parameters=('you', ))
def hello(name):
print 'hello ' + name

# it's possible to call handlers bound to some event in the following way
mouse.press('left') # prints 'foo'

keyboard.press['esc'] = quit # generic quit, implemented per frontend

app.run()
Note that the binding syntax looks identical to the syntax used in the case of nodes (ie. label.drag = handler). I still might drop that app prefix and stick to something like "keyboard.press" for the sake of clarity. I also need to add a way to use modifiers (ie. app.mouse.press('left', modifiers=('ctrl', 'alt', ))) and semantics for drop.

I already have a simple version of "drag" working for the Blender frontend. An element becomes draggable if handlers have been attached to its "drag" event. Simple as that. While the event is being dragged the handlers will be triggered of course. The cool thing about the current implementation is that it works even with native Blender user interface elements. So you could potentially build a user interface designer or something similar without any significant effort.

The next step is to implement semantics for "accept". The idea is that if an element is dragged on an element and dropped on it, the handlers attached to "accept" trigger and receive the dropped element. Then they may operate based on it.

I also have to figure out possible additional semantics for "drag". It might be useful to be able to constraint it somehow (ie. constraint it on some predefined axis, path, bounds etc.). Perhaps the handlers attached to "drag" should be able to mutate the coordinates of the element on the fly.

I just wrote an initial frontend of Scocca for PyQT. Some examples work already both in Blender and PyQT by using the same code. I will drop PyOpenGL frontend and focus on PyQT instead as this way I won't have to implement loads of user interface elements. PyQT has proven to be nice enough to work with so far.

In related news I have began to prototype an image manipulation library (or rather an adapter) to use in Scocca. The project site and some initial code may be found at https://launchpad.net/pyima . Once Scocca is ready enough I will concentrate hooking up pyima with it.

[edit]
Updated the code example to reflect the current situation. I removed the app prefix. Now devices may be accessed directly.
[/edit]

[edit2]
Changed the example to use neater decorator syntax. I prefer this to old one as in this solutions bindings happen in the order they are read.
[/edit2]

Friday, January 30, 2009

Scocca - Blender frontend testable, more examples

One of the best ways to drive development of an user interface library is of course to do something concrete with it. Last weekend I implemented a simple version of Pong just to see how the system scales for it. This resulted in quite a few improvements. I also spent some time working on Blender frontend and porting examples to it. Note that most of those don't work as expected as the layout and style systems need to be revised. Using absolute coordinates to determine user interface node locations should not be a problem, however. Also position attribute works. Using position you can quickly float nodes to different parts of its parent using combinations of "left", "right", "top", "bottom" and "center" keywords (ie. "bottomcenter" would align the node just above the bottom center border of parent).

This morning I ported an example found at Blender's Python API documentation to use Scocca instead. The original code looks like this:

import Blender
from Blender import Draw, BGL

mystring = ""
mymsg = ""
toggle = 0

def event(evt, val): # the function to handle input events
global mystring, mymsg

if not val: # val = 0: it's a key/mbutton release
if evt in [Draw.LEFTMOUSE, Draw.MIDDLEMOUSE, Draw.RIGHTMOUSE]:
mymsg = "You released a mouse button."
Draw.Redraw(1)
return

if evt == Draw.ESCKEY:
Draw.Exit() # exit when user presses ESC
return

elif Draw.AKEY <= evt <= Draw.ZKEY: mystring += chr(evt)
elif evt == Draw.SPACEKEY: mystring += ' '
elif evt == Draw.BACKSPACEKEY and len(mystring):
mystring = mystring[:-1]
else: return # no need to redraw if nothing changed

Draw.Redraw(1)

def button_event(evt): # the function to handle Draw Button events
global mymsg, toggle
if evt == 1:
mymsg = "You pressed the toggle button."
toggle = 1 - toggle
Draw.Redraw(1)

def gui(): # the function to draw the screen
global mystring, mymsg, toggle
if len(mystring) > 90: mystring = ""
BGL.glClearColor(0,0,1,1)
BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)
BGL.glColor3f(1,1,1)
Draw.Toggle("Toggle", 1, 10, 10, 55, 20, toggle,"A toggle button")
BGL.glRasterPos2i(72, 16)
if toggle: toggle_state = "down"
else: toggle_state = "up"
Draw.Text("The toggle button is %s." % toggle_state, "small")
BGL.glRasterPos2i(10, 230)
Draw.Text("Type letters from a to z, ESC to leave.")
BGL.glRasterPos2i(20, 200)
Draw.Text(mystring)
BGL.glColor3f(1,0.4,0.3)
BGL.glRasterPos2i(340, 70)
Draw.Text(mymsg, "tiny")

Draw.Register(gui, event, button_event) # registering the 3 callbacks



After porting it to use my system it looks like this (note that I did not care about the layout, just functionality):

# -*- coding: utf-8 -*-
from Blender import Draw
from scocca.frontend.blender.elements import Label, ToggleButton
from scocca.frontend.blender.window import Window

# TODO: add size for Label (at the best case it would be totally dynamic!) and use PyFTGL for rendering?

window = Window(bg_color='blue')

toggle_status_label = Label(parent=window,
x=72, y=0, color='orange')

mouse_status_label = Label(parent=window,
x=340, y=70, color='white')

help_text = Label(parent=window, x=10, y=200, color='white')
help_text.label = 'Type letters from a to z, ESC to leave'

my_string_text = Label(parent=window, x=20, y=230, color='white')

toggle = ToggleButton(parent=window, label='Toggle',
x=10, y=10, width=55, height=20,
tooltip='A toggle button')

def toggle_pressed():
toggle_state = 'down' if toggle.value else 'up'
toggle_status_label.label = 'The toggle button is %s.' % toggle_state
toggle.press(toggle_pressed)

def add_to_my_string():
my_string_text.label += window.event_manager.last_pressed_key # XXX: ok???
window.bind_hotkey('any_character_key', 'press', add_to_my_string)

def add_space():
my_string_text.label += ' '
window.bind_hotkey('space', 'press', add_space)

def remove_from_my_string():
my_string_text.label = my_string_text.label[:-1]
window.bind_hotkey('backspace', 'press', remove_from_my_string)

def released_mouse_button():
mouse_status_label.label = 'You released a mouse button.'
window.bind_hotkey('any_mouse_button', 'release', released_mouse_button)

def pressed_mouse_button():
mouse_status_label.label = 'You pressed a mouse button.'
window.bind_hotkey('any_mouse_button', 'press', pressed_mouse_button)

window.bind_hotkey('esc', 'press', Draw.Exit)

window.run()



There are still some issues to fix in my version but even as it is it should give some idea of where the differences lie in. Take a good look at the abstraction level and code flow. If you have any ideas how to tweak it further, let me know.

The Blender frontend is pretty much testable. It has some rough spots (font rendering does not scale, missing some wrapping (all menus ie.), layout/style system isn't nice) but that should not stop you from building something simple with it.

EDIT!
Note that I got rid of a couple of now redundant lines (color definitions) in above example. This is because I integrated GrapeFruit color module in Scocca.

Friday, January 23, 2009

Scocca - a new way to define user interface, event system refresh

It can be useful to define user interface just by providing a description just like I have done so far. It can be cumbersome to create one for each script, however. An alternative way to define a user interface is to do it purely in code form. This is what I have been working on since the last post. The idea was that this would make it easier to prototype the style system but I haven't gotten that far yet. Besides this I pretty much rewrote the event system. It's still work in progress and there are some details to finish before it is good enough for basic use.

Next I will provide two of examples to show how the new system can be used. First example will show how to code a simple "Hello world!" application. Latter shows how to create a color picker application.

The code for simplistic "Hello world!" may look like this:

# -*- coding: utf-8 -*-
from scocca.frontend.pyopengl.elements import Label
from scocca.frontend.pyopengl.window import Window

window = Window(width=400, height=200, label='Hello test')

label = Label(parent=window, label='Hello, world!',
width=100, height=20, position='center')

window.run()


There are two important things going on at the code. First of all a Window is created. It is possible to provide it a variety of attributes including the width, height and label of the window. Label, as mentioned in an earlier post, means the visible text part of the window that appears on the top of it. Another important thing to note is how the Label element displaying the "Hello, world!" text has been defined. The most important part of to note is that window is provided as parent for the label so it can be bound to the window and hence displayed. It can also be positioned simply by using position attribute or explicit x and y coordinates (x=20, y=50 for example).

Color picker is slightly more complicated as shown by the code below:

# -*- coding: utf-8 -*-
import sys
from scocca.frontend.pyopengl.elements import Label
from scocca.frontend.pyopengl.window import Window
from scocca.utils.color import pick_color_at_current_cursor_location

window = Window(width=800, height=100, position='topleft',
label='Press "k" to pick color at current location of cursor')

label = Label(parent=window, label='', width=200, height=20,
position='centerleft')

def pick_color(window):
window.bg_color = pick_color_at_current_cursor_location()
label.label = str(window.bg_color)
label.color = map(lambda x: 1.0 - x, window.bg_color)
window.bind_hotkey('k', 'press', pick_color)

def quit_script(window):
sys.exit()
window.bind_hotkey('q', 'press', quit_script)

window.run()


The same ideas as in "Hello, world!" can be seen in this example also. As an addition it contains hotkey bindings. In this case the color picker has been designed so that the user presses 'k' key to capture color under the current location of the mouse cursor. After hitting k the color will be assigned as the background color of the window. Furthermore the color code is displayed in a label. Hopefully the code speaks for itself. Note that the idea of the map function is to invert the color of the label text. There should be a specific function to do this. I have not written an architecture to manipulate colors properly yet though.

The binding idea works more generally as well. Basically it is possible to bind events to nodes in following ways for instance: node.mouse_drag(drag_handler), node.press(press_handler). The central ideas in node events are based on two simple loops of events that go as follows: press-drag-release, enter-over-exit. Drag and latter loop deal with location of the mouse cursor. The difference between drag and over is that in case of drag it expects that certain mouse button is pressed (left mouse button by default). Also drag and over events get triggered each time mouse cursor moves (perhaps I will change this to redraw or add a timer to handle cases in which mouse stays still). This particular part of the system is still under the flux a bit but I think it's the way to go. At least the syntax seems simple enough.

In addition to work made above I also fixed the Blender frontend to work with a simple "Hello, world!" example. Fortunately it wasn't much work. The code of "Hello, world!" is pretty much the same as in case of PyOpenGL frontend except for the imports. In theory it should be possible to port applications written in different frontends just by changing import statements a bit as long as all needed user interface elements and parts of the frontend have been implemented. :) I hope to restore Blender frontend fully along with the old way of defining user interface as soon as possible (perhaps during this weekend even).

Friday, January 16, 2009

BUI - Renaming to Scocca, wiki

As I mentioned earlier, there is already a project named BUI. The project has been renamed as Scocca. Scocca is Italian as pretty much means supporting structure, which this Scocca essentially is. The new project site can be found at https://launchpad.net/scocca . Renaming gave me a chance to migrate to Launchpad. Of course it's bit of a tradeoff.

Google Code and Launchpad have several important differences. Probably the main one for me was the version control system available. Google Code offers SVN while Launchpad offers bzr. SVN is a fine example of a centralized version control system. bzr is a distributed version control system. I have plenty of experience with distributed systems while I have not used distributed systems before. They offer certain advantages over centralized ones however.

The main advantage is that it is extremely simple to branch. This is extremely valuable in open source environment as it allows anyone to easily maintain his own branch of the application. Furthermore the changes can be merged back to the trunk version should that be desired.

The main disadvantage of distributed version control systems is that they are a relatively new phenomenon. This means that the tool support is weaker than in case of SVN for instance. Most of IDEs support SVN in a form or other. Finding an IDE that supports bzr is much harder task to handle.

Of course I still have plenty of things to learn about bzr (how to handle bugs etc. properly) but I believe it's a good time to bite the bullet now than later.

As Google Code, so does Launchpad offer code browser and an issue tracker. Furthermore Launchpad has specific support for blueprints, translations (gettext) and dynamic FAQ (answers). Interestingly it supports cross-project bug reports. This is an important feature for a library. Applications that happen to use it and have their project site at Launchpad can point bug reports dealing with the library directly to it.

Of course it's not all happy, happy, joy, joy. Google Code offers a really nice wiki. This is something that Launchpad does not support. Yet, anyway. Also while blueprints are nice as they allow the users and developers alike to specify features and discuss on them, the page describing the blueprint has to be placed on some external place (say, wiki again?). This is not a problem, just an annoyance. I opened a wiki for the project at http://scocca.wik.is/ .

For time being the idea is that the site will contain the descriptions of blueprints. There is not much information yet though. The motivation behind writing these blueprints is simple. It should be easy to transform these into real documentation. Furthermore they may help to see oversights in design (it's hard to see certain issues while you are delving in the code). And as a bonus the whole process becomes more transparent meaning that the ideas are not just in my head or in the code but in actual, readable documentation. :)

Note that I am not saying Google Code is bad and you should not use it. On the contrary. I think it is an excellent service you should take advantage of particularly if you don't mind using SVN and don't care about the specific infrastructure Launchpad provides.

If you want to get started with Launchpad, check out https://wiki.ubuntu.com/LaunchpadStepByStepInstructions. It may seem a bit strange first with whoamis, push and pull but it actually slowly begins to make sense I hope. :)

My coding time has been severely limited by other duties. I hope to sketch out a style system (think CSS) during the weekend to the wiki and perhaps in code form too. The idea is that this system separates the width/height aspect out of the user interface structure definition. I think this clarifies the design a bit while the user has to provide one extra definition. At minimum the definition gives default style settings such as minimum widths and heights, background colors and such. In that way it will clean up configuration part as well.

There is plenty to do before I dare to release first public version but it's getting there each small step at a time. After all I need to have something to write for the next blog post at least. :)

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.

Friday, January 2, 2009

BUI - restructuring, work on PyOpenGL frontend

Two weeks have gone since the last blog post. I took a tiny break from coding during X-mas but apart from that things have been going quite nicely. To summarize major work done, see following list:
  • Restructured the source to reflect the architecture better. Now there are separate packages for frontend, backend, graphics, utils and tests. It should make more sense now even for people beyond me hopefully. :)
  • Implemented basics of PyOpenGL frontend including window management (only one window at the moment) and a couple of elements (label and separator) for it.
  • Implemented width: auto. This means that the object will scale its width to one of its parent automatically. If width is not set at all, it is considered auto. It is possible to constraint this setting using min_width and max_width. Note that auto setting has to be implemented for height still.
  • Got rid of LayoutManager and replaced it with property based solution. This means updates to object properties (width/height for instance) cascade properly. So for instance if you alter the width of a HorizontalContainer its contents operate accordingly. This also separates the logic a bit better to smaller chunks.
  • Converted elements to their own modules under element package. I am not still exactly convinced that this is the way to go (perhaps there needs to be alias for easier imports?) as in the end each module contains only one or a few classes. I might need to revert this decision at some point.
  • Split up unserialize and clarified its structure.
  • Got rid of Pyglet frontend. It might make sense to use pyglet in graphics package though as it has well optimized drawing methods available.
  • Renamed Containers as Layouts. So instead of VerticalContainer there is VerticalLayout now. This reflects better the purpose of that specific class.
  • Implemented FreeLayout. Children of FreeLayout have unconstrained coordinates. This means you can define the placement of the children freely using x, y (top left corner coordinates!), width and height.
  • Cleared up attribute hierarchy of elements. There should not be any redundant attributes now. I also got rid of x_offset, y_offset and variable as they were pretty useless. I replaced variable using name. Name can be used to refer to certain element(s) internally. This meant I had to specify a way to determine the part visible to the user (think label!). To do this I followed the way used in PyGTK Glade and determined a label attribute.
  • Implemented plenty of OpenGL based drawing functions in the graphics package. Probably quite a few have to be implemented still. :)
At the moment I am working on implementing timers for the PyOpenGL frontend. After this I will focus on the event system. It might take some sort of overhaul to make it work the way I want. GLUT seems to provide lot of needed stuff for me though.

Besides making event system work properly I have to further validate the current code, implement more elements for PyOpenGL frontend, add scrollbars, drag and drop, configuration system (determines default window size), serializer (save work), undo (session recording even?), proper layout system and perhaps something else I can't quite remember at the moment.

I hope to have the first test release done in a few weeks to get some initial user feedback. I am sure there are a lot of rough edges to polish.

I also found out that there is another project that uses the same acronym, BUI. Check it out at http://www.samskivert.com/code/jme-bui/ . Incidentally it seems to be pretty much Java equivalent of this project. Due to the same acronym I might have to rename BUI to something else.

Pytform is another interesting project I happened to stumble upon. Unlike BUI it's totally focused on game creation.

Friday, December 19, 2008

BUI - LayoutManager and much more

Last weekend I progressed on restructuring the code. I managed to get rid of some cruft. Of course there is some new cruft to fix. :)

Here are the major changes made summarized:
  • Implemented LayoutManager to handle calculating all layout related things for all user interface containers/elements. This made it possible to clean up the mess caused by different coordinate systems used by the backend and OpenGL based frontends. In BUI origin of a window is imagined to be at the top left corner. Also the y axis is inverted (value of y is inverse of y in Cartesian coordinate system).
  • Cleaned up the way render is defined in AbstractObject. This leads cleaner frontend code as coordinate doesn't have to be passed on anymore. Also super is no longer needed.
  • Implemented Separator element. It just draws a line based on the container inside which it has been placed. Note that name attribute works only in case it has been placed inside VerticalContainer (vertical flow leads to horizontal separator and vice versa). In this case the name is rendered in the middle of the separator. Also line is not drawn on that specific location. To implement the missing case I probably have to look into Pango and use Cairo to render Pango text into an OpenGL surface or just a raw image which isn't as nice.
  • I also began to separate drawing code into it's own modules. In case of OpenGL the idea is to make the code generic so that the actual import can be handled on the frontend side. This means the same functions can be reused on multiple frontends.
  • Converted Fill to element. It did not make sense to define Fill as a container as it logically cannot contain other elements. Therefore it is an element.
  • Cleaned up the way Blender WindowManager calculates mouse coordinates. It should work alright now. There was a tiny glitch before.
There are still plenty of things to implement before the first official release but at least it's progressing. :) In the next weeks I hope to implement features more visible to the user (CustomContainer with free layout (element coordinates as relative?), event mapper, scrollbars, auto width/height, StyleManager?, ???).