~hp/midi_matrix.lv2

29fa1c7a6f7ef63f2ee2bc5ad7efdb59d743252a — Hanspeter Portner 3 years ago ee3ba73
Squashed 'subprojects/nk_pugl/' changes from 2f43d058..26868708

26868708 Merge commit '2acfaf8a9d27d8b9938f610db033ce1fdc8bed65'
2acfaf8a Squashed 'pugl/' changes from 9a38c762..a5e1f34e

git-subtree-dir: subprojects/nk_pugl
git-subtree-split: 26868708dc0f4a8e1938229a109d8be176096f27
62 files changed, 2279 insertions(+), 2409 deletions(-)

M pugl/.gitignore
M pugl/.gitlab-ci.yml
M pugl/.gitmodules
M pugl/README.md
M pugl/bindings/cxx/include/pugl/cairo.hpp
M pugl/bindings/cxx/include/pugl/gl.hpp
M pugl/bindings/cxx/include/pugl/pugl.hpp
M pugl/bindings/cxx/include/pugl/stub.hpp
M pugl/bindings/cxx/include/pugl/vulkan.hpp
D pugl/doc/_static/custom.css
A pugl/doc/_static/meson.build
D pugl/doc/_templates/about.html
R pugl/doc/c/{Doxyfile => Doxyfile.in}
A pugl/doc/c/api/meson.build
A pugl/doc/c/event-loop.rst
A pugl/doc/c/events.rst
M pugl/doc/c/index.rst
A pugl/doc/c/meson.build
M pugl/doc/c/overview.rst
D pugl/doc/c/reference.rst
A pugl/doc/c/shutting-down.rst
A pugl/doc/c/view.rst
A pugl/doc/c/world.rst
D pugl/doc/c/wscript
A pugl/doc/c/xml/meson.build
M pugl/doc/conf.py.in
R pugl/doc/cpp/{Doxyfile => Doxyfile.in}
A pugl/doc/cpp/api/meson.build
D pugl/doc/cpp/c-reference.rst
D pugl/doc/cpp/cpp-reference.rst
A pugl/doc/cpp/event-loop.rst
A pugl/doc/cpp/events.rst
M pugl/doc/cpp/index.rst
A pugl/doc/cpp/meson.build
M pugl/doc/cpp/overview.rst
A pugl/doc/cpp/view.rst
A pugl/doc/cpp/world.rst
D pugl/doc/cpp/wscript
A pugl/doc/cpp/xml/meson.build
M pugl/doc/deployment.rst
D pugl/doc/mainpage.md
A pugl/doc/meson.build
R pugl/doc/{pugl.rst => summary.rst}
A pugl/examples/meson.build
M pugl/examples/pugl_cairo_demo.c
M pugl/examples/pugl_cursor_demo.c
M pugl/examples/pugl_cxx_demo.cpp
M pugl/examples/pugl_shader_demo.c
M pugl/examples/pugl_vulkan_cxx_demo.cpp
M pugl/examples/pugl_window_demo.c
A pugl/examples/shaders/meson.build
M pugl/include/pugl/pugl.h
A pugl/meson.build
A pugl/meson/meson.build
A pugl/meson_options.txt
A pugl/scripts/cat.py
M pugl/scripts/dox_to_sphinx.py
M pugl/src/x11.c
A pugl/test/meson.build
M pugl/test/test_timer.c
D pugl/waf
D pugl/wscript
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',
                          ['examples/pugl_vulkan_demo.c'],
                          platform,
                          'vulkan', uselib=['M', 'VULKAN', 'VULKAN_LIB'])

            if bld.env.CXX:
                build_example('pugl_vulkan_cxx_demo',
                              ['examples/pugl_vulkan_cxx_demo.cpp',
                               'examples/file_utils.c'],
                              platform, 'vulkan',
                              defines=['PUGL_STATIC', 'PUGL_DISABLE_DEPRECATED'],
                              uselib=['DL', 'M', 'PTHREAD', 'VULKAN'])

        if bld.env.HAVE_CAIRO:
            build_example('pugl_cairo_demo', ['examples/pugl_cairo_demo.c'],
                          platform, 'cairo',
                          uselib=['M', 'CAIRO'])

        for test in basic_tests:
            bld(features     = 'c cprogram',
                source       = 'test/test_%s.c' % test,
                target       = 'test/test_%s' % test,
                install_path = '',
                defines      = ['PUGL_STATIC'],
                use          = ['pugl_%s_static' % platform,
                                'pugl_%s_stub_static' % platform,
                                'pugl_%s_gl_static' % platform],
                uselib       = deps[platform]['uselib'] + ['CAIRO'])

        if bld.env.BUILD_STRICT_HEADER_TEST:
            # Make a hyper strict warning environment for checking API headers
            strict_env = bld.env.derive()
            autowaf.remove_all_warning_flags(strict_env)
            autowaf.enable_all_warnings(strict_env)
            autowaf.set_warnings_as_errors(strict_env)
            autowaf.add_compiler_flags(strict_env, '*', {
                'clang': ['-Wno-padded'],
                'gcc': ['-Wno-padded', '-Wno-suggest-attribute=const'],
                'msvc': [
                    '/wd4514',  # unreferenced inline function has been removed
                    '/wd4820',  # padding added after construct
                ],
            })
            autowaf.add_compiler_flags(strict_env, 'cxx', {
                'clang': ['-Wno-documentation-unknown-command'],
                'msvc': [
                    '/wd4355',  # 'this' used in base member initializer list
                    '/wd4571',  # structured exceptions are no longer caught
                ],
            })

            # Check that C headers build with (almost) no warnings
            bld(features     = 'c cprogram',
                source       = 'test/test_build.c',
                target       = 'test/test_build_c',
                install_path = '',
                env          = strict_env,
                use          = ['pugl_%s_static' % platform],
                uselib       = deps[platform]['uselib'] + ['CAIRO'])

            # Check that C++ headers build with (almost) no warnings
            bld(features     = 'cxx cxxprogram',
                source       = 'test/test_build.cpp',
                target       = 'test/test_build_cpp',
                install_path = '',
                env          = strict_env,
                includes     = ['bindings/cxx/include'],
                use          = ['pugl_%s_static' % platform],
                uselib       = deps[platform]['uselib'] + ['CAIRO'])

        if bld.env.CXX and bld.env.HAVE_GL:
            build_example('pugl_cxx_demo', ['examples/pugl_cxx_demo.cpp'],
                          platform, 'gl',
                          defines=['PUGL_DISABLE_DEPRECATED'],
                          uselib=['GL', 'M'])

    if bld.env.DOCS:
        bld.recurse('doc/c')
        bld.recurse('doc/cpp')


def test(tst):
    if tst.options.gui_tests:
        with tst.group('gui') as check:
            for test in basic_tests:
                check(['test/test_%s' % test])

            if tst.env.HAVE_GL:
                for test in gl_tests:
                    check(['test/test_%s' % test])


class LintContext(Build.BuildContext):
    fun = cmd = 'lint'


def lint(ctx):
    "checks code for style issues"
    import glob
    import json
    import subprocess

    st = 0

    if "FLAKE8" in ctx.env:
        Logs.info("Running flake8")
        st = subprocess.call([ctx.env.FLAKE8[0],
                              "wscript",
                              "--ignore",
                              "E221,W504,E251,E241,E741"])
    else:
        Logs.warn("Not running flake8")

    if "IWYU_TOOL" in ctx.env:
        Logs.info("Running include-what-you-use")
        cmd = [ctx.env.IWYU_TOOL[0], "-o", "clang", "-p", "build", "--",
               "-Xiwyu", "--check_also=*.hpp",
               "-Xiwyu", "--check_also=examples/*.hpp",
               "-Xiwyu", "--check_also=examples/*",
               "-Xiwyu", "--check_also=pugl/*.h",
               "-Xiwyu", "--check_also=bindings/cxx/include/pugl/*.*",
               "-Xiwyu", "--check_also=src/*.c",
               "-Xiwyu", "--check_also=src/*.h",
               "-Xiwyu", "--check_also=src/*.m"]
        output = subprocess.check_output(cmd).decode('utf-8')
        if 'error: ' in output:
            sys.stdout.write(output)
            st += 1
    else:
        Logs.warn("Not running include-what-you-use")

    if "CLANG_TIDY" in ctx.env and "clang" in ctx.env.CC[0]:
        Logs.info("Running clang-tidy")
        with open('build/compile_commands.json', 'r') as db:
            commands = json.load(db)
            files = [c['file'] for c in commands
                     if os.path.basename(c['file']) != 'glad.c']

            c_files = [os.path.join('build', f)
                       for f in files if f.endswith('.c')]

            c_files += glob.glob('include/pugl/*.h')
            c_files += glob.glob('src/implementation.h')

            cpp_files = [os.path.join('build', f)
                         for f in files if f.endswith('.cpp')]

            cpp_files += glob.glob('bindings/cxx/include/pugl/*')

        c_files = list(map(os.path.abspath, c_files))
        cpp_files = list(map(os.path.abspath, cpp_files))

        procs = []
        for c_file in c_files:
            cmd = [ctx.env.CLANG_TIDY[0], "--quiet", "-p=.", c_file]
            procs += [subprocess.Popen(cmd, cwd="build")]

        for cpp_file in cpp_files:
            cmd = [ctx.env.CLANG_TIDY[0],
                   '--header-filter=".*"',
                   "--quiet",
                   "-p=.", cpp_file]
            procs += [subprocess.Popen(cmd, cwd="build")]

        for proc in procs:
            stdout, stderr = proc.communicate()
            st += proc.returncode
    else:
        Logs.warn("Not running clang-tidy")

    if st != 0:
        sys.exit(st)


# Alias .m files to be compiled like .c files, gcc will do the right thing.
@TaskGen.extension('.m')
def m_hook(self, node):
    return self.create_compiled_task('c', node)