Skip to content

Commit

Permalink
Fix "non responsive buttons" issue on the XO (#44)
Browse files Browse the repository at this point in the history
Gtk+ events were not being handled in the application event loop, so we
add a callback to check for and handle the events.

Also ported to Sugargame v1.2 which solves some other problems.

Tested on OLPC XO-4 with OLPC OS 13.2.9.
  • Loading branch information
quozl authored and wilfriedE committed Jan 20, 2018
1 parent e22c3b7 commit 395170f
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 136 deletions.
59 changes: 26 additions & 33 deletions PyCutActivity.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from gettext import gettext as _

import sys

import gi
gi.require_version('Gtk', '3.0')

from gi.repository import Gtk
import pygame

import sugar3.activity.activity
from sugar3.activity.activity import Activity
from sugar3.graphics.toolbarbox import ToolbarBox
from sugar3.activity.widgets import ActivityToolbarButton
from sugar3.graphics.toolbutton import ToolButton
Expand All @@ -14,29 +18,34 @@
import sugargame.canvas
import game

class PyCutActivity(sugar3.activity.activity.Activity):
class PyCutActivity(Activity):
def __init__(self, handle):
super(PyCutActivity, self).__init__(handle)
Activity.__init__(self, handle)

self.paused = False
self.max_participants = 1

# Create the game instance.
self.game = game.PyCutGame()
self.game = game.PyCutGame(poll_cb=self._poll_cb)

# Build the activity toolbar.
self.build_toolbar()

# Build the Pygame canvas.
self._pygamecanvas = sugargame.canvas.PygameCanvas(self)
# Start the game running (self.game.run is called when the
# activity constructor returns).
self.game.canvas = sugargame.canvas.PygameCanvas(self,
main=self.game.run, modules=[pygame.display, pygame.font])

# Note that set_canvas implicitly calls read_file when
# resuming from the Journal.
self.set_canvas(self._pygamecanvas)
self._pygamecanvas.grab_focus()
self.set_canvas(self.game.canvas)
self.game.canvas.grab_focus()

# Start the game running (self.game.run is called when the
# activity constructor returns).
self._pygamecanvas.run_pygame(self.game.run)
def _poll_cb(self):
while Gtk.events_pending():
Gtk.main_iteration()
if self.game.quit_attempt:
Gtk.main_quit()

def build_toolbar(self):
toolbar_box = ToolbarBox()
Expand All @@ -47,16 +56,6 @@ def build_toolbar(self):
toolbar_box.toolbar.insert(activity_button, -1)
activity_button.show()

# Pause/Play button:

stop_play = ToolButton('media-playback-stop')
stop_play.set_tooltip(_("Stop"))
stop_play.set_accelerator(_('<ctrl>space'))
stop_play.connect('clicked', self._stop_play_cb)
stop_play.show()

toolbar_box.toolbar.insert(stop_play, -1)

# Blank space (separator) and Stop button at the end:

separator = Gtk.SeparatorToolItem()
Expand All @@ -68,22 +67,16 @@ def build_toolbar(self):
stop_button = StopButton(self)
toolbar_box.toolbar.insert(stop_button, -1)
stop_button.show()
stop_button.connect('clicked', self._stop_cb)

def _stop_play_cb(self, button):
# Pause or unpause the game.
self.paused = not self.paused
self.game.set_paused(self.paused)

# Update the button to show the next action.
if self.paused:
button.set_icon('media-playback-start')
button.set_tooltip(_("Start"))
else:
button.set_icon('media-playback-stop')
button.set_tooltip(_("Stop"))
def _stop_cb(self, button):
self.game.running = False

def read_file(self, file_path):
self.game.read_file(file_path)

def write_file(self, file_path):
self.game.write_file(file_path)

def get_preview(self):
return self.game.canvas.get_preview()
5 changes: 4 additions & 1 deletion game/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class PyCutGame():
"""docstring for PyCutGame"""
def __init__(self):
def __init__(self, poll_cb=None):
self.dev = True
self.data = None
self.basePath = os.path.dirname(__file__)
Expand All @@ -23,6 +23,7 @@ def __init__(self):
self.quit_attempt = False
self.level = 1
self.total_good_pizza = 0
self.poll_cb = poll_cb

def load_assets(self):
self.game_icon = self.load_image("PyCut_icon.png")
Expand Down Expand Up @@ -68,6 +69,8 @@ def game_loop(self):
self.active_scene = self.starting_scene(self)

while self.active_scene != None:
if self.poll_cb:
self.poll_cb()
pressed_keys = pygame.key.get_pressed()
# Event filtering
filtered_events = []
Expand Down
2 changes: 1 addition & 1 deletion sugargame/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.1'
__version__ = '1.2'
80 changes: 52 additions & 28 deletions sugargame/canvas.py
Original file line number Diff line number Diff line change
@@ -1,62 +1,86 @@
import os
from gi.repository import Gtk
from gi.repository import GObject
from gi.repository import GLib
from sugar3.activity.activity import PREVIEW_SIZE
import pygame
import event

CANVAS = None


class PygameCanvas(Gtk.EventBox):

'''
mainwindow is the activity intself.
'''
def __init__(self, mainwindow, pointer_hint = True):
def __init__(self, activity, pointer_hint=True,
main=None, modules=[pygame]):
GObject.GObject.__init__(self)

global CANVAS
assert CANVAS == None, "Only one PygameCanvas can be created, ever."
CANVAS = self

# Initialize Events translator before widget gets "realized".
self.translator = event.Translator(mainwindow, self)

self._mainwindow = mainwindow
self.translator = event.Translator(activity, self)

self._activity = activity
self._main = main
self._modules = modules

self.set_can_focus(True)

self._socket = Gtk.Socket()
self._socket.connect('realize', self._realize_cb)
self.add(self._socket)

self.show_all()

def run_pygame(self, main_fn):
# Run the main loop after a short delay. The reason for the delay is that the
# Sugar activity is not properly created until after its constructor returns.
# If the Pygame main loop is called from the activity constructor, the
# constructor never returns and the activity freezes.
GObject.idle_add(self._run_pygame_cb, main_fn)
def _realize_cb(self, widget):

def _run_pygame_cb(self, main_fn):
assert pygame.display.get_surface() is None, "PygameCanvas.run_pygame can only be called once."

# Preinitialize Pygame with the X window ID.
assert pygame.display.get_init() == False, "Pygame must not be initialized before calling PygameCanvas.run_pygame."
os.environ['SDL_WINDOWID'] = str(self._socket.get_id())
pygame.init()
os.environ['SDL_WINDOWID'] = str(widget.get_id())
for module in self._modules:
module.init()

# Restore the default cursor.
self._socket.props.window.set_cursor(None)
widget.props.window.set_cursor(None)

# Initialize the Pygame window.
# Confine the Pygame surface to the canvas size
r = self.get_allocation()
pygame.display.set_mode((r.width, r.height), pygame.RESIZABLE)
self._screen = pygame.display.set_mode((r.width, r.height),
pygame.RESIZABLE)

# Hook certain Pygame functions with GTK equivalents.
self.translator.hook_pygame()

# Run the Pygame main loop.
main_fn()
return False
# Call the caller's main loop as an idle source
if self._main:
GLib.idle_add(self._main)

def get_pygame_widget(self):
return self._socket

def get_preview(self):
"""
Return preview of main surface
How to use in activity:
def get_preview(self):
return self.game_canvas.get_preview()
"""

if not hasattr(self, '_screen'):
return None

_tmp_dir = os.path.join(self._activity.get_activity_root(),
'tmp')
_file_path = os.path.join(_tmp_dir, 'preview.png')

width = PREVIEW_SIZE[0]
height = PREVIEW_SIZE[1]
_surface = pygame.transform.scale(self._screen, (width, height))
pygame.image.save(_surface, _file_path)

f = open(_file_path, 'r')
preview = f.read()
f.close()
os.remove(_file_path)

return preview
Loading

0 comments on commit 395170f

Please sign in to comment.