Thursday, August 1, 2013

First UI Morphs implemented

Here is a short status update for the Morphic implementation. So far, I implemented AthensButtonMorph, AthensRadioButtonMorph, AthensCheckBoxMorph, AthensWindowMorph, AthensTextMorph, and AthensIconMorph. The icon morph is simply a text morph that uses Font Awesome to display good-looking icons.

I made one more change to the event handling functionality. Every time we click on a morph, the according mouse event is triggered, no matter whether we clicked the morph itself or one of its submorphs. In my opinion, this is what programmers want most of the time (just one example: click a button that contains an additional image). In the event handler, we can check whether the event was triggered for the top-most morph. Therefore, we can easily check if we clicked the morph itself directly.

If you want to try the implementation yourself, open the tutorial, execute step 33 and then step 37 and/or step 38 (I will eventually change this, such that you don't have to click that often).

The following image shows step 37. It is a simple example that increases or decreases a number. The tiled background is the world morph that contains all other morphs.

This is the source code for step 37.
|window descText counter optIncrement optDecrement button|
"Step 37: [Morphic Demo] Using basic Morphs."

window := AthensWindowMorph new.
window title: 'Counter Example'.

descText := AthensTextMorph new.
descText text: 'Current value: '.
descText translateByX: 25 Y: 40.
window addMorph: descText.

counter := AthensTextMorph new.
counter text: '0'.
counter translateByX: 150 Y: 40.
window addMorph: counter.

optIncrement := AthensRadioButtonMorph new.
optIncrement text: 'Increment number'.
optIncrement translateByX: 25 Y: 70.
window addMorph: optIncrement.

optDecrement := AthensRadioButtonMorph new.
optDecrement text: 'Decrement number'.
optDecrement translateByX: 25 Y: 90.
window addMorph: optDecrement.

optIncrement onChange: [:val | optDecrement checked: val not].
optDecrement onChange: [:val | optIncrement checked: val not].
optIncrement checked: true.

button := AthensButtonMorph new.
button text: 'Do it'.
button translateByX: 25 Y: 120.
button width: 150.
button onMouseClick: [:evt | |val|
val := counter text asNumber.
optIncrement isChecked
ifTrue: [val := val + 1]
ifFalse: [val := val - 1].
counter text: val asString].

window addMorph: button.
surface world addMorph: window.

The next image shows step 38. As I mentioned in a previous post, every morph has its own transformation matrix. This allows us to rotate, scale, translate, and perform any other matrix operation on the morph. The example only contains buttons for rotation and scaling of the x axis.

Performance Issues

You will probably notice that moving windows and especially resizing windows is quite slow. So far, I did not implement any optimizations to the drawing code. Even the smallest change causes the complete world to be redrawn. I plan to optimize this by redrawing only the parts that actually changed.

More importantly, some operations like resizing a window, trigger multiple changes at once, e.g. changing the window's width, its height, and sending a layout change notification to all submorphs (which might again result in changing things). Most operations (changing height, width, transformation, colors, ...) result in a redraw. Therefore, we actually redraw the world multiple time if we just resize a window once. That's why the performance is currently so bad. I plan to optimize this by implementing a world state recorder that draws the world as fast as possible if there are changes. In the example that I just explained, the world state recorder is notified several times that something changes but only redraws the world after the resizing (and all other changes that the resizing triggers) is completed (remember: JavaScript is asyncronous). Therefore, we only redraw the world once even if the width/height of several morphs changes.

No comments:

Post a Comment