From d619ec185948384c2f7af35c739d11effe2c0cc1 Mon Sep 17 00:00:00 2001 From: Jannes Magnusson Date: Sat, 18 Oct 2025 17:26:46 +0200 Subject: [PATCH] start implementation of recipe selection --- .vscode/launch.json | 15 +++++ frontend/__main__.py | 3 - frontend/config.py | 8 ++- frontend/views/button_interface.py | 34 +++++++++++ frontend/views/buttons_manager.py | 62 ++++++-------------- frontend/views/main_view.py | 94 +++++++++++++++++++++--------- frontend/views/recipe_selection.py | 67 +++++++++++++++++++++ 7 files changed, 207 insertions(+), 76 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 frontend/views/button_interface.py create mode 100644 frontend/views/recipe_selection.py diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..831c36d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Verwendet IntelliSense zum Ermitteln möglicher Attribute. + // Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen. + // Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "Main", + "type": "debugpy", + "request": "launch", + "module": "frontend" + } + ] +} \ No newline at end of file diff --git a/frontend/__main__.py b/frontend/__main__.py index f7b6784..5ec7b8f 100644 --- a/frontend/__main__.py +++ b/frontend/__main__.py @@ -56,9 +56,6 @@ class WeightApp(tk.Tk): # Checkboxes for multiple view selection self.view_vars = {} for display_type in DISPLAY_TYPES: - if display_type == DISPLAY_TYPES.TIMER: - continue - var = tk.BooleanVar() var.set(True) # Default to all enabled diff --git a/frontend/config.py b/frontend/config.py index d6dea7f..cef51f0 100644 --- a/frontend/config.py +++ b/frontend/config.py @@ -1,4 +1,4 @@ -from enum import Flag +from enum import Flag, Enum # DEFAULT_CALIB = 307333.83 DEFAULT_CALIB = -105030.71880199667 @@ -14,4 +14,8 @@ MOV_AVG_DEFAULTS = { class DISPLAY_TYPES(Flag): NUMBER = 1 CIRCLE = 2 - TIMER = 4 \ No newline at end of file + +class DISPLAY_MODES(Enum): + MAIN = 1 + SETTINGS = 2 + RECIPE_SELECTION = 3 \ No newline at end of file diff --git a/frontend/views/button_interface.py b/frontend/views/button_interface.py new file mode 100644 index 0000000..1e8508d --- /dev/null +++ b/frontend/views/button_interface.py @@ -0,0 +1,34 @@ +from typing import Tuple + +class ButtonInterface: + def has_button(self) -> Tuple[bool, bool, bool, bool]: + return False, False, False, False + + + def left_press(self): + pass + + def left_long_press(self): + pass + + def right_press(self): + pass + + def right_long_press(self): + pass + + def both_long_press(self): + pass + + + def render_left_press(self, draw, x, y): + pass + + def render_left_long_press(self, draw, x, y): + pass + + def render_right_press(self, draw, x, y): + pass + + def render_right_long_press(self, draw, x, y): + pass \ No newline at end of file diff --git a/frontend/views/buttons_manager.py b/frontend/views/buttons_manager.py index b6673ba..e60dbd0 100644 --- a/frontend/views/buttons_manager.py +++ b/frontend/views/buttons_manager.py @@ -6,22 +6,9 @@ from .draw_utils import draw_clock, draw_long_press class ButtonsManager(View): - # left, left long, right, right long, both long - config = { - 'default': { - 'left_button_press': 'start_timer_command', - 'left_button_long_press': 'reset_timer_command', - 'right_button_press': 'tare_command', - 'right_button_long_press': 'select_recipe_command', - 'both_buttons_long_press': 'open_settings_command', - }, - 'select_recipe': {}, - 'settings': {}, - } - def __init__(self, parent, im_size, center, **actions): - self.current_config = self.config['default'] + self.current_view = actions['default'] self.long_press_threshold = 1000 # milliseconds self.left_press_start = None self.right_press_start = None @@ -61,57 +48,44 @@ class ButtonsManager(View): im = self.bkg_im.copy() draw = ImageDraw.Draw(im) + has_buttons = self.current_view.has_button() # Draw left button - if self.current_config.get('left_button_press', None): + if has_buttons[0]: draw.circle((10, 10), 2, fill='black') - if self.current_config['left_button_press'] == 'start_timer_command': - draw_clock(draw, (20, 10), radius=3) - if self.current_config.get('left_button_long_press', None): + self.current_view.render_left_press(draw, 20, 10) + + if has_buttons[1]: y = self.size[1] - 10 draw_long_press(draw, (10, y)) - if self.current_config['left_button_long_press'] == 'reset_timer_command': - draw_clock(draw, (24, y), radius=3) - draw.text((30, y - 5), "R", fill='black') + self.current_view.render_left_long_press(draw, 24, y) # Draw right button - if self.current_config.get('right_button_press', None): + if has_buttons[2]: draw.circle((self.size[0] - 10, 10), 2, fill='black') - if self.current_config['right_button_press'] == 'tare_command': - draw.text((self.size[0] - 20, 4), "T", fill='black') - if self.current_config.get('right_button_long_press', None): + self.current_view.render_right_press(draw, self.size[0] - 20, 4) + + if has_buttons[3]: y = self.size[1] - 10 draw_long_press(draw, (self.size[0] - 10, y)) - if self.current_config['right_button_long_press'] == 'select_recipe_command': - draw.text((self.size[0] - 22, y - 5), "R", fill='black') + self.current_view.render_right_long_press(draw, self.size[0] - 24, y) return im ############ BUTTON ACTIONS ########### def left_button_press(self): - action = self.current_config.get('left_button_press', None) - if action: - self.__getattribute__(action)() + self.current_view.left_press() def left_button_long_press(self): - action = self.current_config.get('left_button_long_press', None) - if action: - self.__getattribute__(action)() + self.current_view.left_long_press() def right_button_press(self): - action = self.current_config.get('right_button_press', None) - if action: - self.__getattribute__(action)() + self.current_view.right_press() def right_button_long_press(self): - action = self.current_config.get('right_button_long_press', None) - if action: - self.__getattribute__(action)() + self.current_view.right_long_press() def both_buttons_long_press(self): - action = self.current_config.get('both_buttons_long_press', None) - if action: - self.__getattribute__(action)() - + self.current_view.both_long_press() ############ BUTTON PRESS HANDLERS ########### def _left_button_press_start(self, event): @@ -162,7 +136,7 @@ class ButtonsManager(View): """Handle both buttons press start""" self.both_press_start = self.ui.after_idle(lambda: None) # Get current time reference # Schedule long press detection - self.both_press_job = self.ui.after(self.long_press_threshold, self._both_long_press_detected) + self.both_press_job = self.ui.after(self.long_press_threshold, self._both_buttons_long_press_detected) def _both_buttons_press_end(self, event): """Handle both buttons press end""" diff --git a/frontend/views/main_view.py b/frontend/views/main_view.py index e241054..727a935 100644 --- a/frontend/views/main_view.py +++ b/frontend/views/main_view.py @@ -1,14 +1,18 @@ import io import tkinter as tk from tkinter import Frame, Canvas, ttk, PhotoImage +from enum import Enum from PIL import ImageChops -from ..config import DISPLAY_TYPES +from ..config import DISPLAY_TYPES, DISPLAY_MODES from . import NumberView, CircleView, TimerView +from .button_interface import ButtonInterface from .buttons_manager import ButtonsManager +from .recipe_selection import RecipeSelection +from .draw_utils import draw_clock -class MainView(tk.Frame): +class MainView(tk.Frame, ButtonInterface): def __init__(self, parent, tare_command=None, calibrate_command=None, @@ -23,24 +27,59 @@ class MainView(tk.Frame): self.actions.pack() self.calibrate_button = ttk.Button(self.actions, text="Calibrate", command=calibrate_command) self.calibrate_button.pack() - - # self.tare_button = ttk.Button(self.actions, text="Tare", command=tare_command) - # self.tare_button.pack() self.im_size = (168, 144) self.center = (168 // 2, 144 // 2) # Create timer view that's always active - self.timer_view = TimerView(self.actions, self.im_size, self.center) - self.canvas = Canvas(self, width=168, height=144, background='white', highlightthickness=1, highlightbackground="black") self.canvas.pack() + + self.timer_view = TimerView(self.actions, self.im_size, self.center) + self.recipe_selection = RecipeSelection(self, self.im_size, self.center) self.buttons = ButtonsManager(self, self.im_size, self.center, - tare_command=self.tare_command, - start_timer_command=self.timer_view.toggle_timer, - reset_timer_command=self.timer_view.reset_timer) + default=self, + select_recipe=self.recipe_selection) + + self.curr_mode = DISPLAY_MODES.MAIN + + def has_button(self): + return True, True, True, True + + def left_press(self): + self.timer_view.toggle_timer() + + def left_long_press(self): + self.timer_view.reset_timer() + + def right_press(self): + self.tare_command() + + def right_long_press(self): + self.enter_recipe_selection() + + + def render_left_press(self, draw, x, y): + draw_clock(draw, (x, y), radius=3) + + def render_left_long_press(self, draw, x, y): + draw_clock(draw, (x, y), radius=3) + draw.text((x + 6, y - 5), "R", fill='black') + + def render_right_press(self, draw, x, y): + draw.text((x, y), "T", fill='black') + + def render_right_long_press(self, draw, x, y): + draw.text((x, y - 5), "R", fill='black') + + + + def enter_recipe_selection(self): + self.curr_mode = DISPLAY_MODES.RECIPE_SELECTION + self.buttons.current_view = self.recipe_selection + self.refresh(0.0) ################ VIEW MANAGEMENT ################ @@ -63,19 +102,27 @@ class MainView(tk.Frame): def refresh(self, weight: float): ims = [] + + if self.curr_mode == DISPLAY_MODES.MAIN: + # Always include timer and button view + if self.timer_view: + timer_im = self.timer_view.update_weight(weight) + button_im = self.buttons.update_weight(weight) + ims.append(timer_im) + ims.append(button_im) + + # Add other selected views + for view in self.views: + im = view.update_weight(weight) + ims.append(im) - # Always include timer and button view - if self.timer_view: - timer_im = self.timer_view.update_weight(weight) + elif self.curr_mode == DISPLAY_MODES.RECIPE_SELECTION: button_im = self.buttons.update_weight(weight) - ims.append(timer_im) ims.append(button_im) - - # Add other selected views - for view in self.views: - im = view.update_weight(weight) - ims.append(im) - + recipe_im = self.recipe_selection.update_weight(weight) + ims.append(recipe_im) + + self.canvas.delete("all") # Combine images by logical_and if ims: @@ -91,10 +138,3 @@ class MainView(tk.Frame): # Load into PhotoImage and display on canvas self.photo = PhotoImage(data=buffer.getvalue()) self.canvas.create_image(0, 0, anchor="nw", image=self.photo) - - - - - - ########### BUTTON PRESS HANDLING ########### - diff --git a/frontend/views/recipe_selection.py b/frontend/views/recipe_selection.py new file mode 100644 index 0000000..bf041d0 --- /dev/null +++ b/frontend/views/recipe_selection.py @@ -0,0 +1,67 @@ +from typing import Tuple + +from .base import View +from .button_interface import ButtonInterface + +from PIL import ImageDraw, Image + +class RecipeSelection(View, ButtonInterface): + + recipes = [ + "Recipe 1", + "Recipe 2", + ] + + def __init__(self, parent, im_size, center): + self.selected_index = 0 + super().__init__(parent, im_size, center) + + def _get_visual_recipes(self): + recipes = self.recipes + ['+'] + if len(self.recipes) < 5: + return recipes, 0 + start = max(0, self.selected_index - 2) + end = min(len(recipes), start + 5) + + recipes = recipes[start:end] + if len(recipes) < 5: + recipes += ['+'] + + return recipes, start + + def update_weight(self, weight: float) -> Image.Image: + im = self.bkg_im.copy() + draw = ImageDraw.Draw(im) + + recipes, start = self._get_visual_recipes() + for idx, recipe in enumerate(recipes): + if idx + start == self.selected_index: + draw.rectangle((35, 8 + idx * 20, 130, 25 + idx * 20), fill='black') + draw.text((40, 10 + idx * 20), recipe, fill='white') + else: + draw.text((40, 10 + idx * 20), recipe, fill='black') + + + return im + + def left_press(self): + self.selected_index = (self.selected_index - 1) % (len(self.recipes) + 1) + + def right_press(self): + self.selected_index = (self.selected_index + 1) % (len(self.recipes) + 1) + + + def has_button(self) -> Tuple[bool, bool, bool, bool]: + return True, True, True, True + + def render_left_press(self, draw, x, y): + draw.regular_polygon((x, y+2, 5), 3, fill='black') + + def render_left_long_press(self, draw, x, y): + pass + + def render_right_press(self, draw, x, y): + draw.regular_polygon((x, y+4, 5), 3, fill='black', rotation=180) + + def render_right_long_press(self, draw, x, y): + pass