When I first started using Shoes, it took me a while to get the various pieces set in my head. There's a couple of different levels to understand. There are a number of
tutorials about layout, the most fundamental being
Stacks and Flows. This is critical to understanding to design your GUI, but it wasn't enough for me to really understand what was going on.
After
hacking around,
reading through the source code, and
writing some addons I think I'm starting to get my head around it, so here's an attempt to explain some more of the structure of Shoes.
The most basic piece is Shoes.app. This is the method you use to open a window for a new Shoes application; hello world in shoes is something like.
Shoes.app do
para "Hello World"
end
What this does is twofold... it instantiates a new Shoes::App object, and sets self inside the block to point to that object. The Shoes::App object provides all of the helper methods for creating different components, such as para, button, stack, flow, etc, and is responsible for actually rendering them. The objects themselves don't render, they just define a structure that the Shoes::App object will then render. This is why, when trying to do something in a module or class outside of the block, you need to pass in the app object, or access it through the app method on some already rendered object (all shoes objects have an app method to get back to the app they are rendered in).
How does the structure that Shoes::App builds up look? It is a tree format, that actually looks remarkably similar to the DOM in the web world.
At the root of the tree is the slot associated with the original Shoes::App object. This slot
behaves like a flow, and can be accessed via the slot method on the Shoes::App object; within the Shoes.app block that would be self.slot. (This is important; after reading that the app object was a flow, I originally thought I'd be able to use all of the flow methods immediately on it, but you actually need to call them on self.slot.)
Every node in the tree has an accessor called parent and one called children. Lets examine what these look like in the hello world example above, by adding some debug output:
app = Shoes.app do
para "Hello World"
end
Shoes.debug "Parent is #{app.slot.parent.inspect}"
Shoes.debug "Children are #{app.slot.children.inspect}"
The console now shows:
which is pretty much what we'd expect. The root should have no parent, and the paragraph is pretty much the only element, so it should be the first child. What does that paragraph look like? Lets try
Shoes.app do
p = para "Hello World"
Shoes.debug "Para Parent is #{p.parent.inspect}"
Shoes.debug "Para Children are #{p.children.inspect}"
end
Now we see:
The parent is pointing back to the app.slot (which we now see is not actually a flow, but an instance of the Shoes class, which
flows inherit from.) And the children of a Para object actually contain the text of the paragraph.
Understanding this tree structure was one of the biggest breakthroughs for me, because it means that many of the things I know from manipulating the DOM with javascript in the web world are suddenly applicable to Shoes. I'm no javascript genius, but I have already climbed some of the learning curve there, and being able to apply that to Shoes was eye opening.
One of the things the DOM does really well (and the Shoes model should as well) is allow you to navigate, find, and manipulate things that have been generated someplace else. This is where I got the idea for
ShoeQuery, and will prove extremely useful in my efforts to build a Shoes debugger.
I think this similarity could make Shoes the toolkit of choice for web developers coming to the desktop.