|
@@ -1,108 +1,647 @@
|
1
|
1
|
# coding: utf-8
|
2
|
|
-import cocos
|
|
2
|
+import typing
|
|
3
|
+import weakref
|
|
4
|
+from math import floor
|
|
5
|
+
|
3
|
6
|
import pyglet
|
4
|
|
-from pyglet.window import key as wkey
|
|
7
|
+from pyglet.window import mouse
|
|
8
|
+
|
|
9
|
+import cocos
|
|
10
|
+from cocos import collision_model
|
|
11
|
+from cocos import euclid
|
5
|
12
|
from cocos.director import director
|
6
|
|
-from cocos.layer import ScrollableLayer, Layer
|
|
13
|
+from cocos.layer import Layer
|
|
14
|
+from cocos.layer import ScrollableLayer
|
7
|
15
|
from cocos.sprite import Sprite
|
8
|
|
-
|
9
|
16
|
from synergine2.config import Config
|
10
|
17
|
from synergine2.log import SynergineLogger
|
|
18
|
+from synergine2.simulation import Subject
|
11
|
19
|
from synergine2.terminals import Terminal
|
12
|
20
|
from synergine2.terminals import TerminalPackage
|
|
21
|
+from synergine2.xyz import XYZSubjectMixin
|
|
22
|
+from synergine2_cocos2d.actor import Actor
|
|
23
|
+from synergine2_cocos2d.exception import OuterWorldPosition
|
|
24
|
+from synergine2_cocos2d.layer import LayerManager
|
|
25
|
+from synergine2_cocos2d.middleware import TMXMiddleware
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+# class GridManager(object):
|
|
29
|
+# def __init__(
|
|
30
|
+# self,
|
|
31
|
+# layer: Layer,
|
|
32
|
+# square_width: int,
|
|
33
|
+# border: int=0,
|
|
34
|
+# ):
|
|
35
|
+# self.layer = layer
|
|
36
|
+# self.square_width = square_width
|
|
37
|
+# self.border = border
|
|
38
|
+#
|
|
39
|
+# @property
|
|
40
|
+# def final_width(self):
|
|
41
|
+# return self.square_width + self.border
|
|
42
|
+#
|
|
43
|
+# def scale_sprite(self, sprite: Sprite):
|
|
44
|
+# sprite.scale_x = self.final_width / sprite.image.width
|
|
45
|
+# sprite.scale_y = self.final_width / sprite.image.height
|
|
46
|
+#
|
|
47
|
+# def position_sprite(self, sprite: Sprite, grid_position):
|
|
48
|
+# grid_x = grid_position[0]
|
|
49
|
+# grid_y = grid_position[1]
|
|
50
|
+# sprite.position = grid_x * self.final_width, grid_y * self.final_width
|
|
51
|
+#
|
|
52
|
+# def get_window_position(self, grid_position_x, grid_position_y):
|
|
53
|
+# grid_x = grid_position_x
|
|
54
|
+# grid_y = grid_position_y
|
|
55
|
+# return grid_x * self.final_width, grid_y * self.final_width
|
|
56
|
+#
|
|
57
|
+# def get_grid_position(self, window_x, window_y, z=0) -> tuple:
|
|
58
|
+# window_size = director.get_window_size()
|
|
59
|
+#
|
|
60
|
+# window_center_x = window_size[0] // 2
|
|
61
|
+# window_center_y = window_size[1] // 2
|
|
62
|
+#
|
|
63
|
+# window_relative_x = window_x - window_center_x
|
|
64
|
+# window_relative_y = window_y - window_center_y
|
|
65
|
+#
|
|
66
|
+# real_width = self.final_width * self.layer.scale
|
|
67
|
+#
|
|
68
|
+# return int(window_relative_x // real_width),\
|
|
69
|
+# int(window_relative_y // real_width),\
|
|
70
|
+# z
|
|
71
|
+#
|
|
72
|
+#
|
|
73
|
+# class GridLayerMixin(object):
|
|
74
|
+# def __init__(self, *args, **kwargs):
|
|
75
|
+# square_width = kwargs.pop('square_width', 32)
|
|
76
|
+# square_border = kwargs.pop('square_border', 2)
|
|
77
|
+# self.grid_manager = GridManager(
|
|
78
|
+# self,
|
|
79
|
+# square_width=square_width,
|
|
80
|
+# border=square_border,
|
|
81
|
+# )
|
|
82
|
+# super().__init__(*args, **kwargs)
|
13
|
83
|
|
14
|
84
|
|
15
|
85
|
class GridManager(object):
|
16
|
86
|
def __init__(
|
17
|
87
|
self,
|
18
|
|
- layer: Layer,
|
19
|
|
- square_width: int,
|
20
|
|
- border: int=0,
|
|
88
|
+ cell_width: int,
|
|
89
|
+ cell_height: int,
|
|
90
|
+ world_width: int,
|
|
91
|
+ world_height: int,
|
|
92
|
+ ) -> None:
|
|
93
|
+ self.cell_width = cell_width
|
|
94
|
+ self.cell_height = cell_height
|
|
95
|
+ self.world_width = world_width
|
|
96
|
+ self.world_height = world_height
|
|
97
|
+
|
|
98
|
+ def get_grid_position(self, pixel_position: typing.Tuple[int, int]) -> typing.Tuple[int, int]:
|
|
99
|
+ pixel_x, pixel_y = pixel_position
|
|
100
|
+
|
|
101
|
+ cell_x = int(floor(pixel_x / self.cell_width))
|
|
102
|
+ cell_y = int(floor(pixel_y / self.cell_height))
|
|
103
|
+
|
|
104
|
+ if cell_x > self.world_width or cell_y > self.world_height or cell_x < 0 or cell_y < 0:
|
|
105
|
+ raise OuterWorldPosition('Position "{}" is outer world ({}x{})'.format(
|
|
106
|
+ (cell_x, cell_y),
|
|
107
|
+ self.world_width,
|
|
108
|
+ self.world_height,
|
|
109
|
+ ))
|
|
110
|
+
|
|
111
|
+ return cell_x, cell_y
|
|
112
|
+
|
|
113
|
+ def get_pixel_position_of_grid_position(self, grid_position: typing.Tuple[int, int]) -> typing.Tuple[int, int]:
|
|
114
|
+ return grid_position[0] * self.cell_width + self.cell_width,\
|
|
115
|
+ grid_position[1] * self.cell_height + self.cell_height
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+class MinMaxRect(cocos.cocosnode.CocosNode):
|
|
119
|
+ def __init__(self, layer_manager: LayerManager):
|
|
120
|
+ super(MinMaxRect, self).__init__()
|
|
121
|
+ self.layer_manager = layer_manager
|
|
122
|
+ self.color3 = (20, 20, 20)
|
|
123
|
+ self.vertexes = [(0.0, 0.0), (0.0, 0.0), (0.0, 0.0), (0.0, 0.0)]
|
|
124
|
+ self.visible = False
|
|
125
|
+
|
|
126
|
+ def adjust_from_w_minmax(self, wminx, wmaxx, wminy, wmaxy):
|
|
127
|
+ # asumes world to screen preserves order
|
|
128
|
+ sminx, sminy = self.layer_manager.scrolling_manager.world_to_screen(wminx, wminy)
|
|
129
|
+ smaxx, smaxy = self.layer_manager.scrolling_manager.world_to_screen(wmaxx, wmaxy)
|
|
130
|
+ self.vertexes = [(sminx, sminy), (sminx, smaxy), (smaxx, smaxy), (smaxx, sminy)]
|
|
131
|
+
|
|
132
|
+ def draw(self):
|
|
133
|
+ if not self.visible:
|
|
134
|
+ return
|
|
135
|
+ pyglet.gl.glLineWidth(1) # deprecated
|
|
136
|
+ pyglet.gl.glColor3ub(*self.color3)
|
|
137
|
+ pyglet.gl.glBegin(pyglet.gl.GL_LINE_STRIP)
|
|
138
|
+ for v in self.vertexes:
|
|
139
|
+ pyglet.gl.glVertex2f(*v)
|
|
140
|
+ pyglet.gl.glVertex2f(*self.vertexes[0])
|
|
141
|
+ pyglet.gl.glEnd()
|
|
142
|
+
|
|
143
|
+ # rectangle
|
|
144
|
+ pyglet.gl.glColor4f(0, 0, 0, 0.5)
|
|
145
|
+ pyglet.gl.glBegin(pyglet.gl.GL_QUADS)
|
|
146
|
+ pyglet.gl.glVertex3f(self.vertexes[0][0], self.vertexes[0][1], 0)
|
|
147
|
+ pyglet.gl.glVertex3f(self.vertexes[1][0], self.vertexes[1][1], 0)
|
|
148
|
+ pyglet.gl.glVertex3f(self.vertexes[2][0], self.vertexes[2][1], 0)
|
|
149
|
+ pyglet.gl.glVertex3f(self.vertexes[3][0], self.vertexes[3][1], 0)
|
|
150
|
+ pyglet.gl.glEnd()
|
|
151
|
+
|
|
152
|
+ def set_vertexes_from_minmax(self, minx, maxx, miny, maxy):
|
|
153
|
+ self.vertexes = [(minx, miny), (minx, maxy), (maxx, maxy), (maxx, miny)]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+class EditLayer(cocos.layer.Layer):
|
|
157
|
+ is_event_handler = True
|
|
158
|
+
|
|
159
|
+ def __init__(
|
|
160
|
+ self,
|
|
161
|
+ config: Config,
|
|
162
|
+ logger: SynergineLogger,
|
|
163
|
+ layer_manager: LayerManager,
|
|
164
|
+ grid_manager: GridManager,
|
|
165
|
+ worldview,
|
|
166
|
+ bindings=None,
|
|
167
|
+ fastness=None,
|
|
168
|
+ autoscroll_border=10,
|
|
169
|
+ autoscroll_fastness=None,
|
|
170
|
+ wheel_multiplier=None,
|
|
171
|
+ zoom_min=None,
|
|
172
|
+ zoom_max=None,
|
|
173
|
+ zoom_fastness=None,
|
|
174
|
+ mod_modify_selection=None,
|
|
175
|
+ mod_restricted_mov=None,
|
21
|
176
|
):
|
22
|
|
- self.layer = layer
|
23
|
|
- self.square_width = square_width
|
24
|
|
- self.border = border
|
|
177
|
+ # TODO: Clean init params
|
|
178
|
+ super(EditLayer, self).__init__()
|
|
179
|
+
|
|
180
|
+ self.config = config
|
|
181
|
+ self.logger = logger
|
|
182
|
+ self.layer_manager = layer_manager
|
|
183
|
+ self.grid_manager = grid_manager
|
25
|
184
|
|
26
|
|
- @property
|
27
|
|
- def final_width(self):
|
28
|
|
- return self.square_width + self.border
|
|
185
|
+ self.bindings = bindings
|
|
186
|
+ buttons = {}
|
|
187
|
+ modifiers = {}
|
|
188
|
+ for k in bindings:
|
|
189
|
+ buttons[bindings[k]] = 0
|
|
190
|
+ modifiers[bindings[k]] = 0
|
|
191
|
+ self.buttons = buttons
|
|
192
|
+ self.modifiers = modifiers
|
29
|
193
|
|
30
|
|
- def scale_sprite(self, sprite: Sprite):
|
31
|
|
- sprite.scale_x = self.final_width / sprite.image.width
|
32
|
|
- sprite.scale_y = self.final_width / sprite.image.height
|
|
194
|
+ self.fastness = fastness
|
|
195
|
+ self.autoscroll_border = autoscroll_border
|
|
196
|
+ self.autoscroll_fastness = autoscroll_fastness
|
|
197
|
+ self.wheel_multiplier = wheel_multiplier
|
|
198
|
+ self.zoom_min = zoom_min
|
|
199
|
+ self.zoom_max = zoom_max
|
|
200
|
+ self.zoom_fastness = zoom_fastness
|
|
201
|
+ self.mod_modify_selection = mod_modify_selection
|
|
202
|
+ self.mod_restricted_mov = mod_restricted_mov
|
33
|
203
|
|
34
|
|
- def position_sprite(self, sprite: Sprite, grid_position):
|
35
|
|
- grid_x = grid_position[0]
|
36
|
|
- grid_y = grid_position[1]
|
37
|
|
- sprite.position = grid_x * self.final_width, grid_y * self.final_width
|
|
204
|
+ self.weak_scroller = weakref.ref(self.layer_manager.scrolling_manager)
|
|
205
|
+ self.weak_worldview = weakref.ref(worldview)
|
|
206
|
+ self.wwidth = worldview.width
|
|
207
|
+ self.wheight = worldview.height
|
38
|
208
|
|
39
|
|
- def get_window_position(self, grid_position_x, grid_position_y):
|
40
|
|
- grid_x = grid_position_x
|
41
|
|
- grid_y = grid_position_y
|
42
|
|
- return grid_x * self.final_width, grid_y * self.final_width
|
|
209
|
+ self.autoscrolling = False
|
|
210
|
+ self.drag_selecting = False
|
|
211
|
+ self.drag_moving = False
|
|
212
|
+ self.restricted_mov = False
|
|
213
|
+ self.wheel = 0
|
|
214
|
+ self.dragging = False
|
|
215
|
+ self.keyscrolling = False
|
|
216
|
+ self.keyscrolling_descriptor = (0, 0)
|
|
217
|
+ self.wdrag_start_point = (0, 0)
|
|
218
|
+ self.elastic_box = None # type: MinMaxRect
|
|
219
|
+ self.elastic_box_wminmax = 0, 0, 0, 0
|
|
220
|
+ self.selection = {}
|
|
221
|
+ self.screen_mouse = (0, 0)
|
|
222
|
+ self.world_mouse = (0, 0)
|
|
223
|
+ self.sleft = None
|
|
224
|
+ self.sright = None
|
|
225
|
+ self.sbottom = None
|
|
226
|
+ self.s_top = None
|
43
|
227
|
|
44
|
|
- def get_grid_position(self, window_x, window_y, z=0) -> tuple:
|
45
|
|
- window_size = director.get_window_size()
|
|
228
|
+ # opers that change cshape must ensure it goes to False,
|
|
229
|
+ # selection opers must ensure it goes to True
|
|
230
|
+ self.selection_in_collman = True
|
|
231
|
+ # TODO: Hardcoded here, should be obtained from level properties or calc
|
|
232
|
+ # from available actors or current actors in worldview
|
|
233
|
+ gsize = 32 * 1.25
|
|
234
|
+ self.collision_manager = collision_model.CollisionManagerGrid(
|
|
235
|
+ -gsize,
|
|
236
|
+ self.wwidth + gsize,
|
|
237
|
+ -gsize,
|
|
238
|
+ self.wheight + gsize,
|
|
239
|
+ gsize,
|
|
240
|
+ gsize,
|
|
241
|
+ )
|
46
|
242
|
|
47
|
|
- window_center_x = window_size[0] // 2
|
48
|
|
- window_center_y = window_size[1] // 2
|
|
243
|
+ self.schedule(self.update)
|
|
244
|
+ self.selectable_actors = []
|
49
|
245
|
|
50
|
|
- window_relative_x = window_x - window_center_x
|
51
|
|
- window_relative_y = window_y - window_center_y
|
|
246
|
+ def set_selectable(self, actor: Actor) -> None:
|
|
247
|
+ self.selectable_actors.append(actor)
|
|
248
|
+ self.collision_manager.add(actor)
|
52
|
249
|
|
53
|
|
- real_width = self.final_width * self.layer.scale
|
|
250
|
+ def unset_selectable(self, actor: Actor) -> None:
|
|
251
|
+ self.selectable_actors.remove(actor)
|
|
252
|
+ self.collision_manager.remove_tricky(actor)
|
54
|
253
|
|
55
|
|
- return int(window_relative_x // real_width),\
|
56
|
|
- int(window_relative_y // real_width),\
|
57
|
|
- z
|
|
254
|
+ def draw(self, *args, **kwargs) -> None:
|
|
255
|
+ for actor in self.selectable_actors:
|
|
256
|
+ if actor.need_update_cshape:
|
|
257
|
+ if self.collision_manager.knows(actor):
|
|
258
|
+ self.collision_manager.remove_tricky(actor)
|
|
259
|
+ actor.update_cshape()
|
|
260
|
+ self.collision_manager.add(actor)
|
58
|
261
|
|
|
262
|
+ def on_enter(self):
|
|
263
|
+ super(EditLayer, self).on_enter()
|
|
264
|
+ scene = self.get_ancestor(cocos.scene.Scene)
|
|
265
|
+ if self.elastic_box is None:
|
|
266
|
+ self.elastic_box = MinMaxRect(self.layer_manager)
|
|
267
|
+ scene.add(self.elastic_box, z=10)
|
59
|
268
|
|
60
|
|
-class GridLayerMixin(object):
|
61
|
|
- def __init__(self, *args, **kwargs):
|
62
|
|
- square_width = kwargs.pop('square_width', 32)
|
63
|
|
- square_border = kwargs.pop('square_border', 2)
|
64
|
|
- self.grid_manager = GridManager(
|
65
|
|
- self,
|
66
|
|
- square_width=square_width,
|
67
|
|
- border=square_border,
|
|
269
|
+ def update(self, dt):
|
|
270
|
+ mx = self.buttons['right'] - self.buttons['left']
|
|
271
|
+ my = self.buttons['up'] - self.buttons['down']
|
|
272
|
+ dz = self.buttons['zoomin'] - self.buttons['zoomout']
|
|
273
|
+
|
|
274
|
+ # scroll
|
|
275
|
+ if self.autoscrolling:
|
|
276
|
+ self.update_autoscroll(dt)
|
|
277
|
+ else:
|
|
278
|
+ # care for keyscrolling
|
|
279
|
+ new_keyscrolling = ((len(self.selection) == 0) and
|
|
280
|
+ (mx != 0 or my != 0))
|
|
281
|
+ new_keyscrolling_descriptor = (mx, my)
|
|
282
|
+ if ((new_keyscrolling != self.keyscrolling) or
|
|
283
|
+ (new_keyscrolling_descriptor != self.keyscrolling_descriptor)):
|
|
284
|
+ self.keyscrolling = new_keyscrolling
|
|
285
|
+ self.keyscrolling_descriptor = new_keyscrolling_descriptor
|
|
286
|
+ fastness = 1.0
|
|
287
|
+ if mx != 0 and my != 0:
|
|
288
|
+ fastness *= 0.707106 # 1/sqrt(2)
|
|
289
|
+ self.autoscrolling_sdelta = (0.5 * fastness * mx, 0.5 * fastness * my)
|
|
290
|
+ if self.keyscrolling:
|
|
291
|
+ self.update_autoscroll(dt)
|
|
292
|
+
|
|
293
|
+ # selection move
|
|
294
|
+ if self.drag_moving:
|
|
295
|
+ # update positions
|
|
296
|
+ wx, wy = self.world_mouse
|
|
297
|
+ dx = wx - self.wdrag_start_point[0]
|
|
298
|
+ dy = wy - self.wdrag_start_point[1]
|
|
299
|
+ if self.restricted_mov:
|
|
300
|
+ if abs(dy) > abs(dx):
|
|
301
|
+ dx = 0
|
|
302
|
+ else:
|
|
303
|
+ dy = 0
|
|
304
|
+ dpos = euclid.Vector2(dx, dy)
|
|
305
|
+ for actor in self.selection:
|
|
306
|
+ old_pos = self.selection[actor].center
|
|
307
|
+ new_pos = old_pos + dpos
|
|
308
|
+
|
|
309
|
+ try:
|
|
310
|
+ grid_pos = self.grid_manager.get_grid_position(new_pos)
|
|
311
|
+ grid_pixel_pos = self.grid_manager.get_pixel_position_of_grid_position(grid_pos)
|
|
312
|
+ actor.update_position(grid_pixel_pos)
|
|
313
|
+ except OuterWorldPosition:
|
|
314
|
+ # don't update position
|
|
315
|
+ pass
|
|
316
|
+
|
|
317
|
+ scroller = self.weak_scroller()
|
|
318
|
+
|
|
319
|
+ # zoom
|
|
320
|
+ zoom_change = (dz != 0 or self.wheel != 0)
|
|
321
|
+ if zoom_change:
|
|
322
|
+ if self.mouse_into_world():
|
|
323
|
+ wzoom_center = self.world_mouse
|
|
324
|
+ szoom_center = self.screen_mouse
|
|
325
|
+ else:
|
|
326
|
+ # decay to scroller unadorned
|
|
327
|
+ wzoom_center = None
|
|
328
|
+ if self.wheel != 0:
|
|
329
|
+ dt_dz = 0.01666666 * self.wheel
|
|
330
|
+ self.wheel = 0
|
|
331
|
+ else:
|
|
332
|
+ dt_dz = dt * dz
|
|
333
|
+ zoom = scroller.scale + dt_dz * self.zoom_fastness
|
|
334
|
+ if zoom < self.zoom_min:
|
|
335
|
+ zoom = self.zoom_min
|
|
336
|
+ elif zoom > self.zoom_max:
|
|
337
|
+ zoom = self.zoom_max
|
|
338
|
+ scroller.scale = zoom
|
|
339
|
+ if wzoom_center is not None:
|
|
340
|
+ # postprocess toward 'world point under mouse the same before
|
|
341
|
+ # and after zoom' ; other restrictions may prevent fully comply
|
|
342
|
+ wx1, wy1 = self.layer_manager.scrolling_manager.screen_to_world(*szoom_center)
|
|
343
|
+ fx = scroller.restricted_fx + (wzoom_center[0] - wx1)
|
|
344
|
+ fy = scroller.restricted_fy + (wzoom_center[1] - wy1)
|
|
345
|
+ scroller.set_focus(fx, fy)
|
|
346
|
+
|
|
347
|
+ def update_mouse_position(self, sx, sy):
|
|
348
|
+ self.screen_mouse = sx, sy
|
|
349
|
+ self.world_mouse = self.layer_manager.scrolling_manager.screen_to_world(sx, sy)
|
|
350
|
+ # handle autoscroll
|
|
351
|
+ border = self.autoscroll_border
|
|
352
|
+ if border is not None:
|
|
353
|
+ # sleft and companions includes the border
|
|
354
|
+ scroller = self.weak_scroller()
|
|
355
|
+ self.update_view_bounds()
|
|
356
|
+ sdx = 0.0
|
|
357
|
+ if sx < self.sleft:
|
|
358
|
+ sdx = sx - self.sleft
|
|
359
|
+ elif sx > self.sright:
|
|
360
|
+ sdx = sx - self.sright
|
|
361
|
+ sdy = 0.0
|
|
362
|
+ if sy < self.sbottom:
|
|
363
|
+ sdy = sy - self.sbottom
|
|
364
|
+ elif sy > self.s_top:
|
|
365
|
+ sdy = sy - self.s_top
|
|
366
|
+ self.autoscrolling = sdx != 0.0 or sdy != 0.0
|
|
367
|
+ if self.autoscrolling:
|
|
368
|
+ self.autoscrolling_sdelta = (sdx / border, sdy / border)
|
|
369
|
+
|
|
370
|
+ def update_autoscroll(self, dt):
|
|
371
|
+ fraction_sdx, fraction_sdy = self.autoscrolling_sdelta
|
|
372
|
+ scroller = self.weak_scroller()
|
|
373
|
+ worldview = self.weak_worldview()
|
|
374
|
+ f = self.autoscroll_fastness
|
|
375
|
+ wdx = (fraction_sdx * f * dt) / scroller.scale / worldview.scale
|
|
376
|
+ wdy = (fraction_sdy * f * dt) / scroller.scale / worldview.scale
|
|
377
|
+ # ask scroller to try scroll (wdx, wdy)
|
|
378
|
+ fx = scroller.restricted_fx + wdx
|
|
379
|
+ fy = scroller.restricted_fy + wdy
|
|
380
|
+ scroller.set_focus(fx, fy)
|
|
381
|
+ self.world_mouse = self.layer_manager.scrolling_manager.screen_to_world(*self.screen_mouse)
|
|
382
|
+ self.adjust_elastic_box()
|
|
383
|
+ # self.update_view_bounds()
|
|
384
|
+
|
|
385
|
+ def update_view_bounds(self):
|
|
386
|
+ scroller = self.weak_scroller()
|
|
387
|
+ scx, scy = self.layer_manager.scrolling_manager.world_to_screen(
|
|
388
|
+ scroller.restricted_fx,
|
|
389
|
+ scroller.restricted_fy,
|
68
|
390
|
)
|
69
|
|
- super().__init__(*args, **kwargs)
|
|
391
|
+ hw = scroller.view_w / 2.0
|
|
392
|
+ hh = scroller.view_h / 2.0
|
|
393
|
+ border = self.autoscroll_border
|
|
394
|
+ self.sleft = scx - hw + border
|
|
395
|
+ self.sright = scx + hw - border
|
|
396
|
+ self.sbottom = scy - hh + border
|
|
397
|
+ self.s_top = scy + hh - border
|
|
398
|
+
|
|
399
|
+ def mouse_into_world(self):
|
|
400
|
+ worldview = self.weak_worldview()
|
|
401
|
+ # TODO: allow lower limits != 0 ?
|
|
402
|
+ return ((0 <= self.world_mouse[0] <= worldview.width) and
|
|
403
|
+ (0 <= self.world_mouse[1] <= worldview.height))
|
|
404
|
+
|
|
405
|
+ def on_key_press(self, k, m):
|
|
406
|
+ binds = self.bindings
|
|
407
|
+ if k in binds:
|
|
408
|
+ self.buttons[binds[k]] = 1
|
|
409
|
+ self.modifiers[binds[k]] = 1
|
|
410
|
+ return True
|
|
411
|
+ return False
|
|
412
|
+
|
|
413
|
+ def on_key_release(self, k, m):
|
|
414
|
+ binds = self.bindings
|
|
415
|
+ if k in binds:
|
|
416
|
+ self.buttons[binds[k]] = 0
|
|
417
|
+ self.modifiers[binds[k]] = 0
|
|
418
|
+ return True
|
|
419
|
+ return False
|
|
420
|
+
|
|
421
|
+ def on_mouse_motion(self, sx, sy, dx, dy):
|
|
422
|
+ self.update_mouse_position(sx, sy)
|
|
423
|
+
|
|
424
|
+ def on_mouse_leave(self, sx, sy):
|
|
425
|
+ self.autoscrolling = False
|
|
426
|
+
|
|
427
|
+ def on_mouse_press(self, x, y, buttons, modifiers):
|
|
428
|
+ if self.logger.is_debug:
|
|
429
|
+ rx, ry = self.layer_manager.scrolling_manager.screen_to_world(x, y)
|
|
430
|
+ self.logger.debug('GUI click: x: {}, y: {}, rx: {}, ry: {}'.format(x, y, rx, ry))
|
|
431
|
+
|
|
432
|
+ def on_mouse_release(self, sx, sy, button, modifiers):
|
|
433
|
+ # should we handle here mod_restricted_mov ?
|
|
434
|
+ wx, wy = self.layer_manager.scrolling_manager.screen_to_world(sx, sy)
|
|
435
|
+ modify_selection = modifiers & self.mod_modify_selection
|
|
436
|
+ if self.dragging:
|
|
437
|
+ # ignore all buttons except left button
|
|
438
|
+ if button != mouse.LEFT:
|
|
439
|
+ return
|
|
440
|
+ if self.drag_selecting:
|
|
441
|
+ self.end_drag_selection(wx, wy, modify_selection)
|
|
442
|
+ elif self.drag_moving:
|
|
443
|
+ self.end_drag_move(wx, wy)
|
|
444
|
+ self.dragging = False
|
|
445
|
+ else:
|
|
446
|
+ if button == mouse.LEFT:
|
|
447
|
+ self.end_click_selection(wx, wy, modify_selection)
|
|
448
|
+
|
|
449
|
+ def end_click_selection(self, wx, wy, modify_selection):
|
|
450
|
+ under_mouse_unique = self.single_actor_from_mouse()
|
|
451
|
+ if modify_selection:
|
|
452
|
+ # toggle selected status for unique
|
|
453
|
+ if under_mouse_unique in self.selection:
|
|
454
|
+ self.selection_remove(under_mouse_unique)
|
|
455
|
+ elif under_mouse_unique is not None:
|
|
456
|
+ self.selection_add(under_mouse_unique)
|
|
457
|
+ else:
|
|
458
|
+ # new_selected becomes the current selected
|
|
459
|
+ self.selection.clear()
|
|
460
|
+ if under_mouse_unique is not None:
|
|
461
|
+ self.selection_add(under_mouse_unique)
|
|
462
|
+
|
|
463
|
+ def selection_add(self, actor):
|
|
464
|
+ self.selection[actor] = actor.cshape.copy()
|
|
465
|
+
|
|
466
|
+ def selection_remove(self, actor):
|
|
467
|
+ del self.selection[actor]
|
|
468
|
+
|
|
469
|
+ def end_drag_selection(self, wx, wy, modify_selection):
|
|
470
|
+ new_selection = self.collision_manager.objs_into_box(*self.elastic_box_wminmax)
|
|
471
|
+ if not modify_selection:
|
|
472
|
+ # new_selected becomes the current selected
|
|
473
|
+ self.selection.clear()
|
|
474
|
+ for actor in new_selection:
|
|
475
|
+ self.selection_add(actor)
|
|
476
|
+
|
|
477
|
+ self.elastic_box.visible = False
|
|
478
|
+ self.drag_selecting = False
|
|
479
|
+
|
|
480
|
+ def on_mouse_drag(self, sx, sy, dx, dy, buttons, modifiers):
|
|
481
|
+ # TODO: inhibir esta llamada si estamos fuera de la client area / viewport
|
|
482
|
+ self.update_mouse_position(sx, sy)
|
|
483
|
+ if not buttons & mouse.LEFT:
|
|
484
|
+ # ignore except for left-btn-drag
|
|
485
|
+ return
|
|
486
|
+
|
|
487
|
+ if not self.dragging:
|
|
488
|
+ print("begin drag")
|
|
489
|
+ self.begin_drag()
|
|
490
|
+ return
|
|
491
|
+
|
|
492
|
+ if self.drag_selecting:
|
|
493
|
+ # update elastic box
|
|
494
|
+ self.adjust_elastic_box()
|
|
495
|
+ elif self.drag_moving:
|
|
496
|
+ self.restricted_mov = (modifiers & self.mod_restricted_mov)
|
|
497
|
+
|
|
498
|
+ def adjust_elastic_box(self):
|
|
499
|
+ # when elastic_box visible this method needs to be called any time
|
|
500
|
+ # world_mouse changes or screen_to_world results changes (scroll, etc)
|
|
501
|
+ wx0, wy0 = self.wdrag_start_point
|
|
502
|
+ wx1, wy1 = self.world_mouse
|
|
503
|
+ wminx = min(wx0, wx1)
|
|
504
|
+ wmaxx = max(wx0, wx1)
|
|
505
|
+ wminy = min(wy0, wy1)
|
|
506
|
+ wmaxy = max(wy0, wy1)
|
|
507
|
+ self.elastic_box_wminmax = wminx, wmaxx, wminy, wmaxy
|
|
508
|
+ self.elastic_box.adjust_from_w_minmax(*self.elastic_box_wminmax)
|
|
509
|
+
|
|
510
|
+ def begin_drag(self):
|
|
511
|
+ self.dragging = True
|
|
512
|
+ self.wdrag_start_point = self.world_mouse
|
|
513
|
+ under_mouse_unique = self.single_actor_from_mouse()
|
|
514
|
+ if under_mouse_unique is None:
|
|
515
|
+ # begin drag selection
|
|
516
|
+ self.drag_selecting = True
|
|
517
|
+ self.adjust_elastic_box()
|
|
518
|
+ self.elastic_box.visible = True
|
|
519
|
+ print("begin drag selection: drag_selecting, drag_moving",
|
|
520
|
+ self.drag_selecting, self.drag_moving)
|
|
521
|
+
|
|
522
|
+ else:
|
|
523
|
+ # want drag move
|
|
524
|
+ if under_mouse_unique in self.selection:
|
|
525
|
+ # want to move current selection
|
|
526
|
+ pass
|
|
527
|
+ else:
|
|
528
|
+ # change selection before moving
|
|
529
|
+ self.selection.clear()
|
|
530
|
+ self.selection_add(under_mouse_unique)
|
|
531
|
+ self.begin_drag_move()
|
|
532
|
+
|
|
533
|
+ def begin_drag_move(self):
|
|
534
|
+ # begin drag move
|
|
535
|
+ self.drag_moving = True
|
|
536
|
+
|
|
537
|
+ # how-to update collman: remove/add vs clear/add all
|
|
538
|
+ # when total number of actors is low anyone will be fine,
|
|
539
|
+ # with high numbers, most probably we move only a small fraction
|
|
540
|
+ # For simplicity I choose remove/add, albeit a hybrid aproach
|
|
541
|
+ # can be implemented later
|
|
542
|
+ self.set_selection_in_collman(False)
|
|
543
|
+# print "begin drag: drag_selecting, drag_moving", self.drag_selecting, self.drag_moving
|
|
544
|
+
|
|
545
|
+ def end_drag_move(self, wx, wy):
|
|
546
|
+ self.set_selection_in_collman(True)
|
|
547
|
+ for actor in self.selection:
|
|
548
|
+ self.selection[actor] = actor.cshape.copy()
|
|
549
|
+
|
|
550
|
+ self.drag_moving = False
|
|
551
|
+
|
|
552
|
+ def single_actor_from_mouse(self):
|
|
553
|
+ under_mouse = self.collision_manager.objs_touching_point(*self.world_mouse)
|
|
554
|
+ if len(under_mouse) == 0:
|
|
555
|
+ return None
|
|
556
|
+ # return the one with the center most near to mouse, if tie then
|
|
557
|
+ # an arbitrary in the tie
|
|
558
|
+ nearest = None
|
|
559
|
+ near_d = None
|
|
560
|
+ p = euclid.Vector2(*self.world_mouse)
|
|
561
|
+ for actor in under_mouse:
|
|
562
|
+ d = (actor.cshape.center - p).magnitude_squared()
|
|
563
|
+ if nearest is None or (d < near_d):
|
|
564
|
+ nearest = actor
|
|
565
|
+ near_d = d
|
|
566
|
+ return nearest
|
|
567
|
+
|
|
568
|
+ def set_selection_in_collman(self, bool_value):
|
|
569
|
+ if self.selection_in_collman == bool_value:
|
|
570
|
+ return
|
|
571
|
+ self.selection_in_collman = bool_value
|
|
572
|
+ if bool_value:
|
|
573
|
+ for actor in self.selection:
|
|
574
|
+ self.collision_manager.add(actor)
|
|
575
|
+ else:
|
|
576
|
+ for actor in self.selection:
|
|
577
|
+ self.collision_manager.remove_tricky(actor)
|
|
578
|
+
|
|
579
|
+ def on_mouse_scroll(self, x, y, scroll_x, scroll_y):
|
|
580
|
+ # TODO: check if mouse over scroller viewport?
|
|
581
|
+ self.wheel += scroll_y * self.wheel_multiplier
|
70
|
582
|
|
71
|
583
|
|
72
|
584
|
class MainLayer(ScrollableLayer):
|
73
|
585
|
is_event_handler = True
|
74
|
586
|
|
75
|
|
- def __init__(self, terminal: Terminal, scroll_step: int=100):
|
|
587
|
+ def __init__(
|
|
588
|
+ self,
|
|
589
|
+ layer_manager: LayerManager,
|
|
590
|
+ grid_manager: GridManager,
|
|
591
|
+ width: int,
|
|
592
|
+ height: int,
|
|
593
|
+ scroll_step: int=100,
|
|
594
|
+ ) -> None:
|
76
|
595
|
super().__init__()
|
77
|
|
-
|
78
|
|
- self.terminal = terminal
|
|
596
|
+ self.layer_manager = layer_manager
|
79
|
597
|
self.scroll_step = scroll_step
|
80
|
|
- self.grid_manager = GridManager(self, 32, border=2)
|
|
598
|
+ self.grid_manager = grid_manager
|
|
599
|
+
|
|
600
|
+ self.width = width
|
|
601
|
+ self.height = height
|
|
602
|
+ self.px_width = width
|
|
603
|
+ self.px_height = height
|
|
604
|
+
|
81
|
605
|
|
82
|
|
- # Set scene center on center of screen
|
83
|
|
- window_size = director.get_window_size()
|
84
|
|
- self.position = window_size[0] // 2, window_size[1] // 2
|
|
606
|
+class SubjectMapper(object):
|
|
607
|
+ def __init__(
|
|
608
|
+ self,
|
|
609
|
+ actor_class: typing.Type[Actor],
|
|
610
|
+ ) -> None:
|
|
611
|
+ self.actor_class = actor_class
|
85
|
612
|
|
86
|
|
- def on_key_press(self, key, modifiers):
|
87
|
|
- if key == wkey.LEFT:
|
88
|
|
- self.position = (self.position[0] + self.scroll_step, self.position[1])
|
|
613
|
+ def append(
|
|
614
|
+ self,
|
|
615
|
+ subject: XYZSubjectMixin,
|
|
616
|
+ layer_manager: LayerManager,
|
|
617
|
+ ) -> None:
|
|
618
|
+ actor = self.actor_class()
|
|
619
|
+ pixel_position = layer_manager.grid_manager.get_pixel_position_of_grid_position((
|
|
620
|
+ subject.position[0],
|
|
621
|
+ subject.position[1],
|
|
622
|
+ ))
|
|
623
|
+ actor.update_position(euclid.Vector2(*pixel_position))
|
89
|
624
|
|
90
|
|
- if key == wkey.RIGHT:
|
91
|
|
- self.position = (self.position[0] - self.scroll_step, self.position[1])
|
|
625
|
+ # TODO: Selectable nature must be configurable
|
|
626
|
+ layer_manager.add_subject(actor)
|
|
627
|
+ layer_manager.set_selectable(actor)
|
92
|
628
|
|
93
|
|
- if key == wkey.UP:
|
94
|
|
- self.position = (self.position[0], self.position[1] - self.scroll_step)
|
95
|
629
|
|
96
|
|
- if key == wkey.DOWN:
|
97
|
|
- self.position = (self.position[0], self.position[1] + self.scroll_step)
|
|
630
|
+class SubjectMapperFactory(object):
|
|
631
|
+ def __init__(self) -> None:
|
|
632
|
+ self.mapping = {} # type: typing.Dict[typing.Type[XYZSubjectMixin], SubjectMapper]
|
98
|
633
|
|
99
|
|
- if key == wkey.A:
|
100
|
|
- if self.scale >= 0.3:
|
101
|
|
- self.scale -= 0.2
|
|
634
|
+ def register_mapper(self, subject_class: typing.Type[XYZSubjectMixin], mapper: SubjectMapper) -> None:
|
|
635
|
+ if subject_class not in self.mapping:
|
|
636
|
+ self.mapping[subject_class] = mapper
|
|
637
|
+ else:
|
|
638
|
+ raise ValueError('subject_class already register with "{}"'.format(str(self.mapping[subject_class])))
|
102
|
639
|
|
103
|
|
- if key == wkey.Z:
|
104
|
|
- if self.scale <= 4:
|
105
|
|
- self.scale += 0.2
|
|
640
|
+ def get_subject_mapper(self, subject: XYZSubjectMixin) -> SubjectMapper:
|
|
641
|
+ for subject_class, mapper in self.mapping.items():
|
|
642
|
+ if isinstance(subject, subject_class):
|
|
643
|
+ return mapper
|
|
644
|
+ raise KeyError('No mapper for subject "{}"'.format(str(subject)))
|
106
|
645
|
|
107
|
646
|
|
108
|
647
|
class Gui(object):
|
|
@@ -114,19 +653,39 @@ class Gui(object):
|
114
|
653
|
read_queue_interval: float= 1/60.0,
|
115
|
654
|
):
|
116
|
655
|
self.config = config
|
117
|
|
- self.logger = logger,
|
|
656
|
+ self.logger = logger
|
118
|
657
|
self._read_queue_interval = read_queue_interval
|
119
|
658
|
self.terminal = terminal
|
120
|
659
|
self.cycle_duration = self.config.core.cycle_duration
|
121
|
|
- cocos.director.director.init()
|
|
660
|
+
|
|
661
|
+ cocos.director.director.init(
|
|
662
|
+ width=640,
|
|
663
|
+ height=480,
|
|
664
|
+ vsync=True,
|
|
665
|
+ resizable=True,
|
|
666
|
+ )
|
|
667
|
+
|
|
668
|
+ # Enable blending
|
|
669
|
+ pyglet.gl.glEnable(pyglet.gl.GL_BLEND)
|
|
670
|
+ pyglet.gl.glBlendFunc(pyglet.gl.GL_SRC_ALPHA, pyglet.gl.GL_ONE_MINUS_SRC_ALPHA)
|
|
671
|
+
|
|
672
|
+ # Enable transparency
|
|
673
|
+ pyglet.gl.glEnable(pyglet.gl.GL_ALPHA_TEST)
|
|
674
|
+ pyglet.gl.glAlphaFunc(pyglet.gl.GL_GREATER, .1)
|
|
675
|
+
|
|
676
|
+ self.subject_mapper_factory = SubjectMapperFactory()
|
122
|
677
|
|
123
|
678
|
def run(self):
|
|
679
|
+ self.before_run()
|
124
|
680
|
pyglet.clock.schedule_interval(
|
125
|
681
|
lambda *_, **__: self.terminal.read(),
|
126
|
682
|
self._read_queue_interval,
|
127
|
683
|
)
|
128
|
684
|
cocos.director.director.run(self.get_main_scene())
|
129
|
685
|
|
|
686
|
+ def before_run(self) -> None:
|
|
687
|
+ pass
|
|
688
|
+
|
130
|
689
|
def get_main_scene(self) -> cocos.cocosnode.CocosNode:
|
131
|
690
|
raise NotImplementedError()
|
132
|
691
|
|
|
@@ -135,3 +694,46 @@ class Gui(object):
|
135
|
694
|
|
136
|
695
|
def after_received(self, package: TerminalPackage):
|
137
|
696
|
pass
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+class TMXGui(Gui):
|
|
700
|
+ def __init__(
|
|
701
|
+ self,
|
|
702
|
+ config: Config,
|
|
703
|
+ logger: SynergineLogger,
|
|
704
|
+ terminal: Terminal,
|
|
705
|
+ read_queue_interval: float = 1 / 60.0,
|
|
706
|
+ map_dir_path: str=None,
|
|
707
|
+ ):
|
|
708
|
+ assert map_dir_path
|
|
709
|
+ super(TMXGui, self).__init__(
|
|
710
|
+ config,
|
|
711
|
+ logger,
|
|
712
|
+ terminal,
|
|
713
|
+ read_queue_interval,
|
|
714
|
+ )
|
|
715
|
+ self.map_dir_path = map_dir_path
|
|
716
|
+ self.layer_manager = LayerManager(
|
|
717
|
+ self.config,
|
|
718
|
+ self.logger,
|
|
719
|
+ middleware=TMXMiddleware(
|
|
720
|
+ self.config,
|
|
721
|
+ self.logger,
|
|
722
|
+ self.map_dir_path,
|
|
723
|
+ ),
|
|
724
|
+ )
|
|
725
|
+ self.layer_manager.init()
|
|
726
|
+ self.layer_manager.center()
|
|
727
|
+
|
|
728
|
+ def get_main_scene(self) -> cocos.cocosnode.CocosNode:
|
|
729
|
+ return self.layer_manager.main_scene
|
|
730
|
+
|
|
731
|
+ def before_received(self, package: TerminalPackage):
|
|
732
|
+ super().before_received(package)
|
|
733
|
+ if package.subjects: # They are new subjects in the simulation
|
|
734
|
+ for subject in package.subjects:
|
|
735
|
+ self.append_subject(subject)
|
|
736
|
+
|
|
737
|
+ def append_subject(self, subject: XYZSubjectMixin) -> None:
|
|
738
|
+ subject_mapper = self.subject_mapper_factory.get_subject_mapper(subject)
|
|
739
|
+ subject_mapper.append(subject, self.layer_manager)
|