diff options
-rw-r--r-- | bindings/cxx/include/pugl/pugl.hpp | 170 | ||||
-rw-r--r-- | bindings/cxx/include/pugl/pugl.ipp | 176 | ||||
-rw-r--r-- | bindings/cxx/include/pugl/vulkan.hpp | 2 | ||||
-rw-r--r-- | examples/pugl_cxx_demo.cpp | 33 | ||||
-rw-r--r-- | examples/pugl_vulkan_cxx_demo.cpp | 43 | ||||
-rw-r--r-- | test/test_build.cpp | 1 |
6 files changed, 135 insertions, 290 deletions
diff --git a/bindings/cxx/include/pugl/pugl.hpp b/bindings/cxx/include/pugl/pugl.hpp index 54c0648..dde29c8 100644 --- a/bindings/cxx/include/pugl/pugl.hpp +++ b/bindings/cxx/include/pugl/pugl.hpp @@ -353,8 +353,14 @@ enum class Cursor { static_assert(Cursor(PUGL_CURSOR_UP_DOWN) == Cursor::upDown, ""); -/// @copydoc PuglView -class View : protected detail::Wrapper<PuglView, puglFreeView> +/** + A drawable region that receives events. + + This is the generic base class for all views. It can be used directly by + manually setting an event handling function, but typical applications should + use pugl::View which handles dispatching events. +*/ +class ViewBase : protected detail::Wrapper<PuglView, puglFreeView> { public: /** @@ -363,26 +369,13 @@ public: @{ */ - explicit View(World& world) + explicit ViewBase(World& world) : Wrapper{puglNewView(world.cobj())} , _world(world) { - if (cobj()) { - puglSetHandle(cobj(), this); - puglSetEventFunc(cobj(), dispatchEvent); - } - PUGL_CHECK_CONSTRUCTION(cobj(), "Failed to create pugl::View"); } - virtual ~View() noexcept = default; - - View(const View&) = delete; - View& operator=(const View&) = delete; - - View(View&&) = delete; - View&& operator=(View&&) = delete; - const World& world() const noexcept { return _world; } World& world() noexcept { return _world; } @@ -559,6 +552,38 @@ public: return static_cast<Status>(puglStopTimer(cobj(), id)); } + PuglView* cobj() noexcept { return Wrapper::cobj(); } + const PuglView* cobj() const noexcept { return Wrapper::cobj(); } + +private: + World& _world; +}; + +/** + A view with event handlers. + + To implement a view, applications can inherit from this class, which handles + type-safe dispatching of events to methods. This class uses the CRTP + pattern, so the name of the derived class needs to be passed as a template + paramter, for example: + + @code + class MyView : public pugl::View<MyView> + @endcode +*/ +template<class Derived> +class View : public ViewBase +{ +public: + explicit View(World& world) + : ViewBase{world} + { + if (cobj()) { + puglSetHandle(cobj(), this); + puglSetEventFunc(cobj(), eventFunc); + } + } + /** @} @name Event Handlers @@ -566,40 +591,45 @@ public: @{ */ - virtual Status onCreate(const CreateEvent&) PUGL_CONST_FUNC; - virtual Status onDestroy(const DestroyEvent&) PUGL_CONST_FUNC; - virtual Status onConfigure(const ConfigureEvent&) PUGL_CONST_FUNC; - virtual Status onMap(const MapEvent&) PUGL_CONST_FUNC; - virtual Status onUnmap(const UnmapEvent&) PUGL_CONST_FUNC; - virtual Status onUpdate(const UpdateEvent&) PUGL_CONST_FUNC; - virtual Status onExpose(const ExposeEvent&) PUGL_CONST_FUNC; - virtual Status onClose(const CloseEvent&) PUGL_CONST_FUNC; - virtual Status onFocusIn(const FocusInEvent&) PUGL_CONST_FUNC; - virtual Status onFocusOut(const FocusOutEvent&) PUGL_CONST_FUNC; - virtual Status onKeyPress(const KeyPressEvent&) PUGL_CONST_FUNC; - virtual Status onKeyRelease(const KeyReleaseEvent&) PUGL_CONST_FUNC; - virtual Status onText(const TextEvent&) PUGL_CONST_FUNC; - virtual Status onPointerIn(const PointerInEvent&) PUGL_CONST_FUNC; - virtual Status onPointerOut(const PointerOutEvent&) PUGL_CONST_FUNC; - virtual Status onButtonPress(const ButtonPressEvent&) PUGL_CONST_FUNC; - virtual Status onButtonRelease(const ButtonReleaseEvent&) PUGL_CONST_FUNC; - virtual Status onMotion(const MotionEvent&) PUGL_CONST_FUNC; - virtual Status onScroll(const ScrollEvent&) PUGL_CONST_FUNC; - virtual Status onClient(const ClientEvent&) PUGL_CONST_FUNC; - virtual Status onTimer(const TimerEvent&) PUGL_CONST_FUNC; - virtual Status onLoopEnter(const LoopEnterEvent&) PUGL_CONST_FUNC; - virtual Status onLoopLeave(const LoopLeaveEvent&) PUGL_CONST_FUNC; + Status onEvent(const CreateEvent&) noexcept { return Status::success; } + Status onEvent(const DestroyEvent&) noexcept { return Status::success; } + Status onEvent(const ConfigureEvent&) noexcept { return Status::success; } + Status onEvent(const MapEvent&) noexcept { return Status::success; } + Status onEvent(const UnmapEvent&) noexcept { return Status::success; } + Status onEvent(const UpdateEvent&) noexcept { return Status::success; } + Status onEvent(const ExposeEvent&) noexcept { return Status::success; } + Status onEvent(const CloseEvent&) noexcept { return Status::success; } + Status onEvent(const FocusInEvent&) noexcept { return Status::success; } + Status onEvent(const FocusOutEvent&) noexcept { return Status::success; } + Status onEvent(const KeyPressEvent&) noexcept { return Status::success; } + Status onEvent(const KeyReleaseEvent&) noexcept { return Status::success; } + Status onEvent(const TextEvent&) noexcept { return Status::success; } + Status onEvent(const PointerInEvent&) noexcept { return Status::success; } + Status onEvent(const PointerOutEvent&) noexcept { return Status::success; } + Status onEvent(const ButtonPressEvent&) noexcept { return Status::success; } + Status onEvent(const ButtonReleaseEvent&) noexcept + { + return Status::success; + } + Status onEvent(const MotionEvent&) noexcept { return Status::success; } + Status onEvent(const ScrollEvent&) noexcept { return Status::success; } + Status onEvent(const ClientEvent&) noexcept { return Status::success; } + Status onEvent(const TimerEvent&) noexcept { return Status::success; } + Status onEvent(const LoopEnterEvent&) noexcept { return Status::success; } + Status onEvent(const LoopLeaveEvent&) noexcept { return Status::success; } /** @} */ - PuglView* cobj() noexcept { return Wrapper::cobj(); } - const PuglView* cobj() const noexcept { return Wrapper::cobj(); } - private: - static PuglStatus - dispatchEvent(PuglView* view, const PuglEvent* event) noexcept + Derived& self() noexcept { return *static_cast<Derived*>(this); } + const Derived& self() const noexcept + { + return *static_cast<Derived*>(this); + } + + static PuglStatus eventFunc(PuglView* view, const PuglEvent* event) noexcept { View* self = static_cast<View*>(puglGetHandle(view)); @@ -621,79 +651,77 @@ private: return PUGL_SUCCESS; case PUGL_CREATE: return static_cast<PuglStatus>( - onCreate(static_cast<const CreateEvent&>(event->any))); + self().onEvent(static_cast<const CreateEvent&>(event->any))); case PUGL_DESTROY: return static_cast<PuglStatus>( - onDestroy(static_cast<const DestroyEvent&>(event->any))); + self().onEvent(static_cast<const DestroyEvent&>(event->any))); case PUGL_CONFIGURE: - return static_cast<PuglStatus>(onConfigure( + return static_cast<PuglStatus>(self().onEvent( static_cast<const ConfigureEvent&>(event->configure))); case PUGL_MAP: return static_cast<PuglStatus>( - onMap(static_cast<const MapEvent&>(event->any))); + self().onEvent(static_cast<const MapEvent&>(event->any))); case PUGL_UNMAP: return static_cast<PuglStatus>( - onUnmap(static_cast<const UnmapEvent&>(event->any))); + self().onEvent(static_cast<const UnmapEvent&>(event->any))); case PUGL_UPDATE: return static_cast<PuglStatus>( - onUpdate(static_cast<const UpdateEvent&>(event->any))); + self().onEvent(static_cast<const UpdateEvent&>(event->any))); case PUGL_EXPOSE: return static_cast<PuglStatus>( - onExpose(static_cast<const ExposeEvent&>(event->expose))); + self().onEvent(static_cast<const ExposeEvent&>(event->expose))); case PUGL_CLOSE: return static_cast<PuglStatus>( - onClose(static_cast<const CloseEvent&>(event->any))); + self().onEvent(static_cast<const CloseEvent&>(event->any))); case PUGL_FOCUS_IN: return static_cast<PuglStatus>( - onFocusIn(static_cast<const FocusInEvent&>(event->focus))); + self().onEvent(static_cast<const FocusInEvent&>(event->focus))); case PUGL_FOCUS_OUT: - return static_cast<PuglStatus>( - onFocusOut(static_cast<const FocusOutEvent&>(event->focus))); + return static_cast<PuglStatus>(self().onEvent( + static_cast<const FocusOutEvent&>(event->focus))); case PUGL_KEY_PRESS: return static_cast<PuglStatus>( - onKeyPress(static_cast<const KeyPressEvent&>(event->key))); + self().onEvent(static_cast<const KeyPressEvent&>(event->key))); case PUGL_KEY_RELEASE: - return static_cast<PuglStatus>( - onKeyRelease(static_cast<const KeyReleaseEvent&>(event->key))); + return static_cast<PuglStatus>(self().onEvent( + static_cast<const KeyReleaseEvent&>(event->key))); case PUGL_TEXT: return static_cast<PuglStatus>( - onText(static_cast<const TextEvent&>(event->text))); + self().onEvent(static_cast<const TextEvent&>(event->text))); case PUGL_POINTER_IN: - return static_cast<PuglStatus>(onPointerIn( + return static_cast<PuglStatus>(self().onEvent( static_cast<const PointerInEvent&>(event->crossing))); case PUGL_POINTER_OUT: - return static_cast<PuglStatus>(onPointerOut( + return static_cast<PuglStatus>(self().onEvent( static_cast<const PointerOutEvent&>(event->crossing))); case PUGL_BUTTON_PRESS: - return static_cast<PuglStatus>(onButtonPress( + return static_cast<PuglStatus>(self().onEvent( static_cast<const ButtonPressEvent&>(event->button))); case PUGL_BUTTON_RELEASE: - return static_cast<PuglStatus>(onButtonRelease( + return static_cast<PuglStatus>(self().onEvent( static_cast<const ButtonReleaseEvent&>(event->button))); case PUGL_MOTION: return static_cast<PuglStatus>( - onMotion(static_cast<const MotionEvent&>(event->motion))); + self().onEvent(static_cast<const MotionEvent&>(event->motion))); case PUGL_SCROLL: return static_cast<PuglStatus>( - onScroll(static_cast<const ScrollEvent&>(event->scroll))); + self().onEvent(static_cast<const ScrollEvent&>(event->scroll))); case PUGL_CLIENT: return static_cast<PuglStatus>( - onClient(static_cast<const ClientEvent&>(event->client))); + self().onEvent(static_cast<const ClientEvent&>(event->client))); case PUGL_TIMER: return static_cast<PuglStatus>( - onTimer(static_cast<const TimerEvent&>(event->timer))); + self().onEvent(static_cast<const TimerEvent&>(event->timer))); case PUGL_LOOP_ENTER: return static_cast<PuglStatus>( - onLoopEnter(static_cast<const LoopEnterEvent&>(event->any))); + self().onEvent(static_cast<const LoopEnterEvent&>(event->any))); case PUGL_LOOP_LEAVE: return static_cast<PuglStatus>( - onLoopLeave(static_cast<const LoopLeaveEvent&>(event->any))); + self().onEvent(static_cast<const LoopLeaveEvent&>(event->any))); } return PUGL_FAILURE; } - - World& _world; }; /** diff --git a/bindings/cxx/include/pugl/pugl.ipp b/bindings/cxx/include/pugl/pugl.ipp deleted file mode 100644 index c11f7d0..0000000 --- a/bindings/cxx/include/pugl/pugl.ipp +++ /dev/null @@ -1,176 +0,0 @@ -/* - Copyright 2012-2020 David Robillard <d@drobilla.net> - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl.ipp - @brief Pugl C++ API wrapper implementation. - - This file must be included exactly once in the application. -*/ - -#include "pugl/pugl.hpp" - -namespace pugl { - -#ifdef PUGL_HPP_THROW_FAILED_CONSTRUCTION - -const char* -FailedConstructionError::what() const noexcept -{ - return _msg; -} - -#endif - -Status -View::onCreate(const CreateEvent&) -{ - return Status::success; -} - -Status -View::onDestroy(const DestroyEvent&) -{ - return Status::success; -} - -Status -View::onConfigure(const ConfigureEvent&) -{ - return Status::success; -} - -Status -View::onMap(const MapEvent&) -{ - return Status::success; -} - -Status -View::onUnmap(const UnmapEvent&) -{ - return Status::success; -} - -Status -View::onUpdate(const UpdateEvent&) -{ - return Status::success; -} - -Status -View::onExpose(const ExposeEvent&) -{ - return Status::success; -} - -Status -View::onClose(const CloseEvent&) -{ - return Status::success; -} - -Status -View::onFocusIn(const FocusInEvent&) -{ - return Status::success; -} - -Status -View::onFocusOut(const FocusOutEvent&) -{ - return Status::success; -} - -Status -View::onKeyPress(const KeyPressEvent&) -{ - return Status::success; -} - -Status -View::onKeyRelease(const KeyReleaseEvent&) -{ - return Status::success; -} - -Status -View::onText(const TextEvent&) -{ - return Status::success; -} - -Status -View::onPointerIn(const PointerInEvent&) -{ - return Status::success; -} - -Status -View::onPointerOut(const PointerOutEvent&) -{ - return Status::success; -} - -Status -View::onButtonPress(const ButtonPressEvent&) -{ - return Status::success; -} - -Status -View::onButtonRelease(const ButtonReleaseEvent&) -{ - return Status::success; -} - -Status -View::onMotion(const MotionEvent&) -{ - return Status::success; -} - -Status -View::onScroll(const ScrollEvent&) -{ - return Status::success; -} - -Status -View::onClient(const ClientEvent&) -{ - return Status::success; -} - -Status -View::onTimer(const TimerEvent&) -{ - return Status::success; -} - -Status -View::onLoopEnter(const LoopEnterEvent&) -{ - return Status::success; -} - -Status -View::onLoopLeave(const LoopLeaveEvent&) -{ - return Status::success; -} - -} // namespace pugl diff --git a/bindings/cxx/include/pugl/vulkan.hpp b/bindings/cxx/include/pugl/vulkan.hpp index a7b16cb..f9737e8 100644 --- a/bindings/cxx/include/pugl/vulkan.hpp +++ b/bindings/cxx/include/pugl/vulkan.hpp @@ -143,7 +143,7 @@ getInstanceExtensions() noexcept /// @copydoc puglCreateSurface inline VkResult createSurface(const VulkanLoader& loader, - View& view, + ViewBase& view, VkInstance instance, const VkAllocationCallbacks* const allocator, VkSurfaceKHR* const surface) noexcept diff --git a/examples/pugl_cxx_demo.cpp b/examples/pugl_cxx_demo.cpp index ed2d21a..4dab35c 100644 --- a/examples/pugl_cxx_demo.cpp +++ b/examples/pugl_cxx_demo.cpp @@ -26,30 +26,23 @@ #include "pugl/gl.hpp" #include "pugl/pugl.h" #include "pugl/pugl.hpp" -#include "pugl/pugl.ipp" // IWYU pragma: keep #include <cmath> -class CubeView : public pugl::View +class CubeView : public pugl::View<CubeView> { public: explicit CubeView(pugl::World& world) - : pugl::View{world} + : pugl::View<CubeView>{world} {} - CubeView(const CubeView&) = delete; - CubeView& operator=(const CubeView&) = delete; + using pugl::View<CubeView>::onEvent; - CubeView(CubeView&&) = delete; - CubeView& operator=(CubeView&&) = delete; - - ~CubeView() override = default; - - pugl::Status onConfigure(const pugl::ConfigureEvent& event) override; - pugl::Status onUpdate(const pugl::UpdateEvent& event) override; - pugl::Status onExpose(const pugl::ExposeEvent& event) override; - pugl::Status onKeyPress(const pugl::KeyPressEvent& event) override; - pugl::Status onClose(const pugl::CloseEvent& event) override; + static pugl::Status onEvent(const pugl::ConfigureEvent& event) noexcept; + pugl::Status onEvent(const pugl::UpdateEvent& event) noexcept; + pugl::Status onEvent(const pugl::ExposeEvent& event) noexcept; + pugl::Status onEvent(const pugl::KeyPressEvent& event) noexcept; + pugl::Status onEvent(const pugl::CloseEvent& event) noexcept; bool quit() const { return _quit; } @@ -61,7 +54,7 @@ private: }; pugl::Status -CubeView::onConfigure(const pugl::ConfigureEvent& event) +CubeView::onEvent(const pugl::ConfigureEvent& event) noexcept { reshapeCube(static_cast<float>(event.width), static_cast<float>(event.height)); @@ -70,13 +63,13 @@ CubeView::onConfigure(const pugl::ConfigureEvent& event) } pugl::Status -CubeView::onUpdate(const pugl::UpdateEvent&) +CubeView::onEvent(const pugl::UpdateEvent&) noexcept { return postRedisplay(); } pugl::Status -CubeView::onExpose(const pugl::ExposeEvent&) +CubeView::onEvent(const pugl::ExposeEvent&) noexcept { const double thisTime = world().time(); const double dTime = thisTime - _lastDrawTime; @@ -96,7 +89,7 @@ CubeView::onExpose(const pugl::ExposeEvent&) } pugl::Status -CubeView::onKeyPress(const pugl::KeyPressEvent& event) +CubeView::onEvent(const pugl::KeyPressEvent& event) noexcept { if (event.key == PUGL_KEY_ESCAPE || event.key == 'q') { _quit = true; @@ -106,7 +99,7 @@ CubeView::onKeyPress(const pugl::KeyPressEvent& event) } pugl::Status -CubeView::onClose(const pugl::CloseEvent&) +CubeView::onEvent(const pugl::CloseEvent&) noexcept { _quit = true; diff --git a/examples/pugl_vulkan_cxx_demo.cpp b/examples/pugl_vulkan_cxx_demo.cpp index 12b2626..a4634ee 100644 --- a/examples/pugl_vulkan_cxx_demo.cpp +++ b/examples/pugl_vulkan_cxx_demo.cpp @@ -36,7 +36,6 @@ #include "pugl/pugl.h" #include "pugl/pugl.hpp" -#include "pugl/pugl.ipp" // IWYU pragma: keep #include "pugl/vulkan.hpp" #include <vulkan/vk_platform.h> @@ -79,7 +78,7 @@ struct VulkanContext { struct GraphicsDevice { VkResult init(const pugl::VulkanLoader& loader, const VulkanContext& context, - pugl::View& view, + pugl::ViewBase& view, const PuglTestOptions& opts); sk::SurfaceKHR surface; @@ -410,7 +409,7 @@ selectPhysicalDevice(const sk::VulkanApi& vk, VkResult GraphicsDevice::init(const pugl::VulkanLoader& loader, const VulkanContext& context, - pugl::View& view, + pugl::ViewBase& view, const PuglTestOptions& opts) { const auto& vk = context.vk; @@ -1373,22 +1372,24 @@ recordCommandBuffers(const sk::VulkanApi& vk, class PuglVulkanDemo; -class View : public pugl::View +class View : public pugl::View<View> { public: View(pugl::World& world, PuglVulkanDemo& app) - : pugl::View{world} + : pugl::View<View>{world} , _app{app} {} - pugl::Status onConfigure(const pugl::ConfigureEvent& event) override; - pugl::Status onUpdate(const pugl::UpdateEvent& event) override; - pugl::Status onExpose(const pugl::ExposeEvent& event) override; - pugl::Status onLoopEnter(const pugl::LoopEnterEvent& event) override; - pugl::Status onTimer(const pugl::TimerEvent& event) override; - pugl::Status onLoopLeave(const pugl::LoopLeaveEvent& event) override; - pugl::Status onKeyPress(const pugl::KeyPressEvent& event) override; - pugl::Status onClose(const pugl::CloseEvent& event) override; + using pugl::View<View>::onEvent; + + pugl::Status onEvent(const pugl::ConfigureEvent& event); + pugl::Status onEvent(const pugl::UpdateEvent& event); + pugl::Status onEvent(const pugl::ExposeEvent& event); + pugl::Status onEvent(const pugl::LoopEnterEvent& event); + pugl::Status onEvent(const pugl::TimerEvent& event); + pugl::Status onEvent(const pugl::LoopLeaveEvent& event); + pugl::Status onEvent(const pugl::KeyPressEvent& event); + pugl::Status onEvent(const pugl::CloseEvent& event); private: PuglVulkanDemo& _app; @@ -1482,7 +1483,7 @@ recreateRenderer(PuglVulkanDemo& app, } pugl::Status -View::onConfigure(const pugl::ConfigureEvent& event) +View::onEvent(const pugl::ConfigureEvent& event) { // We just record the size here and lazily resize the surface when exposed _app.extent = {static_cast<uint32_t>(event.width), @@ -1492,7 +1493,7 @@ View::onConfigure(const pugl::ConfigureEvent& event) } pugl::Status -View::onUpdate(const pugl::UpdateEvent&) +View::onEvent(const pugl::UpdateEvent&) { return postRedisplay(); } @@ -1633,7 +1634,7 @@ endFrame(const sk::VulkanApi& vk, } pugl::Status -View::onExpose(const pugl::ExposeEvent&) +View::onEvent(const pugl::ExposeEvent&) { const auto& vk = _app.vulkan.vk; const auto& gpu = _app.gpu; @@ -1658,7 +1659,7 @@ View::onExpose(const pugl::ExposeEvent&) } pugl::Status -View::onLoopEnter(const pugl::LoopEnterEvent&) +View::onEvent(const pugl::LoopEnterEvent&) { _app.resizing = true; startTimer(resizeTimerId, @@ -1668,13 +1669,13 @@ View::onLoopEnter(const pugl::LoopEnterEvent&) } pugl::Status -View::onTimer(const pugl::TimerEvent&) +View::onEvent(const pugl::TimerEvent&) { return postRedisplay(); } pugl::Status -View::onLoopLeave(const pugl::LoopLeaveEvent&) +View::onEvent(const pugl::LoopLeaveEvent&) { stopTimer(resizeTimerId); @@ -1686,7 +1687,7 @@ View::onLoopLeave(const pugl::LoopLeaveEvent&) } pugl::Status -View::onKeyPress(const pugl::KeyPressEvent& event) +View::onEvent(const pugl::KeyPressEvent& event) { if (event.key == PUGL_KEY_ESCAPE || event.key == 'q') { _app.quit = true; @@ -1696,7 +1697,7 @@ View::onKeyPress(const pugl::KeyPressEvent& event) } pugl::Status -View::onClose(const pugl::CloseEvent&) +View::onEvent(const pugl::CloseEvent&) { _app.quit = true; diff --git a/test/test_build.cpp b/test/test_build.cpp index 4e29901..5beb4c3 100644 --- a/test/test_build.cpp +++ b/test/test_build.cpp @@ -24,7 +24,6 @@ #include "pugl/gl.hpp" // IWYU pragma: keep #include "pugl/pugl.h" // IWYU pragma: keep #include "pugl/pugl.hpp" // IWYU pragma: keep -#include "pugl/pugl.ipp" // IWYU pragma: keep #include "pugl/stub.hpp" // IWYU pragma: keep int |