Glamorous Toolkit comes with its own graphical stack, which is composed of:
Sparta, the canvas
Bloc, the core UI framework
Brick, the widget library
This stack has a few properties that make it distinct:
it relies on one rendering tree at all times.
it features a moldable and scalable text editor, made out of regular Bloc elements.
graph layouts are regular layouts. The implication here is that any element can be part of a graph-based visualization.
it is debuggable. There are a significant amount of tools and mechanisms that are built for this.
Let us take them one by one by means of examples.
One rendering tree
Everything you see in this window is rendered using one single rendering tree, all the way from the buttons, to the words in the text and to the visualization seen below. One rendering tree. This offers the possibility to build flexible user interfaces and debug them using the same tools.
Moldable editor made of regular elements
The editor is made out of regular elements. This means that we can mix text with graphics in live editors.
To get an idea of what this means, take a look at the editor example below.
Below we have a visualization of the tree of elements out of which the editor element is formed. You can observe how the leafs are word elements and the red square sits right next to them.
Learn more details about the editor at
Object subclass: #GtWhatIsTheMoldableEditorMadeOfExplanation
The graph layouts in Bloc are expressed in the same way as any other layout. This was a challenge in that graph layouts rely on a structure that includes nodes and edges. Specifically, the challenge was to build this logic without having
Object subclass: #BlElement
uses: TBlTransformable + TBlEventTarget + TBlDebug
instanceVariableNames: 'spaceReference parent children bounds measuredBounds boundsCache eventDispatcher constraints layout transformation taskQueue errorHandler userData visuals flags'
know about edges explicitly.
Every layout encapsulates an algorithm whose execution logic depends on the constraints specified for each individual element. And, to support graph layouts, each element defines the relevant edges as constraints.
Let's consider a concrete scenario. The example below shows all subclasses of
BlFitLayout subclass: #GtGraphLayout
as label elements organized in a grid layout with two columns, where each of the elements is aligned to the center of the grid cell.
classHierarchyWithGridLayout <gtExample> | root | root := self rootWithHierarchyElements. root layout: (BlGridLayout horizontal cellSpacing: 20; columnCount: 2). root children do: [ :child | child constraintsDo: [ :c | c grid horizontal alignCenter ] ]. ^ root asPannableElement
Note how the
alignCenter is specified as a constraint for each element.
Now, we take the same elements only this time we add edge constraints between each pair of labels corresponding to a superclass and a subclass and specify a graph layout.
classHierarchyWithEdges <gtExample> | root | root := self rootWithHierarchyElements. ByteArray withAllSubclassesDo: [ :class | root children detect: [ :label | label text asString = class name ] ifFound: [ :subclassLabel | root children detect: [ :label | label text asString = class superclass name ] ifFound: [ :superclassLabel | | edge | edge := GtGraphEdge new from: superclassLabel to: subclassLabel. superclassLabel constraints graph addConnectedEdge: edge. subclassLabel constraints graph addConnectedEdge: edge ] ] ]. root layout: (GtGraphTreeLayout new verticalGap: 50; horizontalGap: 20). ^ root asPannableElement
The scene is laid out as a horizontal tree. To see the concrete connections we can also add line elements:
classHierarchyWithEdgesAndLines <gtExample> | root | root := self classHierarchyWithEdges. root allChildrenBreadthFirstDo: [ :element | element graph nodesAlongOutgoingEdges do: [ :child | | line | line := BlLineElement new. line constraints ignoreByLayout. line zIndex: -1; border: (BlBorder paint: (Color gray alpha: 0.2) width: 1); fromAnchor: (BlElementBottomCenterAnchor element: element); toAnchor: (BlElementTopCenterAnchor element: child). root addChild: line ] ]. ^ root
All in all, the graph layouts are treated in the same way as the other layouts. This allows us to use any elements in a graph visualization.
Debuggable and explainable
Bloc was developed following the moldable development philosophy: every time we had a problem, we built a tool to expose the probem first. As a result, Bloc has built in tools that explain many details of its inner working. Most of these are found as inspector extensions. Let's look at some.
We take as a base example the first element from the above scene. Inspecting the element produces an inspector with multiple views, each capturing an interesting aspect. Please take a moment to explore the different views.
firstChildOfHierarchyElementsWithEdges <gtExample> "self new firstChildOfLayoutHierarchyElementsWithEdges" | root | root := self classHierarchyWithEdgesAndLines. ^ root contentElement children first
A simple view is the one that previews the metrics of the element:
This is a view that is common in webbrowser inspectors. But, let's investigate a few more.
Complicated scenes with deep nesting can pose problems when it comes to understanding how layouts work. For example, every layout needs to measure. The measurement view shows the order of which layouts associated with which elements have measured the current element. In our case, we have 4 such measurements, the last one being from our text element, while the other ones come from the parent elements.
Similarly, the resizers view shows how the current and parent elements specify the resizing strategies. In our case, the text element has
fitContent while all parents have
matchParent. This is highly useful when reasoning about why an element might not appear properly in a scene.
These are but a few interesting tools that are built in.