1
'''This module defines the ScrollableLayer and ScrollingManager classes.
6
You have two options for scrolling:
8
1. automatically scroll the map but stop at the map edges, and
9
2. scroll the map an allow the edge of the map to be displayed.
11
The ScrollingManager has a concept of "focus" which is the pixel
12
position of the player's view focus (*usually* the center of the
13
player sprite itself, but the player may be allowed to
14
move the view around, or you may move it around for them to highlight
15
something else in the scene). The ScrollingManager is clever enough to
16
manage many layers and handle scaling them.
18
Two methods are available for setting the map focus:
21
Attempt to set the focus to the pixel coordinates given. The layer(s)
22
contained in the ScrollingManager are moved accordingly. If a layer
23
would be moved outside of its define px_width, px_height then the
24
scrolling is restricted. The resultant restricted focal point is stored
25
on the ScrollingManager as manager.fx and manager.fy.
28
Force setting the focus to the pixel coordinates given. The layer(s)
29
contained in the ScrollingManager are moved accordingly regardless of
30
whether any out-of-bounds cells would be displayed. The .fx and .fy
31
attributes are still set, but they'll *always* be set to the supplied
35
from cocos.director import director
36
from cocos.layer.base_layers import Layer
39
class ScrollableLayer(Layer):
40
'''A Cocos Layer that is scrollable in a Scene.
42
A layer may have a "parallax" value which is used to scale the position
43
(and not the dimensions) of the view of the layer - the layer's view
44
(x, y) coordinates are calculated as::
46
my_view_x = parallax * passed_view_x
47
my_view_y = parallax * passed_view_y
49
Scrollable layers have a view which identifies the section of the layer
52
The scrolling is usually managed by a ScrollingManager.
56
origin_x = origin_y = origin_z = 0
58
def __init__(self, parallax=1):
59
super(ScrollableLayer,self).__init__()
60
self.parallax = parallax
62
# force (cocos) transform anchor to be 0 so we don't OpenGL
63
# glTranslate() and screw up our pixel alignment on screen
64
self.transform_anchor_x = 0
65
self.transform_anchor_y = 0
68
self.batch = pyglet.graphics.Batch()
70
def set_view(self, x, y, w, h):
73
self.view_x, self.view_y = x, y
74
self.view_w, self.view_h = w, h
77
self.position = (-x, -y)
80
# invoked by Cocos machinery
81
super(ScrollableLayer, self).draw()
83
# XXX overriding draw eh?
84
pyglet.gl.glPushMatrix()
87
pyglet.gl.glPopMatrix()
90
'''The viewport has changed in some way.
94
is_event_handler = True
95
def on_resize(self, width, height):
96
self.view_w, self.view_h = width, height
99
class ScrollingManager(Layer):
100
'''Manages scrolling of Layers in a Cocos Scene.
102
Each ScrollableLayer that is added to this manager (via standard list
103
methods) may have pixel dimensions .px_width and .px_height. Tile
104
module MapLayers have these attribtues. The manager will limit scrolling
105
to stay within the pixel boundary of the most limiting layer.
107
If a layer has no dimensions it will scroll freely and without bound.
109
The manager is initialised with the viewport (usually a Window) which has
110
the pixel dimensions .width and .height which are used during focusing.
112
A ScrollingManager knows how to convert pixel coordinates from its own
113
pixel space to the screen space.
115
def __init__(self, viewport=None):
116
# initialise the viewport stuff
118
from cocos import director
119
self.view_w, self.view_h = director.director.get_window_size()
121
self.view_w, self.view_h = viewport.width, viewport.height
123
# These variables define the Layer-space pixel view which is mapping
124
# to the viewport. If the Layer is not scrolled or scaled then this
125
# will be a one to one mapping.
126
self.view_x, self.view_y = 0, 0
128
# Focal point on the Layer
129
self.fx = self.fy = 0
131
super(ScrollingManager, self).__init__()
133
# always transform about 0,0
134
self.transform_anchor_x = 0
135
self.transform_anchor_y = 0
137
is_event_handler = True
138
def on_resize(self, width, height):
139
self.view_w, self.view_h = width, height
141
self.set_focus(self.fx, self.fy)
144
def set_scale(self, scale):
146
self._old_focus = None # disable NOP check
148
self.set_focus(self.fx, self.fy)
149
scale = property(lambda s: s._scale, set_scale)
151
def add(self, child, z=0, name=None):
152
'''Add the child and then update the manager's focus / viewport.
154
super(ScrollingManager, self).add(child, z=z, name=name)
155
# set the focus again and force it so we don't just skip because the
156
# focal point hasn't changed
157
self.set_focus(self.fx, self.fy, force=True)
159
def pixel_from_screen(self, x, y):
160
'''Look up the Layer-space pixel matching the screen-space pixel.
162
Account for viewport, layer and screen transformations.
164
# director display scaling
165
x, y = director.get_virtual_coordinates(x, y)
167
# normalise x,y coord
168
ww, wh = director.get_window_size()
172
# get the map-space dimensions
173
vx, vy, w, h = self.view_x, self.view_y, self.view_w, self.view_h
175
#print (int(x), int(y)), (vx, vy, w, h), int(vx + sx * w), int(vy + sy * h)
177
# convert screen pixel to map pixel
178
return int(vx + sx * w), int(vy + sy * h)
180
def pixel_to_screen(self, x, y):
181
'''Look up the screen-space pixel matching the Layer-space pixel.
183
Account for viewport, layer and screen transformations.
185
raise NotImplementedError('do this some day')
190
# XXX rotation of layer
196
# XXX director display scaling
198
return int(x), int(y)
201
def set_focus(self, fx, fy, force=False):
202
'''Determine the viewport based on a desired focus pixel in the
203
Layer space (fx, fy) and honoring any bounding restrictions of
206
The focus will always be shifted to ensure no child layers display
207
out-of-bounds data, as defined by their dimensions px_width and px_height.
209
# if no child specifies dimensions then just force the focus
210
if not [l for z,l in self.children if hasattr(l, 'px_width')]:
211
return self.force_focus(fx, fy)
213
# This calculation takes into account the scaling of this Layer (and
214
# therefore also its children).
215
# The result is that all chilren will have their viewport set, defining
216
# which of their pixels should be visible.
218
fx, fy = int(fx), int(fy)
220
a = (fx, fy, self.scale)
222
# check for NOOP (same arg passed in)
223
if not force and self._old_focus == a:
227
# collate children dimensions
228
x1 = []; y1 = []; x2 = []; y2 = []
229
for z, layer in self.children:
230
if not hasattr(layer, 'px_width'): continue
231
x1.append(layer.origin_x)
232
y1.append(layer.origin_y)
233
x2.append(layer.origin_x + layer.px_width)
234
y2.append(layer.origin_y + layer.px_height)
236
# figure the child layer min/max bounds
242
# get our viewport information, scaled as appropriate
243
w = int(self.view_w / self.scale)
244
h = int(self.view_h / self.scale)
247
# check for the minimum, and then maximum bound
248
if (fx - w2) < b_min_x:
249
fx = b_min_x + w2 # hit minimum X extent
250
elif (fx + w2) > b_max_x:
251
fx = b_max_x - w2 # hit maximum X extent
252
if (fy - h2) < b_min_y:
253
fy = b_min_y + h2 # hit minimum Y extent
254
elif (fy + h2) > b_max_y:
255
fy = b_max_y - h2 # hit maximum Y extent
257
# ... and this is our focus point, center of screen
258
self.fx, self.fy = map(int, (fx, fy))
260
# determine child view bounds to match that focus point
261
x, y = int(fx - w2), int(fy - h2)
262
self.view_x, self.view_y = x, y
263
for z, layer in self.children:
264
layer.set_view(x, y, w, h)
266
def force_focus(self, fx, fy):
267
'''Force the manager to focus on a point, regardless of any managed layer
271
# This calculation takes into account the scaling of this Layer (and
272
# therefore also its children).
273
# The result is that all chilren will have their viewport set, defining
274
# which of their pixels should be visible.
276
self.fx, self.fy = map(int, (fx, fy))
278
# get our scaled view size
279
w = int(self.view_w / self.scale)
280
h = int(self.view_h / self.scale)
283
# bottom-left corner of the
284
x, y = fx - cx * self.scale, fy - cy * self.scale
286
self.view_x, self.view_y = x, y
288
# translate the layers to match focus
289
for z, layer in self.children:
290
layer.set_view(x, y, w, h)