Add animation effects for smooth user experience
Implemented comprehensive animation effects throughout the extension: 1. Zone Overlay Animations: - Fade-in animation (200ms) when showing zones - Fade-out animation (150ms) when hiding zones - Uses Clutter.AnimationMode.EASE_OUT_QUAD for smooth easing 2. Window Snapping Animations: - Smooth animated window movement (250ms) when snapping to zones - Animates window actor position and size - Falls back to instant move if actor not available - Final position set precisely after animation completes 3. Visual Snap Feedback: - Green flash effect when window snaps to zone - Flash shows the exact zone boundaries - Fades out over 400ms - Works for both Shift+drag and keyboard snapping Implementation details: - Used Clutter's ease() method for all animations - Overlay fade animations prevent jarring appearance/disappearance - Window animations use compositor actor for smooth transitions - Flash feedback uses temporary widget with auto-cleanup - All animations use EASE_OUT_QUAD for natural feel User experience improvements: - Zones appear and disappear smoothly instead of instantly - Windows glide into position rather than jumping - Clear visual confirmation when snap occurs - Professional, polished feel throughout Fixes TODO item #11. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
8
TODO.md
8
TODO.md
@@ -111,11 +111,11 @@
|
|||||||
- [ ] 1/3 - 2/3 layouts
|
- [ ] 1/3 - 2/3 layouts
|
||||||
- [ ] Picture-in-picture style layouts
|
- [ ] Picture-in-picture style layouts
|
||||||
|
|
||||||
### 11. Animation Effects
|
### 11. Animation Effects ✅ COMPLETED
|
||||||
**Priority: Low**
|
**Priority: Low**
|
||||||
- [ ] Add smooth transitions when snapping windows
|
- [x] Add smooth transitions when snapping windows
|
||||||
- [ ] Animate zone overlay appearance/disappearance
|
- [x] Animate zone overlay appearance/disappearance
|
||||||
- [ ] Visual feedback when window snaps to zone
|
- [x] Visual feedback when window snaps to zone
|
||||||
|
|
||||||
### 12. Settings Panel ✅ COMPLETED
|
### 12. Settings Panel ✅ COMPLETED
|
||||||
**Priority: Medium**
|
**Priority: Medium**
|
||||||
|
|||||||
99
extension.js
99
extension.js
@@ -1044,9 +1044,9 @@ ZoneManager.prototype = {
|
|||||||
|
|
||||||
_showZonesOverlay: function() {
|
_showZonesOverlay: function() {
|
||||||
if (this.isShowing) return;
|
if (this.isShowing) return;
|
||||||
|
|
||||||
let monitor = Main.layoutManager.primaryMonitor;
|
let monitor = Main.layoutManager.primaryMonitor;
|
||||||
|
|
||||||
// Create overlay container
|
// Create overlay container
|
||||||
this.overlay = new St.Widget({
|
this.overlay = new St.Widget({
|
||||||
style_class: 'gridsnap-overlay',
|
style_class: 'gridsnap-overlay',
|
||||||
@@ -1071,6 +1071,14 @@ ZoneManager.prototype = {
|
|||||||
|
|
||||||
Main.layoutManager.addChrome(this.overlay);
|
Main.layoutManager.addChrome(this.overlay);
|
||||||
this.isShowing = true;
|
this.isShowing = true;
|
||||||
|
|
||||||
|
// Fade in animation
|
||||||
|
this.overlay.opacity = 0;
|
||||||
|
this.overlay.ease({
|
||||||
|
opacity: 255,
|
||||||
|
duration: 200,
|
||||||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_createZoneActor: function(zone, monitor, index) {
|
_createZoneActor: function(zone, monitor, index) {
|
||||||
@@ -1125,14 +1133,24 @@ ZoneManager.prototype = {
|
|||||||
|
|
||||||
_hideZonesOverlay: function() {
|
_hideZonesOverlay: function() {
|
||||||
if (!this.isShowing) return;
|
if (!this.isShowing) return;
|
||||||
|
|
||||||
if (this.overlay) {
|
|
||||||
Main.layoutManager.removeChrome(this.overlay);
|
|
||||||
this.overlay.destroy();
|
|
||||||
this.overlay = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isShowing = false;
|
this.isShowing = false;
|
||||||
|
|
||||||
|
if (this.overlay) {
|
||||||
|
// Fade out animation
|
||||||
|
this.overlay.ease({
|
||||||
|
opacity: 0,
|
||||||
|
duration: 150,
|
||||||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||||
|
onComplete: () => {
|
||||||
|
if (this.overlay) {
|
||||||
|
Main.layoutManager.removeChrome(this.overlay);
|
||||||
|
this.overlay.destroy();
|
||||||
|
this.overlay = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_snapWindowToZone: function(window) {
|
_snapWindowToZone: function(window) {
|
||||||
@@ -1159,6 +1177,7 @@ ZoneManager.prototype = {
|
|||||||
|
|
||||||
if (targetZone) {
|
if (targetZone) {
|
||||||
this._moveWindowToZone(window, targetZone, monitor);
|
this._moveWindowToZone(window, targetZone, monitor);
|
||||||
|
this._showSnapFeedback(targetZone, monitor);
|
||||||
|
|
||||||
// Show notification if enabled
|
// Show notification if enabled
|
||||||
if (this.settings.getValue('notification-on-snap')) {
|
if (this.settings.getValue('notification-on-snap')) {
|
||||||
@@ -1181,13 +1200,51 @@ ZoneManager.prototype = {
|
|||||||
if (zoneIndex >= layout.zones.length) return;
|
if (zoneIndex >= layout.zones.length) return;
|
||||||
|
|
||||||
let monitor = Main.layoutManager.primaryMonitor;
|
let monitor = Main.layoutManager.primaryMonitor;
|
||||||
this._moveWindowToZone(window, layout.zones[zoneIndex], monitor);
|
let targetZone = layout.zones[zoneIndex];
|
||||||
|
this._moveWindowToZone(window, targetZone, monitor);
|
||||||
|
this._showSnapFeedback(targetZone, monitor);
|
||||||
|
|
||||||
// Show notification if enabled
|
// Show notification if enabled
|
||||||
if (this.settings.getValue('notification-on-snap')) {
|
if (this.settings.getValue('notification-on-snap')) {
|
||||||
Main.notify('GridSnap', 'Window snapped to zone ' + (zoneIndex + 1));
|
Main.notify('GridSnap', 'Window snapped to zone ' + (zoneIndex + 1));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_showSnapFeedback: function(zone, monitor) {
|
||||||
|
// Create a flash effect on the zone to show snap feedback
|
||||||
|
let x = monitor.x + Math.round(monitor.width * zone.x);
|
||||||
|
let y = monitor.y + Math.round(monitor.height * zone.y);
|
||||||
|
let width = Math.round(monitor.width * zone.width);
|
||||||
|
let height = Math.round(monitor.height * zone.height);
|
||||||
|
|
||||||
|
let flashActor = new St.Widget({
|
||||||
|
style_class: 'gridsnap-flash',
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
|
});
|
||||||
|
|
||||||
|
flashActor.set_style(
|
||||||
|
'border: 4px solid rgba(100, 255, 100, 1.0);' +
|
||||||
|
'background-color: rgba(100, 255, 100, 0.3);' +
|
||||||
|
'border-radius: 4px;'
|
||||||
|
);
|
||||||
|
|
||||||
|
Main.layoutManager.addChrome(flashActor);
|
||||||
|
|
||||||
|
// Animate flash effect
|
||||||
|
flashActor.opacity = 255;
|
||||||
|
flashActor.ease({
|
||||||
|
opacity: 0,
|
||||||
|
duration: 400,
|
||||||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||||
|
onComplete: () => {
|
||||||
|
Main.layoutManager.removeChrome(flashActor);
|
||||||
|
flashActor.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
_moveWindowToZone: function(window, zone, monitor) {
|
_moveWindowToZone: function(window, zone, monitor) {
|
||||||
if (!window || !zone || !monitor) return;
|
if (!window || !zone || !monitor) return;
|
||||||
@@ -1206,8 +1263,26 @@ ZoneManager.prototype = {
|
|||||||
// Use Mainloop.idle_add to defer the operation
|
// Use Mainloop.idle_add to defer the operation
|
||||||
// This ensures the unmaximize completes before move/resize
|
// This ensures the unmaximize completes before move/resize
|
||||||
Mainloop.idle_add(Lang.bind(this, function() {
|
Mainloop.idle_add(Lang.bind(this, function() {
|
||||||
// Move and resize window (false = programmatic operation, not user-initiated)
|
// Get window actor for animation
|
||||||
window.move_resize_frame(false, x, y, width, height);
|
let actor = window.get_compositor_private();
|
||||||
|
if (actor) {
|
||||||
|
// Animate window movement
|
||||||
|
actor.ease({
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
duration: 250,
|
||||||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||||
|
onComplete: () => {
|
||||||
|
// Set final position precisely after animation
|
||||||
|
window.move_resize_frame(false, x, y, width, height);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fallback to instant move if no actor
|
||||||
|
window.move_resize_frame(false, x, y, width, height);
|
||||||
|
}
|
||||||
return false; // Don't repeat
|
return false; // Don't repeat
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user