From 315b1a784d270c9f3ca0d430c3c1802a3ebfd918 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sun, 4 Aug 2019 23:37:58 +0200 Subject: WIP: Update C++ bindings --- .clang-format | 2 +- .clang-tidy | 15 +- examples/pugl_cxx_demo.cpp | 119 ++++++++++++ pugl/pugl.hpp | 437 ++++++++++++++++++++++++++++++++++++++++----- wscript | 31 +++- 5 files changed, 553 insertions(+), 51 deletions(-) create mode 100644 examples/pugl_cxx_demo.cpp diff --git a/.clang-format b/.clang-format index b788676..1c3e537 100644 --- a/.clang-format +++ b/.clang-format @@ -104,7 +104,7 @@ SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: false SpaceBeforeAssignmentOperators: true -SpaceBeforeCpp11BracedList: true +SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements diff --git a/.clang-tidy b/.clang-tidy index 0f46134..d714003 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,14 +1,27 @@ Checks: > *, + -*avoid-c-arrays, -*magic-numbers, + -*non-private-member-variables-in-classes, -*uppercase-literal-suffix, -android-cloexec-fopen, -bugprone-suspicious-string-compare, -clang-analyzer-alpha.*, + -cppcoreguidelines-macro-usage, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-type-static-cast-downcast, + -cppcoreguidelines-pro-type-vararg, + -google-runtime-references, -hicpp-multiway-paths-covered, + -hicpp-no-array-decay, -hicpp-signed-bitwise, + -hicpp-vararg, -llvm-header-guard, - -readability-else-after-return + -modernize-use-trailing-return-type, + -readability-else-after-return, + -readability-implicit-bool-conversion, + -readability-named-parameter, WarningsAsErrors: '' HeaderFilterRegex: 'pugl/.*|test/.*' FormatStyle: file diff --git a/examples/pugl_cxx_demo.cpp b/examples/pugl_cxx_demo.cpp new file mode 100644 index 0000000..4043486 --- /dev/null +++ b/examples/pugl_cxx_demo.cpp @@ -0,0 +1,119 @@ +/* + Copyright 2012-2019 David Robillard + + 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_test.c A simple Pugl test that creates a top-level window. +*/ + +#define GL_SILENCE_DEPRECATION 1 + +#include "cube_view.h" +#include "demo_utils.h" +#include "test/test_utils.h" + +#include "pugl/gl.h" +#include "pugl/pugl.hpp" +#include "pugl/pugl_gl.h" + +#include +#include +#include +#include + +struct CubeData { + double xAngle{0.0}; + double yAngle{0.0}; + double lastDrawTime{0.0}; + unsigned framesDrawn{0}; + bool quit{false}; +}; + +using CubeView = pugl::View; + +static pugl::Status +onConfigure(CubeView&, const pugl::ConfigureEvent& event) +{ + reshapeCube(event.width, event.height); + + return pugl::Status::success; +} + +static pugl::Status +onExpose(CubeView& view, const pugl::ExposeEvent&) +{ + const pugl::World& world = view.getWorld(); + CubeData& data = view.getData(); + const double thisTime = world.getTime(); + const double dTime = thisTime - data.lastDrawTime; + const double dAngle = dTime * 100.0; + + data.xAngle = fmod(data.xAngle + dAngle, 360.0); + data.yAngle = fmod(data.yAngle + dAngle, 360.0); + displayCube(view.cobj(), 8.0, data.xAngle, data.yAngle, false); + + data.lastDrawTime = thisTime; + ++data.framesDrawn; + + return pugl::Status::success; +} + +static pugl::Status +onKeyPress(CubeView& view, const pugl::KeyPressEvent& event) +{ + if (event.key == PUGL_KEY_ESCAPE || event.key == 'q') { + view.getData().quit = true; + } + + return pugl::Status::success; +} + +int +main(int argc, char** argv) +{ + const PuglTestOptions opts = puglParseTestOptions(&argc, &argv); + + pugl::World world; + CubeView view{world}; + PuglFpsPrinter fpsPrinter{}; + + world.setClassName("Pugl C++ Test"); + + view.setFrame({0, 0, 512, 512}); + view.setMinSize(64, 64); + view.setAspectRatio(1, 1, 16, 9); + view.setBackend(puglGlBackend()); + view.setHint(pugl::ViewHint::resizable, opts.resizable); + view.setHint(pugl::ViewHint::samples, opts.samples); + view.setHint(pugl::ViewHint::doubleBuffer, opts.doubleBuffer); + view.setHint(pugl::ViewHint::swapInterval, opts.doubleBuffer); + view.setHint(pugl::ViewHint::ignoreKeyRepeat, opts.ignoreKeyRepeat); + + view.setEventFunc(onConfigure); + view.setEventFunc(onExpose); + view.setEventFunc(onKeyPress); + + view.createWindow("Pugl C++ Test"); + view.showWindow(); + + while (!view.getData().quit) { + view.postRedisplay(); + world.dispatchEvents(); + + puglPrintFps(world.cobj(), &fpsPrinter, &view.getData().framesDrawn); + } + + return 0; +} diff --git a/pugl/pugl.hpp b/pugl/pugl.hpp index 73cfe2a..18451ee 100644 --- a/pugl/pugl.hpp +++ b/pugl/pugl.hpp @@ -22,6 +22,13 @@ #define PUGL_PUGL_HPP #include "pugl/pugl.h" +#include "pugl/pugl_gl.h" // FIXME + +#include +#include +#include +#include +#include /** @defgroup puglxx C++ @@ -37,84 +44,430 @@ */ namespace pugl { -/** - A drawable region that receives events. +enum class Status { + success = PUGL_SUCCESS, + failure = PUGL_FAILURE, + unknownError = PUGL_UNKNOWN_ERROR, + badBackend = PUGL_BAD_BACKEND, + backendFailed = PUGL_BACKEND_FAILED, + registrationFailed = PUGL_REGISTRATION_FAILED, + createWindowFailed = PUGL_CREATE_WINDOW_FAILED, + setFormatFailed = PUGL_SET_FORMAT_FAILED, + createContextFailed = PUGL_CREATE_CONTEXT_FAILED, + unsupportedType = PUGL_UNSUPPORTED_TYPE, +}; - This is a thin wrapper for a PuglView that contains only a pointer. +enum class ViewHint { + useCompatProfile, ///< Use compatible (not core) OpenGL profile + useDebugContext, ///< True to use a debug OpenGL context + contextVersionMajor, ///< OpenGL context major version + contextVersionMinor, ///< OpenGL context minor version + redBits, ///< Number of bits for red channel + greenBits, ///< Number of bits for green channel + blueBits, ///< Number of bits for blue channel + alphaBits, ///< Number of bits for alpha channel + depthBits, ///< Number of bits for depth buffer + stencilBits, ///< Number of bits for stencil buffer + samples, ///< Number of samples per pixel (AA) + doubleBuffer, ///< True if double buffering should be used + swapInterval, ///< Number of frames between buffer swaps + resizable, ///< True if window should be resizable + ignoreKeyRepeat, ///< True if key repeat events are ignored +}; - @ingroup puglxx -*/ -class View { +using Rect = PuglRect; +using NativeWindow = PuglNativeWindow; +using GlFunc = PuglGlFunc; +using Event = PuglEvent; + +template +struct TypedEvent : public Base { + static constexpr const PuglEventType type = t; +}; + +/* Strong types for every event type. */ + +using ButtonPressEvent = TypedEvent; +using ButtonReleaseEvent = TypedEvent; +using CreateEvent = TypedEvent; +using DestroyEvent = TypedEvent; +using MapEvent = TypedEvent; +using UnmapEvent = TypedEvent; +using ConfigureEvent = TypedEvent; +using ExposeEvent = TypedEvent; +using CloseEvent = TypedEvent; +using KeyPressEvent = TypedEvent; +using KeyReleaseEvent = TypedEvent; +using TextEvent = TypedEvent; +using EnterEvent = TypedEvent; +using LeaveEvent = TypedEvent; +using MotionEvent = TypedEvent; +using ScrollEvent = TypedEvent; +using FocusInEvent = TypedEvent; +using FocusOutEvent = TypedEvent; +using ClientEvent = TypedEvent; + +static inline const char* +strerror(pugl::Status status) +{ + return puglStrerror(static_cast(status)); +} + +static inline GlFunc +getProcAddress(const char* name) +{ + return puglGetProcAddress(name); +} + +class World; + +class Clock +{ +public: + using rep = double; + using period = std::ratio<1>; + using duration = std::chrono::duration; + using time_point = std::chrono::time_point; + + static constexpr bool is_steady = true; + + explicit Clock(World& world) + : _world{world} + {} + + time_point now() const; + +private: + const pugl::World& _world; +}; + +class World +{ +public: + World() + : _clock(*this) + , _world(puglNewWorld()) + { + if (!_world) { + throw std::runtime_error("Failed to create pugl::World"); + } + } + + ~World() { puglFreeWorld(_world); } + + World(const World&) = delete; + World& operator=(const World&) = delete; + World(World&&) = delete; + World&& operator=(World&&) = delete; + + Status setClassName(const char* const name) + { + return static_cast(puglSetClassName(_world, name)); + } + + double getTime() const { return puglGetTime(_world); } + + Status pollEvents(const double timeout) + { + return static_cast(puglPollEvents(_world, timeout)); + } + + Status dispatchEvents() + { + return static_cast(puglDispatchEvents(_world)); + } + + const PuglWorld* cobj() const { return _world; } + PuglWorld* cobj() { return _world; } + + const Clock& clock() { return _clock; } + +private: + Clock _clock; + PuglWorld* const _world; +}; + +inline Clock::time_point +Clock::now() const +{ + return time_point{duration{_world.getTime()}}; +} + +class ViewBase +{ public: - View(int* pargc, char** argv) - : _view(puglInit(pargc, argv)) + explicit ViewBase(World& world) + : _world(world) + , _view(puglNewView(world.cobj())) { + if (!_view) { + throw std::runtime_error("Failed to create pugl::View"); + } + puglSetHandle(_view, this); - puglSetEventFunc(_view, _onEvent); } - virtual ~View() { puglDestroy(_view); } + ~ViewBase() { puglFreeView(_view); } + + ViewBase(const ViewBase&) = delete; + ViewBase(ViewBase&&) = delete; + ViewBase& operator=(const ViewBase&) = delete; + ViewBase&& operator=(ViewBase&&) = delete; + + Status setHint(ViewHint hint, int value) + { + return static_cast( + puglSetViewHint(_view, static_cast(hint), value)); + } + + bool getVisible() const { return puglGetVisible(_view); } + + Status postRedisplay() + { + return static_cast(puglPostRedisplay(_view)); + } + + const pugl::World& getWorld() const { return _world; } + pugl::World& getWorld() { return _world; } + + Rect getFrame() const { return puglGetFrame(_view); } - virtual void initWindowParent(PuglNativeWindow parent) { - puglInitWindowParent(_view, parent); + Status setFrame(Rect frame) + { + return static_cast(puglSetFrame(_view, frame)); } - virtual void initWindowSize(int width, int height) { - puglInitWindowSize(_view, width, height); + Status setMinSize(int width, int height) + { + return static_cast(puglSetMinSize(_view, width, height)); } - virtual void initWindowMinSize(int width, int height) { - puglInitWindowMinSize(_view, width, height); + Status setAspectRatio(int minX, int minY, int maxX, int maxY) + { + return static_cast( + puglSetAspectRatio(_view, minX, minY, maxX, maxY)); } - virtual void initWindowAspectRatio(int min_x, int min_y, int max_x, int max_y) { - puglInitWindowAspectRatio(_view, min_x, min_y, max_x, max_y); + Status setWindowTitle(const char* title) + { + return static_cast(puglSetWindowTitle(_view, title)); } - virtual void initResizable(bool resizable) { - puglInitResizable(_view, resizable); + Status setParentWindow(NativeWindow parent) + { + return static_cast(puglSetParentWindow(_view, parent)); } - virtual void initTransientFor(uintptr_t parent) { - puglInitTransientFor(_view, parent); + Status setTransientFor(NativeWindow parent) + { + return static_cast(puglSetTransientFor(_view, parent)); } - virtual void initBackend(const PuglBackend* backend) { - puglInitBackend(_view, backend); + Status createWindow(const char* title) + { + return static_cast(puglCreateWindow(_view, title)); } - virtual void createWindow(const char* title) { - puglCreateWindow(_view, title); + Status showWindow() { return static_cast(puglShowWindow(_view)); } + + Status hideWindow() { return static_cast(puglHideWindow(_view)); } + + NativeWindow getNativeWindow() { return puglGetNativeWindow(_view); } + + Status setBackend(const PuglBackend* backend) + { + return static_cast(puglSetBackend(_view, backend)); } - virtual void showWindow() { puglShowWindow(_view); } - virtual void hideWindow() { puglHideWindow(_view); } - virtual PuglNativeWindow getNativeWindow() { return puglGetNativeWindow(_view); } + void* getContext() { return puglGetContext(_view); } - virtual void onEvent(const PuglEvent* event) = 0; + bool hasFocus() const { return puglHasFocus(_view); } - virtual void* getContext() { return puglGetContext(_view); } - virtual void ignoreKeyRepeat(bool ignore) { puglIgnoreKeyRepeat(_view, ignore); } - virtual void grabFocus() { puglGrabFocus(_view); } - virtual void requestAttention() { puglRequestAttention(_view); } - virtual PuglStatus waitForEvent() { return puglWaitForEvent(_view); } - virtual PuglStatus processEvents() { return puglProcessEvents(_view); } - virtual void postRedisplay() { puglPostRedisplay(_view); } + Status grabFocus() { return static_cast(puglGrabFocus(_view)); } + + Status requestAttention() + { + return static_cast(puglRequestAttention(_view)); + } PuglView* cobj() { return _view; } +protected: + World& _world; + PuglView* _view; +}; + +/** + A drawable region that receives events. + + This is a thin wrapper for a PuglView that contains only a pointer. + + @ingroup puglxx +*/ +template +class View : public ViewBase +{ +public: + template + using TypedEventFunc = std::function; + + using NothingEvent = TypedEvent; + + /** + A tuple of event handlers, one for each event type. + + Note that the indices here must correspond to PuglEventType. + */ + using EventFuncs = std::tuple, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc, + TypedEventFunc>; + + using EventFunc = std::function; + + explicit View(World& world) + : ViewBase{world} + , _data{} + { + puglSetEventFunc(_view, _onEvent); + } + + View(World& world, Data data) + : ViewBase{world} + , _data{data} + { + puglSetEventFunc(_view, _onEvent); + } + + template + Status setEventFunc( + std::function handler) + { + std::get(_eventFuncs) = handler; + + return Status::success; + } + + template + Status setEventFunc(pugl::Status (*handler)(View&, const HandledEvent&)) + { + std::get(_eventFuncs) = handler; + + return Status::success; + } + + const Data& getData() const { return _data; } + Data& getData() { return _data; } + private: - static void _onEvent(PuglView* view, const PuglEvent* event) { - ((View*)puglGetHandle(view))->onEvent(event); + static PuglStatus _onEvent(PuglView* view, const PuglEvent* event) noexcept + { + View* self = static_cast(puglGetHandle(view)); + + return static_cast(self->dispatchEvent(*event)); } - PuglView* _view; + Status dispatchEvent(const PuglEvent& event) + { + switch (event.type) { + case PUGL_NOTHING: + return Status::success; + case PUGL_BUTTON_PRESS: + return dispatchTypedEvent( + static_cast(event.button)); + case PUGL_BUTTON_RELEASE: + return dispatchTypedEvent( + static_cast(event.button)); + case PUGL_CREATE: + return dispatchTypedEvent( + static_cast(event.any)); + case PUGL_DESTROY: + return dispatchTypedEvent( + static_cast(event.any)); + case PUGL_MAP: + return dispatchTypedEvent(static_cast(event.any)); + case PUGL_UNMAP: + return dispatchTypedEvent( + static_cast(event.any)); + case PUGL_CONFIGURE: + return dispatchTypedEvent( + static_cast(event.configure)); + case PUGL_EXPOSE: + return dispatchTypedEvent( + static_cast(event.expose)); + case PUGL_CLOSE: + return dispatchTypedEvent( + static_cast(event.any)); + case PUGL_KEY_PRESS: + return dispatchTypedEvent( + static_cast(event.key)); + case PUGL_KEY_RELEASE: + return dispatchTypedEvent( + static_cast(event.key)); + case PUGL_TEXT: + return dispatchTypedEvent( + static_cast(event.text)); + case PUGL_ENTER_NOTIFY: + return dispatchTypedEvent( + static_cast(event.crossing)); + case PUGL_LEAVE_NOTIFY: + return dispatchTypedEvent( + static_cast(event.crossing)); + case PUGL_MOTION_NOTIFY: + return dispatchTypedEvent( + static_cast(event.motion)); + case PUGL_SCROLL: + return dispatchTypedEvent( + static_cast(event.scroll)); + case PUGL_FOCUS_IN: + return dispatchTypedEvent( + static_cast(event.focus)); + case PUGL_FOCUS_OUT: + return dispatchTypedEvent( + static_cast(event.focus)); + case PUGL_CLIENT: + return dispatchTypedEvent( + static_cast(event.client)); + } + + return Status::failure; + } + + template + Status dispatchTypedEvent(const E& event) + { + auto& handler = std::get(_eventFuncs); + if (handler) { + return handler(*this, event); + } + + return Status::success; + } + + Data _data; + EventFuncs _eventFuncs; }; -} // namespace pugl +} // namespace pugl /** @} */ -#endif /* PUGL_PUGL_HPP */ +#endif /* PUGL_PUGL_HPP */ diff --git a/wscript b/wscript index 510b260..7445e01 100644 --- a/wscript +++ b/wscript @@ -22,6 +22,7 @@ out = 'build' # Build directory def options(ctx): ctx.load('compiler_c') + ctx.load('compiler_cxx') opts = ctx.configuration_options() opts.add_option('--target', default=None, dest='target', @@ -43,24 +44,34 @@ def options(ctx): def configure(conf): conf.load('compiler_c', cache=True) + try: + conf.load('compiler_cxx', cache=True) + except Exception: + pass + conf.load('autowaf', cache=True) autowaf.set_c_lang(conf, 'c99') + if 'COMPILER_CXX' in conf.env: + autowaf.set_cxx_lang(conf, 'c++11') conf.env.ALL_HEADERS = Options.options.all_headers conf.env.TARGET_PLATFORM = Options.options.target or sys.platform platform = conf.env.TARGET_PLATFORM + def append_cflags(flags): + conf.env.append_value('CFLAGS', flags) + conf.env.append_value('CXXFLAGS', flags) + if platform == 'darwin': - conf.env.append_unique('CFLAGS', ['-Wno-deprecated-declarations']) + append_cflags(['-Wno-deprecated-declarations']) if conf.env.MSVC_COMPILER: - conf.env.append_unique('CFLAGS', ['/wd4191']) + append_cflags(['/wd4191']) else: - conf.env.append_value('LINKFLAGS', ['-fvisibility=hidden']) - conf.env.append_value('CFLAGS', ['-fvisibility=hidden']) + conf.env.append_unique('LINKFLAGS', ['-fvisibility=hidden']) + append_cflags(['-fvisibility=hidden']) if Options.options.strict: - conf.env.append_value('CFLAGS', ['-Wunused-parameter', - '-Wno-pedantic']) + append_cflags(['-Wunused-parameter', '-Wno-pedantic']) if Options.options.ultra_strict and 'clang' in conf.env.CC: for var in ['CFLAGS', 'CXXFLAGS']: @@ -289,6 +300,8 @@ def build(bld): source=['pugl/detail/x11_cairo.c']) def build_example(prog, source, platform, backend, **kwargs): + lang = 'cxx' if source[0].endswith('.cpp') else 'c' + use = ['pugl_%s_static' % platform, 'pugl_%s_%s_static' % (platform, backend)] @@ -308,7 +321,7 @@ def build(bld): deps.get(platform, {}).get(k, []) + deps.get(backend_lib, {}).get(k, []))}) - bld(features = 'c cprogram', + bld(features = '%s %sprogram' % (lang, lang), source = source, target = target, use = use, @@ -349,6 +362,10 @@ def build(bld): 'pugl_%s_stub_static' % platform], uselib = deps[platform]['uselib'] + ['CAIRO']) + if bld.env.CXX and bld.env.HAVE_GL: + build_example('pugl_cxx_demo', ['examples/pugl_cxx_demo.cpp'], + platform, 'gl', uselib=['GL', 'M']) + if bld.env.DOCS: autowaf.build_dox(bld, 'PUGL', PUGL_VERSION, top, out) -- cgit v1.2.1