♻️ refactor(osd, switcher): use Animator for smooth value transitions in OSD scale and switcher window animation
This commit is contained in:
5 files changed
+37
-65
No files matched your search
@@ -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
@@ -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)
|
||||
@@ -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
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in new issue
Block a user