Hi Aliaksei,

this is a good and interesting read, especially the feedback on making it fast (solution to some performance problems in Morphic).

However, I have the feeling that the css inspired layout syntax is a step backward on what is/was Morphic (which is using other morphs to constrain and adjust layout in the same way as TeX/LaTeX instead of adding padding, align). Was that syntax choosen because layouting with filler Morphs is considered too slow or too complex to be understandable?

Regards,

Thierry

Le 23/03/2015 20:54, Aliaksei Syrel a écrit :
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.
      o 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.
      o 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.
      o 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


Reply via email to