1- # window.py
2- #
3- # Copyright 2025 code-leech
4- #
5- # This program is free software: you can redistribute it and/or modify
6- # it under the terms of the GNU General Public License as published by
7- # the Free Software Foundation, either version 3 of the License, or
8- # (at your option) any later version.
9- #
10- # This program is distributed in the hope that it will be useful,
11- # but WITHOUT ANY WARRANTY; without even the implied warranty of
12- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13- # GNU General Public License for more details.
14- #
15- # You should have received a copy of the GNU General Public License
16- # along with this program. If not, see <http://www.gnu.org/licenses/>.
17- #
18- # SPDX-License-Identifier: GPL-3.0-or-later
19-
201import math
212import gi
223gi .require_version ('Gtk' , '4.0' )
234gi .require_version ('Adw' , '1' )
24- from gi .repository import Adw
25- from gi .repository import Gtk
26- from gi .repository import GLib
27- from gi .repository import Gdk
5+ from gi .repository import Adw , Gtk , GLib , Gdk
286
297@Gtk .Template (resource_path = '/code/leech/pytimer/window.ui' )
308class PytimerWindow (Adw .ApplicationWindow ):
@@ -34,8 +12,9 @@ class PytimerWindow(Adw.ApplicationWindow):
3412 timer_overlay = Gtk .Template .Child ('timer_overlay' )
3513 progress_circle = Gtk .Template .Child ('progress_circle' )
3614 time_label = Gtk .Template .Child ('time_label' )
37- start_button = Gtk .Template .Child ('start_button' )
3815 minutes_spin = Gtk .Template .Child ('minutes_spin' )
16+ header_bar = Gtk .Template .Child ('header_bar' )
17+ button_box = Gtk .Template .Child ('button_box' )
3918
4019 def __init__ (self , ** kwargs ):
4120 super ().__init__ (** kwargs )
@@ -46,116 +25,112 @@ def __init__(self, **kwargs):
4625 self .total_seconds = 0
4726 self .timeout_id = None
4827
49- # Set up the drawing area
28+ # Create start button
29+ self .start_button = Gtk .Button (
30+ icon_name = "media-playback-start-symbolic" ,
31+ css_classes = ["circular" ]
32+ )
33+ self .start_button .set_valign (Gtk .Align .END )
34+ self .start_button .connect ('clicked' , self ._on_start_clicked )
35+ self .button_box .append (self .start_button )
36+
37+ # Create reset button
38+ self .reset_button = Gtk .Button (
39+ icon_name = "view-refresh-symbolic" ,
40+ css_classes = ["circular" , "destructive-action" ]
41+ )
42+ self .reset_button .set_valign (Gtk .Align .END )
43+ self .reset_button .connect ('clicked' , self ._on_reset_clicked )
44+ self .reset_button .set_visible (False ) # Initially hidden
45+ self .button_box .append (self .reset_button )
46+
47+ # Set up drawing area
5048 self .progress_circle .set_draw_func (self .draw_timer_arc , None )
5149
52- # Connect signals using connect_after to ensure widget is fully initialized
53- self .start_button .connect_after ('clicked' , self ._on_start_clicked )
54- self .minutes_spin .connect_after ('value-changed' , self ._on_minutes_changed )
50+ # Connect signals
51+ self .minutes_spin .connect ('value-changed' , self ._on_minutes_changed )
5552
56- # Add custom CSS for styling
53+ # Add CSS for styling
5754 css_provider = Gtk .CssProvider ()
5855 css_provider .load_from_data (b"""
59- .timer-label {
60- font-size: 48px;
61- font-weight: 300;
62- }
63-
64- .timer-circle {
65- margin: 24px;
66- }
67-
68- headerbar.tall {
69- min-height: 64px;
70- }
71-
72- button.circular {
73- padding: 8px;
74- margin: 4px;
75- border-radius: 9999px;
76- }
77-
78- button.closebutton {
79- padding: 0;
80- margin: 0;
81- min-width: 24px;
82- min-height: 24px;
83- background-color: #ebebeb;
84- border-radius: 9999px;
85- }
86-
87- button.closebutton:hover {
88- background-color: #d4d4d4;
89- }
56+ .timer-label { font-size: 48px; font-weight: 300; }
57+ button.circular { padding: 4px; margin: 2px; border-radius: 9999px; }
58+ headerbar { padding: 0; min-height: 24px; box-shadow: none; border-bottom: none; }
59+ window { margin: 0; }
9060 """ )
9161 Gtk .StyleContext .add_provider_for_display (
9262 Gdk .Display .get_default (),
9363 css_provider ,
9464 Gtk .STYLE_PROVIDER_PRIORITY_APPLICATION
9565 )
9666
97- # Set initial time display
67+ # Initial setup
68+ self ._update_time_label ()
69+ self ._update_start_button_state ()
70+
71+ def _on_reset_clicked (self , button ):
72+ self .remaining_seconds = 0
73+ self .total_seconds = 0
9874 self ._update_time_label ()
75+ self .reset_button .set_visible (False )
76+ self .progress_circle .queue_draw ()
77+
78+ def _on_start_clicked (self , button ):
79+ if not self .timer_running :
80+ if self .total_seconds == 0 :
81+ minutes = self .minutes_spin .get_value_as_int ()
82+ if minutes > 0 :
83+ self .total_seconds = minutes * 60
84+ self .remaining_seconds = self .total_seconds
85+ self ._start_timer ()
86+ button .set_icon_name ("media-playback-stop-symbolic" )
87+ else :
88+ self ._stop_timer ()
89+ button .set_icon_name ("media-playback-start-symbolic" )
90+ if self .remaining_seconds > 0 :
91+ self .reset_button .set_visible (True )
9992
10093 def draw_timer_arc (self , area , cr , width , height , data ):
101- """Draw the circular progress indicator"""
102- # Calculate center and radius
10394 center_x = width / 2
10495 center_y = height / 2
10596 radius = min (width , height ) / 2 - 10
106-
107- # Draw background circle with modern styling
108- cr .set_source_rgba (0.9 , 0.9 , 0.9 , 0.2 ) # More subtle background
97+ cr .set_source_rgba (0.9 , 0.9 , 0.9 , 0.2 )
10998 cr .set_line_width (8 )
11099 cr .arc (center_x , center_y , radius , 0 , 2 * math .pi )
111100 cr .stroke ()
112-
113101 if self .total_seconds > 0 :
114- # Draw progress arc with modern color
102+ cr . set_source_rgb ( 0.2 , 0.6 , 1.0 )
115103 progress = 1 - (self .remaining_seconds / self .total_seconds )
116- cr .set_source_rgb (0.2 , 0.6 , 1.0 ) # Brighter blue for better visibility
117104 cr .arc (center_x , center_y , radius , - math .pi / 2 ,
118- 2 * math .pi * progress - math .pi / 2 )
105+ 2 * math .pi * progress - math .pi / 2 )
119106 cr .stroke ()
120107
121- def _on_start_clicked (self , button ):
122- """Handle start/stop button clicks"""
123- if not self .timer_running :
124- minutes = self .minutes_spin .get_value_as_int ()
125- if minutes > 0 :
126- self .total_seconds = minutes * 60
127- self .remaining_seconds = self .total_seconds
128- self ._start_timer ()
129- button .set_icon_name ("media-playback-stop-symbolic" )
130- else :
131- self ._stop_timer ()
132- button .set_icon_name ("media-playback-start-symbolic" )
133-
134108 def _on_minutes_changed (self , spin_button ):
135- """Handle minutes spinbutton value changes"""
136109 if not self .timer_running :
137110 minutes = spin_button .get_value_as_int ()
138111 self .remaining_seconds = minutes * 60
139- self .total_seconds = self .remaining_seconds # Update total seconds when minutes change
112+ self .total_seconds = self .remaining_seconds
140113 self ._update_time_label ()
141- self .progress_circle .queue_draw ()
114+ self ._update_start_button_state ()
115+
116+ def _update_start_button_state (self ):
117+ minutes = self .minutes_spin .get_value_as_int ()
118+ self .start_button .set_sensitive (minutes > 0 or self .remaining_seconds > 0 )
142119
143120 def _start_timer (self ):
144- """Start the timer"""
145121 self .timer_running = True
146122 self .timeout_id = GLib .timeout_add_seconds (1 , self ._update_timer )
147123 self .minutes_spin .set_sensitive (False )
148124
149125 def _stop_timer (self ):
150- """Stop the timer"""
151126 self .timer_running = False
152127 if self .timeout_id :
153128 GLib .source_remove (self .timeout_id )
154129 self .timeout_id = None
155130 self .minutes_spin .set_sensitive (True )
131+ self ._update_start_button_state ()
156132
157133 def _update_timer (self ):
158- """Update timer state - called every second while timer is running"""
159134 if self .remaining_seconds > 0 :
160135 self .remaining_seconds -= 1
161136 self ._update_time_label ()
@@ -166,13 +141,16 @@ def _update_timer(self):
166141 return False
167142
168143 def _update_time_label (self ):
169- """Update the time display label"""
170144 minutes = self .remaining_seconds // 60
171145 seconds = self .remaining_seconds % 60
172146 self .time_label .set_label (f"{ minutes :02d} :{ seconds :02d} " )
173147
174148 def _timer_finished (self ):
175- """Handle timer completion"""
176149 self .timer_running = False
177150 self .start_button .set_icon_name ("media-playback-start-symbolic" )
178151 self .minutes_spin .set_sensitive (True )
152+ self .remaining_seconds = 0
153+ self .total_seconds = 0
154+ self ._update_time_label ()
155+ self .progress_circle .queue_draw ()
156+ self ._update_start_button_state ()
0 commit comments