gui.py 29KB

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