Files
Modus/src/shared/widgets/customrevealer.py
T
sankalp e4813f35a0 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.
2026-04-10 17:11:32 +05:45

189 lines
5.8 KiB
Python

from fabric.utils import Gtk, math
from shared.widgets.animator import Animator
# macOS-style easing functions for natural motion
def ease_out_quint(progress):
return 1 - math.pow(1 - progress, 5)
def ease_in_cubic(progress):
return progress * progress * progress
class SlideRevealer(Gtk.Overlay):
def __init__(self, child: Gtk.Widget, direction="right", duration=350, size=None):
super().__init__()
self.child = child
self.direction = direction
self.duration = duration
self.fixed_size = size
self._revealed = False
self._cached_dimensions = None
self.animator = Animator(
duration=duration / 1000.0,
timing_function=ease_out_quint,
tick_interval=8, # 120 FPS for smoothness
tick_widget=self,
)
self.animator.connect("notify::value", self._on_animator_value_changed)
self.animator.connect("finished", self._on_animator_finished)
self._fixed = Gtk.Fixed()
self._fixed.set_has_window(False)
self._fixed.add(child)
self.add_overlay(self._fixed)
if self.fixed_size:
self.set_size_request(self.fixed_size[0], self.fixed_size[1])
child.hide()
self.show_all()
else:
child.connect("size-allocate", self._on_size_allocate)
child.hide()
self.show_all()
def _on_size_allocate(self, _widget, allocation):
if not self.fixed_size:
current_req = self.get_size_request()
if (
current_req[0] != allocation.width
or current_req[1] != allocation.height
):
self.set_size_request(allocation.width, allocation.height)
def set_reveal_child(self, reveal: bool):
if reveal:
self.reveal()
else:
self.hide()
def reveal(self):
if self._revealed and not self.animator.playing:
return
self._revealed = True
if self.get_realized():
self._start_animation(show=True)
else:
def on_realize(*_):
self._start_animation(show=True)
self.disconnect_by_func(on_realize)
self.connect("realize", on_realize)
def hide(self):
if not self._revealed and not self.animator.playing:
return
self._revealed = False
self._start_animation(show=False)
def _start_animation(self, show: bool):
self.animator.stop()
self._cached_dimensions = self._get_dimensions()
if self._cached_dimensions[0] == 0 or self._cached_dimensions[1] == 0:
return
self._show_animation = show
# Configure animator based on direction
if show:
self.child.show()
self.animator.timing_function = ease_out_quint
# We animate the 'progress' from 0 to 1
self.animator.min_value = 0.0
self.animator.max_value = 1.0
else:
self.animator.timing_function = ease_in_cubic
self.animator.min_value = 0.0
self.animator.max_value = 1.0
self.animator.play()
def _on_animator_value_changed(self, animator, _pspec):
if not self._cached_dimensions:
return
progress = animator.value
x, y = self._get_position_at_progress_cached(progress)
# Round to nearest pixel for actual positioning
pixel_x, pixel_y = int(round(x)), int(round(y))
self._fixed.move(self.child, pixel_x, pixel_y)
self.queue_draw()
def _on_animator_finished(self, _animator):
self._cached_dimensions = None
if not self._revealed:
self.child.hide()
def _get_container_for_redraw(self):
return self
def _get_dimensions(self):
if self.fixed_size:
return self.fixed_size
else:
alloc = self.child.get_allocation()
return alloc.width, alloc.height
def _get_offscreen_pos_cached(self):
w, h = self._cached_dimensions
if self.direction == "left":
return -w, 0
elif self.direction == "right":
return w, 0
elif self.direction == "top":
return 0, -h
elif self.direction == "bottom":
return 0, h
return 0, 0
def _get_position_at_progress_cached(self, progress):
w, h = self._cached_dimensions
if self._show_animation:
# Showing animation: slide from offscreen to onscreen (0,0)
if self.direction == "left":
return -w + w * progress, 0.0
elif self.direction == "right":
return w - w * progress, 0.0
elif self.direction == "top":
return 0.0, -h + h * progress
elif self.direction == "bottom":
return 0.0, h - h * progress
else:
# Hiding animation: slide from onscreen (0,0) to offscreen
if self.direction == "left":
return -w * progress, 0.0 # Slide left (negative x)
elif self.direction == "right":
return w * progress, 0.0 # Slide right (positive x)
elif self.direction == "top":
return 0.0, -h * progress # Slide up (negative y)
elif self.direction == "bottom":
return 0.0, h * progress # Slide down (positive y)
return 0.0, 0.0
def set_slide_direction(self, direction):
self.direction = direction
def is_revealed(self):
return self._revealed
def is_animating(self):
return self.animator.playing
def get_child_revealed(self):
return self._revealed
def stop_animation(self):
self.animator.stop()
def destroy(self):
self.stop_animation()
super().destroy()