add timer
This commit is contained in:
@@ -13,4 +13,5 @@ MOV_AVG_DEFAULTS = {
|
|||||||
|
|
||||||
class DISPLAY_TYPES(Flag):
|
class DISPLAY_TYPES(Flag):
|
||||||
NUMBER = 1
|
NUMBER = 1
|
||||||
CIRCLE = 2
|
CIRCLE = 2
|
||||||
|
TIMER = 4
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
from .number import NumberView
|
from .number import NumberView
|
||||||
from .circle import CircleView
|
from .circle import CircleView
|
||||||
|
from .timer import TimerView
|
||||||
from .combined_view import CombinedView
|
from .combined_view import CombinedView
|
||||||
@@ -6,6 +6,10 @@ from .base import View
|
|||||||
|
|
||||||
class CircleView(View):
|
class CircleView(View):
|
||||||
|
|
||||||
|
def __init__(self, parent, size, center, radius_offset=10, **kwargs):
|
||||||
|
self.target_radius = min(center) - radius_offset
|
||||||
|
super().__init__(parent, size, center, **kwargs)
|
||||||
|
|
||||||
def init_ui(self, parent):
|
def init_ui(self, parent):
|
||||||
self.ui = tk.Frame(parent)
|
self.ui = tk.Frame(parent)
|
||||||
self.ui.pack()
|
self.ui.pack()
|
||||||
@@ -18,8 +22,7 @@ class CircleView(View):
|
|||||||
def _init_im(self):
|
def _init_im(self):
|
||||||
im = Image.new('1', self.size, 'white')
|
im = Image.new('1', self.size, 'white')
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
self.target_r = min(self.center)-10
|
draw.circle(self.center, self.target_radius,
|
||||||
draw.circle(self.center, self.target_r,
|
|
||||||
outline="#000000")
|
outline="#000000")
|
||||||
return im
|
return im
|
||||||
|
|
||||||
@@ -28,7 +31,7 @@ class CircleView(View):
|
|||||||
bkg_im = self.bkg_im.copy()
|
bkg_im = self.bkg_im.copy()
|
||||||
try:
|
try:
|
||||||
target = float(self.target.get())
|
target = float(self.target.get())
|
||||||
weight_radius = weight / target * self.target_r
|
weight_radius = weight / target * self.target_radius
|
||||||
|
|
||||||
im = Image.new('1', self.size, 'black')
|
im = Image.new('1', self.size, 'black')
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from tkinter import Frame, Canvas, ttk, PhotoImage
|
|||||||
from PIL import Image, ImageChops
|
from PIL import Image, ImageChops
|
||||||
|
|
||||||
from ..config import DISPLAY_TYPES
|
from ..config import DISPLAY_TYPES
|
||||||
from . import NumberView, CircleView
|
from . import NumberView, CircleView, TimerView
|
||||||
|
|
||||||
class CombinedView(tk.Frame):
|
class CombinedView(tk.Frame):
|
||||||
def __init__(self, parent,
|
def __init__(self, parent,
|
||||||
@@ -13,6 +13,7 @@ class CombinedView(tk.Frame):
|
|||||||
**kwargs):
|
**kwargs):
|
||||||
super().__init__(parent, **kwargs)
|
super().__init__(parent, **kwargs)
|
||||||
self.views = []
|
self.views = []
|
||||||
|
self.timer_view = None # Timer view is always active
|
||||||
self.tare_command = tare_command
|
self.tare_command = tare_command
|
||||||
self.calibrate_command = calibrate_command
|
self.calibrate_command = calibrate_command
|
||||||
|
|
||||||
@@ -26,12 +27,16 @@ class CombinedView(tk.Frame):
|
|||||||
self.im_size = (168, 144)
|
self.im_size = (168, 144)
|
||||||
self.center = (168 // 2, 144 // 2)
|
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',
|
self.canvas = Canvas(self, width=168, height=144, background='white',
|
||||||
highlightthickness=1, highlightbackground="black")
|
highlightthickness=1, highlightbackground="black")
|
||||||
self.canvas.pack()
|
self.canvas.pack()
|
||||||
|
|
||||||
|
|
||||||
def update_views(self, selected_types: DISPLAY_TYPES):
|
def update_views(self, selected_types: DISPLAY_TYPES):
|
||||||
|
# Clear only the selectable views, not the timer
|
||||||
for v in self.views:
|
for v in self.views:
|
||||||
if v.ui is not None:
|
if v.ui is not None:
|
||||||
v.ui.destroy()
|
v.ui.destroy()
|
||||||
@@ -48,6 +53,13 @@ class CombinedView(tk.Frame):
|
|||||||
|
|
||||||
def refresh(self, weight: float):
|
def refresh(self, weight: float):
|
||||||
ims = []
|
ims = []
|
||||||
|
|
||||||
|
# Always include timer view
|
||||||
|
if self.timer_view:
|
||||||
|
timer_im = self.timer_view.update_weight(weight)
|
||||||
|
ims.append(timer_im)
|
||||||
|
|
||||||
|
# Add other selected views
|
||||||
for view in self.views:
|
for view in self.views:
|
||||||
im = view.update_weight(weight)
|
im = view.update_weight(weight)
|
||||||
ims.append(im)
|
ims.append(im)
|
||||||
|
|||||||
126
frontend/views/timer.py
Normal file
126
frontend/views/timer.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
from PIL import Image, ImageDraw, ImageChops
|
||||||
|
import time
|
||||||
|
import math
|
||||||
|
|
||||||
|
from .base import View
|
||||||
|
|
||||||
|
class TimerView(View):
|
||||||
|
def __init__(self, parent, size, center, width=5, **kwargs):
|
||||||
|
self.start_time = None
|
||||||
|
self.elapsed_time = 0
|
||||||
|
self.is_running = False
|
||||||
|
self.goal_minutes = 0 # 0 means no goal set
|
||||||
|
self.radius = min(center)-width
|
||||||
|
self.width = width
|
||||||
|
super().__init__(parent, size, center, **kwargs)
|
||||||
|
|
||||||
|
def init_ui(self, parent):
|
||||||
|
self.ui = tk.Frame(parent)
|
||||||
|
self.ui.pack(pady=10)
|
||||||
|
|
||||||
|
# Goal input
|
||||||
|
self.goal_label = ttk.Label(self.ui, text="Goal (sec):")
|
||||||
|
self.goal_label.pack()
|
||||||
|
self.goal_entry = ttk.Entry(self.ui, width=8)
|
||||||
|
self.goal_entry.insert(0, "0")
|
||||||
|
self.goal_entry.pack()
|
||||||
|
|
||||||
|
# Timer buttons
|
||||||
|
self.start_stop_button = ttk.Button(self.ui, text="Start", command=self.toggle_timer)
|
||||||
|
self.start_stop_button.pack(side=tk.LEFT, padx=2)
|
||||||
|
|
||||||
|
self.reset_button = ttk.Button(self.ui, text="Reset", command=self.reset_timer)
|
||||||
|
self.reset_button.pack(side=tk.LEFT, padx=2)
|
||||||
|
|
||||||
|
def toggle_timer(self):
|
||||||
|
if self.is_running:
|
||||||
|
# Stop timer
|
||||||
|
self.is_running = False
|
||||||
|
if self.start_time:
|
||||||
|
self.elapsed_time += time.time() - self.start_time
|
||||||
|
self.start_stop_button.config(text="Start")
|
||||||
|
else:
|
||||||
|
# Start timer
|
||||||
|
self.is_running = True
|
||||||
|
self.start_time = time.time()
|
||||||
|
self.start_stop_button.config(text="Stop")
|
||||||
|
|
||||||
|
def reset_timer(self):
|
||||||
|
self.is_running = False
|
||||||
|
self.start_time = None
|
||||||
|
self.elapsed_time = 0
|
||||||
|
self.start_stop_button.config(text="Start")
|
||||||
|
|
||||||
|
def get_current_time(self):
|
||||||
|
"""Get current elapsed time in seconds"""
|
||||||
|
if self.is_running and self.start_time:
|
||||||
|
return self.elapsed_time + (time.time() - self.start_time)
|
||||||
|
return self.elapsed_time
|
||||||
|
|
||||||
|
def update_weight(self, weight):
|
||||||
|
"""Override to update timer display instead of weight"""
|
||||||
|
current_time = self.get_current_time()
|
||||||
|
|
||||||
|
# Create base image
|
||||||
|
im = self.bkg_im.copy()
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
# Format time display (MM:SS)
|
||||||
|
minutes = int(current_time // 60)
|
||||||
|
seconds = int(current_time % 60)
|
||||||
|
time_text = f"{minutes:02d}:{seconds:02d}"
|
||||||
|
|
||||||
|
if time_text != "00:00" or self.is_running:
|
||||||
|
# Draw timer text in center
|
||||||
|
# Estimate text size for centering
|
||||||
|
text_width = len(time_text) * 10 # Rough estimate
|
||||||
|
text_height = 20
|
||||||
|
text_x = self.center[0] - text_width // 2
|
||||||
|
text_y = self.center[1] - text_height // 2
|
||||||
|
|
||||||
|
draw.text((text_x, text_y), time_text, fill='black')
|
||||||
|
|
||||||
|
# Draw progress circle if goal is set
|
||||||
|
try:
|
||||||
|
goal_sec = float(self.goal_entry.get())
|
||||||
|
if goal_sec > 0:
|
||||||
|
progress = current_time / goal_sec
|
||||||
|
else:
|
||||||
|
progress = current_time / 60
|
||||||
|
|
||||||
|
if progress > 0:
|
||||||
|
inverted = int(progress) % 2 == 1
|
||||||
|
progress = progress % 1.0 # Loop every full circle
|
||||||
|
|
||||||
|
start = self.center[0] - self.radius, self.center[1] - self.radius
|
||||||
|
end = self.center[0] + self.radius, self.center[1] + self.radius
|
||||||
|
|
||||||
|
if inverted:
|
||||||
|
draw.arc((start, end), 360 * progress - 90, 270, fill='black', width=self.width)
|
||||||
|
else:
|
||||||
|
draw.arc((start, end), 270, 360 * progress - 90, fill='black', width=self.width)
|
||||||
|
|
||||||
|
except (ValueError, tk.TclError):
|
||||||
|
# Invalid goal value, just show time without progress
|
||||||
|
pass
|
||||||
|
|
||||||
|
return im
|
||||||
|
|
||||||
|
def _draw_progress_arc(self, draw, progress):
|
||||||
|
"""Draw a progress arc around the outer circle"""
|
||||||
|
if progress <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Draw filled arc by drawing multiple lines from center to circumference
|
||||||
|
center_x, center_y = self.center
|
||||||
|
# Start from top (270 degrees) and go clockwise
|
||||||
|
start_angle = 270
|
||||||
|
num_steps = max(1, int(360 * progress))
|
||||||
|
|
||||||
|
for i in range(num_steps):
|
||||||
|
angle = math.radians(start_angle + i)
|
||||||
|
end_x = center_x + self.outer_radius * math.cos(angle)
|
||||||
|
end_y = center_y + self.outer_radius * math.sin(angle)
|
||||||
|
draw.line([(center_x, center_y), (end_x, end_y)], fill='black', width=1)
|
||||||
Reference in New Issue
Block a user