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

Last change on this file since 1106 was 1106, checked in by sam, 9 years ago

core: try to merge Ticker and Emcee. Still not very good.

  • Property svn:keywords set to Id
File size: 8.7 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(GtkContainer *in_container,
23                     GtkAdjustment *in_hadj,
24                     GtkAdjustment *in_vadj)
25  : hadj(in_hadj), vadj(in_vadj),
26    ticking(FALSE), panning(FALSE), destroyed(FALSE),
27    mapviewer(0),
28    xpan(0.0), ypan(0.0)
29{
30    /* Create new OpenGL widget */
31    int attrlist[] =
32    {
33        GDK_GL_RGBA,
34        GDK_GL_RED_SIZE, 1,
35        GDK_GL_GREEN_SIZE, 1,
36        GDK_GL_BLUE_SIZE, 1,
37        GDK_GL_DEPTH_SIZE, 16,
38        GDK_GL_DOUBLEBUFFER,
39        GDK_GL_NONE
40    };
41
42    glarea = gtk_gl_area_new(attrlist);
43    gtk_widget_set_usize(glarea, 400, 300);
44    gtk_widget_set_events(glarea, GDK_EXPOSURE_MASK |
45                                  GDK_POINTER_MOTION_MASK |
46                                  GDK_BUTTON_PRESS_MASK |
47                                  GDK_BUTTON_RELEASE_MASK);
48    gtk_widget_set_can_focus(glarea, TRUE);
49
50    gtk_container_add(in_container, glarea);
51
52    /* We tick from the idle function instead of a timeout to avoid
53     * stealing time from the GTK loop when the callback time exceeds
54     * the timeout value. */
55    g_idle_add((GSourceFunc)IdleTickSignal, this);
56
57    /* FIXME: for completeness, the "hierarchy_changed" signal should be
58     * intercepted in case we ever get reparented. */
59    g_signal_connect(GTK_OBJECT(glarea), "realize",
60                     GTK_SIGNAL_FUNC(SetupSignal), this);
61    g_signal_connect(GTK_OBJECT(glarea), "expose_event",
62                     GTK_SIGNAL_FUNC(DrawSignal), this);
63    g_signal_connect(GTK_OBJECT(glarea), "configure_event",
64                     GTK_SIGNAL_FUNC(ReshapeSignal), this);
65
66    g_signal_connect(GTK_OBJECT(glarea), "button_press_event",
67                     GTK_SIGNAL_FUNC(MouseButtonSignal), this);
68    g_signal_connect(GTK_OBJECT(glarea), "button_release_event",
69                     GTK_SIGNAL_FUNC(MouseButtonSignal), this);
70    g_signal_connect(GTK_OBJECT(glarea), "motion_notify_event",
71                     GTK_SIGNAL_FUNC(MouseMotionSignal), this);
72
73    g_signal_connect(GTK_OBJECT(glarea), "key_press_event",
74                     GTK_SIGNAL_FUNC(KeyPressSignal), this);
75}
76
77GlMapView::~GlMapView()
78{
79    Shutdown();
80}
81
82void GlMapView::LoadMap(char const *path)
83{
84    // FIXME: detect when the map viewer is killed
85    mapviewer = new MapViewer(path);
86    Ticker::Ref(mapviewer);
87
88    UpdateAdjustments();
89}
90
91void GlMapView::CloseMap()
92{
93    if (mapviewer)
94        Ticker::Unref(mapviewer);
95    mapviewer = NULL;
96
97    UpdateAdjustments();
98}
99
100gboolean GlMapView::IdleTick()
101{
102    if (Ticker::Finished())
103    {
104        /* Release the hijacked exit sequence */
105        gtk_main_quit();
106        return FALSE;
107    }
108
109    if (!ticking)
110    {
111        ticking = TRUE;
112
113        /* FIXME: shouldn't this be protected by a mutex? */
114        if (mapviewer)
115            mapviewer->SetPOV(gtk_adjustment_get_value(hadj),
116                              mapviewer->GetHeight() - glarea->allocation.height
117                               - gtk_adjustment_get_value(vadj));
118    }
119
120    gtk_widget_draw(GTK_WIDGET(glarea), NULL);
121
122    return TRUE;
123}
124
125gboolean GlMapView::Setup()
126{
127    /* Hook to the toplevel's unmap signal */
128    GtkWidget *toplevel = gtk_widget_get_toplevel(glarea);
129    if (g_signal_handler_find(toplevel, G_SIGNAL_MATCH_DATA,
130                              0, 0, NULL, NULL, this) == 0)
131        g_signal_connect(GTK_OBJECT(toplevel), "delete_event",
132                         GTK_SIGNAL_FUNC(DeleteSignal), this);
133
134    /* Set up display */
135    gtk_widget_grab_focus(glarea);
136    if (gtk_gl_area_make_current(GTK_GL_AREA(glarea)))
137    {
138        Ticker::Setup(FPS);
139        Video::Setup(ivec2(glarea->allocation.width,
140                           glarea->allocation.height));
141    }
142
143    UpdateAdjustments();
144
145    return TRUE;
146}
147
148/* This method is called when gtk_main_quit() is called, or when the toplevel
149 * widget is destroyed, or both. */
150gboolean GlMapView::Shutdown()
151{
152    /* Make sure we don't initiate shutdown twice. It could happen if we
153     * got the delete signal first, then the gtk_main_quit() signal. In
154     * that case, ignore the latter. */
155    if (!destroyed)
156    {
157        destroyed = TRUE;
158        CloseMap();
159        Ticker::Shutdown();
160        gtk_widget_set_sensitive(gtk_widget_get_toplevel(glarea), FALSE);
161        /* Hijack the exit sequence by adding another level of gtk_main,
162         * because returning too soon may leave us no time to clean up. */
163        gtk_main();
164    }
165    return FALSE;
166}
167
168gboolean GlMapView::Draw(GdkEventExpose *e)
169{
170    if (e->count > 0)
171        return TRUE;
172
173    /* OpenGL functions can be called only if make_current returns true */
174    if (ticking && gtk_gl_area_make_current(GTK_GL_AREA(glarea)))
175    {
176        ticking = FALSE;
177
178        /* Tick the renderer, show the frame and clamp to desired framerate */
179        Ticker::TickDraw();
180        gtk_gl_area_swapbuffers(GTK_GL_AREA(glarea));
181        while (g_main_context_iteration(NULL, FALSE))
182            ;
183    }
184
185    return TRUE;
186}
187
188void GlMapView::Scroll(double dx, double dy)
189{
190    gtk_adjustment_set_value(hadj, gtk_adjustment_get_value(hadj) + dx);
191    gtk_adjustment_set_value(vadj, gtk_adjustment_get_value(vadj) + dy);
192
193    UpdateAdjustments();
194}
195
196void GlMapView::UpdateAdjustments()
197{
198    float w = mapviewer ? mapviewer->GetWidth() : glarea->allocation.width;
199    float h = mapviewer ? mapviewer->GetHeight() : glarea->allocation.height;
200
201    /* Manage adjustments */
202    struct { GtkAdjustment *adj; float map_size, sw_size; } s[2] =
203    {
204        { hadj, w, glarea->allocation.width },
205        { vadj, h, glarea->allocation.height },
206    };
207
208    for (int i = 0; i < 2; i++)
209    {
210        gtk_adjustment_set_lower(s[i].adj, 0);
211        gtk_adjustment_set_upper(s[i].adj, s[i].map_size);
212        gtk_adjustment_set_step_increment(s[i].adj, 1);
213        gtk_adjustment_set_page_increment(s[i].adj, s[i].sw_size);
214        gtk_adjustment_set_page_size(s[i].adj, s[i].sw_size);
215
216        float val = gtk_adjustment_get_value(s[i].adj);
217        if (val + s[i].sw_size > s[i].map_size)
218        {
219            gtk_adjustment_set_value(s[i].adj,
220                                     s[i].map_size - s[i].sw_size);
221            gtk_adjustment_value_changed(s[i].adj);
222        }
223    }
224}
225
226gboolean GlMapView::MouseButton(GdkEventButton *e)
227{
228    if (e->type == GDK_BUTTON_PRESS && e->button == 2)
229    {
230        panning = TRUE;
231        xpan = e->x;
232        ypan = e->y;
233        GdkCursor *cursor = gdk_cursor_new(GDK_HAND1);
234        gdk_window_set_cursor(glarea->window, cursor);
235        gdk_cursor_unref(cursor);
236        return FALSE;
237    }
238    else if (e->type == GDK_BUTTON_RELEASE && e->button == 2)
239    {
240        panning = FALSE;
241        gdk_window_set_cursor(glarea->window, NULL);
242        return FALSE;
243    }
244
245    return TRUE;
246}
247
248gboolean GlMapView::MouseMotion(GdkEventMotion *e)
249{
250    if (panning)
251    {
252        Scroll(xpan - e->x, ypan - e->y);
253        xpan = e->x;
254        ypan = e->y;
255        return TRUE;
256    }
257
258    return FALSE;
259}
260
261gboolean GlMapView::KeyPress(GdkEventKey *e)
262{
263    switch (e->keyval)
264    {
265    case GDK_Up:    Scroll(  0.0, -10.0); return TRUE;
266    case GDK_Down:  Scroll(  0.0,  10.0); return TRUE;
267    case GDK_Left:  Scroll(-10.0,   0.0); return TRUE;
268    case GDK_Right: Scroll( 10.0,   0.0); return TRUE;
269    }
270
271    return FALSE;
272}
273
274/* Private signal slots */
275gboolean GlMapView::IdleTickSignal(GlMapView *that)
276{
277    return that->IdleTick();
278}
279
280gboolean GlMapView::SetupSignal(GtkWidget *w, GlMapView *that)
281{
282    (void)w;
283    return that->Setup();
284}
285
286gboolean GlMapView::DeleteSignal(GtkWidget *w, GdkEvent *e,
287                                 GlMapView *that)
288{
289    /* XXX: w is not glarea, it's the top level window */
290    (void)w;
291    (void)e;
292    return that->Shutdown();
293}
294
295gboolean GlMapView::DrawSignal(GtkWidget *w, GdkEventExpose *e,
296                               GlMapView *that)
297{
298    (void)w;
299    return that->Draw(e);
300}
301
302gboolean GlMapView::ReshapeSignal(GtkWidget *w, GdkEventConfigure *e,
303                                  GlMapView *that)
304{
305    (void)w;
306    (void)e;
307    return that->Setup();
308}
309
310gboolean GlMapView::MouseButtonSignal(GtkWidget *w, GdkEventButton *e,
311                                      GlMapView *that)
312{
313    (void)w;
314    return that->MouseButton(e);
315}
316
317gboolean GlMapView::MouseMotionSignal(GtkWidget *w, GdkEventMotion *e,
318                                      GlMapView *that)
319{
320    (void)w;
321    return that->MouseMotion(e);
322}
323
324gboolean GlMapView::KeyPressSignal(GtkWidget *w, GdkEventKey *e,
325                                   GlMapView *that)
326{
327    (void)w;
328    return that->KeyPress(e);
329}
330
Note: See TracBrowser for help on using the repository browser.