/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */

/* 
 * Copyright (C) 2006 Red Hat, Inc.
 * 
 * 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.
 */

#include <config.h>

#ifdef HAVE_COMPOSITE_EXTENSIONS

#include <X11/Xlib.h>
#include <glib.h>
#include <cm/ws.h>
#include <cm/wsint.h>
#include <cm/node.h>
#include <cm/drawable-node.h>
#include <string.h>
#include <math.h>

#include "effects.h"
#include "c-window.h"
#include "window.h"
#include "frame.h"
#include "compositor.h"
#include "workspace.h"

typedef struct UnminimizeInfo UnminimizeInfo;

struct _MetaCompWindow
{
    MetaDisplay *display;
    MetaScreen  *screen;
    WsDrawable *drawable;
    WsPixmap   *pixmap;
    CmDrawableNode     *node;
    gboolean	updates;
    WsSyncAlarm *alarm;
    
    WsRectangle size;
    gboolean waiting_for_paint;
    
    gint64	counter_value;
    gint        ref_count;
    
    gboolean    animation_in_progress;
    gboolean	hide_after_animation;
    
    int		stack_freeze_count;
    int		fade_in_idle_id;

    MetaCompWindowDestroy destroy;
    gpointer              closure;

    UnminimizeInfo *unminimize_info;
};

static void cancel_fade (MetaCompWindow *comp_window);
static void start_unminimize (MetaCompWindow *comp_window);

static Window
find_app_window (MetaCompWindow *comp_window)
{
    Window xwindow = WS_RESOURCE_XID (comp_window->drawable);
    MetaWindow *meta_window =
	meta_display_lookup_x_window (comp_window->display, xwindow);
    
    if (meta_window)
	return meta_window->xwindow;
    else
	return xwindow;
}

static WsPixmap *
take_snapshot (WsDrawable *drawable)
{
    WsDisplay *display = WS_RESOURCE (drawable)->display;
    WsRectangle geometry;
    WsPixmap *pixmap;
    
    ws_display_begin_error_trap (display);
    
    ws_drawable_query_geometry (drawable, &geometry);
    
    pixmap = ws_pixmap_new (drawable, geometry.width, geometry.height);
    
    ws_drawable_copy_area (drawable, 0, 0, geometry.width, geometry.height,
			   WS_DRAWABLE (pixmap), 0, 0,
			   NULL);
    
    ws_display_end_error_trap (display);
    
    return pixmap;
}

static void
on_alarm (WsSyncAlarm *alarm,
	  WsAlarmNotifyEvent *event,
	  MetaCompWindow *window)
{
    if (window->pixmap)
	g_object_unref (window->pixmap);
    
    window->pixmap = take_snapshot (window->drawable);
    
    ws_sync_alarm_set (window->alarm, event->counter_value + 2);
    ws_sync_counter_change (event->counter, 1);
}

static gboolean
has_counter (MetaCompWindow *comp_window)
{
    Window xwindow = find_app_window (comp_window);
    WsDisplay *display = WS_RESOURCE (comp_window->drawable)->display;
    WsWindow *window = ws_window_lookup (display, xwindow);
    WsSyncCounter *counter;
    
    ws_display_init_sync (display);
    
    counter = ws_window_get_property_sync_counter (
	window, "_NET_WM_FINISH_FRAME_COUNTER");
    
    if (counter)
    {
	WsSyncAlarm *alarm;
	gint64 value = ws_sync_counter_query_value (counter);
	
#if 0
	g_print ("counter value %lld\n", ws_sync_counter_query_value (counter));
	alarm = ws_sync_alarm_new (WS_RESOURCE (comp_window->drawable)->display,
				   
				   display, counter);
#endif
	
	g_signal_connect (alarm, "alarm_notify_event",
			  G_CALLBACK (on_alarm), comp_window);
	
	if (value % 2 == 1)
	{
	    ws_sync_alarm_set (alarm, value + 2);
	    
#if 0
	    g_print ("wait for %lld\n", value + 2);
	    
	    g_print ("increasing counter\n");
#endif
	    ws_sync_counter_change (counter, 1);
	    
#if 0
	    g_print ("counter value %lld\n",
		     ws_sync_counter_query_value (counter));
#endif
	}
	else
	{
#if 0
	    g_print ("wait for %lld\n", value + 1);
#endif
	    ws_sync_alarm_set (alarm, value + 1);
	}
	
	comp_window->alarm = alarm;
	
    }
    
    if (counter)
	return TRUE;
    else
	return FALSE;
    
#if 0
    if (counter)
    {
	g_print ("found counter %lx on %lx\n",
		 WS_RESOURCE_XID (counter),
		 WS_RESOURCE_XID (window));
    }
    else
    {
	g_print ("no counter found for %lx\n", WS_RESOURCE_XID (window));
    }
#endif
    
    return TRUE;
}

void
meta_comp_window_show (MetaCompWindow *comp_window)
{
    if (comp_window->animation_in_progress)
	comp_window->hide_after_animation = FALSE;
    
    cm_drawable_node_set_viewable (CM_DRAWABLE_NODE (comp_window->node), TRUE);
    cm_drawable_node_update_pixmap (CM_DRAWABLE_NODE (comp_window->node));
}

void
meta_comp_window_hide (MetaCompWindow *comp_window)
{
    if (comp_window->animation_in_progress)
    {
	comp_window->hide_after_animation = TRUE;
	return;
    }
    
    cancel_fade (comp_window);
    
    cm_drawable_node_set_viewable (CM_DRAWABLE_NODE (comp_window->node),
				   FALSE);
}

MetaCompWindow *
meta_comp_window_new (MetaScreen     *screen,
		      WsDrawable     *drawable,
		      MetaCompWindowDestroy destroy,
		      gpointer        closure)
{
    MetaDisplay    *display = screen->display;
    MetaCompWindow *window;
    WsRectangle geometry;
    
    ws_drawable_query_geometry (drawable, &geometry);
    
    window = g_new0 (MetaCompWindow, 1);
    
    window->screen = screen;
    window->display = display;
    window->drawable = g_object_ref (drawable);
    window->node = cm_drawable_node_new (drawable, &geometry);
    window->updates = TRUE;
    window->counter_value = 1;
    window->ref_count = 1;
    window->destroy = destroy;
    window->closure = closure;
    
    meta_comp_window_hide (window);
    
    return window;
}

static MetaCompWindow *
comp_window_ref (MetaCompWindow *comp_window)
{
    comp_window->ref_count++;

    return comp_window;
}

static gboolean
comp_window_unref (MetaCompWindow *comp_window)
{
    if (--comp_window->ref_count == 0)
    {
	if (comp_window->destroy)
	    comp_window->destroy (comp_window, comp_window->closure);
	
	g_object_unref (comp_window->drawable);
	g_object_unref (comp_window->node);
	if (comp_window->alarm)
	    g_object_unref (comp_window->alarm);
	memset (comp_window, 'e', sizeof (MetaCompWindow));
	g_free (comp_window);
	
	return TRUE;
    }
    
    return FALSE;
}

gboolean
meta_comp_window_free (MetaCompWindow *window)
{
    return comp_window_unref (window);
}

void
meta_comp_window_set_size (MetaCompWindow *comp_window,
			   WsRectangle    *rect)
{
    if (comp_window->updates)
    {
	WsWindow *window = WS_WINDOW (comp_window->drawable);
	WsDisplay *display = WS_RESOURCE (window)->display;
	CmDrawableNode *dnode = CM_DRAWABLE_NODE (comp_window->node);
	WsRegion *shape;
	
	ws_display_begin_error_trap (display);
	
	cm_drawable_node_set_geometry (dnode, rect);
	shape = ws_window_get_output_shape (window);
	cm_drawable_node_set_shape (dnode, shape);
	ws_region_destroy (shape);
	
	if (rect->width != comp_window->size.width ||
	    rect->height != comp_window->size.height)
	{
	    cm_drawable_node_update_pixmap (dnode);
	}
	
	comp_window->size = *rect;
	
	ws_display_end_error_trap (display);
    }
}

static gboolean
has_type (WsWindow *window, const char *check_type)
{
    gchar **types = ws_window_get_property_atom_list (window, "_NET_WM_WINDOW_TYPE");
    int i;
    gboolean result;
    
    if (!types)
	return FALSE;
    
    result = FALSE;
    
    for (i = 0; types[i] != NULL; ++i)
    {
	gchar *type = types[i];
	
	if (strcmp (type, check_type) == 0)
	{
	    result = TRUE;
	    break;
	}
    }
    
    g_strfreev (types);
    return result;
}

static MetaWindow *
find_meta_window (MetaCompWindow *comp_window)
{
    Window xwindow = WS_RESOURCE_XID (comp_window->drawable);
    MetaWindow *window =
	meta_display_lookup_x_window (comp_window->display, xwindow);
    
    return window;
}

static void
send_configure_notify (WsDrawable *drawable)
{
    WsWindow *window = WS_WINDOW (drawable);
    WsRectangle geo;
    
    ws_drawable_query_geometry (drawable, &geo);
    
#if 0
    g_print ("sending configure notify %d %d %d %d\n",
	     geo.x, geo.y, geo.width, geo.height);
#endif
    
    ws_window_send_configure_notify (
	window, geo.x, geo.y, geo.width, geo.height,
	0 /* border width */, ws_window_query_override_redirect (window));
}

static WsWindow *
find_client_window (MetaCompWindow *comp_window)
{
    MetaWindow *meta_window = find_meta_window (comp_window);
    
    if (meta_window && meta_window->frame)
    {
	WsDisplay *ws_display = WS_RESOURCE (comp_window->drawable)->display;
	
#if 0
	g_print ("framed window (client: %lx)\n", WS_RESOURCE_XID (comp_window->drawable));
#endif
	
#if 0
	g_print ("framed window (client: %lx\n", meta_window->xwindow);
#endif
	
#if 0
	g_print ("framed window: %p\n", comp_window);
#endif
	return ws_window_lookup (ws_display, meta_window->xwindow);
    }
    else
    {
#if 0
	if (meta_window)
	    g_print ("window not framed, but managed (%p)\n", comp_window);
	else
	    g_print ("no meta window %p\n", comp_window);
#endif
	
	return WS_WINDOW (comp_window->drawable);
    }
}

static gboolean
private_metacity_window (MetaCompWindow *comp_window)
{
    /* Returns TRUE if this is a private metacity window
     * such as a tooltip or a menu
     */
    XID xid = WS_RESOURCE_XID (comp_window->drawable);
    
    return meta_ui_window_is_widget (comp_window->screen->ui, xid);
}

static gboolean
frameless_managed (MetaCompWindow *comp_window)
{
    /* For some reason frameless, managed windows don't respond to
     * sync requests messages. FIXME: at some point need to find out
     * what's going on
     */
    MetaWindow *mw = find_meta_window (comp_window);
    
    return mw && !mw->frame;
}

static gdouble
interpolate (gdouble t, gdouble begin, gdouble end, double power)
{
    return (begin + (end - begin) * pow (t, power));
}

static void
interpolate_rectangle (gdouble		t,
		       WsRectangle *	from,
		       WsRectangle *	to,
		       WsRectangle *	result)
{
    if (!result)
	return;
    
    result->x = interpolate (t, from->x, to->x, 1);
    result->y = interpolate (t, from->y, to->y, 1);
    result->width = interpolate (t, from->width, to->width, 1);
    result->height = interpolate (t, from->height, to->height, 1);
}

static void
comp_window_set_target_rect (MetaCompWindow *window,
			     WsRectangle *rect)
{
    cm_drawable_node_set_scale_rect (window->node, rect);
}

static void
comp_window_get_real_size (MetaCompWindow *window,
			   WsRectangle *size)
{
    if (!size)
	return;
    
    cm_drawable_node_get_clipbox (window->node, size);
}

#define FADE_TIME 0.225

typedef struct
{
    MetaEffect		       *effect;
    MetaCompWindow	       *window;
    GTimer *			timer;
    WsRectangle			from;
    WsRectangle			to;
    gboolean			first_time;
    gdouble			start_alpha;
    gdouble			end_alpha;
} FadeInfo;

static gboolean
update_fade (gpointer data)
{
    FadeInfo *info = data;
    gdouble elapsed = g_timer_elapsed (info->timer, NULL);
    gdouble t = elapsed / FADE_TIME;
    
    if (elapsed >= FADE_TIME)
    {
	comp_window_set_target_rect (info->window, &info->to);
	cm_drawable_node_set_alpha (info->window->node, info->end_alpha);
	cm_drawable_node_unset_patch (info->window->node);
	comp_window_unref (info->window);
	return FALSE;
    }
    else
    {
	gdouble alpha = interpolate (t, info->start_alpha, info->end_alpha, 1.0);
	WsRectangle cur;
	
	if (info->first_time)
	{
	    meta_comp_window_show (info->window);
	    info->first_time = FALSE;
	}
	
	interpolate_rectangle (t, &info->from, &info->to, &cur);
	comp_window_set_target_rect (info->window, &cur);
	cm_drawable_node_set_alpha (info->window->node, alpha);
	return TRUE;
    }
}

static void
cancel_fade (MetaCompWindow *comp_window)
{
    if (comp_window->fade_in_idle_id)
    {
	g_source_remove (comp_window->fade_in_idle_id);
	comp_window->fade_in_idle_id = 0;
    }
}

static void
meta_comp_window_fade_in (MetaCompWindow *comp_window)
{
    FadeInfo *info = g_new0 (FadeInfo, 1);
    WsWindow *window = find_client_window (comp_window);
    
    if (comp_window->fade_in_idle_id)
	return;
    
    info->window = comp_window_ref (comp_window);
    info->timer = g_timer_new ();
    
    comp_window_get_real_size (info->window, &info->to);
    info->from = info->to;
    
    info->start_alpha = 0.1;
    info->first_time = TRUE;
    
    if (has_type (window, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU") ||
	has_type (window, "_NET_WM_WINDOW_TYPE_POPUP_MENU"))
    {
	info->end_alpha = 0.9;
	info->from.width *= 0.6;
	info->from.height *= 0.4;
    }
    else if (has_type (window, "_NET_WM_WINDOW_TYPE_DIALOG"))
    {
	info->end_alpha = 0.9;
    }
    else
    {
	info->end_alpha = 1.0;
    }
    
    comp_window->fade_in_idle_id = g_idle_add (update_fade, info);
}

static void
on_request_alarm (WsSyncAlarm *alarm,
		  WsAlarmNotifyEvent *event,
		  MetaCompWindow *comp_window)
{
    /* This alarm means that the window is ready to be shown on screen */

    if (comp_window->unminimize_info)
	start_unminimize (comp_window);
    else
	meta_comp_window_fade_in (comp_window);
    
    g_object_unref (alarm);
}

static gboolean
send_sync_request (MetaCompWindow *comp_window)
{
    WsDisplay *display;
    WsWindow *client_window = find_client_window (comp_window);
    WsSyncCounter *request_counter;
    WsSyncAlarm *alarm;
    guint32 msg[5];
    display = WS_RESOURCE (comp_window->drawable)->display;
    ws_display_init_sync (display);
    
    if (!client_window)
	return FALSE;
    
    request_counter = ws_window_get_property_sync_counter (
	client_window, "_NET_WM_SYNC_REQUEST_COUNTER");
    
    if (!request_counter)
	return FALSE;
    
    comp_window->counter_value = ws_sync_counter_query_value (request_counter) + 1;
    
    msg[0] = comp_window->display->atom_net_wm_sync_request;
    msg[1] = meta_display_get_current_time (comp_window->display);
    msg[2] = comp_window->counter_value & 0xffffffff;
    msg[3] = (comp_window->counter_value >> 32) & 0xffffffff;
    
    alarm = ws_sync_alarm_new (display, request_counter);
    
    ws_sync_alarm_set (alarm, comp_window->counter_value);
    
    g_signal_connect (alarm, "alarm_notify_event",
		      G_CALLBACK (on_request_alarm), comp_window);
    
    ws_window_send_client_message (client_window,
				   "WM_PROTOCOLS", msg);
    
    send_configure_notify (WS_DRAWABLE (client_window));
    
    ws_display_flush (WS_RESOURCE (client_window)->display);
    
    return TRUE;
}

void
meta_comp_window_refresh_attrs (MetaCompWindow *comp_window)
{
    /* FIXME: this function should not exist - the real problem is
     * probably in meta_screen_info_add_window() where it it called.
     */
    
    double alpha = 1.0;
    CmDrawableNode *node = CM_DRAWABLE_NODE (comp_window->node);
    
#if 0
    g_print ("waiting for paint: %d\n", comp_window->waiting_for_paint);
#endif
    
    if (ws_window_query_mapped (WS_WINDOW (comp_window->drawable))
#if 0
	&& !comp_window->waiting_for_paint
#endif
	)
    {
	WsWindow *window = WS_WINDOW (comp_window->drawable);
	
	cm_drawable_node_unset_patch (CM_DRAWABLE_NODE (node));
	
	if (has_type (window, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"))
	{
	    alpha = 0.9;
	}
	else if (has_type (window, "_NET_WM_WINDOW_TYPE_POPUP_MENU"))
	{
	    alpha = 0.9;
	}
	else
	{
	    alpha = 1.0;
	}
	
	cm_drawable_node_set_alpha (node, alpha);
	
	if (!cm_drawable_node_get_viewable (node))
	{
	    comp_window->waiting_for_paint = TRUE;
#if 0
	    alarm = ws_alarm_new (comp_window->display);
#endif
#if 0
	    finish_counter = ws_window_get_property_sync_counter (
		window, "_NET_WM_FINISH_FRAME_COUNTER");
#endif
	    
	    /* For some reason the panel and nautilus don't respond to the
	     * sync counter stuff. FIXME: this should be figured out at
	     * some point.
	     */
	    if (frameless_managed (comp_window) ||
		private_metacity_window (comp_window) ||
		!send_sync_request (comp_window))
	    {
		meta_comp_window_show (comp_window);
	    }
	}
    }
    else
    {
#if 0
	g_print ("unmapping %p\n", node);
#endif
	
	meta_comp_window_hide (comp_window);
    }
}

void
meta_comp_window_set_updates (MetaCompWindow *comp_window,
			      gboolean	      updates)
{
    CmDrawableNode *node = CM_DRAWABLE_NODE (comp_window->node);
    
    comp_window->updates = updates;
    
    cm_drawable_node_set_updates (node, updates);
    
    if (updates)
    {
	WsRectangle rect;
	WsRegion *shape;
	WsDisplay *display = WS_RESOURCE (node->drawable)->display;
	
	ws_display_begin_error_trap (display);
	ws_drawable_query_geometry (node->drawable, &rect);
	cm_drawable_node_update_pixmap (node);
	cm_drawable_node_set_geometry (node, &rect);
	shape = ws_window_get_output_shape (WS_WINDOW (node->drawable));
	cm_drawable_node_set_shape (node, shape);
	ws_region_destroy (shape);
	ws_display_end_error_trap (display);
    }
}

CmNode *
meta_comp_window_get_node (MetaCompWindow *comp_window)
{
    return CM_NODE (comp_window->node);
}

/*
 * Explosion effect
 */
#define EXPLODE_TIME 1.0

#define BASE 0.5

static double
transform (double in)
{
    return (pow (BASE, in) - 1) / (BASE - 1);
}

typedef struct
{
    MetaEffect		       *effect;
    MetaCompWindow             *comp_window;
    gdouble			level;
    GTimer *			timer;
} ExplodeInfo;

static gboolean
update_explosion (gpointer data)
{
    ExplodeInfo *info = data;
    CmDrawableNode *node = CM_DRAWABLE_NODE (info->comp_window->node);
    gdouble elapsed = g_timer_elapsed (info->timer, NULL);
    
    if (!cm_drawable_node_get_viewable (node) || elapsed > EXPLODE_TIME)
    {
	meta_effect_end (info->effect);
	
	info->comp_window->animation_in_progress = FALSE;
	if (info->comp_window->hide_after_animation)
	    meta_comp_window_hide (info->comp_window);
	
	cm_drawable_node_set_explosion_level (node, 0.0);
	
	comp_window_unref (info->comp_window);
	return FALSE;
    }
    else
    {
	gdouble t = elapsed / EXPLODE_TIME;
	
	cm_drawable_node_set_explosion_level (
	    node, transform (t));
	return TRUE;
    }
}

void
meta_comp_window_explode (MetaCompWindow *comp_window,
			  MetaEffect *effect)
{
    ExplodeInfo *info = g_new0 (ExplodeInfo, 1);
    
    if (!cm_drawable_node_get_viewable (comp_window->node))
	return;
    
    comp_window->animation_in_progress = TRUE;
    
    info->comp_window = comp_window_ref (comp_window);
    info->effect = effect;
    info->level = 0.0;
    info->timer = g_timer_new ();
    
    g_idle_add (update_explosion, info);
}

/* shrinkydink minimize effect */

#define N_PHASES 5

typedef struct
{
    WsRectangle start_rect;
    WsRectangle end_rect;
    gdouble	start_alpha;
    gdouble	end_alpha;
    gdouble	start_time;
    gdouble	end_time;
} Phase;

typedef struct
{
    GTimer *timer;
    
    MetaCompWindow *comp_window;
    
    MetaEffect *effect;
    
    Phase	phases[N_PHASES];
} MiniInfo;

static void
set_geometry (MetaCompWindow *window,
	      Phase          *phase,
	      gdouble         elapsed)
{
    WsRectangle rect;
    gdouble alpha;
    gdouble t = (elapsed - phase->start_time) / (phase->end_time - phase->start_time);
    
    interpolate_rectangle (t, &phase->start_rect, &phase->end_rect, &rect);
    alpha = interpolate (t, phase->start_alpha, phase->end_alpha, 1.0);
    
    comp_window_set_target_rect (window, &rect);
    cm_drawable_node_set_alpha (window->node, alpha);
}

static int
center (gdouble what, gdouble in)
{
    return (in - what) / 2.0 + 0.5;
}

#define WOBBLE_FACTOR 1.5

static void
generate_phases (WsRectangle *start,
		 WsRectangle *icon,
		 Phase        phases[N_PHASES])
{
    const double phase_times[5] = {
	0.225,				/* scale to size of button */
	0.100,				/* scale up a little */
	0.100,				/* scale back a little */
	0.100,				/* drop down to icon */
	0.350,				/* fade out */
    };
    
    WsRectangle cur;
    gdouble alpha;
    gdouble aspect_ratio;
    gdouble time;
    int i;
    
    aspect_ratio = (double)start->width / (double)start->height;
    cur = *start;
    time = 0.0;
    alpha = 1.0;
    for (i = 0; i < N_PHASES; ++i)
    {
	Phase *phase = &(phases[i]);
	WsRectangle *end = &(phase->end_rect);
	
	phase->start_time = time;
	phase->start_rect = cur;
	phase->start_alpha = alpha;
	phase->end_alpha = 1.0;
	phase->end_time = time + phase_times[i];
	
	if (i == 0)
	{
	    /* Shrink to a little rectangle */
	    end->height = icon->height;
	    end->width = icon->height * aspect_ratio;
#if 0
	    end->x = icon->x + center (end->width, icon->width);
#endif
	    end->x = cur.x + center (end->width, cur.width);
	    end->y = cur.y + center (icon->height, cur.height);
	}
	else if (i == 1)
	{
	    /* Zoom out a little */
	    end->x = cur.x + center (WOBBLE_FACTOR * cur.width, cur.width);
	    end->y = cur.y + center (WOBBLE_FACTOR * cur.height, cur.height);
	    end->width = cur.width * WOBBLE_FACTOR;
	    end->height = cur.height * WOBBLE_FACTOR;
	}
	else if (i == 2)
	{
	    /* Zoom back */
	    end->height = icon->height;
	    end->width = icon->height * aspect_ratio;
#if 0
	    end->x = icon->x + center (end->width, icon->width);
#endif
	    end->x = cur.x + center (end->width, cur.width);
	    end->y = cur.y + center (icon->height, cur.height);
	}
	else if (i == 3)
	{
	    /* Move down to the button */
	    end->height = icon->height;
	    end->width = icon->height * aspect_ratio;
	    end->x = icon->x + center (end->width, icon->width);
	    end->y = icon->y;
	}
	else if (i == 4)
	{
	    /* Fade out */
	    end->x = icon->x;
	    end->y = icon->y;
	    end->width = icon->width;
	    end->height = icon->height;

	    phases[i].end_alpha = 0.0;
	}

	alpha = phase->end_alpha;
	cur = phase->end_rect;
	time += phase_times[i];
    }
}

static gboolean
update_minimize (gpointer data)
{
    MiniInfo *info = data;
    Phase *current_phase;
    int i;
    gdouble elapsed = g_timer_elapsed (info->timer, NULL);

    current_phase = NULL;
    for (i = 0; i < N_PHASES; ++i)
    {
	Phase *p = &(info->phases[i]);
	
	if (p->start_time < elapsed && p->end_time >= elapsed)
	{
	    current_phase = p;
	    break;
	}
    }

    if (current_phase)
    {
	set_geometry (info->comp_window, current_phase, elapsed);
	return TRUE;
    }
    else
    {
	meta_comp_window_hide (info->comp_window);
	cm_drawable_node_set_alpha (info->comp_window->node, 1.0);
	cm_drawable_node_unset_patch (info->comp_window->node);
	comp_window_unref (info->comp_window);

	meta_effect_end (info->effect);
	
	return FALSE;
    }
}

static void
meta_rect_to_ws_rect (MetaRectangle *mrect,
		      WsRectangle   *wrect)
{
    if (!mrect || !wrect)
	return;

    wrect->x = mrect->x;
    wrect->y = mrect->y;
    wrect->width = mrect->width;
    wrect->height = mrect->height;
}

void
meta_comp_window_run_minimize (MetaCompWindow           *window,
			       MetaEffect               *effect)
{
    MiniInfo *info = g_new (MiniInfo, 1);
    WsRectangle start, end;

    info->timer = g_timer_new ();
    
    info->comp_window = comp_window_ref (window);

    info->effect = effect;

    meta_rect_to_ws_rect (&(effect->u.minimize.window_rect), &start);
    meta_rect_to_ws_rect (&(effect->u.minimize.icon_rect), &end);
    
    generate_phases (&start, &end, info->phases);
    
    g_idle_add (update_minimize, info);
}

struct UnminimizeInfo
{
    GTimer *timer;
    
    MetaCompWindow *comp_window;
    
    Phase	phases[N_PHASES];
    gboolean	first_time;
};

#define WOBBLE_FACTOR_OUT 0.7

static void
generate_unminimize_phases (WsRectangle *icon,
			    WsRectangle *full,
			    Phase        phases[N_PHASES])
{
    const double phase_times[5] = {
	0.350,				/* fade in */
	0.100,				/* move up from icon */
	0.225,				/* scale to full size */
	0.100,				/* scale down a little */
	0.100,			        /* scale to full size */
    };
    
    WsRectangle cur;
    gdouble aspect_ratio;
    gdouble time;
    int i;
    
    aspect_ratio = (double)full->width / (double)full->height;
    cur = *icon;
    time = 0.0;
    for (i = 0; i < N_PHASES; ++i)
    {
	Phase *phase = &(phases[i]);
	WsRectangle *end = &(phase->end_rect);
	
	phase->start_time = time;
	phase->start_rect = cur;
	phase->start_alpha = 1.0;
	phase->end_alpha = 1.0;
	phase->end_time = time + phase_times[i];
	
	if (i == 0)
	{
	    /* Fade in */
	    phase->end_alpha = 1.0;
	    phase->start_alpha = 0.0;
	    end->height = icon->height;
	    end->width = icon->height * aspect_ratio;
	    end->x = icon->x + center (end->width, icon->width);
	    end->y = icon->y;
	}
	else if (i == 1)
	{
	    /* Move up from icon */
	    end->width = cur.width;
	    end->height = cur.height;
#if 0
	    end->x = cur.x;
#endif
	    end->x = full->x + center (end->width, full->width);
	    end->y = full->y + center (icon->height, full->height);
	}
	else if (i == 2)
	{
	    /* Zoom to full size */
	    *end = *full;
	}
	else if (i == 3)
	{
	    /* Scale down a little */
	    end->x = cur.x + center (cur.width * WOBBLE_FACTOR_OUT, cur.width);
	    end->y = cur.y + center (cur.height * WOBBLE_FACTOR_OUT, cur.height);
	    end->width = cur.width * WOBBLE_FACTOR_OUT;
	    end->height = cur.height * WOBBLE_FACTOR_OUT;
	}
	else if (i == 4)
	{
	    /* Scale up to full size again */
	    *end = *full;
	}

	cur = phase->end_rect;
	time += phase_times[i];
    }
}

static gboolean
update_unminimize (gpointer data)
{
    UnminimizeInfo *info = data;
    Phase *current_phase;
    int i;
    gdouble elapsed = g_timer_elapsed (info->timer, NULL);

    current_phase = NULL;
    for (i = 0; i < N_PHASES; ++i)
    {
	Phase *p = &(info->phases[i]);
	
	if (p->start_time < elapsed && p->end_time >= elapsed)
	{
	    current_phase = p;
	    break;
	}
    }

    if (current_phase)
    {
	if (info->first_time)
	{
	    meta_comp_window_show (info->comp_window);
	    info->first_time = FALSE;
	}
	
	set_geometry (info->comp_window, current_phase, elapsed);
	return TRUE;
    }
    else
    {
	cm_drawable_node_set_alpha (info->comp_window->node, 1.0);
	cm_drawable_node_unset_patch (info->comp_window->node);
	comp_window_unref (info->comp_window);
#if 0
	g_print ("done\n");
#endif

	return FALSE;
    }
    
}

static void
start_unminimize (MetaCompWindow *comp_window)
{
    UnminimizeInfo *info = comp_window->unminimize_info;

    if (!info)
	return;

    comp_window->unminimize_info = NULL;

    info->timer = g_timer_new ();
    info->first_time = TRUE;
    
    g_idle_add (update_unminimize, info);
}

void
meta_comp_window_run_unminimize (MetaCompWindow *comp_window,
				 MetaEffect     *effect)
{
    WsRectangle start, end;
    UnminimizeInfo *info = g_new0 (UnminimizeInfo, 1);

    meta_rect_to_ws_rect (&(effect->u.unminimize.icon_rect), &start);
    meta_rect_to_ws_rect (&(effect->u.unminimize.window_rect), &end);
    
    generate_unminimize_phases (&start, &end, info->phases);

    info->comp_window = comp_window_ref (comp_window);
    
    comp_window->unminimize_info = info;
    
    meta_effect_end (effect);
}

/* bounce effect */

typedef struct
{
    MetaEffect	    *effect;
    MetaCompWindow  *window;
    GTimer	    *timer;
    Model	    *model;
    MetaRectangle   rect;
    gdouble	    last_time;
} FocusInfo;

static gboolean
update_focus (gpointer data)
{
    FocusInfo *info = data;
    CmDrawableNode *node = (CmDrawableNode *)info->window->node;
    gdouble elapsed = g_timer_elapsed (info->timer, NULL);
    int i;
    int n_steps = floor ((elapsed - info->last_time) * 60);
    CmPoint points[4][4];

    if (model_is_calm (info->model) || elapsed > 0.7)
    {
	cm_drawable_node_unset_patch (node);
	meta_effect_end (info->effect);
	g_free(info);
	return FALSE;
    }

    for (i = 0; i < n_steps; ++i)
	model_step (info->model);

    if (i > 0)
	info->last_time = elapsed;

    get_patch_points (info->model, points);

    cm_drawable_node_set_patch (node, points);
    return TRUE;
}

void
meta_comp_window_run_focus (MetaCompWindow *comp_window,
			    MetaEffect     *effect)
{
    FocusInfo *info = g_new0 (FocusInfo, 1);
    MetaWindow *meta_window =
	meta_display_lookup_x_window (comp_window->display,
				      WS_RESOURCE_XID (comp_window->drawable));

    info->window = comp_window;
    info->effect = effect;
    info->timer = g_timer_new ();
    info->last_time = 0;

    compute_window_rect (meta_window, &info->rect);
    info->model = model_new (&info->rect, TRUE);

    g_idle_add (update_focus, info);
}

void
meta_comp_window_freeze_stack (MetaCompWindow *comp_window)
{
    comp_window->stack_freeze_count++;
}

void
meta_comp_window_thaw_stack (MetaCompWindow *comp_window)
{
    comp_window->stack_freeze_count--;
}

gboolean
meta_comp_window_stack_frozen (MetaCompWindow *comp_window)
{
    return comp_window->stack_freeze_count > 0;
}

#endif



syntax highlighted by Code2HTML, v. 0.9.1