♻️ refactor(osd, switcher): use Animator for smooth value transitions in OSD scale and switcher window animation

This commit is contained in:
sankalp committed 2025-06-22 18:52:39 +05:45
1 parent 8b4d829681
commit 9c81dd69a4
5 files changed
+37 -65

No files matched your search

-3
View File
@@ -1,8 +1,5 @@
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.shapes import Corner
from utils.wayland import WaylandWindow as Window
+5 -17
View File
@@ -1,15 +1,15 @@
import json
import config.data as data
from fabric.hyprland.widgets import get_hyprland_connection
from fabric.widgets.box import Box
from fabric.widgets.eventbox import EventBox
from fabric.widgets.revealer import Revealer
from gi.repository import Gdk, GLib, Gtk
from modules.corners import MyCorner
from modules.dock.components import DockComponents
from utils.occlusion import check_occlusion
from utils.wayland import WaylandWindow as Window
from modules.dock.components import DockComponents
class Dock(Window):
@@ -36,7 +36,6 @@ class Dock(Window):
main_box_h_align_val = "center"
dock_wrapper_orientation_val = Gtk.Orientation.HORIZONTAL
else:
# Use DOCK_POSITION directly instead of BAR_POSITION
if data.DOCK_POSITION == "Left":
anchor_to_set = "left"
revealer_transition_type = "slide-right"
@@ -55,7 +54,7 @@ class Dock(Window):
name="dock-window",
layer="top",
anchor=anchor_to_set,
margin="0px 0px 0px 0px", # Set all margins to 0
margin="0px 0px 0px 0px",
exclusivity="auto" if not data.DOCK_AUTO_HIDE else "none",
**kwargs,
)
@@ -104,13 +103,11 @@ class Dock(Window):
self.dock_eventbox.connect("enter-notify-event", self._on_dock_enter)
self.dock_eventbox.connect("leave-notify-event", self._on_dock_leave)
# Create components using DockComponents
self.components = DockComponents(
orientation_val="h" if not data.VERTICAL else "v", dock_instance=self
)
# Add components based on position
if self.actual_dock_is_horizontal: # Bottom dock
if self.actual_dock_is_horizontal:
self.view.add(self.components)
elif data.DOCK_POSITION == "Left":
self.view.add(self.components)
@@ -184,7 +181,7 @@ class Dock(Window):
orientation=Gtk.Orientation.VERTICAL,
v_expand=True,
v_align="fill",
margin=0, # Add explicit margin=0
margin=0,
children=[self.corner_top, self.dock_eventbox, self.corner_bottom],
)
@@ -198,7 +195,6 @@ class Dock(Window):
self.hover_activator = EventBox()
# Adjust hover activator size based on position and dock position
if self.actual_dock_is_horizontal:
# Bottom dock
self.hover_activator.set_size_request(-1, 1)
@@ -209,7 +205,6 @@ class Dock(Window):
self.hover_activator.connect("enter-notify-event", self._on_hover_enter)
self.hover_activator.connect("leave-notify-event", self._on_hover_leave)
# Create main box with correct child order based on position
if self.actual_dock_is_horizontal:
# Bottom dock
self.main_box = Box(
@@ -455,27 +450,20 @@ class Dock(Window):
dock.dock_revealer.set_reveal_child(False)
def on_app_drag_begin(self):
"""Called when application drag begins"""
self._drag_in_progress = True
# Ensure dock is visible during drag
self.dock_revealer.set_reveal_child(True)
if not self.always_occluded:
self.dock_full.remove_style_class("occluded")
def on_app_drag_end(self):
"""Called when application drag ends"""
self._drag_in_progress = False
# Check if we should hide the dock
GLib.idle_add(self.check_occlusion_state)
def prevent_hiding(self, prevent=True):
"""Prevent the dock from hiding"""
self._prevent_occlusion = prevent
if prevent:
# Force dock to be visible
self.dock_revealer.set_reveal_child(True)
if not self.always_occluded:
self.dock_full.remove_style_class("occluded")
else:
# Check if we should hide
GLib.idle_add(self.check_occlusion_state)
+31 -29
View File
@@ -9,16 +9,16 @@ from fabric.notifications import (
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.circularprogressbar import CircularProgressBar
from fabric.widgets.image import Image
from fabric.widgets.label import Label
from fabric.widgets.revealer import Revealer
from fabric.widgets.circularprogressbar import CircularProgressBar
from gi.repository import Gdk, GdkPixbuf, GLib
from loguru import logger
from services import notification_service
from utils.animator import Animator
from utils.custom_image import CustomImage
from utils.wayland import WaylandWindow as Window
from utils.animator import Animator
class ActionButton(Button):
@@ -80,7 +80,6 @@ class NotificationWidget(Box):
self.timeout_ms = timeout_ms
self._timeout_id = None
self.close_button_animator = Animator(
bezier_curve=(0.25, 0.1, 0.25, 1.0),
duration=0.3,
@@ -88,7 +87,9 @@ class NotificationWidget(Box):
max_value=1.0,
repeat=False,
)
self.close_button_animator.connect("notify::value", self.on_hover_animation_value_changed)
self.close_button_animator.connect(
"notify::value", self.on_hover_animation_value_changed
)
self.timeout_progress_animator = Animator(
bezier_curve=(1.0, 0.0, 1.0, 1.0),
@@ -97,7 +98,9 @@ class NotificationWidget(Box):
max_value=1.0,
repeat=False,
)
self.timeout_progress_animator.connect("notify::value", self.on_timeout_progress_changed)
self.timeout_progress_animator.connect(
"notify::value", self.on_timeout_progress_changed
)
self.timeout_progress_animator.connect("finished", self.on_timeout_finished)
self.start_timeout()
@@ -223,10 +226,7 @@ class NotificationWidget(Box):
)
def create_close_button(self):
close_icon = Label(
name="notif-close-label",
markup=icons.cancel
)
close_icon = Label(name="notif-close-label", markup=icons.cancel)
self.close_progress_bar = CircularProgressBar(
name="notif-close-progress",
@@ -270,17 +270,14 @@ class NotificationWidget(Box):
# Only show chevron button if there's something to expand
# (either long text that gets ellipsized or action buttons)
has_expandable_content = (
(self.notification.body and len(self.notification.body) > 50) or # Long text
bool(self.notification.actions) # Has action buttons
(self.notification.body and len(self.notification.body) > 50) # Long text
or bool(self.notification.actions) # Has action buttons
)
if not has_expandable_content:
return Box() # Return empty box if nothing to expand
self.chevron_icon = Label(
name="notif-chevron-label",
markup=icons.chevron_down
)
self.chevron_icon = Label(name="notif-chevron-label", markup=icons.chevron_down)
chevron_button = Button(
name="notif-chevron-button",
@@ -302,9 +299,7 @@ class NotificationWidget(Box):
name="notification-expanded-content",
orientation="v",
spacing=8,
children=[
self.create_action_buttons(notification)
],
children=[self.create_action_buttons(notification)],
)
else:
return Box()
@@ -320,10 +315,11 @@ class NotificationWidget(Box):
self.chevron_icon.set_markup(icons.chevron_down)
# Toggle body label properties
if hasattr(self, 'body_label') and self.body_label:
if hasattr(self, "body_label") and self.body_label:
if self.is_expanded:
# Show full text without ellipsization and enable wrapping
self.body_label.set_markup(self.notification.body) # Keep original newlines
# Keep original newlines
self.body_label.set_markup(self.notification.body)
self.body_label.set_property("ellipsize", 0)
self.body_label.set_property("wrap", True)
self.body_label.set_property("wrap-mode", 2)
@@ -339,7 +335,7 @@ class NotificationWidget(Box):
pass
def on_timeout_progress_changed(self, animator, value):
if hasattr(self, 'close_progress_bar'):
if hasattr(self, "close_progress_bar"):
# Update progress bar value based on timeout progress (convert to float to avoid type issues)
self.close_progress_bar.value = float(animator.value)
@@ -349,7 +345,7 @@ class NotificationWidget(Box):
def start_timeout(self):
self.stop_timeout()
if hasattr(self, 'timeout_progress_animator'):
if hasattr(self, "timeout_progress_animator"):
self.timeout_progress_animator.play()
self._timeout_id = GLib.timeout_add(self.timeout_ms, self.close_notification)
@@ -357,7 +353,7 @@ class NotificationWidget(Box):
if self._timeout_id is not None:
GLib.source_remove(self._timeout_id)
self._timeout_id = None
if hasattr(self, 'timeout_progress_animator'):
if hasattr(self, "timeout_progress_animator"):
self.timeout_progress_animator.stop()
def close_notification(self):
@@ -373,9 +369,9 @@ class NotificationWidget(Box):
def destroy(self):
self.stop_timeout()
if hasattr(self, 'close_button_animator'):
if hasattr(self, "close_button_animator"):
self.close_button_animator.stop()
if hasattr(self, 'timeout_progress_animator'):
if hasattr(self, "timeout_progress_animator"):
self.timeout_progress_animator.stop()
super().destroy()
@@ -391,7 +387,7 @@ class NotificationWidget(Box):
self.set_pointer_cursor(button, "hand2")
# On hover, show full progress (indicating ready to close)
if hasattr(self, 'close_progress_bar'):
if hasattr(self, "close_progress_bar"):
self.close_progress_bar.value = 1.0
def unhover_button(self, button):
@@ -399,7 +395,9 @@ class NotificationWidget(Box):
self.set_pointer_cursor(button, "arrow")
# On unhover, resume showing timeout progress
if hasattr(self, 'timeout_progress_animator') and hasattr(self, 'close_progress_bar'):
if hasattr(self, "timeout_progress_animator") and hasattr(
self, "close_progress_bar"
):
# Resume the timeout progress display
current_progress = float(self.timeout_progress_animator.value)
self.close_progress_bar.value = current_progress
@@ -432,7 +430,11 @@ class NotificationRevealer(Revealer):
):
# Cache the notification to history when it's actually closed/dismissed
self.cache_service.cache_notification(notification)
logger.info(f"[Notification] Cached notification from {notification.app_name} (reason: {reason})")
logger.info(
f"[Notification] Cached notification from {notification.app_name} (reason: {
reason
})"
)
self.set_reveal_child(False)
@@ -465,7 +467,7 @@ class NotificationPopup(Window):
if not self.cache_notification_service.dont_disturb:
new_box = NotificationRevealer(
fabric_notif.get_notification_from_id(id),
self.cache_notification_service
self.cache_notification_service,
)
self.notifications.add(new_box)
new_box.set_reveal_child(True)
+1 -2
View File
@@ -16,7 +16,7 @@ from utils.wayland import WaylandWindow as Window
class AnimatedScale(Scale):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.animator = None # Lazily initialized
self.animator = None
def animate_value(self, value: float):
if not self.animator:
@@ -172,7 +172,6 @@ class AudioOSDContainer(Box):
self.scale.set_value(volume)
self.update_icon(volume)
# Apply muted style based on volume and mute state
if volume == 0 or (self.audio.speaker and self.audio.speaker.muted):
self.scale.add_style_class("muted")
self.icon.add_style_class("muted")
-14
View File
@@ -13,11 +13,6 @@ from utils.wayland import WaylandWindow as Window
class ApplicationSwitcher(Window):
"""
A application switcher implementation for Hyprland that provides a grid-based application switching interface.
Supports keyboard navigation, mouse interaction, and application activation.
"""
def __init__(self, **kwargs):
super().__init__(
name="application-switcher",
@@ -36,7 +31,6 @@ class ApplicationSwitcher(Window):
self.items_per_row = data.WINDOW_SWITCHER_ITEMS_PER_ROW
self.icon_size = 64
# Create a container box that expands
container = Box(
name="application-switcher-container",
orientation="v",
@@ -73,7 +67,6 @@ class ApplicationSwitcher(Window):
self.ungrab_keyboard()
def update_windows(self) -> None:
# Clear existing windows
for child in self.view.get_children():
self.view.remove(child)
@@ -89,7 +82,6 @@ class ApplicationSwitcher(Window):
json.loads(active_data.decode("utf-8")) if active_data else None
)
# Set current index to active window
self.current_index = 0
if active_window:
for i, window in enumerate(self.windows):
@@ -97,7 +89,6 @@ class ApplicationSwitcher(Window):
self.current_index = i
break
# Create window grid
current_row = Box(
name="window-row",
orientation="h",
@@ -111,7 +102,6 @@ class ApplicationSwitcher(Window):
class_name = window.get("class", "").lower()
title = window.get("title", "")
# Get icon
icon_img = self.icon_resolver.get_icon_pixbuf(
class_name, self.icon_size
)
@@ -145,7 +135,6 @@ class ApplicationSwitcher(Window):
)
current_row.add(event_box)
# Create new row if needed
if (i + 1) % self.items_per_row == 0 and i + 1 < len(self.windows):
current_row = Box(
name="window-row",
@@ -192,7 +181,6 @@ class ApplicationSwitcher(Window):
self.hide_switcher()
return True
# Arrow keys
if keyval == Gdk.KEY_Right:
self.current_index = (self.current_index + 1) % len(self.windows)
self.update_selection()
@@ -256,7 +244,6 @@ class ApplicationSwitcher(Window):
print(f"Failed to focus window: {e}")
def grab_keyboard(self):
"""Grab keyboard focus."""
try:
display = Gdk.Display.get_default()
seat = display.get_default_seat()
@@ -266,7 +253,6 @@ class ApplicationSwitcher(Window):
print(f"Failed to grab keyboard: {e}")
def ungrab_keyboard(self):
"""Release keyboard focus."""
try:
display = Gdk.Display.get_default()
seat = display.get_default_seat()