|
1 | 1 | # window.py |
2 | 2 | # |
3 | | -# Copyright 2025 Carbon751 |
| 3 | +# Copyright 2025 code-leech |
4 | 4 | # |
5 | 5 | # This program is free software: you can redistribute it and/or modify |
6 | 6 | # it under the terms of the GNU General Public License as published by |
|
13 | 13 | # GNU General Public License for more details. |
14 | 14 | # |
15 | 15 | # You should have received a copy of the GNU General Public License |
16 | | -# along with this program. If not, see <https://www.gnu.org/licenses/>. |
| 16 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | 17 | # |
18 | 18 | # SPDX-License-Identifier: GPL-3.0-or-later |
19 | 19 |
|
| 20 | +import math |
| 21 | +import gi |
| 22 | +gi.require_version('Gtk', '4.0') |
| 23 | +gi.require_version('Adw', '1') |
20 | 24 | from gi.repository import Adw |
21 | 25 | from gi.repository import Gtk |
| 26 | +from gi.repository import GLib |
| 27 | +from gi.repository import Gdk |
22 | 28 |
|
23 | 29 | @Gtk.Template(resource_path='/code/leech/pytimer/window.ui') |
24 | 30 | class PytimerWindow(Adw.ApplicationWindow): |
25 | 31 | __gtype_name__ = 'PytimerWindow' |
26 | 32 |
|
27 | | - label = Gtk.Template.Child() |
| 33 | + # Template children |
| 34 | + timer_overlay = Gtk.Template.Child('timer_overlay') |
| 35 | + progress_circle = Gtk.Template.Child('progress_circle') |
| 36 | + time_label = Gtk.Template.Child('time_label') |
| 37 | + start_button = Gtk.Template.Child('start_button') |
| 38 | + minutes_spin = Gtk.Template.Child('minutes_spin') |
28 | 39 |
|
29 | 40 | def __init__(self, **kwargs): |
30 | 41 | super().__init__(**kwargs) |
| 42 | + |
| 43 | + # Initialize timer variables |
| 44 | + self.timer_running = False |
| 45 | + self.remaining_seconds = 0 |
| 46 | + self.total_seconds = 0 |
| 47 | + self.timeout_id = None |
| 48 | + |
| 49 | + # Set up the drawing area |
| 50 | + self.progress_circle.set_draw_func(self.draw_timer_arc, None) |
| 51 | + |
| 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) |
| 55 | + |
| 56 | + # Add custom CSS for styling |
| 57 | + css_provider = Gtk.CssProvider() |
| 58 | + 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 | + } |
| 90 | + """) |
| 91 | + Gtk.StyleContext.add_provider_for_display( |
| 92 | + Gdk.Display.get_default(), |
| 93 | + css_provider, |
| 94 | + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION |
| 95 | + ) |
| 96 | + |
| 97 | + # Set initial time display |
| 98 | + self._update_time_label() |
| 99 | + |
| 100 | + def draw_timer_arc(self, area, cr, width, height, data): |
| 101 | + """Draw the circular progress indicator""" |
| 102 | + # Calculate center and radius |
| 103 | + center_x = width / 2 |
| 104 | + center_y = height / 2 |
| 105 | + 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 |
| 109 | + cr.set_line_width(8) |
| 110 | + cr.arc(center_x, center_y, radius, 0, 2 * math.pi) |
| 111 | + cr.stroke() |
| 112 | + |
| 113 | + if self.total_seconds > 0: |
| 114 | + # Draw progress arc with modern color |
| 115 | + progress = 1 - (self.remaining_seconds / self.total_seconds) |
| 116 | + cr.set_source_rgb(0.2, 0.6, 1.0) # Brighter blue for better visibility |
| 117 | + cr.arc(center_x, center_y, radius, -math.pi/2, |
| 118 | + 2 * math.pi * progress - math.pi/2) |
| 119 | + cr.stroke() |
| 120 | + |
| 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 | + |
| 134 | + def _on_minutes_changed(self, spin_button): |
| 135 | + """Handle minutes spinbutton value changes""" |
| 136 | + if not self.timer_running: |
| 137 | + minutes = spin_button.get_value_as_int() |
| 138 | + self.remaining_seconds = minutes * 60 |
| 139 | + self.total_seconds = self.remaining_seconds # Update total seconds when minutes change |
| 140 | + self._update_time_label() |
| 141 | + self.progress_circle.queue_draw() |
| 142 | + |
| 143 | + def _start_timer(self): |
| 144 | + """Start the timer""" |
| 145 | + self.timer_running = True |
| 146 | + self.timeout_id = GLib.timeout_add_seconds(1, self._update_timer) |
| 147 | + self.minutes_spin.set_sensitive(False) |
| 148 | + |
| 149 | + def _stop_timer(self): |
| 150 | + """Stop the timer""" |
| 151 | + self.timer_running = False |
| 152 | + if self.timeout_id: |
| 153 | + GLib.source_remove(self.timeout_id) |
| 154 | + self.timeout_id = None |
| 155 | + self.minutes_spin.set_sensitive(True) |
| 156 | + |
| 157 | + def _update_timer(self): |
| 158 | + """Update timer state - called every second while timer is running""" |
| 159 | + if self.remaining_seconds > 0: |
| 160 | + self.remaining_seconds -= 1 |
| 161 | + self._update_time_label() |
| 162 | + self.progress_circle.queue_draw() |
| 163 | + return True |
| 164 | + else: |
| 165 | + self._timer_finished() |
| 166 | + return False |
| 167 | + |
| 168 | + def _update_time_label(self): |
| 169 | + """Update the time display label""" |
| 170 | + minutes = self.remaining_seconds // 60 |
| 171 | + seconds = self.remaining_seconds % 60 |
| 172 | + self.time_label.set_label(f"{minutes:02d}:{seconds:02d}") |
| 173 | + |
| 174 | + def _timer_finished(self): |
| 175 | + """Handle timer completion""" |
| 176 | + self.timer_running = False |
| 177 | + self.start_button.set_icon_name("media-playback-start-symbolic") |
| 178 | + self.minutes_spin.set_sensitive(True) |
0 commit comments