Browse Source

Add interaction management on gui

Bastien Sevajol 6 years ago
parent
commit
fe1fd110b6

+ 3 - 2
sandbox/tile/gui/actor.py View File

@@ -3,6 +3,7 @@ import pyglet
3 3
 
4 4
 from sandbox.tile.gui.animation import ANIMATION_WALK
5 5
 from sandbox.tile.gui.animation import ANIMATION_CRAWL
6
+from synergine2.simulation import Subject
6 7
 from synergine2_cocos2d.actor import Actor
7 8
 
8 9
 
@@ -29,5 +30,5 @@ class Man(Actor):
29 30
         ]
30 31
     }
31 32
 
32
-    def __init__(self):
33
-        super().__init__(pyglet.resource.image('actors/man.png'))
33
+    def __init__(self, subject: Subject) -> None:
34
+        super().__init__(pyglet.resource.image('actors/man.png'), subject=subject)

+ 3 - 1
sandbox/tile/gui/base.py View File

@@ -5,11 +5,13 @@ from sandbox.tile.gui.animation import ANIMATION_WALK
5 5
 from sandbox.tile.gui.animation import ANIMATION_CRAWL
6 6
 from synergine2_cocos2d.animation import Animate
7 7
 from synergine2_cocos2d.gui import TMXGui
8
+from synergine2_cocos2d.interaction import MoveActorInteraction
8 9
 
9 10
 
10 11
 class Game(TMXGui):
11 12
     def before_run(self) -> None:
12
-        pass
13
+        self.layer_manager.interaction_manager.register(MoveActorInteraction, self.layer_manager)
14
+
13 15
         # Test
14 16
         # from sandbox.tile.gui.actor import Man
15 17
         # from cocos import euclid

+ 3 - 0
synergine2_cocos2d/actor.py View File

@@ -6,6 +6,7 @@ import pyglet
6 6
 import cocos
7 7
 from cocos import collision_model
8 8
 from cocos import euclid
9
+from synergine2.simulation import Subject
9 10
 from synergine2_cocos2d.animation import AnimatedInterface
10 11
 
11 12
 
@@ -16,6 +17,7 @@ class Actor(AnimatedInterface, cocos.sprite.Sprite):
16 17
     def __init__(
17 18
         self,
18 19
         image: pyglet.image.TextureRegion,
20
+        subject: Subject,
19 21
         position=(0, 0),
20 22
         rotation=0,
21 23
         scale=1,
@@ -34,6 +36,7 @@ class Actor(AnimatedInterface, cocos.sprite.Sprite):
34 36
             anchor,
35 37
             **kwargs
36 38
         )
39
+        self.subject = subject
37 40
         self.cshape = None  # type: collision_model.AARectShape
38 41
         self.update_cshape()
39 42
         self.build_animation_images()

+ 21 - 0
synergine2_cocos2d/event.py View File

@@ -0,0 +1,21 @@
1
+# coding: utf-8
2
+
3
+"""
4
+WARNING: Do not import cocos/pyglet stuff here: cocos/pyglet modules must be loaded inside gui process.
5
+"""
6
+import typing
7
+
8
+from synergine2.simulation import Event
9
+
10
+
11
+class GuiRequestMoveEvent(Event):
12
+    def __init__(
13
+        self,
14
+        subject_id: int,
15
+        move_to_position: typing.Tuple[int, int],
16
+        *args,
17
+        **kwargs
18
+    ) -> None:
19
+        super().__init__(*args, **kwargs)
20
+        self.subject_id = subject_id
21
+        self.move_to_position = move_to_position

+ 4 - 0
synergine2_cocos2d/exception.py View File

@@ -11,3 +11,7 @@ class PositionException(WorldException):
11 11
 
12 12
 class OuterWorldPosition(PositionException):
13 13
     pass
14
+
15
+
16
+class InteractionNotFound(Exception):
17
+    pass

+ 15 - 0
synergine2_cocos2d/gl.py View File

@@ -38,3 +38,18 @@ def draw_rectangle(
38 38
         pyglet.gl.glVertex3f(positions[2][0], positions[2][1], 0)
39 39
         pyglet.gl.glVertex3f(positions[3][0], positions[3][1], 0)
40 40
         pyglet.gl.glEnd()
41
+
42
+
43
+def draw_line(
44
+    from_position: typing.Tuple[int, int],
45
+    to_position: typing.Tuple[int, int],
46
+    color: rgb_type,
47
+    width: typing.Optional[int]=1,
48
+):
49
+    pyglet.gl.glColor3ub(*color)
50
+    pyglet.gl.glLineWidth(width)
51
+    pyglet.graphics.draw(
52
+        4,
53
+        pyglet.gl.GL_LINES,
54
+        ("v2f", (0, 0, 0, 0, from_position[0], from_position[1], to_position[0], to_position[1]))
55
+    )

+ 72 - 17
synergine2_cocos2d/gui.py View File

@@ -5,6 +5,7 @@ from math import floor
5 5
 
6 6
 import pyglet
7 7
 from pyglet.window import mouse
8
+from pyglet.window import key
8 9
 
9 10
 import cocos
10 11
 from cocos import collision_model
@@ -17,10 +18,14 @@ from synergine2.terminals import TerminalPackage
17 18
 from synergine2.xyz import XYZSubjectMixin
18 19
 from synergine2_cocos2d.actor import Actor
19 20
 from synergine2_cocos2d.exception import OuterWorldPosition
21
+from synergine2_cocos2d.exception import InteractionNotFound
20 22
 from synergine2_cocos2d.gl import rectangle_positions_type
21 23
 from synergine2_cocos2d.gl import draw_rectangle
24
+from synergine2_cocos2d.interaction import InteractionManager
22 25
 from synergine2_cocos2d.layer import LayerManager
23 26
 from synergine2_cocos2d.middleware import TMXMiddleware
27
+from synergine2_cocos2d.middleware import TMXMiddlewareMapMiddleware
28
+from synergine2_cocos2d.user_action import UserAction
24 29
 
25 30
 
26 31
 class GridManager(object):
@@ -176,6 +181,7 @@ class EditLayer(cocos.layer.Layer):
176 181
         self.sright = None
177 182
         self.sbottom = None
178 183
         self.s_top = None
184
+        self.user_action_pending = None  # UserAction
179 185
 
180 186
         # opers that change cshape must ensure it goes to False,
181 187
         # selection opers must ensure it goes to True
@@ -204,6 +210,11 @@ class EditLayer(cocos.layer.Layer):
204 210
         self.collision_manager.remove_tricky(actor)
205 211
 
206 212
     def draw(self, *args, **kwargs) -> None:
213
+        self.draw_update_cshapes()
214
+        self.draw_selection()
215
+        self.draw_interactions()
216
+
217
+    def draw_update_cshapes(self) -> None:
207 218
         for actor in self.selectable_actors:
208 219
             if actor.need_update_cshape:
209 220
                 if self.collision_manager.knows(actor):
@@ -211,6 +222,7 @@ class EditLayer(cocos.layer.Layer):
211 222
                     actor.update_cshape()
212 223
                     self.collision_manager.add(actor)
213 224
 
225
+    def draw_selection(self) -> None:
214 226
         for actor, cshape in self.selection.items():
215 227
             grid_position = self.grid_manager.get_grid_position(actor.position)
216 228
             rect_positions = self.grid_manager.get_rectangle_positions(grid_position)
@@ -220,6 +232,14 @@ class EditLayer(cocos.layer.Layer):
220 232
                 (0, 81, 211),
221 233
             )
222 234
 
235
+    def draw_interactions(self) -> None:
236
+        if self.user_action_pending:
237
+            try:
238
+                interaction = self.layer_manager.interaction_manager.get_for_user_action(self.user_action_pending)
239
+                interaction.draw_pending()
240
+            except InteractionNotFound:
241
+                pass
242
+
223 243
     def on_enter(self):
224 244
         super(EditLayer, self).on_enter()
225 245
         scene = self.get_ancestor(cocos.scene.Scene)
@@ -365,6 +385,13 @@ class EditLayer(cocos.layer.Layer):
365 385
 
366 386
     def on_key_press(self, k, m):
367 387
         binds = self.bindings
388
+
389
+        # TODO: Clarify code
390
+        # Actions available if actor selected
391
+        if self.selection:
392
+            if k == key.M:
393
+                self.user_action_pending = UserAction.ORDER_MOVE
394
+
368 395
         if k in binds:
369 396
             self.buttons[binds[k]] = 1
370 397
             self.modifiers[binds[k]] = 1
@@ -391,9 +418,24 @@ class EditLayer(cocos.layer.Layer):
391 418
             'GUI click: x: {}, y: {}, rx: {}, ry: {} ({}|{})'.format(x, y, rx, ry, buttons, modifiers)
392 419
         )
393 420
 
394
-        actor = self.single_actor_from_mouse()
395
-        if actor:
396
-            self.selection_add(actor)
421
+        if mouse.LEFT:
422
+            # Non action pending case
423
+            if not self.user_action_pending:
424
+                actor = self.single_actor_from_mouse()
425
+                if actor:
426
+                    self.selection.clear()
427
+                    self.selection_add(actor)
428
+            # Action pending case
429
+            else:
430
+                try:
431
+                    interaction = self.layer_manager.interaction_manager.get_for_user_action(self.user_action_pending)
432
+                    interaction.execute()
433
+                except InteractionNotFound:
434
+                    pass
435
+
436
+        if mouse.RIGHT:
437
+            if self.user_action_pending:
438
+                self.user_action_pending = None
397 439
 
398 440
     def on_mouse_release(self, sx, sy, button, modifiers):
399 441
         # should we handle here mod_restricted_mov ?
@@ -423,6 +465,7 @@ class EditLayer(cocos.layer.Layer):
423 465
         else:
424 466
             # new_selected becomes the current selected
425 467
             self.selection.clear()
468
+            self.user_action_pending = None
426 469
             if under_mouse_unique is not None:
427 470
                 self.selection_add(under_mouse_unique)
428 471
 
@@ -581,11 +624,10 @@ class SubjectMapper(object):
581 624
         subject: XYZSubjectMixin,
582 625
         layer_manager: LayerManager,
583 626
     ) -> None:
584
-        actor = self.actor_class()
585
-        pixel_position = layer_manager.grid_manager.get_pixel_position_of_grid_position((
586
-            subject.position[0],
587
-            subject.position[1],
588
-        ))
627
+        actor = self.actor_class(subject)
628
+        pixel_position = layer_manager.grid_manager.get_pixel_position_of_grid_position(
629
+            (subject.position[0], subject.position[1]),
630
+        )
589 631
         actor.update_position(euclid.Vector2(*pixel_position))
590 632
 
591 633
         # TODO: Selectable nature must be configurable
@@ -631,6 +673,20 @@ class Gui(object):
631 673
             resizable=True,
632 674
         )
633 675
 
676
+        self.interaction_manager = InteractionManager(
677
+            config=self.config,
678
+            logger=self.logger,
679
+            terminal=self.terminal,
680
+        )
681
+        self.layer_manager = LayerManager(
682
+            self.config,
683
+            self.logger,
684
+            middleware=self.get_layer_middleware(),
685
+            interaction_manager=self.interaction_manager,
686
+        )
687
+        self.layer_manager.init()
688
+        self.layer_manager.center()
689
+
634 690
         # Enable blending
635 691
         pyglet.gl.glEnable(pyglet.gl.GL_BLEND)
636 692
         pyglet.gl.glBlendFunc(pyglet.gl.GL_SRC_ALPHA, pyglet.gl.GL_ONE_MINUS_SRC_ALPHA)
@@ -641,6 +697,9 @@ class Gui(object):
641 697
 
642 698
         self.subject_mapper_factory = SubjectMapperFactory()
643 699
 
700
+    def get_layer_middleware(self) -> MapMiddleware:
701
+        raise NotImplementedError()
702
+
644 703
     def run(self):
645 704
         self.before_run()
646 705
         pyglet.clock.schedule_interval(
@@ -672,24 +731,20 @@ class TMXGui(Gui):
672 731
         map_dir_path: str=None,
673 732
     ):
674 733
         assert map_dir_path
734
+        self.map_dir_path = map_dir_path
675 735
         super(TMXGui, self).__init__(
676 736
             config,
677 737
             logger,
678 738
             terminal,
679 739
             read_queue_interval,
680 740
         )
681
-        self.map_dir_path = map_dir_path
682
-        self.layer_manager = LayerManager(
741
+
742
+    def get_layer_middleware(self) -> MapMiddleware:
743
+        return TMXMiddleware(
683 744
             self.config,
684 745
             self.logger,
685
-            middleware=TMXMiddleware(
686
-                self.config,
687
-                self.logger,
688
-                self.map_dir_path,
689
-            ),
746
+            self.map_dir_path,
690 747
         )
691
-        self.layer_manager.init()
692
-        self.layer_manager.center()
693 748
 
694 749
     def get_main_scene(self) -> cocos.cocosnode.CocosNode:
695 750
         return self.layer_manager.main_scene

+ 102 - 0
synergine2_cocos2d/interaction.py View File

@@ -0,0 +1,102 @@
1
+# coding: utf-8
2
+import typing
3
+
4
+from synergine2.config import Config
5
+from synergine2.log import SynergineLogger
6
+from synergine2.terminals import Terminal, TerminalPackage
7
+from synergine2_cocos2d.event import GuiRequestMoveEvent
8
+from synergine2_cocos2d.exception import InteractionNotFound
9
+from synergine2_cocos2d.gl import draw_line
10
+from synergine2_cocos2d.layer import LayerManager
11
+from synergine2_cocos2d.user_action import UserAction
12
+
13
+
14
+class InteractionManager(object):
15
+    def __init__(
16
+        self,
17
+        config: Config,
18
+        logger: SynergineLogger,
19
+        terminal: Terminal,
20
+    ) -> None:
21
+        self.config = config
22
+        self.logger = logger
23
+        self.terminal = terminal
24
+        self.interactions = []
25
+
26
+    def register(
27
+        self,
28
+        interaction_class: typing.Type['Interaction'],
29
+        layer_manager: LayerManager,
30
+    ) -> None:
31
+        self.interactions.append(interaction_class(
32
+            self.config,
33
+            self.logger,
34
+            terminal=self.terminal,
35
+            layer_manager=layer_manager,
36
+        ))
37
+
38
+    def get_for_user_action(self, action: UserAction) -> 'Interaction':
39
+        for interaction in self.interactions:
40
+            if interaction.gui_action == action:
41
+                return interaction
42
+        raise InteractionNotFound('For action"{}"'.format(action))
43
+
44
+
45
+class Interaction(object):
46
+    gui_action = None  # type: UserAction
47
+
48
+    def __init__(
49
+        self,
50
+        config: Config,
51
+        logger: SynergineLogger,
52
+        terminal: Terminal,
53
+        layer_manager: LayerManager,
54
+    ) -> None:
55
+        self.config = config
56
+        self.logger = logger
57
+        self.terminal = terminal
58
+        self.layer_manager = layer_manager
59
+
60
+    def draw_pending(self) -> None:
61
+        pass
62
+
63
+    def execute(self) -> None:
64
+        package = self.get_package_for_terminal()
65
+        self.terminal.send(package)
66
+
67
+    def get_package_for_terminal(self) -> TerminalPackage:
68
+        raise NotImplementedError()
69
+
70
+
71
+class MoveActorInteraction(Interaction):
72
+    gui_action = UserAction.ORDER_MOVE
73
+
74
+    def draw_pending(self) -> None:
75
+            for actor in self.layer_manager.edit_layer.selection:
76
+                grid_position = self.layer_manager.grid_manager.get_grid_position(actor.position)
77
+                pixel_position = self.layer_manager.grid_manager.get_pixel_position_of_grid_position(grid_position)
78
+
79
+                draw_line(
80
+                    self.layer_manager.scrolling_manager.world_to_screen(*pixel_position),
81
+                    self.layer_manager.edit_layer.screen_mouse,
82
+                    (0, 0, 255),
83
+                )
84
+
85
+    def get_package_for_terminal(self) -> TerminalPackage:
86
+        # TODO: MoveEvent ?
87
+        events = []
88
+        mouse_grid_position = self.layer_manager.grid_manager.get_grid_position(
89
+            self.layer_manager.scrolling_manager.screen_to_world(
90
+                *self.layer_manager.edit_layer.screen_mouse,
91
+            )
92
+        )
93
+
94
+        for actor in self.layer_manager.edit_layer.selection:
95
+            events.append(GuiRequestMoveEvent(
96
+                subject_id=actor.subject.id,
97
+                move_to_position=mouse_grid_position,
98
+            ))
99
+
100
+        return TerminalPackage(
101
+            events=events
102
+        )

+ 3 - 0
synergine2_cocos2d/layer.py View File

@@ -12,6 +12,7 @@ from synergine2_cocos2d.middleware import MapMiddleware
12 12
 if False:
13 13
     from synergine2_cocos2d.actor import Actor
14 14
     from synergine2_cocos2d.gui import GridManager
15
+    from synergine2_cocos2d.interaction import InteractionManager
15 16
 
16 17
 
17 18
 class ScrollingManager(cocos.layer.ScrollingManager):
@@ -52,10 +53,12 @@ class LayerManager(object):
52 53
         config: Config,
53 54
         logger: SynergineLogger,
54 55
         middleware: MapMiddleware,
56
+        interaction_manager: 'InteractionManager',
55 57
     ) -> None:
56 58
         self.config = config
57 59
         self.logger = logger
58 60
         self.middleware = middleware
61
+        self.interaction_manager = interaction_manager
59 62
 
60 63
         self.grid_manager = None  # type: GridManager
61 64
         self.scrolling_manager = None  # type: ScrollingManager

+ 6 - 0
synergine2_cocos2d/user_action.py View File

@@ -0,0 +1,6 @@
1
+# coding: utf-8
2
+from enum import Enum
3
+
4
+
5
+class UserAction(Enum):
6
+    ORDER_MOVE = 'ORDER_MOVE'