diff options
-rw-r--r-- | examples/pugl_shader_demo.c | 62 | ||||
-rw-r--r-- | src/implementation.c | 48 | ||||
-rw-r--r-- | src/implementation.h | 8 | ||||
-rw-r--r-- | src/win.c | 15 | ||||
-rw-r--r-- | src/x11.c | 100 | ||||
-rw-r--r-- | src/x11.h | 1 | ||||
-rw-r--r-- | test/test_show_hide.c | 2 | ||||
-rw-r--r-- | test/test_update.c | 2 |
8 files changed, 177 insertions, 61 deletions
diff --git a/examples/pugl_shader_demo.c b/examples/pugl_shader_demo.c index aa5c38e..2fd9a57 100644 --- a/examples/pugl_shader_demo.c +++ b/examples/pugl_shader_demo.c @@ -398,6 +398,41 @@ teardownGl(PuglTestApp* app) deleteProgram(app->drawRect); } +static double +updateTimeout(const PuglTestApp* const app) +{ + if (!puglGetVisible(app->view)) { + return -1.0; // View is invisible (minimized), wait until something happens + } + + if (!app->opts.sync) { + return 0.0; // VSync explicitly disabled, run as fast as possible + } + + /* To minimize input latency and get smooth performance during window + resizing, we want to poll for events as long as possible before starting + to draw the next frame. This ensures that as many events are consumed as + possible before starting to draw, or, equivalently, that the next rendered + frame represents the latest events possible. This is particularly + important for mouse input and "live" window resizing, where many events + tend to pile up within a frame. + + To do this, we keep track of the time when the last frame was finished + drawing, and how long it took to expose (and assume this is relatively + stable). Then, we can calculate how much time there is from now until the + time when we should start drawing to not miss the deadline, and use that + as the timeout for puglUpdate(). + */ + + const int refreshRate = puglGetViewHint(app->view, PUGL_REFRESH_RATE); + const double now = puglGetTime(app->world); + const double nextFrameEndTime = app->lastFrameEndTime + (1.0 / refreshRate); + const double nextExposeTime = nextFrameEndTime - app->lastDrawDuration; + const double timeUntilNext = nextExposeTime - now; + + return timeUntilNext; +} + int main(int argc, char** argv) { @@ -426,36 +461,11 @@ main(int argc, char** argv) printViewHints(app.view); puglShow(app.view); - // Calculate ideal frame duration to drive the main loop at a good rate - const int refreshRate = puglGetViewHint(app.view, PUGL_REFRESH_RATE); - const double frameDuration = 1.0 / (double)refreshRate; - // Grind away, drawing continuously const double startTime = puglGetTime(app.world); PuglFpsPrinter fpsPrinter = {startTime}; while (!app.quit) { - /* To minimize input latency and get smooth performance during window - resizing, we want to poll for events as long as possible before - starting to draw the next frame. This ensures that as many events - are consumed as possible before starting to draw, or, equivalently, - that the next rendered frame represents the latest events possible. - This is particularly important for mouse input and "live" window - resizing, where many events tend to pile up within a frame. - - To do this, we keep track of the time when the last frame was - finished drawing, and how long it took to expose (and assume this is - relatively stable). Then, we can calculate how much time there is - from now until the time when we should start drawing to not miss the - deadline, and use that as the timeout for puglUpdate(). - */ - - const double now = puglGetTime(app.world); - const double nextFrameEndTime = app.lastFrameEndTime + frameDuration; - const double nextExposeTime = nextFrameEndTime - app.lastDrawDuration; - const double timeUntilNext = nextExposeTime - now; - const double timeout = app.opts.sync ? timeUntilNext : 0.0; - - puglUpdate(app.world, fmax(0.0, timeout)); + puglUpdate(app.world, fmax(0.0, updateTimeout(&app))); puglPrintFps(app.world, &fpsPrinter, &app.framesDrawn); } diff --git a/src/implementation.c b/src/implementation.c index ad0de63..d7763a7 100644 --- a/src/implementation.c +++ b/src/implementation.c @@ -373,23 +373,25 @@ puglDispatchSimpleEvent(PuglView* view, const PuglEventType type) } void -puglDispatchEventInContext(PuglView* view, const PuglEvent* event) +puglConfigure(PuglView* view, const PuglEvent* event) { - if (event->type == PUGL_CONFIGURE) { - view->frame.x = event->configure.x; - view->frame.y = event->configure.y; - view->frame.width = event->configure.width; - view->frame.height = event->configure.height; + assert(event->type == PUGL_CONFIGURE); - if (puglMustConfigure(view, &event->configure)) { - view->eventFunc(view, event); - view->lastConfigure = event->configure; - } - } else if (event->type == PUGL_EXPOSE) { - if (event->expose.width > 0 && event->expose.height > 0) { - view->eventFunc(view, event); - } - } else { + view->frame.x = event->configure.x; + view->frame.y = event->configure.y; + view->frame.width = event->configure.width; + view->frame.height = event->configure.height; + + if (puglMustConfigure(view, &event->configure)) { + view->eventFunc(view, event); + view->lastConfigure = event->configure; + } +} + +void +puglExpose(PuglView* view, const PuglEvent* event) +{ + if (event->expose.width > 0.0 && event->expose.height > 0.0) { view->eventFunc(view, event); } } @@ -409,13 +411,25 @@ puglDispatchEvent(PuglView* view, const PuglEvent* event) case PUGL_CONFIGURE: if (puglMustConfigure(view, &event->configure)) { view->backend->enter(view, NULL); - puglDispatchEventInContext(view, event); + puglConfigure(view, event); view->backend->leave(view, NULL); } break; + case PUGL_MAP: + if (!view->visible) { + view->visible = true; + view->eventFunc(view, event); + } + break; + case PUGL_UNMAP: + if (view->visible) { + view->visible = false; + view->eventFunc(view, event); + } + break; case PUGL_EXPOSE: view->backend->enter(view, &event->expose); - puglDispatchEventInContext(view, event); + puglExpose(view, event); view->backend->leave(view, &event->expose); break; default: diff --git a/src/implementation.h b/src/implementation.h index 8c5398c..936322d 100644 --- a/src/implementation.h +++ b/src/implementation.h @@ -58,9 +58,13 @@ puglDecodeUTF8(const uint8_t* buf); void puglDispatchSimpleEvent(PuglView* view, PuglEventType type); -/// Dispatch `event` to `view` while already in the graphics context +/// Process configure event while already in the graphics context void -puglDispatchEventInContext(PuglView* view, const PuglEvent* event); +puglConfigure(PuglView* view, const PuglEvent* event); + +/// Process expose event while already in the graphics context +void +puglExpose(PuglView* view, const PuglEvent* event); /// Dispatch `event` to `view`, entering graphics context if necessary void @@ -579,14 +579,17 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_INTERNALPAINT); } - if ((bool)wParam != view->visible) { - view->visible = wParam; - event.any.type = wParam ? PUGL_MAP : PUGL_UNMAP; - } + event.any.type = wParam ? PUGL_MAP : PUGL_UNMAP; break; case WM_SIZE: - handleConfigure(view, &event); - InvalidateRect(view->impl->hwnd, NULL, false); + if (wParam == SIZE_MINIMIZED) { + event.type = PUGL_UNMAP; + } else if (!view->visible) { + event.type = PUGL_MAP; + } else { + handleConfigure(view, &event); + InvalidateRect(view->impl->hwnd, NULL, false); + } break; case WM_SIZING: if (view->minAspectX) { @@ -48,6 +48,7 @@ #include <sys/select.h> #include <sys/time.h> +#include <limits.h> #include <math.h> #include <stdbool.h> #include <stdint.h> @@ -126,6 +127,8 @@ puglInitWorldInternals(const PuglWorldType type, const PuglWorldFlags flags) impl->atoms.NET_WM_STATE = XInternAtom(display, "_NET_WM_STATE", 0); impl->atoms.NET_WM_STATE_DEMANDS_ATTENTION = XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", 0); + impl->atoms.NET_WM_STATE_HIDDEN = + XInternAtom(display, "_NET_WM_STATE_HIDDEN", 0); // Open input method XSetLocaleModifiers(""); @@ -327,6 +330,7 @@ puglRealize(PuglView* const view) attr.event_mask |= KeyReleaseMask; attr.event_mask |= LeaveWindowMask; attr.event_mask |= PointerMotionMask; + attr.event_mask |= PropertyChangeMask; attr.event_mask |= StructureNotifyMask; attr.event_mask |= VisibilityChangeMask; @@ -550,6 +554,33 @@ translateModifiers(const unsigned xstate) ((xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0u)); } +static PuglStatus +getAtomProperty(PuglView* const view, + const Window window, + const Atom property, + unsigned long* numValues, + Atom** values) +{ + Atom actualType = 0; + int actualFormat = 0; + unsigned long bytesAfter = 0; + + return (XGetWindowProperty(view->impl->display, + window, + property, + 0, + LONG_MAX, + False, + XA_ATOM, + &actualType, + &actualFormat, + numValues, + &bytesAfter, + (unsigned char**)values) == Success) + ? PUGL_SUCCESS + : PUGL_FAILURE; +} + static PuglEvent translateClientMessage(PuglView* const view, const XClientMessageEvent message) { @@ -571,6 +602,39 @@ translateClientMessage(PuglView* const view, const XClientMessageEvent message) } static PuglEvent +translatePropertyNotify(PuglView* const view, XPropertyEvent message) +{ + const PuglX11Atoms* const atoms = &view->world->impl->atoms; + + PuglEvent event = {{PUGL_NOTHING, 0}}; + bool hidden = false; + if (message.atom == atoms->NET_WM_STATE) { + unsigned long numHints = 0; + Atom* hints = NULL; + if (getAtomProperty( + view, view->impl->win, message.atom, &numHints, &hints)) { + return event; + } + + for (unsigned long i = 0; i < numHints; ++i) { + if (hints[i] == atoms->NET_WM_STATE_HIDDEN) { + hidden = true; + } + } + + if (hidden && view->visible) { + event.type = PUGL_UNMAP; + } else if (!hidden && !view->visible) { + event.type = PUGL_MAP; + } + + XFree(hints); + } + + return event; +} + +static PuglEvent translateEvent(PuglView* const view, XEvent xevent) { PuglEvent event = {{PUGL_NOTHING, 0}}; @@ -580,15 +644,21 @@ translateEvent(PuglView* const view, XEvent xevent) case ClientMessage: event = translateClientMessage(view, xevent.xclient); break; + case PropertyNotify: + event = translatePropertyNotify(view, xevent.xproperty); + break; case VisibilityNotify: - view->visible = xevent.xvisibility.state != VisibilityFullyObscured; + if (xevent.xvisibility.state == VisibilityFullyObscured) { + event.type = PUGL_UNMAP; + } else { + event.type = PUGL_MAP; + } break; case MapNotify: event.type = PUGL_MAP; break; case UnmapNotify: - event.type = PUGL_UNMAP; - view->visible = false; + event.type = PUGL_UNMAP; break; case ConfigureNotify: event.type = PUGL_CONFIGURE; @@ -986,21 +1056,35 @@ flushExposures(PuglWorld* const world) for (size_t i = 0; i < world->numViews; ++i) { PuglView* const view = world->views[i]; + // Send update event so the application can trigger redraws if (view->visible) { puglDispatchSimpleEvent(view, PUGL_UPDATE); } + // Copy and reset pending events (in case their handlers write new ones) const PuglEvent configure = view->impl->pendingConfigure; const PuglEvent expose = view->impl->pendingExpose; view->impl->pendingConfigure.type = PUGL_NOTHING; view->impl->pendingExpose.type = PUGL_NOTHING; - if (configure.type || expose.type) { - view->backend->enter(view, expose.type ? &expose.expose : NULL); - puglDispatchEventInContext(view, &configure); - puglDispatchEventInContext(view, &expose); - view->backend->leave(view, expose.type ? &expose.expose : NULL); + if (expose.type) { + view->backend->enter(view, &expose.expose); + + if (configure.type) { + puglConfigure(view, &configure); + } + + puglExpose(view, &expose); + view->backend->leave(view, &expose.expose); + } else if (configure.type) { + view->backend->enter(view, NULL); + + if (configure.type) { + puglConfigure(view, &configure); + } + + view->backend->leave(view, NULL); } } } @@ -38,6 +38,7 @@ typedef struct { Atom NET_WM_NAME; Atom NET_WM_STATE; Atom NET_WM_STATE_DEMANDS_ATTENTION; + Atom NET_WM_STATE_HIDDEN; } PuglX11Atoms; typedef struct { diff --git a/test/test_show_hide.c b/test/test_show_hide.c index 2294709..b624735 100644 --- a/test/test_show_hide.c +++ b/test/test_show_hide.c @@ -75,7 +75,7 @@ onEvent(PuglView* view, const PuglEvent* event) test->state = EXPOSED; break; case PUGL_UNMAP: - assert(test->state == EXPOSED); + assert(test->state == MAPPED || test->state == EXPOSED); test->state = UNMAPPED; break; case PUGL_DESTROY: diff --git a/test/test_update.c b/test/test_update.c index d9c02ab..39415bc 100644 --- a/test/test_update.c +++ b/test/test_update.c @@ -75,8 +75,8 @@ onEvent(PuglView* view, const PuglEvent* event) case PUGL_UPDATE: if (test->state == EXPOSED1) { - puglPostRedisplay(view); test->state = UPDATED; + puglPostRedisplay(view); } break; |