diff options
-rw-r--r-- | bindings/cpp/include/pugl/pugl.hpp | 22 | ||||
-rw-r--r-- | examples/meson.build | 4 | ||||
-rw-r--r-- | examples/pugl_embed_demo.c | 3 | ||||
-rw-r--r-- | examples/pugl_management_demo.app/MacOS/meson.build | 11 | ||||
-rw-r--r-- | examples/pugl_management_demo.app/meson.build | 11 | ||||
-rw-r--r-- | examples/pugl_management_demo.c | 262 | ||||
-rw-r--r-- | examples/pugl_vulkan_cpp_demo.cpp | 3 | ||||
-rw-r--r-- | include/pugl/pugl.h | 118 | ||||
-rw-r--r-- | src/common.c | 12 | ||||
-rw-r--r-- | src/mac.m | 207 | ||||
-rw-r--r-- | src/types.h | 1 | ||||
-rw-r--r-- | src/win.c | 132 | ||||
-rw-r--r-- | src/win.h | 24 | ||||
-rw-r--r-- | src/x11.c | 236 | ||||
-rw-r--r-- | src/x11.h | 10 | ||||
-rw-r--r-- | test/test_utils.h | 49 |
16 files changed, 961 insertions, 144 deletions
diff --git a/bindings/cpp/include/pugl/pugl.hpp b/bindings/cpp/include/pugl/pugl.hpp index 267b07c..e8c9d72 100644 --- a/bindings/cpp/include/pugl/pugl.hpp +++ b/bindings/cpp/include/pugl/pugl.hpp @@ -100,13 +100,15 @@ struct Event final : Base { {} }; -using Mod = PuglMod; ///< @copydoc PuglMod -using Mods = PuglMods; ///< @copydoc PuglMods -using Key = PuglKey; ///< @copydoc PuglKey -using EventType = PuglEventType; ///< @copydoc PuglEventType -using EventFlag = PuglEventFlag; ///< @copydoc PuglEventFlag -using EventFlags = PuglEventFlags; ///< @copydoc PuglEventFlags -using CrossingMode = PuglCrossingMode; ///< @copydoc PuglCrossingMode +using Mod = PuglMod; ///< @copydoc PuglMod +using Mods = PuglMods; ///< @copydoc PuglMods +using Key = PuglKey; ///< @copydoc PuglKey +using EventType = PuglEventType; ///< @copydoc PuglEventType +using EventFlag = PuglEventFlag; ///< @copydoc PuglEventFlag +using EventFlags = PuglEventFlags; ///< @copydoc PuglEventFlags +using CrossingMode = PuglCrossingMode; ///< @copydoc PuglCrossingMode +using ViewStyleFlag = PuglViewStyleFlag; ///< @copydoc PuglViewStyleFlag +using ViewStyleFlags = PuglViewStyleFlags; ///< @copydoc PuglViewStyleFlags /// @copydoc PuglRealizeEvent using RealizeEvent = Event<PUGL_REALIZE, PuglRealizeEvent>; @@ -600,10 +602,10 @@ public: return static_cast<Status>(puglAcceptOffer(cobj(), &offer, typeIndex)); } - /// @copydoc puglRequestAttention - Status requestAttention() noexcept + /// @copydoc puglSetViewStyle + Status setViewStyle(const PuglViewStyleFlags flags) { - return static_cast<Status>(puglRequestAttention(cobj())); + return static_cast<Status>(puglSetViewStyle(cobj(), flags)); } /** diff --git a/examples/meson.build b/examples/meson.build index 70a2384..2b4e5f1 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -18,7 +18,8 @@ gl_examples = [ ] cairo_examples = [ - 'pugl_cairo_demo.c' + 'pugl_cairo_demo.c', + 'pugl_management_demo.c', ] vulkan_examples = [ @@ -89,6 +90,7 @@ if host_machine.system() == 'darwin' if cairo_dep.found() subdir('pugl_cairo_demo.app') + subdir('pugl_management_demo.app') endif if opengl_dep.found() diff --git a/examples/pugl_embed_demo.c b/examples/pugl_embed_demo.c index 94310a5..59d8e2b 100644 --- a/examples/pugl_embed_demo.c +++ b/examples/pugl_embed_demo.c @@ -319,7 +319,8 @@ main(int argc, char** argv) ++framesDrawn; if (!requestedAttention && thisTime > 5.0) { - puglRequestAttention(app.parent); + puglSetViewStyle( + app.parent, puglGetViewStyle(app.parent) | PUGL_VIEW_STYLE_DEMANDING); requestedAttention = true; } diff --git a/examples/pugl_management_demo.app/MacOS/meson.build b/examples/pugl_management_demo.app/MacOS/meson.build new file mode 100644 index 0000000..1ad7b21 --- /dev/null +++ b/examples/pugl_management_demo.app/MacOS/meson.build @@ -0,0 +1,11 @@ +# Copyright 2021-2023 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: 0BSD OR ISC + +executable( + 'pugl_management_demo', + ['../../pugl_management_demo.c'], + c_args: example_defines + example_c_args + cairo_args, + cpp_args: example_defines + example_cpp_args, + dependencies: [pugl_dep, cairo_backend_dep], + include_directories: include_directories('../../..'), +) diff --git a/examples/pugl_management_demo.app/meson.build b/examples/pugl_management_demo.app/meson.build new file mode 100644 index 0000000..380e38d --- /dev/null +++ b/examples/pugl_management_demo.app/meson.build @@ -0,0 +1,11 @@ +# Copyright 2021-2023 David Robillard <d@drobilla.net> +# SPDX-License-Identifier: 0BSD OR ISC + +config = configuration_data() +config.set('NAME', 'pugl_management_demo') + +info_plist = configure_file(configuration: config, + input: files('../../resources/Info.plist.in'), + output: 'Info.plist') + +subdir('MacOS') diff --git a/examples/pugl_management_demo.c b/examples/pugl_management_demo.c new file mode 100644 index 0000000..6a62668 --- /dev/null +++ b/examples/pugl_management_demo.c @@ -0,0 +1,262 @@ +// Copyright 2012-2023 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC + +/* + A demonstration of window types, states, and management. +*/ + +#include "test/test_utils.h" + +#include "pugl/cairo.h" +#include "pugl/pugl.h" + +#include <cairo.h> + +#include <stdbool.h> +#include <stdio.h> + +typedef struct { + PuglView* view; + const char* label; +} LabeledView; + +typedef struct { + PuglWorld* world; + LabeledView mainView; + LabeledView dialogView; + bool quit; + bool verbose; +} DemoApp; + +static PuglStatus +onCommonEvent(PuglView* view, const PuglEvent* event); + +static PuglStatus +onMainEvent(PuglView* view, const PuglEvent* event); + +static PuglStatus +onExpose(PuglView* const view, const PuglExposeEvent* const event) +{ + PuglWorld* const world = puglGetWorld(view); + DemoApp* const app = (DemoApp*)puglGetWorldHandle(world); + const PuglRect frame = puglGetFrame(view); + const PuglViewStyleFlags style = puglGetViewStyle(view); + const PuglCoord cx = (PuglCoord)(frame.width / 2U); + const PuglCoord cy = (PuglCoord)(frame.height / 2U); + cairo_t* const cr = (cairo_t*)puglGetContext(view); + + // Clip to expose region + cairo_rectangle(cr, event->x, event->y, event->width, event->height); + cairo_clip_preserve(cr); + + // Draw background + cairo_set_source_rgb(cr, 0.2, 0.2, 0.2); + cairo_set_line_width(cr, 4.0); + cairo_fill(cr); + + // Set up text renering + char buf[128] = {0}; + cairo_text_extents_t extents = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + cairo_set_font_size(cr, 32.0); + + // Draw time label + snprintf(buf, sizeof(buf), "Draw time: %g", puglGetTime(world)); + cairo_text_extents(cr, buf, &extents); + cairo_move_to(cr, cx - extents.width / 2.0, cy + extents.height / 2.0 - 48.0); + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + cairo_show_text(cr, buf); + + // Draw style label + snprintf(buf, + sizeof(buf), + "Style:%s%s%s%s%s%s%s%s%s", + style & PUGL_VIEW_STYLE_MODAL ? " modal" : "", + style & PUGL_VIEW_STYLE_TALL ? " tall" : "", + style & PUGL_VIEW_STYLE_WIDE ? " wide" : "", + style & PUGL_VIEW_STYLE_HIDDEN ? " hidden" : "", + style & PUGL_VIEW_STYLE_FULLSCREEN ? " fullscreen" : "", + style & PUGL_VIEW_STYLE_ABOVE ? " above" : "", + style & PUGL_VIEW_STYLE_BELOW ? " below" : "", + style & PUGL_VIEW_STYLE_DEMANDING ? " demanding" : "", + style & PUGL_VIEW_STYLE_RESIZING ? " resizing" : ""); + cairo_text_extents(cr, buf, &extents); + cairo_move_to(cr, cx - extents.width / 2.0, cy + extents.height / 2.0); + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + cairo_show_text(cr, buf); + + if (view == app->mainView.view) { + // Draw keyboard help label + snprintf(buf, sizeof(buf), "Keys: Space T W H M F A B D Q"); + cairo_text_extents(cr, buf, &extents); + cairo_move_to( + cr, cx - extents.width / 2.0, cy + extents.height / 2.0 + 48.0); + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + cairo_show_text(cr, buf); + } + + return PUGL_SUCCESS; +} + +static PuglStatus +toggleDialog(DemoApp* const app) +{ + if (app->dialogView.view && puglGetVisible(app->dialogView.view)) { + return puglUnrealize(app->dialogView.view); + } + + if (!app->dialogView.view) { + app->dialogView.view = puglNewView(app->world); + + puglSetBackend(app->dialogView.view, puglCairoBackend()); + puglSetEventFunc(app->dialogView.view, onCommonEvent); + puglSetHandle(app->dialogView.view, &app->dialogView); + puglSetTransientParent(app->dialogView.view, + puglGetNativeView(app->mainView.view)); + puglSetSizeHint(app->dialogView.view, PUGL_DEFAULT_SIZE, 320, 240); + puglSetSizeHint(app->dialogView.view, PUGL_MIN_SIZE, 160, 120); + puglSetViewHint(app->dialogView.view, PUGL_IGNORE_KEY_REPEAT, true); + puglSetViewHint(app->dialogView.view, PUGL_RESIZABLE, true); + puglSetViewHint( + app->dialogView.view, PUGL_VIEW_TYPE, PUGL_VIEW_TYPE_DIALOG); + puglSetWindowTitle(app->dialogView.view, "Dialog"); + } + + return puglShow(app->dialogView.view); +} + +static PuglStatus +onKeyPress(PuglView* view, const PuglKeyEvent* event) +{ + PuglWorld* const world = puglGetWorld(view); + DemoApp* const app = (DemoApp*)puglGetWorldHandle(world); + const PuglViewStyleFlags flags = puglGetViewStyle(view); + + switch (event->key) { + case ' ': + toggleDialog(app); + break; + case 't': + return puglSetViewStyle(view, flags ^ PUGL_VIEW_STYLE_TALL); + case 'w': + return puglSetViewStyle(view, flags ^ PUGL_VIEW_STYLE_WIDE); + case 'h': + return puglSetViewStyle(view, flags ^ PUGL_VIEW_STYLE_HIDDEN); + case 'm': + if ((flags & PUGL_VIEW_STYLE_TALL) && (flags & PUGL_VIEW_STYLE_WIDE)) { + return puglSetViewStyle( + view, flags & ~(PUGL_VIEW_STYLE_TALL | PUGL_VIEW_STYLE_WIDE)); + } + + return puglSetViewStyle( + view, flags | PUGL_VIEW_STYLE_TALL | PUGL_VIEW_STYLE_WIDE); + case 'f': + return puglSetViewStyle(view, flags ^ PUGL_VIEW_STYLE_FULLSCREEN); + case 'a': + return puglSetViewStyle(view, flags ^ PUGL_VIEW_STYLE_ABOVE); + case 'b': + return puglSetViewStyle(view, flags ^ PUGL_VIEW_STYLE_BELOW); + case 'd': + return puglSetViewStyle(view, flags ^ PUGL_VIEW_STYLE_DEMANDING); + case 'q': + case PUGL_KEY_ESCAPE: + app->quit = true; + break; + } + + return PUGL_SUCCESS; +} + +static PuglStatus +onCommonEvent(PuglView* view, const PuglEvent* const event) +{ + PuglWorld* const world = puglGetWorld(view); + DemoApp* const app = (DemoApp*)puglGetWorldHandle(world); + LabeledView* const data = (LabeledView*)puglGetHandle(view); + + const char* const prefix = data->label; + printEvent(event, prefix, app->verbose); + + switch (event->type) { + case PUGL_CLOSE: + if (view == app->dialogView.view) { + puglUnrealize(app->dialogView.view); + } + break; + case PUGL_CONFIGURE: + return puglPostRedisplay(view); + case PUGL_EXPOSE: + return onExpose(view, &event->expose); + case PUGL_KEY_PRESS: + return onKeyPress(view, &event->key); + default: + break; + } + + return PUGL_SUCCESS; +} + +static PuglStatus +onMainEvent(PuglView* view, const PuglEvent* const event) +{ + PuglWorld* const world = puglGetWorld(view); + DemoApp* const app = (DemoApp*)puglGetWorldHandle(world); + + switch (event->type) { + case PUGL_CLOSE: + app->quit = true; + return PUGL_SUCCESS; + default: + break; + } + + return onCommonEvent(view, event); +} + +int +main(int argc, char** argv) +{ + DemoApp app = {0}; + + const PuglTestOptions opts = puglParseTestOptions(&argc, &argv); + if (opts.help) { + puglPrintTestUsage(argv[0], ""); + return 1; + } + + app.verbose = opts.verbose; + + app.world = puglNewWorld(PUGL_PROGRAM, 0); + + puglSetWorldHandle(app.world, &app); + puglSetClassName(app.world, "PuglDemoApp"); + + app.mainView.view = puglNewView(app.world); + app.mainView.label = "Main: "; + app.dialogView.label = "Dialog: "; + + // Set up main view + puglSetBackend(app.mainView.view, puglCairoBackend()); + puglSetEventFunc(app.mainView.view, onMainEvent); + puglSetHandle(app.mainView.view, &app.mainView); + puglSetSizeHint(app.mainView.view, PUGL_DEFAULT_SIZE, 640, 480); + puglSetSizeHint(app.mainView.view, PUGL_MIN_SIZE, 320, 240); + puglSetViewHint(app.mainView.view, PUGL_IGNORE_KEY_REPEAT, true); + puglSetViewHint(app.mainView.view, PUGL_RESIZABLE, true); + puglSetViewHint(app.mainView.view, PUGL_VIEW_TYPE, PUGL_VIEW_TYPE_NORMAL); + puglSetWindowTitle(app.mainView.view, "Main Window"); + + PuglStatus st = PUGL_SUCCESS; + if ((st = puglRealize(app.mainView.view))) { + return logError("Failed to realize view (%s)\n", puglStrerror(st)); + } + + puglShow(app.mainView.view); + + while (!app.quit) { + puglUpdate(app.world, -1.0); + } + + puglFreeView(app.mainView.view); + puglFreeWorld(app.world); + return 0; +} diff --git a/examples/pugl_vulkan_cpp_demo.cpp b/examples/pugl_vulkan_cpp_demo.cpp index df0827e..487fab2 100644 --- a/examples/pugl_vulkan_cpp_demo.cpp +++ b/examples/pugl_vulkan_cpp_demo.cpp @@ -1475,6 +1475,7 @@ View::onEvent(const pugl::ConfigureEvent& event) _app.extent = {static_cast<uint32_t>(event.width), static_cast<uint32_t>(event.height)}; + _app.resizing = event.style & PUGL_VIEW_STYLE_RESIZING; return pugl::Status::success; } @@ -1640,7 +1641,6 @@ View::onEvent(const pugl::ExposeEvent&) pugl::Status View::onEvent(const pugl::LoopEnterEvent&) { - _app.resizing = true; startTimer(resizeTimerId, 1.0 / static_cast<double>(getHint(pugl::ViewHint::refreshRate))); @@ -1660,7 +1660,6 @@ View::onEvent(const pugl::LoopLeaveEvent&) // Trigger a swapchain recreation with the normal present mode _app.renderer.swapchain.extent = {}; - _app.resizing = false; return pugl::Status::success; } diff --git a/include/pugl/pugl.h b/include/pugl/pugl.h index 7b021b8..00dffba 100644 --- a/include/pugl/pugl.h +++ b/include/pugl/pugl.h @@ -132,6 +132,51 @@ typedef struct { */ /** + View style flags. + + Style flags reflect special modes and states supported by the window system. + Applications should ideally use a single main view, but can monitor or + manipulate style flags to better integrate with the window system. +*/ +typedef enum { + /// View is mapped to a real window and potentially visible + PUGL_VIEW_STYLE_MAPPED = 1U << 0U, + + /// View is modal, typically a dialog box of its transient parent + PUGL_VIEW_STYLE_MODAL = 1U << 1U, + + /// View should be above most others + PUGL_VIEW_STYLE_ABOVE = 1U << 2U, + + /// View should be below most others + PUGL_VIEW_STYLE_BELOW = 1U << 3U, + + /// View is minimized, shaded, or otherwise invisible + PUGL_VIEW_STYLE_HIDDEN = 1U << 4U, + + /// View is maximized to fill the screen vertically + PUGL_VIEW_STYLE_TALL = 1U << 5U, + + /// View is maximized to fill the screen horizontally + PUGL_VIEW_STYLE_WIDE = 1U << 6U, + + /// View is enlarged to fill the entire screen with no decorations + PUGL_VIEW_STYLE_FULLSCREEN = 1U << 7U, + + /// View is being resized + PUGL_VIEW_STYLE_RESIZING = 1U << 8U, + + /// View is ready for input or otherwise demanding attention + PUGL_VIEW_STYLE_DEMANDING = 1U << 9U, +} PuglViewStyleFlag; + +/// The maximum #PuglViewStyleFlag value +#define PUGL_MAX_VIEW_STYLE_FLAG PUGL_VIEW_STYLE_DEMANDING + +/// Bitwise OR of #PuglViewStyleFlag values +typedef uint32_t PuglViewStyleFlags; + +/** View realize event. This event is sent when a view is realized before it is first displayed, @@ -163,12 +208,13 @@ typedef PuglAnyEvent PuglUnrealizeEvent; otherwise configure the context, but not to draw anything. */ typedef struct { - PuglEventType type; ///< #PUGL_CONFIGURE - PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values - PuglCoord x; ///< Parent-relative X coordinate of view - PuglCoord y; ///< Parent-relative Y coordinate of view - PuglSpan width; ///< Width of view - PuglSpan height; ///< Height of view + PuglEventType type; ///< #PUGL_CONFIGURE + PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values + PuglCoord x; ///< Parent-relative X coordinate of view + PuglCoord y; ///< Parent-relative Y coordinate of view + PuglSpan width; ///< Width of view + PuglSpan height; ///< Height of view + PuglViewStyleFlags style; ///< Bitwise OR of #PuglViewStyleFlag flags } PuglConfigureEvent; /** @@ -863,10 +909,11 @@ typedef enum { PUGL_RESIZABLE, ///< True if view should be resizable PUGL_IGNORE_KEY_REPEAT, ///< True if key repeat events are ignored PUGL_REFRESH_RATE, ///< Refresh rate in Hz + PUGL_VIEW_TYPE, ///< View type (a #PuglViewType) } PuglViewHint; /// The number of #PuglViewHint values -#define PUGL_NUM_VIEW_HINTS ((unsigned)PUGL_REFRESH_RATE + 1U) +#define PUGL_NUM_VIEW_HINTS ((unsigned)PUGL_VIEW_TYPE + 1U) /// A special view hint value typedef enum { @@ -875,6 +922,13 @@ typedef enum { PUGL_TRUE = 1 ///< Explicitly true } PuglViewHintValue; +/// View type +typedef enum { + PUGL_VIEW_TYPE_NORMAL, ///< A normal top-level window + PUGL_VIEW_TYPE_UTILITY, ///< A utility window like a palette or toolbox + PUGL_VIEW_TYPE_DIALOG, ///< A dialog window +} PuglViewType; + /** A hint for configuring/constraining the size of a view. @@ -1198,6 +1252,30 @@ PUGL_API PuglStatus puglHide(PuglView* view); +/** + Set a view state, if supported by the system. + + This can be used to manipulate the window into various special states, but + note that not all states are supported on all systems. This function may + return failure or an error if the platform implementation doesn't + "understand" how to set the given style, but the return value here can't be + used to determine if the state has actually been set. Any changes to the + actual state of the view will arrive in later configure events. +*/ +PUGL_API +PuglStatus +puglSetViewStyle(PuglView* view, PuglViewStyleFlags flags); + +/** + Return true if the view currently has a state flag set. + + The result is determined based on the state announced in the last configure + event. +*/ +PUGL_API +PuglViewStyleFlags +puglGetViewStyle(const PuglView* view); + /// Return true iff the view is currently visible PUGL_API bool @@ -1399,17 +1477,6 @@ PuglStatus puglSetCursor(PuglView* view, PuglCursor cursor); /** - Request user attention. - - This hints to the system that the window or application requires attention - from the user. The exact effect depends on the platform, but is usually - something like a flashing task bar entry or bouncing application icon. -*/ -PUGL_API -PuglStatus -puglRequestAttention(PuglView* view); - -/** Activate a repeating timer event. This starts a timer which will send a #PuglTimerEvent to `view` every @@ -1943,6 +2010,21 @@ puglGetNativeWindow(PuglView* view) return puglGetNativeView(view); } +/** + Request user attention. + + This hints to the system that the window or application requires attention + from the user. The exact effect depends on the platform, but is usually + something like a flashing task bar entry or bouncing application icon. +*/ +static inline PUGL_DEPRECATED_BY("puglSetViewStyle") +PuglStatus +puglRequestAttention(PuglView* view) +{ + return puglSetViewStyle(view, + puglGetViewStyle(view) | PUGL_VIEW_STYLE_DEMANDING); +} + #endif // PUGL_DISABLE_DEPRECATED /** diff --git a/src/common.c b/src/common.c index 3c2929f..0249e0d 100644 --- a/src/common.c +++ b/src/common.c @@ -106,6 +106,7 @@ puglSetDefaultHints(PuglHints hints) hints[PUGL_RESIZABLE] = PUGL_FALSE; hints[PUGL_IGNORE_KEY_REPEAT] = PUGL_FALSE; hints[PUGL_REFRESH_RATE] = PUGL_DONT_CARE; + hints[PUGL_VIEW_TYPE] = PUGL_DONT_CARE; } PuglView* @@ -255,11 +256,18 @@ puglGetTransientParent(const PuglView* const view) bool puglGetVisible(const PuglView* view) { - return view->stage == PUGL_VIEW_STAGE_MAPPED; + return view->stage == PUGL_VIEW_STAGE_MAPPED && + !(view->lastConfigure.style & PUGL_VIEW_STYLE_HIDDEN); } void* -puglGetContext(PuglView* view) +puglGetContext(PuglView* const view) { return view->backend->getContext(view); } + +PuglViewStyleFlags +puglGetViewStyle(const PuglView* const view) +{ + return view->lastConfigure.style; +} @@ -173,6 +173,27 @@ updateViewRect(PuglView* view) } } +static PuglViewStyleFlags +getCurrentViewStyleFlags(PuglView* const view) +{ + const bool isResizing = view->resizing; + + if (!view->impl->window) { + return (isResizing ? PUGL_VIEW_STYLE_RESIZING : 0U); + } + + const NSWindowStyleMask styleMask = [view->impl->window styleMask]; + + const bool isFullScreen = styleMask & NSWindowStyleMaskFullScreen; + const bool isMiniaturized = [view->impl->window isMiniaturized]; + const bool isZoomed = [view->impl->window isZoomed]; + + return (isFullScreen ? PUGL_VIEW_STYLE_FULLSCREEN : 0U) | + (isMiniaturized ? PUGL_VIEW_STYLE_HIDDEN : 0U) | + (isZoomed ? (PUGL_VIEW_STYLE_TALL | PUGL_VIEW_STYLE_WIDE) : 0U) | + (isResizing ? PUGL_VIEW_STYLE_RESIZING : 0U); +} + @implementation PuglWindow { @public PuglView* puglview; @@ -201,6 +222,24 @@ updateViewRect(PuglView* view) [self setContentSize:sizePoints(view, view->frame.width, view->frame.height)]; } +- (PuglStatus)dispatchCurrentConfiguration +{ + const PuglConfigureEvent ev = { + PUGL_CONFIGURE, + 0, + puglview->frame.x, + puglview->frame.y, + puglview->frame.width, + puglview->frame.height, + getCurrentViewStyleFlags(puglview), + }; + + PuglEvent configureEvent; + configureEvent.configure = ev; + + return puglDispatchEvent(puglview, &configureEvent); +} + - (BOOL)canBecomeKeyWindow { return YES; @@ -216,24 +255,25 @@ updateViewRect(PuglView* view) [super setIsVisible:flag]; if (flag && puglview->stage < PUGL_VIEW_STAGE_MAPPED) { - const PuglConfigureEvent ev = { - PUGL_CONFIGURE, - 0, - puglview->frame.x, - puglview->frame.y, - puglview->frame.width, - puglview->frame.height, - }; - - PuglEvent configureEvent; - configureEvent.configure = ev; - puglDispatchEvent(puglview, &configureEvent); + [self dispatchCurrentConfiguration]; puglDispatchSimpleEvent(puglview, PUGL_MAP); } else if (!flag && puglview->stage == PUGL_VIEW_STAGE_MAPPED) { puglDispatchSimpleEvent(puglview, PUGL_UNMAP); } } +- (void)setIsZoomed:(BOOL)flag +{ + [super setIsZoomed:flag]; + + const bool wasZoomed = (puglview->lastConfigure.style & + (PUGL_VIEW_STYLE_TALL | PUGL_VIEW_STYLE_WIDE)); + + if (flag != wasZoomed) { + [self dispatchCurrentConfiguration]; + } +} + @end @implementation PuglWrapperView { @@ -263,6 +303,7 @@ updateViewRect(PuglView* view) puglview->frame.y, puglview->frame.width, puglview->frame.height, + getCurrentViewStyleFlags(puglview), }; PuglEvent configureEvent; @@ -821,6 +862,11 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type) puglDispatchSimpleEvent(puglview, PUGL_LOOP_ENTER); } +- (void)viewDidEndLiveResize +{ + puglDispatchSimpleEvent(puglview, PUGL_LOOP_LEAVE); +} + - (void)viewWillDraw { puglDispatchSimpleEvent(puglview, PUGL_UPDATE); @@ -837,16 +883,13 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type) puglDispatchEvent(puglview, &timerEvent); } -- (void)viewDidEndLiveResize -{ - puglDispatchSimpleEvent(puglview, PUGL_LOOP_LEAVE); -} - @end @interface PuglWindowDelegate : NSObject<NSWindowDelegate> - (instancetype)initWithPuglWindow:(PuglWindow*)window; +- (void)beginLiveResize:(NSNotification*)notification; +- (void)endLiveResize:(NSNotification*)notification; @end @@ -863,6 +906,22 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type) return self; } +- (void)beginLiveResize:(NSNotification*)notification +{ + (void)notification; + + window->puglview->resizing = true; + [window dispatchCurrentConfiguration]; +} + +- (void)endLiveResize:(NSNotification*)notification +{ + (void)notification; + + window->puglview->resizing = false; + [window dispatchCurrentConfiguration]; +} + - (BOOL)windowShouldClose:(id)sender { (void)sender; @@ -896,6 +955,56 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type) puglDispatchEvent(window->puglview, &ev); } +- (void)windowWillStartLiveResize:(NSNotification*)notification +{ + [self beginLiveResize:notification]; +} + +- (void)windowDidEndLiveResize:(NSNotification*)notification +{ + [self endLiveResize:notification]; +} + +- (void)windowWillMiniaturize:(NSNotification*)notification +{ + [self beginLiveResize:notification]; +} + +- (void)windowDidMiniaturize:(NSNotification*)notification +{ + [self endLiveResize:notification]; +} + +- (void)windowWillDeminiaturize:(NSNotification*)notification +{ + [self beginLiveResize:notification]; +} + +- (void)windowDidDeminiaturize:(NSNotification*)notification +{ + [self endLiveResize:notification]; +} + +- (void)windowWillEnterFullScreen:(NSNotification*)notification +{ + [self beginLiveResize:notification]; +} + +- (void)windowDidEnterFullScreen:(NSNotification*)notification +{ + [self endLiveResize:notification]; +} + +- (void)windowWillExitFullScreen:(NSNotification*)notification +{ + [self beginLiveResize:notification]; +} + +- (void)windowDidExitFullScreen:(NSNotification*)notification +{ + [self endLiveResize:notification]; +} + @end PuglWorldInternals* @@ -1278,11 +1387,69 @@ puglHasFocus(const PuglView* view) [[impl->wrapperView window] firstResponder] == impl->wrapperView); } +static bool +styleIsMaximized(const PuglViewStyleFlags flags) +{ + return (flags & PUGL_VIEW_STYLE_TALL) && (flags & PUGL_VIEW_STYLE_WIDE); +} + PuglStatus -puglRequestAttention(PuglView* view) +puglSetViewStyle(PuglView* const view, const PuglViewStyleFlags flags) { - if (![view->impl->window isKeyWindow]) { - [view->world->impl->app requestUserAttention:NSInformationalRequest]; + NSWindow* const window = view->impl->window; + const PuglViewStyleFlags oldFlags = puglGetViewStyle(view); + if (!window) { + return PUGL_FAILURE; + } + + for (uint32_t mask = 1U; mask <= PUGL_MAX_VIEW_STYLE_FLAG; mask <<= 1U) { + const bool oldValue = oldFlags & mask; + const bool newValue = flags & mask; + if (oldValue == newValue) { + continue; + } + + switch (mask) { + case PUGL_VIEW_STYLE_MODAL: + case PUGL_VIEW_STYLE_TALL: + case PUGL_VIEW_STYLE_WIDE: + break; + + case PUGL_VIEW_STYLE_HIDDEN: + if (newValue) { + [window miniaturize:window]; + } else { + [window deminiaturize:window]; + } + break; + + case PUGL_VIEW_STYLE_FULLSCREEN: + if (newValue != ([window styleMask] & NSFullScreenWindowMask)) { + [window toggleFullScreen:nil]; + } + break; + + case PUGL_VIEW_STYLE_ABOVE: + case PUGL_VIEW_STYLE_BELOW: + break; + + case PUGL_VIEW_STYLE_DEMANDING: + if (![window isKeyWindow]) { + [view->world->impl->app requestUserAttention:NSInformationalRequest]; + } + break; + + case PUGL_VIEW_STYLE_RESIZING: + case PUGL_VIEW_STYLE_MAPPED: + break; + } + } + + // Handle maximization (MacOS doesn't have tall/wide styles) + const bool oldMaximized = styleIsMaximized(oldFlags); + const bool newMaximized = styleIsMaximized(flags); + if (oldMaximized != newMaximized) { + [window zoom:window]; } return PUGL_SUCCESS; diff --git a/src/types.h b/src/types.h index 072d3fe..2f6721b 100644 --- a/src/types.h +++ b/src/types.h @@ -56,6 +56,7 @@ struct PuglViewImpl { PuglHints hints; PuglViewSize sizeHints[PUGL_NUM_SIZE_HINTS]; PuglViewStage stage; + bool resizing; }; /// Cross-platform world definition @@ -110,13 +110,24 @@ puglRegisterWindowClass(const char* name) static unsigned puglWinGetWindowFlags(const PuglView* const view) { + const unsigned commonFlags = WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + if (view->parent) { + return commonFlags | WS_CHILD; + } + + if (view->impl->fullscreen) { + return commonFlags | WS_POPUPWINDOW; + } + + const unsigned typeFlags = + (view->hints[PUGL_VIEW_TYPE] == PUGL_VIEW_TYPE_DIALOG) + ? (WS_DLGFRAME | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU) + : (WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX); + const bool resizable = !!view->hints[PUGL_RESIZABLE]; const unsigned sizeFlags = resizable ? (WS_SIZEBOX | WS_MAXIMIZEBOX) : 0U; - return (WS_CLIPCHILDREN | WS_CLIPSIBLINGS | - (view->parent - ? WS_CHILD - : (WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX | sizeFlags))); + return commonFlags | typeFlags | sizeFlags; } static unsigned @@ -564,6 +575,13 @@ handleConfigure(PuglView* view, PuglEvent* event) event->configure.width = (PuglSpan)width; event->configure.height = (PuglSpan)height; + event->configure.style = + ((view->resizing ? PUGL_VIEW_STYLE_RESIZING : 0U) | + (view->impl->fullscreen ? PUGL_VIEW_STYLE_FULLSCREEN : 0U) | + (view->impl->minimized ? PUGL_VIEW_STYLE_HIDDEN : 0U) | + (view->impl->maximized ? (PUGL_VIEW_STYLE_TALL | PUGL_VIEW_STYLE_WIDE) + : 0U)); + return rect; } @@ -635,11 +653,12 @@ constrainAspect(const PuglView* const view, static LRESULT handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) { - PuglEvent event = {{PUGL_NOTHING, 0}}; - RECT rect = {0, 0, 0, 0}; - POINT pt = {0, 0}; - MINMAXINFO* mmi = NULL; - void* dummy_ptr = NULL; + PuglEvent event = {{PUGL_NOTHING, 0}}; + RECT rect = {0, 0, 0, 0}; + POINT pt = {0, 0}; + MINMAXINFO* mmi = NULL; + void* dummy_ptr = NULL; + WINDOWPLACEMENT placement = {sizeof(WINDOWPLACEMENT), 0, 0, pt, pt}; if (InSendMessageEx(dummy_ptr)) { event.any.flags |= PUGL_IS_SEND_EVENT; @@ -667,20 +686,19 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) event.any.type = wParam ? PUGL_MAP : PUGL_UNMAP; break; - case WM_SIZE: - 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_DISPLAYCHANGE: view->impl->scaleFactor = puglWinGetViewScaleFactor(view); break; case WM_WINDOWPOSCHANGED: + view->impl->minimized = false; + view->impl->maximized = false; + if (GetWindowPlacement(view->impl->hwnd, &placement)) { + if (placement.showCmd == SW_SHOWMINIMIZED) { + view->impl->minimized = true; + } else if (placement.showCmd == SW_SHOWMAXIMIZED) { + view->impl->maximized = true; + } + } handleConfigure(view, &event); break; case WM_SIZING: @@ -690,6 +708,10 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) } break; case WM_ENTERSIZEMOVE: + view->resizing = true; + puglDispatchSimpleEvent(view, PUGL_LOOP_ENTER); + handleConfigure(view, &event); + break; case WM_ENTERMENULOOP: puglDispatchSimpleEvent(view, PUGL_LOOP_ENTER); break; @@ -701,6 +723,10 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) } break; case WM_EXITSIZEMOVE: + view->resizing = false; + puglDispatchSimpleEvent(view, PUGL_LOOP_LEAVE); + handleConfigure(view, &event); + break; case WM_EXITMENULOOP: puglDispatchSimpleEvent(view, PUGL_LOOP_LEAVE); break; @@ -855,13 +881,73 @@ puglHasFocus(const PuglView* view) return GetFocus() == view->impl->hwnd; } +static bool +styleIsMaximized(const PuglViewStyleFlags flags) +{ + return (flags & PUGL_VIEW_STYLE_TALL) && (flags & PUGL_VIEW_STYLE_WIDE); +} + PuglStatus -puglRequestAttention(PuglView* view) +puglSetViewStyle(PuglView* const view, const PuglViewStyleFlags flags) { - FLASHWINFO info = { - sizeof(FLASHWINFO), view->impl->hwnd, FLASHW_ALL | FLASHW_TIMERNOFG, 1, 0}; + PuglInternals* const impl = view->impl; + const PuglViewStyleFlags oldFlags = puglGetViewStyle(view); + + for (uint32_t mask = 1U; mask <= PUGL_MAX_VIEW_STYLE_FLAG; mask <<= 1U) { + const bool oldValue = oldFlags & mask; + const bool newValue = flags & mask; + if (oldValue == newValue) { + continue; + } + + switch (mask) { + case PUGL_VIEW_STYLE_MODAL: + case PUGL_VIEW_STYLE_TALL: + case PUGL_VIEW_STYLE_WIDE: + break; + + case PUGL_VIEW_STYLE_HIDDEN: + ShowWindow(impl->hwnd, newValue ? SW_SHOWMINIMIZED : SW_RESTORE); + break; + + case PUGL_VIEW_STYLE_FULLSCREEN: + impl->fullscreen = newValue; + SetWindowLong(impl->hwnd, GWL_STYLE, (LONG)puglWinGetWindowFlags(view)); + SetWindowLong( + impl->hwnd, GWL_EXSTYLE, (LONG)puglWinGetWindowExFlags(view)); + if (newValue) { + GetWindowPlacement(impl->hwnd, &impl->oldPlacement); + ShowWindow(impl->hwnd, SW_SHOWMAXIMIZED); + } else { + impl->oldPlacement.showCmd = SW_RESTORE; + SetWindowPlacement(impl->hwnd, &impl->oldPlacement); + } + break; - FlashWindowEx(&info); + case PUGL_VIEW_STYLE_ABOVE: + case PUGL_VIEW_STYLE_BELOW: + break; + + case PUGL_VIEW_STYLE_DEMANDING: { + FLASHWINFO info = { + sizeof(FLASHWINFO), impl->hwnd, FLASHW_ALL | FLASHW_TIMERNOFG, 1, 0}; + + FlashWindowEx(&info); + break; + } + + case PUGL_VIEW_STYLE_RESIZING: + break; + } + } + + // Handle maximization (Windows doesn't have tall/wide styles) + const bool oldMaximized = styleIsMaximized(oldFlags); + const bool newMaximized = styleIsMaximized(flags); + if (oldMaximized != newMaximized) { + ShowWindow(impl->hwnd, newMaximized ? SW_SHOWMAXIMIZED : SW_RESTORE); + puglPostRedisplay(view); + } return PUGL_SUCCESS; } @@ -19,16 +19,20 @@ struct PuglWorldInternalsImpl { }; struct PuglInternalsImpl { - PuglWinPFD pfd; - int pfId; - HWND hwnd; - HCURSOR cursor; - HDC hdc; - PuglBlob clipboard; - PuglSurface* surface; - double scaleFactor; - bool flashing; - bool mouseTracked; + PuglWinPFD pfd; + int pfId; + HWND hwnd; + HCURSOR cursor; + HDC hdc; + WINDOWPLACEMENT oldPlacement; + PuglBlob clipboard; + PuglSurface* surface; + double scaleFactor; + bool flashing; + bool mouseTracked; + bool minimized; + bool maximized; + bool fullscreen; }; PUGL_API @@ -155,7 +155,8 @@ puglInitWorldInternals(const PuglWorldType type, const PuglWorldFlags flags) impl->display = display; impl->scaleFactor = puglX11GetDisplayScaleFactor(display); - // Intern the various atoms we will need + // Intern the various atoms we'll need + impl->atoms.CLIPBOARD = XInternAtom(display, "CLIPBOARD", 0); impl->atoms.UTF8_STRING = XInternAtom(display, "UTF8_STRING", 0); impl->atoms.WM_PROTOCOLS = XInternAtom(display, "WM_PROTOCOLS", 0); @@ -164,10 +165,31 @@ puglInitWorldInternals(const PuglWorldType type, const PuglWorldFlags flags) impl->atoms.NET_CLOSE_WINDOW = XInternAtom(display, "_NET_CLOSE_WINDOW", 0); impl->atoms.NET_WM_NAME = XInternAtom(display, "_NET_WM_NAME", 0); impl->atoms.NET_WM_STATE = XInternAtom(display, "_NET_WM_STATE", 0); + + impl->atoms.NET_WM_STATE_ABOVE = + XInternAtom(display, "_NET_WM_STATE_ABOVE", 0); + impl->atoms.NET_WM_STATE_BELOW = + XInternAtom(display, "_NET_WM_STATE_BELOW", 0); impl->atoms.NET_WM_STATE_DEMANDS_ATTENTION = XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", 0); + impl->atoms.NET_WM_STATE_FULLSCREEN = + XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", 0); impl->atoms.NET_WM_STATE_HIDDEN = XInternAtom(display, "_NET_WM_STATE_HIDDEN", 0); + impl->atoms.NET_WM_STATE_MAXIMIZED_HORZ = + XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_HORZ", 0); + impl->atoms.NET_WM_STATE_MAXIMIZED_VERT = + XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_VERT", 0); + impl->atoms.NET_WM_STATE_MODAL = + XInternAtom(display, "_NET_WM_STATE_MODAL", 0); + impl->atoms.NET_WM_WINDOW_TYPE = + XInternAtom(display, "_NET_WM_WINDOW_TYPE", 0); + impl->atoms.NET_WM_WINDOW_TYPE_DIALOG = + XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", 0); + impl->atoms.NET_WM_WINDOW_TYPE_NORMAL = + XInternAtom(display, "_NET_WM_WINDOW_TYPE_NORMAL", 0); + impl->atoms.NET_WM_WINDOW_TYPE_UTILITY = + XInternAtom(display, "_NET_WM_WINDOW_TYPE_UTILITY", 0); impl->atoms.TARGETS = XInternAtom(display, "TARGETS", 0); impl->atoms.text_uri_list = XInternAtom(display, "text/uri-list", 0); @@ -207,6 +229,96 @@ puglInitViewInternals(PuglWorld* const world) return impl; } +static Atom +styleFlagToAtom(PuglWorld* const world, const PuglViewStyleFlag flag) +{ + const PuglX11Atoms* const atoms = &world->impl->atoms; + + switch (flag) { + case PUGL_VIEW_STYLE_MODAL: + return atoms->NET_WM_STATE_MODAL; + case PUGL_VIEW_STYLE_TALL: + return atoms->NET_WM_STATE_MAXIMIZED_VERT; + case PUGL_VIEW_STYLE_WIDE: + return atoms->NET_WM_STATE_MAXIMIZED_HORZ; + case PUGL_VIEW_STYLE_HIDDEN: + return atoms->NET_WM_STATE_HIDDEN; + case PUGL_VIEW_STYLE_FULLSCREEN: + return atoms->NET_WM_STATE_FULLSCREEN; + case PUGL_VIEW_STYLE_ABOVE: + return atoms->NET_WM_STATE_ABOVE; + case PUGL_VIEW_STYLE_BELOW: + return atoms->NET_WM_STATE_BELOW; + case PUGL_VIEW_STYLE_DEMANDING: + return atoms->NET_WM_STATE_DEMANDS_ATTENTION; + case PUGL_VIEW_STYLE_RESIZING: + break; + } + + return 0; +} + +static Atom +viewTypeToAtom(PuglWorld* const world, const PuglViewType type) +{ + const PuglX11Atoms* const atoms = &world->impl->atoms; + + switch (type) { + case PUGL_VIEW_TYPE_NORMAL: + return atoms->NET_WM_WINDOW_TYPE_NORMAL; + case PUGL_VIEW_TYPE_UTILITY: + return atoms->NET_WM_WINDOW_TYPE_UTILITY; + case PUGL_VIEW_TYPE_DIALOG: + return atoms->NET_WM_WINDOW_TYPE_DIALOG; + } + + return 0; +} + +PuglStatus +puglSetViewStyle(PuglView* const view, const PuglViewStyleFlags flags) +{ + PuglWorld* const world = view->world; + PuglInternals* const impl = view->impl; + Display* const display = view->world->impl->display; + const PuglX11Atoms* const atoms = &view->world->impl->atoms; + const PuglViewStyleFlags oldFlags = puglGetViewStyle(view); + + for (uint32_t mask = 1U; mask <= PUGL_MAX_VIEW_STYLE_FLAG; mask <<= 1U) { + const Atom stateAtom = styleFlagToAtom(world, (PuglViewStyleFlag)mask); + const bool oldValue = oldFlags & mask; + const bool newValue = flags & mask; + if (!stateAtom || oldValue == newValue) { + continue; + } + + if (stateAtom == atoms->NET_WM_STATE_HIDDEN) { + // KDE annoyingly doesn't support clients setting the hidden hint + XIconifyWindow(display, impl->win, impl->screen); + } else { + XEvent event = {ClientMessage}; + event.xclient.window = impl->win; + event.xclient.format = 32; + event.xclient.message_type = atoms->NET_WM_STATE; + event.xclient.data.l[0] = newValue ? WM_STATE_ADD : WM_STATE_REMOVE; + event.xclient.data.l[1] = (long)stateAtom; + event.xclient.data.l[2] = 0; + event.xclient.data.l[3] = 1; + event.xclient.data.l[4] = 0; + + if (!XSendEvent(display, + RootWindow(display, impl->screen), + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &event)) { + return PUGL_UNKNOWN_ERROR; + } + } + } + + return PUGL_SUCCESS; +} + static PuglStatus pollX11Socket(PuglWorld* const world, const double timeout) { @@ -435,6 +547,20 @@ puglRealize(PuglView* const view) return st; } + // Set window type + if (view->hints[PUGL_VIEW_TYPE] != PUGL_DONT_CARE) { + const PuglViewType viewType = (PuglViewType)view->hints[PUGL_VIEW_TYPE]; + const Atom windowType = viewTypeToAtom(world, viewType); + XChangeProperty(display, + impl->win, + atoms->NET_WM_WINDOW_TYPE, + XA_ATOM, + 32, + PropModeReplace, + (const unsigned char*)&windowType, + 1); + } + #ifdef HAVE_XRANDR int ignored = 0; if (XRRQueryExtension(display, &ignored, &ignored)) { @@ -787,6 +913,40 @@ translateClientMessage(PuglView* const view, XClientMessageEvent message) return event; } +static PuglViewStyleFlags +getCurrentViewStyleFlags(PuglView* const view) +{ + const PuglX11Atoms* const atoms = &view->world->impl->atoms; + + unsigned long numHints = 0; + Atom* hints = NULL; + PuglViewStyleFlags state = 0U; + if (!getAtomProperty( + view, view->impl->win, atoms->NET_WM_STATE, &numHints, &hints)) { + for (unsigned long i = 0; i < numHints; ++i) { + if (hints[i] == atoms->NET_WM_STATE_MAXIMIZED_VERT) { + state |= PUGL_VIEW_STYLE_TALL; + } else if (hints[i] == atoms->NET_WM_STATE_MAXIMIZED_HORZ) { + state |= PUGL_VIEW_STYLE_WIDE; + } else if (hints[i] == atoms->NET_WM_STATE_HIDDEN) { + state |= PUGL_VIEW_STYLE_HIDDEN; + } else if (hints[i] == atoms->NET_WM_STATE_FULLSCREEN) { + state |= PUGL_VIEW_STYLE_FULLSCREEN; + } else if (hints[i] == atoms->NET_WM_STATE_MODAL) { + state |= PUGL_VIEW_STYLE_MODAL; + } else if (hints[i] == atoms->NET_WM_STATE_ABOVE) { + state |= PUGL_VIEW_STYLE_ABOVE; + } else if (hints[i] == atoms->NET_WM_STATE_BELOW) { + state |= PUGL_VIEW_STYLE_BELOW; + } else if (hints[i] == atoms->NET_WM_STATE_DEMANDS_ATTENTION) { + state |= PUGL_VIEW_STYLE_DEMANDING; + } + } + } + + return state; +} + static PuglEvent getCurrentConfiguration(PuglView* const view) { @@ -794,42 +954,47 @@ getCurrentConfiguration(PuglView* const view) XWindowAttributes attrs; XGetWindowAttributes(view->world->impl->display, view->impl->win, &attrs); - // Build a configure event with the current position and size + // Build a configure event based on the current window configuration PuglEvent configureEvent = {{PUGL_CONFIGURE, 0}}; configureEvent.configure.x = (PuglCoord)attrs.x; configureEvent.configure.y = (PuglCoord)attrs.y; configureEvent.configure.width = (PuglSpan)attrs.width; configureEvent.configure.height = (PuglSpan)attrs.height; + configureEvent.configure.style = getCurrentViewStyleFlags(view); return configureEvent; } static PuglEvent +makeConfigureEvent(PuglView* const view) +{ + PuglEvent event = view->impl->pendingConfigure; + + if (event.type != PUGL_CONFIGURE) { + event = getCurrentConfiguration(view); + } + + return event; +} + +static PuglEvent translatePropertyNotify(PuglView* const view, XPropertyEvent message) { - const PuglX11Atoms* const atoms = &view->world->impl->atoms; + const PuglInternals* const impl = view->impl; + const PuglX11Atoms* const atoms = &view->world->impl->atoms; + PuglEvent event = {{PUGL_NOTHING, 0}}; - PuglEvent event = {{PUGL_NOTHING, 0}}; if (message.atom == atoms->NET_WM_STATE) { + // Get all the current states set in the window hints unsigned long numHints = 0; Atom* hints = NULL; - if (getAtomProperty( - view, view->impl->win, message.atom, &numHints, &hints)) { + if (getAtomProperty(view, impl->win, message.atom, &numHints, &hints)) { return event; } - bool hidden = false; - for (unsigned long i = 0; i < numHints; ++i) { - if (hints[i] == atoms->NET_WM_STATE_HIDDEN) { - hidden = true; - } - } - - if (hidden && view->stage == PUGL_VIEW_STAGE_MAPPED) { - event.type = PUGL_UNMAP; - } else if (!hidden && view->stage == PUGL_VIEW_STAGE_CONFIGURED) { - event.type = PUGL_MAP; - } + // Make a configure event based on the current configuration to update + event = makeConfigureEvent(view); + event.configure.style = getCurrentViewStyleFlags(view); XFree(hints); } @@ -851,9 +1016,7 @@ translateEvent(PuglView* const view, XEvent xevent) event = translatePropertyNotify(view, xevent.xproperty); break; case VisibilityNotify: - event.type = (xevent.xvisibility.state == VisibilityFullyObscured) - ? PUGL_UNMAP - : PUGL_MAP; + event = makeConfigureEvent(view); break; case MapNotify: event.type = PUGL_MAP; @@ -862,7 +1025,7 @@ translateEvent(PuglView* const view, XEvent xevent) event.type = PUGL_UNMAP; break; case ConfigureNotify: - event.type = PUGL_CONFIGURE; + event = makeConfigureEvent(view); event.configure.x = (PuglCoord)xevent.xconfigure.x; event.configure.y = (PuglCoord)xevent.xconfigure.y; event.configure.width = (PuglSpan)xevent.xconfigure.width; @@ -1014,35 +1177,6 @@ puglHasFocus(const PuglView* const view) } PuglStatus -puglRequestAttention(PuglView* const view) -{ - PuglInternals* const impl = view->impl; - Display* const display = view->world->impl->display; - const PuglX11Atoms* const atoms = &view->world->impl->atoms; - XEvent event = PUGL_INIT_STRUCT; - - event.type = ClientMessage; - event.xclient.window = impl->win; - event.xclient.format = 32; - event.xclient.message_type = atoms->NET_WM_STATE; - event.xclient.data.l[0] = WM_STATE_ADD; - event.xclient.data.l[1] = (long)atoms->NET_WM_STATE_DEMANDS_ATTENTION; - event.xclient.data.l[2] = 0; - event.xclient.data.l[3] = 1; - event.xclient.data.l[4] = 0; - - const Window root = RootWindow(display, impl->screen); - - return XSendEvent(display, - root, - False, - SubstructureNotifyMask | SubstructureRedirectMask, - &event) - ? PUGL_SUCCESS - : PUGL_UNKNOWN_ERROR; -} - -PuglStatus puglStartTimer(PuglView* const view, const uintptr_t id, const double timeout) { #ifdef HAVE_XSYNC @@ -27,8 +27,18 @@ typedef struct { Atom NET_CLOSE_WINDOW; Atom NET_WM_NAME; Atom NET_WM_STATE; + Atom NET_WM_STATE_ABOVE; + Atom NET_WM_STATE_BELOW; Atom NET_WM_STATE_DEMANDS_ATTENTION; + Atom NET_WM_STATE_FULLSCREEN; Atom NET_WM_STATE_HIDDEN; + Atom NET_WM_STATE_MAXIMIZED_HORZ; + Atom NET_WM_STATE_MAXIMIZED_VERT; + Atom NET_WM_STATE_MODAL; + Atom NET_WM_WINDOW_TYPE; + Atom NET_WM_WINDOW_TYPE_DIALOG; + Atom NET_WM_WINDOW_TYPE_NORMAL; + Atom NET_WM_WINDOW_TYPE_UTILITY; Atom TARGETS; Atom text_uri_list; } PuglX11Atoms; diff --git a/test/test_utils.h b/test/test_utils.h index fd2c15b..5655e53 100644 --- a/test/test_utils.h +++ b/test/test_utils.h @@ -89,6 +89,33 @@ scrollDirectionString(const PuglScrollDirection direction) return "unknown"; } +static inline const char* +viewStyleFlagString(const PuglViewStyleFlag state) +{ + switch (state) { + case PUGL_VIEW_STYLE_MODAL: + return "modal"; + case PUGL_VIEW_STYLE_TALL: + return "tall"; + case PUGL_VIEW_STYLE_WIDE: + return "wide"; + case PUGL_VIEW_STYLE_HIDDEN: + return "hidden"; + case PUGL_VIEW_STYLE_FULLSCREEN: + return "fullscreen"; + case PUGL_VIEW_STYLE_ABOVE: + return "above"; + case PUGL_VIEW_STYLE_BELOW: + return "below"; + case PUGL_VIEW_STYLE_DEMANDING: + return "demanding"; + case PUGL_VIEW_STYLE_RESIZING: + return "resizing"; + } + + return "unknown"; +} + static inline int printEvent(const PuglEvent* event, const char* prefix, const bool verbose) { @@ -183,12 +210,20 @@ printEvent(const PuglEvent* event, const char* prefix, const bool verbose) case PUGL_UPDATE: return fprintf(stderr, "%sUpdate\n", prefix); case PUGL_CONFIGURE: - return PRINT("%sConfigure " PIFMT " " PUFMT "\n", - prefix, - event->configure.x, - event->configure.y, - event->configure.width, - event->configure.height); + PRINT("%sConfigure " PIFMT " " PUFMT " (", + prefix, + event->configure.x, + event->configure.y, + event->configure.width, + event->configure.height); + for (PuglViewStyleFlags mask = 1U; mask <= PUGL_MAX_VIEW_STYLE_FLAG; + mask <<= 1U) { + if (event->configure.style & mask) { + PRINT(" %s", viewStyleFlagString((PuglViewStyleFlag)mask)); + } + } + PRINT("%s\n", " )"); + return 0; case PUGL_EXPOSE: return PRINT("%sExpose " PIFMT " " PUFMT "\n", prefix, @@ -254,6 +289,8 @@ puglViewHintString(const PuglViewHint hint) return "Ignore key repeat"; case PUGL_REFRESH_RATE: return "Refresh rate"; + case PUGL_VIEW_TYPE: + return "View type"; } return "Unknown"; |