1# This is an example of an combination object: model and view. A number of these are
2# created and added to the superview (see Controller).
3
4require 'osx/cocoa'
5
6include OSX
7
8SQUARE_SIZE = 50
9STROKE_WIDTH = 10
10SHADOW_OFFSET = 5
11SHADOW_BLUR = 3
12
13class ViewModel <  NSView
14	
15	attr_reader :speed
16
17  def initWithFrame(frame)
18    super_initWithFrame(frame)
19		
20		@stroke = STROKE_WIDTH
21		@color = getRandomColor
22		@speed = 0
23		setupShadows
24		setupDrawRect
25		
26    return self
27  end
28	
29	def setNum(n)
30		@num = n
31	end
32	
33	def setupShadows
34		# The shadow will automagically "mask" any white part in a drawn image.
35		# Shadows also work with drawn paths, but leave room for the shadow in the frame
36		# (if object drawing fills frame, then shadow won't be visible).
37		
38		@onShadow = NSShadow.alloc.init
39		offset = NSSize.new(SHADOW_OFFSET, -SHADOW_OFFSET)
40		@onShadow.setShadowOffset(offset) # required to get a shadow.
41		@onShadow.setShadowBlurRadius(SHADOW_BLUR) # default is 0.
42		# The following is not needed since it matches the default. Change if desired.
43		@onShadow.setShadowColor(NSColor.blackColor.colorWithAlphaComponent(0.33)) 
44		
45		# For the off shadow, all that is needed is to create it. It defaults to "no shadow" settings.
46		@offShadow = NSShadow.alloc.init
47	end
48	
49	def setupDrawRect
50		# The draw rect is smaller than the frame, to leave room for the shadow and the stroke width.
51		@drawRect = NSRect.new(@stroke/2, SHADOW_OFFSET + @stroke/2, bounds.width - SHADOW_OFFSET - @stroke, bounds.height - SHADOW_OFFSET - @stroke)
52	end
53	
54	def getRandomColor
55		red = rand(256)/256.0 #forces float
56		green = rand(256)/256.0
57		blue = rand(256)/256.0
58		NSColor.colorWithCalibratedRed_green_blue_alpha(red, green, blue, 1.0)
59	end
60	
61  def drawRect(rect)
62		if superview.shadowSwitch.state == 1
63			@onShadow.set
64		else
65			@offShadow.set
66		end
67
68		@color.set
69		NSBezierPath.setDefaultLineWidth(@stroke)
70    NSBezierPath.strokeRect(@drawRect)
71  end
72	
73	def mouseDown(event)
74		puts "mouseDown for obj #{@num}"
75		
76		# It can be useful to keep this point so that the relationship (distance) between
77		# the object and the pointer can be maintained during a mouseDragged event.
78		# Object-local coordinates.
79		@mouseDownPoint = convertPoint_fromView(event.locationInWindow, nil)
80				
81		# Put this subview on top, if control is set.
82		if superview.moveToTopSwitch.state == 1
83			superview.moveSubviewToTop(self)
84			# or, to move it behind the others:
85			#superview.moveSubviewToIndex(self, 0)
86		end
87	end
88	
89	def mouseDragged(event)
90		myPoint = NSPoint.new(event.locationInWindow.x - superview.frame.origin.x, 
91													event.locationInWindow.y - superview.frame.origin.y)
92		myPoint.x -= @mouseDownPoint.x
93		myPoint.y -= @mouseDownPoint.y
94		
95		if @speed > 0 # was in the process of animating to a new location
96			@speed = 0 # stop the movement
97		end
98		
99		# There is no need to redraw all the objects during a drag.
100		# This method keeps dragging very smooth, even with lots of objects on the screen.
101		# Mark the old rect as needing an update...
102		superview.setNeedsDisplayInRect(frame)
103		# ...update the position...
104		setFrameOrigin(myPoint)
105		# ...and mark the new one too.
106		superview.setNeedsDisplayInRect(frame)
107	end
108	
109	def setMoveDestination(destPt)
110		if destPt.x != frame.x or destPt.y != frame.y
111			@destPt = destPt
112		end
113	end
114	
115	def setSpeed(speed)
116		@speed = speed
117		@angle = getAngleInRadians(frame.origin, @destPt)
118		@xDelta = Math.sin(@angle) * @speed
119		@yDelta = Math.cos(@angle) * @speed
120	end
121	
122	def startMovement
123		# Set the global so the app knows that an object is moving
124		$objMoving = true
125		# start the timer (superview checks to see if it is already going)
126		superview.startTimer
127	end
128	
129	def moveToDestination
130		return 0 if speed == 0
131		currPt = frame.origin
132		if currPt == @destPt  # reached destination
133			@speed = 0
134			return 0
135		end
136		
137		if (currPt.x - @destPt.x).abs < @speed
138			currPt.x = @destPt.x
139		else
140			currPt.x += @xDelta
141		end
142		
143		if (currPt.y - @destPt.y).abs < @speed
144			currPt.y = @destPt.y
145		else
146			currPt.y += @yDelta
147		end
148		
149		# Unlike with dragging, during animation it is inefficient for each object
150		# to call "setNeedsDisplay" as it moves. It is much better to just update the
151		# object's position and let the superview call setNeedsDisplay once. 
152		setFrameOrigin(currPt)
153		
154		return 1 # yes, this object is still moving
155	end
156	
157	def getAngleInRadians(p1, p2) # points
158		x = p2.x - p1.x
159		y = p2.y - p1.y
160		Math.atan2(x,y)
161	end
162end
163
164