gui.py 27KB

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