On Sat, 2010-04-24 at 22:21 +0200, Torsten Schoenfeld wrote:
> If anyone is looking for a challenge, we also still need bindings for 
> the offscreen window stuff:

The first patch change the way damage events are blessed, they were
blessed as Gtk2::Gdk::Event::Damage but it doesn't have the ->area
method. Damage events are really just expose events. Similar cases in
Gtk2::Gdk::Event use a common package and are distinguished by using
->type. As we can't rename Gtk2::Gdk::Event::Expose, I chose to use it
as the common package and bless damage event as a
Gtk2::Gdk::Event::Expose.

The second patch wrap the offscreen gdkwindow stuff.
I'm wondering if offscreen gdkwindows should be in their own package.
I also added a small comment to gdk_window_geometry_changed, as though
it can be used with normal gdkwindows, it seems it is only useful for
offscreen ones.
I haven't found a way to test the from-embedder/to-embedder marshaller.

The third patch contains 3 examples for offscreen gdkwindows.
Two are inspired from the the gtk examples, the reflection one has small
differences, I significantly expanded the rotation example to make it
much more fun and generic by using the same matrix for nearly
everything. (btw a $matrix->copy could be nice, I've used
$matrix->multiply( Cairo::Matrix->init_identity ) instead.)
And I've added a third example that simply scale the widget to the size
of the window.
I don't know if all 3 examples should be included.

One thing I'm wondering, I've used
$offscreen->set_user_data($self->window->get_user_data);
to "attach" the offscreen window to its widget (so that the widget
receive the expose events). GdkWindows use their user_data to hold a
reference to its widget. It's the only way I found to do that, and
though it works fine, maybe there should be a better way. And by the
way, it would be nice if there was a way to get the widget of a
gdkwindow (to be able to pick a widget with the mouse for example), the
only way I know of doing that is iterating through all the widgets, and
it would not work with gdkwindows that are not a $widget->window.
Also it would make accessing the widget's data from the gdkwindow
from-embedder/to-embedder callback much cleaner.

One thing I haven't managed to do, is make it work if the child of the
offscreen-using container is added/replaced after the container is
realized.

Quentin
>From d00c8d928d239ecbaaba14aa76749952b170be7b Mon Sep 17 00:00:00 2001
From: Quentin Sculo <squen...@free.fr>
Date: Wed, 12 May 2010 00:34:48 +0200
Subject: [PATCH 1/3] make Damage Event a Gtk2::Gdk::Event::Expose object

---
 xs/GdkEvent.xs |    3 +--
 1 files changed, 1 insertions(+), 2 deletions(-)

diff --git a/xs/GdkEvent.xs b/xs/GdkEvent.xs
index 317868b..cf636dc 100644
--- a/xs/GdkEvent.xs
+++ b/xs/GdkEvent.xs
@@ -45,7 +45,6 @@ gdk_event_get_package (GType gtype,
                        GdkEvent * event)
 {
 	PERL_UNUSED_VAR (gtype);
-
 	switch (event->type) {
 	    case GDK_NOTHING:
 	    case GDK_DELETE:
@@ -110,7 +109,7 @@ gdk_event_get_package (GType gtype,
 #endif
 #if GTK_CHECK_VERSION (2, 14, 0)
 	    case GDK_DAMAGE:
-		return "Gtk2::Gdk::Event::Damage";
+		return "Gtk2::Gdk::Event::Expose";
 #endif
 	    default:
 		{
-- 
1.6.4.4

>From 7d7a3a8724ff0453f690819bceadfd086b8ba12e Mon Sep 17 00:00:00 2001
From: Quentin Sculo <squen...@free.fr>
Date: Wed, 12 May 2010 00:35:00 +0200
Subject: [PATCH 2/3] wrap offscreen_window functions

---
 t/GdkWindow.t   |   12 ++++++-
 xs/GdkWindow.xs |   98 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 106 insertions(+), 4 deletions(-)

diff --git a/t/GdkWindow.t b/t/GdkWindow.t
index 4b58b68..546a816 100644
--- a/t/GdkWindow.t
+++ b/t/GdkWindow.t
@@ -1,6 +1,6 @@
 #!/usr/bin/perl -w
 use strict;
-use Gtk2::TestHelper tests => 43;
+use Gtk2::TestHelper tests => 45;
 
 # $Id$
 
@@ -327,7 +327,7 @@ SKIP: {
 }
 
 SKIP: {
-  skip 'new 2.18 stuff', 3
+  skip 'new 2.18 stuff', 5
     unless Gtk2->CHECK_VERSION(2, 18, 0);
 
   my $window = Gtk2::Gdk::Window -> new(undef, { window_type => 'toplevel' });
@@ -342,6 +342,14 @@ SKIP: {
   my $sibling = Gtk2::Gdk::Window -> new(undef, { window_type => 'toplevel' });
   $window -> restack(undef, TRUE);
   $window -> restack($sibling, TRUE);
+
+  my $gtkwindow= Gtk2::Window->new;
+  $gtkwindow->show_all;
+  $gtkwindow->realize;
+  my $offscreen= Gtk2::Gdk::Window->new(undef, { window_type	=> 'offscreen', });
+  $offscreen->set_embedder($gtkwindow->window);
+  isa_ok($offscreen->get_pixmap,  'Gtk2::Gdk::Pixmap');
+  isa_ok($offscreen->get_embedder,'Gtk2::Gdk::Window');
 }
 
 $window -> hide();
diff --git a/xs/GdkWindow.xs b/xs/GdkWindow.xs
index 8ceea10..3eafd8d 100644
--- a/xs/GdkWindow.xs
+++ b/xs/GdkWindow.xs
@@ -20,6 +20,7 @@
  */
 
 #include "gtk2perl.h"
+#include <gperl_marshal.h>
 
 /* ------------------------------------------------------------------------- */
 
@@ -99,6 +100,60 @@ SvGdkWindowAttrReal (SV *object, GdkWindowAttributesType *mask)
 	return attr;
 }
 
+#if GTK_CHECK_VERSION (2, 18, 0)
+
+static void
+gtk2perl_offscreen_coord_translate_marshal (GClosure * closure,
+                                  	    GValue * return_value,
+                                  	    guint n_param_values,
+                                  	    const GValue * param_values,
+                                  	    gpointer invocation_hint,
+                                  	    gpointer marshal_data)
+
+{
+	gdouble * return_x, * return_y;
+	dGPERL_CLOSURE_MARSHAL_ARGS;
+
+	GPERL_CLOSURE_MARSHAL_INIT (closure, marshal_data);
+
+	PERL_UNUSED_VAR (return_value);
+	PERL_UNUSED_VAR (n_param_values);
+	PERL_UNUSED_VAR (invocation_hint);
+
+	ENTER;
+	SAVETMPS;
+
+	PUSHMARK (SP);
+
+	GPERL_CLOSURE_MARSHAL_PUSH_INSTANCE (param_values);
+
+	XPUSHs (sv_2mortal (newSViv( g_value_get_double (param_values+1) )) );
+	XPUSHs (sv_2mortal (newSViv( g_value_get_double (param_values+2) )) );
+	return_x = g_value_get_pointer (param_values+3);
+	return_y = g_value_get_pointer (param_values+4);
+
+	GPERL_CLOSURE_MARSHAL_PUSH_DATA;
+
+	PUTBACK;
+
+	GPERL_CLOSURE_MARSHAL_CALL (G_ARRAY);
+
+	if (count == 2) {
+		*return_y = POPn;
+		*return_x = POPn;
+	} else {
+		/* NOTE: croaking here can cause bad things to happen to the
+		 * app, because croaking in signal handlers is bad juju. */
+		croak ("callback must return 2 values : x and y");
+	}
+
+	PUTBACK;
+	FREETMPS;
+	LEAVE;
+}
+#endif
+
+
 #if 0 /* not used at the moment */
 static GdkWindowAttr *
 SvGdkWindowAttr (SV *object)
@@ -138,6 +193,20 @@ gtk2perl_gdk_window_invalidate_maybe_recurse_func (GdkWindow *window,
 
 MODULE = Gtk2::Gdk::Window	PACKAGE = Gtk2::Gdk::Window	PREFIX = gdk_window_
 
+
+BOOT:
+	gperl_signal_set_marshaller_for (GDK_TYPE_WINDOW, "from-embedder", gtk2perl_offscreen_coord_translate_marshal);
+	gperl_signal_set_marshaller_for (GDK_TYPE_WINDOW, "to-embedder", gtk2perl_offscreen_coord_translate_marshal);
+
+=for position post_signals
+
+from-embedder, to-embedder and pick-embedded-child signals are for offscreen windows only.
+
+from-embedder and to-embedder receive the x and y coordinates to translate, and must return the translated x and y coordinate.
+
+=cut
+
+
  ## GdkWindow* gdk_window_new (GdkWindow *parent, GdkWindowAttr *attributes, gint attributes_mask)
 =for apidoc
 Create and return a new window.  parent can be undef to mean the root
@@ -956,12 +1025,37 @@ void gdk_window_flush (GdkWindow *window);
 
 gboolean gdk_window_ensure_native (GdkWindow *window);
 
-void gdk_window_geometry_changed (GdkWindow *window);
-
 GdkCursor_ornull * gdk_window_get_cursor (GdkWindow *window);
 
 void gdk_window_restack (GdkWindow *window, GdkWindow_ornull *sibling, gboolean above);
 
+=for apidoc
+Only useful for offscreen gdkwindows
+=cut
+void gdk_window_geometry_changed (GdkWindow *window);
+
+#endif /* 2.18 */
+
+
+#if GTK_CHECK_VERSION (2, 18, 0)
+
+MODULE = Gtk2::Gdk::Window	PACKAGE = Gtk2::Gdk::Window	PREFIX = gdk_offscreen_window_
+
+=for apidoc
+Only for offscreen gdkwindows
+=cut
+GdkPixmap_ornull * gdk_offscreen_window_get_pixmap (GdkWindow *offscreen_window);
+
+=for apidoc
+Only for offscreen gdkwindows
+=cut
+void gdk_offscreen_window_set_embedder (GdkWindow *offscreen_window, GdkWindow *embedder);
+
+=for apidoc
+Only for offscreen gdkwindows
+=cut
+GdkWindow_ornull * gdk_offscreen_window_get_embedder (GdkWindow *offscreen_window);
+
 #endif /* 2.18 */
 
 MODULE = Gtk2::Gdk::Window	PACKAGE = Gtk2::Gdk	PREFIX = gdk_
-- 
1.6.4.4

>From 8eb1e2873d77eafd8bf7fc7af163d7300d37f3d0 Mon Sep 17 00:00:00 2001
From: Quentin Sculo <squen...@free.fr>
Date: Wed, 12 May 2010 00:43:52 +0200
Subject: [PATCH 3/3] add offscreen examples

---
 examples/offscreen_reflection.pl |  156 ++++++++++++++++++++++++++++
 examples/offscreen_rotation.pl   |  212 ++++++++++++++++++++++++++++++++++++++
 examples/offscreen_scale.pl      |  130 +++++++++++++++++++++++
 3 files changed, 498 insertions(+), 0 deletions(-)
 create mode 100644 examples/offscreen_reflection.pl
 create mode 100644 examples/offscreen_rotation.pl
 create mode 100644 examples/offscreen_scale.pl

diff --git a/examples/offscreen_reflection.pl b/examples/offscreen_reflection.pl
new file mode 100644
index 0000000..b1d85e4
--- /dev/null
+++ b/examples/offscreen_reflection.pl
@@ -0,0 +1,156 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Glib qw(TRUE FALSE);
+use Gtk2 '-init';
+use Cairo;
+
+my $window = Gtk2::Window->new;
+$window->set_title("Reflection example");
+$window->signal_connect( delete_event => sub { exit } );
+$window->set_border_width(10);
+
+my $reflected= Gtk2::Ex::ReflectBin->new;
+
+my $entry=Gtk2::Entry->new;
+my $button1=Gtk2::Button->new;
+$button1->add( Gtk2::Image->new_from_stock('gtk-go-back','button') );
+$button1->signal_connect(clicked=>sub {warn "button 1 clicked\n";});
+my $button2=Gtk2::Button->new;
+$button2->add( Gtk2::Image->new_from_stock('gtk-apply','button') );
+$button2->signal_connect(clicked=>sub {warn "button 2 clicked\n";});
+
+my $hbox=Gtk2::HBox->new;
+$hbox->pack_start($button1,FALSE,FALSE,2);
+$hbox->add($entry);
+$hbox->pack_start($button2,FALSE,FALSE,2);
+
+$reflected->add($hbox);
+$window->add($reflected);
+$window->show_all;
+
+Gtk2->main;
+
+
+
+package Gtk2::Ex::ReflectBin;
+use Gtk2;
+use Glib::Object::Subclass
+	Gtk2::EventBox::,
+	signals =>
+	{	size_allocate	=> \&do_size_allocate,
+		size_request	=> \&do_size_request,
+	};
+
+sub INIT_INSTANCE {
+	my $self=shift;
+	$self->signal_connect( expose_event	=> \&expose_cb);
+	$self->signal_connect( damage_event	=> \&damage_cb);
+	$self->signal_connect( realize		=> \&realize_cb);
+	return $self;
+}
+
+sub do_size_request {
+	my ($self,$req)=...@_;
+	my $border= $self->get_border_width;
+	return unless $self->child;
+	my $child_req=$self->child->size_request;
+	my $w= $child_req->width;
+	my $h= $child_req->height;
+	$req->width( $w +$border*2 );
+	$req->height( $h*2 +$border*2 );
+}
+
+sub do_size_allocate {
+	my ($self,$alloc)=...@_;
+	my $border= $self->get_border_width;
+	my ($x,$y,$w,$h)=$alloc->values;
+	my $olda=$self->allocation;
+	 $olda->x($x); $olda->width($w);
+	 $olda->y($y); $olda->height($h);
+	$w-= 2*$border;
+	$h-= 2*$border;
+	$self->window->move_resize($x+$border,$y+$border,$w,$h) if $self->window;
+	if (my $child=$self->child) {
+		my $req= $child->size_request;
+		my $rect=Gtk2::Gdk::Rectangle->new(0,0,$w,$h/2);
+		$self->child->size_allocate($rect);
+	}
+	if (my $offscreen= $self->{offscreen}) {
+		$offscreen->move_resize(0,0,$w,$h/2);
+		$offscreen->geometry_changed;
+	}
+}
+
+sub damage_cb {
+	my ($self,$event)=...@_;
+	# invalidate the whole window, it could be better to invalidate $event->area and the rectangle containing its reflection
+	$self->window->invalidate_rect(undef,0);
+	return 1;
+}
+
+sub realize_cb {
+	my ($self)=...@_;
+	my $border= $self->get_border_width;
+	my ($x,$y,$w,$h)=$self->allocation->values;
+	my %attr=
+	 (	window_type	=> 'offscreen',
+		wclass		=> 'output',
+		x		=> 0,
+		y		=> 0,
+		width		=> $w-$border*2,
+		height		=> ($h-$border*2)/2,
+		event_mask	=> [qw/pointer-motion-mask button-press-mask button-release-mask exposure_mask/],
+	 );
+	$self->{offscreen}= my $offscreen= Gtk2::Gdk::Window->new($self->get_root_window,\%attr);
+	$offscreen->set_user_data($self->window->get_user_data);
+	$self->window->signal_connect( pick_embedded_child =>sub { return $offscreen; });
+	$self->child->set_parent_window($offscreen) if $self->child;
+	$offscreen->set_embedder($self->window);
+	$offscreen->signal_connect( to_embedder  => sub {my ($offscreen,$x,$y)=...@_; return $x,$y });
+	$offscreen->signal_connect( from_embedder=> sub {my ($offscreen,$x,$y)=...@_; return $x,$y });
+	$self->style->set_background($offscreen,'normal');
+	$offscreen->show;
+}
+
+sub expose_cb {
+	my ($self,$event)=...@_;
+	my $offscreen= $self->{offscreen};
+	if ($event->window == $self->window) {
+		my $pixmap = $offscreen->get_pixmap;
+		return 1 unless $pixmap && $self->child;
+		my (undef,$height)= $pixmap->get_size;
+		my $cr=Gtk2::Gdk::Cairo::Context->create($self->window);
+		my $child_alloc= $self->child->allocation;
+		$cr->rectangle($event->area);
+		$cr->clip;
+		$cr->save; # the above clip is used for both draw
+		 # draw normal version on upper half (0..$height)
+		 $cr->rectangle($child_alloc);
+		 $cr->clip;
+		 $cr->set_source_pixmap($pixmap,0,0);
+		 $cr->paint;
+		 $cr->restore;
+		# draw reflection on lower half ($height..$height*2)
+		my $mask= Cairo::LinearGradient->create(0,$height,0,$height*2);
+		$mask->add_color_stop_rgba(0,    0, 0, 0, 1   );
+		$mask->add_color_stop_rgba(0.25, 0, 0, 0, 0.5 );
+		$mask->add_color_stop_rgba(0.5,  0, 0, 0, 0.25);
+		$mask->add_color_stop_rgba(0.75, 0, 0, 0, 0.1 );
+		$mask->add_color_stop_rgba(1.0,  0, 0, 0, 0   );
+		my $matrix= Cairo::Matrix->init(1, 0, .3, 1, 0, 0);
+		$matrix->translate(-10,$height*2);
+		$matrix->scale(1,-1);
+		$cr->transform($matrix);
+		$mask->set_matrix($matrix);
+		$cr->rectangle($child_alloc);
+		$cr->clip;
+		$cr->set_source_pixmap($pixmap,0,0);
+		$cr->mask($mask);
+	}
+	elsif ($event->window == $offscreen) {
+		$self->propagate_expose($self->child,$event) if $self->child;
+	}
+	1;
+}
+
diff --git a/examples/offscreen_rotation.pl b/examples/offscreen_rotation.pl
new file mode 100644
index 0000000..19d5782
--- /dev/null
+++ b/examples/offscreen_rotation.pl
@@ -0,0 +1,212 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Glib qw(TRUE FALSE);
+use Gtk2 '-init';
+use Cairo;
+
+my $window = Gtk2::Window->new;
+$window->set_title("Rotation example");
+$window->signal_connect( delete_event => sub { exit } );
+$window->set_border_width(5);
+
+my $rotated= Gtk2::Ex::RotatedBin->new;
+
+my $entry=Gtk2::Entry->new;
+my $button=Gtk2::Button->new_from_stock('gtk-ok');
+$button->signal_connect(clicked=>sub {warn "clicked\n";});
+
+my $checkh=Gtk2::CheckButton->new('mirror horizontally');
+$checkh->signal_connect(toggled=>sub { $rotated->set_mirror( horizontal=> $_[0]->get_active ) });
+my $checkv=Gtk2::CheckButton->new('mirror vertically');
+$checkv->signal_connect(toggled=>sub { $rotated->set_mirror( vertical=> $_[0]->get_active ) });
+
+my $adj=Gtk2::Adjustment->new(10, 0, 360, 1,10,0);
+$adj->signal_connect(value_changed=> sub { $rotated->set_angle( $_[0]->value ); });
+my $scale=Gtk2::HScale->new($adj);
+
+my $vbox=Gtk2::VBox->new;
+$vbox->add($_) for $entry,$button,$checkh,$checkv,$scale;
+
+$rotated->add($vbox);
+$window->add($rotated);
+$window->show_all;
+
+#Gtk2::Gdk::Window->set_debug_updates(1);
+Gtk2->main;
+
+
+#FIXME the child needs to be added before the RotatedBin is realized
+# I haven't managed to make it work if the child is added or replaced after
+package Gtk2::Ex::RotatedBin;
+use Gtk2;
+use List::Util qw/min max/;
+use Glib::Object::Subclass
+	Gtk2::EventBox::,
+	signals =>
+	{	size_allocate	=> \&do_size_allocate,
+		size_request	=> \&do_size_request,
+	};
+use constant PI => 4 * atan2(1,1); # needed for the rotation
+
+sub INIT_INSTANCE {
+	my $self=shift;
+	$self->{angle}=10;
+	$self->signal_connect( expose_event	=> \&expose_cb);
+	$self->signal_connect( damage_event	=> \&damage_cb);
+	$self->signal_connect( realize		=> \&realize_cb);
+	return $self;
+}
+
+sub do_size_request {
+	my ($self,$req)=...@_;
+	my $border= $self->get_border_width;
+	my $child_req=$self->child->size_request;
+	my $w= $child_req->width;
+	my $h= $child_req->height;
+	my $diag= sqrt( $w**2 + $h**2 );
+	$diag= 1+int $diag  unless int($diag)==$diag; #round up
+	# request enough to satisfy the child request for any angle
+	$req->width( $diag+$border*2 );
+	$req->height( $diag+$border*2 );
+}
+
+sub do_size_allocate {
+	my ($self,$alloc)=...@_;
+	my $border= $self->get_border_width;
+	my ($x,$y,$w,$h)=$alloc->values;
+	my $olda=$self->allocation;
+	 $olda->x($x); $olda->width($w);
+	 $olda->y($y); $olda->height($h);
+	$w-= 2*$border;
+	$h-= 2*$border;
+	$self->window->move_resize($x+$border,$y+$border,$w,$h) if $self->window;
+	$self->update_matrix;
+}
+
+sub set_angle {
+	my ($self,$angle)=...@_;
+	$self->{angle}=$angle;
+	$self->update_matrix;
+	$self->queue_resize;
+}
+
+sub set_mirror {
+	my ($self,$h_or_v,$on)=...@_;
+	my $key= $h_or_v eq 'vertical' ? 'vmirror' : 'hmirror';
+	$self->{$key}=$on;
+	$self->update_matrix;
+	$self->queue_draw;
+}
+
+# transform the rectangle and find a rectangle containing the transformed rectangle
+sub transform_expose_rectangle {
+	my ($self,$rect,$inv)=...@_;
+	my ($x,$y,$w,$h)=$rect->values;
+	my $matrix= $inv ? $self->{imatrix} : $self->{matrix};
+	my ($xa,$ya)=$matrix->transform_point($x,   $y);
+	my ($xb,$yb)=$matrix->transform_point($x+$w,$y);
+	my ($xc,$yc)=$matrix->transform_point($x,   $y+$h);
+	my ($xd,$yd)=$matrix->transform_point($x+$w,$y+$h);
+	$x= min($xa,$xb,$xc,$xd);
+	$y= min($ya,$yb,$yc,$yd);
+	$w= max($xa,$xb,$xc,$xd) -$x;
+	$h= max($ya,$yb,$yc,$yd) -$y;
+	return Gtk2::Gdk::Rectangle->new($x,$y,$w,$h);
+}
+
+sub update_matrix {
+	my $self=shift;
+	my ($x,$y,$w,$h)= $self->allocation->values;
+	my $border= $self->get_border_width;
+	$x+=$border; $w-=2*$border;
+	$y+=$border; $h-=2*$border;
+	my $angle= $self->{angle}*PI/180;
+	my $matrix0=Cairo::Matrix->init_rotate($angle);
+	my ($xa,$ya)=$matrix0->transform_distance(0,0);
+	my ($xb,$yb)=$matrix0->transform_distance($w,0);
+	my ($xc,$yc)=$matrix0->transform_distance(0,$h);
+	my ($xd,$yd)=$matrix0->transform_distance($w,$h);
+	my $rw= $w / ( max($xa,$xb,$xc,$xd) - min($xa,$xb,$xc,$xd) );
+	my $rh= $h / ( max($ya,$yb,$yc,$yd) - min($ya,$yb,$yc,$yd) );
+	my $r= min($rw,$rh);
+	my $cw= $w*$r;
+	my $ch= $h*$r;
+
+	my $matrix=Cairo::Matrix->init_identity;
+	$matrix->translate($w/2,$h/2);
+	$matrix->rotate($angle);
+	$matrix->translate( -$cw/2,-$ch/2 );
+	if ($self->{hmirror}) {
+		$matrix->scale(-1,1);
+		$matrix->translate( -$cw,0 );
+	}
+	if ($self->{vmirror}) {
+		$matrix->scale(1,-1);
+		$matrix->translate( 0,-$ch );
+	}
+	$self->{matrix}=$matrix;
+
+	my $imatrix= $matrix->multiply( Cairo::Matrix->init_identity ); #copy matrix
+	$imatrix->invert;
+	$self->{imatrix}= $imatrix;
+
+	if (my $o=$self->{offscreen})
+	{	$o->{matrix}= $self->{matrix};
+		$o->{imatrix}=$self->{imatrix};
+		$o->move_resize(0,0,$cw,$ch);
+		$o->geometry_changed;
+	}
+	if (my $child=$self->child)
+	{	my $rect=Gtk2::Gdk::Rectangle->new(0,0, $cw,$ch);
+		$self->child->size_allocate($rect);
+	}
+}
+
+sub damage_cb {
+	my ($self,$event)=...@_;
+	my $rect= transform_expose_rectangle($self,$event->area);
+	$self->window->invalidate_rect( $rect, 0);
+	1;
+}
+
+sub realize_cb {
+	my ($self)=...@_;
+	my ($x,$y,$w,$h)=$self->allocation->values;
+	my %attr=
+	 (	window_type	=> 'offscreen',
+		wclass		=> 'output',
+		event_mask	=> [qw/pointer-motion-mask button-press-mask button-release-mask exposure_mask/],
+	 );
+	$self->{offscreen}= my $offscreen= Gtk2::Gdk::Window->new($self->get_root_window,\%attr);
+	$self->update_matrix;	# will resize $offscreen to the correct size
+	$offscreen->set_user_data($self->window->get_user_data);
+	$self->window->signal_connect( pick_embedded_child =>sub { return $offscreen; }); #could check if transformed position is inside offscreen window
+	$self->child->set_parent_window($offscreen) if $self->child;
+	$offscreen->set_embedder($self->window);
+	$offscreen->signal_connect( to_embedder  => sub { return $_[0]{ matrix}->transform_point($_[1],$_[2]); });
+	$offscreen->signal_connect( from_embedder=> sub { return $_[0]{imatrix}->transform_point($_[1],$_[2]); });
+	$self->style->set_background($offscreen,'normal');
+	$offscreen->show;
+}
+
+sub expose_cb {
+	my ($self,$event)=...@_;
+	my $offscreen= $self->{offscreen};
+	if ($event->window == $self->window) {
+		my $pixmap = $offscreen->get_pixmap;
+		return 1 unless $pixmap;
+		my $cr=Gtk2::Gdk::Cairo::Context->create($self->window);
+		$cr->rectangle($event->area);
+		$cr->clip;
+		$cr->set_matrix( $self->{matrix} );
+		$cr->set_source_pixmap($pixmap,0,0);
+		$cr->paint;
+	}
+	elsif ($event->window == $offscreen) {
+		$self->propagate_expose($self->child,$event) if $self->child;
+	}
+	1;
+}
+
+
diff --git a/examples/offscreen_scale.pl b/examples/offscreen_scale.pl
new file mode 100644
index 0000000..2902ab3
--- /dev/null
+++ b/examples/offscreen_scale.pl
@@ -0,0 +1,130 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Glib qw(TRUE FALSE);
+use Gtk2 '-init';
+use Cairo;
+
+my $window = Gtk2::Window->new;
+$window->set_title("Resize me");
+$window->signal_connect( delete_event => sub { exit } );
+$window->set_border_width(5);
+
+my $scaled= Gtk2::Ex::ScaleBin->new;
+
+my $entry=Gtk2::Entry->new;
+my $button=Gtk2::Button->new_from_stock('gtk-ok');
+$button->signal_connect(clicked=>sub {warn "clicked\n";});
+
+my $vbox=Gtk2::VBox->new;
+$vbox->add($entry);
+$vbox->add($button);
+
+$scaled->add($vbox);
+$window->add($scaled);
+$window->show_all;
+
+
+Gtk2->main;
+
+
+
+package Gtk2::Ex::ScaleBin;
+use Gtk2;
+use Glib::Object::Subclass
+	Gtk2::EventBox::,
+	signals =>
+	{	size_allocate	=> \&do_size_allocate,
+	};
+
+sub INIT_INSTANCE {
+	my $self=shift;
+	$self->signal_connect( expose_event	=> \&expose_cb);
+	$self->signal_connect( damage_event	=> \&damage_cb);
+	$self->signal_connect( realize		=> \&realize_cb);
+	return $self;
+}
+
+sub do_size_allocate {
+	my ($self,$alloc)=...@_;
+	my $border= $self->get_border_width;
+	my ($x,$y,$w,$h)=$alloc->values;
+	my $olda=$self->allocation;
+	 $olda->x($x); $olda->width($w);
+	 $olda->y($y); $olda->height($h);
+	$w-= 2*$border;
+	$h-= 2*$border;
+	$self->window->move_resize($x+$border,$y+$border,$w,$h) if $self->window;
+	my ($reqw,$reqh)= ($w,$h);
+	if (my $child=$self->child) {
+		my $req= $child->size_request;
+		$reqw= $req->width;
+		$reqh= $req->height;
+		my $rect=Gtk2::Gdk::Rectangle->new(0, 0, $reqw, $reqh);
+		$self->child->size_allocate($rect);
+	}
+	$self->{zoom_x}= $w / $reqw;
+	$self->{zoom_y}= $h / $reqh;
+	if (my $offscreen= $self->{offscreen}) {
+		$offscreen->{zoom_x}= $self->{zoom_x};
+		$offscreen->{zoom_y}= $self->{zoom_y};
+		$offscreen->move_resize(0,0, $reqw, $reqh);
+		$offscreen->geometry_changed;
+	}
+}
+
+sub damage_cb {
+	my ($self,$event)=...@_;
+	my ($x,$y,$w,$h)=$event->area->values;
+	my $zx= $self->{zoom_x};
+	my $zy= $self->{zoom_y};
+	my $rect=Gtk2::Gdk::Rectangle->new($x*$zx, $y*$zy, $w*$zx, $h*$zy);
+	$self->window->invalidate_rect($rect,0);
+	1;
+}
+
+sub realize_cb {
+	my ($self)=...@_;
+	my ($x,$y,$w,$h)=$self->allocation->values;
+	my %attr=
+	 (	window_type	=> 'offscreen',
+		wclass		=> 'output',
+		x		=> 0,
+		y		=> 0,
+		width		=> $w,
+		height		=> $h,
+		event_mask	=> [qw/pointer-motion-mask button-press-mask button-release-mask exposure_mask/],
+	 );
+	$self->{offscreen}= my $offscreen= Gtk2::Gdk::Window->new($self->get_root_window,\%attr);
+	$offscreen->set_user_data($self->window->get_user_data);
+	$self->window->signal_connect( pick_embedded_child =>sub { return $offscreen; });
+	$self->child->set_parent_window($offscreen) if $self->child;
+	$offscreen->set_embedder($self->window);
+		$offscreen->{zoom_x}= $self->{zoom_x};
+		$offscreen->{zoom_y}= $self->{zoom_y};
+	$offscreen->signal_connect( to_embedder  => sub {my ($offscreen,$x,$y)=...@_; return $x*$offscreen->{zoom_x},$y*$offscreen->{zoom_y} });
+	$offscreen->signal_connect( from_embedder=> sub {my ($offscreen,$x,$y)=...@_; return $x/$offscreen->{zoom_x},$y/$offscreen->{zoom_y} });
+	$self->style->set_background($offscreen,'normal');
+	$offscreen->show;
+}
+
+sub expose_cb {
+	my ($self,$event)=...@_;
+	my $offscreen= $self->{offscreen};
+	if ($event->window == $self->window) {
+		my $pixmap = $offscreen->get_pixmap;
+		return 1 unless $pixmap;
+		my ($w,$h)= $pixmap->get_size;
+		my $cr=Gtk2::Gdk::Cairo::Context->create($self->window);
+		$cr->rectangle($event->area);
+		$cr->clip;
+		$cr->scale($self->{zoom_x},$self->{zoom_y});
+		$cr->set_source_pixmap($pixmap,0,0);
+		$cr->paint;
+	}
+	elsif ($event->window == $offscreen) {
+		$self->propagate_expose($self->child,$event) if $self->child;
+	}
+	1;
+}
+
-- 
1.6.4.4

_______________________________________________
gtk-perl-list mailing list
gtk-perl-list@gnome.org
http://mail.gnome.org/mailman/listinfo/gtk-perl-list

Reply via email to