The Green Shoes Manual 1.1.362
The Rules of Green Shoes
Time to stop guessing how Green Shoes works. Some of the tricky things will come back to haunt you. I've boiled down the central rules to Green Shoes. These are the things you MUST know to really make it all work.
These are general rules found throughout Green Shoes. While Green Shoes has an overall philosophy of simplicity and clarity, there are a few points that need to be studied and remembered.
Shoes Tricky Blocks
Okay, this is absolutely crucial. Green Shoes does a trick with blocks. This trick makes everything easier to read. But it also can make blocks harder to use once you're in deep.
Let's take a normal Ruby block:
Shoes.app do
ary = ['potion', 'swords', 'shields']
ary.each do |item|
puts item
end
end
In Green Shoes, these sorts of blocks work the same. This block above loops through the array and stores each object in the item
variable. The item
variable disappears (goes out of scope) when the block ends.
One other thing to keep in mind is that self
stays the same inside normal Ruby blocks. Whatever self
was before the call to each
, it is the same inside the each
block.
Both of these things are also true for most Green Shoes blocks.
Shoes.app do
stack do
para "First"
para "Second"
para "Third"
end
end
Here we have two blocks. The first block is sent to Shoes.app
. This app
block changes self
.
The other block is the stack
block. That block does NOT change self.
For what reason does the app
block change self? Let's start by spelling out that last example completely.
Shoes.app do
self.stack do
self.para "First"
self.para "Second"
self.para "Third"
end
end
All of the self
s in the above example are the App object. Green Shoes uses Ruby's instance_eval
to change self inside the app
block. So the method calls to stack
and para
get sent to the app.
This also is why you can use instance variables throughout a Shoes app:
Shoes.app do
@s = stack do
@p1 = para "First"
@p2 = para "Second"
@p3 = para "Third"
end
end
These instance variables will all end up inside the App object.
Whenever you create a new Shoes.app window, self
is also changed.
Shoes.app title: "MAIN" do
para self
button "Spawn" do
Shoes.app title: "CHILD" do
para self
end
end
end
Block Redirection
The stack
block is a different story, though. It doesn't change self
and it's basically a regular block.
But there's a trick: when you attach a stack
and give it a block, the App object places that stack in its memory. The stack gets popped off when the block ends. So all drawing inside the block gets redirected from the App's top slot to the new stack.
So those three para
s will get drawn on the stack
, even though they actually get sent to the App object first.
Shoes.app do
stack do
para "First"
para "Second"
para "Third"
end
end
A bit tricky, you see? This can bite you even if you know about it.
One way it'll get you is if you try to edit a stack somewhere else in your program, outside the app
block.
Like let's say you pass around a stack object. And you have a class that edits that object.
class Messenger
def initialize slot
@slot = slot
end
def add msg
para msg rescue puts $!
end
end
Shoes.app do
slot = stack
m = Messenger.new slot
m.add 'hello'
end
So, let's assume you pass the stack object into your Messenger class when the app starts. And, later, when a message comes in, the add
method gets used to append a paragraph to that stack. Should work, right?
Nope, it won't work. The para
method won't be found. The App object isn't around any more. And it's the one with the para
method.
Fortunately, each Shoes object has an app
method that will let you reopen the App object so you can do some further editing.
class Messenger
def initialize slot
@slot = slot
end
def add msg
@slot.app do
para msg rescue puts $!
end
end
end
Shoes.app do
slot = stack
m = Messenger.new slot
m.add 'hello'
end
As you can imagine, the app
object changes self
to the App object.
So the rules here are:
1. Methods named "app" or which create new windows alter self
to the App object.
(This is true for both Shoes.app and Slot.app, as well as window and dialog.)
2. Blocks attached to stacks, flows or any manipulation method (such as append) do not change self. Instead, they pop the slot on to the app's editing stack.
Careful With Fixed Heights
Fixed widths on slots are great so you can split the window into columns.
Shoes.app do
flow do
stack :width => 200 do
background lavender
caption "Column one"
para "is 200 pixels wide"
end
stack :width => -200 do
background bisque
caption "Column two"
para "is 100% minus 200 pixels wide"
end
end
end
Fixed heights on slots should be less common. Usually you want your text and images to just flow down the window as far as they can. Height usually happens naturally.
The important thing here is that fixed heights actually force slots to behave differently. To be sure that the end of the slot is chopped off perfectly, the slot becomes a nested window. A new layer is created by the operating system to keep the slot in a fixed square.
One difference between normal slots and nested window slots is that the latter can have scrollbars.
Shoes.app do
stack width: 200, height: 200, scroll: true do
background "#DFA"
100.times do |i|
para "Paragraph No. #{i}"
end
flush
end
end
These nested windows require more memory. They tax the application a bit more. So if you're experiencing some slowness with hundreds of fixed-height slots, try a different approach.
Note: Fixed height magic (the slot becomes a nested window) is implemented in Green Shoes as a trial so far. Need to add flush
method.
Shoes.app do
stack :width => 200 do
background "#DFA"
100.times do |i|
para "Paragraph No. #{i}"
end
end
end
Image and Shape Blocks
Most beginners start littering the window with shapes. It's just easier to throw all your rectangles and ovals in a slot.
However, bear in mind that Shoes will create objects for all those shapes!
Shoes.app do
fill black.push(0.1)
100.times do |i|
oval i, i, i * 2 if i > 0
end
end
In this example, one-hundred Oval objects are created. This isn't too bad. But things would be slimmer if we made these into a single shape.
# Not yet available
Shoes.app do
fill black(0.1)
shape do
100.times do |i|
oval i, i, i * 2 if i > 0
end
end
end
Oh, wait. The ovals aren't filled in this time! That's because the ovals have been combined into a single huge shape. And Shoes isn't sure where to fill in this case.
So you usually only want to combine into a single shape when you're dealing strictly with outlines.
Another option is to combine all those ovals into a single image.
# Not yet available
Shoes.app do
fill black(0.1)
image 300, 300 do
100.times do |i|
oval i, i, i * 2
end
end
end
There we go! The ovals are all combined into a single 300 x 300 pixel image. In this case, storing that image in memory might be much bigger than having one-hundred ovals around. But when you're dealing with thousands of shapes, the image block can be cheaper.
The point is: it's easy to group shapes together into image or shape blocks, so give it a try if you're looking to gain some speed. Shape blocks particularly will save you some memory and speed.
Note: Green Shoes doesn't support this magic (grouping shapes together into image or shape blocks) so far.
UTF-8 Everywhere
Ruby itself isn't Unicode aware. And UTF-8 is a type of Unicode. (See Wikipedia for a full explanation of UTF-8.)
However, UTF-8 is common on the web. And lots of different platforms support it. So to cut down on the amount of conversion that Green Shoes has to do, Green Shoes expects all strings to be in UTF-8 format.
This is great because you can show a myriad of languages (Russian, Japanese, Spanish, English) using UTF-8 in Green Shoes. Just be sure that your text editor uses UTF-8!
To illustrate:
Shoes.app do
stack :margin => 10 do
@edit = edit_box do
@para.text = @edit.text
end
@para = para ""
end
end
This app will copy anything you paste into the edit box and display it in a Green Shoes paragraph. You can try copying some foreign text (such as Greek or Japanese) into this box to see how it displays.
This is a good test because it proves that the edit box gives back UTF-8 characters. And the paragraph can be set to any UTF-8 characters.
Important note: if some UTF-8 characters don't display for you, you will need to change the paragraph's font. This is especially common on OS X.
So, a good Japanese font on OS X is AppleGothic and on Windows is MS UI Gothic.
Shoes.app do
para "てすと (te-su-to)",
font: case RUBY_PLATFORM
when /mingw/; "MS UI Gothic"
when /darwin/; "AppleGothic, Arial"
else "Arial"
end
end
Again, anything which takes a string in Green Shoes will need a UTF-8 string. Edit boxes, edit lines, list boxes, window titles and text blocks all take UTF-8. If you give a string with bad characters in it, an error will show up in the console.
The Main App and Its Requires
NOTE: This rule is for Raisins. Policeman and Green Shoes use TOPLEVEL_BINDING. So, you can get main
, Ruby top-level object, with the first snippet. Although you need to use Shoes::Para
instead of Para
outside Shoes.app
block.
Each Shoes app is given a little room where it can create itself. You can create classes and set variables and they won't be seen by other Shoes programs. Each program runs inside its own anonymous class.
main = self
Shoes.app do
para main.to_s
end
This anonymous class is called (shoes)
and it's just an empty, unnamed class. The Shoes
module is mixed into this class (using include Shoes
) so that you can use either Para
or Shoes::Para
when referring to the paragraph class.
The advantages of this approach are:
- Shoes apps cannot share local variables.
- Classes created in the main app code are temporary.
- The Shoes module can be mixed in to the anonymous class, but not the top-level environment of Ruby itself.
- Garbage collection can clean up apps entirely once they complete.
The second part is especially important to remember.
class Storage; end
Shoes.app do
para Storage.new
end
The Storage
class will disappear once the app completes. Other apps aren't able to use the Storage class. And it can't be gotten to from files that are loaded using require
.
When you require
code, though, that code will stick around. It will be kept in the Ruby top-level environment.
So, the rule is: keep your temporary classes in the code with the app and keep your permanent classes in requires.
Next: Shoes