mutter/src/effects.c
Elijah Newren 0201fcfc6c Stick an emacs comment directive at the beginning of all the code files so
2006-10-01  Elijah Newren  <newren gmail com>

	* src/*.[ch]: Stick an emacs comment directive at the beginning of
	all the code files so that people using emacs will be more likely
	to get coding style correct in their patches.  We still need a
	similar vi directive.  #358866
2006-10-01 22:30:10 +00:00

787 lines
23 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* Metacity animation effects */
/*
* Copyright (C) 2001 Anders Carlsson, Havoc Pennington
*
* 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>
#include "effects.h"
#include "display.h"
#include "ui.h"
#include "window.h"
#ifdef HAVE_SHAPE
#include <X11/extensions/shape.h>
#endif
#include <string.h>
typedef enum
{
META_ANIMATION_DRAW_ROOT,
META_ANIMATION_WINDOW_WIREFRAME,
META_ANIMATION_WINDOW_OPAQUE
} MetaAnimationStyle;
typedef struct
{
MetaScreen *screen;
double millisecs_duration;
GTimeVal start_time;
gboolean first_time;
MetaRectangle start_rect;
MetaRectangle end_rect;
/* rect to erase */
MetaRectangle last_rect;
/* used instead of the global flag, since
* we don't want to change midstream.
*/
MetaAnimationStyle style;
/* For wireframe drawn on root window */
GC gc;
/* For wireframe window */
Window wireframe_xwindow;
/* For opaque */
MetaImageWindow *image_window;
GdkPixbuf *orig_pixbuf;
MetaBoxAnimType anim_type;
} BoxAnimationContext;
struct MetaEffectPriv
{
MetaEffectFinished finished;
gpointer finished_data;
};
static void run_default_effect_handler (MetaEffect *effect);
static void run_handler (MetaEffect *effect);
static MetaEffectHandler effect_handler;
static gpointer effect_handler_data;
void
meta_push_effect_handler (MetaEffectHandler handler,
gpointer data)
{
effect_handler = handler;
effect_handler_data = data;
}
void
meta_pop_effect_handler (void)
{
/* FIXME: not implemented yet */
g_assert_not_reached ();
}
static MetaEffect *
create_effect (MetaEffectType type,
MetaWindow *window,
MetaEffectFinished finished,
gpointer finished_data)
{
MetaEffect *effect = g_new (MetaEffect, 1);
effect->type = type;
effect->window = window;
effect->priv = g_new (MetaEffectPriv, 1);
effect->priv->finished = finished;
effect->priv->finished_data = finished_data;
return effect;
}
void
meta_effect_end (MetaEffect *effect)
{
if (effect->priv->finished)
effect->priv->finished (effect, effect->priv->finished_data);
g_free (effect->priv);
g_free (effect);
}
void
meta_effect_run_focus (MetaWindow *window,
MetaEffectFinished finished,
gpointer data)
{
MetaEffect *effect;
g_return_if_fail (window != NULL);
effect = create_effect (META_EFFECT_FOCUS, window, finished, data);
run_handler (effect);
}
void
meta_effect_run_minimize (MetaWindow *window,
MetaRectangle *window_rect,
MetaRectangle *icon_rect,
MetaEffectFinished finished,
gpointer data)
{
MetaEffect *effect;
g_return_if_fail (window != NULL);
g_return_if_fail (icon_rect != NULL);
effect = create_effect (META_EFFECT_MINIMIZE, window, finished, data);
effect->u.minimize.window_rect = *window_rect;
effect->u.minimize.icon_rect = *icon_rect;
run_handler (effect);
}
void
meta_effect_run_unminimize (MetaWindow *window,
MetaRectangle *window_rect,
MetaRectangle *icon_rect,
MetaEffectFinished finished,
gpointer data)
{
MetaEffect *effect;
g_return_if_fail (window != NULL);
g_return_if_fail (icon_rect != NULL);
effect = create_effect (META_EFFECT_UNMINIMIZE, window, finished, data);
effect->u.minimize.window_rect = *window_rect;
effect->u.minimize.icon_rect = *icon_rect;
run_handler (effect);
}
void
meta_effect_run_close (MetaWindow *window,
MetaEffectFinished finished,
gpointer data)
{
MetaEffect *effect;
g_return_if_fail (window != NULL);
effect = create_effect (META_EFFECT_CLOSE, window,
finished, data);
run_handler (effect);
}
/* old ugly minimization effect */
static void
update_wireframe_window (MetaDisplay *display,
Window xwindow,
const MetaRectangle *rect)
{
XMoveResizeWindow (display->xdisplay,
xwindow,
rect->x, rect->y,
rect->width, rect->height);
#ifdef HAVE_SHAPE
#define OUTLINE_WIDTH 3
if (rect->width > OUTLINE_WIDTH * 2 &&
rect->height > OUTLINE_WIDTH * 2)
{
XRectangle xrect;
Region inner_xregion;
Region outer_xregion;
inner_xregion = XCreateRegion ();
outer_xregion = XCreateRegion ();
xrect.x = 0;
xrect.y = 0;
xrect.width = rect->width;
xrect.height = rect->height;
XUnionRectWithRegion (&xrect, outer_xregion, outer_xregion);
xrect.x += OUTLINE_WIDTH;
xrect.y += OUTLINE_WIDTH;
xrect.width -= OUTLINE_WIDTH * 2;
xrect.height -= OUTLINE_WIDTH * 2;
XUnionRectWithRegion (&xrect, inner_xregion, inner_xregion);
XSubtractRegion (outer_xregion, inner_xregion, outer_xregion);
XShapeCombineRegion (display->xdisplay, xwindow,
ShapeBounding, 0, 0, outer_xregion, ShapeSet);
XDestroyRegion (outer_xregion);
XDestroyRegion (inner_xregion);
}
else
{
/* Unset the shape */
XShapeCombineMask (display->xdisplay, xwindow,
ShapeBounding, 0, 0, None, ShapeSet);
}
#endif
}
static void
graphics_sync (BoxAnimationContext *context)
{
XImage *image;
/* A hack to force the X server to synchronize with the
* graphics hardware
*/
image = XGetImage (context->screen->display->xdisplay,
context->screen->xroot,
0, 0, 1, 1,
AllPlanes, ZPixmap);
XDestroyImage (image);
}
static gboolean
effects_draw_box_animation_timeout (BoxAnimationContext *context)
{
double elapsed;
GTimeVal current_time;
MetaRectangle draw_rect;
double fraction;
if (!context->first_time)
{
if (context->style == META_ANIMATION_DRAW_ROOT)
{
/* Restore the previously drawn background */
XDrawRectangle (context->screen->display->xdisplay,
context->screen->xroot,
context->gc,
context->last_rect.x, context->last_rect.y,
context->last_rect.width, context->last_rect.height);
}
}
context->first_time = FALSE;
g_get_current_time (&current_time);
/* We use milliseconds for all times */
elapsed =
((((double)current_time.tv_sec - context->start_time.tv_sec) * G_USEC_PER_SEC +
(current_time.tv_usec - context->start_time.tv_usec))) / 1000.0;
if (elapsed < 0)
{
/* Probably the system clock was set backwards? */
meta_warning ("System clock seemed to go backwards?\n");
elapsed = G_MAXDOUBLE; /* definitely done. */
}
if (elapsed > context->millisecs_duration)
{
/* All done */
if (context->style == META_ANIMATION_WINDOW_OPAQUE)
{
g_object_unref (G_OBJECT (context->orig_pixbuf));
meta_image_window_free (context->image_window);
}
else if (context->style == META_ANIMATION_DRAW_ROOT)
{
meta_display_ungrab (context->screen->display);
meta_ui_pop_delay_exposes (context->screen->ui);
XFreeGC (context->screen->display->xdisplay,
context->gc);
}
else if (context->style == META_ANIMATION_WINDOW_WIREFRAME)
{
XDestroyWindow (context->screen->display->xdisplay,
context->wireframe_xwindow);
}
graphics_sync (context);
g_free (context);
return FALSE;
}
g_assert (context->millisecs_duration > 0.0);
fraction = elapsed / context->millisecs_duration;
draw_rect = context->start_rect;
/* Now add a delta proportional to elapsed time. */
draw_rect.x += (context->end_rect.x - context->start_rect.x) * fraction;
draw_rect.y += (context->end_rect.y - context->start_rect.y) * fraction;
draw_rect.width += (context->end_rect.width - context->start_rect.width) * fraction;
draw_rect.height += (context->end_rect.height - context->start_rect.height) * fraction;
/* don't confuse X or gdk-pixbuf with bogus rectangles */
if (draw_rect.width < 1)
draw_rect.width = 1;
if (draw_rect.height < 1)
draw_rect.height = 1;
context->last_rect = draw_rect;
if (context->style == META_ANIMATION_WINDOW_OPAQUE)
{
GdkPixbuf *scaled;
scaled = NULL;
switch (context->anim_type)
{
case META_BOX_ANIM_SCALE:
scaled = gdk_pixbuf_scale_simple (context->orig_pixbuf,
draw_rect.width,
draw_rect.height,
GDK_INTERP_BILINEAR);
break;
case META_BOX_ANIM_SLIDE_UP:
{
int x, y;
x = context->start_rect.width - draw_rect.width;
y = context->start_rect.height - draw_rect.height;
/* paranoia */
if (x < 0)
x = 0;
if (y < 0)
y = 0;
scaled = gdk_pixbuf_new_subpixbuf (context->orig_pixbuf,
x, y,
draw_rect.width,
draw_rect.height);
}
break;
}
/* handle out-of-memory */
if (scaled != NULL)
{
meta_image_window_set (context->image_window,
scaled,
draw_rect.x, draw_rect.y);
g_object_unref (G_OBJECT (scaled));
}
}
else if (context->style == META_ANIMATION_DRAW_ROOT)
{
/* Draw the rectangle */
XDrawRectangle (context->screen->display->xdisplay,
context->screen->xroot,
context->gc,
draw_rect.x, draw_rect.y,
draw_rect.width, draw_rect.height);
}
else if (context->style == META_ANIMATION_WINDOW_WIREFRAME)
{
update_wireframe_window (context->screen->display,
context->wireframe_xwindow,
&draw_rect);
}
/* kick changes onto the server */
graphics_sync (context);
return TRUE;
}
/* I really don't want this to be a configuration option, but I think
* the wireframe is sucky from a UI standpoint (more confusing than
* opaque), but the opaque is definitely still too slow on some
* systems, and also doesn't look quite right due to the mapping
* and unmapping of windows that's going on.
*/
static MetaAnimationStyle animation_style = META_ANIMATION_WINDOW_WIREFRAME;
void
meta_effects_draw_box_animation (MetaScreen *screen,
MetaRectangle *initial_rect,
MetaRectangle *destination_rect,
double seconds_duration,
MetaBoxAnimType anim_type)
{
BoxAnimationContext *context;
g_return_if_fail (seconds_duration > 0.0);
if (g_getenv ("METACITY_DEBUG_EFFECTS"))
seconds_duration *= 10; /* slow things down */
/* Create the animation context */
context = g_new0 (BoxAnimationContext, 1);
context->screen = screen;
context->millisecs_duration = seconds_duration * 1000.0;
context->first_time = TRUE;
context->start_rect = *initial_rect;
context->end_rect = *destination_rect;
context->anim_type = anim_type;
context->style = animation_style;
#ifndef HAVE_SHAPE
if (context->style == META_ANIMATION_WINDOW_WIREFRAME)
context->style = META_ANIMATION_DRAW_ROOT;
#endif
if (context->style == META_ANIMATION_WINDOW_OPAQUE)
{
GdkPixbuf *pix;
pix = meta_gdk_pixbuf_get_from_window (NULL,
screen->xroot,
initial_rect->x,
initial_rect->y,
0, 0,
initial_rect->width,
initial_rect->height);
if (pix == NULL)
{
/* Fall back to wireframe */
context->style = META_ANIMATION_WINDOW_WIREFRAME;
}
else
{
context->image_window = meta_image_window_new (screen->display->xdisplay,
screen->number,
initial_rect->width,
initial_rect->height);
context->orig_pixbuf = pix;
meta_image_window_set (context->image_window,
context->orig_pixbuf,
initial_rect->x,
initial_rect->y);
meta_image_window_set_showing (context->image_window, TRUE);
}
}
/* Not an else, so that fallback works */
if (context->style == META_ANIMATION_WINDOW_WIREFRAME)
{
XSetWindowAttributes attrs;
attrs.override_redirect = True;
attrs.background_pixel = BlackPixel (screen->display->xdisplay,
screen->number);
context->wireframe_xwindow = XCreateWindow (screen->display->xdisplay,
screen->xroot,
initial_rect->x,
initial_rect->y,
initial_rect->width,
initial_rect->height,
0,
CopyFromParent,
CopyFromParent,
(Visual *)CopyFromParent,
CWOverrideRedirect | CWBackPixel,
&attrs);
update_wireframe_window (screen->display,
context->wireframe_xwindow,
initial_rect);
XMapWindow (screen->display->xdisplay,
context->wireframe_xwindow);
}
if (context->style == META_ANIMATION_DRAW_ROOT)
{
XGCValues gc_values;
gc_values.subwindow_mode = IncludeInferiors;
gc_values.function = GXinvert;
context->gc = XCreateGC (screen->display->xdisplay,
screen->xroot,
GCSubwindowMode | GCFunction,
&gc_values);
/* Grab the X server to avoid screen dirt */
meta_display_grab (context->screen->display);
meta_ui_push_delay_exposes (context->screen->ui);
}
/* Do this only after we get the pixbuf from the server,
* so that the animation doesn't get truncated.
*/
g_get_current_time (&context->start_time);
/* Add the timeout - a short one, could even use an idle,
* but this is maybe more CPU-friendly.
*/
g_timeout_add (15,
(GSourceFunc)effects_draw_box_animation_timeout,
context);
/* kick changes onto the server */
XFlush (context->screen->display->xdisplay);
}
void
meta_effects_begin_wireframe (MetaScreen *screen,
const MetaRectangle *rect,
int width,
int height)
{
/* Grab the X server to avoid screen dirt */
meta_display_grab (screen->display);
meta_ui_push_delay_exposes (screen->ui);
meta_effects_update_wireframe (screen,
NULL, -1, -1,
rect, width, height);
}
static void
draw_xor_rect (MetaScreen *screen,
const MetaRectangle *rect,
int width,
int height)
{
/* The lines in the center can't overlap the rectangle or each
* other, or the XOR gets reversed. So we have to draw things
* a bit oddly.
*/
XSegment segments[8];
MetaRectangle shrunk_rect;
int i;
#define LINE_WIDTH META_WIREFRAME_XOR_LINE_WIDTH
/* We don't want the wireframe going outside the window area.
* It makes it harder for the user to position windows and it exposes other
* annoying bugs.
*/
shrunk_rect = *rect;
shrunk_rect.x += LINE_WIDTH / 2 + LINE_WIDTH % 2;
shrunk_rect.y += LINE_WIDTH / 2 + LINE_WIDTH % 2;
shrunk_rect.width -= LINE_WIDTH + 2 * (LINE_WIDTH % 2);
shrunk_rect.height -= LINE_WIDTH + 2 * (LINE_WIDTH % 2);
XDrawRectangle (screen->display->xdisplay,
screen->xroot,
screen->root_xor_gc,
shrunk_rect.x, shrunk_rect.y,
shrunk_rect.width, shrunk_rect.height);
/* Don't put lines inside small rectangles where they won't fit */
if (shrunk_rect.width < (LINE_WIDTH * 4) ||
shrunk_rect.height < (LINE_WIDTH * 4))
return;
if ((width >= 0) && (height >= 0))
{
XGCValues gc_values = { 0 };
if (XGetGCValues (screen->display->xdisplay,
screen->root_xor_gc,
GCFont, &gc_values))
{
char *text;
int text_length;
XFontStruct *font_struct;
int text_width, text_height;
int box_x, box_y;
int box_width, box_height;
font_struct = XQueryFont (screen->display->xdisplay,
gc_values.font);
if (font_struct != NULL)
{
text = g_strdup_printf ("%d x %d", width, height);
text_length = strlen (text);
text_width = text_length * font_struct->max_bounds.width;
text_height = font_struct->max_bounds.descent +
font_struct->max_bounds.ascent;
box_width = text_width + 2 * LINE_WIDTH;
box_height = text_height + 2 * LINE_WIDTH;
box_x = shrunk_rect.x + (shrunk_rect.width - box_width) / 2;
box_y = shrunk_rect.y + (shrunk_rect.height - box_height) / 2;
if ((box_width < shrunk_rect.width) &&
(box_height < shrunk_rect.height))
{
XFillRectangle (screen->display->xdisplay,
screen->xroot,
screen->root_xor_gc,
box_x, box_y,
box_width, box_height);
XDrawString (screen->display->xdisplay,
screen->xroot,
screen->root_xor_gc,
box_x + LINE_WIDTH,
box_y + LINE_WIDTH + font_struct->max_bounds.ascent,
text, text_length);
}
g_free (text);
XFreeFontInfo (NULL, font_struct, 1);
if ((box_width + LINE_WIDTH) >= (shrunk_rect.width / 3))
return;
if ((box_height + LINE_WIDTH) >= (shrunk_rect.height / 3))
return;
}
}
}
/* Two vertical lines at 1/3 and 2/3 */
segments[0].x1 = shrunk_rect.x + shrunk_rect.width / 3;
segments[0].y1 = shrunk_rect.y + LINE_WIDTH / 2 + LINE_WIDTH % 2;
segments[0].x2 = segments[0].x1;
segments[0].y2 = shrunk_rect.y + shrunk_rect.height - LINE_WIDTH / 2;
segments[1] = segments[0];
segments[1].x1 = shrunk_rect.x + (shrunk_rect.width / 3) * 2;
segments[1].x2 = segments[1].x1;
/* Now make two horizontal lines at 1/3 and 2/3, but not
* overlapping the verticals
*/
segments[2].x1 = shrunk_rect.x + LINE_WIDTH / 2 + LINE_WIDTH % 2;
segments[2].x2 = segments[0].x1 - LINE_WIDTH / 2;
segments[2].y1 = shrunk_rect.y + shrunk_rect.height / 3;
segments[2].y2 = segments[2].y1;
segments[3] = segments[2];
segments[3].x1 = segments[2].x2 + LINE_WIDTH;
segments[3].x2 = segments[1].x1 - LINE_WIDTH / 2;
segments[4] = segments[3];
segments[4].x1 = segments[3].x2 + LINE_WIDTH;
segments[4].x2 = shrunk_rect.x + shrunk_rect.width - LINE_WIDTH / 2;
/* Second horizontal line is just like the first, but
* shifted down
*/
i = 5;
while (i < 8)
{
segments[i] = segments[i - 3];
segments[i].y1 = shrunk_rect.y + (shrunk_rect.height / 3) * 2;
segments[i].y2 = segments[i].y1;
++i;
}
XDrawSegments (screen->display->xdisplay,
screen->xroot,
screen->root_xor_gc,
segments,
G_N_ELEMENTS (segments));
}
void
meta_effects_update_wireframe (MetaScreen *screen,
const MetaRectangle *old_rect,
int old_width,
int old_height,
const MetaRectangle *new_rect,
int new_width,
int new_height)
{
if (old_rect)
draw_xor_rect (screen, old_rect, old_width, old_height);
if (new_rect)
draw_xor_rect (screen, new_rect, new_width, new_height);
XFlush (screen->display->xdisplay);
}
void
meta_effects_end_wireframe (MetaScreen *screen,
const MetaRectangle *old_rect,
int old_width,
int old_height)
{
meta_effects_update_wireframe (screen,
old_rect, old_width, old_height,
NULL, -1, -1);
meta_display_ungrab (screen->display);
meta_ui_pop_delay_exposes (screen->ui);
}
static void
run_default_effect_handler (MetaEffect *effect)
{
switch (effect->type)
{
case META_EFFECT_MINIMIZE:
meta_effects_draw_box_animation (effect->window->screen,
&(effect->u.minimize.window_rect),
&(effect->u.minimize.icon_rect),
META_MINIMIZE_ANIMATION_LENGTH,
META_BOX_ANIM_SCALE);
break;
default:
break;
}
}
static void
run_handler (MetaEffect *effect)
{
if (effect_handler)
{
effect_handler (effect, effect_handler_data);
}
else
{
run_default_effect_handler (effect);
meta_effect_end (effect);
}
}