aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2023-01-07 19:27:14 -0500
committerDavid Robillard <d@drobilla.net>2023-01-07 20:27:35 -0500
commit4ad8621ac1d94c8e9cf88f83c46a3a70cd91212b (patch)
tree63307cb14d43c7391b34f94ca0e532d8e9e01a09
parent677e13dcbb5b64ce85093b9ea5c14025964e35b9 (diff)
downloadpugl-4ad8621ac1d94c8e9cf88f83c46a3a70cd91212b.tar.gz
pugl-4ad8621ac1d94c8e9cf88f83c46a3a70cd91212b.tar.bz2
pugl-4ad8621ac1d94c8e9cf88f83c46a3a70cd91212b.zip
Add support for special view types and styles
-rw-r--r--bindings/cpp/include/pugl/pugl.hpp22
-rw-r--r--examples/meson.build4
-rw-r--r--examples/pugl_embed_demo.c3
-rw-r--r--examples/pugl_management_demo.app/MacOS/meson.build11
-rw-r--r--examples/pugl_management_demo.app/meson.build11
-rw-r--r--examples/pugl_management_demo.c262
-rw-r--r--examples/pugl_vulkan_cpp_demo.cpp3
-rw-r--r--include/pugl/pugl.h118
-rw-r--r--src/common.c12
-rw-r--r--src/mac.m207
-rw-r--r--src/types.h1
-rw-r--r--src/win.c132
-rw-r--r--src/win.h24
-rw-r--r--src/x11.c236
-rw-r--r--src/x11.h10
-rw-r--r--test/test_utils.h49
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;
+}
diff --git a/src/mac.m b/src/mac.m
index 00b886c..72f5f9c 100644
--- a/src/mac.m
+++ b/src/mac.m
@@ -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
diff --git a/src/win.c b/src/win.c
index 79d38c2..b410607 100644
--- a/src/win.c
+++ b/src/win.c
@@ -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;
}
diff --git a/src/win.h b/src/win.h
index 4a89e11..37ffa72 100644
--- a/src/win.h
+++ b/src/win.h
@@ -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
diff --git a/src/x11.c b/src/x11.c
index 30a15be..372c167 100644
--- a/src/x11.c
+++ b/src/x11.c
@@ -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
diff --git a/src/x11.h b/src/x11.h
index de2e0d2..a048495 100644
--- a/src/x11.h
+++ b/src/x11.h
@@ -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";