add first implementation of multitaskingview to replace workspaceview

This commit is contained in:
Tom Beckmann 2014-02-21 15:25:10 +01:00
parent 913721161f
commit af8f3608dc
8 changed files with 696 additions and 21 deletions

View File

@ -95,6 +95,9 @@ vala_precompile(VALA_C
src/Widgets/WindowThumb.vala
src/Widgets/WorkspaceThumb.vala
src/Widgets/WorkspaceView.vala
src/Widgets/MultitaskingView/IconGroup.vala
src/Widgets/MultitaskingView/MultitaskingView.vala
src/Widgets/MultitaskingView/Workspace.vala
${CMAKE_BINARY_DIR}/src/Config.vala
PACKAGES
granite
@ -108,6 +111,7 @@ PACKAGES
xfixes-4.0
OPTIONS
-g
--enable-deprecated
--vapidir=${CMAKE_CURRENT_SOURCE_DIR}/vapi/
${MUTTER36_FLAGS}
${MUTTER38_FLAGS}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -45,6 +45,7 @@ namespace Gala
WorkspaceView workspace_view;
Zooming zooming;
WindowOverview window_overview;
MultitaskingView multitasking_view;
// used to detect which corner was used to trigger an action
Clutter.Actor? last_hotcorner;
@ -52,6 +53,8 @@ namespace Gala
#if HAS_MUTTER38
public Meta.BackgroundGroup background_group { get; private set; }
public Clutter.Actor window_group { get; private set; }
public Clutter.Actor top_window_group { get; private set; }
public Clutter.Actor ui_group { get; private set; }
#endif
@ -131,7 +134,7 @@ namespace Gala
ui_group.reactive = true;
stage.add_child (ui_group);
var window_group = Compositor.get_window_group_for_screen (screen);
window_group = Compositor.get_window_group_for_screen (screen);
stage.remove_child (window_group);
ui_group.add_child (window_group);
@ -143,6 +146,8 @@ namespace Gala
workspace_view = new WorkspaceView (this);
workspace_view.visible = false;
multitasking_view = new MultitaskingView (this);
winswitcher = new WindowSwitcher (this);
zooming = new Zooming (this);
@ -152,8 +157,9 @@ namespace Gala
ui_group.add_child (workspace_view);
ui_group.add_child (winswitcher);
ui_group.add_child (window_overview);
ui_group.add_child (multitasking_view);
var top_window_group = Compositor.get_top_window_group_for_screen (screen);
top_window_group = Compositor.get_top_window_group_for_screen (screen);
stage.remove_child (top_window_group);
ui_group.add_child (top_window_group);
#else
@ -223,7 +229,8 @@ namespace Gala
});
KeyBinding.set_custom_handler ("show-desktop", () => {
workspace_view.show (true);
//workspace_view.show (true);
multitasking_view.toggle ();
});
#if HAS_MUTTER38

View File

@ -0,0 +1,160 @@
using Clutter;
namespace Gala
{
public class IconGroup : Actor
{
List<Meta.Window> windows;
const int SIZE = 64;
static const int PLUS_SIZE = 8;
static const int PLUS_WIDTH = 24;
public signal void selected ();
public IconGroup ()
{
clear ();
width = SIZE;
height = SIZE;
reactive = true;
var canvas = new Canvas ();
canvas.set_size (SIZE, SIZE);
canvas.draw.connect (draw);
content = canvas;
}
public override bool button_release_event (ButtonEvent event)
{
selected ();
return false;
}
public void clear ()
{
windows = new List<Meta.Window> ();
}
public void add_window (Meta.Window window, bool no_redraw = false)
{
windows.append (window);
if (!no_redraw)
redraw ();
}
public void redraw ()
{
content.invalidate ();
}
bool draw (Cairo.Context cr)
{
cr.set_operator (Cairo.Operator.CLEAR);
cr.paint ();
cr.set_operator (Cairo.Operator.OVER);
// we never show more than 9, TODO: show an ellipsis when we do cut
var n_windows = uint.min (windows.length (), 9);
if (n_windows == 1) {
var pix = Utils.get_icon_for_window (windows.nth_data (0), 64);
Gdk.cairo_set_source_pixbuf (cr, pix, 0, 0);
cr.paint ();
return false;
}
// folder
Granite.Drawing.Utilities.cairo_rounded_rectangle (cr, 0.5, 0.5, (int)width - 1, (int)height - 1, 5);
cr.set_source_rgba (0, 0, 0, 0.1);
cr.fill_preserve ();
cr.set_line_width (1);
var grad = new Cairo.Pattern.linear (0, 0, 0, height);
grad.add_color_stop_rgba (0.8, 0, 0, 0, 0);
grad.add_color_stop_rgba (1.0, 1, 1, 1, 0.1);
cr.set_source (grad);
cr.stroke ();
Granite.Drawing.Utilities.cairo_rounded_rectangle (cr, 1.5, 1.5, (int)width - 3, (int)height - 3, 5);
cr.set_source_rgba (0, 0, 0, 0.3);
cr.stroke ();
// the only workspace that can be empty is the last one, so we draw our
// plus here
if (n_windows < 1) {
var buffer = new Granite.Drawing.BufferSurface (SIZE, SIZE);
var offset = SIZE / 2 - PLUS_WIDTH / 2;
buffer.context.rectangle (PLUS_WIDTH / 2 - PLUS_SIZE / 2 + 0.5 + offset,
0.5 + offset,
PLUS_SIZE - 1,
PLUS_WIDTH - 1);
buffer.context.rectangle (0.5 + offset,
PLUS_WIDTH / 2 - PLUS_SIZE / 2 + 0.5 + offset,
PLUS_WIDTH - 1,
PLUS_SIZE - 1);
buffer.context.set_source_rgb (0, 0, 0);
buffer.context.fill_preserve ();
buffer.exponential_blur (5);
buffer.context.set_source_rgb (1, 1, 1);
buffer.context.set_line_width (1);
buffer.context.stroke_preserve ();
buffer.context.set_source_rgb (0.8, 0.8, 0.8);
buffer.context.fill ();
cr.set_source_surface (buffer.surface, 0, 0);
cr.paint ();
return false;
}
int size;
if (n_windows < 5)
size = 22;
else
size = 16;
var columns = (int)Math.ceil (Math.sqrt (n_windows));
var rows = (int)Math.ceil (n_windows / (double)columns);
const int spacing = 6;
var width = columns * size + (columns - 1) * spacing;
var height = rows * size + (rows - 1) * spacing;
var x_offset = SIZE / 2 - width / 2;
var y_offset = SIZE / 2 - height / 2;
var x = x_offset;
var y = y_offset;
for (var i = 0; i < n_windows; i++) {
var pix = Utils.get_icon_for_window (windows.nth_data (i), size);
Gdk.cairo_set_source_pixbuf (cr, pix, x, y);
x += size + spacing;
if (x + size >= SIZE) {
x = x_offset;
y += size + spacing;
}
cr.paint ();
}
return false;
}
}
}

View File

@ -0,0 +1,237 @@
using Clutter;
namespace Gala
{
public class MultitaskingView : Actor
{
public Meta.Screen screen { get; construct set; }
public Plugin plugin { get; construct set; }
Actor icon_groups;
Actor workspaces;
public bool opened { get; private set; default = false; }
const int HIDING_DURATION = 300;
public MultitaskingView (Plugin plugin)
{
Object (plugin: plugin, screen: plugin.get_screen ());
visible = false;
reactive = true;
workspaces = new Actor ();
icon_groups = new Actor ();
icon_groups.layout_manager = new BoxLayout ();
(icon_groups.layout_manager as BoxLayout).spacing = 48;
add_child (icon_groups);
add_child (workspaces);
foreach (var workspace in screen.get_workspaces ())
add_workspace (workspace.index ());
screen.workspace_added.connect (add_workspace);
screen.workspace_removed.connect (remove_workspace);
screen.workspace_switched.connect ((from, to, direction) => {
update_positions (opened);
});
}
public override bool scroll_event (ScrollEvent event)
{
var active_workspace = screen.get_active_workspace ();
var new_workspace = active_workspace.get_neighbor (
event.direction == ScrollDirection.LEFT ||
event.direction == ScrollDirection.UP ?
Meta.MotionDirection.LEFT : Meta.MotionDirection.RIGHT);
if (active_workspace != new_workspace)
new_workspace.activate (screen.get_display ().get_current_time ());
return false;
}
void update_positions (bool animate = false, bool closing = false)
{
float x = 0;
WorkspaceClone? active = null;
var active_index = screen.get_active_workspace ().index ();;
foreach (var child in workspaces.get_children ()) {
var workspace_clone = child as WorkspaceClone;
var index = workspace_clone.workspace.index ();
if (index == active_index)
active = workspace_clone;
workspace_clone.x = index * (workspace_clone.width - 150);
}
if (active != null) {
var dest_x = -active.x;
if (animate)
workspaces.animate (AnimationMode.EASE_OUT_QUAD, 300, x: dest_x);
else
workspaces.x = dest_x;
}
}
void add_workspace (int num)
{
var workspace = new WorkspaceClone (screen.get_workspace_by_index (num));
workspace.window_selected.connect (window_selected);
workspace.selected.connect (activate_workspace);
workspaces.insert_child_at_index (workspace, num);
icon_groups.insert_child_at_index (workspace.icon_group, num);
update_positions ();
}
void remove_workspace (int num)
{
WorkspaceClone? workspace = null;
// FIXME is there a better way to get the removed workspace?
unowned List<Meta.Workspace> existing_workspaces = screen.get_workspaces ();
foreach (var child in workspaces.get_children ()) {
var clone = child as WorkspaceClone;
if (existing_workspaces.index (clone.workspace) < 0) {
workspace = clone;
break;
}
}
if (workspace == null)
return;
workspace.window_selected.disconnect (window_selected);
workspace.selected.disconnect (activate_workspace);
workspace.icon_group.destroy ();
workspace.destroy ();
update_positions ();
}
void activate_workspace (WorkspaceClone clone, bool close_view)
{
close_view = close_view && screen.get_active_workspace () == clone.workspace;
clone.workspace.activate (screen.get_display ().get_current_time ());
if (close_view)
toggle ();
}
public override bool key_press_event (Clutter.KeyEvent event)
{
if (event.keyval == Clutter.Key.Escape)
toggle ();
return false;
}
void window_selected (Meta.Window window)
{
window.activate (screen.get_display ().get_current_time ());
toggle ();
}
public void toggle ()
{
opened = !opened;
var opening = opened;
unowned List<Meta.WindowActor> windows = Meta.Compositor.get_window_actors (screen);
var primary_monitor = screen.get_primary_monitor ();
var monitor = screen.get_monitor_geometry (primary_monitor);
if (opening) {
plugin.begin_modal ();
plugin.background_group.hide ();
plugin.window_group.hide ();
plugin.top_window_group.hide ();
show ();
grab_key_focus ();
icon_groups.x = monitor.width / 2 - icon_groups.width / 2;
icon_groups.y = monitor.height - WorkspaceClone.BOTTOM_OFFSET + 20;
}
// find active workspace clone and raise it, so there are no overlaps while transitioning
WorkspaceClone? active_workspace = null;
var active = screen.get_active_workspace ();
foreach (var child in workspaces.get_children ()) {
var workspace = child as WorkspaceClone;
if (workspace.workspace == active) {
active_workspace = workspace;
break;
}
}
if (active_workspace != null)
workspaces.set_child_above_sibling (active_workspace, null);
update_positions (false);
foreach (var child in workspaces.get_children ()) {
if (opening)
(child as WorkspaceClone).open ();
else
(child as WorkspaceClone).close ();
}
if (!opening) {
Timeout.add (290, () => {
hide ();
plugin.background_group.show ();
plugin.window_group.show ();
plugin.top_window_group.show ();
plugin.end_modal ();
return false;
});
}
/**
* three types of animation for docks:
* - window appears to be at the bottom --> slide up
* - window appears to be at the top --> slide down
* - window appears to be somewhere else --> fade out
var rect = meta_window.get_outer_rect ();
if (about_same (rect.y, monitor_geometry.y)) {
float dest = opening ? monitor_geometry.y - rect.height : rect.y;
window.animate (AnimationMode.EASE_OUT_QUAD, HIDING_DURATION, y: dest);
} else if (about_same (rect.y + rect.height,
monitor_geometry.y + monitor_geometry.height)) {
float dest = opening ? monitor_geometry.y + monitor_geometry.height : rect.y;
window.animate (AnimationMode.EASE_OUT_QUAD, HIDING_DURATION, y: dest);
} else {
uint dest = opening ? 0 : 255;
window.animate (AnimationMode.LINEAR, HIDING_DURATION, opacity: dest);
}
*/
}
/**
* checks if val1 is about the same as val2 with a threshold of 2 by default
*/
private bool about_same (float val1, float val2, float threshold = 2.0f)
{
return Math.fabsf (val1 - val2) <= threshold;
}
}
}

View File

@ -0,0 +1,220 @@
using Clutter;
namespace Gala
{
class FramedBackground : BackgroundManager
{
public FramedBackground (Meta.Screen screen)
{
base (screen);
add_effect (new BackgroundShadowEffect (screen));
}
public override void paint ()
{
base.paint ();
Cogl.set_source_color4ub (0, 0, 0, 100);
Cogl.Path.rectangle (0, 0, width, height);
Cogl.Path.stroke ();
Cogl.set_source_color4ub (255, 255, 255, 80);
Cogl.Path.rectangle (1, 1, width - 2, height - 2);
Cogl.Path.stroke ();
}
}
class BackgroundShadowEffect : Effect
{
static Meta.Screen screen;
static Cogl.Texture? bitmap;
const int SHADOW_SIZE = 40;
const int SHADOW_OFFSET = 5;
static int width;
static int height;
public BackgroundShadowEffect (Meta.Screen _screen)
{
if (bitmap == null) {
screen = _screen;
int screen_width, screen_height;
screen.get_size (out screen_width, out screen_height);
width = screen_width + SHADOW_SIZE * 2;
height = screen_height + SHADOW_SIZE * 2;
var buffer = new Granite.Drawing.BufferSurface (width, height);
buffer.context.rectangle (SHADOW_SIZE - SHADOW_OFFSET, SHADOW_SIZE - SHADOW_OFFSET,
screen_width + SHADOW_OFFSET * 2, screen_height + SHADOW_OFFSET * 2);
buffer.context.set_source_rgba (0, 0, 0, 0.5);
buffer.context.fill ();
buffer.exponential_blur (20);
var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, width, height);
var cr = new Cairo.Context (surface);
cr.set_source_surface (buffer.surface, 0, 0);
cr.paint ();
bitmap = new Cogl.Texture.from_data (width, height, 0, Cogl.PixelFormat.BGRA_8888_PRE,
Cogl.PixelFormat.ANY, surface.get_stride (), surface.get_data ());
}
}
public override void paint (EffectPaintFlags flags)
{
Cogl.set_source_texture (bitmap);
Cogl.rectangle (-SHADOW_SIZE, -SHADOW_SIZE, width - SHADOW_SIZE, height - SHADOW_SIZE);
actor.continue_paint ();
}
}
public class WorkspaceClone : Clutter.Actor
{
public Meta.Workspace workspace { get; construct set; }
public BackgroundManager background { get; private set; }
public IconGroup icon_group { get; private set; }
public signal void window_selected (Meta.Window window);
public signal void selected (bool close_view);
const int TOP_OFFSET = 20;
public static const int BOTTOM_OFFSET = 100;
public WorkspaceClone (Meta.Workspace workspace)
{
Object (workspace: workspace);
background = new FramedBackground (workspace.get_screen ());
background.reactive = true;
background.button_press_event.connect (() => {
selected (true);
return false;
});
icon_group = new IconGroup ();
icon_group.selected.connect (() => {
selected (false);
});
var screen = workspace.get_screen ();
screen.window_left_monitor.connect ((monitor, window) => {
if (monitor == screen.get_primary_monitor ())
remove_window (window);
});
workspace.window_removed.connect (remove_window);
screen.window_entered_monitor.connect ((monitor, window) => {
if (monitor == screen.get_primary_monitor ())
add_window (window);
});
workspace.window_added.connect (add_window);
add_child (background);
}
private void add_window (Meta.Window window)
{
}
private void remove_window (Meta.Window window)
{
var window_actor = window.get_compositor_private ();
foreach (var child in get_children ()) {
if (child is Clone && (child as Clone).source == window_actor) {
child.destroy ();
break;
}
}
}
private void shrink_rectangle (ref Meta.Rectangle rect, int amount)
{
rect.x += amount;
rect.y += amount;
rect.width -= amount * 2;
rect.height -= amount * 2;
}
public void open ()
{
var screen = workspace.get_screen ();
var display = screen.get_display ();
var monitor = screen.get_monitor_geometry (screen.get_primary_monitor ());
var scale = (float)(monitor.height - TOP_OFFSET - BOTTOM_OFFSET) / monitor.height;
var pivotY = TOP_OFFSET / (monitor.height - monitor.height * scale);
background.set_pivot_point (0.5f, pivotY);
background.animate (AnimationMode.EASE_OUT_QUAD, 250, scale_x: scale, scale_y: scale);
Meta.Rectangle area = {
(int)Math.floorf (monitor.x + monitor.width - monitor.width * scale) / 2,
(int)Math.floorf (monitor.y + TOP_OFFSET),
(int)Math.floorf (monitor.width * scale),
(int)Math.floorf (monitor.height * scale)
};
shrink_rectangle (ref area, 32);
icon_group.clear ();
var unsorted_windows = workspace.list_windows ();
var used_windows = new SList<Meta.Window> ();
foreach (var window in unsorted_windows) {
if (window.window_type == Meta.WindowType.NORMAL) {
used_windows.append (window);
icon_group.add_window (window, true);
}
}
icon_group.redraw ();
var windows = display.sort_windows_by_stacking (used_windows);
var clones = new List<WindowThumb> ();
foreach (var window in windows) {
var window_actor = window.get_compositor_private () as Meta.WindowActor;
var clone = new WindowThumb (window, false);
clone.selected.connect (() => {
window_selected (clone.window);
});
clone.set_position (window_actor.x, window_actor.y);
add_child (clone);
clones.append (clone);
}
if (clones.length () > 0)
WindowOverview.grid_placement (area, clones, place_window);
}
void place_window (Actor window, Meta.Rectangle rect)
{
window.animate (AnimationMode.EASE_OUT_CUBIC, 250,
x: rect.x + 0.0f, y: rect.y + 0.0f, width: rect.width + 0.0f, height: rect.height + 0.0f);
(window as WindowThumb).place_children (rect.width, rect.height);
}
public void close ()
{
background.animate (AnimationMode.EASE_IN_OUT_CUBIC, 300, scale_x: 1.0f, scale_y: 1.0f);
foreach (var child in get_children ()) {
if (child is WindowThumb)
(child as WindowThumb).close (true, false);
}
}
~Workspace ()
{
background.destroy ();
}
}
}

View File

@ -26,6 +26,8 @@ namespace Gala
GRID = 0,
NATURAL
}
public delegate void WindowPlacer (Actor window, Meta.Rectangle rect);
public class WindowOverview : Actor
{
@ -93,7 +95,7 @@ namespace Gala
const int BOTTOM_GAP = 100;
//some math utilities
int squared_distance (Gdk.Point a, Gdk.Point b)
static int squared_distance (Gdk.Point a, Gdk.Point b)
{
var k1 = b.x - a.x;
var k2 = b.y - a.y;
@ -101,7 +103,7 @@ namespace Gala
return k1*k1 + k2*k2;
}
bool rect_is_overlapping_any (Meta.Rectangle rect, Meta.Rectangle[] rects, Meta.Rectangle border)
static bool rect_is_overlapping_any (Meta.Rectangle rect, Meta.Rectangle[] rects, Meta.Rectangle border)
{
if (!border.contains_rect (rect))
return true;
@ -116,12 +118,12 @@ namespace Gala
return false;
}
Meta.Rectangle rect_adjusted (Meta.Rectangle rect, int dx1, int dy1, int dx2, int dy2)
static Meta.Rectangle rect_adjusted (Meta.Rectangle rect, int dx1, int dy1, int dx2, int dy2)
{
return {rect.x + dx1, rect.y + dy1, rect.width + (-dx1 + dx2), rect.height + (-dy1 + dy2)};
}
Gdk.Point rect_center (Meta.Rectangle rect)
static Gdk.Point rect_center (Meta.Rectangle rect)
{
return {rect.x + rect.width / 2, rect.y + rect.height / 2};
}
@ -163,13 +165,13 @@ namespace Gala
(int)Math.floorf (geom.height - BOTTOM_GAP)};
if (BehaviorSettings.get_default ().schema.get_enum ("window-overview-type") == WindowOverviewType.GRID)
grid_placement (area, monitors[i]);
grid_placement (area, monitors[i], place_window);
else
natural_placement (area, monitors[i]);
}
}
void grid_placement (Meta.Rectangle area, List<Actor> clones)
public static void grid_placement (Meta.Rectangle area, List<Actor> clones, WindowPlacer place)
{
int columns = (int)Math.ceil (Math.sqrt (clones.length ()));
int rows = (int)Math.ceil (clones.length () / (double)columns);
@ -277,7 +279,7 @@ namespace Gala
if (left_over != columns && slot >= columns * (rows - 1))
target.x += (columns - left_over) * slot_width / 2;
place_window (window, target);
place (window, target);
}
}
@ -492,8 +494,10 @@ namespace Gala
}
// animate a window to the given position
void place_window (WindowThumb clone, Meta.Rectangle rect)
void place_window (Actor actor, Meta.Rectangle rect)
{
var clone = actor as WindowThumb;
var fscale = rect.width / clone.width;
//animate the windows and icons to the calculated positions

View File

@ -32,7 +32,7 @@ namespace Gala
public signal void selected (Window window);
public signal void closed ();
public WindowThumb (Window _window)
public WindowThumb (Window _window, bool add_children_to_stage = true)
{
window = _window;
@ -40,10 +40,12 @@ namespace Gala
var actor = window.get_compositor_private () as WindowActor;
clone = new Clone (actor);
clone.add_constraint (new BindConstraint (this, BindCoordinate.SIZE, 0));
icon = new GtkClutter.Texture ();
icon.scale_x = 0.0f;
icon.scale_y = 0.0f;
icon.opacity = 0;
icon.scale_gravity = Gravity.CENTER;
try {
@ -65,9 +67,40 @@ namespace Gala
add_child (clone);
var stage = Compositor.get_stage_for_screen (window.get_screen ());
stage.add_child (icon);
stage.add_child (close_button);
if (add_children_to_stage) {
var stage = Compositor.get_stage_for_screen (window.get_screen ());
stage.add_child (icon);
stage.add_child (close_button);
} else {
add_child (close_button);
add_child (icon);
}
}
public void place_children (int width, int height)
{
float offset_x, offset_y, offset_width;
Utils.get_window_frame_offset (window, out offset_x, out offset_y, out offset_width, null);
float button_offset = close_button.width * 0.25f;
float scale = width / (window.get_compositor_private () as WindowActor).width;
Granite.CloseButtonPosition pos;
Granite.Widgets.Utils.get_default_close_button_position (out pos);
switch (pos) {
case Granite.CloseButtonPosition.LEFT:
close_button.x = -offset_x * scale - button_offset;
break;
case Granite.CloseButtonPosition.RIGHT:
close_button.x = width - offset_width * scale - close_button.width / 2;
break;
}
close_button.y = -offset_y * scale - button_offset;
icon.x = Math.floorf (width / 2.0f - icon.width / 2.0f);
icon.y = Math.floorf (height - 50.0f);
icon.animate (AnimationMode.EASE_OUT_CUBIC, 350, scale_x: 1.0f, scale_y: 1.0f, opacity: 255);
}
bool close_button_clicked (ButtonEvent event)
@ -155,15 +188,15 @@ namespace Gala
return true;
}
public void close (bool do_animate = true)
public void close (bool do_animate = true, bool use_scale = true)
{
unowned Meta.Rectangle rect = window.get_outer_rect ();
//FIXME need to subtract 10 here to remove jump for most windows, but adds jump for maximized ones
float delta = window.maximized_horizontally || window.maximized_vertically ? 0 : 10;
float x, y, w, h;
Utils.get_window_frame_offset (window, out x, out y, out w, out h);
float dest_x = rect.x - delta;
float dest_y = rect.y - delta;
float dest_x = rect.x + x;
float dest_y = rect.y + y;
//stop all running animations
detach_animation ();
@ -177,7 +210,17 @@ namespace Gala
icon.animate (AnimationMode.EASE_IN_CUBIC, 100, scale_x:0.0f, scale_y:0.0f);
close_button.animate (AnimationMode.EASE_IN_QUAD, 200, scale_x : 0.0f, scale_y : 0.0f);
animate (AnimationMode.EASE_IN_OUT_CUBIC, 300, scale_x:1.0f, scale_y:1.0f, x:dest_x, y:dest_y).completed.connect (() => {
Animation a;
if (use_scale) {
a = animate (AnimationMode.EASE_IN_OUT_CUBIC, 300, scale_x: 1.0f, scale_y: 1.0f,
x: dest_x, y: dest_y);
} else {
var window = window.get_compositor_private () as WindowActor;
a = animate (AnimationMode.EASE_IN_OUT_CUBIC, 300, width: window.width, height: window.height,
x: dest_x, y: dest_y);
}
a.completed.connect (() => {
clone.source.show ();
destroy ();
});