diff --git a/src/FGAme/demos/integrations/kivy_input.py b/src/FGAme/demos/integrations/kivy_input.py new file mode 100644 index 0000000..943ae28 --- /dev/null +++ b/src/FGAme/demos/integrations/kivy_input.py @@ -0,0 +1,86 @@ +from kivy.app import App +from kivy.clock import Clock +from kivy.uix.relativelayout import RelativeLayout + +from FGAme import listen +from FGAme.kivy_backend import KivyWorld, KivyMainLoop, KivyInput + + +@listen('key-up', 'up') +def key_up(*args): + print('key-up') + + +@listen('key-down', 'down') +def key_down(): + print('key-down') + + +@listen('mouse-button-down') +def mouse_down(x, y): + print(x) + print(y) + + +@listen('mouse-button-up') +def mouse_up(*args): + print("zeca") + + +@listen('mouse-long-press', 'left') +def mouse_long_press(*args): + print(args) + + +@listen('long-press', 'x') +def long_press(*args): + print('x') + + +class WorldExample(KivyWorld): + + def init_objects(self): + self.add.circle(20, pos=(100, 110), vel=(300, 0), color='random') + self.add.aabb(shape=(20, 120), pos=(440, 300), color='random') + self.add.rectangle(shape=(20, 120), pos=(300, 100), color='red') + self.add.rectangle(shape=(20, 120), pos=(700, 100), color='red') + self.add.poly([(100, 100), (200, 100), (200, 200), (50, 200), (75, 125)], + vel=(100, 100), pos=(400, 100), color='random') + self.add.margin() + + +class FGAmeWidget(RelativeLayout): + """ + Wraps an FGAme World built with circles. + """ + + def __init__(self, world, fps=60): + super().__init__() + self.mainloop = KivyMainLoop(None, KivyInput(), fps, self) + self.world = world + self.world.widget = self + self.world.init_objects() + Clock.schedule_interval(self.fgame_step, self.mainloop.dt) + + def fgame_step(self, dt): + self.mainloop.step(self.world) + + +class FGAmeApp(App): + """ + Kivy App that runs FGAme simulation. + """ + + def __init__(self, world, widget=None): + super().__init__() + self.world = world + self.widget = widget + + def build(self): + if self.widget is None: + self.widget = FGAmeWidget(self.world) + return self.widget + + +if __name__ == '__main__': + FGAmeApp(WorldExample()).run() diff --git a/src/FGAme/kivy_backend.py b/src/FGAme/kivy_backend.py new file mode 100644 index 0000000..1eb8228 --- /dev/null +++ b/src/FGAme/kivy_backend.py @@ -0,0 +1,208 @@ +from FGAme.input import Input +from FGAme import World +from FGAme import Circle, AABB, Rectangle as FGAmeRectangle, Poly +from FGAme.mainloop import MainLoop + +from kivy.core.window import Window +from kivy.graphics import Color, Ellipse +from kivy.graphics import Rectangle, Rotate, PushMatrix, PopMatrix, Translate +from kivy.graphics.tesselator import Tesselator +from kivy.graphics import Mesh + +from math import pi + +import time + +from functools import singledispatch + +from queue import Queue + + +class KivyInput(Input): + + def __init__(self): + super(KivyInput, self).__init__() + self.events = Queue() + # algumas teclas possuem nomes diferentes + self.keys = {v: k for k, v in Window._system_keyboard.keycodes.items()} + Window.bind(on_key_up=self.add_on_key_up) + Window.bind(on_key_down=self.add_on_key_down) + Window.bind(on_touch_down=self.add_on_touch_down) + Window.bind(on_touch_up=self.add_on_touch_up) + self.hold_mouse = True + self.hold_key = True + + def poll(self): + + while not self.events.empty(): + event = self.events.get() + if event.type == 'key-up': + self.process_key_up(event.key) + elif event.type == 'key-down': + self.process_key_down(event.key) + elif event.type == 'long-press': + self.process_long_press() + elif event.type == 'mouse-button-down': + self.process_mouse_button_down('left', event.pos) + elif event.type == 'mouse-button-up': + self.process_mouse_button_up('left', event.pos) + elif event.type == 'mouse-long-press': + self.process_mouse_longpress() + + def add_on_key_up(self, *args): + self.events.put(Event('key-up', self.keys[args[1]], None)) + self.hold_key = True + + def add_on_key_down(self, *args): + + if self.hold_key: + self.events.put(Event('key-down', self.keys[args[1]], None)) + self.hold_key = False + + self.events.put(Event('long-press', self.keys[args[1]], None)) + + def add_on_touch_down(self, *args): + + self.hold_mouse = True + + self.events.put(Event('mouse-button-down', None, args[1].pos)) + + if self.hold_mouse: + import threading + + def work(): + while self.hold_mouse: + self.events.put( + Event( + 'mouse-long-press', + None, + args[1].pos)) + time.sleep(0.1) + + t = threading.Thread(target=work) + t.daemon = True + t.start() + + def add_on_touch_up(self, *args): + self.events.put(Event('mouse-button-up', None, args[1].pos)) + self.hold_mouse = False + + +class Event: + + def __init__(self, type, key, pos): + self.type = type + self.key = key + self.pos = pos + + +class KivyObjectWrapper: + def __init__(self, obj_fgame, obj_kivy, translation=None, rotation=None): + self.obj_fgame = obj_fgame + self.obj_kivy = obj_kivy + self.translation = translation + self.rotation = rotation + self.initial_pos = obj_fgame.pos + + +class KivyWorld(World): + + def __init__(self, widget=None): + super().__init__() + self.widget = widget + self.objects = [] + + def _add(self, obj, layer=0): + super()._add(obj, layer) + register_to_canvas(obj, self) + + def update(self, dt): + super().update(dt) + for obj in self.objects: + if isinstance(obj.obj_fgame, FGAmeRectangle) or isinstance( + obj.obj_fgame, Poly): + obj.translation.x = obj.obj_fgame.pos.x - obj.initial_pos.x + obj.translation.y = obj.obj_fgame.pos.y - obj.initial_pos.y + obj.rotation.angle = obj.obj_fgame.theta * 180.0 / pi + else: + obj.obj_kivy.pos = obj.obj_fgame.pos_sw + + +@singledispatch +def register_to_canvas(obj, world): + pass + + +@register_to_canvas.register(Circle) +def _(obj, world): + with world.widget.canvas: + diameter = 2 * obj.radius + Color(*obj.color.rgbf) + kcircle = Ellipse(pos=obj.pos_sw, size=(diameter, diameter)) + world.objects.append(KivyObjectWrapper(obj, kcircle)) + + +@register_to_canvas.register(AABB) +def _(obj, world): + with world.widget.canvas: + Color(*obj.color.rgbf) + x, y = obj.xmax - obj.xmin, obj.ymax - obj.ymin + krectangle = Rectangle(pos=obj.pos_sw, size=(x, y)) + world.objects.append(KivyObjectWrapper(obj, krectangle)) + + +@register_to_canvas.register(Poly) +def _(obj, world): + tess = Tesselator() + vertices = [] + for x, y in obj.vertices: + vertices.append(x) + vertices.append(y) + tess.add_contour(vertices) + if not tess.tesselate(): + raise Exception('Tesselator didn\'t work') + with world.widget.canvas: + Color(*obj.color.rgbf) + PushMatrix() + translation = Translate(0, 0) + rotation = Rotate(axis=(0, 0, 1), origin=(obj.pos.x, obj.pos.y, 0)) + for vertices, indices in tess.meshes: + Mesh(vertices=vertices, indices=indices, mode="triangle_fan") + PopMatrix() + world.objects.append( + KivyObjectWrapper( + obj, + tess, + translation, + rotation)) + + +@register_to_canvas.register(FGAmeRectangle) +def _(obj, world): + with world.widget.canvas: + PushMatrix() + translation = Translate(0, 0) + rotation = Rotate(axis=(0, 0, 1), origin=(obj.pos.x, obj.pos.y, 0)) + Color(*obj.color.rgbf) + x, y = obj.xmax - obj.xmin, obj.ymax - obj.ymin + krectangle = Rectangle(pos=obj.pos_sw, size=(x, y)) + PopMatrix() + world.objects.append( + KivyObjectWrapper( + obj, + krectangle, + translation, + rotation)) + + +class KivyMainLoop(MainLoop): + + def __init__(self, screen, input, fps=None, widget=None): + super().__init__(screen, input, fps) + self.widget = widget + + def step_screen(self, world): + pass + + def sleep(self, dt): + pass diff --git a/src/FGAme/mainloop.py b/src/FGAme/mainloop.py index 20d3e59..256535b 100644 --- a/src/FGAme/mainloop.py +++ b/src/FGAme/mainloop.py @@ -150,7 +150,7 @@ def step_endframe(self, state, start_time, wait=True): self.n_skip += 1 frame_skip_signal.trigger(-wait) elif wait: - time.sleep(self.sleep_time) + self.sleep(self.sleep_time) self.time += self.dt self.n_iter += 1 @@ -173,6 +173,9 @@ def stop(self): self._running = False + def sleep(self, dt): + time.sleep(dt) + def schedule(self, time, function=None, args=None, kwargs=None, **passed_kwargs): """