RASCSI/python/ctrlboard/src/menu/menu_controller.py

147 lines
6.4 KiB
Python

"""Module providing the menu controller."""
import time
import importlib
from typing import Dict, Optional
from PIL import Image
from menu.menu import Menu
from menu.menu_builder import MenuBuilder
from menu.menu_renderer_config import MenuRendererConfig
from menu.menu_renderer_luma_oled import MenuRendererLumaOled
from menu.transition import Transition
class MenuController:
"""Class providing the menu controller. The menu controller is a central class
that controls the menu and its associated rendering to a screen."""
def __init__(self, menu_builder: MenuBuilder, menu_renderer=None, menu_renderer_config=None):
self._menus: Dict[str, Menu] = {}
self._active_menu: Optional[Menu] = None
self._menu_renderer = menu_renderer
self._menu_builder: MenuBuilder = menu_builder
self._menu_renderer_config: Optional[MenuRendererConfig]
if menu_renderer_config is None:
self._menu_renderer_config = MenuRendererConfig()
else:
self._menu_renderer_config = menu_renderer_config
if menu_renderer is None: # default to LumaOled renderer if nothing else is stated
self._menu_renderer = MenuRendererLumaOled(self._menu_renderer_config)
else:
self._menu_renderer = menu_renderer
self._transition: Optional[Transition] = None
if self._menu_renderer_config.transition is None:
self._transition = None
return
try:
module = importlib.import_module("menu.transition")
try:
transition_class = getattr(module, self._menu_renderer_config.transition)
if transition_class is not None:
self._transition = transition_class(self._menu_renderer.disp)
except AttributeError:
pass
except ImportError:
print("transition module does not exist. Falling back to default.")
self._transition = None
def add(self, name: str, context_object=None):
"""Adds a menu to the menu collection internal to the controller by name.
The associated class menu builder builds the menu by name."""
self._menus[name] = self._menu_builder.build(name)
if context_object is not None:
self._menus[name].context_object = context_object
def set_active_menu(self, name: str, display_on_device=True):
"""Activates a menu from the controller internal menu collection by name."""
self._active_menu = self._menus[name]
self._menu_renderer.set_menu(self._active_menu)
self._menu_renderer.render(display_on_device)
def refresh(self, name: str, context_object=None):
"""Refreshes a menu by name (by calling the menu builder again to rebuild the menu)."""
item_selection = None
if self._menus.get(name) is not None:
item_selection = self._menus[name].item_selection
self._menus[name] = self._menu_builder.build(name, context_object)
if context_object is not None:
self._menus[name].context_object = context_object
if item_selection is not None:
self._menus[name].item_selection = item_selection
def get_menu(self, name: str):
"""Returns the controller internal menu collection"""
return self._menus[name]
def get_active_menu(self):
"""Returns the currently activated menu"""
return self._active_menu
def get_menu_renderer_config(self):
"""Returns the menu renderer configuration"""
return self._menu_renderer_config
def get_menu_renderer(self):
"""Returns the menu renderer for this menu controller"""
return self._menu_renderer
def segue(self, name, context_object=None, transition_attributes=None):
"""Transitions one menu into the other with all associated actions such
as transition animations."""
self.get_active_menu().context_object = None
self.refresh(name, context_object)
if self._transition is not None and transition_attributes is not None:
source_image = self._menu_renderer.image.copy()
transition_menu = self.get_menu(name)
self._menu_renderer.set_menu(transition_menu)
target_image = self._menu_renderer.render(display_on_device=False)
transition_attributes["transition_speed"] = self._menu_renderer_config.transition_speed
self._transition.perform(source_image, target_image, transition_attributes)
self.set_active_menu(name)
def show_message(self, message: str, sleep=1):
"""Displays a blocking message on the screen that stays for sleep seconds"""
self.get_menu_renderer().message = message
self.get_menu_renderer().render()
time.sleep(sleep)
self.get_menu_renderer().message = ""
def show_timed_message(self, message: str):
"""Shows a message on the screen. The timed message is non-blocking for the main loop and
simply redraws the message on screen if necessary."""
self.get_menu_renderer().message = message
self.get_menu_renderer().render()
def show_mini_message(self, message: str, sleep=1):
"""The mini message is a message on the screen that only coveres the center portion
of the screen, i.e., the remaining content is still visible on the screen while the mini
message is shown in the middle. This version is blocking and stays for sleep seconds."""
self.get_menu_renderer().mini_message = message
self.get_menu_renderer().render()
time.sleep(sleep)
self.get_menu_renderer().mini_message = ""
def show_timed_mini_message(self, message: str):
"""The mini message is a message on the screen that only coveres the center portion of
the screen, i.e., the remaining content is still visible on the screen while the mini
message is shown in the middle. This version is non-blocking for the main loop and
simply redraws the mini message on screen if necessary."""
self.get_menu_renderer().mini_message = message
self.get_menu_renderer().render()
def show_splash_screen(self, filename, sleep=2):
"""Shows a splash screen for a given number of seconds."""
image = Image.open(filename).convert("1")
self.get_menu_renderer().update_display_image(image)
time.sleep(sleep)
def update(self):
"""Updates the menu / draws the screen if necessary."""
self._menu_renderer.update()