/* test gdkwindow animations on multiple windows
 *
 * Copyright © 2011 Canonical Ltd
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA
 *
 * Authored by Andrea Cimitan <andrea.cimitan@canonical.com>
 */

/* 
 * click on the window to test an animation, with multiple windows
 */

#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <X11/extensions/XInput2.h>

/* toplevel widget */
static GtkWidget *toplevel = NULL;

/* window below */
static GdkWindow   *below = NULL;
static GdkRectangle below_allocation;
static GdkRectangle below_mask;

/* window above */
static GdkWindow   *above = NULL;
static GdkRectangle above_allocation;
static GdkRectangle above_mask;

/* animation */
static gboolean active = FALSE;
static gfloat weight = 1.0f;
static gint64 start_time;
static gint64 duration = (gint64) 500 * G_GINT64_CONSTANT (1000);
static guint32 source_id;

/* create window below */
static void
create_below ()
{
  GdkWindowAttr attributes;

  attributes.x = below_allocation.x;
  attributes.y = below_allocation.y;
  attributes.width = below_allocation.width;
  attributes.height = below_allocation.height;
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.visual = gtk_widget_get_visual (toplevel);

  below = gdk_window_new (gtk_widget_get_window (toplevel),
                          &attributes,
                          GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL);

  gdk_window_set_transient_for (below, gtk_widget_get_window (toplevel));
}

/* create window above */
static void
create_above ()
{
  GdkWindowAttr attributes;

  attributes.x = above_allocation.x;
  attributes.y = above_allocation.y;
  attributes.width = above_allocation.width;
  attributes.height = above_allocation.height;
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.visual = gtk_widget_get_visual (toplevel);

  above = gdk_window_new (gtk_widget_get_window (toplevel),
                          &attributes,
                          GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL);

  gdk_window_set_transient_for (above, gtk_widget_get_window (toplevel));
}

/* draw window below */
static void
draw_below ()
{
  GtkStyle *style;

  style = gtk_widget_get_style (toplevel);

  gdk_window_set_background (below, &style->base[GTK_STATE_NORMAL]);

  gdk_window_invalidate_rect (gtk_widget_get_window (toplevel),
                              &below_allocation,
                              TRUE);
}

/* draw window above */
static void
draw_above ()
{
  GdkColor c1, c2, color;
  GtkStyle *style;

  style = gtk_widget_get_style (toplevel);

  if (active == FALSE)
    {
      c1 = style->base[GTK_STATE_INSENSITIVE];
      c2 = style->base[GTK_STATE_SELECTED];
    }
  else
    {
      c1 = style->base[GTK_STATE_SELECTED];
      c2 = style->base[GTK_STATE_INSENSITIVE];
    }

  color.red   = weight * c1.red   + (1.0 - weight) * c2.red;
  color.green = weight * c1.green + (1.0 - weight) * c2.green;
  color.blue  = weight * c1.blue  + (1.0 - weight) * c2.blue;

  gdk_colormap_alloc_color (gdk_drawable_get_colormap (above), &color, FALSE, TRUE);

  gdk_window_set_background (above, &color);

  gdk_window_invalidate_rect (gtk_widget_get_window (toplevel),
                              &above_allocation,
                              TRUE);
}

static void
apply_below_mask ()
{
  gdk_window_shape_combine_region (below,
                                   gdk_region_rectangle (&below_mask),
                                   0, 0);
}

static void
apply_above_mask ()
{
  gdk_window_shape_combine_region (above,
                                   gdk_region_rectangle (&above_mask),
                                   0, 0);
}

static gboolean
update_cb (gpointer user_data)
{
  const gint64 current_time = g_get_monotonic_time ();
  const gint64 end_time = start_time + duration;

  if (current_time < end_time)
    {
      const gfloat diff_time = current_time - start_time;
      weight = diff_time / duration;

      draw_above ();

      return TRUE;
    }
  else
    {
      weight = 1.0;
      source_id = 0;

      draw_above ();

      return FALSE;
    }
}

static gboolean
button_button_press_event_cb (GtkWidget *widget,
                              GdkEvent  *event,
                              gpointer   user_data)
{
  if (source_id != 0)
    {
      g_source_remove (source_id);
      source_id = 0;
    }

  active = TRUE;

  start_time = g_get_monotonic_time ();
  source_id = g_timeout_add (1000/30, update_cb, NULL);

  return FALSE;
}

static gboolean
button_button_release_event_cb (GtkWidget *widget,
                                GdkEvent  *event,
                                gpointer   user_data)
{
  if (source_id != 0)
    {
      g_source_remove (source_id);
      source_id = 0;
    }

  active = FALSE;

  start_time = g_get_monotonic_time ();
  source_id = g_timeout_add (1000/30, update_cb, NULL);

  return FALSE;
}

static void
check_below_toggled_cb (GtkWidget *widget,
                        gpointer   user_data)
{
  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
    apply_below_mask ();
  else
    gdk_window_shape_combine_region (below, NULL, 0, 0);
}

static void
check_above_toggled_cb (GtkWidget *widget,
                        gpointer   user_data)
{
  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
    apply_above_mask ();
  else
    gdk_window_shape_combine_region (above, NULL, 0, 0);
}

static void
toggle_button_toggled_cb (GtkWidget *widget,
                          gpointer   user_data)
{
  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
    gdk_window_hide (below);
  else
    {
      gdk_window_show (below);
      gdk_window_raise (above);
    }
}

static void
toplevel_destroy_cb (GtkWidget *widget,
                     gpointer   user_data)
{
  gtk_main_quit ();
}

int
main (int   argc,
      char *argv[])
{
  GtkAllocation allocation;
  GtkWidget *toggle_button;
  GtkWidget *button;
  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *frame;
  GtkWidget *check_below;  
  GtkWidget *check_above;

  gtk_init (&argc, &argv);

  /* toplevel widget */
  toplevel = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_resizable (GTK_WINDOW (toplevel), FALSE);
  gtk_window_set_title (GTK_WINDOW (toplevel), "Test GdkWindow redrawing");

  /* vbox widget */
  vbox = gtk_vbox_new (FALSE, 0);

  /* hbox widget */
  hbox = gtk_hbox_new (FALSE, 2);

  /* toggle_button widget */
  toggle_button = gtk_toggle_button_new_with_label ("Hide the GdkWindow below");

  /* button widget */
  button = gtk_button_new_with_label ("Animate");

  /* check_below widget */
  check_below = gtk_check_button_new_with_label ("Apply shape on GdkWindow below");

  /* check_above widget */
  check_above = gtk_check_button_new_with_label ("Apply shape on GdkWindow above");
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_above), TRUE);

  /* frame widget */
  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
  gtk_widget_set_size_request (frame, 250, 250);

  /* containers */
  gtk_container_set_border_width (GTK_CONTAINER (toplevel), 2);
  gtk_box_pack_start (GTK_BOX (hbox), toggle_button, TRUE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), check_below, FALSE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), check_above, FALSE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, FALSE, 0);
  gtk_container_add (GTK_CONTAINER (toplevel), vbox);

  /* signals */
  g_signal_connect (G_OBJECT (toplevel), "destroy",
                    G_CALLBACK (toplevel_destroy_cb),
                    NULL);
  g_signal_connect (G_OBJECT (toggle_button), "toggled",
                    G_CALLBACK (toggle_button_toggled_cb),
                    NULL);
  g_signal_connect (G_OBJECT (check_below), "toggled",
                    G_CALLBACK (check_below_toggled_cb),
                    NULL);
  g_signal_connect (G_OBJECT (check_above), "toggled",
                    G_CALLBACK (check_above_toggled_cb),
                    NULL);
  g_signal_connect (G_OBJECT (button), "button-press-event",
                    G_CALLBACK (button_button_press_event_cb),
                    NULL);
  g_signal_connect (G_OBJECT (button), "button-release-event",
                    G_CALLBACK (button_button_release_event_cb),
                    NULL);

  /* show all widgets */
  gtk_widget_show_all (toplevel);

  gtk_widget_get_allocation (frame, &allocation);

  /* below window */
  below_allocation.x = allocation.x + 10;
  below_allocation.y = allocation.y + 10;
  below_allocation.width = allocation.width - 35;
  below_allocation.height = allocation.height - 35;

  create_below ();
  draw_below ();

  /* above window */
  above_allocation.x = allocation.x + 25;
  above_allocation.y = allocation.y + 25;
  above_allocation.width = allocation.width - 35;
  above_allocation.height = allocation.height - 35;

  create_above ();
  draw_above ();

  /* above mask, using coordinates relative to above window */
  above_mask.x = 0;
  above_mask.y = 0;
  above_mask.width = above_allocation.width;
  above_mask.height = above_allocation.width;

  /* XXX comment apply_above_mask to block animation rendering on click */
  apply_above_mask ();

  /* below mask, using coordinates relative to below window */
  below_mask.x = 0;
  below_mask.y = 0;
  below_mask.width = below_allocation.width;
  below_mask.height = below_allocation.width;

  /* show the below window */
  gdk_window_show (below);

  /* show the above window */
  gdk_window_show (above);

  gtk_main ();

  return 0;
}
