summaryrefslogtreecommitdiff
path: root/js/ui/wobbly.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/ui/wobbly.js')
-rw-r--r--js/ui/wobbly.js130
1 files changed, 130 insertions, 0 deletions
diff --git a/js/ui/wobbly.js b/js/ui/wobbly.js
new file mode 100644
index 000000000..ad53fc66f
--- /dev/null
+++ b/js/ui/wobbly.js
@@ -0,0 +1,130 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+const Clutter = imports.gi.Clutter;
+const Lang = imports.lang;
+const Meta = imports.gi.Meta;
+const Shell = imports.gi.Shell;
+
+function clampAbs(v, cap) {
+ if (v > cap)
+ v = cap;
+ if (v < -cap)
+ v = -cap;
+ return v;
+}
+
+const ActorWobbler = new Lang.Class({
+ Name: 'ActorWobbler',
+
+ _init: function(actor) {
+ this._actor = actor;
+ this._effect = new Shell.WobblyEffect();
+ this._actor.add_effect(this._effect);
+ this._actor._wobbler = this;
+
+ this._currentBend = 0;
+ this._currentHeightOffset = 0;
+ this._running = false;
+
+ this._allocationChangedId = this._actor.connect('allocation-changed', Lang.bind(this, this._allocationChanged));
+
+ this._timeline = new Clutter.Timeline({ duration: 100, repeat_count: -1 });
+ this._timeline.connect('new-frame', Lang.bind(this, this._newFrame));
+ this._timeline.start();
+ },
+
+ start: function() {
+ this._running = true;
+ },
+
+ stop: function() {
+ this._running = false;
+ },
+
+ _destroy: function() {
+ this._timeline.run_dispose();
+ this._timeline = null;
+
+ this._actor.disconnect(this._allocationChangedId);
+ this._actor.scale_y = 1.0;
+ this._actor.remove_effect(this._effect);
+ this._actor._wobbler = null;
+ },
+
+ _newFrame: function() {
+ this._step();
+ },
+
+ _step: function() {
+ const DAMPEN = 0.8;
+ this._currentBend *= DAMPEN;
+ if (Math.abs(this._currentBend) < 1)
+ this._currentBend = 0;
+ this._currentHeightOffset *= DAMPEN;
+ if (Math.abs(this._currentHeightOffset) < 1)
+ this._currentHeightOffset = 0;
+
+ // Cap the bend to a 100px shift.
+ const BEND_CAP = 50;
+ this._currentBend = clampAbs(this._currentBend, BEND_CAP);
+ this._effect.set_bend_x(this._currentBend);
+
+ // Cap the height change to 25px in either direction.
+ const HEIGHT_OFFSET_CAP = 25;
+ this._currentHeightOffset = clampAbs(this._currentHeightOffset, HEIGHT_OFFSET_CAP);
+ let [minHeight, natHeight] = this._actor.get_preferred_height(-1);
+ let scale = (natHeight + this._currentHeightOffset) / natHeight;
+ this._actor.scale_y = scale;
+
+ if (!this._running && this._currentBend == 0 && this._currentHeightOffset == 0)
+ this._destroy();
+ },
+
+ _allocationChanged: function(actor, box, flags) {
+ if (!this._running)
+ return;
+
+ if (this._oldX) {
+ let deltaX = box.x1 - this._oldX;
+ // Every 2px the user moves the window, we bend it by 1px.
+ this._currentBend -= deltaX / 2;
+ }
+
+ if (this._oldY) {
+ let deltaY = box.y1 - this._oldY;
+ // Every 2px the user moves the window, we scale it by 1px.
+ this._currentHeightOffset -= deltaY / 2;
+ }
+
+ this._oldX = box.x1;
+ this._oldY = box.y1;
+ },
+});
+
+const WobblyWindowManager = new Lang.Class({
+ Name: 'WobblyWindowManager',
+
+ _init: function() {
+ global.display.connect('grab-op-begin', Lang.bind(this, this._grabOpBegin));
+ global.display.connect('grab-op-end', Lang.bind(this, this._grabOpEnd));
+ },
+
+ _grabOpBegin: function(display, screen, window, op) {
+ if (op != Meta.GrabOp.MOVING)
+ return;
+
+ let actor = window.get_compositor_private();
+ if (!actor._wobbler)
+ new ActorWobbler(actor);
+ actor._wobbler.start();
+ },
+
+ _grabOpEnd: function(display, screen, window, op) {
+ if (!window)
+ return;
+
+ let actor = window.get_compositor_private();
+ if (actor._wobbler)
+ actor._wobbler.stop();
+ },
+});