feat(root): implement dynamic todo list refresh and UI enhancements
This change adds a mechanism to refresh the todo list dynamically when the underlying service updates, improving UI responsiveness and user experience. It also updates documentation and removes deprecated files. - New Features: - Added `_refresh_todos` method to rebuild the todo list when `get_todo_service().todos` changes. - Introduced `TodoListCapture` class for mouse capture handling of the todo list window. - Enhancements: - Updated README with corrected image paths, installation tips, and manual installation commands. - Introduced `start.py` to launch the application or lock mode. - Removed deprecated `config.json` and `debug_memory.py`. - Bug Fixes: - Fixed lock screen screenshot path in README.
This commit is contained in:
267 files changed
+24582
-394
No files matched your search
@@ -0,0 +1,192 @@
|
||||
import contextlib
|
||||
|
||||
from fabric.utils import Gdk, Gtk
|
||||
from fabric.widgets.wayland import WaylandWindow
|
||||
from gi.repository import GtkLayerShell
|
||||
|
||||
from utils.monitors import HyprlandWithMonitors
|
||||
|
||||
|
||||
class PopupWindow(WaylandWindow):
|
||||
"""A simple popover window that can point to a widget."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent: WaylandWindow,
|
||||
pointing_to: Gtk.Widget | None = None,
|
||||
margin: tuple[int, ...] | str = "0px 0px 0px 0px",
|
||||
enable_boundary_checking: bool = True,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self.exclusivity = "none"
|
||||
self._is_centered = False
|
||||
self._parent = parent
|
||||
self._pointing_widget = pointing_to
|
||||
self._hyprland = HyprlandWithMonitors()
|
||||
self._base_margin = self.extract_margin(margin)
|
||||
self.margin = self._base_margin.values()
|
||||
self._enable_boundary_checking = enable_boundary_checking
|
||||
|
||||
self.connect("notify::visible", self.do_update_handlers)
|
||||
|
||||
def get_coords_for_widget(self, widget: Gtk.Widget) -> tuple[int, int]:
|
||||
if not ((toplevel := widget.get_toplevel()) and toplevel.is_toplevel()): # type: ignore
|
||||
return 0, 0
|
||||
allocation = widget.get_allocation()
|
||||
x, y = widget.translate_coordinates(toplevel, allocation.x, allocation.y) or (
|
||||
0,
|
||||
0,
|
||||
)
|
||||
return round(x / 2), round(y / 2)
|
||||
|
||||
def set_pointing_to(self, widget: Gtk.Widget | None):
|
||||
if self._pointing_widget:
|
||||
with contextlib.suppress(Exception):
|
||||
self._pointing_widget.disconnect_by_func(self.do_handle_size_allocate)
|
||||
self._pointing_widget = widget
|
||||
return self.do_update_handlers()
|
||||
|
||||
def do_update_handlers(self, *_):
|
||||
if not self._pointing_widget:
|
||||
return
|
||||
|
||||
if not self.get_visible():
|
||||
try:
|
||||
self._pointing_widget.disconnect_by_func(self.do_handle_size_allocate)
|
||||
self.disconnect_by_func(self.do_handle_size_allocate)
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
self._pointing_widget.connect("size-allocate", self.do_handle_size_allocate)
|
||||
self.connect("size-allocate", self.do_handle_size_allocate)
|
||||
|
||||
return self.do_handle_size_allocate()
|
||||
|
||||
def do_handle_size_allocate(self, *_):
|
||||
return self.do_reposition(self.do_calculate_edges())
|
||||
|
||||
def do_calculate_edges(self):
|
||||
move_axe = "x"
|
||||
parent_anchor = self._parent.anchor
|
||||
|
||||
if len(parent_anchor) != 3:
|
||||
self.anchor = "left bottom"
|
||||
self._is_centered = True
|
||||
return move_axe
|
||||
|
||||
if (
|
||||
GtkLayerShell.Edge.LEFT in parent_anchor
|
||||
and GtkLayerShell.Edge.RIGHT in parent_anchor
|
||||
):
|
||||
# horizontal -> move on x-axies
|
||||
move_axe = "x"
|
||||
if GtkLayerShell.Edge.TOP in parent_anchor:
|
||||
self.anchor = "left top"
|
||||
else:
|
||||
self.anchor = "left bottom"
|
||||
elif (
|
||||
GtkLayerShell.Edge.TOP in parent_anchor
|
||||
and GtkLayerShell.Edge.BOTTOM in parent_anchor
|
||||
):
|
||||
# vertical -> move on y-axies
|
||||
move_axe = "y"
|
||||
if GtkLayerShell.Edge.RIGHT in parent_anchor:
|
||||
self.anchor = "top right"
|
||||
else:
|
||||
self.anchor = "top left"
|
||||
|
||||
self._is_centered = False
|
||||
return move_axe
|
||||
|
||||
def do_reposition(self, move_axe: str):
|
||||
parent_margin = self._parent.margin
|
||||
parent_x_margin, parent_y_margin = parent_margin[0], parent_margin[3]
|
||||
|
||||
height = self.get_allocated_height()
|
||||
width = self.get_allocated_width()
|
||||
|
||||
# Get monitor geometry for boundary checking
|
||||
current_monitor_id = self._hyprland.get_current_gdk_monitor_id()
|
||||
if current_monitor_id is not None:
|
||||
monitor = self._hyprland.display.get_monitor(current_monitor_id)
|
||||
monitor_geometry = monitor.get_geometry()
|
||||
monitor_x, monitor_y = monitor_geometry.x, monitor_geometry.y
|
||||
monitor_width, monitor_height = (
|
||||
monitor_geometry.width,
|
||||
monitor_geometry.height,
|
||||
)
|
||||
else:
|
||||
# Fallback to default screen
|
||||
screen = Gdk.Screen.get_default()
|
||||
monitor_x, monitor_y = 0, 0
|
||||
monitor_width, monitor_height = screen.get_width(), screen.get_height()
|
||||
|
||||
if self._pointing_widget:
|
||||
coords = self.get_coords_for_widget(self._pointing_widget)
|
||||
coords_centered = (
|
||||
round(coords[0] + self._pointing_widget.get_allocated_width() / 2),
|
||||
round(coords[1] + self._pointing_widget.get_allocated_height() / 2),
|
||||
)
|
||||
else:
|
||||
coords_centered = (
|
||||
round(self._parent.get_allocated_width() / 2),
|
||||
round(self._parent.get_allocated_height() / 2),
|
||||
)
|
||||
|
||||
if self._is_centered:
|
||||
# Calculate centered position with boundary checking
|
||||
centered_x = (
|
||||
(monitor_width / 2 - self._parent.get_allocated_width() / 2) - width / 2
|
||||
) + coords_centered[0]
|
||||
|
||||
# Apply boundary checking only if enabled
|
||||
if self._enable_boundary_checking:
|
||||
if centered_x < monitor_x:
|
||||
centered_x = monitor_x
|
||||
elif centered_x + width > monitor_x + monitor_width:
|
||||
centered_x = monitor_x + monitor_width - width
|
||||
|
||||
self.margin = tuple(
|
||||
a + b
|
||||
for a, b in zip(
|
||||
(0, 0, 0, centered_x),
|
||||
self._base_margin.values(),
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
# Calculate position with boundary checking
|
||||
if move_axe == "x":
|
||||
# Horizontal positioning
|
||||
calculated_x = round((parent_x_margin + coords_centered[0]) - (width / 2))
|
||||
|
||||
# Apply boundary checking only if enabled
|
||||
if self._enable_boundary_checking:
|
||||
if calculated_x < monitor_x:
|
||||
calculated_x = monitor_x
|
||||
elif calculated_x + width > monitor_x + monitor_width:
|
||||
calculated_x = monitor_x + monitor_width - width
|
||||
|
||||
margin_values = (0, 0, 0, calculated_x)
|
||||
else:
|
||||
# Vertical positioning
|
||||
calculated_y = round((parent_y_margin + coords_centered[1]) - (height / 2))
|
||||
|
||||
# Apply boundary checking only if enabled
|
||||
if self._enable_boundary_checking:
|
||||
if calculated_y < monitor_y:
|
||||
calculated_y = monitor_y
|
||||
elif calculated_y + height > monitor_y + monitor_height:
|
||||
calculated_y = monitor_y + monitor_height - height
|
||||
|
||||
margin_values = (calculated_y, 0, 0, 0)
|
||||
|
||||
self.margin = tuple(
|
||||
a + b
|
||||
for a, b in zip(
|
||||
margin_values,
|
||||
self._base_margin.values(),
|
||||
)
|
||||
)
|
||||
Reference in new issue
Block a user