diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index c99189cfe..4206f4cbd 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -90,6 +90,7 @@ ui/windowAttentionHandler.js ui/windowMenu.js ui/windowManager.js + ui/wobbly.js ui/workspace.js ui/workspaceSwitcherPopup.js ui/workspaceThumbnail.js diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js index 101f9a8d7..397af8958 100644 --- a/js/ui/windowManager.js +++ b/js/ui/windowManager.js @@ -17,6 +17,7 @@ const Main = imports.ui.main; const ModalDialog = imports.ui.modalDialog; const Tweener = imports.ui.tweener; const WindowMenu = imports.ui.windowMenu; +const Wobbly = imports.ui.wobbly; const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings'; const MAXIMIZE_WINDOW_ANIMATION_TIME = 0.15; @@ -811,6 +812,8 @@ const WindowManager = new Lang.Class({ gesture = new AppSwitchAction(); gesture.connect('activated', Lang.bind(this, this._switchApp)); global.stage.add_action(gesture); + + this._wobblyWindows = new Wobbly.WobblyWindowManager(); }, _lookupIndex: function (windows, metaWindow) { 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(); + }, +}); diff --git a/src/Makefile.am b/src/Makefile.am index a73eaaedf..98ec9d5ac 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -102,6 +102,7 @@ shell_public_headers_h = \ shell-util.h \ shell-window-tracker.h \ shell-wm.h \ + shell-wobbly-effect.h \ $(NULL) if HAVE_NETWORKMANAGER @@ -167,6 +168,7 @@ libgnome_shell_sources = \ shell-util.c \ shell-window-tracker.c \ shell-wm.c \ + shell-wobbly-effect.c \ $(NULL) libgnome_shell_built_sources = \ diff --git a/src/shell-wobbly-effect.c b/src/shell-wobbly-effect.c new file mode 100644 index 000000000..eb1d87484 --- /dev/null +++ b/src/shell-wobbly-effect.c @@ -0,0 +1,216 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 2014 Endless Mobile + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Written by: + * Jasper St. Pierre + */ + +#include "config.h" + +#include "shell-wobbly-effect.h" + +struct _ShellWobblyEffectPrivate +{ + int bend_x; + + int tex_width, tex_height; + CoglPipeline *pipeline; + + int tex_width_uniform; + int bend_x_uniform; +}; +typedef struct _ShellWobblyEffectPrivate ShellWobblyEffectPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (ShellWobblyEffect, shell_wobbly_effect, CLUTTER_TYPE_OFFSCREEN_EFFECT); + +static const gchar *wobbly_decls = + "uniform int tex_width;\n" + "uniform int bend_x;\n"; +static const gchar *wobbly_pre = + "float bend_x_coord = (float(bend_x) / tex_width);\n" + "float interp = (1 - cos(cogl_tex_coord.y * 3.1415926)) / 2;\n" + "cogl_tex_coord.x -= (interp * bend_x_coord);\n"; + +/* XXX - clutter_effect_get_paint_volume is fucking terrible + * and I want to kill myself */ +static gboolean +shell_wobbly_effect_get_paint_volume (ClutterEffect *effect, + ClutterPaintVolume *volume) +{ + ShellWobblyEffect *self = SHELL_WOBBLY_EFFECT (effect); + ShellWobblyEffectPrivate *priv = shell_wobbly_effect_get_instance_private (self); + + float cur_width; + cur_width = clutter_paint_volume_get_width (volume); + cur_width += ABS (priv->bend_x); + clutter_paint_volume_set_width (volume, cur_width); + + /* Also modify the origin if it bends to the left. */ + if (priv->bend_x < 0) + { + ClutterVertex origin; + clutter_paint_volume_get_origin (volume, &origin); + origin.x += priv->bend_x; + clutter_paint_volume_set_origin (volume, &origin); + } + + return TRUE; +} + +static gboolean +shell_wobbly_effect_pre_paint (ClutterEffect *effect) +{ + ShellWobblyEffect *self = SHELL_WOBBLY_EFFECT (effect); + ShellWobblyEffectPrivate *priv = shell_wobbly_effect_get_instance_private (self); + + if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (effect))) + return FALSE; + + /* If we're not doing any bending, we're not needed. */ + if (priv->bend_x == 0) + return FALSE; + + if (!clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL)) + { + /* if we don't have support for GLSL shaders then we + * forcibly disable the ActorMeta + */ + g_warning ("Unable to use the ShellWobblyEffect: the " + "graphics hardware or the current GL driver does not " + "implement support for the GLSL shading language. The " + "effect will be disabled."); + clutter_actor_meta_set_enabled (CLUTTER_ACTOR_META (effect), FALSE); + return FALSE; + } + + if (!CLUTTER_EFFECT_CLASS (shell_wobbly_effect_parent_class)->pre_paint (effect)) + return FALSE; + + ClutterOffscreenEffect *offscreen_effect = CLUTTER_OFFSCREEN_EFFECT (effect); + CoglObject *texture; + + texture = clutter_offscreen_effect_get_texture (offscreen_effect); + cogl_pipeline_set_layer_texture (priv->pipeline, 0, texture); + + priv->tex_width = cogl_texture_get_width (texture); + priv->tex_height = cogl_texture_get_height (texture); + + cogl_pipeline_set_uniform_1i (priv->pipeline, priv->tex_width_uniform, priv->tex_width); + + return TRUE; +} + +static void +shell_wobbly_effect_paint_target (ClutterOffscreenEffect *effect) +{ + ShellWobblyEffect *self = SHELL_WOBBLY_EFFECT (effect); + ShellWobblyEffectPrivate *priv = shell_wobbly_effect_get_instance_private (self); + CoglFramebuffer *fb = cogl_get_draw_framebuffer (); + ClutterActor *actor; + guint8 paint_opacity; + + actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect)); + paint_opacity = clutter_actor_get_paint_opacity (actor); + + cogl_pipeline_set_color4ub (priv->pipeline, + paint_opacity, + paint_opacity, + paint_opacity, + paint_opacity); + + cogl_framebuffer_draw_rectangle (fb, priv->pipeline, + 0, 0, priv->tex_width, priv->tex_height); +} + +static void +shell_wobbly_effect_dispose (GObject *object) +{ + ShellWobblyEffect *self = SHELL_WOBBLY_EFFECT (object); + ShellWobblyEffectPrivate *priv = shell_wobbly_effect_get_instance_private (self); + + g_clear_pointer (&priv->pipeline, cogl_object_unref); + + G_OBJECT_CLASS (shell_wobbly_effect_parent_class)->dispose (object); +} + +static void +shell_wobbly_effect_class_init (ShellWobblyEffectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass); + ClutterOffscreenEffectClass *offscreen_class = CLUTTER_OFFSCREEN_EFFECT_CLASS (klass); + + offscreen_class->paint_target = shell_wobbly_effect_paint_target; + effect_class->pre_paint = shell_wobbly_effect_pre_paint; + effect_class->get_paint_volume = shell_wobbly_effect_get_paint_volume; + object_class->dispose = shell_wobbly_effect_dispose; +} + +static void +update_uniforms (ShellWobblyEffect *self) +{ + ShellWobblyEffectPrivate *priv = shell_wobbly_effect_get_instance_private (self); + cogl_pipeline_set_uniform_1i (priv->pipeline, priv->bend_x_uniform, priv->bend_x); +} + +static void +shell_wobbly_effect_init (ShellWobblyEffect *self) +{ + static CoglPipeline *pipeline_template; + + ShellWobblyEffectPrivate *priv = shell_wobbly_effect_get_instance_private (self); + + if (G_UNLIKELY (pipeline_template == NULL)) + { + CoglSnippet *snippet; + CoglContext *ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ()); + + pipeline_template = cogl_pipeline_new (ctx); + + snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_TEXTURE_LOOKUP, wobbly_decls, NULL); + cogl_snippet_set_pre (snippet, wobbly_pre); + cogl_pipeline_add_layer_snippet (pipeline_template, 0, snippet); + cogl_object_unref (snippet); + + cogl_pipeline_set_layer_null_texture (pipeline_template, + 0, /* layer number */ + COGL_TEXTURE_TYPE_2D); + } + + priv->pipeline = cogl_pipeline_copy (pipeline_template); + priv->tex_width_uniform = cogl_pipeline_get_uniform_location (priv->pipeline, "tex_width"); + priv->bend_x_uniform = cogl_pipeline_get_uniform_location (priv->pipeline, "bend_x"); + + update_uniforms (self); +} + +void +shell_wobbly_effect_set_bend_x (ShellWobblyEffect *self, + int bend_x) +{ + ShellWobblyEffectPrivate *priv = shell_wobbly_effect_get_instance_private (self); + + if (priv->bend_x == bend_x) + return; + + priv->bend_x = bend_x; + update_uniforms (self); + clutter_effect_queue_repaint (CLUTTER_EFFECT (self)); +} diff --git a/src/shell-wobbly-effect.h b/src/shell-wobbly-effect.h new file mode 100644 index 000000000..6c02cea96 --- /dev/null +++ b/src/shell-wobbly-effect.h @@ -0,0 +1,55 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 2014 Endless Mobile + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Written by: + * Jasper St. Pierre + */ + +#include + +#ifndef __SHELL_WOBBLY_EFFECT_H__ +#define __SHELL_WOBBLY_EFFECT_H__ + +#define SHELL_TYPE_WOBBLY_EFFECT (shell_wobbly_effect_get_type ()) +#define SHELL_WOBBLY_EFFECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_TYPE_WOBBLY_EFFECT, ShellWobblyEffect)) +#define SHELL_WOBBLY_EFFECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_WOBBLY_EFFECT, ShellWobblyEffectClass)) +#define SHELL_IS_WOBBLY_EFFECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_TYPE_WOBBLY_EFFECT)) +#define SHELL_IS_WOBBLY_EFFECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_WOBBLY_EFFECT)) +#define SHELL_WOBBLY_EFFECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_WOBBLY_EFFECT, ShellWobblyEffectClass)) + +typedef struct _ShellWobblyEffect ShellWobblyEffect; +typedef struct _ShellWobblyEffectClass ShellWobblyEffectClass; + +struct _ShellWobblyEffect +{ + ClutterOffscreenEffect parent; +}; + +struct _ShellWobblyEffectClass +{ + ClutterOffscreenEffectClass parent_class; +}; + +GType shell_wobbly_effect_get_type (void) G_GNUC_CONST; + +void shell_wobbly_effect_set_bend_x (ShellWobblyEffect *self, + int bend_x); + +#endif /* __SHELL_WOBBLY_EFFECT_H__ */