From 7a3329e8f27946a6bcf3d2f0562e04af3aa7e399 Mon Sep 17 00:00:00 2001 From: Jannes Magnusson Date: Sat, 18 Oct 2025 16:21:01 +0200 Subject: [PATCH] add button manager --- .gitignore | 2 + frontend/views/buttons_manager.py | 178 ++++++++++++++++++++++++++++++ frontend/views/draw_utils.py | 12 ++ frontend/views/main_view.py | 84 ++------------ 4 files changed, 200 insertions(+), 76 deletions(-) create mode 100644 frontend/views/buttons_manager.py create mode 100644 frontend/views/draw_utils.py diff --git a/.gitignore b/.gitignore index 0dbf2f2..88e2958 100644 --- a/.gitignore +++ b/.gitignore @@ -168,3 +168,5 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/frontend/views/buttons_manager.py b/frontend/views/buttons_manager.py new file mode 100644 index 0000000..b6673ba --- /dev/null +++ b/frontend/views/buttons_manager.py @@ -0,0 +1,178 @@ +from tkinter import Frame, ttk +from PIL import ImageDraw + +from .base import View +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.long_press_threshold = 1000 # milliseconds + self.left_press_start = None + self.right_press_start = None + self.both_press_start = None + self.left_press_job = None + self.right_press_job = None + self.both_press_job = None + + for key, action in actions.items(): + setattr(self, key, action) + + super().__init__(parent, im_size, center) + + def init_ui(self, parent): + self.ui = Frame(parent) + self.left_button = ttk.Button(self.ui, text="Left") + # Bind mouse events for press detection + self.left_button.bind("", self._left_button_press_start) + self.left_button.bind("", self._left_button_press_end) + self.left_button.pack(side="left") + + self.right_button = ttk.Button(self.ui, text="Right") + # Bind mouse events for press detection + self.right_button.bind("", self._right_button_press_start) + self.right_button.bind("", self._right_button_press_end) + self.right_button.pack(side="right") + + self.both_buttons = ttk.Button(self.ui, text="Both") + # Bind mouse events for press detection + self.both_buttons.bind("", self._both_buttons_press_start) + self.both_buttons.bind("", self._both_buttons_press_end) + self.both_buttons.pack() + + self.ui.pack() + + def update_weight(self, weight: float) -> None: + im = self.bkg_im.copy() + draw = ImageDraw.Draw(im) + + # Draw left button + if self.current_config.get('left_button_press', None): + 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): + 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') + + # Draw right button + if self.current_config.get('right_button_press', None): + 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): + 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') + + return im + + ############ BUTTON ACTIONS ########### + def left_button_press(self): + action = self.current_config.get('left_button_press', None) + if action: + self.__getattribute__(action)() + + def left_button_long_press(self): + action = self.current_config.get('left_button_long_press', None) + if action: + self.__getattribute__(action)() + + def right_button_press(self): + action = self.current_config.get('right_button_press', None) + if action: + self.__getattribute__(action)() + + def right_button_long_press(self): + action = self.current_config.get('right_button_long_press', None) + if action: + self.__getattribute__(action)() + + def both_buttons_long_press(self): + action = self.current_config.get('both_buttons_long_press', None) + if action: + self.__getattribute__(action)() + + + ############ BUTTON PRESS HANDLERS ########### + def _left_button_press_start(self, event): + """Handle left button press start""" + self.left_press_start = self.ui.after_idle(lambda: None) # Get current time reference + # Schedule long press detection + self.left_press_job = self.ui.after(self.long_press_threshold, self._left_long_press_detected) + + def _left_button_press_end(self, event): + """Handle left button press end""" + if self.left_press_job: + self.ui.after_cancel(self.left_press_job) + self.left_press_job = None + # If we get here, it was a short press + self.left_button_press() + self.left_press_start = None + + def _left_long_press_detected(self): + """Called when long press threshold is reached for left button""" + self.left_press_job = None + self.left_button_long_press() + + + + def _right_button_press_start(self, event): + """Handle right button press start""" + self.right_press_start = self.ui.after_idle(lambda: None) # Get current time reference + # Schedule long press detection + self.right_press_job = self.ui.after(self.long_press_threshold, self._right_long_press_detected) + + def _right_button_press_end(self, event): + """Handle right button press end""" + if self.right_press_job: + self.ui.after_cancel(self.right_press_job) + self.right_press_job = None + # If we get here, it was a short press + self.right_button_press() + self.right_press_start = None + + def _right_long_press_detected(self): + """Called when long press threshold is reached for right button""" + self.right_press_job = None + self.right_button_long_press() + + + + def _both_buttons_press_start(self, event): + """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) + + def _both_buttons_press_end(self, event): + """Handle both buttons press end""" + if self.both_press_job: + self.ui.after_cancel(self.both_press_job) + self.both_press_job = None + # If we get here, it was a short press (no action defined) + self.both_press_start = None + + def _both_buttons_long_press_detected(self): + """Called when long press threshold is reached for both buttons""" + self.both_press_job = None + self.both_buttons_long_press() \ No newline at end of file diff --git a/frontend/views/draw_utils.py b/frontend/views/draw_utils.py new file mode 100644 index 0000000..94bab34 --- /dev/null +++ b/frontend/views/draw_utils.py @@ -0,0 +1,12 @@ +def draw_clock(draw, position, radius=16, width=1): + """Draw a simple clock icon at the given position""" + x, y = position + draw.circle((x, y), radius, outline='black', width=width) + draw.line((x, y - radius + 2, x, y), fill='black', width=width) # Hour hand + draw.line((x, y, x + radius * 3 / 4, y), fill='black', width=width) # Minute hand + +def draw_long_press(draw, position): + """Draw a long press button icon at the given position""" + x, y = position + for i in range(0, 5, 2): + draw.circle((x + i, y), 2, fill='black') diff --git a/frontend/views/main_view.py b/frontend/views/main_view.py index 40563ff..e241054 100644 --- a/frontend/views/main_view.py +++ b/frontend/views/main_view.py @@ -6,6 +6,7 @@ from PIL import ImageChops from ..config import DISPLAY_TYPES from . import NumberView, CircleView, TimerView +from .buttons_manager import ButtonsManager class MainView(tk.Frame): def __init__(self, parent, @@ -18,13 +19,6 @@ class MainView(tk.Frame): self.tare_command = tare_command self.calibrate_command = calibrate_command - # Button press detection variables - self.left_press_start = None - self.right_press_start = None - self.long_press_threshold = 1000 # 1 second in milliseconds - self.left_press_job = None - self.right_press_job = None - self.actions = Frame(self) self.actions.pack() self.calibrate_button = ttk.Button(self.actions, text="Calibrate", command=calibrate_command) @@ -43,19 +37,10 @@ class MainView(tk.Frame): highlightthickness=1, highlightbackground="black") self.canvas.pack() - self.device_frame = Frame(self.actions) - self.left_button = ttk.Button(self.device_frame, text="Left") - # Bind mouse events for press detection - self.left_button.bind("", self._left_button_press_start) - self.left_button.bind("", self._left_button_press_end) - self.left_button.pack(side="left") - - self.right_button = ttk.Button(self.device_frame, text="Right") - # Bind mouse events for press detection - self.right_button.bind("", self._right_button_press_start) - self.right_button.bind("", self._right_button_press_end) - self.right_button.pack(side="right") - self.device_frame.pack() + 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) ################ VIEW MANAGEMENT ################ @@ -79,10 +64,12 @@ class MainView(tk.Frame): def refresh(self, weight: float): ims = [] - # Always include timer view + # 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: @@ -111,58 +98,3 @@ class MainView(tk.Frame): ########### BUTTON PRESS HANDLING ########### - def _left_button_press_start(self, event): - """Handle left button press start""" - self.left_press_start = self.after_idle(lambda: None) # Get current time reference - # Schedule long press detection - self.left_press_job = self.after(self.long_press_threshold, self._left_long_press_detected) - - def _left_button_press_end(self, event): - """Handle left button press end""" - if self.left_press_job: - self.after_cancel(self.left_press_job) - self.left_press_job = None - # If we get here, it was a short press - self.left_button_press() - self.left_press_start = None - - def _left_long_press_detected(self): - """Called when long press threshold is reached for left button""" - self.left_press_job = None - self.left_button_long_press() - - def _right_button_press_start(self, event): - """Handle right button press start""" - self.right_press_start = self.after_idle(lambda: None) # Get current time reference - # Schedule long press detection - self.right_press_job = self.after(self.long_press_threshold, self._right_long_press_detected) - - def _right_button_press_end(self, event): - """Handle right button press end""" - if self.right_press_job: - self.after_cancel(self.right_press_job) - self.right_press_job = None - # If we get here, it was a short press - self.right_button_press() - self.right_press_start = None - - def _right_long_press_detected(self): - """Called when long press threshold is reached for right button""" - self.right_press_job = None - self.right_button_long_press() - - - ############ BUTTON ACTIONS ########### - - def left_button_press(self): - self.timer_view.toggle_timer() - - def left_button_long_press(self): - self.timer_view.reset_timer() - - - def right_button_press(self): - self.tare_command() - - def right_button_long_press(self): - pass \ No newline at end of file