feat(src/window/pomodoro): implement Pomodoro timer widget and integrate it into the panel

Add Pomodoro component with UI controls and state management,
modify Panel to instantiate and cache Pomodoro and TodoList widgets,
hook datetime button to toggle Pomodoro and handle right‑click on todo list,
extend destroy logic to cleanly close Pomodoro and Todo instances.
This commit is contained in:
sankalp committed 2026-07-12 09:32:47 +05:45
1 parent b78710002a
commit 4637799e11
5 files changed
+524 -26

No files matched your search

+449
View File
@@ -0,0 +1,449 @@
import os
from enum import Enum
from pathlib import Path
from fabric.utils import GLib, Gtk
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.circularprogressbar import CircularProgressBar
from fabric.widgets.label import Label
from shared.widgets.flat_scale import FlatScale
from shared.widgets.smooth_switch import SmoothSwitch
from shared.window.applet_window import AppletWindow
class PomodoroState(Enum):
STOPPED = 0
FOCUS = 1
SHORT_BREAK = 2
LONG_BREAK = 3
class SettingsSpinner(Box):
def __init__(self, value, min_val, max_val, callback, **kwargs):
super().__init__(orientation="h", spacing=8, v_align="center", **kwargs)
self.value = value
self.min_val = min_val
self.max_val = max_val
self.callback = callback
self.value_label = Label(label=str(self.value), name="pomodoro-spin-value")
self.value_label.set_size_request(24, -1)
up_icon = Label(label="▲")
down_icon = Label(label="▼")
up_btn = Button(child=up_icon, name="pomodoro-spin-btn")
up_btn.connect("clicked", self._on_up)
down_btn = Button(child=down_icon, name="pomodoro-spin-btn")
down_btn.connect("clicked", self._on_down)
btn_box = Box(orientation="v", spacing=0, children=[up_btn, down_btn])
btn_box.set_name("pomodoro-spin-box")
self.add(self.value_label)
self.add(btn_box)
def _on_up(self, *_):
if self.value < self.max_val:
self.value += 1
self.value_label.set_label(str(self.value))
if self.callback:
self.callback()
def _on_down(self, *_):
if self.value > self.min_val:
self.value -= 1
self.value_label.set_label(str(self.value))
if self.callback:
self.callback()
def get_value(self):
return self.value
class Pomodoro(AppletWindow):
def __init__(self, parent=None, pointing_to=None, **kwargs):
super().__init__(
parent=parent,
pointing_to=pointing_to,
layer="top",
anchor="top right",
margin="2px 10px 0px 0px",
edge_margin=5,
name="pomodoro-popup",
title="modus-pomodoro",
**kwargs,
)
self.state = PomodoroState.STOPPED
self.current_cycle = 1
# Default settings
self.focus_mins = 25
self.short_break_mins = 5
self.long_break_mins = 15
self.max_cycles = 4
self.time_left = self.focus_mins * 60
self.total_time = self.time_left
self.is_running = False
self.auto_start = True
self.volume = 100
self._timeout_id = None
self._build_ui()
self._update_display()
def _build_ui(self):
# Progress Bar and Timer Display
self.time_label = Label(label="25:00", name="pomodoro-time")
self.time_label.set_margin_bottom(5)
self.cycle_label = Label(label="Cycle 1 / 4", name="pomodoro-cycle")
timer_box = Box(
orientation="v",
v_align="center",
h_align="center",
children=[self.time_label, self.cycle_label],
)
self.progress_bar = CircularProgressBar(
value=1.0,
size=180,
line_width=12,
child=timer_box,
name="pomodoro-progress",
)
self.phase_buttons = {}
phase_box = Box(orientation="h", spacing=8, h_align="center")
for phase in ["Focus", "Short Break", "Long Break"]:
btn = Button(
label=phase,
name="pomodoro-phase-btn",
on_clicked=lambda _, p=phase: self._switch_phase(p),
)
self.phase_buttons[phase] = btn
phase_box.add(btn)
header_box = Box(
orientation="v",
h_align="center",
spacing=10,
children=[phase_box, self.progress_bar],
)
header_box.set_margin_top(15)
# Primary Buttons (Start/Pause, Reset)
self.start_btn = Button(
label="Start", name="pomodoro-start-btn", on_clicked=self.toggle_timer
)
self.reset_btn = Button(
label="Reset", name="pomodoro-reset-btn", on_clicked=self.reset_timer
)
primary_btn_box = Box(
orientation="h",
spacing=10,
h_align="center",
children=[self.start_btn, self.reset_btn],
)
# Secondary Buttons (Skip, Restart)
self.skip_btn = Button(
label="Skip", name="pomodoro-secondary-btn", on_clicked=self.skip_session
)
self.restart_btn = Button(
label="Restart",
name="pomodoro-secondary-btn",
on_clicked=self.restart_session,
)
secondary_btn_box = Box(
orientation="h",
spacing=50,
h_align="center",
children=[self.skip_btn, self.restart_btn],
)
secondary_btn_box.set_margin_bottom(15)
secondary_btn_box.set_margin_top(5)
# Settings
self.focus_spin = SettingsSpinner(
self.focus_mins, 1, 120, self._on_settings_changed
)
self.sbreak_spin = SettingsSpinner(
self.short_break_mins, 1, 60, self._on_settings_changed
)
self.lbreak_spin = SettingsSpinner(
self.long_break_mins, 1, 60, self._on_settings_changed
)
self.cycles_spin = SettingsSpinner(
self.max_cycles, 1, 10, self._on_settings_changed
)
settings_list = Box(
orientation="v",
spacing=10,
children=[
self._setting_row("Focus", self.focus_spin, "min"),
self._setting_row("Short Break", self.sbreak_spin, "min"),
self._setting_row("Long Break", self.lbreak_spin, "min"),
self._setting_row("Cycles", self.cycles_spin, ""),
],
)
settings_list.set_margin_bottom(15)
# Options
self.auto_start_switch = SmoothSwitch(
name="settings-switch",
active=self.auto_start,
on_user_toggle=self._on_auto_start_toggled,
)
auto_start_box = Box(
orientation="h",
spacing=10,
children=[
Label(label="Auto-start next session", h_expand=True, h_align="start"),
self.auto_start_switch,
],
)
auto_start_box.set_margin_bottom(10)
# Volume
self.volume_scale = FlatScale(
min_value=0,
max_value=100,
value=self.volume,
h_expand=True,
on_value_changed=self._on_volume_changed,
)
self.volume_scale.set_size_request(-1, 30)
self.volume_scale.add_style_class("flat-scale")
volume_box = Box(
orientation="h",
spacing=10,
children=[Label(label="Volume", h_align="start"), self.volume_scale],
)
main_box = Box(
orientation="v",
spacing=10,
name="pomodoro-container",
children=[
header_box,
primary_btn_box,
secondary_btn_box,
Gtk.Separator(),
settings_list,
Gtk.Separator(),
auto_start_box,
volume_box,
],
)
main_box.set_margin_left(20)
main_box.set_margin_right(20)
main_box.set_margin_bottom(20)
self.children = main_box
def _setting_row(self, label, widget, suffix):
suffix_label = Label(label=suffix) if suffix else None
widget_box = Box(orientation="h", spacing=5, children=[widget])
if suffix_label:
widget_box.add(suffix_label)
return Box(
orientation="h",
children=[Label(label=label, h_expand=True, h_align="start"), widget_box],
)
def _on_settings_changed(self, *_):
self.focus_mins = int(self.focus_spin.get_value())
self.short_break_mins = int(self.sbreak_spin.get_value())
self.long_break_mins = int(self.lbreak_spin.get_value())
self.max_cycles = int(self.cycles_spin.get_value())
if self.state == PomodoroState.STOPPED:
self.reset_timer()
self._update_display()
def _on_auto_start_toggled(self, active: bool):
self.auto_start = active
def _on_volume_changed(self, widget, value):
self.volume = value
def toggle_timer(self, *_):
if self.is_running:
self.pause_timer()
else:
self.start_timer()
def _switch_phase(self, phase):
self.pause_timer()
if phase == "Focus":
self.state = PomodoroState.FOCUS
self.total_time = self.focus_mins * 60
elif phase == "Short Break":
self.state = PomodoroState.SHORT_BREAK
self.total_time = self.short_break_mins * 60
elif phase == "Long Break":
self.state = PomodoroState.LONG_BREAK
self.total_time = self.long_break_mins * 60
self.time_left = self.total_time
self._update_display()
def start_timer(self):
if self.state == PomodoroState.STOPPED:
self.state = PomodoroState.FOCUS
self.total_time = self.focus_mins * 60
self.time_left = self.total_time
self.is_running = True
self.start_btn.set_label("Pause")
if self._timeout_id is None:
self._timeout_id = GLib.timeout_add_seconds(1, self._tick)
def pause_timer(self):
self.is_running = False
self.start_btn.set_label("Start")
if self._timeout_id is not None:
GLib.source_remove(self._timeout_id)
self._timeout_id = None
def reset_timer(self, *_):
self.pause_timer()
self.state = PomodoroState.STOPPED
self.current_cycle = 1
self.total_time = self.focus_mins * 60
self.time_left = self.total_time
self._update_display()
def restart_session(self, *_):
self.pause_timer()
if self.state == PomodoroState.FOCUS:
self.total_time = self.focus_mins * 60
elif self.state == PomodoroState.SHORT_BREAK:
self.total_time = self.short_break_mins * 60
elif self.state == PomodoroState.LONG_BREAK:
self.total_time = self.long_break_mins * 60
else:
self.total_time = self.focus_mins * 60
self.state = PomodoroState.FOCUS
self.time_left = self.total_time
self._update_display()
self.start_timer()
def skip_session(self, *_):
self.pause_timer()
self._finish_session()
def _tick(self):
if not self.is_running:
self._timeout_id = None
return False
if self.time_left > 0:
self.time_left -= 1
self._update_display()
return True
else:
self._timeout_id = None
self._finish_session()
return False
def _finish_session(self):
if self.state == PomodoroState.FOCUS:
if self.current_cycle >= self.max_cycles:
self.state = PomodoroState.LONG_BREAK
self.total_time = self.long_break_mins * 60
os.system(
'notify-send "Pomodoro" "Focus session complete! Time for a long break." &'
)
else:
self.state = PomodoroState.SHORT_BREAK
self.total_time = self.short_break_mins * 60
os.system(
'notify-send "Pomodoro" "Focus session complete! Time for a short break." &'
)
else:
if self.state == PomodoroState.LONG_BREAK:
self.current_cycle = 1
else:
self.current_cycle += 1
self.state = PomodoroState.FOCUS
self.total_time = self.focus_mins * 60
os.system('notify-send "Pomodoro" "Break over! Time to focus." &')
self.play_sound()
self.time_left = self.total_time
self._update_display()
if self.auto_start:
self.start_timer()
else:
self.is_running = False
self.start_btn.set_label("Start")
self._timeout_id = None
def play_sound(self):
sound_file = ""
base_dir = (
Path(__file__).parent.parent.parent.parent
/ "assets"
/ "sounds"
/ "pomodoro"
)
if self.state == PomodoroState.FOCUS:
sound_file = str(base_dir / "alert-work.mp3")
elif self.state == PomodoroState.SHORT_BREAK:
sound_file = str(base_dir / "alert-short-break.mp3")
elif self.state == PomodoroState.LONG_BREAK:
sound_file = str(base_dir / "alert-long-break.mp3")
if os.path.exists(sound_file):
vol = float(self.volume) / 100.0
os.system(f"pw-play --volume={vol} '{sound_file}' &")
def _update_display(self):
mins = self.time_left // 60
secs = self.time_left % 60
self.time_label.set_label(f"{mins:02d}:{secs:02d}")
self.cycle_label.set_label(f"Cycle {self.current_cycle} / {self.max_cycles}")
progress = self.time_left / self.total_time if self.total_time > 0 else 0
self.progress_bar.value = progress
self.progress_bar.queue_draw()
# Update phase button active states
active_name = {
PomodoroState.FOCUS: "Focus",
PomodoroState.SHORT_BREAK: "Short Break",
PomodoroState.LONG_BREAK: "Long Break",
}.get(self.state, "Focus")
for name, btn in self.phase_buttons.items():
if name == active_name:
btn.add_style_class("active")
else:
btn.remove_style_class("active")
# Update progress bar color
if self.state == PomodoroState.SHORT_BREAK:
self.progress_bar.set_name("pomodoro-progress-short")
elif self.state == PomodoroState.LONG_BREAK:
self.progress_bar.set_name("pomodoro-progress-long")
else:
self.progress_bar.set_name("pomodoro-progress")
+433
View File
@@ -0,0 +1,433 @@
from datetime import datetime
from fabric.utils import GLib
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.entry import Entry
from fabric.widgets.label import Label
from shared.window.animated_scrollwindow import AnimatedScrollable
from services.todo import get_todo_service
from shared.window.applet_window import AppletWindow
from utils.utils import svg_file
class TodoItem(Box):
"""Individual todo item widget"""
def __init__(self, todo_data, todo_list_widget, **kwargs):
self.todo_data = todo_data
self.todo_list_widget = todo_list_widget
self.editing = False
super().__init__(
name="todo-item",
orientation="h",
spacing=8,
**kwargs,
)
self._build_ui()
def _build_ui(self):
"""Build the todo item UI"""
# Checkbox for completion - using SVG icons
checkbox_icon = (
"checkbox-check.svg"
if self.todo_data["completed"]
else "checkbox-uncheck.svg"
)
self.checkbox_icon = svg_file(
"todo/" + checkbox_icon,
name="todo-checkbox-icon",
size=24,
)
self.checkbox = Button(
name="todo-checkbox",
child=self.checkbox_icon,
on_clicked=self._toggle_completion,
)
# Todo text (can be converted to entry for editing)
text_content = self.todo_data["text"]
if self.todo_data["completed"]:
text_content = f"<s>{text_content}</s>"
self.text_label = Label(
markup=text_content,
name="todo-text",
h_align="start",
h_expand=True,
line_wrap="word-char",
style_classes=(
["title-widget"]
if not self.todo_data["completed"]
else ["status-label"]
),
)
# Date/time label
created_at = datetime.fromisoformat(self.todo_data["created_at"])
date_text = created_at.strftime("%b %d, %Y at %I:%M %p")
self.date_label = Label(
label=date_text,
name="todo-date",
h_align="start",
style_classes=["todo-date-text"],
)
self.text_entry = Entry(
name="todo-text-entry",
text=self.todo_data["text"],
h_expand=True,
visible=False,
)
self.text_entry.connect("activate", self._save_edit)
# Priority indicator
# priority_symbols = {"high": "🔴", "medium": "🟡", "low": "🟢"}
#
# self.priority_label = Label(
# label=priority_symbols.get(self.todo_data["priority"], "🟡"),
# name="todo-priority-label",
# )
# self.priority_button = Button(
# name="todo-priority",
# size=(20, 20),
# child=self.priority_label,
# on_clicked=self._cycle_priority,
# )
#
# Edit button - using SVG icon
self.edit_icon = svg_file(
"todo/edit.svg",
name="todo-edit-icon",
size=12,
)
self.edit_button = Button(
name="todo-edit",
child=self.edit_icon,
on_clicked=self._start_edit,
)
# Delete button - using SVG icon
self.delete_icon = svg_file(
"todo/delete-symbolic.svg",
name="todo-delete-icon",
size=12,
)
self.delete_button = Button(
name="todo-delete",
child=self.delete_icon,
on_clicked=self._delete_todo,
)
# Text container that switches between label and entry
self.text_container = Box(
orientation="v",
h_expand=True,
children=[
Box(orientation="h", children=[self.text_label]),
self.date_label,
],
)
for child in [
self.checkbox,
self.text_container,
self.edit_button,
self.delete_button,
]:
self.add(child)
def _toggle_completion(self, *_):
"""Toggle todo completion status"""
get_todo_service().toggle_todo(self.todo_data["id"])
def _cycle_priority(self, *_):
"""Cycle through priority levels"""
priorities = ["low", "medium", "high"]
current_priority = self.todo_data.get("priority", "medium")
current_index = priorities.index(current_priority)
new_priority = priorities[(current_index + 1) % len(priorities)]
get_todo_service().set_priority(self.todo_data["id"], new_priority)
def _start_edit(self, *_):
"""Start editing the todo text"""
if self.editing:
return
self.editing = True
self.text_container.children = [
Box(orientation="h", children=[self.text_entry]),
self.date_label,
]
self.text_entry.set_visible(True)
self.text_entry.grab_focus()
self.text_entry.set_position(-1) # Move cursor to end
def _save_edit(self, *_):
"""Save the edited todo text"""
if not self.editing:
return
new_text = self.text_entry.get_text().strip()
if new_text:
get_todo_service().edit_todo(self.todo_data["id"], new_text)
self._cancel_edit()
def _cancel_edit(self):
"""Cancel editing and revert to label"""
self.editing = False
self.text_container.children = [
Box(orientation="h", children=[self.text_label]),
self.date_label,
]
self.text_entry.set_visible(False)
def _delete_todo(self, *_):
"""Delete this todo"""
get_todo_service().delete_todo(self.todo_data["id"])
def update_from_data(self, todo_data):
"""Update the widget from new todo data"""
self.todo_data = todo_data
# Update checkbox icon by recreating it
checkbox_icon = (
"checkbox-check.svg" if todo_data["completed"] else "checkbox-uncheck.svg"
)
new_checkbox_icon = svg_file(
"todo/" + checkbox_icon,
name="todo-checkbox-icon",
size=20,
)
old_child = self.checkbox.get_child()
if old_child:
self.checkbox.remove(old_child)
self.checkbox.add(new_checkbox_icon)
self.checkbox_icon = new_checkbox_icon
# Update text and styling with markup
text_content = todo_data["text"]
if todo_data["completed"]:
text_content = f"<s>{text_content}</s>"
self.text_label.set_markup(text_content)
self.text_label.style_classes = (
["title-widget"] if not todo_data["completed"] else ["status-label"]
)
# Update date/time
created_at = datetime.fromisoformat(todo_data["created_at"])
date_text = created_at.strftime("%b %d, %Y at %I:%M %p")
self.date_label.set_label(date_text)
class TodoListWidget(AppletWindow):
"""Main todo list window"""
def __init__(self, parent=None, pointing_to=None, **kwargs):
super().__init__(
parent=parent,
pointing_to=pointing_to,
layer="top",
title="modus-todo",
anchor="top right",
margin="2px 10px 0px 0px",
exclusivity="auto",
name="todo-list-menu",
visible=False,
**kwargs,
)
self.todo_items = {} # Maps todo IDs to TodoItem widgets
# Register callback with todo service
get_todo_service().add_callback(self._on_todo_event)
self._build_ui()
self._refresh_todos()
# Add keybinding for escape
self.add_keybinding("Escape", self.hide_todo_list)
def _build_ui(self):
"""Build the main UI"""
# Header with title and stats
self.stats_label = Label(
label="",
name="todo-stats",
style_classes=["status-label"],
h_align="start",
)
self.header = Box(
name="todo-header",
orientation="v",
children=[
Label(
label="Todo List",
name="todo-title",
style_classes=["title"],
h_align="start",
),
self.stats_label,
],
)
# Add new todo section
self.new_todo_entry = Entry(
name="new-todo-entry",
placeholder_text="Add a new task...",
h_expand=True,
)
self.new_todo_entry.connect("activate", self._add_todo)
# Add button - using SVG icon
self.add_icon = svg_file(
"todo/plus-symbolic.svg",
name="add-todo-icon",
size=12,
)
self.add_button = Button(
name="add-todo-button",
child=self.add_icon,
on_clicked=self._add_todo,
)
self.add_section = Box(
name="todo-add-section",
orientation="h",
spacing=8,
children=[
self.new_todo_entry,
self.add_button,
],
)
# Todo items container
self.todos_container = Box(
name="todos-container",
orientation="v",
spacing=4,
v_expand=True,
)
# Scrolled window for todos
self.scrolled = AnimatedScrollable(
name="todos-scrolled",
min_content_size=(400, 300),
max_content_size=(-1, 500),
child=self.todos_container,
h_scrollbar_policy="automatic",
v_scrollbar_policy="automatic",
propagate_height=False,
v_expand=True,
)
# Clear completed button
self.clear_button = Button(
name="clear-completed-button",
label="Clear Completed",
style_classes=["status-label"],
on_clicked=self._clear_completed,
)
# Main container
self.main_container = Box(
name="todo-main-container",
orientation="v",
spacing=8,
children=[
self.header,
self.add_section,
self.scrolled,
self.clear_button,
],
)
self.add(self.main_container)
def _add_todo(self, *_):
"""Add a new todo"""
text = self.new_todo_entry.get_text().strip()
if text:
get_todo_service().add_todo(text)
self.new_todo_entry.set_text("")
def _clear_completed(self, *_):
"""Clear all completed todos"""
get_todo_service().clear_completed()
def _on_todo_event(self, event_type, data=None):
"""Handle todo service events via callback"""
if event_type == "todos-changed":
GLib.idle_add(self._refresh_todos)
elif event_type == "todo-added":
GLib.idle_add(self._refresh_todos)
elif event_type == "todo-deleted":
GLib.idle_add(self._refresh_todos)
elif event_type in ["todo-toggled", "todo-edited", "todo-priority-changed"]:
if data and data["id"] in self.todo_items:
GLib.idle_add(
lambda: self.todo_items[data["id"]].update_from_data(data)
)
GLib.idle_add(self._update_stats)
def _refresh_todos(self, *_):
"""Refresh the entire todo list"""
# Clear existing items
self.todo_items.clear()
for child in self.todos_container.get_children():
self.todos_container.remove(child)
# Get all todos
todos = get_todo_service().todos
# Sort todos: incomplete first, then by priority, then by creation date
def sort_key(todo):
priority_order = {"high": 0, "medium": 1, "low": 2}
return (
todo["completed"], # False (incomplete) comes before True (completed)
priority_order.get(todo.get("priority", "medium"), 1),
todo["created_at"],
)
sorted_todos = sorted(todos, key=sort_key)
# Create todo item widgets and add them to the container
for todo in sorted_todos:
todo_item = TodoItem(todo, self)
self.todo_items[todo["id"]] = todo_item
self.todos_container.add(todo_item)
# Ensure they are visible
self.todos_container.show_all()
# Update stats
self._update_stats()
def _update_stats(self):
"""Update the statistics display"""
stats = get_todo_service().get_stats()
stats_text = f"{stats['pending']} pending, {stats['completed']} completed"
self.stats_label.set_label(stats_text)
def set_visible(self, visible):
"""Override set_visible for debugging"""
super().set_visible(visible)
def hide_todo_list(self, *_):
"""Hide the todo list"""
self.hide()
self.hide()
def destroy(self):
"""Clean up when destroyed"""
# Remove callback from todo service
get_todo_service().remove_callback(self._on_todo_event)
super().destroy()
+47 -3
View File
@@ -1,5 +1,5 @@
from fabric.system_tray.widgets import SystemTray
from fabric.utils import logger
from fabric.utils import Gdk, logger
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox
@@ -109,8 +109,16 @@ class Panel(Window):
name="panel-button",
child=DateTime(name="date-time", formatters=["%a %-d %b %I:%M %P"]),
)
self.datetime_btn.add_events(
Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK
)
setup_cursor_hover(self.datetime_btn, "pointer")
self._pomodoro_instance = None
self._todo_instance = None
self.datetime_btn.connect("clicked", self.on_datetime_clicked)
self.datetime_btn.connect("button-press-event", self.on_datetime_right_click)
self.custom_mods = CustomMods(parent_window=self)
self.workspace_indicator = WorkspaceIndicator()
@@ -178,6 +186,36 @@ class Panel(Window):
def on_notification_icon_clicked(self, *_):
self.notification_center.toggle()
def on_datetime_clicked(self, *_):
self.pomodoro.toggle()
def on_datetime_right_click(self, widget, event):
logger.info(f"[Panel] Right click detected: button={event.button}")
if event.button == 3: # Right click
self.todo_widget.toggle()
return True
return False
@property
def pomodoro(self):
if self._pomodoro_instance is None:
from window.panel.components.pomodoro import Pomodoro
self._pomodoro_instance = Pomodoro(
parent=self, pointing_to=self.datetime_btn
)
return self._pomodoro_instance
@property
def todo_widget(self):
if self._todo_instance is None:
from window.panel.components.todo import TodoListWidget
self._todo_instance = TodoListWidget(
parent=self, pointing_to=self.datetime_btn
)
return self._todo_instance
@property
def control_center(self):
if self._control_center_instance is None:
@@ -351,9 +389,15 @@ class Panel(Window):
logger.error(f"An error occurred: {e}")
# Destroy MouseCapture windows
for mc in [self.control_center, self.notification_center]:
for mc in [
self.control_center,
self.notification_center,
self._pomodoro_instance,
self._todo_instance,
]:
try:
mc.destroy()
if mc:
mc.destroy()
except Exception as e:
logger.error(f"An error occurred: {e}")