The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Gtk::GLArea::Glut;

# Glut, as made possible by Perl/Gtk, with the cooperation of GtkGLArea. 
#
# The interface is intended to be identical to that used by Perl/OpenGL, and
# in fact Perl/OpenGL is required, to supply the fonts and teapots.

# Early Alpha version, plenty of stuff not supported yet

use OpenGL qw(:glutconstants
	glutSolidSphere
	glutWireSphere
	glutSolidCone
	glutWireCone
	glutSolidCube
	glutWireCube
	glutSolidTorus
	glutWireTorus
	glutSolidTeapot
	glutWireTeapot
);
use Gtk::GLArea::Constants;

require Exporter;
require Gtk;

@ISA = qw(Exporter);

@glut_func = qw(
	glutInitWindowSize
	glutInitWindowPosition
	glutInitDisplayMode
	glutInit
	glutCreateWindow
	glutCreateSubWindow
	glutMainLoop
	glutDisplayFunc
	glutReshapeFunc
	glutVisibilityFunc
	glutIdleFunc
	glutGet
	glutSwapBuffers
	glutPostRedisplay
	glutCreateMenu
	glutGetMenu
	glutSetMenu
	glutGetWindow
	glutSetWindow
	glutDestroyMenu
	glutAddMenuEntry
	glutAddSubMenu
	glutChangeToMenuEntry
	glutChangeToSubMenu
	glutRemoveMenuItem
	glutAttachMenu
	glutDetachMenu
	glutMenuStateFunc

	
	glutSolidSphere
	glutWireSphere
	glutSolidCone
	glutWireCone
	glutSolidCube
	glutWireCube
	glutSolidTorus
	glutWireTorus
	glutSolidTeapot
	glotWireTeapot
);

@EXPORT = qw();

@EXPORT_OK = (@glut_func, @OpenGL::glut_const);

%EXPORT_TAGS = ('all' => \@EXPORT_OK, 'functions' => \@glut_func, 'constants' => \@OpenGL::glut_const);

$id=0;

sub _NewWindowID {
	return ++$id;
}

$mid=0;

sub _NewMenuID {
	return ++$mid;
}


@window = ();
@menu = ();
$current = undef;
$currentgl = undef;
$currentmenu = undef;
$popupwindow = undef;
$idle = undef;
$idlecb = undef;

@initialSize = (300, 30);
@initialPos = (-1, -1);
$initialMode = ( GLUT_RGB | GLUT_SINGLE | GLUT_DEPTH );

sub glutInitWindowSize ($$) {
	@initialSize = @_;
}

sub glutInitWindowPosition ($$) {
	@initialPos= @_;
}

sub glutInitDisplayMode ($) {
	$initialMode = $_[0];
}

sub glutInit () {
	OpenGL::glutInit();
}

sub _draw {
	my($g) = $_[0];

	$currentgl->endgl if defined $currentgl;

	local($current,$currentgl) = ($g->{data}, $g);
	$currentgl->begingl;
	_invoke($current->{display});

}

sub _idle {

	return _invoke($idle);
}

sub _timer {

	return _invoke($_[0]);
}

sub _key {

}

sub _visible {
}


sub _invoke {
	my($s, @args) = @_;
	if (not defined $s) {
		return 0;
	}
	my(@a) = @$s;
	$s = shift @a;
	if (defined $s) {
		if (defined ref $s and ref $s ne "CODE") {
			my($method) = shift @args;
			$s->$method(@a, @args);
		} else {
			&{$s}(@a, @args);
		}
		return 1;
	} else {
		return 0;
	}
}

sub _button {
	my($g,$e) = @_;
	
	$currentgl->endgl if defined $currentgl;

	local($current,$currentgl) = ($g->{data}, $g);
	$currentgl->begingl;
	
	my($i) = $current->{menu}->[$e->{button}];
	
	return if not defined $i or not defined $menu[$i];
	
	$m = _generateMenu($menu[$i]);
	
	$popupwindow = $current;

	_invoke($current->{menustate}, 1);
	
	$m->popup(undef, undef, $e->{'button'}, $e->{'time'});
}	

sub _menu {
	my($widget,$menu,$value) = @_;

	$currentgl->endgl if defined $currentgl;
	
	$currentmenu = $menu;

	local($current,$currentgl) = ($popupwindow, $popupwindow->{glarea});
	$currentgl->begingl;
	
	&{$menu->{callback}->[0]}($value);
}

sub glutCreateWindow ($) {
	my($title) = @_;
	
	my($w) = (new Gtk::Window -toplevel);
	
	$w->set_title($title);
	$w->set_policy(1, 1, 0);
	#$w->set_usize(@initialSize) if @initialSize and $initialSize[0]>0 and $initialSize[1]>0;
	#$w->set_uposition(@initialPos) if @initialPos and $initialPos[0]>0 and $initialPos[1]>0;
	$w->set_default_size(100, 100);

	my($f) = new Gtk::Fixed;
	
	#show $f;
	
	$w->add($f);
	
	my($g) = new Gtk::DrawingArea; # _glut_init($initialMode);
	$g->set_events([-exposure_mask, -button_press_mask, -key_press_mask, -pointer_motion_mask, -pointer_motion_hint_mask]);

	use Data::Dumper;

	$f->set_usize(40,40); #@initialSize) if @initialSize and $initialSize[0]>0 and $initialSize[1]>0;
	
	$f->put($g, 0, 0);
	
	
	$w->signal_connect("configure_event" => sub {
		my($widget,$e) = @_;
		use Data::Dumper;
		print Dumper($e);
		if ($e->{width} > 0 and $e->{height} > 0) {
			$g->set_usize($e->{width}, $e->{height});
		}
	});

	$g->signal_connect("size_allocate" => sub {
		print "size_allocate: ", Dumper($_[1]);
		#$g->set_usize($_[1]->[2], $_[1]->[3]);
	});

#	$g->signal_connect("expose_event" => \&_draw);
	$g->signal_connect("visibility_notify_event" => \&_visible);
	$g->signal_connect("button_press_event" => \&_button);
	$g->signal_connect("key_press_event" => \&_key);

	$g->signal_connect("map" => sub { 
		if (!$g->{data}->{visible}) {
			_invoke($g->{visibility}, 1);
			$g->{data}->{visible} = 1;
		}
	});

	$g->signal_connect("unmap" => sub { 
		if ($g->{data}->{visible}) {
			_invoke($g->{visibility}, 0);
			$g->{data}->{visible} = 0;
		}
	});

	$g->{window} = $w;
	
	#show $g;
	
	show_all $w;

#	$g->can_focus(1);
#	$g->grab_focus();

	my($id) = _NewWindowID();
	
	$window[$id] = { "window" => $w, "parent" => undef, "id" => $id, "fixed" => $f, "glarea" => $g,
					"display" => undef, "toplevel" => 1 };

	$g->{data} = $window[$id];
	
	$current = $window[$id];
	
#	$g->begingl;
	
	return $id;
}

sub glutCreateSubWindow ($$$$$) {
	my($parentID, $x, $y, $w, $h) = @_;
	
	my($id) = _NewWindowID();
	
	my($f) = new Gtk::Fixed;
	$f->set_usize($w, $h);
	show $f;
	
	$window[$parentID]->{fixed}->put($f, $x, $y);
	

	my($g) = _glut_init($initialMode);
	$g->set_events([-exposure_mask, -button_press_mask, -key_press_mask, -pointer_motion_mask, -pointer_motion_hint_mask]);

#	$g->signal_connect("expose_event" => \&_draw);
	$g->signal_connect("visibility_notify_event" => \&_visible);
	$g->signal_connect("button_press_event" => \&_button);
	$g->signal_connect("key_press_event" => \&_key);
	
	
	$window[$id] = { "parent" => $window[$parentID], "id" => $id, "glarea" => $g, "window" => $window[$parentID]->{window}, "fixed" => $f,
					"toplevel" => 0, "visible" => 0};

	$g->signal_connect("map" => sub { 
		if (!$g->{data}->{visible}) {
			_invoke($g->{visibility}, 1);
			$g->{data}->{visible} = 1;
		}
	});

	$g->signal_connect("unmap" => sub { 
		if ($g->{data}->{visible}) {
			_invoke($g->{visibility}, 0);
			$g->{data}->{visible} = 0;
		}
	});
	
	push @{$window[$parentID]->{children}}, $id;

	$g->{data} = $window[$id];
	
	$g->set_usize($w, $h);
	
	show $g;

	$f->put($g, 0, 0);
	
	$current = $window[$id];
	
	$g->begingl;
	
	return $id;
}

sub glutDestroyWindow ($) {
	my($id) = @_;
	
	my($w) = $window[$id];
	
	$w->{window}->hide;
	
	if (defined $w->{parent}) {
		@{$w->{parent}->{children}} = grep {$_ != $id} @{$w->{parent}->{children}};
	}
	
	my(@children) = @{$w->{children}};
	
	foreach (@children) {
		glutDestroyWindow($_);
	}
	
	$window[$id] = undef;
}

sub glutGetWindow () {
	return $current->{id};
}

sub glutSetWindow ($) {
	my($id) = $_[0];
	
	$current = $window[$id];
	$currentgl = $current->{glarea};
}

sub glutShowWindow {
	$current->{window}->show;
}

sub glutHideWindow {
	$current->{window}->hide;
}

sub glutDisplayFunc (@) {

	$current->{display} = [@_];
}

sub glutReshapeFunc (@) {
	
	$current->{reshape} = [@_];
	
	if (@_ and defined $_[0]) {
		if (!defined $g->{reshapeID}) {
			$current->{reshapeID} = $current->{glarea}->signal_connect("configure_event" => sub {
				my($g) = $_[0];

				$currentgl->endgl if defined $currentgl;

				local($current,$currentgl) = ($g->{data}, $g);
				$currentgl->begingl;

				my($a) = $g->allocation;
				if (_invoke($current->{reshape}, $a->[2], $a->[3])) {
					glutPostRedisplay();
				}
			
			});
		}
	} else {
		if (defined $g->{reshapeID}) {
			$current->{glarea}->signal_disconnect($current->{reshapeID});
			delete $current->{reshapeID};
		}
	}

}

sub glutVisibilityFunc (@) {
	# This doesn't work as you'd expect, unfortunately.
	$current->{visible} = [@_];
}

sub glutKeyboardFunc (@) {
	$current->{keyboard} = [@_];
}

sub glutMenuStateFunc (@) {
	$current->{menustate} = [@_];
}

sub glutIdleFunc (@) {
	$idle = [@_];
	if (defined $idlecb) {
		Gtk->idle_remove($idlecb);
		$idlecb = undef;
	}
	if (@_) {
		$idlecb = Gtk->idle_add(\&_idle);
	}
}

sub glutTimerFunc ($@) {
	my($msec, @handler) = @_;
	Gtk->timeout_add($msec, \&_timer, \@handler);
}

sub glutMainLoop {
	Gtk->main();
}

sub glutGet ($) {
	my($x) = $_[0];
	
	if ($x == GLUT_WINDOW_WIDTH) {
		my($a) = $current->{glarea}->allocation;
		return $a->[2];
	}
	elsif ($x == GLUT_WINDOW_HEIGHT) {
		my($a) = $current->{glarea}->allocation;
		return $a->[3];
	}
	else {
		return OpenGL::glutGet($x);
	}
}

sub glutSwapBuffers () {
	$current->{glarea}->swapbuffers();
}

sub glutPostRedisplay () {
	$current->{glarea}->queue_draw();
}

sub glutCreateMenu (@) {
	my($m) = { "callback" => [@_], "id" => _NewMenuID() };
	$menu[$m->{id}] = $m;
	
	$currentmenu = $m;
	
	return $m->{id};
}

sub glutGetMenu () {
	return $currentmenu->{id};
}

sub glutSetMenu ($) {
	my($id) = @_;
	
	$currentmenu = $menu[$id];
}

sub glutDestroyMenu ($) {
	$menu[$id] = undef;
	
	if ($currentmenu->{id} == $id) {
		if (defined $currentmenu->{generated}) {
			$currentmenu->{generate}->destroy;
		}
		$currentmenu = undef;
	}
}

sub glutAddMenuEntry ($$) {
	my($label, $value) = @_;
	push @{$currentmenu->{entries}}, [1, $label, $value];
	
	$currentmenu->{regenerate}=1;
}

sub glutAddSubMenu ($$) {
	my($label, $submenu) = @_;
	push @{$currentmenu->{entries}}, [2, $label, $submenuid];

	$currentmenu->{regenerate}=1;
}

sub glutChangeToMenuEntry ($$$) {
	my($item, $label, $value) = @_;
	$currentmenu->{entries}->[$item] = [1, $label, $value];

	$currentmenu->{regenerate}=1;
}

sub glutChangeToSubMenu ($$$) {
	my($item, $label, $submenu) = @_;
	$currentmenu->{entries}->[$item] = [2, $label, $submenu];

	$currentmenu->{regenerate}=1;
}

sub glutRemoveMenuItem ($) {
	my($item) = @_;
	splice @{$currentmenu->{entries}}, $item, 1;

	$currentmenu->{regenerate}=1;
}

sub _regenerateMenu {
	my($m) = $_[0];
	
	if ($m->{regenerate} or !$m->{generated}) {
		return 1;
	}

	foreach (@{$m->{entries}}) {
		if ($_->[0] == 2 and $menu[$_->[2]]) {
			if (_regenerateMenu($menu[$_->[2]])) {
				return 1;
			}
		}
	}
	
	return 0;
}

sub _generateMenu {
	my($m) = @_;
	
	if (!_regenerateMenu($m)) {
		return $m->{generated};
	}

	if ($m->{generated}) {
		$m->{generated}->popdown;
		#destroy $m->{generated};
		$m->{generated} = undef;
		# Let it get garbage collected
	}

	my($menu) = new Gtk::Menu;
	
	foreach (@{$m->{entries}}) {
		if ($_->[0] == 1) {
			my($i) = new Gtk::MenuItem $_->[1];
			$i->signal_connect("activate" => \&_menu, $m, $_->[2]);
			show $i;
			$menu->append($i);
		} elsif ($_->[0] == 2) {
			my($i) = new Gtk::MenuItem $_->[1];
			$i->set_submenu(_generateMenu($menu[$_->[2]]));
			show $i;
			$menu->append($i);
		}
	}

	$menu->signal_connect("deactivate" => sub { _invoke($current->{menustate}, 0); } );

	$m->{generated} = $menu;
	$m->{regenerate} = 0;
	
	return $menu;
}

sub glutAttachMenu ($) {
	my($button) = @_;
	
	$current->{menu}->[$button] = $currentmenu->{id};
}

sub glutDetachMenu ($) {
	my($button) = @_;
	
	$current->{menu}->[$button] = undef;
}

sub glutPushWindow () {
	
	$current->{window}->window->lower();
}


sub glutPopWindow () {
	
	$current->{window}->window->raise();
}

sub _glut_init {
	my($mode) = @_;
	my(@i);
	my($g);
	
	if (($mode & GLUT_INDEX) != GLUT_INDEX) {
	
		push @i, GDK_GL_RGBA, GDK_GL_RED_SIZE, 1, GDK_GL_GREEN_SIZE, 1, GDK_GL_BLUE_SIZE, 1;

		if (($mode & GLUT_ALPHA) == GLUT_ALPHA) {
			push @i, GDK_GL_ALPHA_SIZE, 1;
		}

		if (($mode & GLUT_DOUBLE) == GLUT_DOUBLE) {
			push @i, GDK_GL_DOUBLEBUFFER;
		}

		if (($mode & GLUT_STEREO) == GLUT_STEREO) {
			push @i, GDK_GL_STEREO;
		}

		if (($mode & GLUT_DEPTH) == GLUT_DEPTH) {
			push @i, GDK_GL_DEPTH_SIZE, 1;
		}

		if (($mode & GLUT_STENCIL) == GLUT_STENCIL) {
			push @i, GDK_GL_STENCIL_SIZE, 1;
		}

		if (($mode & GLUT_ACCUM) == GLUT_ACCUM) {
			push @i, GDK_GL_ACCUM_RED_SIZE, 1;
			push @i, GDK_GL_ACCUM_GREEN_SIZE, 1;
			push @i, GDK_GL_ACCUM_BLUE_SIZE, 1;

			if (($mode & GLUT_ALPHA) == GLUT_ALPHA) {
				push @i, GDK_GL_ACCUM_ALPHA_SIZE, 1;
			}
		}

		return new Gtk::GLArea @i;
	
	} else {
	
		push @i, GDK_GL_BUFFER_SIZE, 1;

		if (($mode & GLUT_DOUBLE) == GLUT_DOUBLE) {
			push @i, GDK_GL_DOUBLEBUFFER;
		}

		if (($mode & GLUT_STEREO) == GLUT_STEREO) {
			push @i, GDK_GL_STEREO;
		}

		if (($mode & GLUT_DEPTH) == GLUT_DEPTH) {
			push @i, GDK_GL_DEPTH_SIZE, 1;
		}

		if (($mode & GLUT_STENCIL) == GLUT_STENCIL) {
			push @i, GDK_GL_STENCIL_SIZE, 1;
		}
	
		foreach (16, 12, 8, 4, 2, 1, 0) {
			$i[1] = $_;
			
			$g = new Gtk::GLArea @i;
			
			return $g if defined $g;
		}
	}
	
	return undef;
}

1;