feat: enhance animations with macOS-like smoothness and easing functions
This commit is contained in:
1 parent
84d0f14526
commit
209f58e75d
2 files changed
+63
-24
No files matched your search
+56
-20
@@ -1,5 +1,6 @@
|
||||
from gi.repository import GLib, Gtk
|
||||
import gi
|
||||
import math
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
|
||||
@@ -21,7 +22,8 @@ class AnimationManager:
|
||||
def add_widget(self, widget):
|
||||
self._animating_widgets.add(widget)
|
||||
if self._timer_id is None:
|
||||
self._timer_id = GLib.timeout_add(16, self._animate_all) # 60 FPS
|
||||
# Use 120 FPS for ultra-smooth animations like macOS
|
||||
self._timer_id = GLib.timeout_add(8, self._animate_all) # 120 FPS
|
||||
|
||||
def remove_widget(self, widget):
|
||||
self._animating_widgets.discard(widget)
|
||||
@@ -60,8 +62,8 @@ class AnimationManager:
|
||||
return len(self._animating_widgets) > 0 # Continue if widgets remain
|
||||
|
||||
def _get_optimal_interval(self):
|
||||
"""Keep consistent 60 FPS regardless of widget count"""
|
||||
return 16 # 60 FPS
|
||||
"""Keep consistent 120 FPS for macOS-like smoothness"""
|
||||
return 8 # 120 FPS
|
||||
|
||||
def _start_timer(self):
|
||||
interval = self._get_optimal_interval()
|
||||
@@ -72,19 +74,47 @@ class AnimationManager:
|
||||
pass
|
||||
|
||||
|
||||
class MacOSEasing:
|
||||
"""macOS-style easing functions for natural motion"""
|
||||
|
||||
@staticmethod
|
||||
def ease_out_expo(t):
|
||||
"""Exponential ease out - fast start, slow end"""
|
||||
return 1 - math.pow(2, -10 * t) if t != 1 else 1
|
||||
|
||||
@staticmethod
|
||||
def ease_in_out_quart(t):
|
||||
"""Quartic ease in-out for smooth acceleration/deceleration"""
|
||||
return 8 * t * t * t * t if t < 0.5 else 1 - math.pow(-2 * t + 2, 4) / 2
|
||||
|
||||
@staticmethod
|
||||
def ease_out_back(t):
|
||||
"""Back ease out for slight overshoot effect"""
|
||||
c1 = 1.70158
|
||||
c3 = c1 + 1
|
||||
return 1 + c3 * math.pow(t - 1, 3) + c1 * math.pow(t - 1, 2)
|
||||
|
||||
@staticmethod
|
||||
def ease_out_cubic_bezier(t):
|
||||
"""Custom cubic bezier similar to macOS default (0.25, 0.1, 0.25, 1.0)"""
|
||||
# Approximation of cubic-bezier(0.25, 0.1, 0.25, 1.0)
|
||||
return t * t * t * (t * (6 * t - 15) + 10)
|
||||
|
||||
|
||||
class SlideRevealer(Gtk.Overlay):
|
||||
def __init__(self, child: Gtk.Widget, direction="right", duration=400, size=None):
|
||||
def __init__(self, child: Gtk.Widget, direction="right", duration=350, size=None):
|
||||
super().__init__()
|
||||
|
||||
self.child = child
|
||||
self.direction = direction
|
||||
self.duration = duration # Reduced default duration for snappier feel
|
||||
self.duration = duration # Slightly faster for snappier feel
|
||||
self.fixed_size = size
|
||||
self._revealed = False
|
||||
self._animating = False
|
||||
self._start_time = None
|
||||
self._show_animation = False
|
||||
self._pending_position = None
|
||||
self._current_position = (0.0, 0.0) # Use float for sub-pixel positioning
|
||||
|
||||
self._fixed = Gtk.Fixed()
|
||||
self._fixed.set_has_window(False)
|
||||
@@ -142,7 +172,9 @@ class SlideRevealer(Gtk.Overlay):
|
||||
|
||||
if show:
|
||||
self.child.show()
|
||||
self._fixed.move(self.child, *self._get_offscreen_pos_cached())
|
||||
pos = self._get_offscreen_pos_cached()
|
||||
self._current_position = (float(pos[0]), float(pos[1]))
|
||||
self._fixed.move(self.child, int(pos[0]), int(pos[1]))
|
||||
|
||||
AnimationManager.get_instance().add_widget(self)
|
||||
|
||||
@@ -154,11 +186,11 @@ class SlideRevealer(Gtk.Overlay):
|
||||
t = min(elapsed / self.duration, 1.0)
|
||||
|
||||
if self._show_animation:
|
||||
# Smoother ease out cubic for revealing
|
||||
eased = 1 - (1 - t) ** 3
|
||||
# Use macOS-style ease out for revealing (fast start, smooth end)
|
||||
eased = MacOSEasing.ease_out_cubic_bezier(t)
|
||||
else:
|
||||
# Smoother ease in cubic for hiding
|
||||
eased = t ** 3
|
||||
# Use ease in-out for hiding (smooth both ends)
|
||||
eased = MacOSEasing.ease_in_out_quart(t)
|
||||
|
||||
self._pending_position = self._get_position_at_progress_cached(eased)
|
||||
|
||||
@@ -173,7 +205,11 @@ class SlideRevealer(Gtk.Overlay):
|
||||
def _apply_position(self):
|
||||
if self._pending_position:
|
||||
x, y = self._pending_position
|
||||
self._fixed.move(self.child, x, y)
|
||||
# Use sub-pixel positioning for smoother motion
|
||||
self._current_position = (x, y)
|
||||
# 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._pending_position = None
|
||||
|
||||
def _get_container_for_redraw(self):
|
||||
@@ -203,24 +239,24 @@ class SlideRevealer(Gtk.Overlay):
|
||||
if self._show_animation:
|
||||
# Showing animation: slide from offscreen to onscreen (0,0)
|
||||
if self.direction == "left":
|
||||
return int(-w + w * progress), 0
|
||||
return -w + w * progress, 0.0
|
||||
elif self.direction == "right":
|
||||
return int(w - w * progress), 0
|
||||
return w - w * progress, 0.0
|
||||
elif self.direction == "top":
|
||||
return 0, int(-h + h * progress)
|
||||
return 0.0, -h + h * progress
|
||||
elif self.direction == "bottom":
|
||||
return 0, int(h - h * progress)
|
||||
return 0.0, h - h * progress
|
||||
else:
|
||||
# Hiding animation: slide from onscreen (0,0) to offscreen
|
||||
if self.direction == "left":
|
||||
return int(-w * progress), 0 # Slide left (negative x)
|
||||
return -w * progress, 0.0 # Slide left (negative x)
|
||||
elif self.direction == "right":
|
||||
return int(w * progress), 0 # Slide right (positive x)
|
||||
return w * progress, 0.0 # Slide right (positive x)
|
||||
elif self.direction == "top":
|
||||
return 0, int(-h * progress) # Slide up (negative y)
|
||||
return 0.0, -h * progress # Slide up (negative y)
|
||||
elif self.direction == "bottom":
|
||||
return 0, int(h * progress) # Slide down (positive y)
|
||||
return 0, 0
|
||||
return 0.0, h * progress # Slide down (positive y)
|
||||
return 0.0, 0.0
|
||||
|
||||
def set_slide_direction(self, direction):
|
||||
self.direction = direction
|
||||
|
||||
Reference in new issue
Block a user