From 29fa1c7a6f7ef63f2ee2bc5ad7efdb59d743252a Mon Sep 17 00:00:00 2001 From: Hanspeter Portner Date: Wed, 14 Apr 2021 11:06:00 +0200 Subject: [PATCH] Squashed 'subprojects/nk_pugl/' changes from 2f43d058..26868708 26868708 Merge commit '2acfaf8a9d27d8b9938f610db033ce1fdc8bed65' 2acfaf8a Squashed 'pugl/' changes from 9a38c762..a5e1f34e git-subtree-dir: subprojects/nk_pugl git-subtree-split: 26868708dc0f4a8e1938229a109d8be176096f27 --- pugl/.gitignore | 2 - pugl/.gitlab-ci.yml | 114 +-- pugl/.gitmodules | 3 - pugl/README.md | 21 +- pugl/bindings/cxx/include/pugl/cairo.hpp | 2 +- pugl/bindings/cxx/include/pugl/gl.hpp | 2 +- pugl/bindings/cxx/include/pugl/pugl.hpp | 34 +- pugl/bindings/cxx/include/pugl/stub.hpp | 2 +- pugl/bindings/cxx/include/pugl/vulkan.hpp | 2 +- pugl/doc/_static/custom.css | 95 --- pugl/doc/_static/meson.build | 2 + pugl/doc/_templates/about.html | 57 -- pugl/doc/c/{Doxyfile => Doxyfile.in} | 12 +- pugl/doc/c/api/meson.build | 5 + pugl/doc/c/event-loop.rst | 101 +++ pugl/doc/c/events.rst | 84 +++ pugl/doc/c/index.rst | 9 +- pugl/doc/c/meson.build | 44 ++ pugl/doc/c/overview.rst | 577 +-------------- pugl/doc/c/reference.rst | 18 - pugl/doc/c/shutting-down.rst | 20 + pugl/doc/c/view.rst | 321 +++++++++ pugl/doc/c/world.rst | 65 ++ pugl/doc/c/wscript | 43 -- pugl/doc/c/xml/meson.build | 19 + pugl/doc/conf.py.in | 81 ++- pugl/doc/cpp/{Doxyfile => Doxyfile.in} | 16 +- pugl/doc/cpp/api/meson.build | 5 + pugl/doc/cpp/c-reference.rst | 20 - pugl/doc/cpp/cpp-reference.rst | 18 - pugl/doc/cpp/event-loop.rst | 37 + pugl/doc/cpp/events.rst | 43 ++ pugl/doc/cpp/index.rst | 11 +- pugl/doc/cpp/meson.build | 43 ++ pugl/doc/cpp/overview.rst | 406 +---------- pugl/doc/cpp/view.rst | 299 ++++++++ pugl/doc/cpp/world.rst | 41 ++ pugl/doc/cpp/wscript | 44 -- pugl/doc/cpp/xml/meson.build | 21 + pugl/doc/deployment.rst | 16 +- pugl/doc/mainpage.md | 77 -- pugl/doc/meson.build | 13 + pugl/doc/{pugl.rst => summary.rst} | 4 - pugl/examples/meson.build | 80 +++ pugl/examples/pugl_cairo_demo.c | 1 + pugl/examples/pugl_cursor_demo.c | 1 + pugl/examples/pugl_cxx_demo.cpp | 1 + pugl/examples/pugl_shader_demo.c | 1 + pugl/examples/pugl_vulkan_cxx_demo.cpp | 1 + pugl/examples/pugl_window_demo.c | 1 + pugl/examples/shaders/meson.build | 35 + pugl/include/pugl/pugl.h | 34 +- pugl/meson.build | 455 ++++++++++++ pugl/meson/meson.build | 196 ++++++ pugl/meson_options.txt | 20 + pugl/scripts/cat.py | 7 + pugl/scripts/dox_to_sphinx.py | 109 ++- pugl/src/x11.c | 21 +- pugl/test/meson.build | 33 + pugl/test/test_timer.c | 2 +- pugl/waf | 27 - pugl/wscript | 814 ---------------------- 62 files changed, 2279 insertions(+), 2409 deletions(-) delete mode 100644 pugl/doc/_static/custom.css create mode 100644 pugl/doc/_static/meson.build delete mode 100644 pugl/doc/_templates/about.html rename pugl/doc/c/{Doxyfile => Doxyfile.in} (64%) create mode 100644 pugl/doc/c/api/meson.build create mode 100644 pugl/doc/c/event-loop.rst create mode 100644 pugl/doc/c/events.rst create mode 100644 pugl/doc/c/meson.build delete mode 100644 pugl/doc/c/reference.rst create mode 100644 pugl/doc/c/shutting-down.rst create mode 100644 pugl/doc/c/view.rst create mode 100644 pugl/doc/c/world.rst delete mode 100644 pugl/doc/c/wscript create mode 100644 pugl/doc/c/xml/meson.build rename pugl/doc/cpp/{Doxyfile => Doxyfile.in} (52%) create mode 100644 pugl/doc/cpp/api/meson.build delete mode 100644 pugl/doc/cpp/c-reference.rst delete mode 100644 pugl/doc/cpp/cpp-reference.rst create mode 100644 pugl/doc/cpp/event-loop.rst create mode 100644 pugl/doc/cpp/events.rst create mode 100644 pugl/doc/cpp/meson.build create mode 100644 pugl/doc/cpp/view.rst create mode 100644 pugl/doc/cpp/world.rst delete mode 100644 pugl/doc/cpp/wscript create mode 100644 pugl/doc/cpp/xml/meson.build delete mode 100644 pugl/doc/mainpage.md create mode 100644 pugl/doc/meson.build rename pugl/doc/{pugl.rst => summary.rst} (98%) create mode 100644 pugl/examples/meson.build create mode 100644 pugl/examples/shaders/meson.build create mode 100644 pugl/meson.build create mode 100644 pugl/meson/meson.build create mode 100644 pugl/meson_options.txt create mode 100755 pugl/scripts/cat.py create mode 100644 pugl/test/meson.build delete mode 100755 pugl/waf delete mode 100644 pugl/wscript diff --git a/pugl/.gitignore b/pugl/.gitignore index dad71c3..41c45d2 100644 --- a/pugl/.gitignore +++ b/pugl/.gitignore @@ -1,5 +1,3 @@ -.waf*/ build/ -.lock-waf* __pycache__ *.pyc diff --git a/pugl/.gitlab-ci.yml b/pugl/.gitlab-ci.yml index c0d0330..dc82cf8 100644 --- a/pugl/.gitlab-ci.yml +++ b/pugl/.gitlab-ci.yml @@ -2,48 +2,45 @@ stages: - build - deploy -variables: - GIT_SUBMODULE_STRATEGY: normal - .build_template: &build_definition stage: build arm32_dbg: <<: *build_definition image: lv2plugin/debian-arm32 - script: python ./waf configure build -dST --werror --no-coverage - variables: - CC: "arm-linux-gnueabihf-gcc" - CXX: "arm-linux-gnueabihf-g++" + script: + - meson setup build --cross-file=/usr/share/meson/cross/arm-linux-gnueabihf.ini -Dbuildtype=debug -Ddocs=disabled -Dstrict=true -Dwerror=true + - ninja -C build arm32_rel: <<: *build_definition image: lv2plugin/debian-arm32 - script: python ./waf configure build -ST --werror --no-coverage - variables: - CC: "arm-linux-gnueabihf-gcc" - CXX: "arm-linux-gnueabihf-g++" + script: + - meson setup build --cross-file=/usr/share/meson/cross/arm-linux-gnueabihf.ini -Dbuildtype=release -Ddocs=disabled -Dstrict=true -Dwerror=true + - ninja -C build + arm64_dbg: <<: *build_definition image: lv2plugin/debian-arm64 - script: python ./waf configure build -dST --werror --no-coverage - variables: - CC: "aarch64-linux-gnu-gcc" - CXX: "aarch64-linux-gnu-g++" + script: + - meson setup build --cross-file=/usr/share/meson/cross/aarch64-linux-gnu.ini -Dbuildtype=debug -Ddocs=disabled -Dstrict=true -Dwerror=true + - ninja -C build arm64_rel: <<: *build_definition image: lv2plugin/debian-arm64 - script: python ./waf configure build -ST --werror --no-coverage - variables: - CC: "aarch64-linux-gnu-gcc" - CXX: "aarch64-linux-gnu-g++" + script: + - meson setup build --cross-file=/usr/share/meson/cross/aarch64-linux-gnu.ini -Dbuildtype=release -Ddocs=disabled -Dstrict=true -Dwerror=true + - ninja -C build + x64_dbg: <<: *build_definition image: lv2plugin/debian-x64 - script: python3 ./waf configure build -dST --werror --no-coverage --docs + script: + - meson setup build -Dbuildtype=debug -Ddocs=enabled -Dstrict=true -Dwerror=true + - ninja -C build artifacts: paths: - build/doc @@ -51,60 +48,91 @@ x64_dbg: x64_rel: <<: *build_definition image: lv2plugin/debian-x64 - script: python ./waf configure build -ST --werror --no-coverage + script: + - meson setup build -Dbuildtype=release -Ddocs=disabled -Dstrict=true -Dwerror=true + - ninja -C build + + +x64_static: + <<: *build_definition + image: lv2plugin/debian-x64 + script: + - meson setup build -Ddefault_library=static -Ddocs=disabled -Dstrict=true -Dwerror=true + - ninja -C build + + +x64_sanitize: + <<: *build_definition + image: lv2plugin/debian-x64-clang + script: + - meson setup build -Db_lundef=false -Dbuildtype=plain -Ddocs=disabled -Dstrict=true -Dwerror=true + - ninja -C build + variables: + CC: "clang" + CXX: "clang++" + CFLAGS: "-fno-sanitize-recover=all -fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero -fsanitize=unsigned-integer-overflow -fsanitize=implicit-conversion -fsanitize=local-bounds -fsanitize=nullability" + CXXFLAGS: "-fno-sanitize-recover=all -fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero -fsanitize=unsigned-integer-overflow -fsanitize=implicit-conversion -fsanitize=local-bounds -fsanitize=nullability" + LDFLAGS: "-fno-sanitize-recover=all -fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero -fsanitize=unsigned-integer-overflow -fsanitize=implicit-conversion -fsanitize=local-bounds -fsanitize=nullability" + mingw32_dbg: <<: *build_definition image: lv2plugin/debian-mingw32 - script: python ./waf configure build -dST --werror --no-coverage --target=win32 - variables: - CC: "i686-w64-mingw32-gcc" - CXX: "i686-w64-mingw32-g++" + script: + - meson setup build --cross-file=/usr/share/meson/cross/i686-w64-mingw32.ini -Dbuildtype=debug -Ddocs=disabled -Dstrict=true -Dwerror=true + - ninja -C build mingw32_rel: <<: *build_definition image: lv2plugin/debian-mingw32 - script: python ./waf configure build -ST --werror --no-coverage --target=win32 - variables: - CC: "i686-w64-mingw32-gcc" - CXX: "i686-w64-mingw32-g++" + script: + - meson setup build --cross-file=/usr/share/meson/cross/i686-w64-mingw32.ini -Dbuildtype=release -Ddocs=disabled -Dstrict=true -Dwerror=true + - ninja -C build + mingw64_dbg: <<: *build_definition image: lv2plugin/debian-mingw64 - script: python ./waf configure build -dST --werror --no-coverage --target=win32 - variables: - CC: "x86_64-w64-mingw32-gcc" - CXX: "x86_64-w64-mingw32-g++" + script: + - meson setup build --cross-file=/usr/share/meson/cross/x86_64-w64-mingw32.ini -Dbuildtype=debug -Ddocs=disabled -Dstrict=true -Dwerror=true + - ninja -C build mingw64_rel: <<: *build_definition image: lv2plugin/debian-mingw64 - script: python ./waf configure build -ST --werror --no-coverage --target=win32 - variables: - CC: "x86_64-w64-mingw32-gcc" - CXX: "x86_64-w64-mingw32-g++" + script: + - meson setup build --cross-file=/usr/share/meson/cross/x86_64-w64-mingw32.ini -Dbuildtype=release -Ddocs=disabled -Dstrict=true -Dwerror=true + - ninja -C build + mac_dbg: <<: *build_definition - script: python ./waf configure build -dST --werror --no-coverage tags: [macos] + script: + - meson setup build -Dbuildtype=debug -Ddocs=disabled -Dstrict=true -Dwerror=true + - ninja -C build mac_rel: <<: *build_definition - script: python ./waf configure build -ST --werror --no-coverage tags: [macos] + script: + - meson setup build -Dbuildtype=release -Ddocs=disabled -Dstrict=true -Dwerror=true + - ninja -C build + win_dbg: <<: *build_definition + tags: [windows,meson] script: - - python ./waf configure build -dST --werror --no-coverage - tags: [windows,msvc,python] + - meson setup build -Dbuildtype=debug -Ddocs=disabled -Dstrict=true -Dwerror=true + - ninja -C build win_rel: <<: *build_definition - script: python ./waf configure build -ST --werror --no-coverage - tags: [windows,msvc,python] + tags: [windows,meson] + script: + - meson setup build -Dbuildtype=release -Ddocs=disabled -Dstrict=true -Dwerror=true + - ninja -C build pages: stage: deploy diff --git a/pugl/.gitmodules b/pugl/.gitmodules index cc8b569..e69de29 100644 --- a/pugl/.gitmodules +++ b/pugl/.gitmodules @@ -1,3 +0,0 @@ -[submodule "waflib"] - path = waflib - url = ../../drobilla/autowaf.git diff --git a/pugl/README.md b/pugl/README.md index 97906a8..5b4f82f 100644 --- a/pugl/README.md +++ b/pugl/README.md @@ -43,24 +43,23 @@ Documentation Pugl is a C library that includes C++ bindings. Each API is documented separately: - * [C Documentation](https://lv2.gitlab.io/pugl/c/singlehtml) - * [C++ Documentation](https://lv2.gitlab.io/pugl/cpp/singlehtml) + * [C Documentation (single page)](https://lv2.gitlab.io/pugl/c/singlehtml/) + * [C Documentation (paginated)](https://lv2.gitlab.io/pugl/c/html/) + * [C++ Documentation (single page)](https://lv2.gitlab.io/pugl/cpp/singlehtml/) + * [C++ Documentation (paginated)](https://lv2.gitlab.io/pugl/cpp/html/) The documentation can also be built from the source by configuring with `--docs`. Testing ------- -There are a few unit tests included, but unfortunately manual testing is still -required. The tests and example programs will be built if you pass the -`--test` option when configuring: +Some unit tests are included, but unfortunately manual testing is still +required. The tests and example programs are built by default. You can run +all the tests at once via ninja: - ./waf configure --test - -Then, after building, the unit tests can be run: - - ./waf - ./waf test --gui-tests + meson setup build + cd build + ninja test The `examples` directory contains several programs that serve as both manual tests and demonstrations: diff --git a/pugl/bindings/cxx/include/pugl/cairo.hpp b/pugl/bindings/cxx/include/pugl/cairo.hpp index 126bfe3..15dc5de 100644 --- a/pugl/bindings/cxx/include/pugl/cairo.hpp +++ b/pugl/bindings/cxx/include/pugl/cairo.hpp @@ -25,7 +25,7 @@ namespace pugl { /** @defgroup cairoxx Cairo Cairo graphics support. - @ingroup pugl_cxx + @ingroup puglxx @{ */ diff --git a/pugl/bindings/cxx/include/pugl/gl.hpp b/pugl/bindings/cxx/include/pugl/gl.hpp index c845d80..023dd45 100644 --- a/pugl/bindings/cxx/include/pugl/gl.hpp +++ b/pugl/bindings/cxx/include/pugl/gl.hpp @@ -26,7 +26,7 @@ namespace pugl { /** @defgroup glxx OpenGL OpenGL graphics support. - @ingroup pugl_cxx + @ingroup puglxx @{ */ diff --git a/pugl/bindings/cxx/include/pugl/pugl.hpp b/pugl/bindings/cxx/include/pugl/pugl.hpp index 9e65589..fc3bb03 100644 --- a/pugl/bindings/cxx/include/pugl/pugl.hpp +++ b/pugl/bindings/cxx/include/pugl/pugl.hpp @@ -580,13 +580,43 @@ public: return static_cast(puglRequestAttention(cobj())); } - /// @copydoc puglStartTimer + /** + Activate a repeating timer event. + + This starts a timer which will send a timer event to `view` every + `timeout` seconds. This can be used to perform some action in a view at a + regular interval with relatively low frequency. Note that the frequency + of timer events may be limited by how often update() is called. + + If the given timer already exists, it is replaced. + + @param id The identifier for this timer. This is an application-specific + ID that should be a low number, typically the value of a constant or `enum` + that starts from 0. There is a platform-specific limit to the number of + supported timers, and overhead associated with each, so applications should + create only a few timers and perform several tasks in one if necessary. + + @param timeout The period, in seconds, of this timer. This is not + guaranteed to have a resolution better than 10ms (the maximum timer + resolution on Windows) and may be rounded up if it is too short. On X11 + and MacOS, a resolution of about 1ms can usually be relied on. + + @return #PUGL_FAILURE if timers are not supported by the system, + #PUGL_UNKNOWN_ERROR if setting the timer failed. + */ Status startTimer(const uintptr_t id, const double timeout) noexcept { return static_cast(puglStartTimer(cobj(), id, timeout)); } - /// @copydoc puglStopTimer + /** + Stop an active timer. + + @param id The ID previously passed to startTimer(). + + @return #PUGL_FAILURE if timers are not supported by this system, + #PUGL_UNKNOWN_ERROR if stopping the timer failed. + */ Status stopTimer(const uintptr_t id) noexcept { return static_cast(puglStopTimer(cobj(), id)); diff --git a/pugl/bindings/cxx/include/pugl/stub.hpp b/pugl/bindings/cxx/include/pugl/stub.hpp index 6946fe0..fbafcee 100644 --- a/pugl/bindings/cxx/include/pugl/stub.hpp +++ b/pugl/bindings/cxx/include/pugl/stub.hpp @@ -25,7 +25,7 @@ namespace pugl { /** @defgroup stubxx Stub Stub graphics support. - @ingroup pugl_cxx + @ingroup puglxx @{ */ diff --git a/pugl/bindings/cxx/include/pugl/vulkan.hpp b/pugl/bindings/cxx/include/pugl/vulkan.hpp index 2612578..f3dbcad 100644 --- a/pugl/bindings/cxx/include/pugl/vulkan.hpp +++ b/pugl/bindings/cxx/include/pugl/vulkan.hpp @@ -43,7 +43,7 @@ namespace pugl { vulkan-hpp smart handles, it is relatively straightforward to wrap the result of createSurface() manually. - @ingroup pugl_cxx + @ingroup puglxx @{ */ diff --git a/pugl/doc/_static/custom.css b/pugl/doc/_static/custom.css deleted file mode 100644 index 60aa759..0000000 --- a/pugl/doc/_static/custom.css +++ /dev/null @@ -1,95 +0,0 @@ -div.document { - margin: 0; -} - -div.body { - margin-top: 2em; -} - -div.sphinxsidebarwrapper { - background: #EEE; -} - -div.sphinxsidebarwrapper p.blurb { - text-align: center; -} - -div.sphinxsidebarwrapper span.logo { - display: block; - text-align: center; - font-family: Georgia, serif; - padding: 0; - font-size: 180%; -} - -div.sphinxsidebar a { - border-width: 0; -} - -div.sphinxsidebar li { - color: #444; -} - -div.section { - margin-top: 2.5em; -} - -a.reference { - border-bottom: none; -} - -code.xref { - font-weight: normal; - background-color: #F8F8F8; - padding: 0.1em 0 0.1em 0; -} - -div.section > dl.c > dt:first-child, -div.section > dl.cpp > dt:first-child { - background-color: #F8F8F8; - font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.9em; - font-weight: normal; - margin-bottom: 0.5em; - padding: 0.1em 0 0.1em 0; -} - -tt.descname, tt.descclassname, code.descname, code.descclassname { - font-size: 0.9em; -} - -dl.member { - margin-top: 0.5em; -} - -dl.enumerator { - margin-top: 0.5em; -} - -dl.field-list > dt { - padding-left: 0; -} - -pre, tt, code { - background-color: #F8F8F8; -} - -.toctree-l1 { - margin-top: 1.0em; -} - -img.logo { - width: 6em; -} - -.class { - padding-top: 1.5em; -} - -.exception { - padding-top: 1.5em; -} - -.class > dd > dl.function { - padding-top: 1.0em; -} diff --git a/pugl/doc/_static/meson.build b/pugl/doc/_static/meson.build new file mode 100644 index 0000000..fc7792c --- /dev/null +++ b/pugl/doc/_static/meson.build @@ -0,0 +1,2 @@ +configure_file(copy: true, input: '../../resources/pugl.svg', output: 'pugl.svg') + diff --git a/pugl/doc/_templates/about.html b/pugl/doc/_templates/about.html deleted file mode 100644 index 5bbadbe..0000000 --- a/pugl/doc/_templates/about.html +++ /dev/null @@ -1,57 +0,0 @@ -{% if theme_logo %} - -{% else %} -

{{ project }}

-{% endif %} - -{% if theme_description %} -

{{ theme_description }}

-{% endif %} - -{% if theme_github_user and theme_github_repo %} -{% if theme_github_button|lower == 'true' %} -

- -

-{% endif %} -{% endif %} - -{% if theme_travis_button|lower != 'false' %} -{% if theme_travis_button|lower == 'true' %} - {% set path = theme_github_user + '/' + theme_github_repo %} -{% else %} - {% set path = theme_travis_button %} -{% endif %} -

- - https://secure.travis-ci.org/{{ path }}.svg?branch={{ theme_badge_branch }} - -

-{% endif %} - -{% if theme_codecov_button|lower != 'false' %} -{% if theme_codecov_button|lower == 'true' %} - {% set path = theme_github_user + '/' + theme_github_repo %} -{% else %} - {% set path = theme_codecov_button %} -{% endif %} -

- - https://codecov.io/github/{{ path }}/coverage.svg?branch={{ theme_badge_branch }} - -

-{% endif %} diff --git a/pugl/doc/c/Doxyfile b/pugl/doc/c/Doxyfile.in similarity index 64% rename from pugl/doc/c/Doxyfile rename to pugl/doc/c/Doxyfile.in index bdc3a46..96bbf63 100644 --- a/pugl/doc/c/Doxyfile +++ b/pugl/doc/c/Doxyfile.in @@ -2,12 +2,13 @@ PROJECT_NAME = Pugl PROJECT_BRIEF = "A minimal portable API for embeddable GUIs" QUIET = YES -WARN_AS_ERROR = NO +WARN_AS_ERROR = YES WARN_IF_UNDOCUMENTED = NO WARN_NO_PARAMDOC = NO JAVADOC_AUTOBRIEF = YES +FULL_PATH_NAMES = NO CASE_SENSE_NAMES = YES HIDE_IN_BODY_DOCS = YES REFERENCES_LINK_SOURCE = NO @@ -21,10 +22,7 @@ SHOW_FILES = NO MACRO_EXPANSION = YES PREDEFINED = PUGL_API PUGL_DISABLE_DEPRECATED PUGL_CONST_API= PUGL_CONST_FUNC= -INPUT = ../../include/pugl/cairo.h \ - ../../include/pugl/gl.h \ - ../../include/pugl/pugl.h \ - ../../include/pugl/stub.h \ - ../../include/pugl/vulkan.h +STRIP_FROM_PATH = @PUGL_SRCDIR@ +INPUT = @PUGL_HEADERS@ -OUTPUT_DIRECTORY = . +OUTPUT_DIRECTORY = doc/c diff --git a/pugl/doc/c/api/meson.build b/pugl/doc/c/api/meson.build new file mode 100644 index 0000000..5c1e30e --- /dev/null +++ b/pugl/doc/c/api/meson.build @@ -0,0 +1,5 @@ +c_pugl_rst = custom_target( + 'C API ReST Documentation', + command: [dox_to_sphinx, '-f', '@INPUT0@', 'doc/c/api'], + input: [c_index_xml] + c_rst_files, + output: 'pugl.rst') diff --git a/pugl/doc/c/event-loop.rst b/pugl/doc/c/event-loop.rst new file mode 100644 index 0000000..3b9915f --- /dev/null +++ b/pugl/doc/c/event-loop.rst @@ -0,0 +1,101 @@ +.. default-domain:: c +.. highlight:: c + +###################### +Driving the Event Loop +###################### + +Pugl does not contain any threads or other event loop "magic". +For flexibility, the event loop is driven explicitly by repeatedly calling :func:`puglUpdate`, +which processes events from the window system and dispatches them to views when necessary. + +The exact use of :func:`puglUpdate` depends on the application. +Plugins should call it with a ``timeout`` of 0 in a callback driven by the host. +This avoids blocking the main loop, +since other plugins and the host itself need to run as well. + +A program can use whatever timeout is appropriate: +event-driven applications may wait forever by using a ``timeout`` of -1, +while those that draw continuously may use a significant fraction of the frame period +(with enough time left over to render). + +********* +Redrawing +********* + +Occasional redrawing can be requested by calling :func:`puglPostRedisplay` or :func:`puglPostRedisplayRect`. +After these are called, +a :struct:`PuglEventExpose` will be dispatched on the next call to :func:`puglUpdate`. + +For continuous redrawing, +call :func:`puglPostRedisplay` while handling a :struct:`PuglEventUpdate` event. +This event is sent just before views are redrawn, +so it can be used as a hook to expand the update region right before the view is exposed. +Anything else that needs to be done every frame can be handled similarly. + +***************** +Event Dispatching +***************** + +Ideally, pending events are dispatched during a call to :func:`puglUpdate`, +directly within the scope of that call. + +Unfortunately, this is not universally true due to differences between platforms. + +MacOS +===== + +On MacOS, drawing is handled specially and not by the normal event queue mechanism. +This means that configure and expose events, +and possibly others, +may be dispatched to a view outside the scope of a :func:`puglUpdate` call. +In general, you can not rely on coherent event dispatching semantics on MacOS: +the operating system can call into application code at "random" times, +and these calls may result in Pugl events being dispatched. + +An application that follows the Pugl guidelines should work fine, +but there is one significant inconsistency you may encounter on MacOS: +posting a redisplay will not wake up a blocked :func:`puglUpdate` call. + +Windows +======= + +On Windows, the application has relatively tight control over the event loop, +so events are typically dispatched explicitly by :func:`puglUpdate`. +Drawing is handled by events, +so posting a redisplay will wake up a blocked :func:`puglUpdate` call. + +However, it is possible for the system to dispatch events at other times. +So, +it is possible for events to be dispatched outside the scope of a :func:`puglUpdate` call, +but this does not happen in normal circumstances and can largely be ignored. + +X11 +=== + +On X11, the application strictly controls event dispatching, +and there is no way for the system to call into application code at surprising times. +So, all events are dispatched in the scope of a :func:`puglUpdate` call. + +********************* +Recursive Event Loops +********************* + +On Windows and MacOS, +the event loop is stalled while the user is resizing the window or, +on Windows, +has displayed the window menu. +This means that :func:`puglUpdate` will block until the resize is finished, +or the menu is closed. + +Pugl dispatches :struct:`PuglEventLoopEnter` and :struct:`PuglEventLoopLeave` events to notify the application of this situation. +If you want to continuously redraw during resizing on these platforms, +you can schedule a timer with :func:`puglStartTimer` when the recursive loop is entered, +and post redisplays when handling the :struct:`PuglEventTimer`. +Be sure to remove the timer with :func:`puglStopTimer` when the recursive loop is finished. + +On X11, there are no recursive event loops, +and everything works as usual while the user is resizing the window. +There is nothing special about a "live resize" on X11, +and the above loop events will never be dispatched. + diff --git a/pugl/doc/c/events.rst b/pugl/doc/c/events.rst new file mode 100644 index 0000000..bf964db --- /dev/null +++ b/pugl/doc/c/events.rst @@ -0,0 +1,84 @@ +.. default-domain:: c +.. highlight:: c + +*************** +Handling Events +*************** + +Events are sent to a view when it has received user input, +must be drawn, or in other situations that may need to be handled such as resizing. + +Events are sent to the event handler as a :union:`PuglEvent` union. +The ``type`` field defines the type of the event and which field of the union is active. +The application must handle at least :enumerator:`PUGL_CONFIGURE ` +and :enumerator:`PUGL_EXPOSE ` to draw anything, +but there are many other :enum:`event types `. + +For example, a basic event handler might look something like this: + +.. code-block:: c + + static PuglStatus + onEvent(PuglView* view, const PuglEvent* event) + { + MyApp* app = (MyApp*)puglGetHandle(view); + + switch (event->type) { + case PUGL_CREATE: + return setupGraphics(app); + case PUGL_DESTROY: + return teardownGraphics(app); + case PUGL_CONFIGURE: + return resize(app, event->configure.width, event->configure.height); + case PUGL_EXPOSE: + return draw(app, view); + case PUGL_CLOSE: + return quit(app); + case PUGL_BUTTON_PRESS: + return onButtonPress(app, view, event->button); + default: + break; + } + + return PUGL_SUCCESS; + } + +Using the Graphics Context +========================== + +Drawing +------- + +Note that Pugl uses a different drawing model than many libraries, +particularly those designed for game-style main loops like `SDL `_ and `GLFW `_. + +In that style of code, drawing is performed imperatively in the main loop, +but with Pugl, the application must draw only while handling an expose event. +This is because Pugl supports event-driven applications that only draw the damaged region when necessary, +and handles exposure internally to provide optimized and consistent behavior across platforms. + +Cairo Context +------------- + +A Cairo context is created for each :struct:`PuglEventExpose`, +and only exists during the handling of that event. +Null is returned by :func:`puglGetContext` at any other time. + +OpenGL Context +-------------- + +The OpenGL context is only active during the handling of these events: + +- :struct:`PuglEventCreate` +- :struct:`PuglEventDestroy` +- :struct:`PuglEventConfigure` +- :struct:`PuglEventExpose` + +As always, drawing is only possible during an expose. + +Vulkan Context +-------------- + +With Vulkan, the graphics context is managed by the application rather than Pugl. +However, drawing must still only be performed during an expose. + diff --git a/pugl/doc/c/index.rst b/pugl/doc/c/index.rst index 6f046de..020cf32 100644 --- a/pugl/doc/c/index.rst +++ b/pugl/doc/c/index.rst @@ -1,6 +1,11 @@ +#### +Pugl +#### + +.. include:: summary.rst + .. toctree:: - pugl deployment overview - reference + api/pugl diff --git a/pugl/doc/c/meson.build b/pugl/doc/c/meson.build new file mode 100644 index 0000000..df9363e --- /dev/null +++ b/pugl/doc/c/meson.build @@ -0,0 +1,44 @@ +config = configuration_data() +config.set('PUGL_VERSION', meson.project_version()) + +conf_py = configure_file(configuration: config, + input: '../conf.py.in', + output: 'conf.py') + +configure_file(copy: true, input: '../deployment.rst', output: 'deployment.rst') +configure_file(copy: true, input: '../summary.rst', output: 'summary.rst') + +c_rst_files = files( + 'index.rst', + 'overview.rst', + 'world.rst', + 'view.rst', + 'events.rst', + 'event-loop.rst', + 'shutting-down.rst' +) + +foreach f : c_rst_files + configure_file(copy: true, input: f, output: '@PLAINNAME@') +endforeach + +subdir('xml') +subdir('api') + +docs = custom_target( + 'C API Documentation (singlehtml)', + command: [sphinx_build, '-M', 'singlehtml', 'doc/c/', 'doc/c/', '-E', '-q', '-t', 'singlehtml'], + input: [c_rst_files, c_pugl_rst, c_index_xml], + output: 'singlehtml', + build_by_default: true, + install: true, + install_dir: docdir / 'pugl-0') + +docs = custom_target( + 'C API Documentation (html)', + command: [sphinx_build, '-M', 'html', 'doc/c/', 'doc/c/', '-E', '-q', '-t', 'html'], + input: [c_rst_files, c_pugl_rst, c_index_xml], + output: 'html', + build_by_default: true, + install: true, + install_dir: docdir / 'pugl-0') diff --git a/pugl/doc/c/overview.rst b/pugl/doc/c/overview.rst index 3ebe21e..4bd024d 100644 --- a/pugl/doc/c/overview.rst +++ b/pugl/doc/c/overview.rst @@ -1,579 +1,26 @@ .. default-domain:: c .. highlight:: c -The core API (excluding backend-specific components) is declared in ``pugl.h``: - -.. code-block:: c +######## +Overview +######## - #include - -The API revolves around two main objects: the `world` and the `view`. +The Pugl API revolves around two main objects: the `world` and the `view`. An application creates a world to manage top-level state, then creates one or more views to display. -**************** -Creating a World -**************** - -The world is the top-level object which represents an instance of Pugl. -It handles the connection to the window system, -and manages views and the event loop. - -An application typically has a single world, -which is constructed once on startup and used to drive the main event loop. - -Construction -============ - -A world must be created before any views, and it must outlive all of its views. -A world is created with :func:`puglNewWorld`, for example: - -.. code-block:: c - - PuglWorld* world = puglNewWorld(PUGL_PROGRAM, 0); - -For a plugin, specify :enumerator:`PUGL_MODULE ` instead. -In some cases, it is necessary to pass additional flags. -For example, Vulkan requires thread support: - -.. code-block:: c - - PuglWorld* world = puglNewWorld(PUGL_MODULE, PUGL_WORLD_THREADS) - -It is a good idea to set a class name for your project with :func:`puglSetClassName`. -This allows the window system to distinguish different applications and, -for example, users to set up rules to manage their windows nicely: - -.. code-block:: c - - puglSetClassName(world, "MyAwesomeProject") - -Setting Application Data -======================== - -Pugl will call an event handler in the application with only a view pointer and an event, -so there needs to be some way to access the data you use in your application. -This is done by setting an opaque handle on the world with :func:`puglSetWorldHandle`, -for example: - -.. code-block:: c - - puglSetWorldHandle(world, myApp); - -The handle can be later retrieved with :func:`puglGetWorldHandle`: - -.. code-block:: c - - MyApp* app = (MyApp*)puglGetWorldHandle(world); - -All non-constant data should be accessed via this handle, -to avoid problems associated with static mutable data. - -*************** -Creating a View -*************** - -A view is a drawable region that receives events. -You may think of it as a window, -though it may be embedded and not represent a top-level system window. [#f1]_ - -Creating a visible view is a multi-step process. -When a new view is created with :func:`puglNewView`, -it does not yet represent a "real" system view: - -.. code-block:: c - - PuglView* view = puglNewView(world); - -Configuring the Frame -===================== - -Before display, -the necessary :doc:`frame ` and :doc:`window ` attributes should be set. -These allow the window system (or plugin host) to arrange the view properly. -For example: - -.. code-block:: c - - const double defaultWidth = 1920.0; - const double defaultHeight = 1080.0; - - puglSetWindowTitle(view, "My Window"); - puglSetDefaultSize(view, defaultWidth, defaultHeight); - puglSetMinSize(view, defaultWidth / 4.0, defaultHeight / 4.0); - puglSetAspectRatio(view, 1, 1, 16, 9); - -There are also several :enum:`hints ` for basic attributes that can be set: - -.. code-block:: c - - puglSetViewHint(view, PUGL_RESIZABLE, PUGL_TRUE); - puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, PUGL_TRUE); - -Embedding -========= - -To embed the view in another window, -you will need to somehow get the :type:`native view handle ` for the parent, -then set it with :func:`puglSetParentWindow`. -If the parent is a Pugl view, -the native handle can be accessed with :func:`puglGetNativeWindow`. -For example: - -.. code-block:: c - - puglSetParentWindow(view, puglGetNativeWindow(parent)); - -Setting an Event Handler -======================== - -In order to actually do anything, a view must process events from the system. -Pugl dispatches all events to a single :type:`event handling function `, -which is set with :func:`puglSetEventFunc`: - -.. code-block:: c - - puglSetEventFunc(view, onEvent); - -See `Handling Events`_ below for details on writing the event handler itself. - -Setting View Data -================= - -Since the event handler is called with only a view pointer and an event, -there needs to be some way to access application data associated with the view. -Similar to `Setting Application Data`_ above, -this is done by setting an opaque handle on the view with :func:`puglSetHandle`, -for example: - -.. code-block:: c - - puglSetHandle(view, myViewData); - -The handle can be later retrieved, -likely in the event handler, -with :func:`puglGetHandle`: - -.. code-block:: c - - MyViewData* data = (MyViewData*)puglGetHandle(view); - -All non-constant data should be accessed via this handle, -to avoid problems associated with static mutable data. - -If data is also associated with the world, -it can be retrieved via the view using :func:`puglGetWorld`: - -.. code-block:: c - - PuglWorld* world = puglGetWorld(view); - MyApp* app = (MyApp*)puglGetWorldHandle(world); - -Setting a Backend -================= - -Before being realized, the view must have a backend set with :func:`puglSetBackend`. - -The backend manages the graphics API that will be used for drawing. -Pugl includes backends and supporting API for -:doc:`Cairo `, :doc:`OpenGL `, and :doc:`Vulkan `. - -Using Cairo ------------ - -Cairo-specific API is declared in the ``cairo.h`` header: - -.. code-block:: c - - #include - -The Cairo backend is provided by :func:`puglCairoBackend()`: - -.. code-block:: c - - puglSetBackend(view, puglCairoBackend()); - -No additional configuration is required for Cairo. -To draw when handling an expose event, -the `Cairo context `_ can be accessed with :func:`puglGetContext`: - -.. code-block:: c - - cairo_t* cr = (cairo_t*)puglGetContext(view); - -Using OpenGL ------------- - -OpenGL-specific API is declared in the ``gl.h`` header: - -.. code-block:: c - - #include - -The OpenGL backend is provided by :func:`puglGlBackend()`: - -.. code-block:: c - - puglSetBackend(view, puglGlBackend()); - -Some hints must also be set so that the context can be set up correctly. -For example, to use OpenGL 3.3 Core Profile: - -.. code-block:: c - - puglSetViewHint(view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE); - puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 3); - puglSetViewHint(view, PUGL_CONTEXT_VERSION_MINOR, 3); - -If you need to perform some setup using the OpenGL API, -there are two ways to do so. - -The OpenGL context is active when -:enumerator:`PUGL_CREATE ` and -:enumerator:`PUGL_DESTROY ` -events are dispatched, -so things like creating and destroying shaders and textures can be done then. - -Alternatively, if it is cumbersome to set up and tear down OpenGL in the event handler, -:func:`puglEnterContext` and :func:`puglLeaveContext` can be used to manually activate the OpenGL context during application setup. -Note, however, that unlike many other APIs, these functions must not be used for drawing. -It is only valid to use the OpenGL API for configuration in a manually entered context, -rendering will not work. -For example: - -.. code-block:: c - - puglEnterContext(view); - setupOpenGL(myApp); - puglLeaveContext(view); - - while (!myApp->quit) { - puglUpdate(world, 0.0); - } - - puglEnterContext(view); - teardownOpenGL(myApp); - puglLeaveContext(view); - -Using Vulkan ------------- - -Vulkan-specific API is declared in the ``vulkan.h`` header. -This header includes Vulkan headers, -so if you are dynamically loading Vulkan at runtime, -you should define ``VK_NO_PROTOTYPES`` before including it. - -.. code-block:: c - - #define VK_NO_PROTOTYPES - - #include - -The Vulkan backend is provided by :func:`puglVulkanBackend()`: - -.. code-block:: c - - puglSetBackend(view, puglVulkanBackend()); - -Unlike OpenGL, almost all Vulkan configuration is done using the Vulkan API directly. -Pugl only provides a portable mechanism to load the Vulkan library and get the functions used to load the rest of the Vulkan API. - -Loading Vulkan -^^^^^^^^^^^^^^ - -For maximum compatibility, -it is best to not link to Vulkan at compile-time, -but instead load the Vulkan API at run-time. -To do so, first create a :struct:`PuglVulkanLoader`: - -.. code-block:: c - - PuglVulkanLoader* loader = puglNewVulkanLoader(world); - -The loader manages the dynamically loaded Vulkan library, -so it must be kept alive for as long as the application is using Vulkan. -You can get the function used to load Vulkan functions with :func:`puglGetInstanceProcAddrFunc`: - -.. code-block:: c - - PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = - puglGetInstanceProcAddrFunc(loader); - -This vkGetInstanceProcAddr_ function can be used to load the rest of the Vulkan API. -For example, you can use it to get the vkCreateInstance_ function, -then use that to create your Vulkan instance. -In practice, you will want to use some loader or wrapper API since there are many Vulkan functions. - -For advanced situations, -there is also :func:`puglGetDeviceProcAddrFunc` which retrieves the vkGetDeviceProcAddr_ function instead. - -The Vulkan loader is provided for convenience, -so that applications to not need to write platform-specific code to load Vulkan. -Its use it not mandatory and Pugl can be used with Vulkan loaded by some other method. - -Linking with Vulkan -^^^^^^^^^^^^^^^^^^^ - -If you do want to link to the Vulkan library at compile time, -note that the Pugl Vulkan backend does not depend on it, -so you will have to do so explicitly. - -Creating a Surface -^^^^^^^^^^^^^^^^^^ - -The details of using Vulkan are far beyond the scope of this documentation, -but Pugl provides a portable function, :func:`puglCreateSurface`, -to get the Vulkan surface for a view. -Assuming you have somehow created your ``VkInstance``, -you can get the surface for a view using :func:`puglCreateSurface`: - -.. code-block:: c - - VkSurfaceKHR* surface = NULL; - puglCreateSurface(puglGetDeviceProcAddrFunc(loader), - view, - vulkanInstance, - NULL, - &surface); - -Showing the View -================ - -Once the view is configured, it can be "realized" with :func:`puglRealize`. -This creates a "real" system view, for example: - -.. code-block:: c - - PuglStatus status = puglRealize(view); - if (status) { - fprintf(stderr, "Error realizing view (%s)\n", puglStrerror(status)); - } - -Note that realizing a view can fail for many reasons, -so the return code should always be checked. -This is generally the case for any function that interacts with the window system. -Most functions also return a :enum:`PuglStatus`, -but these checks are omitted for brevity in the rest of this documentation. - -A realized view is not initially visible, -but can be shown with :func:`puglShow`: - -.. code-block:: c - - puglShow(view); - -To create an initially visible view, -it is also possible to simply call :func:`puglShow` right away. -The view will be automatically realized if necessary. - -*************** -Handling Events -*************** - -Events are sent to a view when it has received user input, -must be drawn, or in other situations that may need to be handled such as resizing. - -Events are sent to the event handler as a :union:`PuglEvent` union. -The ``type`` field defines the type of the event and which field of the union is active. -The application must handle at least :enumerator:`PUGL_CONFIGURE ` -and :enumerator:`PUGL_EXPOSE ` to draw anything, -but there are many other :enum:`event types `. - -For example, a basic event handler might look something like this: +The core API (excluding backend-specific components) is declared in ``pugl.h``: .. code-block:: c - static PuglStatus - onEvent(PuglView* view, const PuglEvent* event) - { - MyApp* app = (MyApp*)puglGetHandle(view); - - switch (event->type) { - case PUGL_CREATE: - return setupGraphics(app); - case PUGL_DESTROY: - return teardownGraphics(app); - case PUGL_CONFIGURE: - return resize(app, event->configure.width, event->configure.height); - case PUGL_EXPOSE: - return draw(app, view); - case PUGL_CLOSE: - return quit(app); - case PUGL_BUTTON_PRESS: - return onButtonPress(app, view, event->button); - default: - break; - } - - return PUGL_SUCCESS; - } - -Using the Graphics Context -========================== - -Drawing -------- - -Note that Pugl uses a different drawing model than many libraries, -particularly those designed for game-style main loops like `SDL `_ and `GLFW `_. - -In that style of code, drawing is performed imperatively in the main loop, -but with Pugl, the application must draw only while handling an expose event. -This is because Pugl supports event-driven applications that only draw the damaged region when necessary, -and handles exposure internally to provide optimized and consistent behavior across platforms. - -Cairo Context -------------- - -A Cairo context is created for each :struct:`PuglEventExpose`, -and only exists during the handling of that event. -Null is returned by :func:`puglGetContext` at any other time. - -OpenGL Context --------------- - -The OpenGL context is only active during the handling of these events: - -- :struct:`PuglEventCreate` -- :struct:`PuglEventDestroy` -- :struct:`PuglEventConfigure` -- :struct:`PuglEventExpose` - -As always, drawing is only possible during an expose. - -Vulkan Context --------------- - -With Vulkan, the graphics context is managed by the application rather than Pugl. -However, drawing must still only be performed during an expose. - -********************** -Driving the Event Loop -********************** - -Pugl does not contain any threads or other event loop "magic". -For flexibility, the event loop is driven explicitly by repeatedly calling :func:`puglUpdate`, -which processes events from the window system and dispatches them to views when necessary. - -The exact use of :func:`puglUpdate` depends on the application. -Plugins should call it with a ``timeout`` of 0 in a callback driven by the host. -This avoids blocking the main loop, -since other plugins and the host itself need to run as well. - -A program can use whatever timeout is appropriate: -event-driven applications may wait forever by using a ``timeout`` of -1, -while those that draw continuously may use a significant fraction of the frame period -(with enough time left over to render). - -Redrawing -========= - -Occasional redrawing can be requested by calling :func:`puglPostRedisplay` or :func:`puglPostRedisplayRect`. -After these are called, -a :struct:`PuglEventExpose` will be dispatched on the next call to :func:`puglUpdate`. - -For continuous redrawing, -call :func:`puglPostRedisplay` while handling a :struct:`PuglEventUpdate` event. -This event is sent just before views are redrawn, -so it can be used as a hook to expand the update region right before the view is exposed. -Anything else that needs to be done every frame can be handled similarly. - -Event Dispatching -================= - -Ideally, pending events are dispatched during a call to :func:`puglUpdate`, -directly within the scope of that call. - -Unfortunately, this is not universally true due to differences between platforms. - -MacOS ------ - -On MacOS, drawing is handled specially and not by the normal event queue mechanism. -This means that configure and expose events, -and possibly others, -may be dispatched to a view outside the scope of a :func:`puglUpdate` call. -In general, you can not rely on coherent event dispatching semantics on MacOS: -the operating system can call into application code at "random" times, -and these calls may result in Pugl events being dispatched. - -An application that follows the Pugl guidelines should work fine, -but there is one significant inconsistency you may encounter on MacOS: -posting a redisplay will not wake up a blocked :func:`puglUpdate` call. - -Windows -------- - -On Windows, the application has relatively tight control over the event loop, -so events are typically dispatched explicitly by :func:`puglUpdate`. -Drawing is handled by events, -so posting a redisplay will wake up a blocked :func:`puglUpdate` call. - -However, it is possible for the system to dispatch events at other times. -So, -it is possible for events to be dispatched outside the scope of a :func:`puglUpdate` call, -but this does not happen in normal circumstances and can largely be ignored. - -X11 ---- - -On X11, the application strictly controls event dispatching, -and there is no way for the system to call into application code at surprising times. -So, all events are dispatched in the scope of a :func:`puglUpdate` call. - -Recursive Event Loops ---------------------- - -On Windows and MacOS, -the event loop is stalled while the user is resizing the window or, -on Windows, -has displayed the window menu. -This means that :func:`puglUpdate` will block until the resize is finished, -or the menu is closed. - -Pugl dispatches :struct:`PuglEventLoopEnter` and :struct:`PuglEventLoopLeave` events to notify the application of this situation. -If you want to continuously redraw during resizing on these platforms, -you can schedule a timer with :func:`puglStartTimer` when the recursive loop is entered, -and post redisplays when handling the :struct:`PuglEventTimer`. -Be sure to remove the timer with :func:`puglStopTimer` when the recursive loop is finished. - -On X11, there are no recursive event loops, -and everything works as usual while the user is resizing the window. -There is nothing special about a "live resize" on X11, -and the above loop events will never be dispatched. - -************* -Shutting Down -************* - -When a view is closed, -it will receive a :struct:`PuglEventClose`. -An application may also set a flag based on user input or other conditions, -which can be used to break out of the main loop and stop calling :func:`puglUpdate`. - -When the main event loop has finished running, -any views and the world need to be destroyed, in that order. -For example: + #include -.. code-block:: c +.. toctree:: - puglFreeView(view); - puglFreeWorld(world); + world + view + events + event-loop + shutting-down .. _pkg-config: https://www.freedesktop.org/wiki/Software/pkg-config/ - -.. _vkCreateInstance: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCreateInstance.html - -.. _vkGetDeviceProcAddr: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkGetDeviceProcAddr.html - -.. _vkGetInstanceProcAddr: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkGetInstanceProcAddr.html - -.. rubric:: Footnotes - -.. [#f1] MacOS has a strong distinction between - `views `_, - which may be nested, and - `windows `_, - which may not. - On Windows and X11, everything is a nestable window, - but top-level windows are configured differently. diff --git a/pugl/doc/c/reference.rst b/pugl/doc/c/reference.rst deleted file mode 100644 index 21a187f..0000000 --- a/pugl/doc/c/reference.rst +++ /dev/null @@ -1,18 +0,0 @@ -############# -API Reference -############# - -This section contains the generated documentation for all symbols in the public -API. - -.. toctree:: - - api/status - api/world - api/view - api/events - - api/cairo - api/gl - api/stub - api/vulkan diff --git a/pugl/doc/c/shutting-down.rst b/pugl/doc/c/shutting-down.rst new file mode 100644 index 0000000..dfb56cd --- /dev/null +++ b/pugl/doc/c/shutting-down.rst @@ -0,0 +1,20 @@ +.. default-domain:: c +.. highlight:: c + +############# +Shutting Down +############# + +When a view is closed, +it will receive a :struct:`PuglEventClose`. +An application may also set a flag based on user input or other conditions, +which can be used to break out of the main loop and stop calling :func:`puglUpdate`. + +When the main event loop has finished running, +any views and the world need to be destroyed, in that order. +For example: + +.. code-block:: c + + puglFreeView(view); + puglFreeWorld(world); diff --git a/pugl/doc/c/view.rst b/pugl/doc/c/view.rst new file mode 100644 index 0000000..12f146d --- /dev/null +++ b/pugl/doc/c/view.rst @@ -0,0 +1,321 @@ +.. default-domain:: c +.. highlight:: c + +############### +Creating a View +############### + +A view is a drawable region that receives events. +You may think of it as a window, +though it may be embedded and not represent a top-level system window. [#f1]_ + +Creating a visible view is a multi-step process. +When a new view is created with :func:`puglNewView`, +it does not yet represent a "real" system view: + +.. code-block:: c + + PuglView* view = puglNewView(world); + +********************* +Configuring the Frame +********************* + +Before display, +the necessary :doc:`frame ` and :doc:`window ` attributes should be set. +These allow the window system (or plugin host) to arrange the view properly. +For example: + +.. code-block:: c + + const double defaultWidth = 1920.0; + const double defaultHeight = 1080.0; + + puglSetWindowTitle(view, "My Window"); + puglSetDefaultSize(view, defaultWidth, defaultHeight); + puglSetMinSize(view, defaultWidth / 4.0, defaultHeight / 4.0); + puglSetAspectRatio(view, 1, 1, 16, 9); + +There are also several :enum:`hints ` for basic attributes that can be set: + +.. code-block:: c + + puglSetViewHint(view, PUGL_RESIZABLE, PUGL_TRUE); + puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, PUGL_TRUE); + +********* +Embedding +********* + +To embed the view in another window, +you will need to somehow get the :type:`native view handle ` for the parent, +then set it with :func:`puglSetParentWindow`. +If the parent is a Pugl view, +the native handle can be accessed with :func:`puglGetNativeWindow`. +For example: + +.. code-block:: c + + puglSetParentWindow(view, puglGetNativeWindow(parent)); + +************************ +Setting an Event Handler +************************ + +In order to actually do anything, a view must process events from the system. +Pugl dispatches all events to a single :type:`event handling function `, +which is set with :func:`puglSetEventFunc`: + +.. code-block:: c + + puglSetEventFunc(view, onEvent); + +See :doc:`events` for details on writing the event handler itself. + +***************** +Setting View Data +***************** + +Since the event handler is called with only a view pointer and an event, +there needs to be some way to access application data associated with the view. +Similar to :ref:`setting application data `, +this is done by setting an opaque handle on the view with :func:`puglSetHandle`, +for example: + +.. code-block:: c + + puglSetHandle(view, myViewData); + +The handle can be later retrieved, +likely in the event handler, +with :func:`puglGetHandle`: + +.. code-block:: c + + MyViewData* data = (MyViewData*)puglGetHandle(view); + +All non-constant data should be accessed via this handle, +to avoid problems associated with static mutable data. + +If data is also associated with the world, +it can be retrieved via the view using :func:`puglGetWorld`: + +.. code-block:: c + + PuglWorld* world = puglGetWorld(view); + MyApp* app = (MyApp*)puglGetWorldHandle(world); + +***************** +Setting a Backend +***************** + +Before being realized, the view must have a backend set with :func:`puglSetBackend`. + +The backend manages the graphics API that will be used for drawing. +Pugl includes backends and supporting API for +:doc:`Cairo `, :doc:`OpenGL `, and :doc:`Vulkan `. + +Using Cairo +=========== + +Cairo-specific API is declared in the ``cairo.h`` header: + +.. code-block:: c + + #include + +The Cairo backend is provided by :func:`puglCairoBackend()`: + +.. code-block:: c + + puglSetBackend(view, puglCairoBackend()); + +No additional configuration is required for Cairo. +To draw when handling an expose event, +the `Cairo context `_ can be accessed with :func:`puglGetContext`: + +.. code-block:: c + + cairo_t* cr = (cairo_t*)puglGetContext(view); + +Using OpenGL +============ + +OpenGL-specific API is declared in the ``gl.h`` header: + +.. code-block:: c + + #include + +The OpenGL backend is provided by :func:`puglGlBackend()`: + +.. code-block:: c + + puglSetBackend(view, puglGlBackend()); + +Some hints must also be set so that the context can be set up correctly. +For example, to use OpenGL 3.3 Core Profile: + +.. code-block:: c + + puglSetViewHint(view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE); + puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 3); + puglSetViewHint(view, PUGL_CONTEXT_VERSION_MINOR, 3); + +If you need to perform some setup using the OpenGL API, +there are two ways to do so. + +The OpenGL context is active when +:enumerator:`PUGL_CREATE ` and +:enumerator:`PUGL_DESTROY ` +events are dispatched, +so things like creating and destroying shaders and textures can be done then. + +Alternatively, if it is cumbersome to set up and tear down OpenGL in the event handler, +:func:`puglEnterContext` and :func:`puglLeaveContext` can be used to manually activate the OpenGL context during application setup. +Note, however, that unlike many other APIs, these functions must not be used for drawing. +It is only valid to use the OpenGL API for configuration in a manually entered context, +rendering will not work. +For example: + +.. code-block:: c + + puglEnterContext(view); + setupOpenGL(myApp); + puglLeaveContext(view); + + while (!myApp->quit) { + puglUpdate(world, 0.0); + } + + puglEnterContext(view); + teardownOpenGL(myApp); + puglLeaveContext(view); + +Using Vulkan +============ + +Vulkan-specific API is declared in the ``vulkan.h`` header. +This header includes Vulkan headers, +so if you are dynamically loading Vulkan at runtime, +you should define ``VK_NO_PROTOTYPES`` before including it. + +.. code-block:: c + + #define VK_NO_PROTOTYPES + + #include + +The Vulkan backend is provided by :func:`puglVulkanBackend()`: + +.. code-block:: c + + puglSetBackend(view, puglVulkanBackend()); + +Unlike OpenGL, almost all Vulkan configuration is done using the Vulkan API directly. +Pugl only provides a portable mechanism to load the Vulkan library and get the functions used to load the rest of the Vulkan API. + +Loading Vulkan +-------------- + +For maximum compatibility, +it is best to not link to Vulkan at compile-time, +but instead load the Vulkan API at run-time. +To do so, first create a :struct:`PuglVulkanLoader`: + +.. code-block:: c + + PuglVulkanLoader* loader = puglNewVulkanLoader(world); + +The loader manages the dynamically loaded Vulkan library, +so it must be kept alive for as long as the application is using Vulkan. +You can get the function used to load Vulkan functions with :func:`puglGetInstanceProcAddrFunc`: + +.. code-block:: c + + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = + puglGetInstanceProcAddrFunc(loader); + +This vkGetInstanceProcAddr_ function can be used to load the rest of the Vulkan API. +For example, you can use it to get the vkCreateInstance_ function, +then use that to create your Vulkan instance. +In practice, you will want to use some loader or wrapper API since there are many Vulkan functions. + +For advanced situations, +there is also :func:`puglGetDeviceProcAddrFunc` which retrieves the vkGetDeviceProcAddr_ function instead. + +The Vulkan loader is provided for convenience, +so that applications to not need to write platform-specific code to load Vulkan. +Its use it not mandatory and Pugl can be used with Vulkan loaded by some other method. + +Linking with Vulkan +------------------- + +If you do want to link to the Vulkan library at compile time, +note that the Pugl Vulkan backend does not depend on it, +so you will have to do so explicitly. + +Creating a Surface +------------------ + +The details of using Vulkan are far beyond the scope of this documentation, +but Pugl provides a portable function, :func:`puglCreateSurface`, +to get the Vulkan surface for a view. +Assuming you have somehow created your ``VkInstance``, +you can get the surface for a view using :func:`puglCreateSurface`: + +.. code-block:: c + + VkSurfaceKHR* surface = NULL; + puglCreateSurface(puglGetDeviceProcAddrFunc(loader), + view, + vulkanInstance, + NULL, + &surface); + +**************** +Showing the View +**************** + +Once the view is configured, it can be "realized" with :func:`puglRealize`. +This creates a "real" system view, for example: + +.. code-block:: c + + PuglStatus status = puglRealize(view); + if (status) { + fprintf(stderr, "Error realizing view (%s)\n", puglStrerror(status)); + } + +Note that realizing a view can fail for many reasons, +so the return code should always be checked. +This is generally the case for any function that interacts with the window system. +Most functions also return a :enum:`PuglStatus`, +but these checks are omitted for brevity in the rest of this documentation. + +A realized view is not initially visible, +but can be shown with :func:`puglShow`: + +.. code-block:: c + + puglShow(view); + +To create an initially visible view, +it is also possible to simply call :func:`puglShow` right away. +The view will be automatically realized if necessary. + +.. rubric:: Footnotes + +.. [#f1] MacOS has a strong distinction between + `views `_, + which may be nested, and + `windows `_, + which may not. + On Windows and X11, everything is a nestable window, + but top-level windows are configured differently. + +.. _vkCreateInstance: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCreateInstance.html + +.. _vkGetDeviceProcAddr: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkGetDeviceProcAddr.html + +.. _vkGetInstanceProcAddr: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkGetInstanceProcAddr.html diff --git a/pugl/doc/c/world.rst b/pugl/doc/c/world.rst new file mode 100644 index 0000000..83d9dbd --- /dev/null +++ b/pugl/doc/c/world.rst @@ -0,0 +1,65 @@ +################ +Creating a World +################ + +.. default-domain:: c +.. highlight:: c + +The world is the top-level object which represents an instance of Pugl. +It handles the connection to the window system, +and manages views and the event loop. + +An application typically has a single world, +which is constructed once on startup and used to drive the main event loop. + +************ +Construction +************ + +A world must be created before any views, and it must outlive all of its views. +A world is created with :func:`puglNewWorld`, for example: + +.. code-block:: c + + PuglWorld* world = puglNewWorld(PUGL_PROGRAM, 0); + +For a plugin, specify :enumerator:`PUGL_MODULE ` instead. +In some cases, it is necessary to pass additional flags. +For example, Vulkan requires thread support: + +.. code-block:: c + + PuglWorld* world = puglNewWorld(PUGL_MODULE, PUGL_WORLD_THREADS) + +It is a good idea to set a class name for your project with :func:`puglSetClassName`. +This allows the window system to distinguish different applications and, +for example, users to set up rules to manage their windows nicely: + +.. code-block:: c + + puglSetClassName(world, "MyAwesomeProject") + +.. _setting-application-data: + +************************ +Setting Application Data +************************ + +Pugl will call an event handler in the application with only a view pointer and an event, +so there needs to be some way to access the data you use in your application. +This is done by setting an opaque handle on the world with :func:`puglSetWorldHandle`, +for example: + +.. code-block:: c + + puglSetWorldHandle(world, myApp); + +The handle can be later retrieved with :func:`puglGetWorldHandle`: + +.. code-block:: c + + MyApp* app = (MyApp*)puglGetWorldHandle(world); + +All non-constant data should be accessed via this handle, +to avoid problems associated with static mutable data. + diff --git a/pugl/doc/c/wscript b/pugl/doc/c/wscript deleted file mode 100644 index 4e0fbc9..0000000 --- a/pugl/doc/c/wscript +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python - -def build(bld): - dox_to_sphinx = bld.path.find_node("../../scripts/dox_to_sphinx.py") - index_xml = bld.path.get_bld().make_node("xml/index.xml") - - files = [ - ("../../resources/pugl.svg", "sphinx/_static/pugl.svg"), - ("../_static/custom.css", "sphinx/_static/custom.css"), - ("../_templates/about.html", "sphinx/_templates/about.html"), - ("../deployment.rst", "sphinx/deployment.rst"), - ("../pugl.rst", "sphinx/pugl.rst"), - ("index.rst", "sphinx/index.rst"), - ("overview.rst", "sphinx/overview.rst"), - ("reference.rst", "sphinx/reference.rst"), - ] - - # Run Doxygen to generate XML documentation - bld(features="doxygen", doxyfile="Doxyfile") - - # Substitute variables to make Sphinx configuration file - bld(features="subst", - source="../conf.py.in", - target="sphinx/conf.py", - PUGL_VERSION=bld.env.PUGL_VERSION) - - # Copy static documentation files to Sphinx build directory - for f in files: - bld(features="subst", is_copy=True, source=f[0], target=f[1]) - - # Generate Sphinx markup from Doxygen XML - bld.add_group() - bld(rule="${PYTHON} " + dox_to_sphinx.abspath() + " -f ${SRC} ${TGT}", - source=index_xml, - target="sphinx/api/") - - # Run Sphinx to generate HTML documentation - doc_dir = bld.env.DOCDIR + "/pugl-%s/" % bld.env.PUGL_MAJOR_VERSION - bld(features="sphinx", - sphinx_source=bld.path.get_bld().make_node("sphinx"), - sphinx_output_format="singlehtml", - sphinx_options=["-E", "-q"], - install_path=doc_dir + "c/singlehtml/") diff --git a/pugl/doc/c/xml/meson.build b/pugl/doc/c/xml/meson.build new file mode 100644 index 0000000..d79d59a --- /dev/null +++ b/pugl/doc/c/xml/meson.build @@ -0,0 +1,19 @@ +doxygen = find_program('doxygen') + +c_doxygen_input = [] +foreach h : c_headers + c_doxygen_input += ['..' / h] +endforeach + +config = configuration_data() +config.set('PUGL_HEADERS', ' '.join(c_doxygen_input)) +config.set('PUGL_SRCDIR', pugl_src_root) + +c_doxyfile = configure_file(configuration: config, + input: '../Doxyfile.in', + output: 'Doxyfile') + +c_index_xml = custom_target('c-index.xml', + command: [doxygen, '@INPUT0@'], + input: [c_doxyfile] + c_header_files, + output: 'index.xml') diff --git a/pugl/doc/conf.py.in b/pugl/doc/conf.py.in index 9ca7bfa..3fa8ea2 100644 --- a/pugl/doc/conf.py.in +++ b/pugl/doc/conf.py.in @@ -7,20 +7,12 @@ release = "@PUGL_VERSION@" # General configuration +exclude_patterns = ["xml"] language = "en" - -extensions = [ - # 'breathe', - # 'sphinx_rtd_theme', - # 'sphinx.ext.autodoc', - # 'sphinx.ext.doctest', - # 'sphinx.ext.napoleon', - # 'sphinx.ext.viewcode', -] - -# Enable nitpicky mode to get warnings about broken links -# Unfortunately this means we need to explicitly ignore everything external nitpicky = True +pygments_style = "friendly" + +# Ignore everything opaque or external for nitpicky mode _opaque = [ "PFN_vkGetDeviceProcAddr", "PFN_vkGetInstanceProcAddr", @@ -36,53 +28,58 @@ _opaque = [ "uint32_t", "uintptr_t", ] + _c_nitpick_ignore = map(lambda x: ("c:identifier", x), _opaque) _cpp_nitpick_ignore = map(lambda x: ("cpp:identifier", x), _opaque) nitpick_ignore = list(_c_nitpick_ignore) + list(_cpp_nitpick_ignore) -templates_path = ["_templates"] - -pygments_style = "friendly" - # C++ cpp_index_common_prefix = ["pugl::"] # HTML output -exclude_patterns = ["xml"] -html_static_path = ["_static"] - -html_theme = "alabaster" -# html_theme = "sphinx_rtd_theme" +html_copy_source = False +html_short_title = "Pugl" +html_static_path = ["../_static"] +html_theme = "sphinx_lv2_theme" -if html_theme == "alabaster": +if tags.has('singlehtml'): + html_sidebars = { + "**": [ + "globaltoc.html", + ] + } html_theme_options = { + "body_max_width": "51em", + "body_min_width": "51em", "description": "A minimal portable API for embeddable GUIs.", - "donate_url": "http://drobilla.net/pages/donate.html", - # "github_repo": "pugl", - # "github_user": "lv2", + "show_footer_version": True, + "show_logo_version": False, "logo": "pugl.svg", "logo_name": True, - "logo_text_align": "center", - "page_width": "80em - 20em", - "sidebar_width": "20em", - } - - html_sidebars = { - "**": [ - "about.html", - "localtoc.html", - "donate.html", - ] + "logo_width": "8em", + "nosidebar": False, + "page_width": "80em", + "sidebar_width": "16em", + "globaltoc_maxdepth": 3, + "globaltoc_collapse": False, } -elif html_theme == "sphinx_rtd_theme": - +else: html_theme_options = { - "sticky_navigation": False, - "collapse_navigation": False, - "navigation_depth": 4, - "display_version": True, + "body_max_width": "60em", + "body_min_width": "40em", + "description": "A minimal portable API for embeddable GUIs.", + "show_footer_version": True, + "show_logo_version": False, + "logo": "pugl.svg", + "logo_name": True, + "logo_width": "8em", + "nosidebar": True, + "page_width": "60em", + "sidebar_width": "14em", + "globaltoc_maxdepth": 1, + "globaltoc_collapse": True, } diff --git a/pugl/doc/cpp/Doxyfile b/pugl/doc/cpp/Doxyfile.in similarity index 52% rename from pugl/doc/cpp/Doxyfile rename to pugl/doc/cpp/Doxyfile.in index 0f5f636..889ac0b 100644 --- a/pugl/doc/cpp/Doxyfile +++ b/pugl/doc/cpp/Doxyfile.in @@ -2,7 +2,7 @@ PROJECT_NAME = Pugl PROJECT_BRIEF = "A minimal portable API for embeddable GUIs" QUIET = YES -WARN_AS_ERROR = NO +WARN_AS_ERROR = YES WARN_IF_UNDOCUMENTED = NO WARN_NO_PARAMDOC = NO @@ -26,15 +26,7 @@ SHOW_FILES = NO MACRO_EXPANSION = YES PREDEFINED = PUGL_API PUGL_DISABLE_DEPRECATED PUGL_CONST_API= PUGL_CONST_FUNC= -INPUT = ../../include/pugl/cairo.h \ - ../../include/pugl/gl.h \ - ../../include/pugl/pugl.h \ - ../../include/pugl/stub.h \ - ../../include/pugl/vulkan.h \ - ../../bindings/cxx/include/pugl/cairo.hpp \ - ../../bindings/cxx/include/pugl/gl.hpp \ - ../../bindings/cxx/include/pugl/pugl.hpp \ - ../../bindings/cxx/include/pugl/stub.hpp \ - ../../bindings/cxx/include/pugl/vulkan.hpp +STRIP_FROM_PATH = @PUGL_SRCDIR@ +INPUT = @PUGL_HEADERS@ -OUTPUT_DIRECTORY = . +OUTPUT_DIRECTORY = doc/cpp diff --git a/pugl/doc/cpp/api/meson.build b/pugl/doc/cpp/api/meson.build new file mode 100644 index 0000000..4bbbec2 --- /dev/null +++ b/pugl/doc/cpp/api/meson.build @@ -0,0 +1,5 @@ +cpp_pugl_rst = custom_target( + 'C++ API ReST Documentation', + command: [dox_to_sphinx, '-l', 'cpp', '-f', '@INPUT@', 'doc/cpp/api'], + input: cpp_index_xml, + output: 'pugl.rst') diff --git a/pugl/doc/cpp/c-reference.rst b/pugl/doc/cpp/c-reference.rst deleted file mode 100644 index 546e4d3..0000000 --- a/pugl/doc/cpp/c-reference.rst +++ /dev/null @@ -1,20 +0,0 @@ -############### -C API Reference -############### - -This section contains the generated documentation for all symbols in the public -C API. -It is included here because some C++ wrapper definitions refer to the underlying C symbols, -but direct use of the C API should not be necessary in C++ applications. - -.. toctree:: - - api/status - api/world - api/view - api/events - - api/cairo - api/gl - api/stub - api/vulkan diff --git a/pugl/doc/cpp/cpp-reference.rst b/pugl/doc/cpp/cpp-reference.rst deleted file mode 100644 index 96c523c..0000000 --- a/pugl/doc/cpp/cpp-reference.rst +++ /dev/null @@ -1,18 +0,0 @@ -################# -C++ API Reference -################# - -This section contains the generated documentation for all symbols in the public -C++ API. - -.. toctree:: - - api/statusxx - api/worldxx - api/viewxx - api/eventsxx - - api/cairoxx - api/glxx - api/stubxx - api/vulkanxx diff --git a/pugl/doc/cpp/event-loop.rst b/pugl/doc/cpp/event-loop.rst new file mode 100644 index 0000000..1d2ac41 --- /dev/null +++ b/pugl/doc/cpp/event-loop.rst @@ -0,0 +1,37 @@ +.. default-domain:: cpp +.. highlight:: cpp +.. namespace:: pugl + +###################### +Driving the Event Loop +###################### + +Pugl does not contain any threads or other event loop "magic". +For flexibility, the event loop is driven manually by repeatedly calling :func:`World::update`, +which processes events from the window system and dispatches them to views when necessary. + +The exact use of :func:`World::update` depends on the application. +Plugins typically call it with a ``timeout`` of 0 in a callback driven by the host. +This avoids blocking the main loop, +since other plugins and the host itself need to run as well. + +A program can use whatever timeout is appropriate: +event-driven applications may wait forever by using a ``timeout`` of -1, +while those that draw continuously may use a significant fraction of the frame period +(with enough time left over to render). + +********* +Redrawing +********* + +Occasional redrawing can be requested by calling :func:`View::postRedisplay` or :func:`View::postRedisplayRect`. +After these are called, +a :type:`ExposeEvent` will be dispatched on the next call to :func:`World::update`. +Note, however, that this will not wake up a blocked :func:`World::update` call on MacOS +(which does not handle drawing via events). + +For continuous redrawing, +call :func:`View::postRedisplay` while handling a :type:`UpdateEvent`. +This event is sent just before views are redrawn, +so it can be used as a hook to expand the update region right before the view is exposed. +Anything else that needs to be done every frame can be handled similarly. diff --git a/pugl/doc/cpp/events.rst b/pugl/doc/cpp/events.rst new file mode 100644 index 0000000..72c396c --- /dev/null +++ b/pugl/doc/cpp/events.rst @@ -0,0 +1,43 @@ +.. default-domain:: cpp +.. highlight:: cpp +.. namespace:: pugl + +############### +Handling Events +############### + +Events are sent to a view when it has received user input, +must be drawn, or in other situations that may need to be handled such as resizing. + +Events are sent to the ``onEvent`` method that takes the matching event type. +The application must handle at least :type:`ConfigureEvent` +and :type:`ExposeEvent` to draw anything, +but there are many other :type:`event types `. + +For example, basic event handling for our above class might look something like: + +.. code-block:: cpp + + pugl::Status + MyView::onEvent(const pugl::ConfigureEvent& event) noexcept + { + return resize(event.width, event.height); + } + + pugl::Status + MyView::onEvent(const pugl::ExposeEvent& event) noexcept + { + return drawMyAwesomeInterface(event.x, event.y, event.width, event.height); + } + +******* +Drawing +******* + +Note that Pugl uses a different drawing model than many libraries, +particularly those designed for game-style main loops like `SDL `_ and `GLFW `_. + +In that style of code, drawing is performed imperatively in the main loop, +but with Pugl, the application must draw only while handling an expose event. +This is because Pugl supports event-driven applications that only draw the damaged region when necessary, +and handles exposure internally to provide optimized and consistent behavior across platforms. diff --git a/pugl/doc/cpp/index.rst b/pugl/doc/cpp/index.rst index c3d330e..b11d028 100644 --- a/pugl/doc/cpp/index.rst +++ b/pugl/doc/cpp/index.rst @@ -1,7 +1,12 @@ +#### +Pugl +#### + +.. include:: summary.rst + .. toctree:: - pugl deployment overview - cpp-reference - c-reference + api/pugl + api/puglxx diff --git a/pugl/doc/cpp/meson.build b/pugl/doc/cpp/meson.build new file mode 100644 index 0000000..d8bae11 --- /dev/null +++ b/pugl/doc/cpp/meson.build @@ -0,0 +1,43 @@ +config = configuration_data() +config.set('PUGL_VERSION', meson.project_version()) + +conf_py = configure_file(configuration: config, + input: '../conf.py.in', + output: 'conf.py') + +configure_file(copy: true, input: '../deployment.rst', output: 'deployment.rst') +configure_file(copy: true, input: '../summary.rst', output: 'summary.rst') + +cpp_rst_files = files( + 'index.rst', + 'overview.rst', + 'world.rst', + 'view.rst', + 'events.rst', + 'event-loop.rst', +) + +foreach f : cpp_rst_files + configure_file(copy: true, input: f, output: '@PLAINNAME@') +endforeach + +subdir('xml') +subdir('api') + +docs = custom_target( + 'C++ API Documentation (singlehtml)', + command: [sphinx_build, '-M', 'singlehtml', 'doc/cpp/', 'doc/cpp/', '-E', '-q', '-t', 'singlehtml'], + input: [cpp_rst_files, cpp_pugl_rst, cpp_index_xml], + output: 'singlehtml', + build_by_default: true, + install: true, + install_dir: docdir / 'puglxx-0') + +docs = custom_target( + 'C++ API Documentation (html)', + command: [sphinx_build, '-M', 'html', 'doc/cpp/', 'doc/cpp/', '-E', '-q', '-t', 'html'], + input: [cpp_rst_files, cpp_pugl_rst, cpp_index_xml], + output: 'html', + build_by_default: true, + install: true, + install_dir: docdir / 'puglxx-0') diff --git a/pugl/doc/cpp/overview.rst b/pugl/doc/cpp/overview.rst index 5fffe37..1928fba 100644 --- a/pugl/doc/cpp/overview.rst +++ b/pugl/doc/cpp/overview.rst @@ -2,6 +2,10 @@ .. highlight:: cpp .. namespace:: pugl +######## +Overview +######## + Pugl is a C library, but the bindings documented here provide a more idiomatic and type-safe API for C++. If you would rather use C, @@ -23,401 +27,9 @@ The API revolves around two main objects: the `world` and the `view`. An application creates a world to manage top-level state, then creates one or more views to display. -Creating a World -================ - -The world is the top-level object which represents an instance of Pugl. -It handles the connection to the window system, -and manages views and the event loop. - -An application typically has a single world, -which is constructed once on startup and used to drive the main event loop. - -Construction ------------- - -A world must be created before any views, and it must outlive all of its views. -The world constructor requires an argument to specify the application type: - -.. code-block:: cpp - - pugl::World world{pugl::WorldType::program}; - -For a plugin, specify :enumerator:`WorldType::module` instead. -In some cases, it is necessary to pass additional flags. -For example, Vulkan requires thread support: - -.. code-block:: cpp - - pugl::World world{pugl::WorldType::program, pugl::WorldFlag::threads}; - -It is a good idea to set a class name for your project with :func:`World::setClassName`. -This allows the window system to distinguish different applications and, -for example, users to set up rules to manage their windows nicely: - -.. code-block:: cpp - - world.setClassName("MyAwesomeProject"); - -Creating a View -=============== - -A `view` is a drawable region that receives events. -You may think of it as a window, -though it may be embedded and not represent a top-level system window. [#f1]_ - -Pugl communicates with views by dispatching events. -For flexibility, the event handler can be a different object than the view. -This allows using :class:`View` along with a separate event handler class. -Alternatively, a view class can inherit from :class:`View` and set itself as its event handler, -for a more object-oriented style. - -This documentation will use the latter approach, -so we will define a class for our view that contains everything needed: - -.. code-block:: cpp - - class MyView : public pugl::View - { - public: - explicit MyView(pugl::World& world) - : pugl::View{world} - { - setEventHandler(*this); - } - - pugl::Status onEvent(const pugl::ConfigureEvent& event) noexcept; - pugl::Status onEvent(const pugl::ExposeEvent& event) noexcept; - - // With other handlers here as needed... - - // Fallback handler for all other events - template - pugl::Status onEvent(const pugl::Event&) noexcept - { - return pugl::Status::success; - } - - private: - // Some data... - }; - -Pugl will call an ``onEvent`` method of the event handler (the view in this case) for every event. - -Note that Pugl uses a static dispatching mechanism rather than virtual functions to minimize overhead. -It is therefore necessary for the final class to define a handler for every event type. -A terse way to do this without writing every implementation is to define a fallback handler as a template, -as in the example above. -Alternatively, you can define an explicit handler for each event that simply returns :enumerator:`Status::success`. -This way, it will be a compile error if any event is not explicitly handled. - -Configuring the Frame ---------------------- - -Before display, -the necessary :doc:`frame ` and :doc:`window ` attributes should be set. -These allow the window system (or plugin host) to arrange the view properly. - -Derived classes can configure themselves during construction, -but we assume here that configuration is being done outside the view. -For example: - -.. code-block:: cpp - - const double defaultWidth = 1920.0; - const double defaultHeight = 1080.0; - - view.setWindowTitle("My Window"); - view.setDefaultSize(defaultWidth, defaultHeight); - view.setMinSize(defaultWidth / 4.0, defaultHeight / 4.0); - view.setAspectRatio(1, 1, 16, 9); - -There are also several :type:`hints ` for basic attributes that can be set: - -.. code-block:: cpp - - view.setHint(pugl::ViewHint::resizable, true); - view.setHint(pugl::ViewHint::ignoreKeyRepeat, true); - -Embedding ---------- - -To embed the view in another window, -you will need to somehow get the :type:`native view handle ` for the parent, -then set it with :func:`View::setParentWindow`. -If the parent is a Pugl view, -the native handle can be accessed with :func:`View::nativeWindow`. -For example: - -.. code-block:: cpp - - view.setParentWindow(view, parent.getNativeWindow()); - -Setting a Backend ------------------ - -Before being realized, the view must have a backend set with :func:`View::setBackend`. - -The backend manages the graphics API that will be used for drawing. -Pugl includes backends and supporting API for -:doc:`Cairo `, :doc:`OpenGL `, and :doc:`Vulkan `. - -Using Cairo -^^^^^^^^^^^ - -Cairo-specific API is declared in the ``cairo.hpp`` header: - -.. code-block:: cpp - - #include - -The Cairo backend is provided by :func:`cairoBackend()`: - -.. code-block:: cpp - - view.setBackend(pugl::cairoBackend()); - -No additional configuration is required for Cairo. -To draw when handling an expose event, -the `Cairo context `_ can be accessed with :func:`View::context`: - -.. code-block:: cpp - - cairo_t* cr = static_cast(view.context()); - -Using OpenGL -^^^^^^^^^^^^ - -OpenGL-specific API is declared in the ``gl.hpp`` header: - -.. code-block:: cpp - - #include - -The OpenGL backend is provided by :func:`glBackend()`: - -.. code-block:: cpp - - view.setBackend(pugl::glBackend()); - -Some hints must also be set so that the context can be set up correctly. -For example, to use OpenGL 3.3 Core Profile: - -.. code-block:: cpp - - view.setHint(pugl::ViewHint::useCompatProfile, false); - view.setHint(pugl::ViewHint::contextVersionMajor, 3); - view.setHint(pugl::ViewHint::contextVersionMinor, 3); - -If you need to perform some setup using the OpenGL API, -there are two ways to do so. - -The OpenGL context is active when -:type:`CreateEvent` and -:type:`DestroyEvent` -events are dispatched, -so things like creating and destroying shaders and textures can be done then. - -Alternatively, if it is cumbersome to set up and tear down OpenGL in the event handler, -:func:`enterContext` and :func:`leaveContext` can be used to manually activate the OpenGL context during application setup. -Note, however, that unlike many other APIs, these functions must not be used for drawing. -It is only valid to use the OpenGL API for configuration in a manually entered context, -rendering will not work. -For example: - -.. code-block:: cpp - - pugl::enterContext(view); - myApp.setupOpenGL(); - pugl::leaveContext(view); - - while (!myApp.quit()) { - world.update(0.0); - } - - pugl::enterContext(view); - myApp.teardownOpenGL(); - pugl::leaveContext(view); - -Using Vulkan -^^^^^^^^^^^^ - -Vulkan-specific API is declared in the ``vulkan.hpp`` header. -This header includes Vulkan headers, -so if you are dynamically loading Vulkan at runtime, -you should define ``VK_NO_PROTOTYPES`` before including it. - -.. code-block:: cpp - - #define VK_NO_PROTOTYPES - - #include - -The Vulkan backend is provided by :func:`vulkanBackend()`: - -.. code-block:: cpp - - view.setBackend(pugl::vulkanBackend()); - -Unlike OpenGL, almost all Vulkan configuration is done using the Vulkan API directly. -Pugl only provides a portable mechanism to load the Vulkan library and get the functions used to load the rest of the Vulkan API. - -Loading Vulkan -^^^^^^^^^^^^^^ - -For maximum compatibility, -it is best to not link to Vulkan at compile-time, -but instead load the Vulkan API at run-time. -To do so, first create a :class:`VulkanLoader`: - -.. code-block:: cpp - - pugl::VulkanLoader loader{world}; - -The loader manages the dynamically loaded Vulkan library, -so it must be kept alive for as long as the application is using Vulkan. -You can get the function used to load Vulkan functions with :func:`VulkanLoader::getInstanceProcAddrFunc`: - -.. code-block:: cpp - - auto vkGetInstanceProcAddr = loader.getInstanceProcAddrFunc(); - -It is best to use this function to load everything at run time, -rather than link to the Vulkan library at run time. -You can, for example, pass this to get the ``vkCreateInstance`` function using this, -then use that to create your Vulkan instance. -In practice, you will want to use some loader or wrapper API since there are many Vulkan functions. - -It is not necessary to use :class:`VulkanLoader`, -you can, for example, use the ``DynamicLoader`` from ``vulkan.hpp`` in the Vulkan SDK instead. - -The details of using Vulkan are far beyond the scope of this documentation, -but Pugl provides a portable function, :func:`createSurface`, -to get the Vulkan surface for a view. -Assuming you have somehow created your ``VkInstance``, -you can get the surface for a view using :func:`createSurface`: - -.. code-block:: cpp - - VkSurfaceKHR* surface = nullptr; - puglCreateSurface(loader.getDeviceProcAddrFunc(), - view, - vulkanInstance, - nullptr, - &surface); - -Pugl does not provide API that uses ``vulkan.hpp`` to avoid the onerous dependency, -but if you are using it with exceptions and unique handles, -it is straightforward to wrap the surface handle yourself. - -Showing the View ----------------- - -Once the view is configured, it can be "realized" with :func:`View::realize`. -This creates a "real" system view, for example: - -.. code-block:: cpp - - pugl::Status status = view.realize(); - if (status != pugl::Status::success) { - std::cerr << "Error realizing view: " << pugl::strerror(status) << "\n"; - } - -Note that realizing a view can fail for many reasons, -so the return code should always be checked. -This is generally the case for any function that interacts with the window system. -Most functions also return a :enum:`Status`, -but these checks are omitted for brevity in the rest of this documentation. - -A realized view is not initially visible, -but can be shown with :func:`View::show`: - -.. code-block:: cpp - - view.show(); - -To create an initially visible view, -it is also possible to simply call :func:`View::show()` right away. -The view will be automatically realized if necessary. - -Handling Events -=============== - -Events are sent to a view when it has received user input, -must be drawn, or in other situations that may need to be handled such as resizing. - -Events are sent to the ``onEvent`` method that takes the matching event type. -The application must handle at least :type:`ConfigureEvent` -and :type:`ExposeEvent` to draw anything, -but there are many other :type:`event types `. - -For example, basic event handling for our above class might look something like: - -.. code-block:: cpp - - pugl::Status - MyView::onEvent(const pugl::ConfigureEvent& event) noexcept - { - return resize(event.width, event.height); - } - - pugl::Status - MyView::onEvent(const pugl::ExposeEvent& event) noexcept - { - return drawMyAwesomeInterface(event.x, event.y, event.width, event.height); - } - -Drawing -------- - -Note that Pugl uses a different drawing model than many libraries, -particularly those designed for game-style main loops like `SDL `_ and `GLFW `_. - -In that style of code, drawing is performed imperatively in the main loop, -but with Pugl, the application must draw only while handling an expose event. -This is because Pugl supports event-driven applications that only draw the damaged region when necessary, -and handles exposure internally to provide optimized and consistent behavior across platforms. - -Driving the Event Loop -====================== - -Pugl does not contain any threads or other event loop "magic". -For flexibility, the event loop is driven manually by repeatedly calling :func:`World::update`, -which processes events from the window system and dispatches them to views when necessary. - -The exact use of :func:`World::update` depends on the application. -Plugins typically call it with a ``timeout`` of 0 in a callback driven by the host. -This avoids blocking the main loop, -since other plugins and the host itself need to run as well. - -A program can use whatever timeout is appropriate: -event-driven applications may wait forever by using a ``timeout`` of -1, -while those that draw continuously may use a significant fraction of the frame period -(with enough time left over to render). - -Redrawing ---------- - -Occasional redrawing can be requested by calling :func:`View::postRedisplay` or :func:`View::postRedisplayRect`. -After these are called, -a :type:`ExposeEvent` will be dispatched on the next call to :func:`World::update`. -Note, however, that this will not wake up a blocked :func:`World::update` call on MacOS -(which does not handle drawing via events). - -For continuous redrawing, -call :func:`View::postRedisplay` while handling a :type:`UpdateEvent`. -This event is sent just before views are redrawn, -so it can be used as a hook to expand the update region right before the view is exposed. -Anything else that needs to be done every frame can be handled similarly. - -.. _pkg-config: https://www.freedesktop.org/wiki/Software/pkg-config/ - -.. rubric:: Footnotes +.. toctree:: -.. [#f1] MacOS has a strong distinction between - `views `_, - which may be nested, and - `windows `_, - which may not. - On Windows and X11, everything is a nestable window, - but top-level windows are configured differently. + world + view + events + event-loop diff --git a/pugl/doc/cpp/view.rst b/pugl/doc/cpp/view.rst new file mode 100644 index 0000000..3f5aee8 --- /dev/null +++ b/pugl/doc/cpp/view.rst @@ -0,0 +1,299 @@ +.. default-domain:: cpp +.. highlight:: cpp +.. namespace:: pugl + +############### +Creating a View +############### + +A `view` is a drawable region that receives events. +You may think of it as a window, +though it may be embedded and not represent a top-level system window. [#f1]_ + +Pugl communicates with views by dispatching events. +For flexibility, the event handler can be a different object than the view. +This allows using :class:`View` along with a separate event handler class. +Alternatively, a view class can inherit from :class:`View` and set itself as its event handler, +for a more object-oriented style. + +This documentation will use the latter approach, +so we will define a class for our view that contains everything needed: + +.. code-block:: cpp + + class MyView : public pugl::View + { + public: + explicit MyView(pugl::World& world) + : pugl::View{world} + { + setEventHandler(*this); + } + + pugl::Status onEvent(const pugl::ConfigureEvent& event) noexcept; + pugl::Status onEvent(const pugl::ExposeEvent& event) noexcept; + + // With other handlers here as needed... + + // Fallback handler for all other events + template + pugl::Status onEvent(const pugl::Event&) noexcept + { + return pugl::Status::success; + } + + private: + // Some data... + }; + +Pugl will call an ``onEvent`` method of the event handler (the view in this case) for every event. + +Note that Pugl uses a static dispatching mechanism rather than virtual functions to minimize overhead. +It is therefore necessary for the final class to define a handler for every event type. +A terse way to do this without writing every implementation is to define a fallback handler as a template, +as in the example above. +Alternatively, you can define an explicit handler for each event that simply returns :enumerator:`Status::success`. +This way, it will be a compile error if any event is not explicitly handled. + +********************* +Configuring the Frame +********************* + +Before display, +the necessary :doc:`frame ` and :doc:`window ` attributes should be set. +These allow the window system (or plugin host) to arrange the view properly. + +Derived classes can configure themselves during construction, +but we assume here that configuration is being done outside the view. +For example: + +.. code-block:: cpp + + const double defaultWidth = 1920.0; + const double defaultHeight = 1080.0; + + view.setWindowTitle("My Window"); + view.setDefaultSize(defaultWidth, defaultHeight); + view.setMinSize(defaultWidth / 4.0, defaultHeight / 4.0); + view.setAspectRatio(1, 1, 16, 9); + +There are also several :type:`hints ` for basic attributes that can be set: + +.. code-block:: cpp + + view.setHint(pugl::ViewHint::resizable, true); + view.setHint(pugl::ViewHint::ignoreKeyRepeat, true); + +********* +Embedding +********* + +To embed the view in another window, +you will need to somehow get the :type:`native view handle ` for the parent, +then set it with :func:`View::setParentWindow`. +If the parent is a Pugl view, +the native handle can be accessed with :func:`View::nativeWindow`. +For example: + +.. code-block:: cpp + + view.setParentWindow(view, parent.getNativeWindow()); + +***************** +Setting a Backend +***************** + +Before being realized, the view must have a backend set with :func:`View::setBackend`. + +The backend manages the graphics API that will be used for drawing. +Pugl includes backends and supporting API for +:doc:`Cairo `, :doc:`OpenGL `, and :doc:`Vulkan `. + +Using Cairo +=========== + +Cairo-specific API is declared in the ``cairo.hpp`` header: + +.. code-block:: cpp + + #include + +The Cairo backend is provided by :func:`cairoBackend()`: + +.. code-block:: cpp + + view.setBackend(pugl::cairoBackend()); + +No additional configuration is required for Cairo. +To draw when handling an expose event, +the `Cairo context `_ can be accessed with :func:`View::context`: + +.. code-block:: cpp + + cairo_t* cr = static_cast(view.context()); + +Using OpenGL +============ + +OpenGL-specific API is declared in the ``gl.hpp`` header: + +.. code-block:: cpp + + #include + +The OpenGL backend is provided by :func:`glBackend()`: + +.. code-block:: cpp + + view.setBackend(pugl::glBackend()); + +Some hints must also be set so that the context can be set up correctly. +For example, to use OpenGL 3.3 Core Profile: + +.. code-block:: cpp + + view.setHint(pugl::ViewHint::useCompatProfile, false); + view.setHint(pugl::ViewHint::contextVersionMajor, 3); + view.setHint(pugl::ViewHint::contextVersionMinor, 3); + +If you need to perform some setup using the OpenGL API, +there are two ways to do so. + +The OpenGL context is active when +:type:`CreateEvent` and +:type:`DestroyEvent` +events are dispatched, +so things like creating and destroying shaders and textures can be done then. + +Alternatively, if it is cumbersome to set up and tear down OpenGL in the event handler, +:func:`enterContext` and :func:`leaveContext` can be used to manually activate the OpenGL context during application setup. +Note, however, that unlike many other APIs, these functions must not be used for drawing. +It is only valid to use the OpenGL API for configuration in a manually entered context, +rendering will not work. +For example: + +.. code-block:: cpp + + pugl::enterContext(view); + myApp.setupOpenGL(); + pugl::leaveContext(view); + + while (!myApp.quit()) { + world.update(0.0); + } + + pugl::enterContext(view); + myApp.teardownOpenGL(); + pugl::leaveContext(view); + +Using Vulkan +============ + +Vulkan-specific API is declared in the ``vulkan.hpp`` header. +This header includes Vulkan headers, +so if you are dynamically loading Vulkan at runtime, +you should define ``VK_NO_PROTOTYPES`` before including it. + +.. code-block:: cpp + + #define VK_NO_PROTOTYPES + + #include + +The Vulkan backend is provided by :func:`vulkanBackend()`: + +.. code-block:: cpp + + view.setBackend(pugl::vulkanBackend()); + +Unlike OpenGL, almost all Vulkan configuration is done using the Vulkan API directly. +Pugl only provides a portable mechanism to load the Vulkan library and get the functions used to load the rest of the Vulkan API. + +Loading Vulkan +-------------- + +For maximum compatibility, +it is best to not link to Vulkan at compile-time, +but instead load the Vulkan API at run-time. +To do so, first create a :class:`VulkanLoader`: + +.. code-block:: cpp + + pugl::VulkanLoader loader{world}; + +The loader manages the dynamically loaded Vulkan library, +so it must be kept alive for as long as the application is using Vulkan. +You can get the function used to load Vulkan functions with :func:`VulkanLoader::getInstanceProcAddrFunc`: + +.. code-block:: cpp + + auto vkGetInstanceProcAddr = loader.getInstanceProcAddrFunc(); + +It is best to use this function to load everything at run time, +rather than link to the Vulkan library at run time. +You can, for example, pass this to get the ``vkCreateInstance`` function using this, +then use that to create your Vulkan instance. +In practice, you will want to use some loader or wrapper API since there are many Vulkan functions. + +It is not necessary to use :class:`VulkanLoader`, +you can, for example, use the ``DynamicLoader`` from ``vulkan.hpp`` in the Vulkan SDK instead. + +The details of using Vulkan are far beyond the scope of this documentation, +but Pugl provides a portable function, :func:`createSurface`, +to get the Vulkan surface for a view. +Assuming you have somehow created your ``VkInstance``, +you can get the surface for a view using :func:`createSurface`: + +.. code-block:: cpp + + VkSurfaceKHR* surface = nullptr; + puglCreateSurface(loader.getDeviceProcAddrFunc(), + view, + vulkanInstance, + nullptr, + &surface); + +Pugl does not provide API that uses ``vulkan.hpp`` to avoid the onerous dependency, +but if you are using it with exceptions and unique handles, +it is straightforward to wrap the surface handle yourself. + +**************** +Showing the View +**************** + +Once the view is configured, it can be "realized" with :func:`View::realize`. +This creates a "real" system view, for example: + +.. code-block:: cpp + + pugl::Status status = view.realize(); + if (status != pugl::Status::success) { + std::cerr << "Error realizing view: " << pugl::strerror(status) << "\n"; + } + +Note that realizing a view can fail for many reasons, +so the return code should always be checked. +This is generally the case for any function that interacts with the window system. +Most functions also return a :enum:`Status`, +but these checks are omitted for brevity in the rest of this documentation. + +A realized view is not initially visible, +but can be shown with :func:`View::show`: + +.. code-block:: cpp + + view.show(); + +To create an initially visible view, +it is also possible to simply call :func:`View::show()` right away. +The view will be automatically realized if necessary. + +.. rubric:: Footnotes + +.. [#f1] MacOS has a strong distinction between + `views `_, + which may be nested, and + `windows `_, + which may not. + On Windows and X11, everything is a nestable window, + but top-level windows are configured differently. diff --git a/pugl/doc/cpp/world.rst b/pugl/doc/cpp/world.rst new file mode 100644 index 0000000..1a3b432 --- /dev/null +++ b/pugl/doc/cpp/world.rst @@ -0,0 +1,41 @@ +.. default-domain:: cpp +.. highlight:: cpp +.. namespace:: pugl + +################ +Creating a World +################ + +The world is the top-level object which represents an instance of Pugl. +It handles the connection to the window system, +and manages views and the event loop. + +An application typically has a single world, +which is constructed once on startup and used to drive the main event loop. + +************ +Construction +************ + +A world must be created before any views, and it must outlive all of its views. +The world constructor requires an argument to specify the application type: + +.. code-block:: cpp + + pugl::World world{pugl::WorldType::program}; + +For a plugin, specify :enumerator:`WorldType::module` instead. +In some cases, it is necessary to pass additional flags. +For example, Vulkan requires thread support: + +.. code-block:: cpp + + pugl::World world{pugl::WorldType::program, pugl::WorldFlag::threads}; + +It is a good idea to set a class name for your project with :func:`World::setClassName`. +This allows the window system to distinguish different applications and, +for example, users to set up rules to manage their windows nicely: + +.. code-block:: cpp + + world.setClassName("MyAwesomeProject"); diff --git a/pugl/doc/cpp/wscript b/pugl/doc/cpp/wscript deleted file mode 100644 index a7aefc1..0000000 --- a/pugl/doc/cpp/wscript +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python - -def build(bld): - dox_to_sphinx = bld.path.find_node("../../scripts/dox_to_sphinx.py") - index_xml = bld.path.get_bld().make_node("xml/index.xml") - - files = [ - ("../../resources/pugl.svg", "sphinx/_static/pugl.svg"), - ("../_static/custom.css", "sphinx/_static/custom.css"), - ("../_templates/about.html", "sphinx/_templates/about.html"), - ("../deployment.rst", "sphinx/deployment.rst"), - ("../pugl.rst", "sphinx/pugl.rst"), - ("c-reference.rst", "sphinx/c-reference.rst"), - ("cpp-reference.rst", "sphinx/cpp-reference.rst"), - ("index.rst", "sphinx/index.rst"), - ("overview.rst", "sphinx/overview.rst"), - ] - - # Run Doxygen to generate XML documentation - bld(features="doxygen", doxyfile="Doxyfile") - - # Substitute variables to make Sphinx configuration file - bld(features="subst", - source="../conf.py.in", - target="sphinx/conf.py", - PUGL_VERSION=bld.env.PUGL_VERSION) - - # Copy static documentation files to Sphinx build directory - for f in files: - bld(features="subst", is_copy=True, source=f[0], target=f[1]) - - # Generate Sphinx markup from Doxygen XML - bld.add_group() - bld(rule="${PYTHON} " + dox_to_sphinx.abspath() + " -l cpp -f ${SRC} ${TGT}", - source=index_xml, - target="sphinx/api/") - - # Run Sphinx to generate HTML documentation - doc_dir = bld.env.DOCDIR + "/pugl-%s/" % bld.env.PUGL_MAJOR_VERSION - bld(features="sphinx", - sphinx_source=bld.path.get_bld().make_node("sphinx"), - sphinx_output_format="singlehtml", - sphinx_options=["-E", "-q"], - install_path=doc_dir + "cpp/singlehtml/") diff --git a/pugl/doc/cpp/xml/meson.build b/pugl/doc/cpp/xml/meson.build new file mode 100644 index 0000000..3f87f2a --- /dev/null +++ b/pugl/doc/cpp/xml/meson.build @@ -0,0 +1,21 @@ +doxygen = find_program('doxygen') + +cpp_doxygen_input = [] +foreach h : c_headers + cpp_headers + cpp_doxygen_input += ['..' / h] +endforeach + +config = configuration_data() +config.set('PUGL_HEADERS', ' '.join(cpp_doxygen_input)) +config.set('PUGL_SRCDIR', pugl_src_root) + +cpp_doxyfile = configure_file(configuration: config, + input: '../Doxyfile.in', + output: 'Doxyfile') + +cpp_index_xml = custom_target( + 'cpp-index.xml', + command: [doxygen, '@INPUT0@'], + input: [cpp_doxyfile] + c_header_files + cpp_header_files, + output: 'index.xml') + diff --git a/pugl/doc/deployment.rst b/pugl/doc/deployment.rst index ffbeae9..4afc51a 100644 --- a/pugl/doc/deployment.rst +++ b/pugl/doc/deployment.rst @@ -1,13 +1,13 @@ -########## -Using Pugl -########## +##### +Usage +##### -Pugl is designed for flexible deployment, -so the exact method of building against it depends on your approach. +********************* +Building Against Pugl +********************* -When targeting systems with pkg-config_, -packages are provided that link against the core platform library and the desired backend, -along with any backend dependencies: +When Pugl is installed, +pkg-config_ packages are provided that link with the core platform library and desired backend: - ``pugl-cairo-0`` - ``pugl-gl-0`` diff --git a/pugl/doc/mainpage.md b/pugl/doc/mainpage.md deleted file mode 100644 index 92f7409..0000000 --- a/pugl/doc/mainpage.md +++ /dev/null @@ -1,77 +0,0 @@ -This is the documentation for Pugl, a minimal API for writing GUIs. - -## Reference - -Pugl is implemented in C, but also provides a header-only C++ API wrapper. - - * [C API reference](@ref pugl) - * [C++ API reference](@ref puglxx) - -## Overview - -The Pugl API revolves around two main objects: the World and the View. -An application creates a single world to manage top-level state, -then creates one or more views to display. - -### World - -The [World](@ref PuglWorld) contains all top-level state, -and manages views and the event loop. - -A world must be [created](@ref puglNewWorld) before any views, -and it must outlive all views. - -### View - -A [View](@ref PuglView) is a drawable region that receives events. - -Creating a visible view is a multi-step process. -When a new view is [created](@ref puglNewView), -it does not yet represent a real system view or window. -To display, it must first have a [backend](@ref puglSetBackend) -and [event handler](@ref puglSetEventFunc) set, -and be configured by [setting hints](@ref puglSetViewHint) -and optionally [adjusting the frame](@ref frame). - -The [Backend](@ref PuglBackend) controls drawing for a view. -Pugl includes [Cairo](@ref cairo), [OpenGL](@ref gl), and [Vulkan](@ref vulkan) backends, -as well as a [stub](@ref stub) backend that creates a native window with no portable drawing context. - -Once the view is configured, -it can be [realized](@ref puglRealize) and [shown](@ref puglShow). -By default a view will correspond to a top-level system window. -To create a view within another window, -it must have a [parent window set](@ref puglSetParentWindow) before being created. - -### Events - -[Events](@ref PuglEvent) are sent to a view when it has received user input or must be drawn. - -Events are handled by the [event handler](@ref PuglEventFunc) set during initialisation. -This function is called whenever something happens that the view must respond to. -This includes user interaction like mouse and keyboard input, -and system events like window resizing and exposure. - -### Event Loop - -The event loop is driven by repeatedly calling #puglUpdate which processes events from the window system, -and dispatches them to views when necessary. - -Typically, a plugin calls #puglUpdate with timeout 0 in some callback driven by the host. -A program can use whatever timeout is appropriate: -event-driven applications may wait forever, -or for continuous animation, -use a timeout that is a significant fraction of the frame period -(with enough time left over to render). - -Redrawing can be requested by calling #puglPostRedisplay or #puglPostRedisplayRect, -which post expose events to the queue. -Note, however, that this will not wake up a blocked #puglUpdate call on MacOS -(which does not handle drawing via events). -For continuous redrawing, call #puglPostRedisplay when a #PUGL_UPDATE event is received. -This event is sent before views are redrawn, -so can be used as a hook to expand the update region right before the view is exposed. - -### Error Handling - -Most functions return a [Status](@ref PuglStatus) which should be checked to detect failure. diff --git a/pugl/doc/meson.build b/pugl/doc/meson.build new file mode 100644 index 0000000..583f09d --- /dev/null +++ b/pugl/doc/meson.build @@ -0,0 +1,13 @@ +docdir = get_option('datadir') / 'doc' + +doxygen = find_program('doxygen', required: get_option('docs')) +dox_to_sphinx = find_program('../scripts/dox_to_sphinx.py') +sphinx_build = find_program('sphinx-build', required: get_option('docs')) + +build_docs = doxygen.found() and sphinx_build.found() + +if build_docs + subdir('_static') + subdir('c') + subdir('cpp') +endif diff --git a/pugl/doc/pugl.rst b/pugl/doc/summary.rst similarity index 98% rename from pugl/doc/pugl.rst rename to pugl/doc/summary.rst index c48021b..f05515f 100644 --- a/pugl/doc/pugl.rst +++ b/pugl/doc/summary.rst @@ -1,7 +1,3 @@ -#### -Pugl -#### - Pugl is an API for writing portable and embeddable GUIs. Pugl is not a toolkit or framework, but a minimal portability layer that sets up a drawing context and delivers events. diff --git a/pugl/examples/meson.build b/pugl/examples/meson.build new file mode 100644 index 0000000..d455faf --- /dev/null +++ b/pugl/examples/meson.build @@ -0,0 +1,80 @@ +data_dir = get_option('prefix') / get_option('datadir') / 'pugl-0' +example_args = ['-DPUGL_DATA_DIR="@0@"'.format(data_dir)] + +gl_examples = [ + 'pugl_cxx_demo.cpp', + 'pugl_embed_demo.c', + 'pugl_print_events.c', + 'pugl_shader_demo.c', + 'pugl_window_demo.c', +] + +cairo_examples = [ + 'pugl_cairo_demo.c' +] + +vulkan_examples = [ + 'pugl_vulkan_cxx_demo.cpp', + 'pugl_vulkan_demo.c', +] + +includes = [ + '.', + '..', + '../bindings/cxx/include', + '../include', +] + +subdir('shaders') + +# Build GL examples +if opengl_dep.found() + foreach example : gl_examples + source = [example] + target = example.split('.')[0] + dependencies = [gl_backend_dep] + + if target == 'pugl_shader_demo' + source += ['file_utils.c', 'glad/glad.c'] + dependencies += [dl_dep] + elif target == 'pugl_print_events' + dependencies += [stub_backend_dep] + endif + + executable(target, source, + include_directories: include_directories(includes), + c_args: example_args, + cpp_args: example_args, + dependencies: dependencies) + endforeach +endif + +# Build Cairo examples +if cairo_dep.found() + foreach example : cairo_examples + target = example.split('.')[0] + executable(target, example, + include_directories: include_directories(includes), + c_args: example_args, + dependencies: [pugl_dep, cairo_backend_dep]) + endforeach +endif + +# Build Vulkan examples +if vulkan_dep.found() + foreach example : vulkan_examples + source = [example] + target = example.split('.')[0] + dependencies = [dl_dep, vulkan_backend_dep] + + if target == 'pugl_vulkan_cxx_demo' + source += ['file_utils.c'] + endif + + executable(target, source, + include_directories: include_directories(includes), + c_args: example_args, + cpp_args: example_args, + dependencies: dependencies) + endforeach +endif diff --git a/pugl/examples/pugl_cairo_demo.c b/pugl/examples/pugl_cairo_demo.c index 4da0caf..67bc13c 100644 --- a/pugl/examples/pugl_cairo_demo.c +++ b/pugl/examples/pugl_cairo_demo.c @@ -231,6 +231,7 @@ main(int argc, char** argv) puglSetWindowTitle(view, "Pugl Cairo Demo"); puglSetDefaultSize(view, 512, 512); puglSetMinSize(view, 256, 256); + puglSetMaxSize(view, 2048, 2048); puglSetViewHint(view, PUGL_RESIZABLE, app.opts.resizable); puglSetHandle(view, &app); puglSetBackend(view, puglCairoBackend()); diff --git a/pugl/examples/pugl_cursor_demo.c b/pugl/examples/pugl_cursor_demo.c index 60ec3d3..97e3b9f 100644 --- a/pugl/examples/pugl_cursor_demo.c +++ b/pugl/examples/pugl_cursor_demo.c @@ -139,6 +139,7 @@ main(int argc, char** argv) puglSetWindowTitle(view, "Pugl Window Demo"); puglSetDefaultSize(view, 512, 256); puglSetMinSize(view, 128, 64); + puglSetMaxSize(view, 512, 256); puglSetBackend(view, puglGlBackend()); puglSetViewHint(view, PUGL_USE_DEBUG_CONTEXT, app.opts.errorChecking); diff --git a/pugl/examples/pugl_cxx_demo.cpp b/pugl/examples/pugl_cxx_demo.cpp index 4914724..d663a3f 100644 --- a/pugl/examples/pugl_cxx_demo.cpp +++ b/pugl/examples/pugl_cxx_demo.cpp @@ -125,6 +125,7 @@ main(int argc, char** argv) view.setWindowTitle("Pugl C++ Test"); view.setDefaultSize(512, 512); view.setMinSize(64, 64); + view.setMaxSize(256, 256); view.setAspectRatio(1, 1, 16, 9); view.setBackend(pugl::glBackend()); view.setHint(pugl::ViewHint::resizable, opts.resizable); diff --git a/pugl/examples/pugl_shader_demo.c b/pugl/examples/pugl_shader_demo.c index 087afc5..aa5c38e 100644 --- a/pugl/examples/pugl_shader_demo.c +++ b/pugl/examples/pugl_shader_demo.c @@ -275,6 +275,7 @@ setupPugl(PuglTestApp* app) puglSetWindowTitle(app->view, "Pugl OpenGL 3"); puglSetDefaultSize(app->view, defaultWidth, defaultHeight); puglSetMinSize(app->view, defaultWidth / 4, defaultHeight / 4); + puglSetMaxSize(app->view, defaultWidth * 4, defaultHeight * 4); puglSetAspectRatio(app->view, 1, 1, 16, 9); puglSetBackend(app->view, puglGlBackend()); puglSetViewHint(app->view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE); diff --git a/pugl/examples/pugl_vulkan_cxx_demo.cpp b/pugl/examples/pugl_vulkan_cxx_demo.cpp index e816091..d92e652 100644 --- a/pugl/examples/pugl_vulkan_cxx_demo.cpp +++ b/pugl/examples/pugl_vulkan_cxx_demo.cpp @@ -1722,6 +1722,7 @@ run(const char* const programPath, app.view.setAspectRatio(1, 1, 16, 9); app.view.setDefaultSize(width, height); app.view.setMinSize(width / 4, height / 4); + app.view.setMaxSize(width * 4, height * 4); app.view.setBackend(pugl::vulkanBackend()); app.view.setHint(pugl::ViewHint::resizable, opts.resizable); const pugl::Status st = app.view.realize(); diff --git a/pugl/examples/pugl_window_demo.c b/pugl/examples/pugl_window_demo.c index be320dd..f7d5b2c 100644 --- a/pugl/examples/pugl_window_demo.c +++ b/pugl/examples/pugl_window_demo.c @@ -209,6 +209,7 @@ main(int argc, char** argv) puglSetFrame(view, frame); puglSetDefaultSize(view, 512, 512); puglSetMinSize(view, 128, 128); + puglSetMaxSize(view, 2048, 2048); puglSetBackend(view, puglGlBackend()); puglSetViewHint(view, PUGL_USE_DEBUG_CONTEXT, opts.errorChecking); diff --git a/pugl/examples/shaders/meson.build b/pugl/examples/shaders/meson.build new file mode 100644 index 0000000..e47be9d --- /dev/null +++ b/pugl/examples/shaders/meson.build @@ -0,0 +1,35 @@ +shader_files = [ + 'header_330.glsl', + 'header_420.glsl', + 'rect.frag', + 'rect.vert', +] + +# Copy shader sources for GL examples +foreach shader_file : shader_files + configure_file(copy: true, input: shader_file, output: shader_file) +endforeach + +# Build SPV shader binaries for Vulkan examples +if vulkan_dep.found() + cat = find_program('../../scripts/cat.py') + glslang = find_program('glslangValidator') + + shaders = ['rect.vert', 'rect.frag'] + foreach shader : shaders + source = shader.split('.')[0] + '.vulkan.' + shader.split('.')[1] + shader_input = custom_target(source, + output: source, + input: ['header_420.glsl', shader], + command: [cat, '@INPUT@'], + build_by_default: true, + capture: true) + + mytarget = custom_target(shader, + output: shader + '.spv', + input: shader_input, + command: [glslang, '-V', '-o', '@OUTPUT@', '@INPUT@'], + build_by_default: true, + install: false) + endforeach +endif diff --git a/pugl/include/pugl/pugl.h b/pugl/include/pugl/pugl.h index 8a261c7..cd77334 100644 --- a/pugl/include/pugl/pugl.h +++ b/pugl/include/pugl/pugl.h @@ -190,13 +190,6 @@ typedef enum { PUGL_TIMER, ///< Timer triggered, a #PuglEventTimer PUGL_LOOP_ENTER, ///< Recursive loop entered, a #PuglEventLoopEnter PUGL_LOOP_LEAVE, ///< Recursive loop left, a #PuglEventLoopLeave - -#ifndef PUGL_DISABLE_DEPRECATED - PUGL_ENTER_NOTIFY PUGL_DEPRECATED_BY("PUGL_POINTER_IN") = PUGL_POINTER_IN, - PUGL_LEAVE_NOTIFY PUGL_DEPRECATED_BY("PUGL_POINTER_OUT") = PUGL_POINTER_OUT, - PUGL_MOTION_NOTIFY PUGL_DEPRECATED_BY("PUGL_MOTION") = PUGL_MOTION, -#endif - } PuglEventType; /// Common flags for all event types @@ -728,7 +721,8 @@ puglGetTime(const PuglWorld* world); of the ideal frame period should be used, to minimize input latency by ensuring that as many input events are consumed as possible before drawing. - @return #PUGL_SUCCESS if events are read, #PUGL_FAILURE if not, or an error. + @return #PUGL_SUCCESS if events are read, #PUGL_FAILURE if no events are + read, or an error. */ PUGL_API PuglStatus @@ -1087,7 +1081,7 @@ puglGetNativeWindow(PuglView* view); API requires one. It is only available during an expose. Cairo: Returns a pointer to a - [`cairo_t`](http://www.cairographics.org/manual/cairo-cairo-t.html). + [cairo_t](http://www.cairographics.org/manual/cairo-cairo-t.html). All other backends: returns null. */ @@ -1178,7 +1172,7 @@ puglSetClipboard(PuglView* view, @param view The view. @param[out] type Set to the MIME type of the data. @param[out] len Set to the length of the data in bytes. - @return The clipboard contents, or `NULL`. + @return The clipboard contents, or null. */ PUGL_API const void* @@ -1191,9 +1185,8 @@ puglGetClipboard(PuglView* view, const char** type, size_t* len); the view. May fail if setting the cursor is not supported on this system, for example if compiled on X11 without Xcursor support. - Errors: - - #PUGL_BAD_PARAMETER if the given cursor is invalid. - - #PUGL_FAILURE if the cursor isknown but loading it from the system fails. + @return #PUGL_BAD_PARAMETER if the given cursor is invalid, + #PUGL_FAILURE if the cursor is known but loading it system fails. */ PUGL_API PuglStatus @@ -1233,9 +1226,8 @@ puglRequestAttention(PuglView* view); resolution on Windows) and may be rounded up if it is too short. On X11 and MacOS, a resolution of about 1ms can usually be relied on. - Errors: - - #PUGL_FAILURE if timers are not supported by this system or build. - - #PUGL_UNKNOWN_ERROR if setting the timer failed. + @return #PUGL_FAILURE if timers are not supported by the system, + #PUGL_UNKNOWN_ERROR if setting the timer failed. */ PUGL_API PuglStatus @@ -1247,9 +1239,8 @@ puglStartTimer(PuglView* view, uintptr_t id, double timeout); @param view The view that the timer is set for. @param id The ID previously passed to puglStartTimer(). - Errors: - - #PUGL_FAILURE if timers are not supported by this system or build. - - #PUGL_UNKNOWN_ERROR if stopping the timer failed. + @return #PUGL_FAILURE if timers are not supported by this system, + #PUGL_UNKNOWN_ERROR if stopping the timer failed. */ PUGL_API PuglStatus @@ -1268,9 +1259,8 @@ puglStopTimer(PuglView* view, uintptr_t id); puglPostRedisplayRect(), but will always send a message to the X server, even when called in an event handler. - Errors: - - #PUGL_UNSUPPORTED_TYPE if sending events of this type is not supported. - - #PUGL_UNKNOWN_ERROR if sending the event failed. + @return #PUGL_UNSUPPORTED_TYPE if sending events of this type is not supported, + #PUGL_UNKNOWN_ERROR if sending the event failed. */ PUGL_API PuglStatus diff --git a/pugl/meson.build b/pugl/meson.build new file mode 100644 index 0000000..02fae17 --- /dev/null +++ b/pugl/meson.build @@ -0,0 +1,455 @@ +project('pugl', ['c'], + version: '0.3.0', + license: 'ISC', + meson_version: '>= 0.49.2', + default_options: [ + 'c_std=c99', + 'cpp_std=c++11', + 'default_library=shared' + ]) + +pugl_src_root = meson.current_source_dir() +major_version = meson.project_version().split('.')[0] +version_suffix = '-@0@'.format(major_version) +versioned_name = 'pugl' + version_suffix + +# Load build tools +pkg = import('pkgconfig') +cc = meson.get_compiler('c') + +# Enable C++ support if we're building the examples +if get_option('examples') + add_languages(['cpp']) + cpp = meson.get_compiler('cpp') +endif + +# Enable Objective C support if we're building for MacOS +if host_machine.system() == 'darwin' + add_languages(['objc']) + objcc = meson.get_compiler('objc') +endif + +# Set ultra strict warnings for developers, if requested +if get_option('strict') + subdir('meson') + + # C warnings + c_warnings = all_c_warnings + if cc.get_id() == 'clang' + c_warnings += [ + '-Wno-bad-function-cast', + '-Wno-documentation', # Cairo + '-Wno-documentation-unknown-command', # Cairo + '-Wno-float-equal', + '-Wno-implicit-fallthrough', + '-Wno-padded', + '-Wno-reserved-id-macro', + '-Wno-switch-default', + '-Wno-switch-enum', + '-Wno-unused-macros', # Mac + ] + elif cc.get_id() == 'gcc' + c_warnings += [ + '-Wno-bad-function-cast', + '-Wno-float-equal', + '-Wno-inline', + '-Wno-padded', + '-Wno-pedantic', + '-Wno-suggest-attribute=const', + '-Wno-suggest-attribute=malloc', + '-Wno-suggest-attribute=pure', + '-Wno-switch-default', + '-Wno-switch-enum', + '-Wno-unsuffixed-float-constants', + ] + elif cc.get_id() == 'msvc' + c_warnings += [ + '/wd4028', # formal parameter different from declaration + '/wd4061', # enumerator in switch is not explicitly handled + '/wd4191', # unsafe conversion from type to type + '/wd4514', # unreferenced inline function has been removed + '/wd4706', # assignment within conditional expression + '/wd4710', # function not inlined + '/wd4711', # function selected for automatic inline expansion + '/wd4800', # implicit conversion from int to bool + '/wd4820', # padding added after construct + '/wd4996', # function or variable may be unsafe + '/wd5045', # will insert Spectre mitigation for memory load + ] + endif + + add_project_arguments(cc.get_supported_arguments(c_warnings), + language: ['c', 'objc']) + + # C++ warnings + cpp_warnings = all_cpp_warnings + if is_variable('cpp') + if cpp.get_id() == 'clang' + cpp_warnings += [ + '-Wno-documentation-unknown-command', # Cairo + '-Wno-old-style-cast', + '-Wno-padded', + '-Wno-reserved-id-macro', + '-Wno-switch-enum', + '-Wno-unused-macros', # Mac + ] + elif cpp.get_id() == 'gcc' + cpp_warnings += [ + '-Wno-effc++', + '-Wno-inline', + '-Wno-old-style-cast', + '-Wno-padded', + '-Wno-suggest-attribute=const', + '-Wno-suggest-attribute=malloc', + '-Wno-suggest-attribute=pure', + '-Wno-suggest-final-methods', + '-Wno-switch-default', + '-Wno-switch-enum', + '-Wno-unused-const-variable', + '-Wno-useless-cast', + ] + elif cpp.get_id() == 'msvc' + cpp_warnings += [ + '/wd4061', # enumerator in switch is not explicitly handled + '/wd4191', # unsafe conversion from type to type + '/wd4355', # 'this' used in base member initializer list + '/wd4514', # unreferenced inline function has been removed + '/wd4571', # structured exceptions (SEH) are no longer caught + '/wd4625', # copy constructor implicitly deleted + '/wd4626', # assignment operator implicitly deleted + '/wd4706', # assignment within conditional expression + '/wd4710', # function not inlined + '/wd4711', # function selected for automatic inline expansion + '/wd4800', # implicit conversion from int to bool + '/wd4820', # padding added after construct + '/wd4868', # compiler may not enforce left-to-right evaluation order + '/wd4996', # function or variable may be unsafe + '/wd5026', # move constructor implicitly deleted + '/wd5027', # move assignment operator implicitly deleted + '/wd5039', # potentially throwing function passed to C + '/wd5045', # will insert Spectre mitigation for memory load + ] + endif + + add_project_arguments(cpp.get_supported_arguments(cpp_warnings), + language: ['cpp']) + endif + + # Objective C warnings + if is_variable('objcc') + add_project_arguments(objcc.get_supported_arguments(all_objc_warnings), + language: ['objc']) + endif +endif + +# Disable deprecated API which is not used by tests or examples +add_project_arguments(['-DPUGL_DISABLE_DEPRECATED'], + language: ['c', 'cpp', 'objc']) + +c_headers = [ + 'include/pugl/pugl.h', + + 'include/pugl/cairo.h', + 'include/pugl/gl.h', + 'include/pugl/stub.h', + 'include/pugl/vulkan.h', +] + +c_header_files = files(c_headers) + +cpp_headers = [ + 'bindings/cxx/include/pugl/pugl.hpp', + + 'bindings/cxx/include/pugl/cairo.hpp', + 'bindings/cxx/include/pugl/gl.hpp', + 'bindings/cxx/include/pugl/stub.hpp', + 'bindings/cxx/include/pugl/vulkan.hpp', +] + +cpp_header_files = files(cpp_headers) + +core_sources = [ + 'src/implementation.c' +] + +# System libraries +m_dep = cc.find_library('m', required: false) +dl_dep = cc.find_library('dl', required: false) +thread_dep = dependency('threads') + +# Cairo (optional backend) +cairo_dep = dependency('cairo', + required: get_option('cairo')) + +# OpenGL (optional backend) +opengl_dep = dependency('GL', + required: get_option('opengl')) + +# Vulkan (optional backend) +vulkan_dep = dependency('vulkan', + required: get_option('vulkan')) + +core_args = [] + +# MacOS +if host_machine.system() == 'darwin' + cocoa_dep = dependency('Cocoa', required: false, modules: 'foundation') + corevideo_dep = dependency('CoreVideo', required: false) + + platform = 'mac' + platform_sources = ['src/mac.m', 'src/mac_stub.m'] + core_deps = [cocoa_dep, corevideo_dep] + extension = '.m' + + add_project_arguments(['-Wno-deprecated-declarations'], language: ['objc']) + add_project_arguments(['-Wno-direct-ivar-access'], language: ['objc']) + + add_project_arguments(['-DGL_SILENCE_DEPRECATION'], + language: ['c', 'objc']) + + add_project_link_arguments(['-Wl,-framework,Cocoa'], + language: ['c', 'objc']) + +# Windows +elif host_machine.system() == 'windows' + if cpp.get_id() == 'msvc' + msvc_args = [ + '/TP', + '/experimental:external', + '/external:W0', + '/external:anglebrackets', + ] + + add_project_arguments(msvc_args, language: ['c', 'cpp']) + endif + + win_args = [ + '-DWIN32_LEAN_AND_MEAN', + '-D_CRT_SECURE_NO_WARNINGS', + ] + + add_project_arguments(win_args, language: ['c', 'cpp']) + + platform = 'win' + platform_sources = ['src/win.c'] + core_deps = [] + extension = '.c' + +else # X11 + x11_dep = cc.find_library('X11') + + xcursor_dep = cc.find_library('Xcursor', required: false) + if xcursor_dep.found() + core_args += ['-DHAVE_XCURSOR'] + endif + + xrandr_dep = cc.find_library('Xrandr', required: false) + if xrandr_dep.found() + core_args += ['-DHAVE_XRANDR'] + endif + + xext_dep = cc.find_library('Xext', required: false) + if xext_dep.found() + xsync_fragment = '''#include + #include + int main(void) { XSyncQueryExtension(0, 0, 0); return 0; }''' + if cc.compiles(xsync_fragment, name: 'Xsync') + core_args += ['-DHAVE_XSYNC'] + endif + endif + + platform = 'x11' + platform_sources = ['src/x11.c'] + core_deps = [x11_dep, xcursor_dep, xrandr_dep, xext_dep] + extension = '.c' +endif + +# Build core library + +core_deps += [m_dep] +core_sources += platform_sources +core_name = 'pugl_@0@@1@'.format(platform, version_suffix) + +library_args = ['-DPUGL_INTERNAL'] +if get_option('default_library') == 'both' + if host_machine.system() == 'windows' + error('default_library=both is not supported on Windows') + endif + + library_type = 'both_libraries' +elif get_option('default_library') == 'shared' + library_type = 'shared_library' +else + library_type = 'static_library' + add_project_arguments(['-DPUGL_STATIC'], language: ['c', 'cpp', 'objc']) +endif + +libpugl = build_target( + core_name, core_sources, + version: meson.project_version(), + include_directories: include_directories(['include']), + c_args: library_args + core_args, + dependencies: core_deps, + gnu_symbol_visibility: 'hidden', + install: true, + target_type: library_type) + +pugl_dep = declare_dependency(link_with: libpugl, dependencies: core_deps) + +pkg.generate(libpugl, + name: 'Pugl', + filebase: versioned_name, + subdirs: [versioned_name], + version: meson.project_version(), + description: 'Pugl GUI library core') + +# Build stub backend + +name = 'pugl_' + platform + '_stub' + version_suffix +sources = 'src/' + platform + '_stub' + extension + +stub_backend = build_target( + name, sources, + version: meson.project_version(), + include_directories: include_directories(['include']), + c_args: library_args, + dependencies: [pugl_dep], + gnu_symbol_visibility: 'hidden', + install: true, + target_type: library_type) + +stub_backend_dep = declare_dependency(link_with: stub_backend) + +pkg.generate(stub_backend, + name: 'Pugl Stub', + filebase: 'pugl-stub-@0@'.format(major_version), + subdirs: [name], + version: meson.project_version(), + description: 'Native window pugl graphics backend') + +# Build GL backend +if opengl_dep.found() + name = 'pugl_' + platform + '_gl' + version_suffix + sources = 'src/' + platform + '_gl' + extension + + gl_backend = build_target( + name, sources, + version: meson.project_version(), + include_directories: include_directories(['include']), + c_args: library_args, + dependencies: [pugl_dep, opengl_dep], + gnu_symbol_visibility: 'hidden', + install: true, + target_type: library_type) + + gl_backend_dep = declare_dependency(link_with: gl_backend, + dependencies: [pugl_dep, opengl_dep]) + + pkg.generate(gl_backend, + name: 'Pugl OpenGL', + filebase: 'pugl-gl-@0@'.format(major_version), + subdirs: [name], + version: meson.project_version(), + description: 'Pugl GUI library with OpenGL backend') +endif + +# Build Cairo backend +if cairo_dep.found() + name = 'pugl_' + platform + '_cairo' + version_suffix + sources = ['src/' + platform + '_cairo' + extension, + 'src/' + platform + '_stub' + extension] + + cairo_backend = build_target( + name, sources, + version: meson.project_version(), + include_directories: include_directories(['include']), + c_args: library_args, + dependencies: [pugl_dep, cairo_dep, stub_backend_dep], + gnu_symbol_visibility: 'hidden', + install: true, + target_type: library_type) + + cairo_backend_dep = declare_dependency( + link_with: cairo_backend, + dependencies: [pugl_dep, cairo_dep, stub_backend_dep]) + + pkg.generate(cairo_backend, + name: 'Pugl Cairo', + filebase: 'pugl-cairo-@0@'.format(major_version), + subdirs: [name], + version: meson.project_version(), + description: 'Pugl GUI library with Cairo backend') +endif + +# Build Vulkan backend +if vulkan_dep.found() + name = 'pugl_' + platform + '_vulkan' + version_suffix + sources = ['src/' + platform + '_vulkan' + extension, + 'src/' + platform + '_stub' + extension] + + vulkan_deps = [pugl_dep, vulkan_dep, dl_dep] + vulkan_c_args = library_args + vulkan_link_args = [] + if platform == 'mac' + metal_dep = dependency('Metal', modules: 'foundation') + quartzcore_dep = dependency('QuartzCore', modules: 'foundation') + + vulkan_deps += [metal_dep, quartzcore_dep] + endif + + vulkan_backend = build_target( + name, sources, + version: meson.project_version(), + include_directories: include_directories(['include']), + c_args: library_args, + dependencies: vulkan_deps, + gnu_symbol_visibility: 'hidden', + install: true, + target_type: library_type) + + vulkan_backend_dep = declare_dependency( + link_with: vulkan_backend, + dependencies: [pugl_dep, vulkan_dep, thread_dep]) + + pkg.generate(vulkan_backend, + name: 'Pugl Vulkan', + filebase: 'pugl-vulkan-@0@'.format(major_version), + subdirs: [name], + version: meson.project_version(), + description: 'Pugl GUI library with Vulkan backend') +endif + +install_headers(c_headers, subdir: versioned_name / 'pugl') +install_headers(cpp_headers, subdir: 'puglxx' + version_suffix) + +if not get_option('docs').disabled() + subdir('doc') +else + build_docs = false +endif + +if get_option('examples') + subdir('examples') +endif + +if get_option('tests') + subdir('test') +endif + +if meson.version().version_compare('>=0.53.0') + summary('Platform', platform) + summary('Cairo backend', cairo_dep.found(), bool_yn: true) + summary('OpenGL backend', opengl_dep.found(), bool_yn: true) + summary('Vulkan backend', vulkan_dep.found(), bool_yn: true) + summary('Tests', get_option('tests'), bool_yn: true) + summary('Examples', get_option('examples'), bool_yn: true) + summary('Documentation', build_docs, bool_yn: true) + + summary('Install prefix', get_option('prefix')) + summary('Headers', get_option('prefix') / get_option('includedir')) + summary('Libraries', get_option('prefix') / get_option('libdir')) + + if get_option('examples') + summary('Executables', get_option('prefix') / get_option('bindir')) + endif +endif diff --git a/pugl/meson/meson.build b/pugl/meson/meson.build new file mode 100644 index 0000000..20e0522 --- /dev/null +++ b/pugl/meson/meson.build @@ -0,0 +1,196 @@ +# General code to enable approximately all warnings. +# +# This is trivial for clang and MSVC, but GCC does not have such an option, and +# has several esoteric warnings, so we need to enable everything we want +# explicitly. We enable everything that does not require a value argument, +# except for warnings that are only relevant for very old languages (earlier +# than C99 or C++11) or non-standard extensions. +# +# Omitted common warnings: +# +# Wabi= +# Waggregate-return +# Walloc-size-larger-than=BYTES +# Walloca-larger-than=BYTES +# Wframe-larger-than=BYTES +# Wlarger-than=BYTES +# Wstack-usage=BYTES +# Wsystem-headers +# Wtraditional +# Wtraditional-conversion +# Wtrampolines +# Wvla-larger-than=BYTES +# +# Omitted C warnings: +# +# Wc90-c99-compat +# Wdeclaration-after-statement +# Wtraditional +# Wtraditional-conversion +# +# Omitted C++ warnings: +# +# Wnamespaces +# Wtemplates + +gcc_common_warnings = [ + '-Walloc-zero', + '-Walloca', + '-Wanalyzer-too-complex', + '-Warith-conversion', + '-Warray-bounds=2', + '-Wattribute-alias=2', + '-Wcast-align=strict', + '-Wcast-qual', + '-Wconversion', + '-Wdate-time', + '-Wdisabled-optimization', + '-Wdouble-promotion', + '-Wduplicated-branches', + '-Wduplicated-cond', + '-Wfloat-equal', + '-Wformat-overflow=2', + '-Wformat-signedness', + '-Wformat-truncation=2', + '-Wformat=2', + '-Wimplicit-fallthrough=2', + '-Winit-self', + '-Winline', + '-Winvalid-pch', + '-Wlogical-op', + '-Wmissing-declarations', + '-Wmissing-include-dirs', + '-Wmultichar', + '-Wnormalized=nfc', + '-Wnull-dereference', + '-Wpacked', + '-Wpadded', + '-Wredundant-decls', + '-Wscalar-storage-order', + '-Wshadow', + '-Wshift-overflow=2', + '-Wsizeof-array-argument', + '-Wstack-protector', + '-Wstrict-aliasing=3', + '-Wstrict-overflow=5', + '-Wstringop-overflow=3', + '-Wsuggest-attribute=cold', + '-Wsuggest-attribute=const', + '-Wsuggest-attribute=format', + '-Wsuggest-attribute=malloc', + '-Wsuggest-attribute=noreturn', + '-Wsuggest-attribute=pure', + '-Wswitch-default', + '-Wswitch-enum', + '-Wsync-nand', + '-Wundef', + '-Wunused-const-variable=2', + '-Wunused-macros', + '-Wvarargs', + '-Wvector-operation-performance', + '-Wvla', + '-Wwrite-strings', +] + +gcc_c_warnings = [ + '-Wbad-function-cast', + '-Wc++-compat', + '-Wc99-c11-compat', + '-Wdesignated-init', + '-Wdiscarded-array-qualifiers', + '-Wdiscarded-qualifiers', + '-Wincompatible-pointer-types', + '-Wjump-misses-init', + '-Wmissing-prototypes', + '-Wnested-externs', + '-Wold-style-definition', + '-Wstrict-prototypes', + '-Wunsuffixed-float-constants', +] + +# Set all_c_warnings for the current C compiler +if is_variable('cc') + if cc.get_id() == 'clang' + all_c_warnings = ['-Weverything'] + elif cc.get_id() == 'gcc' + all_c_warnings = gcc_common_warnings + [ + '-Wbad-function-cast', + '-Wc++-compat', + '-Wc99-c11-compat', + '-Wdesignated-init', + '-Wdiscarded-array-qualifiers', + '-Wdiscarded-qualifiers', + '-Wincompatible-pointer-types', + '-Wjump-misses-init', + '-Wmissing-prototypes', + '-Wnested-externs', + '-Wold-style-definition', + '-Wstrict-prototypes', + '-Wunsuffixed-float-constants', + ] + elif cc.get_id() == 'msvc' + all_c_warnings = ['/Wall'] + else + all_c_warnings = [] + endif +endif + +# Set all_cpp_warnings for the current C++ compiler +if is_variable('cpp') + if cpp.get_id() == 'clang' + all_cpp_warnings = [ + '-Weverything', + '-Wno-c++98-compat', + '-Wno-c++98-compat-pedantic' + ] + elif cpp.get_id() == 'gcc' + all_cpp_warnings = gcc_common_warnings + [ + '-Wabi-tag', + '-Waligned-new=all', + '-Wcatch-value=3', + '-Wcomma-subscript', + '-Wconditionally-supported', + '-Wctor-dtor-privacy', + '-Wdeprecated-copy-dtor', + '-Weffc++', + '-Wextra-semi', + '-Wmismatched-tags', + '-Wmultiple-inheritance', + '-Wnoexcept', + '-Wnoexcept-type', + '-Wnon-virtual-dtor', + '-Wold-style-cast', + '-Woverloaded-virtual', + '-Wplacement-new=2', + '-Wredundant-tags', + '-Wregister', + '-Wsign-promo', + '-Wstrict-null-sentinel', + '-Wsuggest-final-methods', + '-Wsuggest-final-types', + '-Wsuggest-override', + '-Wuseless-cast', + '-Wvirtual-inheritance', + '-Wvolatile', + '-Wzero-as-null-pointer-constant', + ] + elif cpp.get_id() == 'msvc' + all_cpp_warnings = ['/Wall'] + else + all_cpp_warnings = [] + endif +endif + +# Set all_objc_warnings for the current Objective C compiler +if is_variable('objcc') + all_objc_warnings = [] + if objcc.get_id() == 'clang' + all_objc_warnings = ['-Weverything'] + elif objc.get_id() == 'gcc' + all_objc_warnings = gcc_common_warnings + [ + '-Wno-direct-ivar-access', + ] + else + all_objc_warnings = [] + endif +endif diff --git a/pugl/meson_options.txt b/pugl/meson_options.txt new file mode 100644 index 0000000..dd6ea8c --- /dev/null +++ b/pugl/meson_options.txt @@ -0,0 +1,20 @@ +option('cairo', type: 'feature', value: 'auto', + description : 'Enable support for the Cairo graphics API') + +option('examples', type: 'boolean', value: true, + description: 'Build example programs') + +option('docs', type: 'feature', value: 'auto', + description: 'Build documentation') + +option('opengl', type: 'feature', value: 'auto', + description : 'Enable support for the OpenGL graphics API') + +option('strict', type: 'boolean', value: false, + description: 'Enable ultra-strict warnings') + +option('tests', type: 'boolean', value: true, + description: 'Build tests') + +option('vulkan', type: 'feature', value: 'auto', + description : 'Enable support for the Vulkan graphics API') diff --git a/pugl/scripts/cat.py b/pugl/scripts/cat.py new file mode 100755 index 0000000..5f628b6 --- /dev/null +++ b/pugl/scripts/cat.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +import sys + +for filename in sys.argv[1:]: + with open(filename, 'r') as f: + sys.stdout.write(f.read()) diff --git a/pugl/scripts/dox_to_sphinx.py b/pugl/scripts/dox_to_sphinx.py index b4eee9f..c3f7c44 100755 --- a/pugl/scripts/dox_to_sphinx.py +++ b/pugl/scripts/dox_to_sphinx.py @@ -238,7 +238,7 @@ def heading(text, level): chars = ("#", "*", "=", "-", "^", '"') line = chars[level] * len(text) - return "%s\n%s\n%s\n\n" % (line if level < 3 else "", text, line) + return "%s%s\n%s\n\n" % (line + "\n" if level < 3 else "", text, line) def dox_to_rst(index, lang, node): @@ -257,6 +257,12 @@ def dox_to_rst(index, lang, node): return " " + markup.strip() + if node.tag == "lsquo": + return "‘" + + if node.tag == "rsquo": + return "’" + if node.tag == "computeroutput": assert len(node) == 0 return "``%s``" % node.text @@ -332,7 +338,7 @@ def description_markup(index, lang, node): assert not (node.tag == "briefdescription" and len(node) > 1) assert len(node.text.strip()) == 0 - return "".join([dox_to_rst(index, lang, child) for child in node]) + return "".join([dox_to_rst(index, lang, child) for child in node]).strip() def set_descriptions(index, lang, definition, record): @@ -454,6 +460,9 @@ def read_definition_doc(index, lang, root): name, ) + elif kind == "variable": + record["definition"] = member.find("definition").text + def declaration_string(record): """ @@ -474,6 +483,11 @@ def declaration_string(record): result += record["prototype"] elif kind == "typedef": result += record["definition"] + elif kind == "variable": + if "parent" in record: + result += "%s %s" % (record["type"], local_name(record["name"])) + else: + result += record["definition"] elif "type" in record: result += "%s %s" % (record["type"], local_name(record["name"])) else: @@ -497,9 +511,9 @@ def document_markup(index, lang, record): markup += ".. %s:: %s\n" % (role, declaration_string(record)) # Write main description blurb - markup += "\n" - markup += indent(record["briefdescription"], 1) - markup += indent(record["detaileddescription"], 1) + markup += "\n" + indent(record["briefdescription"] + "\n", 1) + if len(record["detaileddescription"]) > 0: + markup += "\n" + indent(record["detaileddescription"], 1) + "\n" assert ( kind in ["class", "enum", "namespace", "struct", "union"] @@ -510,7 +524,7 @@ def document_markup(index, lang, record): child_indent = 0 if kind == "namespace" else 1 # Write inline children if applicable - markup += "\n" + markup += "\n" if "children" in record else "" for child_id in record.get("children", []): child_record = index[child_id] child_role = sphinx_role(child_record, lang) @@ -524,7 +538,6 @@ def document_markup(index, lang, record): markup += indent(child_header, child_indent) markup += indent(child_record["briefdescription"], child_indent + 1) markup += indent(child_record["detaileddescription"], child_indent + 1) - markup += "\n" return markup @@ -535,28 +548,7 @@ def symbol_filename(name): return name.replace("::", "__") -def emit_symbols(index, lang, symbol_dir, force): - """Write a description file for every symbol documented in the index.""" - - for record in index.values(): - if ( - record["kind"] in ["group", "namespace"] - or "parent" in record - and index[record["parent"]]["kind"] != "group" - ): - continue - - name = record["name"] - filename = os.path.join(symbol_dir, symbol_filename("%s.rst" % name)) - if not force and os.path.exists(filename): - raise FileExistsError("File already exists: '%s'" % filename) - - with open(filename, "w") as rst: - rst.write(heading(local_name(name), 3)) - rst.write(document_markup(index, lang, record)) - - -def emit_groups(index, output_dir, symbol_dir_name, force): +def emit_groups(index, lang, output_dir, force): """Write a description file for every group documented in the index.""" for record in index.values(): @@ -569,40 +561,38 @@ def emit_groups(index, output_dir, symbol_dir_name, force): raise FileExistsError("File already exists: '%s'" % filename) with open(filename, "w") as rst: - rst.write(heading(record["title"], 2)) + rst.write(heading(record["title"], 1)) # Get all child group and symbol names - group_names = [] - symbol_names = [] + child_groups = {} + child_symbols = {} for child_id in record["children"]: child = index[child_id] if child["kind"] == "group": - group_names += [child["name"]] + child_groups[child["name"]] = child else: - symbol_names += [child["name"]] + child_symbols[child["name"]] = child # Emit description (document body) - rst.write(record["briefdescription"] + "\n\n") - rst.write(record["detaileddescription"] + "\n\n") - - # Emit TOC - rst.write(".. toctree::\n") + if len(record["briefdescription"]) > 0: + rst.write(record["briefdescription"] + "\n\n") + if len(record["detaileddescription"]) > 0: + rst.write(record["detaileddescription"] + "\n\n") - # Emit groups at the top of the TOC - for group_name in group_names: - rst.write("\n" + indent(group_name, 1)) + if len(child_groups) > 0: + # Emit TOC for child groups + rst.write(".. toctree::\n\n") + for name, group in child_groups.items(): + rst.write(indent(group["name"], 1) + "\n") # Emit symbols in sorted order - for symbol_name in sorted(symbol_names): - path = "/".join( - [symbol_dir_name, symbol_filename(symbol_name)] - ) - rst.write("\n" + indent(path, 1)) + for name, symbol in child_symbols.items(): + rst.write("\n") + rst.write(document_markup(index, lang, symbol)) + rst.write("\n") - rst.write("\n") - -def run(index_xml_path, output_dir, symbol_dir_name, language, force): +def run(index_xml_path, output_dir, language, force): """Write a directory of Sphinx files from a Doxygen XML directory.""" # Build skeleton index from index.xml @@ -624,11 +614,14 @@ def run(index_xml_path, output_dir, symbol_dir_name, language, force): for root in definition_docs: read_definition_doc(index, language, root) + # Create output directory + try: + os.makedirs(output_dir) + except OSError: + pass + # Emit output files - symbol_dir = os.path.join(output_dir, symbol_dir_name) - os.makedirs(symbol_dir, exist_ok=True) - emit_symbols(index, language, symbol_dir, force) - emit_groups(index, output_dir, symbol_dir_name, force) + emit_groups(index, language, output_dir, force) if __name__ == "__main__": @@ -653,14 +646,8 @@ if __name__ == "__main__": help="language domain for output", ) - ap.add_argument( - "-s", - "--symbol-dir-name", - default="symbols", - help="name for subdirectory of symbol documentation files", - ) - ap.add_argument("index_xml_path", help="path index.xml from Doxygen") ap.add_argument("output_dir", help="output directory") + print(sys.argv) run(**vars(ap.parse_args(sys.argv[1:]))) diff --git a/pugl/src/x11.c b/pugl/src/x11.c index 24054f9..2cdb0f3 100644 --- a/pugl/src/x11.c +++ b/pugl/src/x11.c @@ -217,20 +217,23 @@ updateSizeHints(const PuglView* view) sizeHints.max_height = (int)view->frame.height; } else { if (view->defaultWidth || view->defaultHeight) { - sizeHints.flags = PBaseSize; + sizeHints.flags |= PBaseSize; sizeHints.base_width = view->defaultWidth; sizeHints.base_height = view->defaultHeight; } + if (view->minWidth || view->minHeight) { - sizeHints.flags = PMinSize; + sizeHints.flags |= PMinSize; sizeHints.min_width = view->minWidth; sizeHints.min_height = view->minHeight; } + if (view->maxWidth || view->maxHeight) { - sizeHints.flags = PMaxSize; + sizeHints.flags |= PMaxSize; sizeHints.max_width = view->maxWidth; sizeHints.max_height = view->maxHeight; } + if (view->minAspectX) { sizeHints.flags |= PAspect; sizeHints.min_aspect.x = view->minAspectX; @@ -1145,8 +1148,12 @@ puglDispatchX11Events(PuglWorld* world) XWindowAttributes attrs; XGetWindowAttributes(view->impl->display, view->impl->win, &attrs); - const PuglEventConfigure configure = { - PUGL_CONFIGURE, 0, attrs.x, attrs.y, attrs.width, attrs.height}; + const PuglEventConfigure configure = {PUGL_CONFIGURE, + 0, + (double)attrs.x, + (double)attrs.y, + (double)attrs.width, + (double)attrs.height}; puglDispatchEvent(view, (const PuglEvent*)&configure); puglDispatchEvent(view, &event); @@ -1297,8 +1304,8 @@ puglSetMinSize(PuglView* const view, const int width, const int height) PuglStatus puglSetMaxSize(PuglView* const view, const int width, const int height) { - view->minWidth = width; - view->minHeight = height; + view->maxWidth = width; + view->maxHeight = height; return updateSizeHints(view); } diff --git a/pugl/test/meson.build b/pugl/test/meson.build new file mode 100644 index 0000000..340a7dd --- /dev/null +++ b/pugl/test/meson.build @@ -0,0 +1,33 @@ +basic_tests = [ + 'realize', + 'redisplay', + 'show_hide', + 'stub_hints', + 'timer', + 'update', +] + +gl_tests = [ + 'gl_hints' +] + +includes = [ + '.', + '../include', +] + +foreach test : basic_tests + test(test, + executable('test_' + test, 'test_@0@.c'.format(test), + include_directories: include_directories(includes), + dependencies: [pugl_dep, stub_backend_dep])) +endforeach + +if opengl_dep.found() + foreach test : gl_tests + test(test, + executable('test_' + test, 'test_@0@.c'.format(test), + include_directories: include_directories(includes), + dependencies: [pugl_dep, gl_backend_dep])) + endforeach +endif diff --git a/pugl/test/test_timer.c b/pugl/test/test_timer.c index 18518ec..200ddc2 100644 --- a/pugl/test/test_timer.c +++ b/pugl/test/test_timer.c @@ -41,7 +41,7 @@ static const double timeout = -1.0; #ifdef _WIN32 // Windows SetTimer has a maximum resolution of 10ms -static const double tolerance = 0.011; +static const double tolerance = 0.012; #else static const double tolerance = 0.002; #endif diff --git a/pugl/waf b/pugl/waf deleted file mode 100755 index 58d14c3..0000000 --- a/pugl/waf +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python - -# Minimal waf script for projects that include waflib directly - -import sys -import inspect -import os - -try: - from waflib import Context, Scripting -except Exception as e: - sys.stderr.write('error: Failed to import waf (%s)\n' % e) - if os.path.exists('.git'): - sys.stderr.write("Are submodules up to date? " - "Try 'git submodule update --init --recursive'\n") - - sys.exit(1) - - -def main(): - script_path = os.path.abspath(inspect.getfile(inspect.getmodule(main))) - project_path = os.path.dirname(os.path.realpath(script_path)) - Scripting.waf_entry_point(os.getcwd(), Context.WAFVERSION, project_path) - - -if __name__ == '__main__': - main() diff --git a/pugl/wscript b/pugl/wscript deleted file mode 100644 index 1dc95c1..0000000 --- a/pugl/wscript +++ /dev/null @@ -1,814 +0,0 @@ -#!/usr/bin/env python - -import os -import sys - -from waflib import Build, Logs, Options, TaskGen -from waflib.extras import autowaf - -# Library and package version (UNIX style major, minor, micro) -# major increment <=> incompatible changes -# minor increment <=> compatible changes (additions) -# micro increment <=> no interface changes -PUGL_VERSION = '0.2.0' -PUGL_MAJOR_VERSION = '0' - -# Mandatory waf variables -APPNAME = 'pugl' # Package name for waf dist -VERSION = PUGL_VERSION # Package version for waf dist -top = '.' # Source directory -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', - help='target platform (e.g. "win32" or "darwin")') - - ctx.add_flags( - opts, - {'all-headers': 'install complete header implementation', - 'no-vulkan': 'do not build Vulkan support', - 'no-gl': 'do not build OpenGL support', - 'no-cxx': 'do not build C++ examples', - 'no-cairo': 'do not build Cairo support', - 'no-static': 'do not build static library', - 'no-shared': 'do not build shared library'}) - - ctx.get_option_group('Test options').add_option( - '--gui-tests', action='store_true', help='Run GUI tests') - - -def configure(conf): - conf.load('compiler_c', cache=True) - if not Options.options.no_cxx: - 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.TARGET_PLATFORM = Options.options.target or sys.platform - platform = conf.env.TARGET_PLATFORM - - data_dir = '%s/%s' % (conf.env.DATADIR, 'pugl-%s' % PUGL_MAJOR_VERSION) - - if Options.options.strict: - # Check for programs used by lint target - conf.find_program("flake8", var="FLAKE8", mandatory=False) - conf.find_program("clang-tidy", var="CLANG_TIDY", mandatory=False) - conf.find_program("iwyu_tool", var="IWYU_TOOL", mandatory=False) - - if Options.options.ultra_strict: - # All warnings enabled by autowaf, disable some we trigger - - autowaf.add_compiler_flags(conf.env, '*', { - 'clang': [ - '-Wno-padded', - '-Wno-reserved-id-macro', - '-Wno-switch-enum', - ], - 'gcc': [ - '-Wno-inline', - '-Wno-padded', - '-Wno-suggest-attribute=const', - '-Wno-suggest-attribute=malloc', - '-Wno-suggest-attribute=pure', - '-Wno-switch-enum', - ], - 'msvc': [ - '/wd4061', # enumerator in switch is not explicitly handled - '/wd4514', # unreferenced inline function has been removed - '/wd4710', # function not inlined - '/wd4711', # function selected for automatic inline expansion - '/wd4820', # padding added after construct - '/wd4996', # POSIX name for this item is deprecated - '/wd5045', # will insert Spectre mitigation for memory load - ], - }) - - autowaf.add_compiler_flags(conf.env, 'c', { - 'clang': [ - '-Wno-bad-function-cast', - '-Wno-float-equal', - '-Wno-implicit-fallthrough', - ], - 'gcc': [ - '-Wno-bad-function-cast', - '-Wno-float-equal', - '-Wno-pedantic', - ], - 'msvc': [ - '/wd4191', # unsafe conversion from type to type - '/wd4706', # assignment within conditional expression - '/wd4710', # function not inlined - '/wd5045', # will insert Spectre mitigation for memory load - ], - }) - - autowaf.add_compiler_flags(conf.env, 'cxx', { - 'clang': [ - '-Wno-cast-align', # pugl_vulkan_cxx_demo - '-Wno-documentation-unknown-command', - '-Wno-old-style-cast', - ], - 'gcc': [ - '-Wno-cast-align', # pugl_vulkan_cxx_demo - '-Wno-effc++', - '-Wno-old-style-cast', - '-Wno-suggest-final-methods', - '-Wno-useless-cast', - ], - 'msvc': [ - '/wd4191', # unsafe conversion between function pointers - '/wd4355', # 'this' used in base member initializer list - '/wd4571', # structured exceptions (SEH) are no longer caught - '/wd4623', # default constructor implicitly deleted - '/wd4625', # copy constructor implicitly deleted - '/wd4626', # assignment operator implicitly deleted - '/wd4706', # assignment within conditional expression - '/wd4868', # may not enforce left-to-right evaluation order - '/wd5026', # move constructor implicitly deleted - '/wd5027', # move assignment operator implicitly deleted - '/wd5039', # pointer to throwing function passed to C function - ], - }) - - # Add some platform-specific warning suppressions - if conf.env.TARGET_PLATFORM == "win32": - autowaf.add_compiler_flags(conf.env, '*', { - 'gcc': ['-Wno-cast-function-type', - '-Wno-conversion', - '-Wno-format', - '-Wno-suggest-attribute=format'], - 'clang': ['-D_CRT_SECURE_NO_WARNINGS', - '-Wno-format-nonliteral', - '-Wno-nonportable-system-include-path'], - }) - elif conf.env.TARGET_PLATFORM == 'darwin': - autowaf.add_compiler_flags(conf.env, '*', { - 'clang': ['-DGL_SILENCE_DEPRECATION', - '-Wno-deprecated-declarations', - '-Wno-direct-ivar-access'], - 'gcc': ['-DGL_SILENCE_DEPRECATION', - '-Wno-deprecated-declarations', - '-Wno-direct-ivar-access'], - }) - - if Options.options.debug and not Options.options.no_coverage: - conf.env.append_unique('CFLAGS', ['--coverage']) - conf.env.append_unique('CXXFLAGS', ['--coverage']) - conf.env.append_unique('LINKFLAGS', ['--coverage']) - - if conf.env.DOCS: - conf.load('python') - conf.load('sphinx') - conf.find_program('doxygen', var='DOXYGEN') - - if not (conf.env.DOXYGEN and conf.env.SPHINX_BUILD): - conf.env.DOCS = False - - sys_header = 'windows.h' if platform == 'win32' else '' - - if not Options.options.no_vulkan: - vulkan_sdk = os.environ.get('VULKAN_SDK', None) - vulkan_cflags = '' - if vulkan_sdk: - vk_include_path = os.path.join(vulkan_sdk, 'Include') - vulkan_cflags = conf.env.CPPPATH_ST % vk_include_path - - # Check for Vulkan header (needed for backends) - conf.check(features='c cxx', - cflags=vulkan_cflags, - cxxflags=vulkan_cflags, - header_name=sys_header + ' vulkan/vulkan.h', - uselib_store='VULKAN', - mandatory=False) - - if conf.env.BUILD_TESTS and conf.env.HAVE_VULKAN: - # Check for Vulkan library and shader compiler - # The library is needed by pugl_vulkan_demo.c which has no loader - vulkan_linkflags = '' - if vulkan_sdk: - vk_lib_path = os.path.join(vulkan_sdk, 'Lib') - vulkan_linkflags = conf.env.LIBPATH_ST % vk_lib_path - - # The Vulkan library has a different name on Windows - for l in ['vulkan', 'vulkan-1']: - if conf.check(lib=l, - uselib_store='VULKAN_LIB', - cflags=vulkan_cflags, - linkflags=vulkan_linkflags, - mandatory=False): - break - - validator_name = 'glslangValidator' - if vulkan_sdk: - vk_bin_path = os.path.join(vulkan_sdk, 'bin') - validator_name = os.path.join(vk_bin_path, 'glslangValidator') - - conf.find_program(validator_name, var='GLSLANGVALIDATOR') - - # Check for base system libraries needed on some systems - conf.check_cc(lib='pthread', uselib_store='PTHREAD', mandatory=False) - conf.check_cc(lib='m', uselib_store='M', mandatory=False) - conf.check_cc(lib='dl', uselib_store='DL', mandatory=False) - - # Check for "native" platform dependencies - conf.env.HAVE_GL = False - if platform == 'darwin': - conf.check_cc(framework_name='Cocoa', framework='Cocoa', - uselib_store='COCOA') - conf.check_cc(framework_name='Corevideo', framework='Corevideo', - uselib_store='COREVIDEO') - if not Options.options.no_gl: - conf.check_cc(framework_name='OpenGL', uselib_store='GL', - mandatory=False) - conf.env.HAVE_GL = conf.env.FRAMEWORK_GL - - elif platform == 'win32': - conf.check_cc(lib='gdi32', uselib_store='GDI32') - conf.check_cc(lib='user32', uselib_store='USER32') - if not Options.options.no_gl: - conf.check_cc(lib='opengl32', uselib_store='GL', mandatory=False) - conf.env.HAVE_GL = conf.env.LIB_GL - - else: - conf.check_cc(lib='X11', uselib_store='X11') - - xsync_fragment = """#include - #include - int main(void) { XSyncQueryExtension(0, 0, 0); return 0; }""" - if conf.check_cc(fragment=xsync_fragment, - uselib_store='XSYNC', - lib='Xext', - mandatory=False, - msg='Checking for function XSyncQueryExtension'): - conf.define('HAVE_XSYNC', 1) - - if conf.check_cc(lib='Xcursor', - uselib_store='XCURSOR', - mandatory=False): - conf.define('HAVE_XCURSOR', 1) - - if conf.check_cc(lib='Xrandr', - uselib_store='XRANDR', - mandatory=False): - conf.define('HAVE_XRANDR', 1) - - if not Options.options.no_gl: - glx_fragment = """#include - int main(void) { glXSwapBuffers(0, 0); return 0; }""" - - conf.check_cc(lib='GLX', uselib_store='GLX', mandatory=False) - conf.check_cc(lib='GL', uselib_store='GL', mandatory=False) - conf.check_cc(fragment=glx_fragment, - lib='GLX' if conf.env.LIB_GLX else 'GL', - mandatory=False, - msg='Checking for GLX') - conf.env.HAVE_GL = conf.env.LIB_GL or conf.env.LIB_GLX - - # Check for Cairo via pkg-config - if not Options.options.no_cairo: - autowaf.check_pkg(conf, 'cairo', - uselib_store = 'CAIRO', - atleast_version = '1.0.0', - system = True, - mandatory = False) - - conf.env.update({ - 'BUILD_SHARED': not Options.options.no_shared, - 'BUILD_STATIC': conf.env.BUILD_TESTS or not Options.options.no_static, - 'BUILD_STRICT_HEADER_TEST': Options.options.strict, - 'PUGL_MAJOR_VERSION': PUGL_MAJOR_VERSION, - }) - - if conf.env.TARGET_PLATFORM == 'win32': - conf.env.PUGL_PLATFORM = 'win' - elif conf.env.TARGET_PLATFORM == 'darwin': - conf.env.PUGL_PLATFORM = 'mac' - else: - conf.env.PUGL_PLATFORM = 'x11' - - conf.define('PUGL_DATA_DIR', data_dir) - - autowaf.set_lib_env(conf, 'pugl', PUGL_VERSION, - lib='pugl_' + conf.env.PUGL_PLATFORM) - - autowaf.set_lib_env(conf, 'pugl_gl', PUGL_VERSION, - lib='pugl_%s_gl' % conf.env.PUGL_PLATFORM) - - autowaf.display_summary( - conf, - {"Build static library": bool(conf.env.BUILD_STATIC), - "Build shared library": bool(conf.env.BUILD_SHARED), - "Cairo support": bool(conf.env.HAVE_CAIRO), - "OpenGL support": bool(conf.env.HAVE_GL), - "Vulkan support": bool(conf.env.HAVE_VULKAN)}) - - -def _build_pc_file(bld, - name, - desc, - target, - libname, - deps={}, - requires=[], - cflags=[]): - "Builds a pkg-config file for a library" - env = bld.env - prefix = env.PREFIX - xprefix = os.path.dirname(env.LIBDIR) - if libname is not None: - libname += '-%s' % PUGL_MAJOR_VERSION - - uselib = deps.get('uselib', []) - pkg_deps = [l for l in uselib if 'PKG_' + l.lower() in env] - lib_deps = [l for l in uselib if 'PKG_' + l.lower() not in env] - lib = deps.get('lib', []) + [libname] if libname is not None else [] - - link_flags = [env.LIB_ST % l for l in lib] - for l in lib_deps: - link_flags += [env.LIB_ST % l for l in env['LIB_' + l]] - for f in deps.get('framework', []): - link_flags += ['-framework', f] - - bld(features='subst', - source='pugl.pc.in', - target='%s-%s.pc' % (target, PUGL_MAJOR_VERSION), - install_path=os.path.join(env.LIBDIR, 'pkgconfig'), - - PREFIX=prefix, - EXEC_PREFIX='${prefix}' if xprefix == prefix else xprefix, - LIBDIR='${exec_prefix}/' + os.path.basename(env.LIBDIR), - INCLUDEDIR=env.INCLUDEDIR.replace(prefix, '${prefix}', 1), - - NAME=name, - DESCRIPTION=desc, - PUGL_MAJOR_VERSION=PUGL_MAJOR_VERSION, - REQUIRES=' '.join(requires + [p.lower() for p in pkg_deps]), - LIBS=' '.join(link_flags), - CFLAGS=' '.join(cflags)) - - -gl_tests = ['gl_hints'] - -basic_tests = [ - 'clipboard', - 'realize', - 'redisplay', - 'show_hide', - 'stub_hints', - 'timer', - 'update', -] - -tests = gl_tests + basic_tests - - -def concatenate(task): - """Task to concatenate all input files into the output file""" - with open(task.outputs[0].abspath(), 'w') as out: - for filename in task.inputs: - with open(filename.abspath(), 'r') as source: - for line in source: - out.write(line) - - -def build(bld): - # C Headers - includedir = '${INCLUDEDIR}/pugl-%s/pugl' % PUGL_MAJOR_VERSION - bld.install_files(includedir, bld.path.ant_glob('include/pugl/*.h')) - - if 'COMPILER_CXX' in bld.env: - # C++ Headers - includedirxx = '${INCLUDEDIR}/puglxx-%s/pugl' % PUGL_MAJOR_VERSION - bld.install_files(includedirxx, - bld.path.ant_glob('bindings/cxx/include/pugl/*.hpp')) - bld.install_files(includedirxx, - bld.path.ant_glob('bindings/cxx/include/pugl/*.ipp')) - - # Library dependencies of pugl libraries (for building examples) - deps = {} - - data_dir = os.path.join(bld.env.DATADIR, 'pugl-%s' % PUGL_MAJOR_VERSION) - - def build_pugl_lib(name, **kwargs): - deps[name] = {} - for k in ('lib', 'framework', 'uselib'): - deps[name][k] = kwargs.get(k, []) - - args = kwargs.copy() - args.update({'includes': ['include'], - 'export_includes': ['include'], - 'install_path': '${LIBDIR}', - 'vnum': PUGL_VERSION}) - - flags = [] - if not bld.env.MSVC_COMPILER: - flags = ['-fvisibility=hidden'] - if bld.env.TARGET_PLATFORM != 'win32': - flags = ['-fPIC'] - - if bld.env.BUILD_SHARED: - bld(features = 'c cshlib', - name = name, - target = 'pugl_%s-%s' % (name, PUGL_MAJOR_VERSION), - defines = ['PUGL_INTERNAL'], - cflags = flags, - linkflags = flags, - **args) - - if bld.env.BUILD_STATIC: - bld(features = 'c cstlib', - name = 'pugl_%s_static' % name, - target = 'pugl_%s-%s' % (name, PUGL_MAJOR_VERSION), - defines = ['PUGL_STATIC', 'PUGL_INTERNAL', 'PUGL_DISABLE_DEPRECATED'], - cflags = flags, - linkflags = flags, - **args) - - def build_platform(platform, **kwargs): - build_pugl_lib(platform, **kwargs) - _build_pc_file(bld, 'Pugl', 'Pugl GUI library core', - 'pugl', 'pugl_%s' % platform, - deps=kwargs, - cflags=['-I${includedir}/pugl-%s' % PUGL_MAJOR_VERSION]) - - def build_backend(platform, backend, **kwargs): - name = '%s_%s' % (platform, backend) - label = 'OpenGL' if backend == 'gl' else backend.title() - build_pugl_lib(name, **kwargs) - _build_pc_file(bld, 'Pugl %s' % label, - 'Pugl GUI library with %s backend' % label, - 'pugl-%s' % backend, 'pugl_' + name, - deps=kwargs, - requires=['pugl-%s' % PUGL_MAJOR_VERSION], - cflags=['-I${includedir}/pugl-%s' % PUGL_MAJOR_VERSION]) - - lib_source = ['src/implementation.c'] - if bld.env.TARGET_PLATFORM == 'win32': - platform = 'win' - build_platform('win', - uselib=['GDI32', 'USER32'], - source=lib_source + ['src/win.c']) - - build_backend('win', 'stub', - uselib=['GDI32', 'USER32'], - source=['src/win_stub.c']) - - if bld.env.HAVE_GL: - build_backend('win', 'gl', - uselib=['GDI32', 'USER32', 'GL'], - source=['src/win_gl.c']) - - if bld.env.HAVE_VULKAN: - build_backend('win', 'vulkan', - uselib=['GDI32', 'USER32', 'VULKAN'], - source=['src/win_vulkan.c', 'src/win_stub.c']) - - if bld.env.HAVE_CAIRO: - build_backend('win', 'cairo', - uselib=['CAIRO', 'GDI32', 'USER32'], - source=['src/win_cairo.c', 'src/win_stub.c']) - - elif bld.env.TARGET_PLATFORM == 'darwin': - platform = 'mac' - build_platform('mac', - framework=['Cocoa', 'Corevideo'], - source=lib_source + ['src/mac.m']) - - build_backend('mac', 'stub', - framework=['Cocoa', 'Corevideo'], - source=['src/mac_stub.m']) - - if bld.env.HAVE_GL: - build_backend('mac', 'gl', - framework=['Cocoa', 'Corevideo', 'OpenGL'], - source=['src/mac_gl.m']) - - if bld.env.HAVE_VULKAN: - build_backend('mac', 'vulkan', - framework=['Cocoa', 'QuartzCore'], - source=['src/mac_vulkan.m']) - - if bld.env.HAVE_CAIRO: - build_backend('mac', 'cairo', - framework=['Cocoa', 'Corevideo'], - uselib=['CAIRO'], - source=['src/mac_cairo.m']) - else: - platform = 'x11' - build_platform('x11', - uselib=['M', 'X11', 'XSYNC', 'XCURSOR', 'XRANDR'], - source=lib_source + ['src/x11.c']) - - build_backend('x11', 'stub', - uselib=['X11'], - source=['src/x11_stub.c']) - - if bld.env.HAVE_GL: - glx_lib = 'GLX' if bld.env.LIB_GLX else 'GL' - build_backend('x11', 'gl', - uselib=[glx_lib, 'X11'], - source=['src/x11_gl.c']) - - if bld.env.HAVE_VULKAN: - build_backend('x11', 'vulkan', - uselib=['DL', 'X11'], - source=['src/x11_vulkan.c', 'src/x11_stub.c']) - - if bld.env.HAVE_CAIRO: - build_backend('x11', 'cairo', - uselib=['CAIRO', 'X11'], - source=['src/x11_cairo.c', 'src/x11_stub.c']) - - if 'COMPILER_CXX' in bld.env: - _build_pc_file( - bld, 'Pugl C++', - 'C++ bindings for the Pugl GUI library', - 'puglxx', - None, - requires=['pugl-%s' % PUGL_MAJOR_VERSION], - cflags=['-I${includedir}/puglxx-%s' % PUGL_MAJOR_VERSION]) - - bld.add_group() - - def build_example(prog, source, platform, backend, **kwargs): - lang = 'cxx' if source[0].endswith('.cpp') else 'c' - - includes = ['.'] + (['bindings/cxx/include'] if lang == 'cxx' else []) - - use = ['pugl_%s_static' % platform, - 'pugl_%s_%s_static' % (platform, backend)] - - target = 'examples/' + prog - if bld.env.TARGET_PLATFORM == 'darwin': - target = '{0}.app/Contents/MacOS/{0}'.format(prog) - - bld(features = 'subst', - source = 'resources/Info.plist.in', - target = '{}.app/Contents/Info.plist'.format(prog), - includes = includes, - install_path = '', - NAME = prog) - - backend_lib = platform + '_' + backend - for k in ('lib', 'framework', 'uselib'): - kwargs.update({k: (kwargs.get(k, []) + - deps.get(platform, {}).get(k, []) + - deps.get(backend_lib, {}).get(k, []))}) - - kwargs['defines'] = kwargs.get('defines', []) + ['PUGL_STATIC'] - - bld(features = '%s %sprogram' % (lang, lang), - source = source, - target = target, - includes = includes, - use = use, - install_path = bld.env.BINDIR, - **kwargs) - - if bld.env.BUILD_TESTS: - for s in [ - 'header_330.glsl', - 'header_420.glsl', - 'rect.frag', - 'rect.vert', - ]: - # Copy shaders to build directory for example programs - bld(features = 'subst', - is_copy = True, - source = 'examples/shaders/%s' % s, - target = 'examples/shaders/%s' % s, - install_path = os.path.join(data_dir, 'shaders')) - - if bld.env.HAVE_GL: - glad_cflags = [] if bld.env.MSVC_COMPILER else ['-Wno-pedantic'] - build_example('pugl_embed_demo', ['examples/pugl_embed_demo.c'], - platform, 'gl', uselib=['GL', 'M']) - build_example('pugl_window_demo', ['examples/pugl_window_demo.c'], - platform, 'gl', uselib=['GL', 'M']) - build_example('pugl_cursor_demo', ['examples/pugl_cursor_demo.c'], - platform, 'gl', uselib=['GL', 'M']) - build_example('pugl_print_events', - ['examples/pugl_print_events.c'], - platform, 'stub') - build_example('pugl_shader_demo', - ['examples/pugl_shader_demo.c', - 'examples/file_utils.c', - 'examples/glad/glad.c'], - platform, 'gl', - cflags=glad_cflags, - uselib=['DL', 'GL', 'M']) - - for test in gl_tests: - bld(features = 'c cprogram', - source = 'test/test_%s.c' % test, - target = 'test/test_%s' % test, - install_path = '', - defines = ['PUGL_STATIC'], - use = ['pugl_%s_static' % platform, - 'pugl_%s_gl_static' % platform], - uselib = deps[platform]['uselib'] + ['GL']) - - if bld.env.HAVE_VULKAN and 'GLSLANGVALIDATOR' in bld.env: - for s in ['rect.vert', 'rect.frag']: - complete = bld.path.get_bld().make_node( - 'shaders/%s' % s.replace('.', '.vulkan.')) - bld(rule = concatenate, - source = ['examples/shaders/header_420.glsl', - 'examples/shaders/%s' % s], - target = complete) - - cmd = bld.env.GLSLANGVALIDATOR[0] + " -V -o ${TGT} ${SRC}" - bld(rule = cmd, - source = complete, - target = 'examples/shaders/%s.spv' % s, - install_path = os.path.join(data_dir, 'shaders')) - - build_example('pugl_vulkan_demo', - ['examples/pugl_vulkan_demo.c'], - platform, - 'vulkan', uselib=['M', 'VULKAN', 'VULKAN_LIB']) - - if bld.env.CXX: - build_example('pugl_vulkan_cxx_demo', - ['examples/pugl_vulkan_cxx_demo.cpp', - 'examples/file_utils.c'], - platform, 'vulkan', - defines=['PUGL_STATIC', 'PUGL_DISABLE_DEPRECATED'], - uselib=['DL', 'M', 'PTHREAD', 'VULKAN']) - - if bld.env.HAVE_CAIRO: - build_example('pugl_cairo_demo', ['examples/pugl_cairo_demo.c'], - platform, 'cairo', - uselib=['M', 'CAIRO']) - - for test in basic_tests: - bld(features = 'c cprogram', - source = 'test/test_%s.c' % test, - target = 'test/test_%s' % test, - install_path = '', - defines = ['PUGL_STATIC'], - use = ['pugl_%s_static' % platform, - 'pugl_%s_stub_static' % platform, - 'pugl_%s_gl_static' % platform], - uselib = deps[platform]['uselib'] + ['CAIRO']) - - if bld.env.BUILD_STRICT_HEADER_TEST: - # Make a hyper strict warning environment for checking API headers - strict_env = bld.env.derive() - autowaf.remove_all_warning_flags(strict_env) - autowaf.enable_all_warnings(strict_env) - autowaf.set_warnings_as_errors(strict_env) - autowaf.add_compiler_flags(strict_env, '*', { - 'clang': ['-Wno-padded'], - 'gcc': ['-Wno-padded', '-Wno-suggest-attribute=const'], - 'msvc': [ - '/wd4514', # unreferenced inline function has been removed - '/wd4820', # padding added after construct - ], - }) - autowaf.add_compiler_flags(strict_env, 'cxx', { - 'clang': ['-Wno-documentation-unknown-command'], - 'msvc': [ - '/wd4355', # 'this' used in base member initializer list - '/wd4571', # structured exceptions are no longer caught - ], - }) - - # Check that C headers build with (almost) no warnings - bld(features = 'c cprogram', - source = 'test/test_build.c', - target = 'test/test_build_c', - install_path = '', - env = strict_env, - use = ['pugl_%s_static' % platform], - uselib = deps[platform]['uselib'] + ['CAIRO']) - - # Check that C++ headers build with (almost) no warnings - bld(features = 'cxx cxxprogram', - source = 'test/test_build.cpp', - target = 'test/test_build_cpp', - install_path = '', - env = strict_env, - includes = ['bindings/cxx/include'], - use = ['pugl_%s_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', - defines=['PUGL_DISABLE_DEPRECATED'], - uselib=['GL', 'M']) - - if bld.env.DOCS: - bld.recurse('doc/c') - bld.recurse('doc/cpp') - - -def test(tst): - if tst.options.gui_tests: - with tst.group('gui') as check: - for test in basic_tests: - check(['test/test_%s' % test]) - - if tst.env.HAVE_GL: - for test in gl_tests: - check(['test/test_%s' % test]) - - -class LintContext(Build.BuildContext): - fun = cmd = 'lint' - - -def lint(ctx): - "checks code for style issues" - import glob - import json - import subprocess - - st = 0 - - if "FLAKE8" in ctx.env: - Logs.info("Running flake8") - st = subprocess.call([ctx.env.FLAKE8[0], - "wscript", - "--ignore", - "E221,W504,E251,E241,E741"]) - else: - Logs.warn("Not running flake8") - - if "IWYU_TOOL" in ctx.env: - Logs.info("Running include-what-you-use") - cmd = [ctx.env.IWYU_TOOL[0], "-o", "clang", "-p", "build", "--", - "-Xiwyu", "--check_also=*.hpp", - "-Xiwyu", "--check_also=examples/*.hpp", - "-Xiwyu", "--check_also=examples/*", - "-Xiwyu", "--check_also=pugl/*.h", - "-Xiwyu", "--check_also=bindings/cxx/include/pugl/*.*", - "-Xiwyu", "--check_also=src/*.c", - "-Xiwyu", "--check_also=src/*.h", - "-Xiwyu", "--check_also=src/*.m"] - output = subprocess.check_output(cmd).decode('utf-8') - if 'error: ' in output: - sys.stdout.write(output) - st += 1 - else: - Logs.warn("Not running include-what-you-use") - - if "CLANG_TIDY" in ctx.env and "clang" in ctx.env.CC[0]: - Logs.info("Running clang-tidy") - with open('build/compile_commands.json', 'r') as db: - commands = json.load(db) - files = [c['file'] for c in commands - if os.path.basename(c['file']) != 'glad.c'] - - c_files = [os.path.join('build', f) - for f in files if f.endswith('.c')] - - c_files += glob.glob('include/pugl/*.h') - c_files += glob.glob('src/implementation.h') - - cpp_files = [os.path.join('build', f) - for f in files if f.endswith('.cpp')] - - cpp_files += glob.glob('bindings/cxx/include/pugl/*') - - c_files = list(map(os.path.abspath, c_files)) - cpp_files = list(map(os.path.abspath, cpp_files)) - - procs = [] - for c_file in c_files: - cmd = [ctx.env.CLANG_TIDY[0], "--quiet", "-p=.", c_file] - procs += [subprocess.Popen(cmd, cwd="build")] - - for cpp_file in cpp_files: - cmd = [ctx.env.CLANG_TIDY[0], - '--header-filter=".*"', - "--quiet", - "-p=.", cpp_file] - procs += [subprocess.Popen(cmd, cwd="build")] - - for proc in procs: - stdout, stderr = proc.communicate() - st += proc.returncode - else: - Logs.warn("Not running clang-tidy") - - if st != 0: - sys.exit(st) - - -# Alias .m files to be compiled like .c files, gcc will do the right thing. -@TaskGen.extension('.m') -def m_hook(self, node): - return self.create_compiled_task('c', node) -- 2.38.5