@@ -18,7 +18,7 @@ test_map_CXXFLAGS = `pkg-config --cflags sdl gl SDL_image` | |||
test_map_LDADD = libcommon.a | |||
test_map_LDFLAGS = `pkg-config --libs sdl gl SDL_image` -lpipi | |||
editor_SOURCES = gtk/editor.cpp | |||
editor_SOURCES = gtk/editor.cpp gtk/glmapview.cpp gtk/glmapview.h | |||
editor_CXXFLAGS = `pkg-config --cflags sdl gl SDL_image gtk+-2.0 gtkgl-2.0` | |||
editor_LDADD = libcommon.a | |||
editor_LDFLAGS = `pkg-config --libs sdl gl gtk+-2.0 gtkgl-2.0 SDL_image` | |||
@@ -7,178 +7,15 @@ | |||
# include "config.h" | |||
#endif | |||
#include <cstdio> | |||
#include <cstdlib> | |||
#include <cmath> | |||
#include <gtk/gtk.h> | |||
#include <gtkgl/gtkglarea.h> | |||
#include "core.h" | |||
#include "glmapview.h" | |||
#include "debugfps.h" | |||
static volatile int quit = 0; | |||
static int ticking = 0; | |||
static int panning = 0; | |||
static double xpan = 0.0f, ypan = 0.0f; | |||
static float const FPS = 30.0f; | |||
static MapViewer *mv; | |||
static GtkWidget *glarea; | |||
static GtkAdjustment *hadj, *vadj; | |||
static gint main_quit(GtkWidget *widget, GdkEventExpose *event) | |||
{ | |||
(void)widget; | |||
(void)event; | |||
quit = 1; | |||
gtk_main_quit(); | |||
return FALSE; | |||
} | |||
static gboolean tick(void *widget) | |||
{ | |||
// FIXME: do not do anything if the previous tick was too recent? | |||
// FIXME: only quit if all entities have been cleaned | |||
if (quit) | |||
return FALSE; | |||
ticking = 1; | |||
mv->SetPOV(gtk_adjustment_get_value(hadj), gtk_adjustment_get_value(vadj)); | |||
/* Tick the game */ | |||
Ticker::TickGame(); | |||
gtk_widget_draw(GTK_WIDGET(widget), NULL); | |||
return TRUE; | |||
} | |||
static gboolean mouse_button(GtkWidget *widget, GdkEventButton *event, | |||
gpointer user_data) | |||
{ | |||
if (event->type == GDK_BUTTON_PRESS && event->button == 2) | |||
{ | |||
panning = 1; | |||
xpan = event->x; | |||
ypan = event->y; | |||
GdkCursor *cursor = gdk_cursor_new(GDK_HAND1); | |||
gdk_window_set_cursor(widget->window, cursor); | |||
gdk_cursor_unref(cursor); | |||
return FALSE; | |||
} | |||
else if (event->type == GDK_BUTTON_RELEASE && event->button == 2) | |||
{ | |||
panning = 0; | |||
gdk_window_set_cursor(widget->window, NULL); | |||
return FALSE; | |||
} | |||
return TRUE; | |||
} | |||
static gboolean mouse_motion(GtkWidget *widget, GdkEventMotion *event, | |||
gpointer user_data) | |||
{ | |||
if (panning) | |||
{ | |||
if (event->x != xpan) | |||
{ | |||
double val = gtk_adjustment_get_value(hadj); | |||
val += xpan - event->x; | |||
xpan = event->x; | |||
if (val + widget->allocation.width > mv->GetWidth()) | |||
val = mv->GetWidth() - widget->allocation.width; | |||
gtk_adjustment_set_value(hadj, val); | |||
gtk_adjustment_value_changed(hadj); | |||
} | |||
if (event->y != ypan) | |||
{ | |||
double val = gtk_adjustment_get_value(vadj); | |||
val += ypan - event->y; | |||
ypan = event->y; | |||
if (val + widget->allocation.height > mv->GetHeight()) | |||
val = mv->GetHeight() - widget->allocation.height; | |||
gtk_adjustment_set_value(vadj, val); | |||
gtk_adjustment_value_changed(vadj); | |||
} | |||
} | |||
return TRUE; | |||
} | |||
static gint init(GtkWidget *widget) | |||
{ | |||
/* Manage adjustments */ | |||
struct | |||
{ | |||
GtkAdjustment *adj; | |||
float map_size, sw_size; | |||
} | |||
s[2] = | |||
{ | |||
{ hadj, mv->GetWidth(), widget->allocation.width }, | |||
{ vadj, mv->GetHeight(), widget->allocation.height }, | |||
}; | |||
for (int i = 0; i < 2; i++) | |||
{ | |||
gtk_adjustment_set_lower(s[i].adj, 0); | |||
gtk_adjustment_set_upper(s[i].adj, s[i].map_size); | |||
gtk_adjustment_set_step_increment(s[i].adj, 1); | |||
gtk_adjustment_set_page_increment(s[i].adj, s[i].sw_size); | |||
gtk_adjustment_set_page_size(s[i].adj, s[i].sw_size); | |||
float val = gtk_adjustment_get_value(s[i].adj); | |||
if (val + s[i].sw_size > s[i].map_size) | |||
{ | |||
gtk_adjustment_set_value(s[i].adj, s[i].map_size - s[i].sw_size); | |||
gtk_adjustment_value_changed(s[i].adj); | |||
} | |||
} | |||
/* Set up display */ | |||
if (gtk_gl_area_make_current(GTK_GL_AREA(widget))) | |||
Video::Setup(widget->allocation.width, widget->allocation.height); | |||
return TRUE; | |||
} | |||
static gint reshape(GtkWidget *widget, GdkEventConfigure *event) | |||
{ | |||
(void)event; | |||
return init(widget); | |||
} | |||
static gint draw(GtkWidget *widget, GdkEventExpose *event) | |||
{ | |||
if (event->count > 0) | |||
return TRUE; | |||
/* OpenGL functions can be called only if make_current returns true */ | |||
if (ticking && gtk_gl_area_make_current(GTK_GL_AREA(widget))) | |||
{ | |||
ticking = 0; | |||
/* Clear the screen, tick the renderer, show the frame and | |||
* clamp to desired framerate */ | |||
Video::Clear(); | |||
Ticker::TickDraw(); | |||
gtk_gl_area_swapbuffers(GTK_GL_AREA(widget)); | |||
while (g_main_context_iteration(NULL, FALSE)) | |||
; | |||
Ticker::ClampFps(1000.0f / FPS); | |||
} | |||
return TRUE; | |||
} | |||
int main(int argc, char **argv) | |||
{ | |||
/* Initialize GTK */ | |||
@@ -191,69 +28,29 @@ int main(int argc, char **argv) | |||
return EXIT_FAILURE; | |||
} | |||
/* Build the application interface and keep a few member pointers */ | |||
/* Build the application interface */ | |||
GtkBuilder *builder = gtk_builder_new(); | |||
if (!gtk_builder_add_from_file(builder, "src/gtk/editor.xml", NULL)) | |||
{ | |||
g_print("Cannot build from XML\n"); | |||
return EXIT_FAILURE; | |||
} | |||
gtk_builder_connect_signals(builder, NULL); | |||
GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window1")); | |||
GtkWidget *viewport = GTK_WIDGET( | |||
gtk_builder_get_object(builder, "viewport1")); | |||
hadj = gtk_range_get_adjustment(GTK_RANGE( | |||
gtk_builder_get_object(builder, "hscrollbar1"))); | |||
vadj = gtk_range_get_adjustment(GTK_RANGE( | |||
gtk_builder_get_object(builder, "vscrollbar1"))); | |||
g_object_unref(G_OBJECT(builder)); | |||
/* Add our custom GL map viewer */ | |||
GlMapView *glmapview = new GlMapView(builder); | |||
/* Create new OpenGL widget */ | |||
int attrlist[] = | |||
{ | |||
GDK_GL_RGBA, | |||
GDK_GL_RED_SIZE, 1, | |||
GDK_GL_GREEN_SIZE, 1, | |||
GDK_GL_BLUE_SIZE, 1, | |||
GDK_GL_DOUBLEBUFFER, | |||
GDK_GL_NONE | |||
}; | |||
glarea = gtk_gl_area_new(attrlist); | |||
gtk_widget_set_usize(glarea, 400, 300); | |||
gtk_widget_set_events(glarea, GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | |||
| GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); | |||
gtk_container_add(GTK_CONTAINER(viewport), glarea); | |||
/* We tick from the idle function instead of a timeout to avoid | |||
* stealing time from the GTK loop when the callback time exceeds | |||
* the timeout value. */ | |||
gtk_idle_add(tick, glarea); | |||
/* Connect signals and show window */ | |||
gtk_signal_connect(GTK_OBJECT(window), "delete_event", | |||
GTK_SIGNAL_FUNC(main_quit), NULL); | |||
gtk_signal_connect(GTK_OBJECT(glarea), "expose_event", | |||
GTK_SIGNAL_FUNC(draw), NULL); | |||
gtk_signal_connect(GTK_OBJECT(glarea), "configure_event", | |||
GTK_SIGNAL_FUNC(reshape), NULL); | |||
gtk_signal_connect(GTK_OBJECT(glarea), "realize", | |||
GTK_SIGNAL_FUNC(init), NULL); | |||
gtk_signal_connect(GTK_OBJECT(glarea), "button_press_event", | |||
GTK_SIGNAL_FUNC(mouse_button), NULL); | |||
gtk_signal_connect(GTK_OBJECT(glarea), "button_release_event", | |||
GTK_SIGNAL_FUNC(mouse_button), NULL); | |||
gtk_signal_connect(GTK_OBJECT(glarea), "motion_notify_event", | |||
GTK_SIGNAL_FUNC(mouse_motion), NULL); | |||
/* Show window. We're good to go! */ | |||
gtk_widget_show_all(GTK_WIDGET(gtk_builder_get_object(builder, "window1"))); | |||
g_object_unref(G_OBJECT(builder)); | |||
// FIXME: detect when the map viewer is killed | |||
mv = new MapViewer("maps/testmap.tmx"); | |||
new DebugFps(); | |||
glmapview->LoadMap("maps/testmap.tmx"); | |||
gtk_widget_show_all(window); | |||
gtk_main(); | |||
delete glmapview; | |||
return EXIT_SUCCESS; | |||
} | |||
@@ -4,6 +4,7 @@ | |||
<!-- interface-naming-policy project-wide --> | |||
<object class="GtkWindow" id="window1"> | |||
<property name="title" translatable="yes">Deus Hax Editor</property> | |||
<signal name="delete_event" handler="gtk_main_quit"/> | |||
<child> | |||
<object class="GtkVBox" id="vbox1"> | |||
<property name="visible">True</property> | |||
@@ -160,6 +161,7 @@ | |||
<child> | |||
<object class="GtkHScrollbar" id="hscrollbar1"> | |||
<property name="visible">True</property> | |||
<property name="adjustment">gl_hadj</property> | |||
</object> | |||
<packing> | |||
<property name="top_attach">1</property> | |||
@@ -171,6 +173,7 @@ | |||
<object class="GtkVScrollbar" id="vscrollbar1"> | |||
<property name="visible">True</property> | |||
<property name="orientation">vertical</property> | |||
<property name="adjustment">gl_vadj</property> | |||
</object> | |||
<packing> | |||
<property name="left_attach">1</property> | |||
@@ -179,7 +182,7 @@ | |||
</packing> | |||
</child> | |||
<child> | |||
<object class="GtkViewport" id="viewport1"> | |||
<object class="GtkViewport" id="gl_container"> | |||
<property name="visible">True</property> | |||
<property name="resize_mode">queue</property> | |||
<child> | |||
@@ -229,4 +232,16 @@ | |||
</object> | |||
</child> | |||
</object> | |||
<object class="GtkAdjustment" id="gl_hadj"> | |||
<property name="upper">100</property> | |||
<property name="step_increment">1</property> | |||
<property name="page_increment">10</property> | |||
<property name="page_size">10</property> | |||
</object> | |||
<object class="GtkAdjustment" id="gl_vadj"> | |||
<property name="upper">100</property> | |||
<property name="step_increment">1</property> | |||
<property name="page_increment">10</property> | |||
<property name="page_size">10</property> | |||
</object> | |||
</interface> |
@@ -0,0 +1,257 @@ | |||
// | |||
// Deus Hax (working title) | |||
// Copyright (c) 2010 Sam Hocevar <sam@hocevar.net> | |||
// | |||
#if defined HAVE_CONFIG_H | |||
# include "config.h" | |||
#endif | |||
#include <gtk/gtk.h> | |||
#include <gtkgl/gtkglarea.h> | |||
#include "core.h" | |||
#include "glmapview.h" | |||
static float const FPS = 30.0f; | |||
GlMapView::GlMapView(GtkBuilder *builder) | |||
: hadj(GTK_ADJUSTMENT(gtk_builder_get_object(builder, "gl_hadj"))), | |||
vadj(GTK_ADJUSTMENT(gtk_builder_get_object(builder, "gl_vadj"))), | |||
ticking(FALSE), panning(FALSE), | |||
mapviewer(0), | |||
xpan(0.0), ypan(0.0) | |||
{ | |||
/* Create new OpenGL widget */ | |||
int attrlist[] = | |||
{ | |||
GDK_GL_RGBA, | |||
GDK_GL_RED_SIZE, 1, | |||
GDK_GL_GREEN_SIZE, 1, | |||
GDK_GL_BLUE_SIZE, 1, | |||
GDK_GL_DOUBLEBUFFER, | |||
GDK_GL_NONE | |||
}; | |||
glarea = gtk_gl_area_new(attrlist); | |||
gtk_widget_set_usize(glarea, 400, 300); | |||
gtk_widget_set_events(glarea, GDK_EXPOSURE_MASK | | |||
GDK_POINTER_MOTION_MASK | | |||
GDK_BUTTON_PRESS_MASK | | |||
GDK_BUTTON_RELEASE_MASK); | |||
GtkContainer *cont = GTK_CONTAINER(gtk_builder_get_object(builder, | |||
"gl_container")); | |||
gtk_container_add(cont, glarea); | |||
/* We tick from the idle function instead of a timeout to avoid | |||
* stealing time from the GTK loop when the callback time exceeds | |||
* the timeout value. */ | |||
g_idle_add((GSourceFunc)IdleTickSignal, this); | |||
gtk_signal_connect(GTK_OBJECT(glarea), "realize", | |||
GTK_SIGNAL_FUNC(SetupSignal), this); | |||
gtk_signal_connect(GTK_OBJECT(glarea), "destroy", | |||
GTK_SIGNAL_FUNC(DestroySignal), this); | |||
gtk_signal_connect(GTK_OBJECT(glarea), "expose_event", | |||
GTK_SIGNAL_FUNC(DrawSignal), this); | |||
gtk_signal_connect(GTK_OBJECT(glarea), "configure_event", | |||
GTK_SIGNAL_FUNC(ReshapeSignal), this); | |||
gtk_signal_connect(GTK_OBJECT(glarea), "button_press_event", | |||
GTK_SIGNAL_FUNC(MouseButtonSignal), this); | |||
gtk_signal_connect(GTK_OBJECT(glarea), "button_release_event", | |||
GTK_SIGNAL_FUNC(MouseButtonSignal), this); | |||
gtk_signal_connect(GTK_OBJECT(glarea), "motion_notify_event", | |||
GTK_SIGNAL_FUNC(MouseMotionSignal), this); | |||
} | |||
void GlMapView::LoadMap(char const *path) | |||
{ | |||
// FIXME: detect when the map viewer is killed | |||
mapviewer = new MapViewer(path); | |||
} | |||
gboolean GlMapView::IdleTick() | |||
{ | |||
// FIXME: do not do anything if the previous tick was too recent? | |||
ticking = TRUE; | |||
if (mapviewer) | |||
mapviewer->SetPOV(gtk_adjustment_get_value(hadj), | |||
gtk_adjustment_get_value(vadj)); | |||
/* Tick the game */ | |||
Ticker::TickGame(); | |||
gtk_widget_draw(GTK_WIDGET(glarea), NULL); | |||
return TRUE; | |||
} | |||
gboolean GlMapView::Setup() | |||
{ | |||
if (mapviewer) | |||
{ | |||
/* Manage adjustments */ | |||
struct { GtkAdjustment *adj; float map_size, sw_size; } s[2] = | |||
{ | |||
{ hadj, mapviewer->GetWidth(), glarea->allocation.width }, | |||
{ vadj, mapviewer->GetHeight(), glarea->allocation.height }, | |||
}; | |||
for (int i = 0; i < 2; i++) | |||
{ | |||
gtk_adjustment_set_lower(s[i].adj, 0); | |||
gtk_adjustment_set_upper(s[i].adj, s[i].map_size); | |||
gtk_adjustment_set_step_increment(s[i].adj, 1); | |||
gtk_adjustment_set_page_increment(s[i].adj, s[i].sw_size); | |||
gtk_adjustment_set_page_size(s[i].adj, s[i].sw_size); | |||
float val = gtk_adjustment_get_value(s[i].adj); | |||
if (val + s[i].sw_size > s[i].map_size) | |||
{ | |||
gtk_adjustment_set_value(s[i].adj, | |||
s[i].map_size - s[i].sw_size); | |||
gtk_adjustment_value_changed(s[i].adj); | |||
} | |||
} | |||
} | |||
/* Set up display */ | |||
if (gtk_gl_area_make_current(GTK_GL_AREA(glarea))) | |||
Video::Setup(glarea->allocation.width, glarea->allocation.height); | |||
return TRUE; | |||
} | |||
gboolean GlMapView::Destroy() | |||
{ | |||
g_idle_remove_by_data(this); | |||
return TRUE; | |||
} | |||
gboolean GlMapView::Draw(GdkEventExpose *event) | |||
{ | |||
if (event->count > 0) | |||
return TRUE; | |||
/* OpenGL functions can be called only if make_current returns true */ | |||
if (ticking && gtk_gl_area_make_current(GTK_GL_AREA(glarea))) | |||
{ | |||
ticking = FALSE; | |||
/* Clear the screen, tick the renderer, show the frame and | |||
* clamp to desired framerate */ | |||
Video::Clear(); | |||
Ticker::TickDraw(); | |||
gtk_gl_area_swapbuffers(GTK_GL_AREA(glarea)); | |||
while (g_main_context_iteration(NULL, FALSE)) | |||
; | |||
Ticker::ClampFps(1000.0f / FPS); | |||
} | |||
return TRUE; | |||
} | |||
gboolean GlMapView::MouseButton(GdkEventButton *event) | |||
{ | |||
if (event->type == GDK_BUTTON_PRESS && event->button == 2) | |||
{ | |||
panning = TRUE; | |||
xpan = event->x; | |||
ypan = event->y; | |||
GdkCursor *cursor = gdk_cursor_new(GDK_HAND1); | |||
gdk_window_set_cursor(glarea->window, cursor); | |||
gdk_cursor_unref(cursor); | |||
return FALSE; | |||
} | |||
else if (event->type == GDK_BUTTON_RELEASE && event->button == 2) | |||
{ | |||
panning = FALSE; | |||
gdk_window_set_cursor(glarea->window, NULL); | |||
return FALSE; | |||
} | |||
return TRUE; | |||
} | |||
gboolean GlMapView::MouseMotion(GdkEventMotion *event) | |||
{ | |||
if (panning) | |||
{ | |||
if (event->x != xpan) | |||
{ | |||
double val = gtk_adjustment_get_value(hadj); | |||
int map_width = mapviewer->GetWidth(); | |||
val += xpan - event->x; | |||
xpan = event->x; | |||
if (val + glarea->allocation.width > map_width) | |||
val = map_width - glarea->allocation.width; | |||
gtk_adjustment_set_value(hadj, val); | |||
gtk_adjustment_value_changed(hadj); | |||
} | |||
if (event->y != ypan) | |||
{ | |||
double val = gtk_adjustment_get_value(vadj); | |||
int map_height = mapviewer->GetHeight(); | |||
val += ypan - event->y; | |||
ypan = event->y; | |||
if (val + glarea->allocation.height > map_height) | |||
val = map_height - glarea->allocation.height; | |||
gtk_adjustment_set_value(vadj, val); | |||
gtk_adjustment_value_changed(vadj); | |||
} | |||
} | |||
return TRUE; | |||
} | |||
/* Private signal slots */ | |||
gboolean GlMapView::IdleTickSignal(GlMapView *that) | |||
{ | |||
return that->IdleTick(); | |||
} | |||
gboolean GlMapView::SetupSignal(GtkWidget *w, GlMapView *that) | |||
{ | |||
(void)w; | |||
return that->Setup(); | |||
} | |||
gboolean GlMapView::DestroySignal(GtkWidget *w, GlMapView *that) | |||
{ | |||
(void)w; | |||
return that->Destroy(); | |||
} | |||
gboolean GlMapView::DrawSignal(GtkWidget *w, GdkEventExpose *event, | |||
GlMapView *that) | |||
{ | |||
(void)w; | |||
return that->Draw(event); | |||
} | |||
gboolean GlMapView::ReshapeSignal(GtkWidget *w, GdkEventConfigure *event, | |||
GlMapView *that) | |||
{ | |||
(void)w; | |||
(void)event; | |||
return that->Setup(); | |||
} | |||
gboolean GlMapView::MouseButtonSignal(GtkWidget *w, GdkEventButton *event, | |||
GlMapView *that) | |||
{ | |||
(void)w; | |||
return that->MouseButton(event); | |||
} | |||
gboolean GlMapView::MouseMotionSignal(GtkWidget *w, GdkEventMotion *event, | |||
GlMapView *that) | |||
{ | |||
(void)w; | |||
return that->MouseMotion(event); | |||
} | |||
@@ -0,0 +1,49 @@ | |||
// | |||
// Deus Hax (working title) | |||
// Copyright (c) 2010 Sam Hocevar <sam@hocevar.net> | |||
// | |||
#if !defined __DH_GLMAPVIEW_H__ | |||
#define __DH_GLMAPVIEW_H__ | |||
#include <gtk/gtk.h> | |||
class GlMapView | |||
{ | |||
public: | |||
GlMapView(GtkBuilder *builder); | |||
void LoadMap(char const *path); | |||
private: | |||
/* Private methods */ | |||
gboolean IdleTick(); | |||
gboolean Setup(); | |||
gboolean Destroy(); | |||
gboolean Draw(GdkEventExpose *event); | |||
gboolean MouseButton(GdkEventButton *event); | |||
gboolean MouseMotion(GdkEventMotion *event); | |||
/* Private signal slots */ | |||
static gboolean IdleTickSignal(GlMapView *that); | |||
static gboolean SetupSignal(GtkWidget *w, GlMapView *that); | |||
static gboolean DestroySignal(GtkWidget *w, GlMapView *that); | |||
static gboolean DrawSignal(GtkWidget *w, GdkEventExpose *event, | |||
GlMapView *that); | |||
static gboolean ReshapeSignal(GtkWidget *w, GdkEventConfigure *event, | |||
GlMapView *that); | |||
static gboolean MouseButtonSignal(GtkWidget *w, GdkEventButton *event, | |||
GlMapView *that); | |||
static gboolean MouseMotionSignal(GtkWidget *w, GdkEventMotion *event, | |||
GlMapView *that); | |||
private: | |||
GtkAdjustment *hadj, *vadj; | |||
GtkWidget *glarea; | |||
gboolean ticking, panning; | |||
MapViewer *mapviewer; | |||
double xpan, ypan; | |||
}; | |||
#endif // __DH_GLMAPVIEW_H__ | |||