Hi,
Sorry if my reply will be too long, but I tried to summarise our experience
with Morphic/Brick and give some useful feedback or even provide ideas. Who
wants will read :)
Brick is not a thin layer anymore.
It depends on what you mean under 'a thin layer' ;) Anyway, Brick was born
out of need. Spotter is completely built using Brick and Rubric. No morphs
are used at all (except Rubric of course). All Bricks in Spotter in the end
are subclasses of GLMBrick, which uses from original Morph only Canvas
(without drawing) and MorphicEvents. Everything else was rewritten from
scratch. During some period of time Brick was able to render itself on
Athens canvas, but we dropped it because of Font issues in athens.
Spotter is not the only tool written using Brick, GLMPager - a pane pager
in GT-Inspector, GT-Playground and GT-Debugger is also completely done
using Brick. Almost all tab labels and tab selector in GT tools are Bricks.
I could say that we collected quite some experience building it. Now I
would like to enumerate features of UI framework we used to solve
encountered problems.
- Local bound coordinates.
Nothing to say more here :) It's somehow obvious to have.
- Detects graph of Bricks that should be invalidated when Brick triggers
invalidation. So there is no need to invalidate the whole graph.
- For example if Brick fills parent it means that it doesn't depend
on children (simple and most common situation). So if any child triggers
invalidation there is no need to invalidate parent as it doesn't
depend on
them. The same works in reversed way.
- Build graph separately for each dimension: width and height. If
only height of Brick was changed and children depend only on width, there
is no need to invalidate them => x2 faster.
- Invalidate resizing and re layouting separately. If there is no
need to reposition bricks when size changes we just skip it.
- Link subbricks in a DoubleLinked list. So subbrickAfter and
subbrickBefore could be done in O(1). Morphic does it in O(n). It iterates
over the whole submorphs collection of the owner finds index if current
morph and then returns submorphs at: index +/-1. submorphAfter is used when
morphic tries to detect which morph should take focus. Take a look at:
Morph>>nextMorphWantingFocus. It iterates over owner's subbricks and for
each asks submorphAfter (O(n)) - resulting time O(n^2)! That is why opening
a window with a lot of submorphs takes so many time, especially system
browser for big classes.
- Native support of *paddings* with css-like syntax:
padding: anArrayOrInteger
paddingTop: anInteger
> paddingRight: anInteger
> paddingBottom: anInteger
> paddingLeft: anInteger
padding: 10. "top, right, bottom, left padding are 10px"
padding: #(10 20). "top, bottom padding 10px. left, right padding 20px"
padding: #(10 20 30). "top 10px. left, right 20px. bottom 30px"
padding: #(10 20 30 40). "top 10px, right 20px, bottom 30px, left 40px"
- Native support of *margins* with css-like syntax:
margin: anArrayOrInteger
marginTop: anInteger
> marginRight: anInteger
> marginBottom: anInteger
> marginLeft: anInteger
Have the same syntax as padding.
- Native support of dynamic *minHeight, minWidth, maxHeight, maxWidth*
minWidth: anIntegerOrBlock "actually any object that implements brickValue:"
> maxHeight: anIntegerOrBlock
minHeight: [ : brick | brick label width * 2 ].
minHeight: 200.
maxWidth: [ self height / 2 ].
maxWidth: 100.
- Native support of *z-index*, so we don't rely on subbricks' order any
more if one brick should be drawn above/below others. Works the same as css
one.
zIndex: 2. "default value is 1"
- Native support of *floating *bricks. Floating bricks don't influence
on any other neighbour bricks in layout.
floating: aSymbol
floating: #right. "top right brick's corner matches top right parent's
corner + corresponding paddings and margins"
floating: #center. "will be in the center of the parent and doesn't care
about other subbricks"
Example use case is scroll bar in spotter - it has floating: #right and
zIndex: 2 so it appears above all list elements. (+ some margins to make it
fancy).
- Native support of horizontal and vertical *aligning*.
hAlign: aSymbol
vAlign: aSymbol
hAlign: #right.
vAlign: #center.
- Horizontal and vertical *shrinking* to fit children. It is possible
to have in one brick two subbricks where one shrink wraps and other fills
parent.
hShrinkWrap.
> vShrinkWrap.
- Horizontal and vertical *filling* with support of percents and min/max
height/width. If in brick one child shrink wraps width (or have static
width) and other fills 100% of parent's width, filling one will take only
available space left by shrinking (or static) brick.
hSpaceFill: aFloat - fills aFloat percents of parent's width
> hSpaceFill. - alias for 100 percents.
> vSpaceFill: aFloat - fills aFloat percents of parent's height
> vSpaceFill. - alias for 100 percents.
- Transparent *border* styling with possibility to separately configure
each side.
borderWidth: anArrayOrInteger.
> borderColor: anArrayOrColor
The same syntax as used for paddings/margins
- Native support of smooth gradient *box-shadows* with rounded corners:
shadowColor: aColor.
> shadowWidth: anInteger.
- Layout events such as:
onLayouted - "called when brick was completely resized and layouted in its
> parent"
onChildrenLayouted - "called when all children are completely resized and
> layouted"
Use case: implementing flexible and responsive UI. Used in GLMLabelBrick to
shrink text and add '...' when it doesn't fit in parent. Example is tab
labels in Playground. Morphic version of tabs shrinks text and change ui of
the tab during drawing. WAT?
- Application dependent themes. To make app work perfect all UI elements
should have the same style. In case of spotter, item preview is created
outside of spotter and there is no way using existing morph theme mechanism
to customize its look&feel especially for spotter, because widgets always
refer to global UITheme. Brick extends theming mechanism and allows to
extend existing themes specifically for needed application and it will be
applied to the whole tree of subbricks. For example spotter can completely
change theme in runtime to dark (without even closing it).
"in context of SpotterMorph" self themer: GTSpotterBrickDarkThemer new. (or
> just replace first line in initialize)
Even having white theme chosen in settings. (all rubric fields become dark
too).
- What we really want to have in Bloc is an easy way to detect that user
clicked outside of specific morph.
What do you want with brick ? A new framework or just an "implementation
> detail" for spotter?
> It is *really* important to clarify this.
Now it becomes more stable, api changes less and less. We already have
quite a number of tests for layouting logic and documentation for basic
widgets.
What we want from brick... I think we want to have a way to build fast and
cheap unique and complex UIs. While it will fit our needs in GT Team we
will use it. We really would like to move all tools to Bloc as soon as
possible, but we just need something right now that works and can be used
in current version. Of course it would be great to cooperate to make Bloc
release sooner.
Some time ago I tried to port Brick on top of Bloc saving all business
logic untouched and only changing events from morphic ones to bloc and
FormCanvas to AthensCanvas. It was rather successful. I will be happy if it
would be possible to do everything or almost everything mentioned above in
Bloc. I think we will never (but who knows) announce Brick as separate
project. In our minds it is just useful helper library.
Thanks for reading :)
Cheers,
Alex