Shoes Widgets - lljk/shoes-stuff GitHub Wiki
So... buttons and edit_boxes are alright, but I want StarMonkeys - where the heck are the StarMonkeys in Shoes?!
Well my friend, Shoes doesn't ship with StarMonkeys - but the good news is that you can build them yourself!
Introducing....
The Shoes::Widget
Widgets are awesome - and you can use them to make StarMonkeys, ChocolateHamsters, CurdledCucumbers, or just about anything else you can think up...
Enough talk - Let's make stuff!
class StarMonkey < Shoes::Widget
def initialize
stack stroke: red, fill: blue do
r = star(80, 65, 5, 60, 40)
r.style(stroke: blue, fill: black)
star(50, 60, 5, 25, 10)
star(110, 60, 5, 25, 10)
a = arc(80, 100, 100, 20, 90, 180)
end
end
end
Shoes.app do
star_monkey
end
Look! You made a StarMonkey! Wasn't that easy? Did you notice the flash of light and puff of smoke? You might be thinking, 'I swear I called this guy a StarMonkey, and not a star_monkey - and how did I make one by just saying his name?' Well, in the same way that you create a Shoes::Button by simply typing button
, or a Shoes::EditBox with edit_box
. Magic. Cool, right? This is an authentic Shoes::StarMonkey you've built here, and it's called in the same way as any other widget.
I know what you're thinking now - you're thinking of amassing a StarMonkey army to do your bidding. So in your Shoes.app block instead of typing merely star_monkey
, you set your eyes on the horizon and enter:
100.times{star_monkey}
'Ha!' you think, or perhaps even utter aloud - 'My very own StarMonkey army!' You run starmonkey.rb
, rubbing your palms together just waiting to see those hundred monkeys and - 'Hey, what gives? I only see one...'
That's true. You only see one, but they're all there - it's just that they're piled up on top of each other a mile high, and you can only see the last of the buggers.
You could do something like this...
Shoes.app do
laverne = star_monkey
shirley = star_monkey
shirley.move(150, 0)
end
...and now you'll see both your StarMonkeys clearly. 'But jeez man,' you'll rightly say, 'creating an army of StarMonkeys this way will take me forever, and I've got to be at my kazoo lesson by six...'
Fret not, your future as first kazoo is safe - this'll just take a second...
Our StarMonkeys are dimensionless - so instead of forming into rows and columns when we create a bunch of them, they all just exist in the same fuzzy void. If we assign a width and a height to our StarMonkeys before we make them, they'll get into rank and file nicely when called upon... Check this out - (the changed and new lines are commented so's to be nice and noticeable)
class StarMonkey < Shoes::Widget
def initialize
stack width: 140, height: 200, stroke: red, fill: blue do ###
r = star(70, 65, 5, 60, 40)
r.style(stroke: blue, fill: black)
star(40, 60, 5, 25, 10)
star(100, 60, 5, 25, 10)
a = arc(70, 90, 100, 20, 90, 180)
end
self.style(width: 140, height: 200) ###
end
end
Shoes.app do
100.times{star_monkey} ###
end
Voila! - a legion of StarMonkeys at your disposal!
##But they just sit there...
They're handsome devils, yes, but at the moment they don't do a whole heck of a lot. Let's back off from the army for the moment (there'll be time yet for world domination,) and focus instead on making a few domestic StarMonkeys to help out with chores around the house.
We'll give each of them a name, and the ability to do some stuff when we tell 'em to...
class StarMonkey < Shoes::Widget
attr_reader :name ###
def initialize(name) ###
@name = name ###
stack width: 140, height: 200, stroke: red, fill: blue do
r = star(70, 65, 5, 60, 40)
r.style(stroke: blue, fill: black)
star(40, 60, 5, 25, 10)
star(100, 60, 5, 25, 10)
a = arc(70, 90, 100, 20, 90, 180)
para name, align: "center", displace_top: 150, stroke: blue
end
self.style(width: 140, height: 200)
self.click{yield if block_given?} ###
end
end
Shoes.app do
sm1 = star_monkey("Laverne"){para "#{sm1.name} to sew L's on my sweaters. ", stroke: green}
sm2 = star_monkey("Shirley"){para "#{sm2.name} to keep BooBoo Kitty company. ", stroke: orange}
sm3 = star_monkey("Lenny"){para "#{sm3.name} to drive the truck. ", stroke: red}
sm4 = star_monkey("Squiggy"){para "#{sm4.name} to collect moths. ", stroke: blue}
end
attr_reader
and attr_accessor
are great ways to give and get information to and from your widgets - and throwing in a quick self.click{yield if block_given?}
lets you tell these StarMonkeys to get off their furry butts and do something when they're clicked on. Someone call Carmine!
###'So I'm back from my kazoo lesson, and I was thinking...' ##I want pop-up StarMonkeys!
Of course you do, who wouldn't?! Well, let's back off even more, and focus on Shirley, she was the cute one. Now, nice and relaxed, stick this in your Shoes and smoke it...
class StarMonkey < Shoes::Widget
attr_reader :name
def initialize(name)
@name = name
window width: 340, height: 400 do ###
s = stack width: 140, height: 200, stroke: red, fill: blue do ###
r = star(70, 65, 5, 60, 40)
r.style(stroke: blue, fill: black)
star(40, 60, 5, 25, 10)
star(100, 60, 5, 25, 10)
a = arc(70, 90, 100, 20, 90, 180)
para name, align: "center", displace_top: 150, stroke: blue
end
s.style(width: 140, height: 200) ###
s.click{yield if block_given?} ###
s.move(100, 100) ###
end
end
end
Shoes.app do
btn = button("call Shirley")
btn.click{
stack do
sm = star_monkey("Shirley"){para "#{sm.name} to keep BooBoo Kitty company. ", stroke: green}
end
}
end
A pop-up Shirley! Wicked... So, what have we done here? Not too much really - check out the commented lines for the changes.
First, there's this...
window width: 340, height: 400 do
...
end
Instead of putting our StarMonkey in the Shoes::App main window, we're going to pop her up in a window of her own.
Doing this changes that crafty keyword self
, which refers now to the window we've created, and not to our StarMonkey as it did before. All those self's we've got in our code are going to get lost! Not to worry - what we're really interested in is the stack
that the monkey is drawn in, so let's refer to that when defining dimensions, what to do when clicked, and where to place it in our new window -
s = stack width: 140, height: 200, stroke: red, fill: blue do
...
s.style(width: 140, height: 200)
s.click{yield if block_given?}
s.move(100, 100)
'Ah... much better...' you'll say, 'But what's with this?'...
btn.click{
stack do
sm = star_monkey("Shirley"){para "#{sm.name} to keep BooBoo Kitty company. ", stroke: green}
end
}
...'Why have you stuck my star_monkey call inside a stack?'
A fine question, and one I can't rightly answer. This is kind of quirky, but my aunt Liza is kind of quirky and she's really a lovely person, so try to just roll with it... If you call a widget from a button click, you've got to stick it in a slot first. Any old flow or stack will do though, so don't worry about it too much.
So, this is great - we can call up Shirley any time we want and get her to keep BooBoo Kitty company. If we threw a couple more readable attributes into Shirley, using attr_reader
, we could check on BooBoo whenever we wanted to as well.
##'But I've got to go to Vegas for a week for a kazoo convention...'
Wow! That sounds like fun! I understand your concern though... You don't want to have to call Shirley every half hour to check on BooBoo - and what if there's an emergency?! It would be so much easier if you could teach Shirley to use the phone, so she could call you if there were a problem with BooBoo.
StarMonkeys are clever critters - you can teach them just about anything. Show Shirley how to use the Observer Pattern, and she'll get in touch with you should anything go wrong with BooBoo...
require 'observer' ###
class StarMonkey < Shoes::Widget
include Observable ###
attr_reader :name
def initialize(name)
@name = name
window width: 340, height: 400 do
s = stack width: 140, height: 200, stroke: red, fill: blue do
r = star(70, 65, 5, 60, 40)
r.style(stroke: blue, fill: black)
star(40, 60, 5, 25, 10)
star(100, 60, 5, 25, 10)
a = arc(70, 90, 100, 20, 90, 180)
para name, align: "center", displace_top: 150, stroke: blue
end
s.style(width: 140, height: 200)
s.click{yield if block_given?}
s.move(100, 100)
end
end
def watch_BooBoo ###
every(0.5){
x = rand(10)
case x
when 3
message = "We're out of Chocolate Syrup!"
changed ###
notify_observers(message) ###
when 7
message = "Hairball Attack!"
changed ###
notify_observers(message) ###
when 9
message = "Send more Fruit Loops!"
changed ###
notify_observers(message) ###
end
}
end
end
Shoes.app do
btn = button("call Shirley")
btn.click{
stack do
sm = star_monkey("Shirley"){
para "Watching BooBoo...", stroke: green
sm.watch_BooBoo ###
}
sm.add_observer(self) ###
end
}
def update(message) ###
para message, stroke: red
end
end
Fire this one up, call Shirley, and then click on her to get her to start watching BooBoo like she promised. See that? Now Shirley can call you whenever she needs to. Maybe this wasn't such a good idea, Shirley can get kind of neurotic. Well, what's done is done - let's take a look at how we taught her to get in touch with us...
First, there's this bit -
require 'observer'
and this bit -
include Observable
These two lines set up your phone service... the Observer Pattern.
Then we define a watch_BooBoo
method, in which we tell Shirley to look out for certain numbers. When she finds them, she assigns a value to the message
variable, and picks up the phone and starts shouting 'Message! Message! I've got a message for you!' She does that using a couple of observer methods:
changed
notify_observers(message)
So Shirley's there shouting into the phone, but we've got to pick up our end if we're going to hear her. From within our Shoes::App, we do a couple of things...
First, in Shirley's block (which is executed when she's clicked,) we tell her to start watching BooBoo. Then we add an observer to Shirley - the Shoes::App itself...
sm = star_monkey("Shirley"){
para "Watching BooBoo...", stroke: green
sm.watch_BooBoo
}
sm.add_observer(self)
Next, we write an update()
method, which will be called whenever our App is notified of a change...
def update(message)
para message, stroke: red
end
And that's that - now you can confidently convene with your kazooist comrades, knowing that Shirley will call you if and when she needs to.
##Nesting your Monkeys
You can call one Shoes::Widget from within another, which doesn't sound so exciting until you really stop and think about it - your StarMonkeys can accessorize! You can get Shirley that cool pair of sunglasses she's been asking you for...
Add this class into the mix:
class CoolShades < Shoes::Widget
def initialize
flow do
angle = 0
lenses(angle)
animate(3){
@lensflow.clear
angle += 30
angle = 0 if angle > 330
lenses(angle)
}
end
end
def lenses(angle)
@lensflow = flow stroke: green, fill: orange do
rotate(angle)
star(50, 60, 5, 25, 10)
star(110, 60, 5, 25, 10)
end
end
end
And now, back in the initialize
method of our StarMonkey class, we put the shades on...
##(class StarMonkey)##
def initialize(name)
@name = name
window width: 340, height: 400 do
s = stack width: 140, height: 200, stroke: red, fill: blue do
r = star(70, 65, 5, 60, 40)
r.style(stroke: blue, fill: black)
star(40, 60, 5, 25, 10)
star(100, 60, 5, 25, 10)
a = arc(70, 90, 100, 20, 90, 180)
para name, align: "center", displace_top: 150, stroke: blue
shades = cool_shades ###
shades.move(-10, 0) ###
end
s.style(width: 140, height: 200)
s.click{yield if block_given?}
s.move(100, 100)
end
end
Doesn't she look lovely with those crazy things?
#Get Crackin'!
So, that's the Shoes::Widget for ya. Handy, right? Create all the SeaGoats, pop-up ToadCrust, and nested NettleKnickers that your heart desires. Just please keep your NettleKnickers away from the StarMonkeys - that's a dangerous mix if I've ever heard of one...