gui.py 24KB

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