Friday, June 1, 2012

Some farewell thoughts/code on widget layout/placement

A couple of releases ago, an object called Panel was introduced by me to VisualWorks. New UIs were put together with it, including among others: BundleOrderTools, PrereqTool, and new Change Tools, as well as the new Skinny widgets.

Panel was a hard swing away from the traditional VisualWorks layout facilities. It took the position that layout was entirely a container responsibility (whereas the traditional framework puts a lot of emphasis on encapsulating the layout parameters of a widget with it on a one-to-one basis). The advantage of doing it this way, was that you could build layout algorithms that took into account the interplay between the different children better. It was a hard swing, because rather than a rich set of as-yet-understood-ideas, I made egregious use of blocks to pull it off.

A couple of months back (maybe 6 even), I sat down and began playing with some ideas that were a little more "half way." Having done VisualWorks for many years, having explored ideas with Panel, I was interested in addressing the following ideas:

  • Neither Panel nor CompositePart have a good way of differentiating between what their ideally composed size would be from what their layout ended up being. In other words, the preferredExtent of a Composite is just whatever it ended up laying things out as.
  • While various widgets in the system can answer preferredHeight/preferredBounds/preferredWidth/preferredExtent, they don't deal with the need to some times set or tune these values on a per instance basis
  • 95% of layouts are follow one or two axes (e.g. i want a "row" of buttons)
The unfinished product of this is published as WidgetRowsColumnsAndWeaves, replicated into the OR.

It has two primary types of classes in it. One set is the ViewStripe and ViewWeave classes. ViewStripe is a one axis layout container. It can be configured as a row or a column. And ViewWeave is a two axis container, what some might think of as a LayoutGridBag or whatever Java calls those things.

The other half of the classes, are the EdgePlacement classes. These are the worker objects that a ViewStripe or ViewWeave might use to place it's child widgets. They make heavy use of the RectangleEdge classes that were integrated in VisualWorks 7.9.

One of the things I realized when working on this, was that when I think programmatically about widget placement, I *don't* think in rectangles. A widget's frame may be a rectangle, but I don't compose them that way. I think in axes. I might a though process that goes something like "I need a row of widgets. Vertically, I want them all to to hang from the top, inset down by 2 pixels. And horizontally, I want them evenly distributed, so that they're all the same size, with a gap of 4 between them, and edge insets of 5." See how the reasoning is about the axes separately?

So when you configure the layout of a VisualStripe or a ViewWeave, you see messages like

row leftRight stretchAll
row topBottom alignTop

The leftRight or topBottom message will return either a SingleCellPlacememt or a MultiCellPlacement depending on how it's configured.

There are a variety of tests and examples in the code that are worth perusing. Here's a piece of the ViewWeave exampleTicTacToe method:

me leftRight stretchAllCells.
me leftRight perCell alignCenter.
me topBottom stretchAllCells.
me topBottom perCell alignCenter.

That single "two-scrollbar-long" method is able to define a complete TicTacToe game. There's also an exampleCalendar which actually does quite a bit more with the features in there.

I used properties a bit to pull some of this off. You can attach a #cellWidth or #cellHeight property to a widget that can be used to tune it's cell size along an axis. You can do things like set #cellBackground and #cellBorder as well.

Over all, I was somewhat pleased with this. I regret that I won't be staying around to see it to completion. Maybe someone will pick it up and run with it.

Tuesday, May 29, 2012

QueryTwo

As promised, here's one of those "tying up loose ends" things.

A couple months ago, I posted a prototype of some code inspired by JQuery like behavior for VisualWorks view trees. It got a variety of feedback. A lot positive, some skeptical, some mixed.

Recently, I've been working on modifying the newer VisualWorks comparison tool that presents changes in a disclosure/rollup navigation style. I've been working to put filters in it, so you can filter out different kinds of changes (e.g. hide all the category changes, or show just the additions).

I found myself wanting that JQuery like behavior to have lightweight communication between view tree objects again. I did not want to fabricate a model with dependencies just to facilitate some cross talk. So I took a "go around #2" at the idea. This time, no funky syntax. More based on real world needs. Definitely lighter weight.

It's been published as QueryTwo to the Open Repository. What will come of it? I don't know at this point. Maybe it'll get integrated, maybe not, that's up to others to decide now.

Here is an example of me using it in real life (instead of hypothetical examples).

<pre><code>

propogateChanges

(self query)
top;
type: AbstractComparisonRollupView;
do: [:view | view hideTypes: hiddenTypes].
self updateCellFills

</code></pre>

It reminds me a lot of writing Glorp queries. Similar patterns, you create one, send messages to configure it, and then enumerate it. Or kind of like Seaside html writer pattern too. Make one, configure it, execute a block for it.

What follows is a portion of the class comment that describes usage:


The most common case is to ask a VisualPart or Window to create one for you using


self query


This will return a query that has the receiver as the root of its query path. One can send top to the query to shift the root of the query to the top most element of the view tree (e.g. the window at the top).


You can also ask an ApplicationModel to create one


myApplicationModel viewQuery


The pattern is that after creating a query, one sends configurations messages to it, and then invokes various collection methods (e.g. do:). The enumeration methods should be sent after configuration methods. There are couple of different methods that govern which objects in the view tree are traversed, they come in pairs:


Traversal Configuration Messages


up - causes the query to proceed upwards through the parent path from the root object
down - causes the query to proceed downwards through the children of the root object (this is the defaut if neither up nor down is sent to configure the query)


withRoot - causes the query to include the root in its traversal
withoutRoot - causes only parents or children (as configured by up/down) to be traversed (this is the default if neither withRoot or withoutRoot is sent to configure the query)


one - causes the query to cease traversal after the first match is found and enumerated
many - causes the query to traverse all elements, matching as many as encountered that match (this is the default if neither one or many or sent to configure the query)


Queries


Adding queries controls which elements of the traversal are "matched" and thus show up in things like do: enumerations. By default the query will match everything. Methods found in the queries method category provide utility methods for setting up some common queries. Ultimately, they all pass through the addQuery: method. The argument to the this method is a block, which is cull:ed for every element in the traversal, and for those that answer true to this block, they will be enumerated. Repeated query configuration messages, will AND the queries together. The method reset will return the query to its default [true] state.


Examples


(self query id: #foobar) any
"will return the first element with id of #foobar"


(self query top property: #frame satisfies: [:rect | rect area isZero]) not invalidate
"invalidate all widgets in my window that have real area to them"


(self query up withRoot) do: [:each | each flash]
"flash me and all my parents"


(self query top hasProperty: #UpdateGroup) do: #update
"send #update to all elements in my window that are marked with the #UpdateGroup property"

Stepping Out of the Balloon

Many many years ago, I returned from an LDS Mission in the lovely country of Norway. It was Christmas of 1991. I took a job at what was then Siemens Nuclear Power. In the months that followed, I was introduced to this novel computer programming language called Smalltalk. I took to it, and I like to think it took to me. For the last twenty years, I've done quite a few things with it. From writing nuclear fuel design and assembly software (which are still running today) to making sure that the french fries, green beans, and much of the rest of the world's food is a little cleaner and better. From numeric modeling to implementing frivolous things like roman numeral message selectors and goto. And quite a bit of toolsmithing. To say it's "served me well" is an understatement.


The wonderful world of Smalltalk technology and philosophy, wouldn't have been as enriching for me, if not for the wonderful community of people I have rubbed shoulders with over the years. I remember my first post via bitdearn to comp.lang.smalltalk back in 1994. Meeting people at conferences such as OOPSLA, ESUG, STIC, and others. I have made a ton of friends and come to admire the work and enthusiasm of so many people.

Working at Cincom, the "original" commercial Smalltalk vendor, has always been a sort of pinnacle in my Smalltalk pilgrimage. A chance to be at a hub of where Smalltalk was happening at.



But all journeys must come to an end. And the time for this journey, for me, for now, has come to an end. On June 4th, I will begin work at Nelson Irrigation, doing embedded automation work, sprinkled (that's a pun) with a variety of end user application work. I am super excited. It's a neat project, a neat company, and an indescribably neat culture.

But it means I'll be dropping out of that central involvement in the Smalltalk community. I may still do some Smalltalking for sure, and the ethos that is Smalltalk will permeate all the work I do, but it's unlikely I'll show up at a Smalltalk conference in the near future or be active in the mailing lists as a heavy contributor.

And so it's in some ways, a probable good bye for me. And that makes me sad. And yet happy, because it's better to feel sad about what I'm losing with the community, than thrilled to be shot of it all.

I also want to point out something my departure from Cincom does NOT mean. There have been some others prominent names that have left Cincom recently, and one might assume there was a sinking ship meme going on. Such is simply not the case with me. The timing of this opportunity to learn and be involved in some new and different things, was out of my hands. When it surfaced, unfortunate timing aside, I felt I could not pass the opportunity up. So please don't read any sort of ill boded fate for Cincom or VisualWorks into my departure. I have faith in the people that remain, and in the people that will replace me, I'm sure they'll take the balloon farther and better heights than I was capable of. Any age or oddities aside, it remains some of the best tech that is out there.

As for this blog, I'm not sure what will happen. The purpose of this blog was always meant to be about Smalltalk, and in particular the live "biological" nature of the Smalltalk program philosophy. There are one or two things of the normal ilk that I'd like to write about based on some work I've been doing of late, and then, it'll likely take a hiatus, possibly permanent.

If our paths don't cross in the future, in the immortal (and skewed) words of Spock, may you "Learn Long and Prosper." And remember, "Dead men never wish they'd spent more time at the office."


Tuesday, May 15, 2012

Caching and The Way of the Object

Lately, we've had an internal debate about how to make some places where we do sorting go faster. Of course, there's always the caveat: Make it Work, Make it Right, and if you need to, Make it Fast. Learning when you need to care about that last step, is always a sort of art based on experience. Often the answer is simply "all other things considered, it's fast enough, I've got bigger problems to solve elsewhere."

Let's take an example though. Take a collection of Class objects (we'll leave the MetaClasses out), and sort them by their response to toolListDisplayString:

| classes |
classes := Object withAllSubclasses reject: #isMeta.
classes sorted: #toolListDisplayString ascending

In our example, we're using VisualWorks' ability to substitute simple Symbols as replacements for BlockClosures that send a single unary message to their arguments. It is equivalent in behavior to the more traditional:

| classes |
classes := Object withAllSubclasses reject: [:each | each isMeta].
classes sorted: [:each | each toolListDisplayString] ascending

Sending ascending to a BlockClosure before using it was first developed back in this post and this following post. And that was then integrated into VisualWorks 7.8 (or was it 7.8.1?).

The problem with our example, is that the method toolListDisplayString is not cheap. It's more than just concatenating strings for class and namespace names together. It looks at how much context needs to be added to the class name by itself to make it unique. Or put another way, since there are multiple classes in the system with the name Text, it determines it must add some info about the containing namespace, while the class PostgreSQLEXDIBLOBManipulationOutsideTransaction probably only has one instance and doesn't need any namespace info to contextualize it.

The core default sort algorithm in VisualWorks is hybridization of quicksort and a insertion sort. The implications for this, is that this somewhat expensive toolListDisplayString method may be called repeatedly for some objects. That means redundant CPU cycles.

A common solution to this kind of problem is memoization. Memoization basically is a fancy word which means "cache the results of your computation function, so you only evaluate the function once for each unique input and just look up the cached result for subsequent calls."

How to go about doing memoization around sorting sites can be accomplished a number of different ways.


In Place

The first and simplest way is to simply do it right at the sort site. We could rewrite our example to read:

| classes |
memory := IdentityDictionary new.
classes := Core.Object withAllSubclasses reject: [:each | each isMeta].
classes sorted: [:each | memory at: each ifAbsentPut: [each toolListDisplayString]] ascending

This is the simplest thing that could possibly work. That's its single advantage. The disadvantages is that adds a bit code bloat for every place we decide this is worth doing. It intermingles with what was otherwise pretty simple and easy to read. To flip back and forth between memoized and non-memoized is a pain. And it gets done again and again and again at each call site, so there's no real reuse involved. The risk of implementing it wrong is retaken at each implementation.

The desire to be able to easily flip back and forth between memoizing and not, shouldn't be underrated. Memoization is not free. It costs cycles. It is usually trial and error under conditions that the programmer knows to be common for his code, that determine if the overhead of memoizing is less than the cost of the original redundant function.

This technique is best for those that like to write more code. If you like to brag about how much code you've written, how many lines, classes, or methods, this might be for you. It's simple, and you can demonstrate your superior typing speeds.


More Sort Methods

Another approach is to add a new sort method. VisualWorks already has sort, sort:, sorted, sorted:, sortWith:, and probably some I've missed. Application developers tend to add one or two of their own. A common one in the past has been sortBy: which supports using a single arg block. So you figure out how many of these APIs you want to replicate as memoized alternatives and implement them, for example: memoizedSortBy:, etc. This is if you're a good citizen. If you're not so kind, you use something that looks like just another general purpose sorting API (e.g. sorting: aOneArgBlock).

Implementing memoizedSortBy: gives you the advantage of optimizing things a little differently. You can choose to build a parallel vector of objects collect:ing for the function, retaining index information, sort those, and then basically apply those indices to the original input set. Or you can just go with the Dictionary and at:ifAbsent: approach.

Now the only change we need to make to our call site is to change it to:

| classes |
memory := IdentityDictionary new.
classes := Core.Object withAllSubclasses reject: [:each | each isMeta].
classes memoizedSortBy: [:each | each toolListDisplayString]

You'll note that we don't have ascending anymore in there. The SortFunctions stuff is basically incompatible with this approach. Since this API wants to work with single arg blocks, which it's memoizing the results for, it has hard coded the sort direction inside of it.

I consider this the C Programmer's (or procedural) Approach. If at first you don't find a function, try, try, another one. That it is in this simplistic form incompatible with the SortFunctions thing, is personally aggrieving to me (we lose the elegance of setting the direction, as well as chaining functions, or deriving our own rocketship sorts). Another disappointment is that it's one more API I have to figure out which one I should use. I see a family of sort methods, and I've got to figure out (or recall) what the different nuances of each are (this one takes one arg, this one takes none, this one takes two, each has different trade offs, etc).

Finally, it limits the technique of memoization to sorting. What if I want to use memoization for collect:ing over a collection that I know has redundant elements. In that case, I have to go back to the In Place approach.


The Way of the Object

I'd rather take a page from the SortFunction technique. BlockClosures (or more generally, objects which respond to the message value: and fill the roles of functions) are real Objects too. And I'm declaring that they too have a right to be included in the General Love Fest of Polymorphism. The idea here, is that we add a new memoizing method to BlockClosure (and Symbol too so they can continue to stand double as simple BlockClosures). Sending memoizing to a BlockClosure returns a MemoizedFunction object which can do value: just like a BlockClosure. But it keeps a memory of evaluations and uses those when found. My first cut implementation is published as TAG-MemoizedFunctions in the Open Repository.

Now our example just turns in to:

| classes |
classes := Object withAllSubclasses reject: #isMeta.
classes sorted: #toolListDisplayString memoizing ascending

For this simplistic example, slapping memoizing in there is a 10x speed boost.

What do I like about this approach? First of all, it was fun. This kind of thing, to me, is where the zen of Object Oriented dispatch is at (I don't pretend to be brilliant about this at all, Wilf LaLonde probably wrote an article demonstrating this 20 years ago). I like that it is terse. I like that it localizes the decision about whether to memoize around the function itself rather than the API using it. This is the fastest/easiest way to toggle memoization on and off to profile the differences. I like that I can use it with collect:, or detect:, or allSatisfy:, or any method that makes use of first class function objects. And I like that it only took 10 methods and one class to do. Because Less is More.

Happy Memoizing!

(Why does Apple insist on constantly changing "memoizing" to read "memorizing"? Grumble...)

Monday, April 30, 2012

Smalltalk meets Cubism


Everytime I meet up with Alexandre Bergel at a Smalltalk conference, we talk about Mondrian. And I always ask him a question: "Why is it that every time I see Mondrian, it's always about Rectangles?" In some ways, it's appropriate that Mondrian is always about rectangles. Google Mondrian, and the first images you'll see are all about rectangles. Nearly all of the artwork associated with Piet Mondrian is a love affair with rectangles.

Anyway, I thought it was time to put up or shut up. I want a new question to bug Alexandre about when we cross paths in the future. So I decided to play a little. I wasn't interested in rewriting all that Mondrian is. I just wanted to experiment a little with other polyshapes to express multiple simultaneous attributes of subjects I was trying to visualize. Mostly, I was interested in playing with MeshGradients using Cairo, because I was curious if I could find an interesting problem I could use mesh gradients with.

If you google Cubism, you'll see the artwork that I was inspired by. According to Wikipedia

In cubist artworks, objects are broken up, analyzed, and re-assembled in an abstracted form—instead of depicting objects from one viewpoint, the artist depicts the subject from a multitude of viewpoints to represent the subject in a greater context.

That sounded exactly like what I was trying to do. I published my couple-day-prototype in the Open Repository in a package called TAG-Cubist.  I realized in playing with this how it's not just about rectangles (of course), but how important layout of the per-subject-graphics is. I didn't do anything, other than present then in a tiled format. I'll leave that kind of thing to others. Here are screencaptures of the four example methods I put on the Portfolio class (a Portfolio is a collection of similar drawings for a group of different subjects).

Some of the methods found in the Portfolio object, showing clockwise, from noon high position: LoC, inst var ref count, selector size, argument count, and bytecodes.

Some of the top-level packages found in my image, showing clockwise, from noon high position: prerequisites, defined classes, extended classes.
Classes found in the ArithmeticValue class hierarchy, showing clockwise, from noon high position: methods, inst vars, refs to globals, global refs to the class (attributes suggested by Bob Hartwig, thanks!).

Top-level bundles in my image, showing clockwise, from noon high position: child packages, child bundles, prerequisites, comment size, defined classes, methods of defined classes, extended classes, extension methods.

If I was to go on playing, I'd basically start to reinvent Mondrian. Which is not something I really wanted to do. I might play with the way the shape is generated some more, make it more of a star graph (whereas it's a sort of spider plot right now). And I'd definitely figure out how to do a legend plot.

Wednesday, April 11, 2012

Cairo 1.12.0

A couple weeks ago, the good folks that give us Cairo announced that version 1.12.0 is ready for consumption.

In the last couple weeks, at sundry moments, I've been updating the VisualWorks binding to it, and playing with it some. There's some exciting news and some not-yet-good news. First, the good.

Doing a build for Linux was quite simple. And that allowed me to update/play right away. You can build it yourself, or ask me, I'm curious how portable the binaries I built are for consumption.

On of the exciting things new in 1.12.0 is at long last, Mesh Gradients. Mesh gradients are the "you can do anything with it" gradient. They can be used to simulate any other kind of gradient. Radial and Linear gradients can be done with them (Cairo gives us first class versions of these already). You can also do the elusive conical gradient.


Here'a round one (they don't have to be round) which varies evenly on hue. This can be fun to build pie charts with. Or you can build a conical gradient useful for doing highlighting masks after clipping to a shape


You can do more complex things with them, such as display map data, but I didn't find any simple ones to read and map to mesh (if anyone knows of some, let me know). Other than cones I haven't done to much with the meshes yet. But I'm excited to figure out what kinds of data visualization one could do with them for an application that might be written with VisualWorks. I have an idea of something, but... that'll have to wait for a later post if it works out.

The not-yet-good news is that so far, I only have this working on Linux. The build method I've used in the past, doesn't seem to work with this new version (it could just as well be that I'm now running Lion and newer versions of Xcode). I did manage to grab a binary from someone who had built just prior to release. While that worked for many things, it crashed when using the MeshGradient stuff. And the same goes for Windows so far. I hope to do some looking and have more/better news on these fronts in the weeks to come.

A Post STIC Talk Thought


At STIC I gave a talk about Skins. Skins are a way of separating display behavior from widgets, and trying to rectify some of what we see as "no longer sound" design decisions. At one point, I had put this graphic up on the screenfollowed by this one


It describes the central, fatter role, we're trying to give widgets we develop with the Skin mindset in mind. The one thing this kind of Widget does not do, is actual drawing.

I was asked later on "so, if we wanted to do some new widgets in with this newer GUI approach, how would we do this? Would we make a new skin?

The answer is no. Or at least, most likely not. The whole point of separating the drawing behavior into stateless policy objects, is to deal with having a single widget object, that can be rendered in different look and feels. But for most custom widget development, you don't care about different looks. You're not trying to ape Windows or OSX or something else, the look of your widget tends to be uniform. And so you don't need the whole separation/decoupling that a skin approach provides. You just have your Widget do it's own #displayOn: method.


Monday, March 12, 2012

Apple's MVC Song

While prepping for my talk at STIC about Skins, I stumbled across this homemade song and video from the folks at Apple about Model-View-Controller. The first minute or so is skippable, but the lyrics are cute once the song starts. Look for the Smalltalk hat tip at 4.14.



What's (again) interesting to me is how fungible the controller aspect is. Every MVC framework you meet treats Controller a little differently. Everyone agrees that core state and ui presentation should be separated. Makes good design sense for a variety of reasons. The devils in how you bridge or connect the two. And everyone then calls this Controller. It would have been better to have renamed the pattern Model-View-Connector

Tuesday, January 17, 2012

TAG-MemoryUsage

Prompted by a discussion in the VWNC mailing list, I decided to write a little tool to help me see where and how much memory is being used by what objects.

Unable to display content. Adobe Flash is required.

Friday, January 13, 2012

Some Edgy Reification

Ever had that experience where you're working on something, and it just feels like something's missing? You muddle on for a while. You begin to suspect there's an object whispering in the digital ether to come into being. When it works out (it doesn't always), I've found it to be one of the more rewarding experiences along the Zen Path of All Objects All The Time.

Working with GUIs and widgets and layout, an object I get to know real well is Rectangle. It's a pretty classic object that you find in many a class library, Smalltalk or otherwise. When working with widget trees and layout, rectangles make a lot of sense, because they store (directly or indirectly) the data you need to approximate most widget's layouts (e.g. top, left, bottom, and right). But when it comes to actually working with layout, my experience has been that often just Rectangles are sub optimal.

Generally, the types of behavior Rectangle supports are methods that work with all or most of the 4 implied values of a rectangle. But much of layout involves dealing with just one side of a rectangle. When we're left aligning some rectangles, we care about their left first, and then possibly all of their rights. So we often see code that looks like

newBox := bounds left + someDelta @ bounds top extent: bounds extent.

We didn't want to have to care about tops or extents.

We could do
aBox left: aBox left + someDelta

but I see less of this pattern actually.

A more involved example (no code) involves determining the bottomRight corner of a rectangle for a widget that may or may not have scrollbars around it. First we check if we need to move the right in for a scrollbar, and do so by the thickness if necessary. Then we use that width to determine if we need to show a horizontal scrollbar now. And now if we adjusted for that, we have to check one more time to see if we didn't need a vertical scrollbar before but do now. We care about first the right side, then the bottom side, then the right side.

Another pattern I've seen is that there are many types of widgets and layouts that can be either vertical or horizontal. A row layout container and column layout container are very similar, but one works along the x axis and the other the y axis. Consider a chunk of code that given a list of rectangles, stretch their widths from the leftmost to the rightmost, proportionately by their current widths.

| sumWidth scalar lastRight |
sumWidth := aBoxList inject: 0 into: [:sum :each | sum + each width].
scalar := sumWidth / (aBoxList last right - aBoxList first left).
lastRight := aBoxList first left.aBoxList do:
[:each |
each left: lastRight.
each right: (lastRight := each width * scalar + each left)]


If I want to reuse that same algorithm to work in a vertical direction, I get to copy/paste it and modify as such

| sumHeight scalar lastRight |
sumHeight := aBoxList inject: 0 into: [:sum :each | sum + each height].
scalar := sumHeight / (aBoxList last bottom - aBoxList first top).
lastBottom := aBoxList first top.aBoxList do:
[:each |
each top: lastBottom.
each bottom: (lasBottom := each height * scalar + each top)]


It gets even funner when you're working with points and have to transpose all of the constant x values for constant y values.

The Free Dictionary defines reification as
To regard or treat (an abstraction) as if it had concrete or material existence.

What I found working with these types of problems was that there was an object for a Rectangle's edge that really wanted to come into being. RectangleEdge is an abstract class defining the API, and polymorphic subclasses for TopEdge, LeftEdge, RightEdge, and BottomEdge take care of the particulars. Having an object which reifies the edge of a rectangle allows us to talk about rectangle manipulation at a higher level for many problems.

What follows is an overview of the classes' use and APIs. Followed by a revisit of the above examples, but using edges.

Creation


A particular edge can created by sending leftEdge, rightEdge, bottomEdge, or topEdge to an instance a Rectangle.

aRectangle bottomEdge

Or by sending the message of: to a given EdgeClass
BottomEdge of: aRectangle


A selection of some of the handier high level messages that allow us to talk about rectangle edges more directly

Accessing
complementDistance
"How far apart are I and my complement (opposite) edge?"
position
"The placement value for this edge along either the x or y axis as appropriate."
position: aNumber
"Modify my target rectangle such that the edge I reify is now at aNumber."

Adjusting
-= aDelta
"Modify my position so that it is my current position minus aDelta."
+= aDelta
"Modify my position so that it is the sum of aDelta and my current position."
lineUpWith: anEdge
"Change my position so that I line up with anEdge."

Other Edges
adjacentEdges
"Return the edges that are normal or perpendicular to me."
complementEdge
"Answer the edge of my box that is opposite of me."
sameEdgeOf: aRectangle
"Return the equivalent edge of aRectangle to me."

Testing
attractsEdge: anEdge
"Answer whether I'm the kind of edge that would want to be considered in concert with anEdge for alignment purposes."
linesUpWith: anEdge
"Do I associate with anEdge in alignment considerations?"

There's more than these, but these seemed some of the more obvious. I actually came up with these classes when I first came to work at Cincom and was working on the failed next generation UIPainter with Vassili Bykov called "Splash". Having reified edges was a big help in doing snap to alignment and other interactive layout operations. And I've been using it with the work on skins (see the description of doing scrollbar layout above). And have been using it with layout prototypes on beyond the current Panel idea found in VisualWorks. It's likely it will finally be integrated in a build near you, before our next release.

Circling back around to the original examples, I now write code like

aBox rightEdge -= self scrollbarThickness


I think this makes the code more expressive of what I'm doing, instead of being lost in how I'm trying to manipulate a rectangle to get the same result. The layout algorithm is even more interesting. Parameterizing the code shown with which edge classes to use, one can make it work for either horizontal or vertical manipulation, by just changing one variable:

| sum scalar lastPosition edgeClass |
edgeClass := LeftEdge.sum := aBoxList inject: 0
into: [:sum :each | sum + (edgeClass of: each) complementDistance].
scalar := sum /
((edgeClass of: aBoxList last) complementEdge position - (edgeClass of: aBoxList first) position).
last := (edgeClass of: aBoxList first) position.aBoxList do:
[:each |
| lowEdge |
lowEdge := (edgeClass of: each) position: last.
lowEdge complementEdge
position: (last := lowEdge complementDistance * scalar + lowEdge position)]


This reified Edge object has enabled me to build layout algorithms that can be configured to work in either the x or y directions without having to have lots of code duplication.