start implementing edit_recipe

This commit is contained in:
Jannes Magnusson
2025-10-18 23:28:55 +02:00
parent 06df2e0e9b
commit eb32f089ad
12 changed files with 241 additions and 70 deletions

View File

@@ -14,8 +14,10 @@ MOV_AVG_DEFAULTS = {
class DISPLAY_TYPES(Flag): class DISPLAY_TYPES(Flag):
NUMBER = 1 NUMBER = 1
CIRCLE = 2 CIRCLE = 2
class DISPLAY_MODES(Enum): class DISPLAY_MODES(Enum):
MAIN = 1 MAIN = 1
SETTINGS = 2 SETTINGS = 2
RECIPE_SELECTION = 3 RECIPE_SELECTION = 3
EDIT_RECIPE = 4
DO_RECIPE = 5

View File

@@ -2,13 +2,13 @@ from tkinter import Frame, ttk
from PIL import ImageDraw from PIL import ImageDraw
from .base import View from .base import View
from .draw_utils import draw_clock, draw_long_press from .button_interface import ButtonInterface
from .draw_utils import draw_long_press
class ButtonsManager(View): class ButtonsManager(View):
def __init__(self, parent, im_size, center, def __init__(self, parent, im_size, center, curr_view):
**actions): self.current_view: ButtonInterface = curr_view
self.current_view = actions['default']
self.long_press_threshold = 1000 # milliseconds self.long_press_threshold = 1000 # milliseconds
self.left_press_start = None self.left_press_start = None
self.right_press_start = None self.right_press_start = None
@@ -17,9 +17,6 @@ class ButtonsManager(View):
self.right_press_job = None self.right_press_job = None
self.both_press_job = None self.both_press_job = None
for key, action in actions.items():
setattr(self, key, action)
super().__init__(parent, im_size, center) super().__init__(parent, im_size, center)
def init_ui(self, parent): def init_ui(self, parent):

34
frontend/views/confirm.py Normal file
View File

@@ -0,0 +1,34 @@
from .base import View
from ..button_interface import ButtonInterface
class ConfirmView(View, ButtonInterface):
def __init__(self, parent, im_size, center,
message: str,
confirm_command,
cancel_command):
self.message = message
self.confirm_command = confirm_command
self.cancel_command = cancel_command
super().__init__(parent, im_size, center)
def update_weight(self, weight: float):
from PIL import ImageDraw
im = self.bkg_im.copy()
draw = ImageDraw.Draw(im)
draw.text((20, 60), self.message, fill='black')
return im
def render_left_long_press(self, draw, x, y):
draw.text((x, y), 'Confirm', fill='black')
def render_right_press(self, draw, x, y):
draw.text((x, y), 'Cancel', fill='black')
def left_press(self):
self.confirm_command()
def right_press(self):
self.cancel_command()

View File

@@ -1,9 +1,9 @@
def draw_clock(draw, position, radius=16, width=1): def draw_clock(draw, position, radius=16, width=1, color='black'):
"""Draw a simple clock icon at the given position""" """Draw a simple clock icon at the given position"""
x, y = position x, y = position
draw.circle((x, y), radius, outline='black', width=width) draw.circle((x, y), radius, outline=color, width=width)
draw.line((x, y - radius + 2, x, y), fill='black', width=width) # Hour hand draw.line((x, y - radius + 2, x, y), fill=color, width=width) # Hour hand
draw.line((x, y, x + radius * 3 / 4, y), fill='black', width=width) # Minute hand draw.line((x, y, x + radius * 3 / 4, y), fill=color, width=width) # Minute hand
def draw_long_press(draw, position): def draw_long_press(draw, position):
"""Draw a long press button icon at the given position""" """Draw a long press button icon at the given position"""

View File

@@ -1,16 +1,17 @@
import io import io
import tkinter as tk import tkinter as tk
from tkinter import Frame, Canvas, ttk, PhotoImage from tkinter import Frame, Canvas, ttk, PhotoImage
from enum import Enum
from PIL import ImageChops from PIL import ImageChops
from ..config import DISPLAY_TYPES, DISPLAY_MODES from ..config import DISPLAY_TYPES, DISPLAY_MODES
from .draw_utils import draw_clock
from . import NumberView, CircleView, TimerView from . import NumberView, CircleView, TimerView
from .button_interface import ButtonInterface from .button_interface import ButtonInterface
from .buttons_manager import ButtonsManager from .buttons_manager import ButtonsManager
from .recipes.recipe_selection import RecipeSelection
from .draw_utils import draw_clock from .recipes import RecipeSelection, RecipeManager, EditRecipe, Recipe
class MainView(tk.Frame, ButtonInterface): class MainView(tk.Frame, ButtonInterface):
def __init__(self, parent, def __init__(self, parent,
@@ -38,10 +39,9 @@ class MainView(tk.Frame, ButtonInterface):
self.timer_view = TimerView(self.actions, self.im_size, self.center) self.timer_view = TimerView(self.actions, self.im_size, self.center)
self.recipe_selection = RecipeSelection(self, self.im_size, self.center, deactivate_command=self.enter_main_mode)
self.buttons = ButtonsManager(self, self.im_size, self.center, self.buttons = ButtonsManager(self, self.im_size, self.center,
default=self, curr_view=self)
select_recipe=self.recipe_selection) self.recipes_manager = RecipeManager()
self.curr_mode = DISPLAY_MODES.MAIN self.curr_mode = DISPLAY_MODES.MAIN
@@ -82,10 +82,22 @@ class MainView(tk.Frame, ButtonInterface):
def enter_recipe_selection(self): def enter_recipe_selection(self):
self.curr_mode = DISPLAY_MODES.RECIPE_SELECTION self.curr_mode = DISPLAY_MODES.RECIPE_SELECTION
self.buttons.current_view = self.recipe_selection self.buttons.current_view = RecipeSelection(self,
self.im_size, self.center,
recipe_manager=self.recipes_manager,
edit_recipe_command=self.enter_edit_recipe,
deactivate_command=self.enter_main_mode)
self.refresh(0.0) self.refresh(0.0)
def enter_edit_recipe(self, recipe: Recipe = None):
self.curr_mode = DISPLAY_MODES.EDIT_RECIPE
self.buttons.current_view = EditRecipe(self,
self.im_size, self.center,
recipe=recipe,
recipe_manager=self.recipes_manager,
deactivate_command=self.enter_recipe_selection)
self.refresh(0.0)
################ VIEW MANAGEMENT ################ ################ VIEW MANAGEMENT ################
def update_views(self, selected_types: DISPLAY_TYPES): def update_views(self, selected_types: DISPLAY_TYPES):
@@ -120,10 +132,10 @@ class MainView(tk.Frame, ButtonInterface):
im = view.update_weight(weight) im = view.update_weight(weight)
ims.append(im) ims.append(im)
elif self.curr_mode == DISPLAY_MODES.RECIPE_SELECTION: else:
button_im = self.buttons.update_weight(weight) button_im = self.buttons.update_weight(weight)
ims.append(button_im) ims.append(button_im)
recipe_im = self.recipe_selection.update_weight(weight) recipe_im = self.buttons.current_view.update_weight(weight)
ims.append(recipe_im) ims.append(recipe_im)

View File

@@ -0,0 +1,4 @@
from .recipe_selection import RecipeSelection
from .recipe_manager import RecipeManager
from .edit_recipe import EditRecipe
from .recipe import Recipe

View File

@@ -1,39 +0,0 @@
from typing import Tuple
from PIL import ImageDraw, Image
from ..base import View
from ..button_interface import ButtonInterface
class AddRecipe(View, ButtonInterface):
def __init__(self, parent, im_size, center,
save_command=None, cancel_command=None):
self.save_command = save_command
self.cancel_command = cancel_command
super().__init__(parent, im_size, center)
def update_weight(self, weight: float) -> Image.Image:
im = self.bkg_im.copy()
draw = ImageDraw.Draw(im)
draw.text((40, 30), "Add Recipe", fill='black')
draw.text((40, 60), "Save", fill='black')
draw.text((40, 90), "Cancel", fill='black')
return im
def left_press(self):
if self.save_command:
self.save_command()
def right_press(self):
if self.cancel_command:
self.cancel_command()
def has_button(self) -> Tuple[bool, bool, bool, bool]:
return True, False, True, False
def render_left_press(self, draw, x, y):
draw.text((x, y), 'Save', fill='black')
def render_right_press(self, draw, x, y):
draw.text((x, y), 'Cancel', fill='black')

View File

View File

@@ -0,0 +1,104 @@
from typing import Tuple
from PIL import ImageDraw, Image
from ..base import View
from ..button_interface import ButtonInterface
from .recipe_manager import RecipeManager
from .recipe import Recipe
class EditRecipe(View, ButtonInterface):
def __init__(self, parent, im_size, center,
recipe_manager: RecipeManager,
deactivate_command,
recipe: Recipe = None):
self.deactivate_command = deactivate_command
self.recipe_manager = recipe_manager
self.recipe = recipe
if recipe is None:
self.recipe = Recipe("New", [])
self.confirm_view = False
self.selected_field = 0 # 0: name, 1+: steps
super().__init__(parent, im_size, center)
def _get_visual_steps(self):
steps = self.recipe.steps + ['+']
if len(steps) < 4:
return steps, 0
start = max(0, self.selected_field - 2)
end = min(len(steps), start + 4)
steps = steps[start:end]
return steps, start
def update_weight(self, weight: float) -> Image.Image:
im = self.bkg_im.copy()
draw = ImageDraw.Draw(im)
x = 40
draw.text((x, 10), "Name:", fill='black')
if self.selected_field == 0:
r = 10
offset = 35
for i in range(0, 90, r // 2):
draw.circle((x + i, offset), r, fill='black')
draw.text((x, 30), self.recipe.name, fill='white')
else:
draw.text((x, 30), self.recipe.name, fill='black')
visual_steps, start = self._get_visual_steps()
for idx, step in enumerate(visual_steps):
y_pos = 60 + idx * 20
if start + idx + 1 == self.selected_field:
r = 10
offset = 15
for i in range(0, 90, r // 2):
draw.circle((x + i, y_pos), r, fill='black')
if str(step) != '+':
step.step_type.render(draw, (x, y_pos - 5), fill='white')
draw.text((x + 30, y_pos - 5), step.value_str, fill='white')
else:
draw.text((x, y_pos - 5), '+', fill='white')
elif str(step) == '+':
draw.text((x, y_pos - 5), '+', fill='black')
else:
step.step_type.render(draw, (x, y_pos - 5), fill='black')
draw.text((x + 30, y_pos - 5), step.value_str, fill='black')
return im
def left_press(self):
# edit entry
pass
def left_long_press(self):
# save
self.recipe_manager.add_recipe(self.recipe)
self.deactivate_command()
def right_press(self):
self.selected_field += 1
if self.selected_field > len(self.recipe.steps) + 1:
self.selected_field = 0
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, 5), 3, fill='black', rotation=270)
def render_left_long_press(self, draw, x, y):
draw.text((x - 3, y - 5), 'Save', fill='black')
def render_right_press(self, draw, x, y):
draw.regular_polygon((x, y + 6, 5), 3, fill='black', rotation=180)
def render_right_long_press(self, draw, x, y):
draw.text((x - 30, y - 5), 'Cancel', fill='black')

View File

@@ -3,6 +3,8 @@ from typing import Union
from enum import Enum from enum import Enum
from ..draw_utils import draw_clock
class Recipe: class Recipe:
def __init__(self, name: str, steps: list[Step]): def __init__(self, name: str, steps: list[Step]):
self.name = name self.name = name
@@ -19,6 +21,16 @@ class StepType(Enum):
WAIT_TIME_FINISHED = 3 WAIT_TIME_FINISHED = 3
TARE = 4 TARE = 4
def render(self, draw, position, fill='black') -> str:
if self == StepType.SECTION:
draw.text(position, "T", fill=fill)
elif self == StepType.WEIGH:
draw.text(position, "W", fill=fill)
elif self == StepType.START_TIME or self == StepType.WAIT_TIME_FINISHED:
draw_clock(draw, (position[0] + 3, position[1] + 5), radius=3, color=fill)
elif self == StepType.TARE:
draw.text(position, "0.0g", fill=fill)
class Step: class Step:
def __init__(self, def __init__(self,
@@ -26,6 +38,29 @@ class Step:
value: float | str = None): value: float | str = None):
self.step_type = step_type self.step_type = step_type
self.value = value self.value = value
@property
def value_str(self) -> str:
if self.step_type in [StepType.WEIGH]:
return f"{self.value}g"
elif self.step_type == StepType.START_TIME:
if self.value == -1:
return "Start"
else:
minutes = self.value // 60
seconds = self.value % 60
if minutes == 0:
return f"{seconds}s"
else:
return f"{minutes}:{seconds:02d}"
elif self.step_type == StepType.SECTION:
return str(self.value)
elif self.step_type == StepType.TARE:
return "Tare"
elif self.step_type == StepType.WAIT_TIME_FINISHED:
return "Wait"
else:
return ""
###### example recipes ######### ###### example recipes #########

View File

@@ -0,0 +1,14 @@
from .recipe import V60, ESPRESSO
class RecipeManager:
def __init__(self):
self.recipes = [
V60,
ESPRESSO
]
def add_recipe(self, recipe):
self.recipes.append(recipe)
def remove_recipe(self, recipe):
self.recipes.remove(recipe)

View File

@@ -3,20 +3,25 @@ from typing import Tuple
from ..base import View from ..base import View
from ..button_interface import ButtonInterface from ..button_interface import ButtonInterface
from .recipe_manager import RecipeManager
from .recipe import V60, ESPRESSO from .recipe import V60, ESPRESSO
from PIL import ImageDraw, Image from PIL import ImageDraw, Image
class RecipeSelection(View, ButtonInterface): class RecipeSelection(View, ButtonInterface):
recipes = [ @property
V60, def recipes(self):
ESPRESSO return self.recipe_manager.recipes
]
def __init__(self, parent, im_size, center, deactivate_command=None): def __init__(self, parent, im_size, center,
recipe_manager: RecipeManager = None,
edit_recipe_command=None,
deactivate_command=None):
self.selected_index = 0 self.selected_index = 0
self.deactivate_command = deactivate_command self.deactivate_command = deactivate_command
self.recipe_manager = recipe_manager
self.edit_recipe_command = edit_recipe_command
super().__init__(parent, im_size, center) super().__init__(parent, im_size, center)
def _get_visual_recipes(self): def _get_visual_recipes(self):
@@ -56,6 +61,10 @@ class RecipeSelection(View, ButtonInterface):
def left_press(self): def left_press(self):
self.selected_index = (self.selected_index - 1) % (len(self.recipes) + 2) self.selected_index = (self.selected_index - 1) % (len(self.recipes) + 2)
def left_long_press(self):
if self.selected_index < len(self.recipes):
self.edit_recipe_command(self.recipes[self.selected_index])
def right_press(self): def right_press(self):
self.selected_index = (self.selected_index + 1) % (len(self.recipes) + 2) self.selected_index = (self.selected_index + 1) % (len(self.recipes) + 2)
@@ -65,8 +74,7 @@ class RecipeSelection(View, ButtonInterface):
# activate selected recipe # activate selected recipe
print(f"Activating recipe: {self.recipes[self.selected_index]}") print(f"Activating recipe: {self.recipes[self.selected_index]}")
elif self.selected_index == len(self.recipes): elif self.selected_index == len(self.recipes):
# add new recipe self.edit_recipe_command()
print("Adding new recipe")
else: else:
self.selected_index = 0 self.selected_index = 0
self.deactivate_command() self.deactivate_command()