import threading
from transitions.extensions import HierarchicalMachine as Machine
-class KeyMachine(Widget):
+# All drawing operations should happen in the main thread
+# https://github.com/kivy/kivy/wiki/Working-with-Python-threads-inside-a-Kivy-application
+from kivy.clock import mainthread
+
+class KeyMachine():
STATES = [
'initial',
'configuring',
{
'trigger': 'repeat_protection_finished',
'source': 'loaded_protecting_repeat',
- 'dest': 'loaded'
+ 'dest': 'loaded',
+ 'after': 'callback_action_state_changed'
},
]
- state = StringProperty("")
+ def __setattr__(self, name, value):
+ if hasattr(self, 'initialized') and name == 'state':
+ self.key.update_state(value)
+ super().__setattr__(name, value)
def __init__(self, key, **kwargs):
self.key = key
Machine(model=self, states=self.STATES,
transitions=self.TRANSITIONS, initial='initial',
ignore_invalid_triggers=True, queued=True)
- super(KeyMachine, self).__init__(**kwargs)
+
+ self.initialized = True
# Machine states / events
def is_loaded_or_failed(self):
def is_loaded_inactive(self):
return self.is_loaded_no_config() or self.is_loaded_no_actions()
+ @mainthread
def on_enter_configuring(self):
self.destroy_actions()
self.key.unset_description()
self.key.repeat_protection_finished()
# Callbacks
+ @mainthread
def key_loaded_callback(self):
self.key.parent.key_loaded_callback()
+ def callback_action_state_changed(self):
+ if self.state not in ['failed', 'loading', 'loaded']:
+ return
+
+ if any(action.is_failed() for action in self.key.actions):
+ self.to_failed()
+ elif any(action.is_loading() for action in self.key.actions):
+ self.to_loading()
+ else:
+ self.to_loaded()
+ self.key_loaded_callback()
class Key(ButtonBehavior, Widget):
else:
raise AttributeError
- def machine_state_changed(self, instance, machine_state):
- self.machine_state = self.machine.state
-
def __init__(self, **kwargs):
self.actions = []
self.current_action = None
self.machine = KeyMachine(self)
- self.machine.bind(state=self.machine_state_changed)
super(Key, self).__init__(**kwargs)
# Kivy events
+ @mainthread
+ def update_state(self, value):
+ self.machine_state = value
+
def on_key_sym(self, key, key_sym):
if key_sym != "":
self.configure()
def interrupt(self):
self.current_action.interrupt()
- # Callbacks
- def callback_action_ready(self, action, success):
- if not success:
- self.fail()
- elif all(action.is_loaded_or_failed() for action in self.actions):
- self.success()
-
# Setters
def set_description(self, description):
if description[0] is not None: