123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868 |
- # coding: utf-8
- import typing
- import weakref
- from math import floor
-
- import pyglet
- import time
-
- from cocos.collision_model import AARectShape
- from pyglet.window import mouse
-
- import cocos
- from cocos import collision_model
- from cocos import euclid
- from cocos.audio.pygame import mixer
- from cocos.layer import ScrollableLayer
- from synergine2.config import Config
- from synergine2.exceptions import SynergineException
- from synergine2.log import get_logger
- from synergine2.terminals import Terminal
- from synergine2.terminals import TerminalPackage
- from synergine2_cocos2d.actor import Actor
- from synergine2_cocos2d.const import SELECTION_COLOR_RGB
- from synergine2_cocos2d.const import DEFAULT_SELECTION_COLOR_RGB
- from synergine2_cocos2d.exception import InteractionNotFound
- from synergine2_cocos2d.exception import OuterWorldPosition
- from synergine2_cocos2d.gl import draw_rectangle
- from synergine2_cocos2d.gl import rectangle_positions_type
- from synergine2_cocos2d.interaction import InteractionManager
- from synergine2_cocos2d.layer import LayerManager
- from synergine2_cocos2d.middleware import MapMiddleware
- from synergine2_cocos2d.middleware import TMXMiddleware
- from synergine2_cocos2d.user_action import UserAction
- from synergine2_cocos2d.util import ensure_dir_exist
- from synergine2_xyz.physics import Physics
- from synergine2_xyz.xyz import XYZSubjectMixin
-
-
- class GridManager(object):
- def __init__(
- self,
- cell_width: int,
- cell_height: int,
- world_width: int,
- world_height: int,
- ) -> None:
- self.cell_width = cell_width
- self.cell_height = cell_height
- self.world_width = world_width
- self.world_height = world_height
-
- def get_grid_position(self, pixel_position: typing.Tuple[int, int]) -> typing.Tuple[int, int]:
- pixel_x, pixel_y = pixel_position
-
- cell_x = int(floor(pixel_x / self.cell_width))
- cell_y = int(floor(pixel_y / self.cell_height))
-
- if cell_x > self.world_width or cell_y > self.world_height or cell_x < 0 or cell_y < 0:
- raise OuterWorldPosition('Position "{}" is outer world ({}x{})'.format(
- (cell_x, cell_y),
- self.world_width,
- self.world_height,
- ))
-
- return cell_x, cell_y
-
- def get_world_position_of_grid_position(self, grid_position: typing.Tuple[int, int]) -> typing.Tuple[int, int]:
- return grid_position[0] * self.cell_width + (self.cell_width // 2),\
- grid_position[1] * self.cell_height + (self.cell_height // 2)
-
- def get_rectangle_positions(
- self,
- grid_position: typing.Tuple[int, int],
- ) -> rectangle_positions_type:
- """
- A<---D
- | |
- B--->C
- :param grid_position:grid position to exploit
- :return: grid pixel corners positions
- """
- grid_x, grid_y = grid_position
-
- a = grid_x * self.cell_width, grid_y * self.cell_height + self.cell_height
- b = grid_x * self.cell_width, grid_y * self.cell_height
- c = grid_x * self.cell_width + self.cell_width, grid_y * self.cell_height
- d = grid_x * self.cell_width + self.cell_width, grid_y * self.cell_height + self.cell_height
-
- return a, d, c, b
-
-
- class MinMaxRect(cocos.cocosnode.CocosNode):
- def __init__(self, layer_manager: LayerManager):
- super(MinMaxRect, self).__init__()
- self.layer_manager = layer_manager
- self.color3 = (20, 20, 20)
- self.color3f = (0, 0, 0, 0.2)
- self.vertexes = [(0.0, 0.0), (0.0, 0.0), (0.0, 0.0), (0.0, 0.0)]
- self.visible = False
-
- def adjust_from_w_minmax(self, wminx, wmaxx, wminy, wmaxy):
- # asumes world to screen preserves order
- sminx, sminy = self.layer_manager.scrolling_manager.world_to_screen(wminx, wminy)
- smaxx, smaxy = self.layer_manager.scrolling_manager.world_to_screen(wmaxx, wmaxy)
- self.vertexes = [(sminx, sminy), (sminx, smaxy), (smaxx, smaxy), (smaxx, sminy)]
-
- def draw(self):
- if not self.visible:
- return
-
- draw_rectangle(
- self.vertexes,
- self.color3,
- self.color3f,
- )
-
- def set_vertexes_from_minmax(self, minx, maxx, miny, maxy):
- self.vertexes = [(minx, miny), (minx, maxy), (maxx, maxy), (maxx, miny)]
-
-
- class FinishedCallback(Exception):
- pass
-
-
- class Callback(object):
- def __init__(
- self,
- func: typing.Callable[[], None],
- duration: float,
- delay: float=None,
- end_callback: typing.Callable[[], None]=None,
- start_callback: typing.Callable[[], None]=None,
- ) -> None:
- self.func = func
- self.duration = duration
- # Started timestamp
- self.started = None # type: float
- self.require_delay = False
- self.delay = delay
- if delay is not None:
- self.require_delay = True
- self.end_callback = end_callback
- self.start_callback = start_callback
-
- def execute(self) -> None:
- if self.started is None and self.start_callback:
- self.start_callback()
-
- if self.require_delay and not self.started:
- self.started = time.time()
- return
- elif self.require_delay and time.time() - self.started < self.delay:
- return
- elif self.require_delay:
- self.started = None
- self.require_delay = False
-
- if self.started is None:
- self.started = time.time()
-
- if time.time() - self.started <= self.duration:
- self.func()
- elif not self.duration:
- self.func()
-
- if self.end_callback is not None:
- self.end_callback()
-
- raise FinishedCallback()
- else:
- if self.end_callback is not None:
- self.end_callback()
-
- raise FinishedCallback()
-
-
- class EditLayer(cocos.layer.Layer):
- is_event_handler = True
-
- def __init__(
- self,
- config: Config,
- layer_manager: LayerManager,
- grid_manager: GridManager,
- worldview,
- bindings=None,
- fastness=None,
- autoscroll_border=10,
- autoscroll_fastness=None,
- wheel_multiplier=None,
- zoom_min=None,
- zoom_max=None,
- zoom_fastness=None,
- mod_modify_selection=None,
- mod_restricted_mov=None,
- ):
- # TODO: Clean init params
- super().__init__()
-
- self.config = config
- self.logger = get_logger('EditLayer', config)
- self.layer_manager = layer_manager
- self.grid_manager = grid_manager
-
- self.bindings = bindings
- buttons = {}
- modifiers = {}
- for k in bindings:
- buttons[bindings[k]] = 0
- modifiers[bindings[k]] = 0
- self.buttons = buttons
- self.modifiers = modifiers
-
- self.fastness = fastness
- self.autoscroll_border = autoscroll_border
- self.autoscroll_fastness = autoscroll_fastness
- self.wheel_multiplier = wheel_multiplier
- self.zoom_min = zoom_min
- self.zoom_max = zoom_max
- self.zoom_fastness = zoom_fastness
- self.mod_modify_selection = mod_modify_selection
- self.mod_restricted_mov = mod_restricted_mov
-
- self.weak_scroller = weakref.ref(self.layer_manager.scrolling_manager)
- self.weak_worldview = weakref.ref(worldview)
- self.wwidth = worldview.width
- self.wheight = worldview.height
-
- self.autoscrolling = False
- self.drag_selecting = False
- self.drag_moving = False
- self.restricted_mov = False
- self.wheel = 0
- self.dragging = False
- self.keyscrolling = False
- self.keyscrolling_descriptor = (0, 0)
- self.wdrag_start_point = (0, 0)
- self.elastic_box = None # type: MinMaxRect
- self.elastic_box_wminmax = 0, 0, 0, 0
- self.selection = {} # type: typing.Dict[Actor, AARectShape]
- self.screen_mouse = (0, 0)
- self.world_mouse = (0, 0)
- self.sleft = None
- self.sright = None
- self.sbottom = None
- self.s_top = None
- self.user_action_pending = None # type: UserAction
-
- # opers that change cshape must ensure it goes to False,
- # selection opers must ensure it goes to True
- self.selection_in_collman = True
- # TODO: Hardcoded here, should be obtained from level properties or calc
- # from available actors or current actors in worldview
- gsize = 32 * 1.25
- self.collision_manager = collision_model.CollisionManagerGrid(
- -gsize,
- self.wwidth + gsize,
- -gsize,
- self.wheight + gsize,
- gsize,
- gsize,
- )
-
- self.schedule(self.update)
- self.selectable_actors = []
- self.callbacks = [] # type: typing.List[Callback]
-
- def append_callback(
- self,
- callback: typing.Callable[[], None],
- duration: float,
- delay: float=None,
- start_callback: typing.Callable[[], None]=None,
- end_callback: typing.Callable[[], None]=None,
- ) -> None:
- self.callbacks.append(Callback(
- callback,
- duration,
- delay=delay,
- start_callback=start_callback,
- end_callback=end_callback,
- ))
-
- def set_selectable(self, actor: Actor) -> None:
- self.selectable_actors.append(actor)
- self.collision_manager.add(actor)
-
- def unset_selectable(self, actor: Actor) -> None:
- self.selectable_actors.remove(actor)
- self.collision_manager.remove_tricky(actor)
-
- def draw(self, *args, **kwargs) -> None:
- self.draw_update_cshapes()
- self.draw_selection()
- self.draw_interactions()
- self.execute_callbacks()
-
- def execute_callbacks(self) -> None:
- for callback in self.callbacks[:]:
- try:
- callback.execute()
- except FinishedCallback:
- self.callbacks.remove(callback)
-
- def draw_update_cshapes(self) -> None:
- for actor in self.selectable_actors:
- if actor.need_update_cshape:
- if self.collision_manager.knows(actor):
- self.collision_manager.remove_tricky(actor)
- actor.update_cshape()
- self.collision_manager.add(actor)
-
- def draw_selection(self) -> None:
- for actor, cshape in self.selection.items():
- grid_position = self.grid_manager.get_grid_position(actor.position)
- rect_positions = self.grid_manager.get_rectangle_positions(grid_position)
-
- draw_rectangle(
- self.layer_manager.scrolling_manager.world_to_screen_positions(rect_positions),
- actor.subject.properties.get(
- SELECTION_COLOR_RGB,
- self.config.get(DEFAULT_SELECTION_COLOR_RGB, (0, 81, 211))
- ),
- )
-
- def draw_interactions(self) -> None:
- if self.user_action_pending:
- try:
- interaction = self.layer_manager.interaction_manager.get_for_user_action(self.user_action_pending)
- interaction.draw_pending()
- except InteractionNotFound:
- pass
-
- def on_enter(self):
- super().on_enter()
- scene = self.get_ancestor(cocos.scene.Scene)
- if self.elastic_box is None:
- self.elastic_box = MinMaxRect(self.layer_manager)
- scene.add(self.elastic_box, z=10)
-
- def update(self, dt):
- mx = self.buttons['right'] - self.buttons['left']
- my = self.buttons['up'] - self.buttons['down']
- dz = self.buttons['zoomin'] - self.buttons['zoomout']
-
- # scroll
- if self.autoscrolling:
- self.update_autoscroll(dt)
- else:
- # care for keyscrolling
- new_keyscrolling = ((len(self.selection) == 0) and
- (mx != 0 or my != 0))
- new_keyscrolling_descriptor = (mx, my)
- if ((new_keyscrolling != self.keyscrolling) or
- (new_keyscrolling_descriptor != self.keyscrolling_descriptor)):
- self.keyscrolling = new_keyscrolling
- self.keyscrolling_descriptor = new_keyscrolling_descriptor
- fastness = 1.0
- if mx != 0 and my != 0:
- fastness *= 0.707106 # 1/sqrt(2)
- self.autoscrolling_sdelta = (0.5 * fastness * mx, 0.5 * fastness * my)
- if self.keyscrolling:
- self.update_autoscroll(dt)
-
- # selection move
- if self.drag_moving:
- # update positions
- wx, wy = self.world_mouse
- dx = wx - self.wdrag_start_point[0]
- dy = wy - self.wdrag_start_point[1]
- if self.restricted_mov:
- if abs(dy) > abs(dx):
- dx = 0
- else:
- dy = 0
- dpos = euclid.Vector2(dx, dy)
- for actor in self.selection:
- old_pos = self.selection[actor].center
- new_pos = old_pos + dpos
-
- try:
- grid_pos = self.grid_manager.get_grid_position(new_pos)
- grid_pixel_pos = self.grid_manager.get_world_position_of_grid_position(grid_pos)
- actor.update_position(grid_pixel_pos)
- except OuterWorldPosition:
- # don't update position
- pass
-
- scroller = self.weak_scroller()
-
- # zoom
- zoom_change = (dz != 0 or self.wheel != 0)
- if zoom_change:
- if self.mouse_into_world():
- wzoom_center = self.world_mouse
- szoom_center = self.screen_mouse
- else:
- # decay to scroller unadorned
- wzoom_center = None
- if self.wheel != 0:
- dt_dz = 0.01666666 * self.wheel
- self.wheel = 0
- else:
- dt_dz = dt * dz
- zoom = scroller.scale + dt_dz * self.zoom_fastness
- if zoom < self.zoom_min:
- zoom = self.zoom_min
- elif zoom > self.zoom_max:
- zoom = self.zoom_max
- scroller.scale = zoom
- if wzoom_center is not None:
- # postprocess toward 'world point under mouse the same before
- # and after zoom' ; other restrictions may prevent fully comply
- wx1, wy1 = self.layer_manager.scrolling_manager.screen_to_world(*szoom_center)
- fx = scroller.restricted_fx + (wzoom_center[0] - wx1)
- fy = scroller.restricted_fy + (wzoom_center[1] - wy1)
- scroller.set_focus(fx, fy)
-
- def update_mouse_position(self, sx, sy):
- self.screen_mouse = sx, sy
- self.world_mouse = self.layer_manager.scrolling_manager.screen_to_world(sx, sy)
- # handle autoscroll
- border = self.autoscroll_border
- if border is not None:
- # sleft and companions includes the border
- scroller = self.weak_scroller()
- self.update_view_bounds()
- sdx = 0.0
- if sx < self.sleft:
- sdx = sx - self.sleft
- elif sx > self.sright:
- sdx = sx - self.sright
- sdy = 0.0
- if sy < self.sbottom:
- sdy = sy - self.sbottom
- elif sy > self.s_top:
- sdy = sy - self.s_top
- self.autoscrolling = sdx != 0.0 or sdy != 0.0
- if self.autoscrolling:
- self.autoscrolling_sdelta = (sdx / border, sdy / border)
-
- def update_autoscroll(self, dt):
- fraction_sdx, fraction_sdy = self.autoscrolling_sdelta
- scroller = self.weak_scroller()
- worldview = self.weak_worldview()
- f = self.autoscroll_fastness
- wdx = (fraction_sdx * f * dt) / scroller.scale / worldview.scale
- wdy = (fraction_sdy * f * dt) / scroller.scale / worldview.scale
- # ask scroller to try scroll (wdx, wdy)
- fx = scroller.restricted_fx + wdx
- fy = scroller.restricted_fy + wdy
- scroller.set_focus(fx, fy)
- self.world_mouse = self.layer_manager.scrolling_manager.screen_to_world(*self.screen_mouse)
- self.adjust_elastic_box()
- # self.update_view_bounds()
-
- def update_view_bounds(self):
- scroller = self.weak_scroller()
- scx, scy = self.layer_manager.scrolling_manager.world_to_screen(
- scroller.restricted_fx,
- scroller.restricted_fy,
- )
- hw = scroller.view_w / 2.0
- hh = scroller.view_h / 2.0
- border = self.autoscroll_border
- self.sleft = scx - hw + border
- self.sright = scx + hw - border
- self.sbottom = scy - hh + border
- self.s_top = scy + hh - border
-
- def mouse_into_world(self):
- worldview = self.weak_worldview()
- # TODO: allow lower limits != 0 ?
- return ((0 <= self.world_mouse[0] <= worldview.width) and
- (0 <= self.world_mouse[1] <= worldview.height))
-
- def on_key_press(self, k, m):
- binds = self.bindings
- self._on_key_press(k, m)
-
- if k in binds:
- self.buttons[binds[k]] = 1
- self.modifiers[binds[k]] = 1
- return True
- return False
-
- def _on_key_press(self, k, m):
- pass
-
- def on_key_release(self, k, m):
- binds = self.bindings
- if k in binds:
- self.buttons[binds[k]] = 0
- self.modifiers[binds[k]] = 0
- return True
- return False
-
- def on_mouse_motion(self, sx, sy, dx, dy):
- self.update_mouse_position(sx, sy)
-
- def on_mouse_leave(self, sx, sy):
- self.autoscrolling = False
-
- def on_mouse_press(self, x, y, buttons, modifiers):
- rx, ry = self.layer_manager.scrolling_manager.screen_to_world(x, y)
- self.logger.debug(
- 'GUI click: x: {}, y: {}, rx: {}, ry: {} ({}|{})'.format(x, y, rx, ry, buttons, modifiers)
- )
-
- if mouse.LEFT:
- # Non action pending case
- if not self.user_action_pending:
- actor = self.single_actor_from_mouse()
- if actor:
- self.selection.clear()
- self.selection_add(actor)
- # Action pending case
- else:
- try:
- interaction = self.layer_manager.interaction_manager.get_for_user_action(self.user_action_pending)
- interaction.execute()
- except InteractionNotFound:
- pass
-
- if mouse.RIGHT:
- if self.user_action_pending:
- self.user_action_pending = None
-
- def on_mouse_release(self, sx, sy, button, modifiers):
- # should we handle here mod_restricted_mov ?
- wx, wy = self.layer_manager.scrolling_manager.screen_to_world(sx, sy)
- modify_selection = modifiers & self.mod_modify_selection
- if self.dragging:
- # ignore all buttons except left button
- if button != mouse.LEFT:
- return
- if self.drag_selecting:
- self.end_drag_selection(wx, wy, modify_selection)
- elif self.drag_moving:
- self.end_drag_move(wx, wy)
- self.dragging = False
- else:
- if button == mouse.LEFT:
- self.end_click_selection(wx, wy, modify_selection)
-
- def end_click_selection(self, wx, wy, modify_selection):
- under_mouse_unique = self.single_actor_from_mouse()
- if modify_selection:
- # toggle selected status for unique
- if under_mouse_unique in self.selection:
- self.selection_remove(under_mouse_unique)
- elif under_mouse_unique is not None:
- self.selection_add(under_mouse_unique)
- else:
- # new_selected becomes the current selected
- self.selection.clear()
- self.user_action_pending = None
- if under_mouse_unique is not None:
- self.selection_add(under_mouse_unique)
-
- def selection_add(self, actor):
- self.selection[actor] = actor.cshape.copy()
-
- def selection_remove(self, actor):
- del self.selection[actor]
-
- def end_drag_selection(self, wx, wy, modify_selection):
- new_selection = self.collision_manager.objs_into_box(*self.elastic_box_wminmax)
- if not modify_selection:
- # new_selected becomes the current selected
- self.selection.clear()
- for actor in new_selection:
- self.selection_add(actor)
-
- self.elastic_box.visible = False
- self.drag_selecting = False
-
- def on_mouse_drag(self, sx, sy, dx, dy, buttons, modifiers):
- # TODO: inhibir esta llamada si estamos fuera de la client area / viewport
- self.update_mouse_position(sx, sy)
- if not buttons & mouse.LEFT:
- # ignore except for left-btn-drag
- return
-
- if not self.dragging:
- print("begin drag")
- self.begin_drag()
- return
-
- if self.drag_selecting:
- # update elastic box
- self.adjust_elastic_box()
- elif self.drag_moving:
- self.restricted_mov = (modifiers & self.mod_restricted_mov)
-
- def adjust_elastic_box(self):
- # when elastic_box visible this method needs to be called any time
- # world_mouse changes or screen_to_world results changes (scroll, etc)
- wx0, wy0 = self.wdrag_start_point
- wx1, wy1 = self.world_mouse
- wminx = min(wx0, wx1)
- wmaxx = max(wx0, wx1)
- wminy = min(wy0, wy1)
- wmaxy = max(wy0, wy1)
- self.elastic_box_wminmax = wminx, wmaxx, wminy, wmaxy
- self.elastic_box.adjust_from_w_minmax(*self.elastic_box_wminmax)
-
- def begin_drag(self):
- self.dragging = True
- self.wdrag_start_point = self.world_mouse
- under_mouse_unique = self.single_actor_from_mouse()
- if under_mouse_unique is None:
- # begin drag selection
- self.drag_selecting = True
- self.adjust_elastic_box()
- self.elastic_box.visible = True
- print("begin drag selection: drag_selecting, drag_moving",
- self.drag_selecting, self.drag_moving)
-
- elif self.can_move(under_mouse_unique):
- # want drag move
- if under_mouse_unique in self.selection:
- # want to move current selection
- pass
- else:
- # change selection before moving
- self.selection.clear()
- self.selection_add(under_mouse_unique)
- self.begin_drag_move()
-
- def can_move(self, selected) -> bool:
- return True
-
- def begin_drag_move(self):
- # begin drag move
- self.drag_moving = True
-
- # how-to update collman: remove/add vs clear/add all
- # when total number of actors is low anyone will be fine,
- # with high numbers, most probably we move only a small fraction
- # For simplicity I choose remove/add, albeit a hybrid aproach
- # can be implemented later
- self.set_selection_in_collman(False)
- # print "begin drag: drag_selecting, drag_moving", self.drag_selecting, self.drag_moving
-
- def end_drag_move(self, wx, wy):
- self.set_selection_in_collman(True)
- for actor in self.selection:
- self.selection[actor] = actor.cshape.copy()
-
- self.drag_moving = False
-
- def single_actor_from_mouse(self):
- under_mouse = self.collision_manager.objs_touching_point(*self.world_mouse)
- if len(under_mouse) == 0:
- return None
- # return the one with the center most near to mouse, if tie then
- # an arbitrary in the tie
- nearest = None
- near_d = None
- p = euclid.Vector2(*self.world_mouse)
- for actor in under_mouse:
- d = (actor.cshape.center - p).magnitude_squared()
- if nearest is None or (d < near_d):
- nearest = actor
- near_d = d
- return nearest
-
- def set_selection_in_collman(self, bool_value):
- if self.selection_in_collman == bool_value:
- return
- self.selection_in_collman = bool_value
- if bool_value:
- for actor in self.selection:
- self.collision_manager.add(actor)
- else:
- for actor in self.selection:
- self.collision_manager.remove_tricky(actor)
-
- def on_mouse_scroll(self, x, y, scroll_x, scroll_y):
- # TODO: check if mouse over scroller viewport?
- self.wheel += scroll_y * self.wheel_multiplier
-
-
- class MainLayer(ScrollableLayer):
- is_event_handler = True
-
- def __init__(
- self,
- layer_manager: LayerManager,
- grid_manager: GridManager,
- width: int,
- height: int,
- scroll_step: int=100,
- ) -> None:
- super().__init__()
- self.layer_manager = layer_manager
- self.scroll_step = scroll_step
- self.grid_manager = grid_manager
-
- self.width = width
- self.height = height
- self.px_width = width
- self.px_height = height
-
-
- class SubjectMapper(object):
- def __init__(
- self,
- config: Config,
- actor_class: typing.Type[Actor],
- ) -> None:
- self.config = config
- self.actor_class = actor_class
-
- def append(
- self,
- subject: XYZSubjectMixin,
- layer_manager: LayerManager,
- ) -> None:
- actor = self.actor_class(self.config, subject)
- pixel_position = layer_manager.grid_manager.get_world_position_of_grid_position(
- (subject.position[0], subject.position[1]),
- )
- actor.update_position(euclid.Vector2(*pixel_position))
-
- # TODO: Selectable nature must be configurable
- layer_manager.add_subject(actor)
- layer_manager.set_selectable(actor)
-
-
- class SubjectMapperFactory(object):
- def __init__(self) -> None:
- self.mapping = {} # type: typing.Dict[typing.Type[XYZSubjectMixin], SubjectMapper]
-
- def register_mapper(self, subject_class: typing.Type[XYZSubjectMixin], mapper: SubjectMapper) -> None:
- if subject_class not in self.mapping:
- self.mapping[subject_class] = mapper
- else:
- raise ValueError('subject_class already register with "{}"'.format(str(self.mapping[subject_class])))
-
- def get_subject_mapper(self, subject: XYZSubjectMixin) -> SubjectMapper:
- for subject_class, mapper in self.mapping.items():
- if isinstance(subject, subject_class):
- return mapper
- raise KeyError('No mapper for subject "{}"'.format(str(subject)))
-
-
- class Gui(object):
- layer_manager_class = LayerManager
-
- def __init__(
- self,
- config: Config,
- terminal: Terminal,
- physics: Physics,
- read_queue_interval: float= 1/60.0,
- ):
- self.config = config
- self.logger = get_logger('Gui', config)
- self.physics = physics
- self._read_queue_interval = read_queue_interval
- self.terminal = terminal
- self.cycle_duration = self.config.resolve('core.cycle_duration')
-
- # Manager cache directory
- cache_dir_path = self.config.resolve('global.cache_dir_path')
- if not cache_dir_path:
- raise SynergineException(
- 'This code require the "global.cache_dir_path" config',
- )
-
- ensure_dir_exist(cache_dir_path)
-
- cocos.director.director.init(
- width=640,
- height=480,
- vsync=True,
- resizable=False
- )
- mixer.init()
-
- self.interaction_manager = InteractionManager(
- config=self.config,
- terminal=self.terminal,
- )
- self.layer_manager = self.layer_manager_class(
- self.config,
- middleware=self.get_layer_middleware(),
- interaction_manager=self.interaction_manager,
- gui=self,
- )
- self.layer_manager.init()
- self.layer_manager.connect_layers()
- self.layer_manager.center()
-
- # Enable blending
- pyglet.gl.glEnable(pyglet.gl.GL_BLEND)
- pyglet.gl.glBlendFunc(pyglet.gl.GL_SRC_ALPHA, pyglet.gl.GL_ONE_MINUS_SRC_ALPHA)
-
- # Enable transparency
- pyglet.gl.glEnable(pyglet.gl.GL_ALPHA_TEST)
- pyglet.gl.glAlphaFunc(pyglet.gl.GL_GREATER, .1)
-
- self.subject_mapper_factory = SubjectMapperFactory()
-
- def get_layer_middleware(self) -> MapMiddleware:
- raise NotImplementedError()
-
- def run(self):
- self.before_run()
- pyglet.clock.schedule_interval(
- lambda *_, **__: self.terminal.read(),
- self._read_queue_interval,
- )
- cocos.director.director.run(self.get_main_scene())
-
- def before_run(self) -> None:
- pass
-
- def get_main_scene(self) -> cocos.cocosnode.CocosNode:
- raise NotImplementedError()
-
- def before_received(self, package: TerminalPackage):
- pass
-
- def after_received(self, package: TerminalPackage):
- pass
-
-
- class TMXGui(Gui):
- def __init__(
- self,
- config: Config,
- terminal: Terminal,
- physics: Physics,
- read_queue_interval: float = 1 / 60.0,
- map_dir_path: str=None,
- ):
- assert map_dir_path
- self.map_dir_path = map_dir_path
- super(TMXGui, self).__init__(
- config,
- terminal,
- physics=physics,
- read_queue_interval=read_queue_interval,
- )
- self.physics = physics
-
- def get_layer_middleware(self) -> MapMiddleware:
- return TMXMiddleware(
- self.config,
- self.map_dir_path,
- )
-
- def get_main_scene(self) -> cocos.cocosnode.CocosNode:
- return self.layer_manager.main_scene
-
- def before_received(self, package: TerminalPackage):
- super().before_received(package)
- if package.subjects: # They are new subjects in the simulation
- for subject in package.subjects:
- self.append_subject(subject)
-
- def append_subject(self, subject: XYZSubjectMixin) -> None:
- subject_mapper = self.subject_mapper_factory.get_subject_mapper(subject)
- subject_mapper.append(subject, self.layer_manager)
|