source: trunk/deushax/gtk/glmapview.cpp @ 824

Last change on this file since 824 was 824, checked in by sam, 10 years ago

gtk: refactor the GtkGlArea handling so that it can be destroyed even if
we are not actually closing the whole application.

  • Property svn:keywords set to Id
File size: 8.2 KB
Line 
1//
2// Deus Hax (working title)
3// Copyright (c) 2010 Sam Hocevar <sam@hocevar.net>
4//
5
6#if defined HAVE_CONFIG_H
7#   include "config.h"
8#endif
9
10#include <gtk/gtk.h>
11#include <gtkgl/gtkglarea.h>
12#include <gdk/gdkkeysyms.h>
13
14#include "core.h"
15
16using namespace lol;
17
18#include "glmapview.h"
19
20static float const FPS = 30.0f;
21
22GlMapView::GlMapView(GtkBuilder *builder)
23  : hadj(GTK_ADJUSTMENT(gtk_builder_get_object(builder, "gl_hadj"))),
24    vadj(GTK_ADJUSTMENT(gtk_builder_get_object(builder, "gl_vadj"))),
25    ticking(FALSE), panning(FALSE), destroyed(FALSE),
26    mapviewer(0),
27    xpan(0.0), ypan(0.0)
28{
29    /* Create new OpenGL widget */
30    int attrlist[] =
31    {
32        GDK_GL_RGBA,
33        GDK_GL_RED_SIZE, 1,
34        GDK_GL_GREEN_SIZE, 1,
35        GDK_GL_BLUE_SIZE, 1,
36        GDK_GL_DEPTH_SIZE, 16,
37        GDK_GL_DOUBLEBUFFER,
38        GDK_GL_NONE
39    };
40
41    glarea = gtk_gl_area_new(attrlist);
42    gtk_widget_set_usize(glarea, 400, 300);
43    gtk_widget_set_events(glarea, GDK_EXPOSURE_MASK |
44                                  GDK_POINTER_MOTION_MASK |
45                                  GDK_BUTTON_PRESS_MASK |
46                                  GDK_BUTTON_RELEASE_MASK);
47    gtk_widget_set_can_focus(glarea, TRUE);
48
49    GtkContainer *cont = GTK_CONTAINER(gtk_builder_get_object(builder,
50                                                              "gl_container"));
51    gtk_container_add(cont, glarea);
52
53    /* We tick from the idle function instead of a timeout to avoid
54     * stealing time from the GTK loop when the callback time exceeds
55     * the timeout value. */
56    g_idle_add((GSourceFunc)IdleTickSignal, this);
57
58    /* We must divert gtk_main_quit() to release our resources when the
59     * GL widget is still realised. We'll call gtk_main_quit() when we
60     * are sure that everything has been cleaned. */
61    gtk_quit_add(0, (GtkFunction)ShutdownSignal, this);
62
63    gtk_signal_connect(GTK_OBJECT(glarea), "realize",
64                       GTK_SIGNAL_FUNC(SetupSignal), this);
65    gtk_signal_connect(GTK_OBJECT(glarea), "expose_event",
66                       GTK_SIGNAL_FUNC(DrawSignal), this);
67    gtk_signal_connect(GTK_OBJECT(glarea), "configure_event",
68                       GTK_SIGNAL_FUNC(ReshapeSignal), this);
69
70    gtk_signal_connect(GTK_OBJECT(glarea), "button_press_event",
71                       GTK_SIGNAL_FUNC(MouseButtonSignal), this);
72    gtk_signal_connect(GTK_OBJECT(glarea), "button_release_event",
73                       GTK_SIGNAL_FUNC(MouseButtonSignal), this);
74    gtk_signal_connect(GTK_OBJECT(glarea), "motion_notify_event",
75                       GTK_SIGNAL_FUNC(MouseMotionSignal), this);
76
77    gtk_signal_connect(GTK_OBJECT(glarea), "key_press_event",
78                       GTK_SIGNAL_FUNC(KeyPressSignal), this);
79}
80
81void GlMapView::LoadMap(char const *path)
82{
83    // FIXME: detect when the map viewer is killed
84    mapviewer = new MapViewer(path);
85    Ticker::Ref(mapviewer);
86
87    UpdateAdjustments();
88}
89
90void GlMapView::CloseMap()
91{
92    if (mapviewer)
93        Ticker::Unref(mapviewer);
94    mapviewer = NULL;
95
96    UpdateAdjustments();
97}
98
99gboolean GlMapView::IdleTick()
100{
101    if (Ticker::Finished())
102    {
103        /* Release the hijacked exit sequence */
104        gtk_main_quit();
105        return FALSE;
106    }
107
108    ticking = TRUE;
109
110    if (mapviewer)
111        mapviewer->SetPOV(gtk_adjustment_get_value(hadj),
112                          mapviewer->GetHeight() - glarea->allocation.height
113                           - gtk_adjustment_get_value(vadj));
114
115    /* Tick the game */
116    Ticker::TickGame();
117
118    gtk_widget_draw(GTK_WIDGET(glarea), NULL);
119
120    return TRUE;
121}
122
123gboolean GlMapView::Setup()
124{
125    /* Set up display */
126    gtk_widget_grab_focus(glarea);
127    if (gtk_gl_area_make_current(GTK_GL_AREA(glarea)))
128    {
129        Ticker::Setup(FPS);
130        Video::Setup(vec2i(glarea->allocation.width,
131                           glarea->allocation.height));
132    }
133
134    UpdateAdjustments();
135
136    return TRUE;
137}
138
139gboolean GlMapView::Shutdown()
140{
141    if (!destroyed)
142    {
143        destroyed = TRUE;
144        CloseMap();
145        Ticker::Shutdown();
146        gtk_widget_set_sensitive(gtk_widget_get_toplevel(glarea), FALSE);
147        /* Hijack the exit sequence by adding another level of gtk_main */
148        gtk_main();
149    }
150    return FALSE;
151}
152
153gboolean GlMapView::Draw(GdkEventExpose *e)
154{
155    if (e->count > 0)
156        return TRUE;
157
158    /* OpenGL functions can be called only if make_current returns true */
159    if (ticking && gtk_gl_area_make_current(GTK_GL_AREA(glarea)))
160    {
161        ticking = FALSE;
162
163        /* Tick the renderer, show the frame and clamp to desired framerate */
164        Ticker::TickDraw();
165        gtk_gl_area_swapbuffers(GTK_GL_AREA(glarea));
166        while (g_main_context_iteration(NULL, FALSE))
167            ;
168
169        // FIXME: do some GTK stuff in here
170        Ticker::ClampFps();
171    }
172
173    return TRUE;
174}
175
176void GlMapView::Scroll(double dx, double dy)
177{
178    gtk_adjustment_set_value(hadj, gtk_adjustment_get_value(hadj) + dx);
179    gtk_adjustment_set_value(vadj, gtk_adjustment_get_value(vadj) + dy);
180
181    UpdateAdjustments();
182}
183
184void GlMapView::UpdateAdjustments()
185{
186    float w = mapviewer ? mapviewer->GetWidth() : glarea->allocation.width;
187    float h = mapviewer ? mapviewer->GetHeight() : glarea->allocation.height;
188
189    /* Manage adjustments */
190    struct { GtkAdjustment *adj; float map_size, sw_size; } s[2] =
191    {
192        { hadj, w, glarea->allocation.width },
193        { vadj, h, glarea->allocation.height },
194    };
195
196    for (int i = 0; i < 2; i++)
197    {
198        gtk_adjustment_set_lower(s[i].adj, 0);
199        gtk_adjustment_set_upper(s[i].adj, s[i].map_size);
200        gtk_adjustment_set_step_increment(s[i].adj, 1);
201        gtk_adjustment_set_page_increment(s[i].adj, s[i].sw_size);
202        gtk_adjustment_set_page_size(s[i].adj, s[i].sw_size);
203
204        float val = gtk_adjustment_get_value(s[i].adj);
205        if (val + s[i].sw_size > s[i].map_size)
206        {
207            gtk_adjustment_set_value(s[i].adj,
208                                     s[i].map_size - s[i].sw_size);
209            gtk_adjustment_value_changed(s[i].adj);
210        }
211    }
212}
213
214gboolean GlMapView::MouseButton(GdkEventButton *e)
215{
216    if (e->type == GDK_BUTTON_PRESS && e->button == 2)
217    {
218        panning = TRUE;
219        xpan = e->x;
220        ypan = e->y;
221        GdkCursor *cursor = gdk_cursor_new(GDK_HAND1);
222        gdk_window_set_cursor(glarea->window, cursor);
223        gdk_cursor_unref(cursor);
224        return FALSE;
225    }
226    else if (e->type == GDK_BUTTON_RELEASE && e->button == 2)
227    {
228        panning = FALSE;
229        gdk_window_set_cursor(glarea->window, NULL);
230        return FALSE;
231    }
232
233    return TRUE;
234}
235
236gboolean GlMapView::MouseMotion(GdkEventMotion *e)
237{
238    if (panning)
239    {
240        Scroll(xpan - e->x, ypan - e->y);
241        xpan = e->x;
242        ypan = e->y;
243        return TRUE;
244    }
245
246    return FALSE;
247}
248
249gboolean GlMapView::KeyPress(GdkEventKey *e)
250{
251    switch (e->keyval)
252    {
253    case GDK_Up:    Scroll(  0.0, -10.0); return TRUE;
254    case GDK_Down:  Scroll(  0.0,  10.0); return TRUE;
255    case GDK_Left:  Scroll(-10.0,   0.0); return TRUE;
256    case GDK_Right: Scroll( 10.0,   0.0); return TRUE;
257    }
258
259    return FALSE;
260}
261
262/* Private signal slots */
263gboolean GlMapView::IdleTickSignal(GlMapView *that)
264{
265    return that->IdleTick();
266}
267
268gboolean GlMapView::SetupSignal(GtkWidget *w, GlMapView *that)
269{
270    (void)w;
271    return that->Setup();
272}
273
274gboolean GlMapView::ShutdownSignal(GlMapView *that)
275{
276    return that->Shutdown();
277}
278
279gboolean GlMapView::DrawSignal(GtkWidget *w, GdkEventExpose *e,
280                               GlMapView *that)
281{
282    (void)w;
283    return that->Draw(e);
284}
285
286gboolean GlMapView::ReshapeSignal(GtkWidget *w, GdkEventConfigure *e,
287                                  GlMapView *that)
288{
289    (void)w;
290    (void)e;
291    return that->Setup();
292}
293
294gboolean GlMapView::MouseButtonSignal(GtkWidget *w, GdkEventButton *e,
295                                      GlMapView *that)
296{
297    (void)w;
298    return that->MouseButton(e);
299}
300
301gboolean GlMapView::MouseMotionSignal(GtkWidget *w, GdkEventMotion *e,
302                                      GlMapView *that)
303{
304    (void)w;
305    return that->MouseMotion(e);
306}
307
308gboolean GlMapView::KeyPressSignal(GtkWidget *w, GdkEventKey *e,
309                                   GlMapView *that)
310{
311    (void)w;
312    return that->KeyPress(e);
313}
314
Note: See TracBrowser for help on using the repository browser.