M pugl/.gitignore => pugl/.gitignore +0 -2
@@ 1,5 1,3 @@
-.waf*/
build/
-.lock-waf*
__pycache__
*.pyc
M pugl/.gitlab-ci.yml => pugl/.gitlab-ci.yml +71 -43
@@ 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
M pugl/.gitmodules => pugl/.gitmodules +0 -3
@@ 1,3 0,0 @@
-[submodule "waflib"]
- path = waflib
- url = ../../drobilla/autowaf.git
M pugl/README.md => pugl/README.md +10 -11
@@ 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:
M pugl/bindings/cxx/include/pugl/cairo.hpp => pugl/bindings/cxx/include/pugl/cairo.hpp +1 -1
@@ 25,7 25,7 @@ namespace pugl {
/**
@defgroup cairoxx Cairo
Cairo graphics support.
- @ingroup pugl_cxx
+ @ingroup puglxx
@{
*/
M pugl/bindings/cxx/include/pugl/gl.hpp => pugl/bindings/cxx/include/pugl/gl.hpp +1 -1
@@ 26,7 26,7 @@ namespace pugl {
/**
@defgroup glxx OpenGL
OpenGL graphics support.
- @ingroup pugl_cxx
+ @ingroup puglxx
@{
*/
M pugl/bindings/cxx/include/pugl/pugl.hpp => pugl/bindings/cxx/include/pugl/pugl.hpp +32 -2
@@ 580,13 580,43 @@ public:
return static_cast<Status>(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<Status>(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<Status>(puglStopTimer(cobj(), id));
M pugl/bindings/cxx/include/pugl/stub.hpp => pugl/bindings/cxx/include/pugl/stub.hpp +1 -1
@@ 25,7 25,7 @@ namespace pugl {
/**
@defgroup stubxx Stub
Stub graphics support.
- @ingroup pugl_cxx
+ @ingroup puglxx
@{
*/
M pugl/bindings/cxx/include/pugl/vulkan.hpp => pugl/bindings/cxx/include/pugl/vulkan.hpp +1 -1
@@ 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
@{
*/
D pugl/doc/_static/custom.css => pugl/doc/_static/custom.css +0 -95
@@ 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;
-}
A pugl/doc/_static/meson.build => pugl/doc/_static/meson.build +2 -0
@@ 0,0 1,2 @@
+configure_file(copy: true, input: '../../resources/pugl.svg', output: 'pugl.svg')
+
D pugl/doc/_templates/about.html => pugl/doc/_templates/about.html +0 -57
@@ 1,57 0,0 @@
-{% if theme_logo %}
-<p class="logo">
- <a href="{{ pathto(master_doc) }}">
- <img class="logo" src="{{ pathto('_static/' ~ theme_logo, 1) }}" alt="Logo"/>
- {% if theme_logo_name|lower == 'true' %}
- <span class="logo logo-name">{{ project }}</span>
- {% endif %}
- </a>
-</p>
-{% else %}
-<h1 class="logo"><a href="{{ pathto(master_doc) }}">{{ project }}</a></h1>
-{% endif %}
-
-{% if theme_description %}
-<p class="blurb">{{ theme_description }}</p>
-{% endif %}
-
-{% if theme_github_user and theme_github_repo %}
-{% if theme_github_button|lower == 'true' %}
-<p>
-<iframe src="https://ghbtns.com/github-btn.html?user={{ theme_github_user }}&repo={{ theme_github_repo }}&type={{ theme_github_type }}&count={{ theme_github_count }}&size=large&v=2"
- allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe>
-</p>
-{% 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 %}
-<p>
-<a class="badge" href="https://travis-ci.org/{{ path }}">
- <img
- alt="https://secure.travis-ci.org/{{ path }}.svg?branch={{ theme_badge_branch }}"
- src="https://secure.travis-ci.org/{{ path }}.svg?branch={{ theme_badge_branch }}"
- />
-</a>
-</p>
-{% 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 %}
-<p>
-<a class="badge" href="https://codecov.io/github/{{ path }}">
- <img
- alt="https://codecov.io/github/{{ path }}/coverage.svg?branch={{ theme_badge_branch }}"
- src="https://codecov.io/github/{{ path }}/coverage.svg?branch={{ theme_badge_branch }}"
- />
-</a>
-</p>
-{% endif %}
R pugl/doc/c/Doxyfile => pugl/doc/c/Doxyfile.in +5 -7
@@ 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
A pugl/doc/c/api/meson.build => pugl/doc/c/api/meson.build +5 -0
@@ 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')
A pugl/doc/c/event-loop.rst => pugl/doc/c/event-loop.rst +101 -0
@@ 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.
+
A pugl/doc/c/events.rst => pugl/doc/c/events.rst +84 -0
@@ 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 <PuglEventType.PUGL_CONFIGURE>`
+and :enumerator:`PUGL_EXPOSE <PuglEventType.PUGL_EXPOSE>` to draw anything,
+but there are many other :enum:`event types <PuglEventType>`.
+
+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 <https://libsdl.org/>`_ and `GLFW <https://www.glfw.org/>`_.
+
+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.
+
M pugl/doc/c/index.rst => pugl/doc/c/index.rst +7 -2
@@ 1,6 1,11 @@
+####
+Pugl
+####
+
+.. include:: summary.rst
+
.. toctree::
- pugl
deployment
overview
- reference
+ api/pugl
A pugl/doc/c/meson.build => pugl/doc/c/meson.build +44 -0
@@ 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')
M pugl/doc/c/overview.rst => pugl/doc/c/overview.rst +12 -565
@@ 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 <pugl/pugl.h>
-
-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 <PuglWorldType.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 <api/frame>` and :doc:`window <api/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 <PuglViewHint>` 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 <PuglNativeView>` 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 <PuglEventFunc>`,
-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 <api/cairo>`, :doc:`OpenGL <api/gl>`, and :doc:`Vulkan <api/vulkan>`.
-
-Using Cairo
------------
-
-Cairo-specific API is declared in the ``cairo.h`` header:
-
-.. code-block:: c
-
- #include <pugl/cairo.h>
-
-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 <https://www.cairographics.org/manual/cairo-cairo-t.html>`_ 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 <pugl/gl.h>
-
-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 <PuglEventType.PUGL_CREATE>` and
-:enumerator:`PUGL_DESTROY <PuglEventType.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 <pugl/vulkan.h>
-
-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 <PuglEventType.PUGL_CONFIGURE>`
-and :enumerator:`PUGL_EXPOSE <PuglEventType.PUGL_EXPOSE>` to draw anything,
-but there are many other :enum:`event types <PuglEventType>`.
-
-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 <https://libsdl.org/>`_ and `GLFW <https://www.glfw.org/>`_.
-
-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 <pugl/pugl.h>
-.. 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 <https://developer.apple.com/documentation/appkit/nsview>`_,
- which may be nested, and
- `windows <https://developer.apple.com/documentation/appkit/nswindow>`_,
- which may not.
- On Windows and X11, everything is a nestable window,
- but top-level windows are configured differently.
D pugl/doc/c/reference.rst => pugl/doc/c/reference.rst +0 -18
@@ 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
A pugl/doc/c/shutting-down.rst => pugl/doc/c/shutting-down.rst +20 -0
@@ 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);
A pugl/doc/c/view.rst => pugl/doc/c/view.rst +321 -0
@@ 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 <api/frame>` and :doc:`window <api/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 <PuglViewHint>` 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 <PuglNativeView>` 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 <PuglEventFunc>`,
+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 <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 <api/cairo>`, :doc:`OpenGL <api/gl>`, and :doc:`Vulkan <api/vulkan>`.
+
+Using Cairo
+===========
+
+Cairo-specific API is declared in the ``cairo.h`` header:
+
+.. code-block:: c
+
+ #include <pugl/cairo.h>
+
+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 <https://www.cairographics.org/manual/cairo-cairo-t.html>`_ 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 <pugl/gl.h>
+
+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 <PuglEventType.PUGL_CREATE>` and
+:enumerator:`PUGL_DESTROY <PuglEventType.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 <pugl/vulkan.h>
+
+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 <https://developer.apple.com/documentation/appkit/nsview>`_,
+ which may be nested, and
+ `windows <https://developer.apple.com/documentation/appkit/nswindow>`_,
+ 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
A pugl/doc/c/world.rst => pugl/doc/c/world.rst +65 -0
@@ 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 <PuglWorldType.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.
+
D pugl/doc/c/wscript => pugl/doc/c/wscript +0 -43
@@ 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/")
A pugl/doc/c/xml/meson.build => pugl/doc/c/xml/meson.build +19 -0
@@ 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')
M pugl/doc/conf.py.in => pugl/doc/conf.py.in +39 -42
@@ 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,
}
R pugl/doc/cpp/Doxyfile => pugl/doc/cpp/Doxyfile.in +4 -12
@@ 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
A pugl/doc/cpp/api/meson.build => pugl/doc/cpp/api/meson.build +5 -0
@@ 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')
D pugl/doc/cpp/c-reference.rst => pugl/doc/cpp/c-reference.rst +0 -20
@@ 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
D pugl/doc/cpp/cpp-reference.rst => pugl/doc/cpp/cpp-reference.rst +0 -18
@@ 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
A pugl/doc/cpp/event-loop.rst => pugl/doc/cpp/event-loop.rst +37 -0
@@ 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.
A pugl/doc/cpp/events.rst => pugl/doc/cpp/events.rst +43 -0
@@ 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 <pugl::EventType>`.
+
+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 <https://libsdl.org/>`_ and `GLFW <https://www.glfw.org/>`_.
+
+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.
M pugl/doc/cpp/index.rst => pugl/doc/cpp/index.rst +8 -3
@@ 1,7 1,12 @@
+####
+Pugl
+####
+
+.. include:: summary.rst
+
.. toctree::
- pugl
deployment
overview
- cpp-reference
- c-reference
+ api/pugl
+ api/puglxx
A pugl/doc/cpp/meson.build => pugl/doc/cpp/meson.build +43 -0
@@ 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')
M pugl/doc/cpp/overview.rst => pugl/doc/cpp/overview.rst +9 -397
@@ 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<PuglEventType t, class Base>
- pugl::Status onEvent(const pugl::Event<t, Base>&) 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 <api/frame>` and :doc:`window <api/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 <PuglViewHint>` 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 <pugl::NativeView>` 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 <api/cairo>`, :doc:`OpenGL <api/gl>`, and :doc:`Vulkan <api/vulkan>`.
-
-Using Cairo
-^^^^^^^^^^^
-
-Cairo-specific API is declared in the ``cairo.hpp`` header:
-
-.. code-block:: cpp
-
- #include <pugl/cairo.hpp>
-
-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 <https://www.cairographics.org/manual/cairo-cairo-t.html>`_ can be accessed with :func:`View::context`:
-
-.. code-block:: cpp
-
- cairo_t* cr = static_cast<cairo_t*>(view.context());
-
-Using OpenGL
-^^^^^^^^^^^^
-
-OpenGL-specific API is declared in the ``gl.hpp`` header:
-
-.. code-block:: cpp
-
- #include <pugl/gl.hpp>
-
-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 <pugl/vulkan.hpp>
-
-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 <pugl::EventType>`.
-
-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 <https://libsdl.org/>`_ and `GLFW <https://www.glfw.org/>`_.
-
-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 <https://developer.apple.com/documentation/appkit/nsview>`_,
- which may be nested, and
- `windows <https://developer.apple.com/documentation/appkit/nswindow>`_,
- which may not.
- On Windows and X11, everything is a nestable window,
- but top-level windows are configured differently.
+ world
+ view
+ events
+ event-loop
A pugl/doc/cpp/view.rst => pugl/doc/cpp/view.rst +299 -0
@@ 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<PuglEventType t, class Base>
+ pugl::Status onEvent(const pugl::Event<t, Base>&) 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 <api/frame>` and :doc:`window <api/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 <PuglViewHint>` 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 <pugl::NativeView>` 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 <api/cairo>`, :doc:`OpenGL <api/gl>`, and :doc:`Vulkan <api/vulkan>`.
+
+Using Cairo
+===========
+
+Cairo-specific API is declared in the ``cairo.hpp`` header:
+
+.. code-block:: cpp
+
+ #include <pugl/cairo.hpp>
+
+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 <https://www.cairographics.org/manual/cairo-cairo-t.html>`_ can be accessed with :func:`View::context`:
+
+.. code-block:: cpp
+
+ cairo_t* cr = static_cast<cairo_t*>(view.context());
+
+Using OpenGL
+============
+
+OpenGL-specific API is declared in the ``gl.hpp`` header:
+
+.. code-block:: cpp
+
+ #include <pugl/gl.hpp>
+
+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 <pugl/vulkan.hpp>
+
+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 <https://developer.apple.com/documentation/appkit/nsview>`_,
+ which may be nested, and
+ `windows <https://developer.apple.com/documentation/appkit/nswindow>`_,
+ which may not.
+ On Windows and X11, everything is a nestable window,
+ but top-level windows are configured differently.
A pugl/doc/cpp/world.rst => pugl/doc/cpp/world.rst +41 -0
@@ 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");
D pugl/doc/cpp/wscript => pugl/doc/cpp/wscript +0 -44
@@ 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/")
A pugl/doc/cpp/xml/meson.build => pugl/doc/cpp/xml/meson.build +21 -0
@@ 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')
+
M pugl/doc/deployment.rst => pugl/doc/deployment.rst +8 -8
@@ 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``
D pugl/doc/mainpage.md => pugl/doc/mainpage.md +0 -77
@@ 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.
A pugl/doc/meson.build => pugl/doc/meson.build +13 -0
@@ 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
R pugl/doc/pugl.rst => pugl/doc/summary.rst +0 -4
@@ 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.
A pugl/examples/meson.build => pugl/examples/meson.build +80 -0
@@ 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
M pugl/examples/pugl_cairo_demo.c => pugl/examples/pugl_cairo_demo.c +1 -0
@@ 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());
M pugl/examples/pugl_cursor_demo.c => pugl/examples/pugl_cursor_demo.c +1 -0
@@ 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);
M pugl/examples/pugl_cxx_demo.cpp => pugl/examples/pugl_cxx_demo.cpp +1 -0
@@ 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);
M pugl/examples/pugl_shader_demo.c => pugl/examples/pugl_shader_demo.c +1 -0
@@ 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);
M pugl/examples/pugl_vulkan_cxx_demo.cpp => pugl/examples/pugl_vulkan_cxx_demo.cpp +1 -0
@@ 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();
M pugl/examples/pugl_window_demo.c => pugl/examples/pugl_window_demo.c +1 -0
@@ 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);
A pugl/examples/shaders/meson.build => pugl/examples/shaders/meson.build +35 -0
@@ 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
M pugl/include/pugl/pugl.h => pugl/include/pugl/pugl.h +12 -22
@@ 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
A pugl/meson.build => pugl/meson.build +455 -0
@@ 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 <X11/Xlib.h>
+ #include <X11/extensions/sync.h>
+ 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
A pugl/meson/meson.build => pugl/meson/meson.build +196 -0
@@ 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
A pugl/meson_options.txt => pugl/meson_options.txt +20 -0
@@ 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')
A pugl/scripts/cat.py => pugl/scripts/cat.py +7 -0
@@ 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())
M pugl/scripts/dox_to_sphinx.py => pugl/scripts/dox_to_sphinx.py +48 -61
@@ 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:])))
M pugl/src/x11.c => pugl/src/x11.c +14 -7
@@ 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);
}
A pugl/test/meson.build => pugl/test/meson.build +33 -0
@@ 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
M pugl/test/test_timer.c => pugl/test/test_timer.c +1 -1
@@ 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
D pugl/waf => pugl/waf +0 -27
@@ 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()
D pugl/wscript => pugl/wscript +0 -814
@@ 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 <X11/Xlib.h>
- #include <X11/extensions/sync.h>
- 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 <GL/glx.h>
- 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',