diff options
Diffstat (limited to 'helpers/key.py')
-rw-r--r-- | helpers/key.py | 205 |
1 files changed, 148 insertions, 57 deletions
diff --git a/helpers/key.py b/helpers/key.py index 34c5140..bf46eeb 100644 --- a/helpers/key.py +++ b/helpers/key.py | |||
@@ -1,59 +1,183 @@ | |||
1 | from kivy.uix.widget import Widget | 1 | from kivy.uix.widget import Widget |
2 | from kivy.properties import AliasProperty, BooleanProperty, \ | 2 | from kivy.properties import AliasProperty, BooleanProperty, \ |
3 | ListProperty, StringProperty | 3 | ListProperty, StringProperty |
4 | from kivy.clock import Clock | ||
5 | from kivy.uix.behaviors import ButtonBehavior | 4 | from kivy.uix.behaviors import ButtonBehavior |
6 | 5 | ||
7 | from .action import * | 6 | from .action import Action |
8 | from . import debug_print | 7 | from . import debug_print |
9 | import time | 8 | import time |
9 | from transitions.extensions import HierarchicalMachine as Machine | ||
10 | 10 | ||
11 | class Key(ButtonBehavior, Widget): | 11 | class Key(ButtonBehavior, Widget): |
12 | STATES = [ | ||
13 | 'initial', | ||
14 | 'configuring', | ||
15 | 'configured', | ||
16 | 'loading', | ||
17 | 'failed', | ||
18 | { | ||
19 | 'name': 'loaded', | ||
20 | 'children': ['no_config', 'no_actions', 'running'] | ||
21 | } | ||
22 | ] | ||
23 | |||
24 | TRANSITIONS = [ | ||
25 | { | ||
26 | 'trigger': 'configure', | ||
27 | 'source': 'initial', | ||
28 | 'dest': 'configuring' | ||
29 | }, | ||
30 | { | ||
31 | 'trigger': 'fail', | ||
32 | 'source': 'configuring', | ||
33 | 'dest': 'failed' | ||
34 | }, | ||
35 | { | ||
36 | 'trigger': 'success', | ||
37 | 'source': 'configuring', | ||
38 | 'dest': 'configured', | ||
39 | 'after': 'load' | ||
40 | }, | ||
41 | { | ||
42 | 'trigger': 'no_config', | ||
43 | 'source': 'configuring', | ||
44 | 'dest': 'loaded_no_config', | ||
45 | }, | ||
46 | { | ||
47 | 'trigger': 'load', | ||
48 | 'source': 'configured', | ||
49 | 'dest': 'loading' | ||
50 | }, | ||
51 | { | ||
52 | 'trigger': 'fail', | ||
53 | 'source': 'loading', | ||
54 | 'dest': 'failed' | ||
55 | }, | ||
56 | { | ||
57 | 'trigger': 'success', | ||
58 | 'source': 'loading', | ||
59 | 'dest': 'loaded' | ||
60 | }, | ||
61 | { | ||
62 | 'trigger': 'no_actions', | ||
63 | 'source': 'loading', | ||
64 | 'dest': 'loaded_no_actions', | ||
65 | }, | ||
66 | { | ||
67 | 'trigger': 'reload', | ||
68 | 'source': 'loaded', | ||
69 | 'dest': 'configuring' | ||
70 | }, | ||
71 | { | ||
72 | 'trigger': 'run', | ||
73 | 'source': 'loaded', | ||
74 | 'dest': 'loaded_running', | ||
75 | 'after': 'finish', | ||
76 | # if a child, like loaded_no_actions, has no transitions, then it is | ||
77 | # bubbled to the parent, and we don't want that. | ||
78 | 'conditions': ['is_loaded'] | ||
79 | }, | ||
80 | { | ||
81 | 'trigger': 'finish', | ||
82 | 'source': 'loaded_running', | ||
83 | 'dest': 'loaded' | ||
84 | } | ||
85 | ] | ||
86 | |||
12 | key_sym = StringProperty(None) | 87 | key_sym = StringProperty(None) |
13 | custom_color = ListProperty([0, 1, 0, 1]) | 88 | custom_color = ListProperty([0, 1, 0]) |
14 | custom_unready_color = ListProperty([0, 1, 0, 100/255]) | ||
15 | description_title = StringProperty("") | 89 | description_title = StringProperty("") |
16 | description = ListProperty([]) | 90 | description = ListProperty([]) |
17 | is_key_ready = BooleanProperty(True) | 91 | state = StringProperty("") |
18 | 92 | ||
19 | def get_color(self): | 93 | def get_alias_color(self): |
20 | if not self.has_actions: | 94 | if self.is_loaded_inactive(): |
21 | return [1, 1, 1, 1] | 95 | return [1, 1, 1, 1] |
22 | elif self.all_actions_ready: | 96 | elif self.is_loaded(allow_substates=True): |
23 | return self.custom_color | 97 | return [*self.custom_color, 1] |
98 | elif self.is_failed(): | ||
99 | return [0, 0, 0, 1] | ||
24 | else: | 100 | else: |
25 | return self.custom_unready_color | 101 | return [*self.custom_color, 100/255] |
26 | def set_color(self): | 102 | def set_alias_color(self): |
27 | pass | 103 | pass |
28 | 104 | ||
29 | color = AliasProperty(get_color, set_color, bind=['is_key_ready']) | 105 | color = AliasProperty(get_alias_color, set_alias_color, |
106 | bind=['state', 'custom_color']) | ||
30 | 107 | ||
31 | def __init__(self, **kwargs): | 108 | def __init__(self, **kwargs): |
32 | super(Key, self).__init__(**kwargs) | ||
33 | self.actions = [] | 109 | self.actions = [] |
110 | Machine(model=self, states=self.STATES, | ||
111 | transitions=self.TRANSITIONS, initial='initial', | ||
112 | ignore_invalid_triggers=True, queued=True) | ||
113 | super(Key, self).__init__(**kwargs) | ||
34 | 114 | ||
115 | # Kivy events | ||
35 | def on_key_sym(self, key, key_sym): | 116 | def on_key_sym(self, key, key_sym): |
36 | if key_sym in self.parent.key_config: | 117 | if key_sym != "": |
37 | self.is_key_ready = False | 118 | self.configure() |
119 | |||
120 | def on_press(self): | ||
121 | self.list_actions() | ||
38 | 122 | ||
39 | self.config = self.parent.key_config[key_sym] | 123 | # Machine states / events |
124 | def is_loaded_or_failed(self): | ||
125 | return self.is_loaded(allow_substates=True) or self.is_failed() | ||
126 | |||
127 | def is_loaded_inactive(self): | ||
128 | return self.is_loaded_no_config() or self.is_loaded_no_actions() | ||
129 | |||
130 | def on_enter_configuring(self): | ||
131 | if self.key_sym in self.parent.key_config: | ||
132 | self.config = self.parent.key_config[self.key_sym] | ||
40 | 133 | ||
41 | self.actions = [] | 134 | self.actions = [] |
42 | for key_action in self.config['actions']: | 135 | for key_action in self.config['actions']: |
43 | self.add_action(key_action[0], **key_action[1]) | 136 | self.add_action(key_action[0], **key_action[1]) |
44 | 137 | ||
45 | if 'description' in self.config['properties']: | 138 | if 'description' in self.config['properties']: |
46 | key.set_description(self.config['properties']['description']) | 139 | self.set_description(self.config['properties']['description']) |
47 | if 'color' in self.config['properties']: | 140 | if 'color' in self.config['properties']: |
48 | key.set_color(self.config['properties']['color']) | 141 | self.set_color(self.config['properties']['color']) |
142 | self.success() | ||
143 | else: | ||
144 | self.no_config() | ||
49 | 145 | ||
50 | Clock.schedule_interval(self.check_all_active, 1) | 146 | def on_enter_loading(self): |
147 | if len(self.actions) > 0: | ||
148 | for action in self.actions: | ||
149 | action.load() | ||
150 | else: | ||
151 | self.no_actions() | ||
152 | |||
153 | def on_enter_loaded_running(self): | ||
154 | self.parent.parent.ids['KeyList'].append(self.key_sym) | ||
155 | debug_print("running actions for {}".format(self.key_sym)) | ||
156 | start_time = time.time() | ||
157 | self.parent.start_running(self, start_time) | ||
158 | action_number = 0 | ||
159 | for self.current_action in self.actions: | ||
160 | if self.parent.keep_running(self, start_time): | ||
161 | self.list_actions(action_number=action_number + 0.5) | ||
162 | self.current_action.run() | ||
163 | action_number += 1 | ||
164 | self.list_actions(action_number=action_number) | ||
51 | 165 | ||
52 | def check_all_active(self, dt): | 166 | self.parent.finished_running(self, start_time) |
53 | if self.all_actions_ready: | ||
54 | self.is_key_ready = True | ||
55 | return False | ||
56 | 167 | ||
168 | # This one cannot be in the Machine state since it would be queued to run | ||
169 | # *after* the loop is ended... | ||
170 | def interrupt(self): | ||
171 | self.current_action.interrupt() | ||
172 | |||
173 | # Callbacks | ||
174 | def callback_action_ready(self, action, success): | ||
175 | if not success: | ||
176 | self.fail() | ||
177 | elif all(action.is_loaded_or_failed() for action in self.actions): | ||
178 | self.success() | ||
179 | |||
180 | # Setters | ||
57 | def set_description(self, description): | 181 | def set_description(self, description): |
58 | if description[0] is not None: | 182 | if description[0] is not None: |
59 | self.description_title = str(description[0]) | 183 | self.description_title = str(description[0]) |
@@ -65,45 +189,12 @@ class Key(ButtonBehavior, Widget): | |||
65 | 189 | ||
66 | def set_color(self, color): | 190 | def set_color(self, color): |
67 | color = [x / 255 for x in color] | 191 | color = [x / 255 for x in color] |
68 | color.append(1) | ||
69 | self.custom_color = color | 192 | self.custom_color = color |
70 | color[3] = 100 / 255 | ||
71 | self.custom_unready_color = tuple(color) | ||
72 | |||
73 | @property | ||
74 | def has_actions(self): | ||
75 | return len(self.actions) > 0 | ||
76 | |||
77 | @property | ||
78 | def all_actions_ready(self): | ||
79 | return all(action.ready() for action in self.actions) | ||
80 | 193 | ||
194 | # Actions handling | ||
81 | def add_action(self, action_name, **arguments): | 195 | def add_action(self, action_name, **arguments): |
82 | self.actions.append(Action(action_name, self, **arguments)) | 196 | self.actions.append(Action(action_name, self, **arguments)) |
83 | 197 | ||
84 | def interrupt_action(self): | ||
85 | self.current_action.interrupt() | ||
86 | |||
87 | def do_actions(self): | ||
88 | if not self.enabled: | ||
89 | return None | ||
90 | |||
91 | self.parent.parent.ids['KeyList'].append(self.key_sym) | ||
92 | debug_print("running actions for {}".format(self.key_sym)) | ||
93 | start_time = time.time() | ||
94 | self.parent.start_running(self, start_time) | ||
95 | action_number = 0 | ||
96 | for self.current_action in self.actions: | ||
97 | if self.parent.keep_running(self, start_time): | ||
98 | self.list_actions(action_number=action_number + 0.5) | ||
99 | self.current_action.run() | ||
100 | action_number += 1 | ||
101 | self.list_actions(action_number=action_number) | ||
102 | |||
103 | self.parent.finished_running(self, start_time) | ||
104 | |||
105 | def list_actions(self, action_number=0): | 198 | def list_actions(self, action_number=0): |
106 | self.parent.parent.ids['ActionList'].update_list(self, action_number) | 199 | self.parent.parent.ids['ActionList'].update_list(self, action_number) |
107 | 200 | ||
108 | def on_press(self): | ||
109 | self.list_actions() | ||