X-Git-Url: https://git.immae.eu/?p=perso%2FImmae%2FProjets%2FPython%2FMusicSampler.git;a=blobdiff_plain;f=helpers%2Fkey.py;h=4ec08d19aa4c98185dcf9e12d2abb8a751c57d2c;hp=612758f5e4f01f7e489ce7b01f64ca5cec573652;hb=ab47d2a1269c20d70f42942c4295c056544491f4;hpb=fba0caf0417cbb4e5b370e16e4b0c855c74094c1 diff --git a/helpers/key.py b/helpers/key.py index 612758f..4ec08d1 100644 --- a/helpers/key.py +++ b/helpers/key.py @@ -1,156 +1,210 @@ -from .rounded_rect import * -from .action import * -from .font import font +from kivy.uix.widget import Widget +from kivy.properties import AliasProperty, BooleanProperty, \ + ListProperty, StringProperty +from kivy.uix.behaviors import ButtonBehavior + +from .action import Action +from . import debug_print import time -import sys -import pygame - -class Key: - default_outer_color = (120, 120, 120) - lighter_outer_color = (200, 200, 200) - default_inner_color = (255, 255, 255) - mapped_inner_color = ( 0, 255, 0) - mapped_unready_inner_color = ( 0, 255, 0, 100) - - def __init__(self, mapping, draw_lock, key_name, key_sym, top, left, width = 48, height = 48, disabled = False): - self.draw_lock = draw_lock - self.mapping = mapping - self.key_name = key_name - self.key_sym = key_sym - - self.top = top - self.left = left - self.width = width - self.height = height - - self.bottom = self.top + self.height - self.right = self.left + self.width - - self.rect = (self.left, self.top, self.right, self.bottom) - self.position = (self.left, self.top) - self.disabled = disabled - - if disabled: - self.outer_color = self.lighter_outer_color - self.linewidth = 1 +from transitions.extensions import HierarchicalMachine as Machine + +class Key(ButtonBehavior, Widget): + STATES = [ + 'initial', + 'configuring', + 'configured', + 'loading', + 'failed', + { + 'name': 'loaded', + 'children': ['no_config', 'no_actions', 'running'] + } + ] + + TRANSITIONS = [ + { + 'trigger': 'configure', + 'source': 'initial', + 'dest': 'configuring' + }, + { + 'trigger': 'fail', + 'source': 'configuring', + 'dest': 'failed', + 'after': 'key_loaded_callback' + }, + { + 'trigger': 'success', + 'source': 'configuring', + 'dest': 'configured', + 'after': 'load' + }, + { + 'trigger': 'no_config', + 'source': 'configuring', + 'dest': 'loaded_no_config', + 'after': 'key_loaded_callback' + }, + { + 'trigger': 'load', + 'source': 'configured', + 'dest': 'loading' + }, + { + 'trigger': 'fail', + 'source': 'loading', + 'dest': 'failed', + 'after': 'key_loaded_callback' + }, + { + 'trigger': 'success', + 'source': 'loading', + 'dest': 'loaded', + 'after': 'key_loaded_callback' + }, + { + 'trigger': 'no_actions', + 'source': 'loading', + 'dest': 'loaded_no_actions', + 'after': 'key_loaded_callback' + }, + { + 'trigger': 'reload', + 'source': ['loaded','failed'], + 'dest': 'configuring', + 'after': 'key_loaded_callback' + }, + { + 'trigger': 'run', + 'source': 'loaded', + 'dest': 'loaded_running', + 'after': 'finish', + # if a child, like loaded_no_actions, has no transitions, then it is + # bubbled to the parent, and we don't want that. + 'conditions': ['is_loaded'] + }, + { + 'trigger': 'finish', + 'source': 'loaded_running', + 'dest': 'loaded' + } + ] + + key_sym = StringProperty(None) + custom_color = ListProperty([0, 1, 0]) + description_title = StringProperty("") + description = ListProperty([]) + state = StringProperty("") + + def get_alias_color(self): + if self.is_loaded_inactive(): + return [1, 1, 1, 1] + elif self.is_loaded(allow_substates=True): + return [*self.custom_color, 1] + elif self.is_failed(): + return [0, 0, 0, 1] else: - self.outer_color = self.default_outer_color - self.linewidth = 3 + return [*self.custom_color, 100/255] + def set_alias_color(self): + pass - self.inner_color = self.default_inner_color - self.actions = [] - self.description = [] - self.custom_color = None - self.custom_unready_color = None - - def square(self, all_actions_ready): - if self.has_actions(): - if all_actions_ready: - self.inner_color = self.custom_color or self.mapped_inner_color - else: - self.inner_color = self.custom_unready_color or self.mapped_unready_inner_color + color = AliasProperty(get_alias_color, set_alias_color, + bind=['state', 'custom_color']) - return RoundedRect((0, 0, self.width, self.height), - self.outer_color, self.inner_color, self.linewidth) + def __init__(self, **kwargs): + self.actions = [] + Machine(model=self, states=self.STATES, + transitions=self.TRANSITIONS, initial='initial', + ignore_invalid_triggers=True, queued=True) + super(Key, self).__init__(**kwargs) + + # Kivy events + def on_key_sym(self, key, key_sym): + if key_sym != "": + self.configure() + + def on_press(self): + self.list_actions() + + # Machine states / events + def is_loaded_or_failed(self): + return self.is_loaded(allow_substates=True) or self.is_failed() + + def is_loaded_inactive(self): + return self.is_loaded_no_config() or self.is_loaded_no_actions() + + def on_enter_configuring(self): + if self.key_sym in self.parent.key_config: + self.config = self.parent.key_config[self.key_sym] + + self.actions = [] + for key_action in self.config['actions']: + self.add_action(key_action[0], **key_action[1]) + + if 'description' in self.config['properties']: + self.set_description(self.config['properties']['description']) + if 'color' in self.config['properties']: + self.set_color(self.config['properties']['color']) + self.success() + else: + self.no_config() - def collidepoint(self, position): - return self.surface.get_rect().collidepoint( - position[0] - self.position[0], - position[1] - self.position[1] - ) + def on_enter_loading(self): + if len(self.actions) > 0: + for action in self.actions: + action.load() + else: + self.no_actions() + def on_enter_loaded_running(self, modifiers): + self.parent.parent.ids['KeyList'].append(self.key_sym) + debug_print("running actions for {}".format(self.key_sym)) + start_time = time.time() + self.parent.start_running(self, start_time) + action_number = 0 + for self.current_action in self.actions: + if self.parent.keep_running(self, start_time): + self.list_actions(action_number=action_number + 0.5) + self.current_action.run() + action_number += 1 + self.list_actions(action_number=action_number) + + self.parent.finished_running(self, start_time) + + # This one cannot be in the Machine state since it would be queued to run + # *after* the loop is ended... + def interrupt(self): + self.current_action.interrupt() + + # Callbacks + def key_loaded_callback(self): + self.parent.key_loaded_callback() + + 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): - for desc in description: + if description[0] is not None: + self.description_title = str(description[0]) + self.description = [] + for desc in description[1 :]: if desc is None: self.description.append("") else: - self.description.append(str(desc)) + self.description.append(str(desc).replace(" ", " ")) def set_color(self, color): - self.custom_color = tuple(color) - color.append(100) - self.custom_unready_color = tuple(color) - - def draw(self, background_surface): - self.draw_lock.acquire() - all_actions_ready = self.all_actions_ready() - - self.surface = self.square(all_actions_ready).surface() - - police = font(14) - text_police = font(10) - - police.set_bold(True) - text = police.render(self.key_sym, True, (0,0,0)) - self.surface.blit(text, (5,5)) - - is_first_line = True - offset = 11 + text_police.get_linesize() - 4 - first_line_offset = 18 - for description in self.description: - text = text_police.render(description, True, (0,0,0)) - if is_first_line: - self.surface.blit(text, (first_line_offset, 9)) - is_first_line = False - else: - self.surface.blit(text, (3, offset)) - offset += text_police.get_linesize() - 4 - - background_surface.blit(self.surface, self.position) - self.draw_lock.release() - - return not all_actions_ready - - def poll_redraw(self, background): - while True: - time.sleep(1) - if self.all_actions_ready(): - self.draw(background) - self.mapping.blit() - break - - def has_actions(self): - return len(self.actions) > 0 - - def all_actions_ready(self): - return all(action.ready() for action in self.actions) + color = [x / 255 for x in color] + self.custom_color = color + # Actions handling def add_action(self, action_name, **arguments): self.actions.append(Action(action_name, self, **arguments)) - def do_actions(self): - print("running actions for {}".format(self.key_sym)) - start_time = time.time() - self.mapping.start_running(self, start_time) - for action in self.actions: - if self.mapping.keep_running(self, start_time): - action.run() - - self.mapping.finished_running(self, start_time) - - def list_actions(self, screen): - action_descriptions = [action.description() for action in self.actions] - #print("actions linked to key {}:".format(self.key_sym)) - #print("\t" + "\n\t".join(action_descriptions)) - self.draw_lock.acquire() - surface = pygame.Surface((690, 250)).convert() - surface.fill((250, 250, 250)) - police = font(14) - - offset = 0 - police.set_bold(True) - text = police.render("actions linked to key {}:".format(self.key_sym), True, (0,0,0)) - surface.blit(text, (0, offset)) - offset += police.get_linesize() - - police.set_bold(False) - for description in action_descriptions: - text = police.render(description, True, (0,0,0)) - surface.blit(text, (0, offset)) - offset += police.get_linesize() - - screen.blit(surface, (5, 308)) - pygame.display.flip() - self.draw_lock.release() - + def list_actions(self, action_number=0): + self.parent.parent.ids['ActionList'].update_list(self, action_number)