Potentially, there's a lot to pick apart there; so I'll try to give some practical answers to the immediate questions, and then see where things lead from there - it could be one of those "one question leads to another" kind of threads (that's not a problem to me, just that bite-size chunks might be in order! Let me know how I'm doing!)
adamszabo wrote:Which are the things one can use in a class?
I can't think of any obvious limitations that are likely to hurt you too much when starting out. The problems tend to be more with things
outside of the actual classes in my experience - and they're not so much definite "nopes" as "don't run with scissors".
For example, Class and Module names are visible
everywhere in the Ruby interpreter, so you have to be careful about naming and version control, otherwise a rogue toolbox module containing old code might overwrite the updated code that you're working on in your schematic (yes, toolbox modules can execute code - the thumbnails are NOT bitmaps!) Ruby errors due to startup problems are a PITA too - if you define a class once to share among RubyEdits, you have to be absolutely sure that the Class is defined before trying to create any instances - it's very easy to save a "working" schematic, only for it to load with loads of Ruby errors because your Class definition is at the wrong level of module nesting etc.
adamszabo wrote:Whats the difference?
Tricky without getting too technical, but in as plain English as I can say it:
In the second example, Module "Calc"
is the calculator. Not
a calculator,
the calculator. The name "Calc" always points to the module from any RubyEdit any time you use it. Calc is the one true calculator!
OTOH, in the first example, Class "Calculator" is just a "recipe" for making Calculators. You don't actually have a calculator until you call "Calculator.new" to ask nicely for one. And each time you call that, it makes a new one, stored in a new place in memory. Also, the name "@calc" is not global, it's bound to the specific RubyEdit, so in every case, two different RubyEdits will always be referring to two different Calculators (a moot point here, as the "Calculator.new" call is making a new one every single time anyway).
Now, there's nothing wrong with your code per se. When you call "plus" or "minus", you expect that passing the same arguments will always return the same result - it would be a rubbish calculator otherwise! Most Rubyists would probably prefer the Module way to do it (one global calculator is fine here), but the other way is fine too (especially if you might be adding "instance-specific" features later).
Unfortunately, however, calculators were an unlucky choice for demonstrating the difference between Modules and Classes! Calc and every Calculator are defined to behave exactly the same way forever, which hides a lot of those differences, especially as you can't easily see that there are so many Calculators being made.
The big deal with Classes is that every single instance shares "behaviour" with other's of its Class (they all have the same methods), but that each instance can also store some data specific to itself, which the methods have access to. Think of a Midi object, for example - when you call its "status" method, it returns the status of the specific MIDI message that it represents. The Midi Class contains the code of method "status", defined as something like "get the first four bits of the first byte of the stored data and return it", but the data itself belongs to the instance, and every other instance has its own data. Hence, Calculators aren't a very typical Class example, as they don't have any internal data at all (unfortunately, each one still takes up extra memory, though - Ruby's own "housekeeping" data).
That part of Classes is the thing I'd suggest mastering first - Modules can certainly be useful, but really getting to know how a Class definition relates to its instances, and to an instance's "attributes" (its internal data), is the key that opens up everything else. The special cases, whether Modules or FS-specific weirdness, will come a lot easier once you're confident with Classes, IMHO. I also found it useful to think of methods as "stuff you can ask an object to do" rather than as "functions" - maybe that's just my (not very) Maths background; but part of good Class design is to hide away as much gnarly mathsy stuff as you can inside the Class to leave a nice neat "black box" with cute, fluffy method names.
The main difference from RubyEdit code is that a class has to define methods for accessing the data (attributes) of its instances - there are no methods "event", "input", "output", etc., so you have to provide your own methods to allow the external code to interact with the object as you'd like. So I'd look out for a few examples which use @instance_variables inside a Class, the "initialize" method, and/or the "attr" family of methods (one follows shortly!)
adamszabo wrote:Can I call the draw function in a class, and draw object on another ruby instance?
No - don't try to force drawing by calling draw yourself, not even from the same RubyEdit. You don't draw onto the RubyEdit, but to the View object which is passed into the "draw" method from the View link. When you ask nicely for a redraw, FlowStone goes away, prepares an empty View object, works out what other layers might have to be rendered and in what order, then passes the View object around to everyone who needs to draw to it. These View objects are FlowStone's responsibility, as they are basically a low-level interface to Windows' GDI+ rendering engine, and they're only temporary.
If you need to draw an object on a different RubyEdit than you created it in, you have to send it there somehow. The safe way (I'll leave the dangerous ways for another time!) is to use the Ruby "Value" connectors and links - these don't copy an object, they pass a reference to the original to the RubyEdit at the far end. However it can be a bit clunky, as the receiving RubyEdit will have to handle the incoming Ruby object in its "event" method.
However, there are ways that you can usefully delegate drawing and mouse-handling - that is; create objects which represent thing on the GUI, and which can lighten the load for your "draw" and mouse methods. For example, here's a very basic interactive rectangle...
- Code: Select all
class MouseRect
# "initialize" is called whenever a new instance is made, and takes the
# arguments which were passed to "new".
def initialize(color, area)
# Instance variables store the MouseRects's internal data.
@brush = Brush.new(color)
@position = area
end
# Draw the rectangle onto the given view.
def draw(view)
view.drawRectangle(@brush, @position)
end
# Test if the mouse is inside the rectangle.
def mouse_over?(mx, my)
x, y, w, h = @position
mx.between?(x, x + w) && my.between?(y, y + h)
end
end # of class
# Setup up the RubyEdit
def init
@shape = MouseRect.new(Color.new(200, 0, 0), [1, 1, 2, 3])
end
# RubyEdit draw
def draw(view)
# Get the rectangle to draw itself.
@shape.draw(view)
end
# RubyEdit mouse
def isInMousePoint(x, y)
# Ask the rectangle to work it out.
@shape.mouse_over?(x, y)
end
Where this comes in really handy is when there are different kinds of things that you might want to draw...
Let's say we also make a "MouseEllipse" class - just the same as above, but it does Ellipses. We could also do maybe a fancy "MouseStar" class, or whatever we want. Now, so long as those other kinds of object still have the methods "draw(view)" and "mouse_over?(x, y)", they can be swapped with the MouseRect or with each other at any old time, and the RubyEdit just doesn't care - it just calls "@shape.draw(view)", and, whatever kind of shape is pointed at by @shape gets drawn all by itself.
And THAT is the real power of using Ruby classes. By defining kinds of object with the same "interface" - the same method names taking the same arguments - you can make them interchangeable. No "if...then...else" is needed, no "case" statements, maybe I didn't even write the drawing code myself, it doesn't matter - just call the bog-standard method name and let the object deal with it!
As for the question of what can be shared where: I think I'll leave writing more on that for a later post, as it's a pretty big subject in its own right - and one which can tempt us with its possibilities at the same as time as having lots of crocodiles and alligators!