Search code examples
rubyjrubyshoes

Ruby Shoes4 : how does Sample13 work?


I recently rediscovered Ruby Shoes framework, which is a tiny graphical framework, using the Ruby internal DSL capabilities, and more specifically here Shoes4.

One of the examples (located in ./samples) impressed me in particular: sample13.rb, that I cannot fully understand.

Running this example, you get a canvas with a button named "new". Each time you push this button, a new figure (named box in the program) is added to the canvas (with random shape and color). But even more, you can click these figures afterwards, and move them away.

The code is surprisingly short :

Shoes.app :width => 300, :height => 300 do
  COLORS = Shoes::COLORS
  i = 45

  button 'new' do
    i += 5
    box = rand(2) == 0 ? rect(i, i, 20) : oval(i, i, 20)
    box.style :fill => send(COLORS.keys[rand(COLORS.keys.size)])
    @flag = false
    box.click{@flag = true; @box = box}
    box.release{@flag = false}
  end

  motion{|left, top| @box.move(left-10, top-10) if @flag}
end

I would like explanations about how this code works.

It seems that each figure created is stored somewhere, but where exactly ? Is there a means to have access to the collection of newly created figures ?

Moreover, as it appears, a figure is further added methods such as click and release. Is this the case for each object in Shoes in general (I cannot find that on Shoes website) ?


Solution

  • So the figures are not stored anywhere, but there is quite some scoping magic going on. The click and release elements are available on quite some elements, it should be all elements mentioned here under :click but that list is not fully supported in Shoes4 (yet).

    So let's look at this one by one:

    box.click{@flag = true; @box = box}
    box.release{@flag = false}
    

    So when an element is clicked @flag is set to true (that flag seems to mark that an element is currently being dragged), which is why the flag is set to false when the mouse click is released. Furthermore what happens on a click is that the instance variable @box is set to the box that was just clicked. That is possible as blocks retain the scope they were created in. This means that the scope remembers that box refers to the created box for the block that is called when clicking on the box. This is possible since a reference is saved in the local variable box during the creation of the block, and the block captures this scope.

    So now to the motion:

    motion{|left, top| @box.move(left-10, top-10) if @flag}
    

    Motion captures the movement of the mouse. What it does it moves the currently clicked box (if there is any, hence the @flag) to the position of the current mouse cursor (specified by top and left). I dunno why the -10 is needed but I seem to remember that we had problems with that elsewhere. I'll try to investigate and open issue, but that's not too important here :-)

    Oh and thank you for trying out shoes! :-) Plus thanks for telling me that you can actually drag those around, I didn't even know that!