gui.py 24KB

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