~hp/nk_pugl

bfea30b198e3ab18cc645914ff61ce5a6964755c — Hanspeter Portner 3 years ago b84d849
Squashed 'pugl/' changes from 8f28d8c9..1b1a1c3a

1b1a1c3a Call glXSwapIntervalEXT inside active context
44a7690c Move puglX11GlEnter() and puglX11GlLeave() above puglX11GlCreate()
181bd895 Fix clang-tidy warnings
d70aa80d Improve struct packing
abf44b12 Ensure that all API headers are warning-free
80838668 Update autowaf
98f8d255 Move stub backend function implementations to a detail header
90abfef1 Replace isHint bool with a flag
6c246041 Add scroll direction field
7d45962c Print crossing mode of enter and leave events
294c0f7d Replace grab flag in PuglEventFocus with crossing mode
eac255d8 Remove redundant focus field from PuglEventMotion
02f230d5 Remove count field from PuglEventExpose
72ffadb2 Use more precise warning flags
ad471068 Only enable MSVC C++ warnings when compiling C++ code
e9b269a2 Clean up and strengthen warning flags
f00254d4 Windows: Fix old-style declaration warning
3b497c84 Windows: Remove unused constants
3452f489 X11: Fix internal timer ID type to match public interface
be340fd0 X11: Fix integer to enum conversion warning
6a776bb6 X11: Fix sign conversion warning
87219173 Fix publicly accessible non-virtual destructor warning
d2c84ddf Enable Werror on CI
47b3bc94 Use ultra-strict warnings on CI
4dca7972 Mac: Disable Wno-direct-ivar-access
daa0f272 Mac: Fix implicit conversion warnings
5182a034 Mac: Declare instance variables in implementation
9483e340 Fix werror with clang and deprecation warnings on Mac
f53cdd77 Disable function type cast warnings with MinGW
eb79d768 Fix cast alignment warnings on 32-bit ARM
6422c76a Strengthen lint target
3b43e447 Fix check for clang
2d907d2c Mac: Only create an AutoreleasePool for programs
2f90b716 Add puglSetCursor()
81f9debf Fix mouse position of events on high resolution MacOS
eebb62db Disable implicit-fallthrough warning with clang
580e7b6e Cleanup: Fix implicit conversion warnings with clang
abcff5b7 Cleanup: Fix uninitialised variables
f036af07 Cleanup: Fix flake8 warnings
eb85b35c Add editorconfig file
21d8b08e Windows: Only call SwapBuffers with OpenGL
4bb9fc2c Windows: Fix maximum size
fe96ed3c Add default and maximum size
3200cda2 Fix indentation
a4ac15c1 Use line comments where appropriate
ceded7cf Use email address in copyright headers
85104b23 Fix file documentation
8d6c4141 Remove deprecated Doxygen configuration values
c30e1178 Fix redisplay test on MacOS
8fffa8f1 Make show/hide test tolerant to multiple exposures
6e42e48a C++ Demo: Fix sync option
cfdabdb6 C++ Demo: Fix help option
69366ae5 Set library environment for building against local Pugl
c737df9c Disable pedantic warnings only for shader demo (for GLAD)
14e959b0 Set library flags on targets only instead of globally
256e75d9 Clean up Mac flags
d26436ac Only set custom warnings flags with ultra-strict configuration
5e5d74d3 Add major version to library names
5695cb52 Remove deprecated pugl_stub_backend.h
bfa08201 Remove deprecated backend headers
29ba1095 Mac: Use high-resolution backing surfaces
53d8fe0c Implement puglSetTransientFor() for Mac and Windows
20fd80c8 Shader Demo: Support both GL 3 and 4
11800b61 Shader Demo: Use a UBO
ecc281c5 Shader Demo: Factor out version-dependent GLSL header
ac3036fd Shader Demo: Factor out animated rectangle definitions
84222d61 Shader Demo: Explicitly set up alpha blending
45477347 Fix mismatched printf parameter warning
a17e87f3 Update README
841b9705 Rewrite C++ bindings

git-subtree-dir: pugl
git-subtree-split: 1b1a1c3a48e31ad338d22b340408465843b25871
M .clang-format => .clang-format +2 -2
@@ 59,7 59,7 @@ Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat:   false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
FixNamespaceComments: true
ForEachMacros:
  - foreach
  - Q_FOREACH


@@ 104,7 104,7 @@ SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements

M .clang-tidy => .clang-tidy +7 -2
@@ 1,5 1,6 @@
Checks: >
  *,
  -*avoid-c-arrays,
  -*magic-numbers,
  -*uppercase-literal-suffix,
  -android-cloexec-fopen,


@@ 7,10 8,14 @@ Checks: >
  -cert-flp30-c,
  -clang-analyzer-alpha.*,
  -clang-analyzer-security.FloatLoopCounter,
  -google-runtime-references,
  -hicpp-multiway-paths-covered,
  -hicpp-signed-bitwise,
  -llvm-header-guard,
  -readability-else-after-return
  -modernize-use-trailing-return-type,
  -readability-else-after-return,
  -readability-implicit-bool-conversion,
  -readability-named-parameter,
WarningsAsErrors: ''
HeaderFilterRegex: 'pugl/.*|test/.*'
HeaderFilterRegex: 'pugl/.*|test/.*|examples/.*'
FormatStyle: file

A .editorconfig => .editorconfig +18 -0
@@ 0,0 1,18 @@
root = true

[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true

[*.{c,h,m,cpp,hpp,mm,glsl,frag,vert}]
indent_style = tab

[wscript]
indent_style = space
indent_size = 4

[doc/**.{html,xml,css}]
indent_style = space
indent_size = 2

M .gitlab-ci.yml => .gitlab-ci.yml +14 -14
@@ 11,7 11,7 @@ variables:
arm32_dbg:
  <<: *build_definition
  image: lv2plugin/debian-arm32
  script: python ./waf configure build -dsT --no-coverage
  script: python ./waf configure build -dST --werror --no-coverage
  variables:
    CC: "arm-linux-gnueabihf-gcc"
    CXX: "arm-linux-gnueabihf-g++"


@@ 19,7 19,7 @@ arm32_dbg:
arm32_rel:
  <<: *build_definition
  image: lv2plugin/debian-arm32
  script: python ./waf configure build -sT --no-coverage
  script: python ./waf configure build -ST --werror --no-coverage
  variables:
    CC: "arm-linux-gnueabihf-gcc"
    CXX: "arm-linux-gnueabihf-g++"


@@ 27,7 27,7 @@ arm32_rel:
arm64_dbg:
  <<: *build_definition
  image: lv2plugin/debian-arm64
  script: python ./waf configure build -dsT --no-coverage
  script: python ./waf configure build -dST --werror --no-coverage
  variables:
    CC: "aarch64-linux-gnu-gcc"
    CXX: "aarch64-linux-gnu-g++"


@@ 35,7 35,7 @@ arm64_dbg:
arm64_rel:
  <<: *build_definition
  image: lv2plugin/debian-arm64
  script: python ./waf configure build -sT --no-coverage
  script: python ./waf configure build -ST --werror --no-coverage
  variables:
    CC: "aarch64-linux-gnu-gcc"
    CXX: "aarch64-linux-gnu-g++"


@@ 43,7 43,7 @@ arm64_rel:
x64_dbg:
  <<: *build_definition
  image: lv2plugin/debian-x64
  script: python ./waf configure build -dsT --no-coverage --docs
  script: python ./waf configure build -dST --werror --no-coverage --docs
  artifacts:
    paths:
      - build/doc


@@ 51,12 51,12 @@ x64_dbg:
x64_rel:
  <<: *build_definition
  image: lv2plugin/debian-x64
  script: python ./waf configure build -sT --no-coverage
  script: python ./waf configure build -ST --werror --no-coverage

mingw32_dbg:
  <<: *build_definition
  image: lv2plugin/debian-mingw32
  script: python ./waf configure build -dsT --no-coverage --target=win32
  script: python ./waf configure build -dST --werror --no-coverage --target=win32
  variables:
    CC: "i686-w64-mingw32-gcc"
    CXX: "i686-w64-mingw32-g++"


@@ 64,7 64,7 @@ mingw32_dbg:
mingw32_rel:
  <<: *build_definition
  image: lv2plugin/debian-mingw32
  script: python ./waf configure build -sT --no-coverage --target=win32
  script: python ./waf configure build -ST --werror --no-coverage --target=win32
  variables:
    CC: "i686-w64-mingw32-gcc"
    CXX: "i686-w64-mingw32-g++"


@@ 72,7 72,7 @@ mingw32_rel:
mingw64_dbg:
  <<: *build_definition
  image: lv2plugin/debian-mingw64
  script: python ./waf configure build -dsT --no-coverage --target=win32
  script: python ./waf configure build -dST --werror --no-coverage --target=win32
  variables:
    CC: "x86_64-w64-mingw32-gcc"
    CXX: "x86_64-w64-mingw32-g++"


@@ 80,30 80,30 @@ mingw64_dbg:
mingw64_rel:
  <<: *build_definition
  image: lv2plugin/debian-mingw64
  script: python ./waf configure build -sT --no-coverage --target=win32
  script: python ./waf configure build -ST --werror --no-coverage --target=win32
  variables:
    CC: "x86_64-w64-mingw32-gcc"
    CXX: "x86_64-w64-mingw32-g++"

mac_dbg:
  <<: *build_definition
  script: python ./waf configure build -dsT --no-coverage
  script: python ./waf configure build -dST --werror --no-coverage
  tags: [macos]

mac_rel:
  <<: *build_definition
  script: python ./waf configure build -sT --no-coverage
  script: python ./waf configure build -ST --werror --no-coverage
  tags: [macos]

win_dbg:
  <<: *build_definition
  script:
    - python ./waf configure build -dT --no-coverage
    - python ./waf configure build -dST --werror --no-coverage
  tags: [windows,msvc,python]

win_rel:
  <<: *build_definition
  script: python ./waf configure build -T --no-coverage
  script: python ./waf configure build -ST --werror --no-coverage
  tags: [windows,msvc,python]

pages:

M AUTHORS => AUTHORS +2 -1
@@ 8,4 8,5 @@ Hanspeter Portner <dev@open-music-kontrollers.ch>
Stefan Westerfeld <stefan@space.twc.de>
Jordan Halase <jordan@halase.me>
Oliver Schmidt <oliver@luced.de>
Zoë Sparks <zoe@milky.flowers>
\ No newline at end of file
Zoë Sparks <zoe@milky.flowers>
Jean Pierre Cimalando <jp-dev@inbox.ru>

M COPYING => COPYING +1 -1
@@ 1,4 1,4 @@
Copyright 2011-2020 David Robillard <http://drobilla.net>
Copyright 2011-2020 David Robillard <d@drobilla.net>

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above

M README.md => README.md +15 -5
@@ 70,8 70,16 @@ developers can build portable plugin binaries.
Testing
-------

There are a few unit tests included which can be run with `python waf test
--gui-tests`, but unfortunately manual testing is still required.
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:

    ./waf configure --test

Then, after building, the unit tests can be run:

    ./waf
    ./waf test --gui-tests

Several example programs are included that serve as both manual tests and
demonstrations:


@@ 84,9 92,9 @@ demonstrations:

 * `pugl_window_demo` demonstrates multiple top-level windows.

 * `pugl_gl3_demo` demonstrates using more modern OpenGL where dynamic loading
   and shaders are required.  It can also be used to test performance by
   passing the number of rectangles to draw on the command line.
 * `pugl_shader_demo` demonstrates using more modern OpenGL (version 3 or 4)
   where dynamic loading and shaders are required.  It can also be used to test
   performance by passing the number of rectangles to draw on the command line.

 * `pugl_cairo_demo` demonstrates using Cairo on top of the native windowing
   system (without OpenGL), and partial redrawing.


@@ 94,6 102,8 @@ demonstrations:
 * `pugl_print_events` is a utility that prints all received events to the
   console in a human readable format.

 * `pugl_cxx_demo` is a simple cube demo that uses the C++ API.

All example programs support several command line options to control various
behaviours, see the output of `--help` for details.  Please file an issue if
any of these programs do not work as expected on your system.

M doc/layout.xml => doc/layout.xml +2 -1
@@ 25,6 25,7 @@
  <!-- Layout definition for a class page -->
  <class>
    <briefdescription visible="yes"/>
    <detaileddescription title=""/>
    <includes visible="$SHOW_INCLUDE_FILES"/>
    <inheritancegraph visible="$CLASS_GRAPH"/>
    <collaborationgraph visible="$COLLABORATION_GRAPH"/>


@@ 107,6 108,7 @@
  <!-- Layout definition for a file page -->
  <file>
    <briefdescription visible="yes"/>
    <detaileddescription title=""/>
    <includes visible="$SHOW_INCLUDE_FILES"/>
    <includegraph visible="$INCLUDE_GRAPH"/>
    <includedbygraph visible="$INCLUDED_BY_GRAPH"/>


@@ 122,7 124,6 @@
      <variables title=""/>
      <membergroups visible="yes"/>
    </memberdecl>
    <detaileddescription title=""/>
    <memberdef>
      <inlineclasses title=""/>
      <defines title=""/>

M doc/mainpage.md => doc/mainpage.md +2 -2
@@ 1,6 1,6 @@
This is the API documentation for Pugl.
This documentation is based around the [C API](@ref pugl_api),
there is also a [C++ API](@ref pugl) in the `pugl` namespace.
This page refers to the [C API](@ref pugl_c),
there is also a [C++ API](@ref pugl_cxx) in the `pugl` namespace.

The Pugl API revolves around two main objects:
the [World](@ref world) and the [View](@ref view).

M doc/reference.doxygen.in => doc/reference.doxygen.in +11 -17
@@ 243,12 243,6 @@ TAB_SIZE               = 4

ALIASES                =

# This tag can be used to specify a number of word-keyword mappings (TCL only).
# A mapping has the form "name=value". For example adding "class=itcl::class"
# will allow you to use the command class in the itcl::class meaning.

TCL_SUBST              =

# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For
# instance, some of the names that are used will be different. The list of all


@@ 447,7 441,7 @@ LOOKUP_CACHE_SIZE      = 0
# normally produced when WARNINGS is set to YES.
# The default value is: NO.

EXTRACT_ALL            = YES
EXTRACT_ALL            = NO

# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
# be included in the documentation.


@@ 498,7 492,7 @@ EXTRACT_ANON_NSPACES   = NO
# section is generated. This option has no effect if EXTRACT_ALL is enabled.
# The default value is: NO.

HIDE_UNDOC_MEMBERS     = YES
HIDE_UNDOC_MEMBERS     = NO

# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
# undocumented classes that are normally visible in the class hierarchy. If set


@@ 506,7 500,7 @@ HIDE_UNDOC_MEMBERS     = YES
# has no effect if EXTRACT_ALL is enabled.
# The default value is: NO.

HIDE_UNDOC_CLASSES     = YES
HIDE_UNDOC_CLASSES     = NO

# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
# (class|struct|union) declarations. If set to NO, these declarations will be


@@ 543,7 537,7 @@ CASE_SENSE_NAMES       = YES
# scope will be hidden.
# The default value is: NO.

HIDE_SCOPE_NAMES       = NO
HIDE_SCOPE_NAMES       = YES

# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
# append additional text to a page's title, such as Class Reference. If set to


@@ 752,7 746,7 @@ WARNINGS               = YES
# will automatically be disabled.
# The default value is: YES.

WARN_IF_UNDOCUMENTED   = YES
WARN_IF_UNDOCUMENTED   = NO

# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
# potential errors in the documentation, such as not documenting some parameters


@@ 769,7 763,7 @@ WARN_IF_DOC_ERROR      = YES
# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
# The default value is: NO.

WARN_NO_PARAMDOC       = YES
WARN_NO_PARAMDOC       = NO

# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
# a warning is encountered.


@@ 1165,7 1159,7 @@ HTML_EXTRA_FILES       =
# Minimum value: 0, maximum value: 359, default value: 220.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_COLORSTYLE_HUE    = 160
HTML_COLORSTYLE_HUE    = 120

# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
# in the HTML output. For a value of 0 the output will use grayscales only. A


@@ 1173,7 1167,7 @@ HTML_COLORSTYLE_HUE    = 160
# Minimum value: 0, maximum value: 255, default value: 100.
# This tag requires that the tag GENERATE_HTML is set to YES.

HTML_COLORSTYLE_SAT    = 100
HTML_COLORSTYLE_SAT    = 32

# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
# luminance component of the colors in the HTML output. Values below 100


@@ 1685,7 1679,7 @@ COMPACT_LATEX          = NO
# The default value is: a4.
# This tag requires that the tag GENERATE_LATEX is set to YES.

PAPER_TYPE             = a4wide
PAPER_TYPE             = a4

# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
# that should be included in the LaTeX output. The package can be specified just


@@ 1930,7 1924,7 @@ MAN_LINKS              = NO
# captures the structure of the code including all documentation.
# The default value is: NO.

GENERATE_XML           = NO
GENERATE_XML           = YES

# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of


@@ 2090,7 2084,7 @@ INCLUDE_FILE_PATTERNS  =
# recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.

PREDEFINED             = PUGL_API PUGL_DISABLE_DEPRECATED
PREDEFINED             = PUGL_API PUGL_DISABLE_DEPRECATED PUGL_CONST_FUNC=

# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The

M doc/style.css => doc/style.css +64 -9
@@ 233,6 233,14 @@ div.groupHeader {
  font-weight: 700;
}

h2.groupheader {
  line-height: 1.18em;
  font-size: 1em;
  font-weight: 600;
  font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif;
  margin: 1.25em 0 0.5em 0;
}

a + h2.groupheader {
  display: none;
}


@@ 387,6 395,18 @@ table.memberdecls {
  line-height: 1.3em;
}

table.memberdecls h3 {
  line-height: 1.18em;
  font-size: 1em;
  font-weight: 600;
  font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif;
  margin: 1.25em 0 0.5em 0;
}

tr.inherit_header td {
  padding: 1em 0 0.5em 0;
}

.mdescLeft,.mdescRight,.memItemLeft,.memItemRight,.memTemplItemLeft,.memTemplItemRight,.memTemplParams {
  margin: 0;
  padding: 0;


@@ 419,13 439,28 @@ td.memSeparator {
  display: none;
}

td.mlabels-left {
  margin-left: 0;
  padding-left: 0;
}

td.mlabels-right {
  vertical-align: top;
  padding-top: 4px;
  color: #B4C342;
  font-weight: normal;
  margin-left: 1em;
  vertical-align: bottom;
}

.memtitle {
  border-bottom: 1px solid #EEE;
  font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif;
  font-size: 1.18em;
  font-weight: 600;
  line-height: 1.41em;
  margin: 1.5em 0 0 0;
}

.permalink {
  display: none;
}



@@ 446,18 481,20 @@ td.mlabels-right {
}

.memitem {
  padding: 0.5em 0.5em 0.25em 0.5em;
  margin: 1em 0 2em 0;
  padding: 0;
  margin: 0 0 3em 0;
}

.memproto {
  border-bottom: 1px solid #EEE;
  border-left: 1px solid #EEE;
  color: #444;
  float: right;
  font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed;
  font-size: 1.09em;
  font-weight: 600;
  line-height: 1.41em;
  margin-bottom: 0.25em;
  padding-bottom: 0.125em;
  font-size: small;
  margin-bottom: 1em;
  margin-left: 1em;
  padding: 0.25em 0 0.25em 0.25em;
}

.memproto .paramname {


@@ 465,6 502,11 @@ td.mlabels-right {
  padding-right: 0.25em;
}

.mlabels {
  padding-left: 0;
  padding-right: 0;
}

.memdoc {
  padding: 0;
}


@@ 473,6 515,19 @@ td.mlabels-right {
  font-style: italic;
  color: #444;
  margin-bottom: 0.75em;
  margin-top: 0;
  padding-top: 0.25em;
  font-weight: normal;
}

.memdoc > p:first-child, .memdoc .textblock > h3:first-child {
  color: #444;
  margin-bottom: 0.75em;
  margin-top: 0;
  padding-top: 0.25em;
  font-weight: normal;
  color: #444;
  font-size: 0.9em;
}

.paramkey {

M examples/cube_view.h => examples/cube_view.h +1 -1
@@ 1,5 1,5 @@
/*
  Copyright 2012-2020 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above

M examples/demo_utils.h => examples/demo_utils.h +2 -2
@@ 1,5 1,5 @@
/*
  Copyright 2012-2019 David Robillard <http://drobilla.net>
  Copyright 2012-2019 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 135,7 135,7 @@ mat4Ortho(mat4        m,
	m[3][3] = 1.0f;
}

/** Calculate a projection matrix for a given perspective. */
/// Calculate a projection matrix for a given perspective
static inline void
perspective(float* m, float fov, float aspect, float zNear, float zFar)
{

M examples/pugl_cairo_demo.c => examples/pugl_cairo_demo.c +6 -6
@@ 1,5 1,5 @@
/*
  Copyright 2012-2020 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file pugl_cairo_demo.c An example of drawing with Cairo.
   @file pugl_cairo_demo.c
   @brief An example of drawing with Cairo.
*/

#include "demo_utils.h"


@@ 32,8 33,8 @@
#include <string.h>

typedef struct {
	PuglTestOptions opts;
	PuglWorld*      world;
	PuglTestOptions opts;
	unsigned        framesDrawn;
	int             quit;
	bool            entered;


@@ 235,11 236,10 @@ main(int argc, char** argv)
	app.world = puglNewWorld(PUGL_PROGRAM, 0);
	puglSetClassName(app.world, "PuglCairoTest");

	PuglRect  frame = { 0, 0, 512, 512 };
	PuglView* view  = puglNewView(app.world);
	PuglView* view = puglNewView(app.world);

	puglSetWindowTitle(view, "Pugl Cairo Demo");
	puglSetFrame(view, frame);
	puglSetDefaultSize(view, 512, 512);
	puglSetMinSize(view, 256, 256);
	puglSetViewHint(view, PUGL_RESIZABLE, app.opts.resizable);
	puglSetHandle(view, &app);

A examples/pugl_cursor_demo.c => examples/pugl_cursor_demo.c +171 -0
@@ 0,0 1,171 @@
/*
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.

  THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

/**
   @file pugl_cursor_demo.c
   @brief An example of changing the mouse cursor.
*/

#include "test/test_utils.h"

#include "pugl/gl.h"
#include "pugl/pugl.h"
#include "pugl/pugl_gl.h"

#include <stdbool.h>

static const int N_CURSORS = 7;
static const int N_ROWS    = 2;
static const int N_COLS    = 4;

typedef struct {
	PuglWorld*      world;
	PuglTestOptions opts;
	bool            quit;
} PuglTestApp;

static void
onConfigure(const double width, const double height)
{
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LESS);
	glClearColor(0.2f, 0.2f, 0.2f, 1.0f);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glViewport(0, 0, (int)width, (int)height);
}

static void
onExpose(void)
{
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glColor3f(0.6f, 0.6f, 0.6f);

	for (int row = 1; row < N_ROWS; ++row) {
		const float y = (float)row * (2.0f / (float)N_ROWS) - 1.0f;
		glBegin(GL_LINES);
		glVertex2f(-1.0f, y);
		glVertex2f(1.0f, y);
		glEnd();
	}

	for (int col = 1; col < N_COLS; ++col) {
		const float x = (float)col * (2.0f / (float)N_COLS) - 1.0f;
		glBegin(GL_LINES);
		glVertex2f(x, -1.0f);
		glVertex2f(x, 1.0f);
		glEnd();
	}
}

static void
onMotion(PuglView* view, double x, double y)
{
	const PuglRect frame = puglGetFrame(view);
	int            row   = (int)(y * N_ROWS / frame.height);
	int            col   = (int)(x * N_COLS / frame.width);

	row = (row < 0) ? 0 : (row >= N_ROWS) ? (N_ROWS - 1) : row;
	col = (col < 0) ? 0 : (col >= N_COLS) ? (N_COLS - 1) : col;

	const PuglCursor cursor = (PuglCursor)((row * N_COLS + col) % N_CURSORS);
	puglSetCursor(view, cursor);
}

static PuglStatus
onEvent(PuglView* view, const PuglEvent* event)
{
	PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);

	printEvent(event, "Event: ", app->opts.verbose);

	switch (event->type) {
	case PUGL_CONFIGURE:
		onConfigure(event->configure.width, event->configure.height);
		break;
	case PUGL_KEY_PRESS:
		if (event->key.key == 'q' || event->key.key == PUGL_KEY_ESCAPE) {
			app->quit = 1;
		}
		break;
	case PUGL_MOTION:
		onMotion(view, event->motion.x, event->motion.y);
		break;
	case PUGL_EXPOSE:
		onExpose();
		break;
	case PUGL_CLOSE:
		app->quit = 1;
		break;
	default:
		break;
	}

	return PUGL_SUCCESS;
}

int
main(int argc, char** argv)
{
	PuglTestApp app = {0};

	app.opts = puglParseTestOptions(&argc, &argv);
	if (app.opts.help) {
		puglPrintTestUsage(argv[0], "");
		return 1;
	}

	app.world = puglNewWorld(PUGL_PROGRAM, 0);

	puglSetWorldHandle(app.world, &app);
	puglSetClassName(app.world, "Pugl Test");

	PuglView* view = puglNewView(app.world);

	puglSetWindowTitle(view, "Pugl Window Demo");
	puglSetDefaultSize(view, 512, 256);
	puglSetMinSize(view, 128, 64);
	puglSetBackend(view, puglGlBackend());

	puglSetViewHint(view, PUGL_USE_DEBUG_CONTEXT, app.opts.errorChecking);
	puglSetViewHint(view, PUGL_RESIZABLE, app.opts.resizable);
	puglSetViewHint(view, PUGL_SAMPLES, app.opts.samples);
	puglSetViewHint(view, PUGL_DOUBLE_BUFFER, app.opts.doubleBuffer);
	puglSetViewHint(view, PUGL_SWAP_INTERVAL, app.opts.sync);
	puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, app.opts.ignoreKeyRepeat);
	puglSetHandle(view, &app);
	puglSetEventFunc(view, onEvent);

	const PuglStatus st = puglRealize(view);
	if (st) {
		return logError("Failed to create window (%s)\n", puglStrerror(st));
	}

	puglShowWindow(view);

	while (!app.quit) {
		puglUpdate(app.world, -1.0);
	}

	puglFreeView(view);
	puglFreeWorld(app.world);

	return 0;
}

A examples/pugl_cxx_demo.cpp => examples/pugl_cxx_demo.cpp +146 -0
@@ 0,0 1,146 @@
/*
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.

  THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

/**
   @file pugl_cxx_demo.cpp
   @brief A simple demo of the Pugl C++ API.
*/

#include "cube_view.h"
#include "demo_utils.h"
#include "test/test_utils.h"

#include "pugl/gl.h"
#include "pugl/pugl.h"
#include "pugl/pugl.hpp"
#include "pugl/pugl.ipp"
#include "pugl/pugl_gl.hpp"

#include <cmath>

class CubeView : public pugl::View
{
public:
	explicit CubeView(pugl::World& world)
	    : pugl::View{world}
	{}

	pugl::Status onConfigure(const pugl::ConfigureEvent& event) override;
	pugl::Status onUpdate(const pugl::UpdateEvent& event) override;
	pugl::Status onExpose(const pugl::ExposeEvent& event) override;
	pugl::Status onKeyPress(const pugl::KeyPressEvent& event) override;
	pugl::Status onClose(const pugl::CloseEvent& event) override;

	bool quit() const { return _quit; }

private:
	double _xAngle{0.0};
	double _yAngle{0.0};
	double _lastDrawTime{0.0};
	bool   _quit{false};
};

pugl::Status
CubeView::onConfigure(const pugl::ConfigureEvent& event)
{
	reshapeCube(static_cast<float>(event.width),
	            static_cast<float>(event.height));

	return pugl::Status::success;
}

pugl::Status
CubeView::onUpdate(const pugl::UpdateEvent&)
{
	return postRedisplay();
}

pugl::Status
CubeView::onExpose(const pugl::ExposeEvent&)
{
	const double thisTime = world().time();
	const double dTime    = thisTime - _lastDrawTime;
	const double dAngle   = dTime * 100.0;

	_xAngle = fmod(_xAngle + dAngle, 360.0);
	_yAngle = fmod(_yAngle + dAngle, 360.0);
	displayCube(cobj(),
	            8.0f,
	            static_cast<float>(_xAngle),
	            static_cast<float>(_yAngle),
	            false);

	_lastDrawTime = thisTime;

	return pugl::Status::success;
}

pugl::Status
CubeView::onKeyPress(const pugl::KeyPressEvent& event)
{
	if (event.key == PUGL_KEY_ESCAPE || event.key == 'q') {
		_quit = true;
	}

	return pugl::Status::success;
}

pugl::Status
CubeView::onClose(const pugl::CloseEvent&)
{
	_quit = true;

	return pugl::Status::success;
}

int
main(int argc, char** argv)
{
	const PuglTestOptions opts = puglParseTestOptions(&argc, &argv);
	if (opts.help) {
		puglPrintTestUsage("pugl_cxx_demo", "");
		return 1;
	}

	pugl::World    world{pugl::WorldType::program};
	CubeView       view{world};
	PuglFpsPrinter fpsPrinter{};

	world.setClassName("PuglCppTest");

	view.setWindowTitle("Pugl C++ Test");
	view.setDefaultSize(512, 512);
	view.setMinSize(64, 64);
	view.setAspectRatio(1, 1, 16, 9);
	view.setBackend(pugl::glBackend());
	view.setHint(pugl::ViewHint::resizable, opts.resizable);
	view.setHint(pugl::ViewHint::samples, opts.samples);
	view.setHint(pugl::ViewHint::doubleBuffer, opts.doubleBuffer);
	view.setHint(pugl::ViewHint::swapInterval, opts.sync);
	view.setHint(pugl::ViewHint::ignoreKeyRepeat, opts.ignoreKeyRepeat);
	view.realize();
	view.showWindow();

	unsigned framesDrawn = 0;
	while (!view.quit()) {
		world.update(0.0);

		++framesDrawn;
		puglPrintFps(world.cobj(), &fpsPrinter, &framesDrawn);
	}

	return 0;
}

M examples/pugl_embed_demo.c => examples/pugl_embed_demo.c +8 -6
@@ 1,5 1,5 @@
/*
  Copyright 2012-2020 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file pugl_embed_demo.c An example of embedding a view in another.
   @file pugl_embed_demo.c
   @brief An example of embedding a view in another.
*/

#include "cube_view.h"


@@ 40,14 41,14 @@ typedef struct
	PuglWorld* world;
	PuglView*  parent;
	PuglView*  child;
	bool       continuous;
	int        quit;
	double     xAngle;
	double     yAngle;
	float      dist;
	double     lastMouseX;
	double     lastMouseY;
	double     lastDrawTime;
	float      dist;
	int        quit;
	bool       continuous;
	bool       mouseEntered;
	bool       verbose;
	bool       reversing;


@@ 292,8 293,9 @@ main(int argc, char** argv)
	puglSetClassName(app.world, "Pugl Test");

	const PuglRect parentFrame = { 0, 0, 512, 512 };
	puglSetFrame(app.parent, parentFrame);
	puglSetDefaultSize(app.parent, 512, 512);
	puglSetMinSize(app.parent, borderWidth * 3, borderWidth * 3);
	puglSetMaxSize(app.parent, 1024, 1024);
	puglSetAspectRatio(app.parent, 1, 1, 16, 9);
	puglSetBackend(app.parent, puglGlBackend());


M examples/pugl_print_events.c => examples/pugl_print_events.c +3 -2
@@ 1,5 1,5 @@
/*
  Copyright 2012-2020 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file pugl_print_events.c A utility program that prints view events.
   @file pugl_print_events.c
   @brief A utility program that prints view events.
*/

#include "test/test_utils.h"

R examples/pugl_gl3_demo.c => examples/pugl_shader_demo.c +58 -65
@@ 1,5 1,5 @@
/*
  Copyright 2012-2020 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file pugl_gl3_demo.c An example of drawing with OpenGL 3.
   @file pugl_shader_demo.c
   @brief An example of drawing with OpenGL 3/4.

   This is an example of using OpenGL for pixel-perfect 2D drawing.  It uses
   pixel coordinates for positions and sizes so that things work roughly like a


@@ 35,6 36,7 @@
*/

#include "demo_utils.h"
#include "rects.h"
#include "shader_utils.h"
#include "test/test_utils.h"



@@ 44,7 46,6 @@
#include "pugl/pugl.h"
#include "pugl/pugl_gl.h"

#include <math.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>


@@ 55,27 56,14 @@ static const int defaultHeight = 512;

typedef struct
{
	float pos[2];
	float size[2];
	float fillColor[4];
} Rect;

// clang-format off
static const GLfloat rectVertices[] = {
	0.0f, 0.0f, // TL
	1.0f, 0.0f, // TR
	0.0f, 1.0f, // BL
	1.0f, 1.0f, // BR
};
// clang-format on

static const GLuint rectIndices[4] = {0, 1, 2, 3};
	mat4 projection;
} RectUniforms;

typedef struct
{
	PuglTestOptions opts;
	PuglWorld*      world;
	PuglView*       view;
	PuglTestOptions opts;
	size_t          numRects;
	Rect*           rects;
	Program         drawRect;


@@ 83,8 71,9 @@ typedef struct
	GLuint          vbo;
	GLuint          instanceVbo;
	GLuint          ibo;
	GLint           u_projection;
	unsigned        framesDrawn;
	int             glMajorVersion;
	int             glMinorVersion;
	int             quit;
} PuglTestApp;



@@ 100,7 89,8 @@ onConfigure(PuglView* view, double width, double height)
	(void)view;

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
	glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glViewport(0, 0, (int)width, (int)height);
}


@@ 129,25 119,12 @@ onExpose(PuglView* view)
	glUseProgram(app->drawRect.program);
	glBindVertexArray(app->vao);

	// Set projection matrix uniform
	glUniformMatrix4fv(app->u_projection, 1, GL_FALSE, (const GLfloat*)&proj);

	for (size_t i = 0; i < app->numRects; ++i) {
		Rect*       rect      = &app->rects[i];
		const float normal    = i / (float)app->numRects;
		const float offset[2] = {normal * 128.0f, normal * 128.0f};

		// Move rect around in an arbitrary way that looks cool
		rect->pos[0] = (width - rect->size[0] + offset[0]) *
		               (sinf((float)time * rect->size[0] / 64.0f + normal) +
		                1.0f) /
		               2.0f;
		rect->pos[1] = (height - rect->size[1] + offset[1]) *
		               (cosf((float)time * rect->size[1] / 64.0f + normal) +
		                1.0f) /
		               2.0f;
		moveRect(&app->rects[i], i, app->numRects, width, height, time);
	}

	glBufferData(GL_UNIFORM_BUFFER, sizeof(proj), &proj, GL_STREAM_DRAW);

	glBufferSubData(GL_ARRAY_BUFFER,
	                0,
	                (GLsizeiptr)(app->numRects * sizeof(Rect)),


@@ 198,20 175,9 @@ onEvent(PuglView* view, const PuglEvent* event)
static Rect*
makeRects(const size_t numRects)
{
	const float minSize  = (float)defaultWidth / 64.0f;
	const float maxSize  = (float)defaultWidth / 6.0f;
	const float boxAlpha = 0.2f;

	Rect* rects = (Rect*)calloc(numRects, sizeof(Rect));
	for (size_t i = 0; i < numRects; ++i) {
		const float s = (sinf((float)i) / 2.0f + 0.5f);
		const float c = (cosf((float)i) / 2.0f + 0.5f);

		rects[i].size[0]      = minSize + s * maxSize;
		rects[i].size[1]      = minSize + c * maxSize;
		rects[i].fillColor[1] = s / 2.0f + 0.25f;
		rects[i].fillColor[2] = c / 2.0f + 0.25f;
		rects[i].fillColor[3] = boxAlpha;
		rects[i] = makeRect(i, (float)defaultWidth);
	}

	return rects;


@@ 241,6 207,8 @@ loadShader(const char* const path)
static int
parseOptions(PuglTestApp* app, int argc, char** argv)
{
	char* endptr = NULL;

	// Parse command line options
	app->numRects = 1024;
	app->opts     = puglParseTestOptions(&argc, &argv);


@@ 249,11 217,24 @@ parseOptions(PuglTestApp* app, int argc, char** argv)
	}

	// Parse number of rectangles argument, if given
	if (argc == 1) {
		char* endptr = NULL;

	if (argc >= 1) {
		app->numRects = (size_t)strtol(argv[0], &endptr, 10);
		if (endptr != argv[0] + strlen(argv[0])) {
			logError("Invalid number of rectangles: %s\n", argv[0]);
			return 1;
		}
	}

	// Parse OpenGL major version argument, if given
	if (argc >= 2) {
		app->glMajorVersion = (int)strtol(argv[1], &endptr, 10);
		if (endptr != argv[1] + strlen(argv[1])) {
			logError("Invalid GL major version: %s\n", argv[1]);
			return 1;
		} else if (app->glMajorVersion == 4) {
			app->glMinorVersion = 2;
		} else if (app->glMajorVersion != 3) {
			logError("Unsupported GL major version %d\n", app->glMajorVersion);
			return 1;
		}
	}


@@ 262,7 243,7 @@ parseOptions(PuglTestApp* app, int argc, char** argv)
}

static void
setupPugl(PuglTestApp* app, const PuglRect frame)
setupPugl(PuglTestApp* app)
{
	// Create world, view, and rect data
	app->world = puglNewWorld(PUGL_PROGRAM, 0);


@@ 272,14 253,14 @@ setupPugl(PuglTestApp* app, const PuglRect frame)
	// Set up world and view
	puglSetClassName(app->world, "PuglGL3Demo");
	puglSetWindowTitle(app->view, "Pugl OpenGL 3");
	puglSetFrame(app->view, frame);
	puglSetDefaultSize(app->view, defaultWidth, defaultHeight);
	puglSetMinSize(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);
	puglSetViewHint(app->view, PUGL_USE_DEBUG_CONTEXT, app->opts.errorChecking);
	puglSetViewHint(app->view, PUGL_CONTEXT_VERSION_MAJOR, 3);
	puglSetViewHint(app->view, PUGL_CONTEXT_VERSION_MINOR, 3);
	puglSetViewHint(app->view, PUGL_CONTEXT_VERSION_MAJOR, app->glMajorVersion);
	puglSetViewHint(app->view, PUGL_CONTEXT_VERSION_MINOR, app->glMinorVersion);
	puglSetViewHint(app->view, PUGL_RESIZABLE, app->opts.resizable);
	puglSetViewHint(app->view, PUGL_SAMPLES, app->opts.samples);
	puglSetViewHint(app->view, PUGL_DOUBLE_BUFFER, app->opts.doubleBuffer);


@@ 298,7 279,12 @@ setupGl(PuglTestApp* app)
		return PUGL_FAILURE;
	}

	const char* const headerFile = (app->glMajorVersion == 3
	                                ? "shaders/header_330.glsl"
	                                : "shaders/header_420.glsl");

	// Load shader sources
	char* const headerSource   = loadShader(headerFile);
	char* const vertexSource   = loadShader("shaders/rect.vert");
	char* const fragmentSource = loadShader("shaders/rect.frag");
	if (!vertexSource || !fragmentSource) {


@@ 307,16 293,23 @@ setupGl(PuglTestApp* app)
	}

	// Compile rectangle shaders and program
	app->drawRect = compileProgram(vertexSource, fragmentSource);
	app->drawRect = compileProgram(headerSource, vertexSource, fragmentSource);
	free(fragmentSource);
	free(vertexSource);
	free(headerSource);
	if (!app->drawRect.program) {
		return PUGL_FAILURE;
	}

	// Get location of rectangle shader uniforms
	app->u_projection =
	        glGetUniformLocation(app->drawRect.program, "u_projection");
	// Get location of rectangle shader uniform block
	const GLuint globalsIndex = glGetUniformBlockIndex(app->drawRect.program,
	                                                   "UniformBufferObject");

	// Generate/bind a uniform buffer for setting rectangle properties
	GLuint uboHandle = 0;
	glGenBuffers(1, &uboHandle);
	glBindBuffer(GL_UNIFORM_BUFFER, uboHandle);
	glBindBufferBase(GL_UNIFORM_BUFFER, globalsIndex, uboHandle);

	// Generate/bind a VAO to track state
	glGenVertexArrays(1, &app->vao);


@@ 391,19 384,19 @@ teardownGl(PuglTestApp* app)
int
main(int argc, char** argv)
{
	PuglTestApp app;
	memset(&app, 0, sizeof(app));
	PuglTestApp app = {0};

	const PuglRect frame = {0, 0, defaultWidth, defaultHeight};
	app.glMajorVersion = 3;
	app.glMinorVersion = 3;

	// Parse command line options
	if (parseOptions(&app, argc, argv)) {
		puglPrintTestUsage("pugl_gl3_demo", "[NUM_RECTS]");
		puglPrintTestUsage("pugl_shader_demo", "[NUM_RECTS] [GL_MAJOR]");
		return 1;
	}

	// Create and configure world and view
	setupPugl(&app, frame);
	setupPugl(&app);

	// Create window (which will send a PUGL_CREATE event)
	const PuglStatus st = puglRealize(app.view);

M examples/pugl_window_demo.c => examples/pugl_window_demo.c +18 -10
@@ 1,5 1,5 @@
/*
  Copyright 2012-2020 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file pugl_window_demo.c A demonstration of multiple Pugl windows.
   @file pugl_window_demo.c
   @brief A demonstration of multiple Pugl windows.
*/

#include "cube_view.h"


@@ 33,21 34,23 @@ typedef struct {
	PuglView* view;
	double    xAngle;
	double    yAngle;
	float     dist;
	double    lastMouseX;
	double    lastMouseY;
	double    lastDrawTime;
	float     dist;
	bool      entered;
} CubeView;

typedef struct {
	PuglWorld* world;
	CubeView   cubes[2];
	bool       continuous;
	int        quit;
	bool       continuous;
	bool       verbose;
} PuglTestApp;

static const double pad = 64.0;

static void
onDisplay(PuglView* view)
{


@@ 199,19 202,19 @@ main(int argc, char** argv)
	puglSetClassName(app.world, "Pugl Test");

	PuglStatus st = PUGL_SUCCESS;
	for (size_t i = 0; i < 2; ++i) {
	for (unsigned i = 0; i < 2; ++i) {
		CubeView*      cube  = &app.cubes[i];
		PuglView*      view  = cube->view;
		static const double pad = 64.0;
		const PuglRect frame = {pad + (256.0 + pad) * i,
		                        pad + (256.0 + pad) * i,
		                        256.0,
		                        256.0};
		const PuglRect frame = {pad + (128.0 + pad) * i,
		                        pad + (128.0 + pad) * i,
		                        512.0,
		                        512.0};

		cube->dist = 10;

		puglSetWindowTitle(view, "Pugl Window Demo");
		puglSetFrame(view, frame);
		puglSetDefaultSize(view, 512, 512);
		puglSetMinSize(view, 128, 128);
		puglSetBackend(view, puglGlBackend());



@@ 224,6 227,11 @@ main(int argc, char** argv)
		puglSetHandle(view, cube);
		puglSetEventFunc(view, onEvent);

		if (i == 1) {
			puglSetTransientFor(app.cubes[1].view,
			                    puglGetNativeWindow(app.cubes[0].view));
		}

		if ((st = puglRealize(view))) {
			return logError("Failed to create window (%s)\n", puglStrerror(st));
		}

A examples/rects.h => examples/rects.h +82 -0
@@ 0,0 1,82 @@
/*
  Copyright 2019-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.

  THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

/**
   @file rects.h
   @brief Utilities for rectangle animation demos.

   This file contains common definitions for demos that show an animation of
   many 2D rectangles.
*/

#include <math.h>
#include <stddef.h>

typedef float vec2[2];

typedef struct {
	float pos[2];
	float size[2];
	float fillColor[4];
} Rect;

static const vec2 rectVertices[] = {
    {0.0f, 0.0f}, // TL
    {1.0f, 0.0f}, // TR
    {0.0f, 1.0f}, // BL
    {1.0f, 1.0f}  // BR
};

static const unsigned rectIndices[4] = {0, 1, 2, 3};

/// Make a new rectangle with the given index (each is slightly different)
static inline Rect
makeRect(const size_t index, const float frameWidth)
{
	static const float alpha   = 0.3f;
	const float        minSize = frameWidth / 64.0f;
	const float        maxSize = frameWidth / 6.0f;
	const float        s       = (sinf((float)index) / 2.0f + 0.5f);
	const float        c       = (cosf((float)index) / 2.0f + 0.5f);

	const Rect rect = {
	    {0.0f, 0.0f}, // Position is set later during expose
	    {minSize + s * maxSize, minSize + c * maxSize},
	    {0.0f, s / 2.0f + 0.25f, c / 2.0f + 0.25f, alpha},
	};

	return rect;
}

/// Move `rect` with the given index around in an arbitrary way that looks cool
static inline void
moveRect(Rect* const  rect,
         const size_t index,
         const size_t numRects,
         const float  frameWidth,
         const float  frameHeight,
         const double time)
{
	const float normal    = (float)index / (float)numRects;
	const float offset[2] = {normal * 128.0f, normal * 128.0f};

	rect->pos[0] = (frameWidth - rect->size[0] + offset[0]) *
	               (sinf((float)time * rect->size[0] / 64.0f + normal) + 1.0f) /
	               2.0f;
	rect->pos[1] = (frameHeight - rect->size[1] + offset[1]) *
	               (cosf((float)time * rect->size[1] / 64.0f + normal) + 1.0f) /
	               2.0f;
}

M examples/shader_utils.h => examples/shader_utils.h +18 -13
@@ 1,5 1,5 @@
/*
  Copyright 2019 David Robillard <http://drobilla.net>
  Copyright 2019-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 28,17 28,18 @@ typedef struct
} Program;

static GLuint
compileShader(const char* source, const GLenum type)
compileShader(const char* header, const char* source, const GLenum type)
{
	GLuint    shader       = glCreateShader(type);
	const int sourceLength = (int)strlen(source);
	glShaderSource(shader, 1, &source, &sourceLength);
	const GLchar* sources[] = {header, source};
	const GLint   lengths[] = {(GLint)strlen(header), (GLint)strlen(source)};
	GLuint        shader    = glCreateShader(type);
	glShaderSource(shader, 2, sources, lengths);
	glCompileShader(shader);

	int status;
	int status = 0;
	glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
	if (status == GL_FALSE) {
		GLint length;
		GLint length = 0;
		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);

		char* log = (char*)calloc(1, (size_t)length);


@@ 61,13 62,17 @@ deleteProgram(Program program)
}

static Program
compileProgram(const char* vertexSource, const char* fragmentSource)
compileProgram(const char* headerSource,
               const char* vertexSource,
               const char* fragmentSource)
{
	static const Program nullProgram = {0, 0, 0};

	Program program = {compileShader(vertexSource, GL_VERTEX_SHADER),
	                   compileShader(fragmentSource, GL_FRAGMENT_SHADER),
	                   glCreateProgram()};
	Program program = {
	    compileShader(headerSource, vertexSource, GL_VERTEX_SHADER),
	    compileShader(headerSource, fragmentSource, GL_FRAGMENT_SHADER),
	    glCreateProgram(),
	};

	if (!program.vertexShader || !program.fragmentShader || !program.program) {
		deleteProgram(program);


@@ 78,10 83,10 @@ compileProgram(const char* vertexSource, const char* fragmentSource)
	glAttachShader(program.program, program.fragmentShader);
	glLinkProgram(program.program);

	GLint status;
	GLint status = 0;
	glGetProgramiv(program.program, GL_LINK_STATUS, &status);
	if (status == GL_FALSE) {
		GLint length;
		GLint length = 0;
		glGetProgramiv(program.program, GL_INFO_LOG_LENGTH, &length);

		char* log = (char*)calloc(1, (size_t)length);

M pugl/detail/implementation.c => pugl/detail/implementation.c +10 -8
@@ 1,5 1,5 @@
/*
  Copyright 2012-2020 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file implementation.c Platform-independent implementation.
   @file implementation.c
   @brief Platform-independent implementation.
*/

#include "pugl/detail/implementation.h"


@@ 60,6 61,7 @@ puglStrerror(const PuglStatus status)
	case PUGL_FAILURE:               return "Non-fatal failure";
	case PUGL_UNKNOWN_ERROR:         return "Unknown system error";
	case PUGL_BAD_BACKEND:           return "Invalid or missing backend";
	case PUGL_BAD_CONFIGURATION:     return "Invalid view configuration";
	case PUGL_BAD_PARAMETER:         return "Invalid parameter";
	case PUGL_BACKEND_FAILED:        return "Backend initialisation failed";
	case PUGL_REGISTRATION_FAILED:   return "Class registration failed";


@@ 188,9 190,9 @@ puglNewView(PuglWorld* const world)
		return NULL;
	}

	view->world        = world;
	view->frame.width  = 640;
	view->frame.height = 480;
	view->world     = world;
	view->minWidth  = 1;
	view->minHeight = 1;

	puglSetDefaultHints(view->hints);



@@ 309,7 311,7 @@ PuglStatus
puglEnterContext(PuglView* view, bool drawing)
{
	const PuglEventExpose expose = {
	        PUGL_EXPOSE, 0, 0, 0, view->frame.width, view->frame.height, 0};
	    PUGL_EXPOSE, 0, 0.0, 0.0, view->frame.width, view->frame.height};

	view->backend->enter(view, drawing ? &expose : NULL);



@@ 320,7 322,7 @@ PuglStatus
puglLeaveContext(PuglView* view, bool drawing)
{
	const PuglEventExpose expose = {
	        PUGL_EXPOSE, 0, 0, 0, view->frame.width, view->frame.height, 0};
	    PUGL_EXPOSE, 0, 0.0, 0.0, view->frame.width, view->frame.height};

	view->backend->leave(view, drawing ? &expose : NULL);



@@ 336,7 338,7 @@ puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc)
	return PUGL_SUCCESS;
}

/** Return the code point for buf, or the replacement character on error. */
/// Return the code point for buf, or the replacement character on error
uint32_t
puglDecodeUTF8(const uint8_t* buf)
{

M pugl/detail/implementation.h => pugl/detail/implementation.h +15 -14
@@ 1,5 1,5 @@
/*
  Copyright 2012-2020 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file implementation.h Shared declarations for implementation.
   @file implementation.h
   @brief Shared declarations for implementation.
*/

#ifndef PUGL_DETAIL_IMPLEMENTATION_H


@@ 29,42 30,42 @@

PUGL_BEGIN_DECLS

/** Set `blob` to `data` with length `len`, reallocating if necessary. */
/// Set `blob` to `data` with length `len`, reallocating if necessary
void puglSetBlob(PuglBlob* dest, const void* data, size_t len);

/** Reallocate and set `*dest` to `string`. */
/// Reallocate and set `*dest` to `string`
void puglSetString(char** dest, const char* string);

/** Allocate and initialise world internals (implemented once per platform) */
/// Allocate and initialise world internals (implemented once per platform)
PuglWorldInternals*
puglInitWorldInternals(PuglWorldType type, PuglWorldFlags flags);

/** Destroy and free world internals (implemented once per platform) */
/// Destroy and free world internals (implemented once per platform)
void puglFreeWorldInternals(PuglWorld* world);

/** Allocate and initialise view internals (implemented once per platform) */
/// Allocate and initialise view internals (implemented once per platform)
PuglInternals* puglInitViewInternals(void);

/** Destroy and free view internals (implemented once per platform) */
/// Destroy and free view internals (implemented once per platform)
void puglFreeViewInternals(PuglView* view);

/** Return the Unicode code point for `buf` or the replacement character. */
/// Return the Unicode code point for `buf` or the replacement character
uint32_t puglDecodeUTF8(const uint8_t* buf);

/** Dispatch an event with a simple `type` to `view`. */
/// Dispatch an event with a simple `type` to `view`
void puglDispatchSimpleEvent(PuglView* view, PuglEventType type);

/** Dispatch `event` to `view` while already in the graphics context. */
/// Dispatch `event` to `view` while already in the graphics context
void puglDispatchEventInContext(PuglView* view, const PuglEvent* event);

/** Dispatch `event` to `view`, entering graphics context if necessary. */
/// Dispatch `event` to `view`, entering graphics context if necessary
void puglDispatchEvent(PuglView* view, const PuglEvent* event);

/** Set internal (stored in view) clipboard contents. */
/// Set internal (stored in view) clipboard contents
const void*
puglGetInternalClipboard(const PuglView* view, const char** type, size_t* len);

/** Set internal (stored in view) clipboard contents. */
/// Set internal (stored in view) clipboard contents
PuglStatus
puglSetInternalClipboard(PuglView*   view,
                         const char* type,

M pugl/detail/mac.h => pugl/detail/mac.h +5 -15
@@ 1,5 1,5 @@
/*
  Copyright 2012-2019 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>
  Copyright 2017 Hanspeter Portner <dev@open-music-kontrollers.ch>

  Permission to use, copy, modify, and/or distribute this software for any


@@ 16,7 16,8 @@
*/

/**
   @file mac.h Shared definitions for MacOS implementation.
   @file mac.h
   @brief Shared definitions for MacOS implementation.
*/

#include "pugl/pugl.h"


@@ 26,15 27,6 @@
#include <stdint.h>

@interface PuglWrapperView : NSView<NSTextInputClient>
{
@public
	PuglView*                  puglview;
	NSTrackingArea*            trackingArea;
	NSMutableAttributedString* markedText;
	NSTimer*                   timer;
	NSMutableDictionary*       userTimers;
	bool                       reshaped;
}

- (void) dispatchExpose:(NSRect)rect;
- (void) setReshaped;


@@ 42,10 34,6 @@
@end

@interface PuglWindow : NSWindow
{
@public
	PuglView* puglview;
}

- (void) setPuglview:(PuglView*)view;



@@ 60,6 48,8 @@ struct PuglInternalsImpl {
	NSApplication*   app;
	PuglWrapperView* wrapperView;
	NSView*          drawView;
	NSCursor*        cursor;
	PuglWindow*      window;
	uint32_t         mods;
	bool             mouseTracked;
};

M pugl/detail/mac.m => pugl/detail/mac.m +287 -72
@@ 1,5 1,5 @@
/*
  Copyright 2012-2020 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>
  Copyright 2017 Hanspeter Portner <dev@open-music-kontrollers.ch>

  Permission to use, copy, modify, and/or distribute this software for any


@@ 16,7 16,8 @@
*/

/**
   @file mac.m MacOS implementation.
   @file mac.m
   @brief MacOS implementation.
*/

#define GL_SILENCE_DEPRECATION 1


@@ 40,31 41,88 @@ typedef NSUInteger NSWindowStyleMask;
#endif

static NSRect
rectToScreen(NSRect rect)
rectToScreen(NSScreen* screen, NSRect rect)
{
	const double screenHeight = [[NSScreen mainScreen] frame].size.height;
	const double screenHeight = [screen frame].size.height;

	rect.origin.y = screenHeight - rect.origin.y - rect.size.height;
	return rect;
}

static NSScreen*
viewScreen(PuglView* view)
{
	return view->impl->window ? [view->impl->window screen] : [NSScreen mainScreen];
}

static NSRect
nsRectToPoints(PuglView* view, const NSRect rect)
{
	const double scaleFactor = [viewScreen(view) backingScaleFactor];

	return NSMakeRect(rect.origin.x / scaleFactor,
	                  rect.origin.y / scaleFactor,
	                  rect.size.width / scaleFactor,
	                  rect.size.height / scaleFactor);
}

static NSRect
nsRectFromPoints(PuglView* view, const NSRect rect)
{
	const double scaleFactor = [viewScreen(view) backingScaleFactor];

	return NSMakeRect(rect.origin.x * scaleFactor,
	                  rect.origin.y * scaleFactor,
	                  rect.size.width * scaleFactor,
	                  rect.size.height * scaleFactor);
}

static NSPoint
nsPointFromPoints(PuglView* view, const NSPoint point)
{
	const double scaleFactor = [viewScreen(view) backingScaleFactor];

	return NSMakePoint(point.x * scaleFactor, point.y * scaleFactor);
}

static NSRect
rectToNsRect(const PuglRect rect)
{
	return NSMakeRect(rect.x, rect.y, rect.width, rect.height);
}

static NSSize
sizePoints(PuglView* view, const double width, const double height)
{
	const double scaleFactor = [viewScreen(view) backingScaleFactor];

	return NSMakeSize(width / scaleFactor, height / scaleFactor);
}

static void
updateViewRect(PuglView* view)
{
	NSWindow* const window = view->impl->window;
	if (window) {
		const double screenHeight = [[NSScreen mainScreen] frame].size.height;
		const NSRect frame        = [window frame];
		const NSRect content      = [window contentRectForFrameRect:frame];

		view->frame.x      = content.origin.x;
		view->frame.y      = screenHeight - content.origin.y - content.size.height;
		view->frame.width  = content.size.width;
		view->frame.height = content.size.height;
		const NSRect screenFramePt = [[NSScreen mainScreen] frame];
		const NSRect screenFramePx = nsRectFromPoints(view, screenFramePt);
		const NSRect framePt       = [window frame];
		const NSRect contentPt     = [window contentRectForFrameRect:framePt];
		const NSRect contentPx     = nsRectFromPoints(view, contentPt);
		const double screenHeight  = screenFramePx.size.height;

		view->frame.x      = contentPx.origin.x;
		view->frame.y      = screenHeight - contentPx.origin.y - contentPx.size.height;
		view->frame.width  = contentPx.size.width;
		view->frame.height = contentPx.size.height;
	}
}

@implementation PuglWindow
{
@public
	PuglView* puglview;
}

- (id) initWithContentRect:(NSRect)contentRect
                 styleMask:(NSWindowStyleMask)aStyle


@@ 74,9 132,9 @@ updateViewRect(PuglView* view)
	(void)flag;

	NSWindow* result = [super initWithContentRect:contentRect
					    styleMask:aStyle
					      backing:bufferingType
						defer:NO];
	                                    styleMask:aStyle
	                                      backing:bufferingType
	                                        defer:NO];

	[result setAcceptsMouseMovedEvents:YES];
	return (PuglWindow*)result;


@@ 85,7 143,9 @@ updateViewRect(PuglView* view)
- (void)setPuglview:(PuglView*)view
{
	puglview = view;
	[self setContentSize:NSMakeSize(view->frame.width, view->frame.height)];

	[self
	    setContentSize:sizePoints(view, view->frame.width, view->frame.height)];
}

- (BOOL) canBecomeKeyWindow


@@ 124,13 184,24 @@ updateViewRect(PuglView* view)
@end

@implementation PuglWrapperView
{
@public
	PuglView*                  puglview;
	NSTrackingArea*            trackingArea;
	NSMutableAttributedString* markedText;
	NSTimer*                   timer;
	NSMutableDictionary*       userTimers;
	bool                       reshaped;
}

- (void) dispatchExpose:(NSRect)rect
{
	const double scaleFactor = [[NSScreen mainScreen] backingScaleFactor];

	if (reshaped) {
		updateViewRect(puglview);

		const PuglEventConfigure ev =  {
		const PuglEventConfigure ev = {
			PUGL_CONFIGURE,
			0,
			puglview->frame.x,


@@ 150,16 221,26 @@ updateViewRect(PuglView* view)
	const PuglEventExpose ev = {
		PUGL_EXPOSE,
		0,
		rect.origin.x,
		rect.origin.y,
		rect.size.width,
		rect.size.height,
		0
		rect.origin.x * scaleFactor,
		rect.origin.y * scaleFactor,
		rect.size.width * scaleFactor,
		rect.size.height * scaleFactor,
	};

	puglDispatchEvent(puglview, (const PuglEvent*)&ev);
}

- (NSSize) intrinsicContentSize
{
	if (puglview->defaultWidth || puglview->defaultHeight) {
		return sizePoints(puglview,
		                  puglview->defaultWidth,
		                  puglview->defaultHeight);
	}

	return NSMakeSize(NSViewNoInstrinsicMetric, NSViewNoInstrinsicMetric);
}

- (BOOL) isFlipped
{
	return YES;


@@ 246,7 327,9 @@ keySymToSpecial(const NSEvent* const ev)

- (NSPoint) eventLocation:(NSEvent*)event
{
	return [self convertPoint:[event locationInWindow] fromView:nil];
	return nsPointFromPoints(puglview,
	                         [self convertPoint:[event locationInWindow]
	                                   fromView:nil]);
}

static void


@@ 272,11 355,15 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
- (void) mouseEntered:(NSEvent*)event
{
	handleCrossing(self, event, PUGL_POINTER_IN);
	[puglview->impl->cursor set];
	puglview->impl->mouseTracked = true;
}

- (void) mouseExited:(NSEvent*)event
{
	[[NSCursor arrowCursor] set];
	handleCrossing(self, event, PUGL_POINTER_OUT);
	puglview->impl->mouseTracked = false;
}

- (void) mouseMoved:(NSEvent*)event


@@ 292,8 379,6 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
		rloc.x,
		[[NSScreen mainScreen] frame].size.height - rloc.y,
		getModifiers(event),
		0,
		1,
	};

	puglDispatchEvent(puglview, (const PuglEvent*)&ev);


@@ 374,19 459,32 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)

- (void) scrollWheel:(NSEvent*)event
{
	const NSPoint         wloc = [self eventLocation:event];
	const NSPoint         rloc = [NSEvent mouseLocation];
	const PuglEventScroll ev   = {
		PUGL_SCROLL,
		0,
		[event timestamp],
		wloc.x,
		wloc.y,
		rloc.x,
		[[NSScreen mainScreen] frame].size.height - rloc.y,
		getModifiers(event),
		[event scrollingDeltaX],
		[event scrollingDeltaY],
	const NSPoint             wloc = [self eventLocation:event];
	const NSPoint             rloc = [NSEvent mouseLocation];
	const double              dx   = [event scrollingDeltaX];
	const double              dy   = [event scrollingDeltaY];
	const PuglScrollDirection dir =
	    ((dx == 0.0 && dy > 0.0)
	         ? PUGL_SCROLL_UP
	         : ((dx == 0.0 && dy < 0.0)
	                ? PUGL_SCROLL_DOWN
	                : ((dy == 0.0 && dx > 0.0)
	                       ? PUGL_SCROLL_RIGHT
	                       : ((dy == 0.0 && dx < 0.0) ? PUGL_SCROLL_LEFT
	                                                  : PUGL_SCROLL_SMOOTH))));

	const PuglEventScroll ev = {
	    PUGL_SCROLL,
	    0,
	    [event timestamp],
	    wloc.x,
	    wloc.y,
	    rloc.x,
	    [[NSScreen mainScreen] frame].size.height - rloc.y,
	    getModifiers(event),
	    [event hasPreciseScrollingDeltas] ? PUGL_SCROLL_SMOOTH : dir,
	    dx,
	    dy,
	};

	puglDispatchEvent(puglview, (const PuglEvent*)&ev);


@@ 653,15 751,15 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
@end

@interface PuglWindowDelegate : NSObject<NSWindowDelegate>
{
	PuglWindow* window;
}

- (instancetype) initWithPuglWindow:(PuglWindow*)window;

@end

@implementation PuglWindowDelegate
{
	PuglWindow* window;
}

- (instancetype) initWithPuglWindow:(PuglWindow*)puglWindow
{


@@ 692,7 790,7 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
	(void)notification;

	PuglEvent ev = {{PUGL_FOCUS_IN, 0}};
	ev.focus.grab = false;
	ev.focus.mode = PUGL_CROSSING_NORMAL;
	puglDispatchEvent(window->puglview, &ev);
}



@@ 701,21 799,23 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
	(void)notification;

	PuglEvent ev = {{PUGL_FOCUS_OUT, 0}};
	ev.focus.grab = false;
	ev.focus.mode = PUGL_CROSSING_NORMAL;
	puglDispatchEvent(window->puglview, &ev);
}

@end

PuglWorldInternals*
puglInitWorldInternals(PuglWorldType PUGL_UNUSED(type),
                       PuglWorldFlags PUGL_UNUSED(flags))
puglInitWorldInternals(PuglWorldType type, PuglWorldFlags PUGL_UNUSED(flags))
{
	PuglWorldInternals* impl = (PuglWorldInternals*)calloc(
		1, sizeof(PuglWorldInternals));

	impl->app             = [NSApplication sharedApplication];
	impl->autoreleasePool = [NSAutoreleasePool new];
	impl->app = [NSApplication sharedApplication];

	if (type == PUGL_PROGRAM) {
		impl->autoreleasePool = [NSAutoreleasePool new];
	}

	return impl;
}


@@ 723,7 823,10 @@ puglInitWorldInternals(PuglWorldType PUGL_UNUSED(type),
void
puglFreeWorldInternals(PuglWorld* world)
{
	[world->impl->autoreleasePool drain];
	if (world->impl->autoreleasePool) {
		[world->impl->autoreleasePool drain];
	}

	free(world->impl);
}



@@ 736,7 839,11 @@ puglGetNativeWorld(PuglWorld* PUGL_UNUSED(world))
PuglInternals*
puglInitViewInternals(void)
{
	return (PuglInternals*)calloc(1, sizeof(PuglInternals));
	PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));

	impl->cursor = [NSCursor arrowCursor];

	return impl;
}

static NSLayoutConstraint*


@@ 749,13 856,35 @@ puglConstraint(id item, NSLayoutAttribute attribute, float constant)
		                   toItem: nil
		                attribute: NSLayoutAttributeNotAnAttribute
		               multiplier: 1.0
		                 constant: constant];
		                 constant: (CGFloat)constant];
}

PuglStatus
puglRealize(PuglView* view)
{
	PuglInternals* impl = view->impl;
	PuglInternals*        impl        = view->impl;
	const NSScreen* const screen      = [NSScreen mainScreen];
	const double          scaleFactor = [screen backingScaleFactor];

	if (view->frame.width == 0.0 && view->frame.height == 0.0) {
		if (view->defaultWidth == 0.0 && view->defaultHeight == 0.0) {
			return PUGL_BAD_CONFIGURATION;
		}

		const double screenWidthPx  = [screen frame].size.width * scaleFactor;
		const double screenHeightPx = [screen frame].size.height * scaleFactor;

		view->frame.width  = view->defaultWidth;
		view->frame.height = view->defaultHeight;
		view->frame.x      = screenWidthPx / 2.0 - view->frame.width / 2.0;
		view->frame.y      = screenHeightPx / 2.0 - view->frame.height / 2.0;
	}

	const NSRect framePx = rectToNsRect(view->frame);
	const NSRect framePt = NSMakeRect(framePx.origin.x / scaleFactor,
	                                  framePx.origin.y / scaleFactor,
	                                  framePx.size.width / scaleFactor,
	                                  framePx.size.height / scaleFactor);

	// Create wrapper view to handle input
	impl->wrapperView             = [PuglWrapperView alloc];


@@ 763,8 892,7 @@ puglRealize(PuglView* view)
	impl->wrapperView->userTimers = [[NSMutableDictionary alloc] init];
	impl->wrapperView->markedText = [[NSMutableAttributedString alloc] init];
	[impl->wrapperView setAutoresizesSubviews:YES];
	[impl->wrapperView initWithFrame:
		     NSMakeRect(0, 0, view->frame.width, view->frame.height)];
	[impl->wrapperView initWithFrame:framePt];
	[impl->wrapperView addConstraint:
		     puglConstraint(impl->wrapperView, NSLayoutAttributeWidth, view->minWidth)];
	[impl->wrapperView addConstraint:


@@ 788,10 916,6 @@ puglRealize(PuglView* view)
		[impl->drawView setHidden:NO];
		[[impl->drawView window] makeFirstResponder:impl->wrapperView];
	} else {
		const NSRect frame = rectToScreen(
			NSMakeRect(view->frame.x, view->frame.y,
			           view->minWidth, view->minHeight));

		unsigned style = (NSClosableWindowMask |
		                  NSTitledWindowMask |
		                  NSMiniaturizableWindowMask );


@@ 800,7 924,7 @@ puglRealize(PuglView* view)
		}

		PuglWindow* window = [[[PuglWindow alloc]
			initWithContentRect:frame
			initWithContentRect:rectToScreen([NSScreen mainScreen], framePt)
			          styleMask:style
			            backing:NSBackingStoreBuffered
			              defer:NO


@@ 817,7 941,8 @@ puglRealize(PuglView* view)
		}

		if (view->minWidth || view->minHeight) {
			[window setContentMinSize:NSMakeSize(view->minWidth,
			[window setContentMinSize:sizePoints(view,
			                                     view->minWidth,
			                                     view->minHeight)];
		}
		impl->window = window;


@@ 826,10 951,13 @@ puglRealize(PuglView* view)
			                  initWithPuglWindow:window];

		if (view->minAspectX && view->minAspectY) {
			[window setContentAspectRatio:NSMakeSize(view->minAspectX,
			[window setContentAspectRatio:sizePoints(view,
			                                         view->minAspectX,
			                                         view->minAspectY)];
		}

		puglSetFrame(view, view->frame);

		[window setContentView:impl->wrapperView];
		[view->world->impl->app activateIgnoringOtherApps:YES];
		[window makeFirstResponder:impl->wrapperView];


@@ 1066,8 1194,9 @@ puglPostRedisplay(PuglView* view)
PuglStatus
puglPostRedisplayRect(PuglView* view, const PuglRect rect)
{
	[view->impl->drawView setNeedsDisplayInRect:
		NSMakeRect(rect.x, rect.y, rect.width, rect.height)];
	const NSRect rectPx = rectToNsRect(rect);

	[view->impl->drawView setNeedsDisplayInRect:nsRectToPoints(view, rectPx)];

	return PUGL_SUCCESS;
}


@@ 1103,32 1232,59 @@ puglSetFrame(PuglView* view, const PuglRect frame)
	// Update view frame to exactly the requested frame in Pugl coordinates
	view->frame = frame;

	const NSRect rect = NSMakeRect(frame.x, frame.y, frame.width, frame.height);
	const NSRect framePx = rectToNsRect(frame);
	const NSRect framePt = nsRectToPoints(view, framePx);
	if (impl->window) {
		// Resize window to fit new content rect
		const NSRect windowFrame = [
			impl->window frameRectForContentRect:rectToScreen(rect)];
		const NSRect screenPt = rectToScreen(viewScreen(view), framePt);
		const NSRect winFrame = [impl->window frameRectForContentRect:screenPt];

		[impl->window setFrame:windowFrame display:NO];
		[impl->window setFrame:winFrame display:NO];
	}

	// Resize views
	const NSRect drawRect = NSMakeRect(0, 0, frame.width, frame.height);
	[impl->wrapperView setFrame:(impl->window ? drawRect : rect)];
	[impl->drawView setFrame:drawRect];
	const NSRect sizePx = NSMakeRect(0, 0, frame.width, frame.height);
	const NSRect sizePt = [impl->drawView convertRectFromBacking:sizePx];

	[impl->wrapperView setFrame:(impl->window ? sizePt : framePt)];
	[impl->drawView setFrame:sizePt];

	return PUGL_SUCCESS;
}

PuglStatus
puglSetDefaultSize(PuglView* const view, const int width, const int height)
{
	view->defaultWidth  = width;
	view->defaultHeight = height;
	return PUGL_SUCCESS;
}

PuglStatus
puglSetMinSize(PuglView* const view, const int width, const int height)
{
	view->minWidth  = width;
	view->minHeight = height;

	if (view->impl->window && (view->minWidth || view->minHeight)) {
		[view->impl->window
		    setContentMinSize:NSMakeSize(view->minWidth, view->minHeight)];
		[view->impl->window setContentMinSize:sizePoints(view,
		                                                 view->minWidth,
		                                                 view->minHeight)];
	}

	return PUGL_SUCCESS;
}

PuglStatus
puglSetMaxSize(PuglView* const view, const int width, const int height)
{
	view->maxWidth  = width;
	view->maxHeight = height;

	if (view->impl->window && (view->maxWidth || view->maxHeight)) {
		[view->impl->window setContentMaxSize:sizePoints(view,
		                                                 view->maxWidth,
		                                                 view->maxHeight)];
	}

	return PUGL_SUCCESS;


@@ 1147,13 1303,31 @@ puglSetAspectRatio(PuglView* const view,
	view->maxAspectY = maxY;

	if (view->impl->window && view->minAspectX && view->minAspectY) {
		[view->impl->window setContentAspectRatio:NSMakeSize(view->minAspectX,
		[view->impl->window setContentAspectRatio:sizePoints(view,
		                                                     view->minAspectX,
		                                                     view->minAspectY)];
	}

	return PUGL_SUCCESS;
}

PuglStatus
puglSetTransientFor(PuglView* view, PuglNativeView parent)
{
	view->transientParent = parent;

	if (view->impl->window) {
		NSWindow* parentWindow = [(NSView*)parent window];
		if (parentWindow) {
			[parentWindow addChildWindow:view->impl->window
			                     ordered:NSWindowAbove];
			return PUGL_SUCCESS;
		}
	}

	return PUGL_FAILURE;
}

const void*
puglGetClipboard(PuglView* const    view,
                 const char** const type,


@@ 1171,6 1345,47 @@ puglGetClipboard(PuglView* const    view,
	return puglGetInternalClipboard(view, type, len);
}

static NSCursor*
puglGetNsCursor(const PuglCursor cursor)
{
	switch (cursor) {
	case PUGL_CURSOR_ARROW:
		return [NSCursor arrowCursor];
	case PUGL_CURSOR_CARET:
		return [NSCursor IBeamCursor];
	case PUGL_CURSOR_CROSSHAIR:
		return [NSCursor crosshairCursor];
	case PUGL_CURSOR_HAND:
		return [NSCursor pointingHandCursor];
	case PUGL_CURSOR_NO:
		return [NSCursor operationNotAllowedCursor];
	case PUGL_CURSOR_LEFT_RIGHT:
		return [NSCursor resizeLeftRightCursor];
	case PUGL_CURSOR_UP_DOWN:
		return [NSCursor resizeUpDownCursor];
	}

	return NULL;
}

PuglStatus
puglSetCursor(PuglView* view, PuglCursor cursor)
{
	PuglInternals* const impl = view->impl;
	NSCursor* const      cur  = puglGetNsCursor(cursor);
	if (!cur) {
		return PUGL_FAILURE;
	}

	impl->cursor = cur;

	if (impl->mouseTracked) {
		[cur set];
	}

	return PUGL_SUCCESS;
}

PuglStatus
puglSetClipboard(PuglView* const   view,
                 const char* const type,

M pugl/detail/mac_cairo.m => pugl/detail/mac_cairo.m +19 -10
@@ 1,5 1,5 @@
/*
  Copyright 2019 David Robillard <http://drobilla.net>
  Copyright 2019-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,13 15,14 @@
*/

/**
   @file mac_cairo.m Cairo graphics backend for MacOS.
   @file mac_cairo.m
   @brief Cairo graphics backend for MacOS.
*/

#include "pugl/detail/implementation.h"
#include "pugl/detail/mac.h"
#include "pugl/detail/stub.h"
#include "pugl/pugl_cairo.h"
#include "pugl/pugl_stub.h"

#include <cairo-quartz.h>



@@ 30,6 31,9 @@
#include <assert.h>

@interface PuglCairoView : NSView
@end

@implementation PuglCairoView
{
@public
	PuglView*        puglview;


@@ 37,13 41,11 @@
	cairo_t*         cr;
}

@end

@implementation PuglCairoView

- (id) initWithFrame:(NSRect)frame
{
	return (self = [super initWithFrame:frame]);
	self = [super initWithFrame:frame];

	return self;
}

- (void) resizeWithOldSuperviewSize:(NSSize)oldSize


@@ 69,7 71,7 @@ puglMacCairoCreate(PuglView* view)
	PuglCairoView* drawView = [PuglCairoView alloc];

	drawView->puglview = view;
	[drawView initWithFrame:NSMakeRect(0, 0, view->frame.width, view->frame.height)];
	[drawView initWithFrame:[impl->wrapperView bounds]];
	if (view->hints[PUGL_RESIZABLE]) {
		[drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
	} else {


@@ 103,10 105,17 @@ puglMacCairoEnter(PuglView* view, const PuglEventExpose* expose)
	assert(!drawView->surface);
	assert(!drawView->cr);

	const double scale   = 1.0 / [[NSScreen mainScreen] backingScaleFactor];
	CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
	const CGSize sizePx  = {view->frame.width, view->frame.height};
	const CGSize sizePt  = CGContextConvertSizeToUserSpace(context, sizePx);

	// Convert coordinates to standard Cairo space
	CGContextTranslateCTM(context, 0.0, -sizePt.height);
	CGContextScaleCTM(context, scale, -scale);

	drawView->surface = cairo_quartz_surface_create_for_cg_context(
		context, view->frame.width, view->frame.height);
		context, (unsigned)sizePx.width, (unsigned)sizePx.height);

	drawView->cr = cairo_create(drawView->surface);


M pugl/detail/mac_gl.m => pugl/detail/mac_gl.m +10 -10
@@ 1,5 1,5 @@
/*
  Copyright 2019-2020 David Robillard <http://drobilla.net>
  Copyright 2019-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,28 15,28 @@
*/

/**
   @file mac_gl.m OpenGL graphics backend for MacOS.
   @file mac_gl.m
   @brief OpenGL graphics backend for MacOS.
*/

#include "pugl/detail/implementation.h"
#include "pugl/detail/mac.h"
#include "pugl/detail/stub.h"
#include "pugl/pugl_gl.h"
#include "pugl/pugl_stub.h"

#ifndef __MAC_10_10
#    define NSOpenGLProfileVersion4_1Core NSOpenGLProfileVersion3_2Core
#endif

@interface PuglOpenGLView : NSOpenGLView
@end

@implementation PuglOpenGLView
{
@public
	PuglView* puglview;
}

@end

@implementation PuglOpenGLView

- (id) initWithFrame:(NSRect)frame
{
	const bool     compat  = puglview->hints[PUGL_USE_COMPAT_PROFILE];


@@ 69,6 69,8 @@
		self = [super initWithFrame:frame];
	}

	[self setWantsBestResolutionOpenGLSurface:YES];

	if (self) {
		[[self openGLContext] makeCurrentContext];
		[self reshape];


@@ 97,11 99,9 @@ puglMacGlCreate(PuglView* view)
{
	PuglInternals*  impl     = view->impl;
	PuglOpenGLView* drawView = [PuglOpenGLView alloc];
	const NSRect    rect     = NSMakeRect(
		0, 0, view->frame.width, view->frame.height);

	drawView->puglview = view;
	[drawView initWithFrame:rect];
	[drawView initWithFrame:[impl->wrapperView bounds]];
	if (view->hints[PUGL_RESIZABLE]) {
		[drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
	} else {

M pugl/detail/mac_stub.m => pugl/detail/mac_stub.m +7 -6
@@ 1,5 1,5 @@
/*
  Copyright 2019-2020 David Robillard <http://drobilla.net>
  Copyright 2019-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,25 15,26 @@
*/

/**
   @file mac_stub.m Stub graphics backend for MacOS.
   @file mac_stub.m
   @brief Stub graphics backend for MacOS.
*/

#include "pugl/detail/implementation.h"
#include "pugl/detail/mac.h"
#include "pugl/detail/stub.h"
#include "pugl/pugl_stub.h"

#import <Cocoa/Cocoa.h>

@interface PuglStubView : NSView
@end

@implementation PuglStubView
{
@public
	PuglView* puglview;
}

@end

@implementation PuglStubView

- (void) resizeWithOldSuperviewSize:(NSSize)oldSize
{
	PuglWrapperView* wrapper = (PuglWrapperView*)[self superview];

A pugl/detail/stub.h => pugl/detail/stub.h +75 -0
@@ 0,0 1,75 @@
/*
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.

  THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

/**
   @file stub.h
   @brief Definition of generic stub backend functions.
*/

#ifndef PUGL_DETAIL_STUB_H
#define PUGL_DETAIL_STUB_H

#include "pugl/pugl.h"

PUGL_BEGIN_DECLS

static inline PuglStatus
puglStubConfigure(PuglView* view)
{
	(void)view;
	return PUGL_SUCCESS;
}

static inline PuglStatus
puglStubCreate(PuglView* view)
{
	(void)view;
	return PUGL_SUCCESS;
}

static inline PuglStatus
puglStubDestroy(PuglView* view)
{
	(void)view;
	return PUGL_SUCCESS;
}

static inline PuglStatus
puglStubEnter(PuglView* view, const PuglEventExpose* expose)
{
	(void)view;
	(void)expose;
	return PUGL_SUCCESS;
}

static inline PuglStatus
puglStubLeave(PuglView* view, const PuglEventExpose* expose)
{
	(void)view;
	(void)expose;
	return PUGL_SUCCESS;
}

static inline void*
puglStubGetContext(PuglView* view)
{
	(void)view;
	return NULL;
}

PUGL_END_DECLS

#endif // PUGL_DETAIL_STUB_H

M pugl/detail/types.h => pugl/detail/types.h +24 -19
@@ 1,5 1,5 @@
/*
  Copyright 2012-2019 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file types.h Shared internal type definitions.
   @file types.h
   @brief Shared internal type definitions.
*/

#ifndef PUGL_DETAIL_TYPES_H


@@ 36,22 37,22 @@
#   define PUGL_UNUSED(name) name
#endif

/** Platform-specific world internals. */
/// Platform-specific world internals
typedef struct PuglWorldInternalsImpl PuglWorldInternals;

/** Platform-specific view internals. */
/// Platform-specific view internals
typedef struct PuglInternalsImpl PuglInternals;

/** View hints. */
/// View hints
typedef int PuglHints[PUGL_NUM_VIEW_HINTS];

/** Blob of arbitrary data. */
/// Blob of arbitrary data
typedef struct {
	void*  data; //!< Dynamically allocated data
	size_t len;  //!< Length of data in bytes
	void*  data; ///< Dynamically allocated data
	size_t len;  ///< Length of data in bytes
} PuglBlob;

/** Cross-platform view definition. */
/// Cross-platform view definition
struct PuglViewImpl {
	PuglWorld*         world;
	const PuglBackend* backend;


@@ 62,11 63,15 @@ struct PuglViewImpl {
	PuglBlob           clipboard;
	PuglNativeView     parent;
	uintptr_t          transientParent;
	PuglHints          hints;
	PuglRect           frame;
	PuglEventConfigure lastConfigure;
	PuglHints          hints;
	int                defaultWidth;
	int                defaultHeight;
	int                minWidth;
	int                minHeight;
	int                maxWidth;
	int                maxHeight;
	int                minAspectX;
	int                minAspectY;
	int                maxAspectX;


@@ 74,7 79,7 @@ struct PuglViewImpl {
	bool               visible;
};

/** Cross-platform world definition. */
/// Cross-platform world definition
struct PuglWorldImpl {
	PuglWorldInternals* impl;
	PuglWorldHandle     handle;


@@ 86,27 91,27 @@ struct PuglWorldImpl {
	PuglLogLevel        logLevel;
};

/** Opaque surface used by graphics backend. */
/// Opaque surface used by graphics backend
typedef void PuglSurface;

/** Graphics backend interface. */
/// Graphics backend interface
struct PuglBackendImpl {
	/** Get visual information from display and setup view as necessary. */
	/// Get visual information from display and setup view as necessary
	PuglStatus (*configure)(PuglView*);

	/** Create surface and drawing context. */
	/// Create surface and drawing context
	PuglStatus (*create)(PuglView*);

	/** Destroy surface and drawing context. */
	/// Destroy surface and drawing context
	PuglStatus (*destroy)(PuglView*);

	/** Enter drawing context, for drawing if expose is non-null. */
	/// Enter drawing context, for drawing if expose is non-null
	PuglStatus (*enter)(PuglView*, const PuglEventExpose*);

	/** Leave drawing context, after drawing if expose is non-null. */
	/// Leave drawing context, after drawing if expose is non-null
	PuglStatus (*leave)(PuglView*, const PuglEventExpose*);

	/** Return the puglGetContext() handle for the application, if any. */
	/// Return the puglGetContext() handle for the application, if any
	void* (*getContext)(PuglView*);
};


M pugl/detail/win.c => pugl/detail/win.c +92 -8
@@ 1,5 1,5 @@
/*
  Copyright 2012-2020 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,12 15,14 @@
*/

/**
   @file win.c Windows implementation.
   @file win.c
   @brief Windows implementation.
*/

#include "pugl/detail/win.h"

#include "pugl/detail/implementation.h"
#include "pugl/detail/stub.h"
#include "pugl/pugl.h"
#include "pugl/pugl_stub.h"



@@ 189,6 191,8 @@ puglRealize(PuglView* view)
		puglSetWindowTitle(view, view->title);
	}

	view->impl->cursor = LoadCursor(NULL, IDC_ARROW);

	puglSetFrame(view, view->frame);
	SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view);



@@ 342,7 346,7 @@ initScrollEvent(PuglEvent* event, PuglView* view, LPARAM lParam)
	event->scroll.dy     = 0;
}

/** Return the code point for buf, or the replacement character on error. */
/// Return the code point for buf, or the replacement character on error
static uint32_t
puglDecodeUTF16(const wchar_t* buf, const int len)
{


@@ 539,6 543,11 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
	}

	switch (message) {
	case WM_SETCURSOR:
		if (LOWORD(lParam) == HTCLIENT) {
			SetCursor(view->impl->cursor);
		}
		break;
	case WM_SHOWWINDOW:
		if (wParam) {
			handleConfigure(view, &event);


@@ 577,8 586,9 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
			RedrawWindow(view->impl->hwnd, NULL, NULL,
			             RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT);
		} else if (wParam >= PUGL_USER_TIMER_MIN) {
			const PuglEventTimer ev = {PUGL_TIMER, 0, wParam - PUGL_USER_TIMER_MIN};
			puglDispatchEvent(view, (const PuglEvent*)&ev);
			PuglEvent ev = {{PUGL_TIMER, 0}};
			ev.timer.id  = wParam - PUGL_USER_TIMER_MIN;
			puglDispatchEvent(view, &ev);
		}
		break;
	case WM_EXITSIZEMOVE:


@@ 591,6 601,10 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
		mmi                   = (MINMAXINFO*)lParam;
		mmi->ptMinTrackSize.x = view->minWidth;
		mmi->ptMinTrackSize.y = view->minHeight;
		if (view->maxWidth > 0 && view->maxHeight > 0) {
			mmi->ptMaxTrackSize.x = view->maxWidth;
			mmi->ptMaxTrackSize.y = view->maxHeight;
		}
		break;
	case WM_PAINT:
		GetUpdateRect(view->impl->hwnd, &rect, false);


@@ 599,7 613,6 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
		event.expose.y      = rect.top;
		event.expose.width  = rect.right - rect.left;
		event.expose.height = rect.bottom - rect.top;
		event.expose.count  = 0;
		break;
	case WM_ERASEBKGND:
		return true;


@@ 627,7 640,6 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
		event.motion.xRoot   = pt.x;
		event.motion.yRoot   = pt.y;
		event.motion.state   = getModifiers();
		event.motion.isHint  = false;
		break;
	case WM_MOUSELEAVE:
		GetCursorPos(&pt);


@@ 656,10 668,16 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
	case WM_MOUSEWHEEL:
		initScrollEvent(&event, view, lParam);
		event.scroll.dy = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
		event.scroll.direction = (event.scroll.dy > 0
		                          ? PUGL_SCROLL_UP
		                          : PUGL_SCROLL_DOWN);
		break;
	case WM_MOUSEHWHEEL:
		initScrollEvent(&event, view, lParam);
		event.scroll.dx = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
		event.scroll.direction = (event.scroll.dx > 0
		                          ? PUGL_SCROLL_RIGHT
		                          : PUGL_SCROLL_LEFT);
		break;
	case WM_KEYDOWN:
		if (!ignoreKeyEvent(view, lParam)) {


@@ 958,6 976,14 @@ puglSetFrame(PuglView* view, const PuglRect frame)
}

PuglStatus
puglSetDefaultSize(PuglView* const view, const int width, const int height)
{
	view->defaultWidth  = width;
	view->defaultHeight = height;
	return PUGL_SUCCESS;
}

PuglStatus
puglSetMinSize(PuglView* const view, const int width, const int height)
{
	view->minWidth  = width;


@@ 966,6 992,14 @@ puglSetMinSize(PuglView* const view, const int width, const int height)
}

PuglStatus
puglSetMaxSize(PuglView* const view, const int width, const int height)
{
	view->maxWidth  = width;
	view->maxHeight = height;
	return PUGL_SUCCESS;
}

PuglStatus
puglSetAspectRatio(PuglView* const view,
                   const int       minX,
                   const int       minY,


@@ 979,6 1013,23 @@ puglSetAspectRatio(PuglView* const view,
	return PUGL_SUCCESS;
}

PuglStatus
puglSetTransientFor(PuglView* view, PuglNativeView parent)
{
	if (view->parent) {
		return PUGL_FAILURE;
	}

	view->transientParent = parent;

	if (view->impl->hwnd) {
		SetWindowLongPtr(view->impl->hwnd, GWLP_HWNDPARENT, (LONG_PTR)parent);
		return GetLastError() == NO_ERROR ? PUGL_SUCCESS : PUGL_FAILURE;
	}

	return PUGL_SUCCESS;
}

const void*
puglGetClipboard(PuglView* const    view,
                 const char** const type,


@@ 1064,7 1115,40 @@ puglWinStubLeave(PuglView* view, const PuglEventExpose* expose)
	if (expose) {
		PAINTSTRUCT ps;
		EndPaint(view->impl->hwnd, &ps);
		SwapBuffers(view->impl->hdc);
	}

	return PUGL_SUCCESS;
}

static const char* const cursor_ids[] = {
    IDC_ARROW,  // ARROW
    IDC_IBEAM,  // CARET
    IDC_CROSS,  // CROSSHAIR
    IDC_HAND,   // HAND
    IDC_NO,     // NO
    IDC_SIZEWE, // LEFT_RIGHT
    IDC_SIZENS, // UP_DOWN
};

PuglStatus
puglSetCursor(PuglView* view, PuglCursor cursor)
{
	PuglInternals* const impl  = view->impl;
	const unsigned       index = (unsigned)cursor;
	const unsigned       count = sizeof(cursor_ids) / sizeof(cursor_ids[0]);

	if (index >= count) {
		return PUGL_BAD_PARAMETER;
	}

	const HCURSOR cur = LoadCursor(NULL, cursor_ids[index]);
	if (!cur) {
		return PUGL_FAILURE;
	}

	impl->cursor = cur;
	if (impl->mouseTracked) {
		SetCursor(cur);
	}

	return PUGL_SUCCESS;

M pugl/detail/win.h => pugl/detail/win.h +29 -7
@@ 1,5 1,5 @@
/*
  Copyright 2012-2019 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file win.h Shared definitions for Windows implementation.
   @file win.h
   @brief Shared definitions for Windows implementation.
*/

#include "pugl/detail/implementation.h"


@@ 34,6 35,7 @@ struct PuglInternalsImpl {
	PuglWinPFD   pfd;
	int          pfId;
	HWND         hwnd;
	HCURSOR      cursor;
	HDC          hdc;
	PuglSurface* surface;
	DWORD        refreshRate;


@@ 87,15 89,35 @@ puglWinGetWindowExFlags(const PuglView* const view)
}

static inline PuglStatus
puglWinCreateWindow(const PuglView* const view,
                    const char* const     title,
                    HWND* const           hwnd,
                    HDC* const            hdc)
puglWinCreateWindow(PuglView* const   view,
                    const char* const title,
                    HWND* const       hwnd,
                    HDC* const        hdc)
{
	const char*    className  = (const char*)view->world->className;
	const unsigned winFlags   = puglWinGetWindowFlags(view);
	const unsigned winExFlags = puglWinGetWindowExFlags(view);

	if (view->frame.width == 0.0 && view->frame.height == 0.0) {
		if (view->defaultWidth == 0.0 && view->defaultHeight == 0.0) {
			return PUGL_BAD_CONFIGURATION;
		}

		RECT desktopRect;
		GetClientRect(GetDesktopWindow(), &desktopRect);

		const int screenWidth  = desktopRect.right - desktopRect.left;
		const int screenHeight = desktopRect.bottom - desktopRect.top;

		view->frame.width  = view->defaultWidth;
		view->frame.height = view->defaultHeight;
		view->frame.x      = screenWidth / 2.0 - view->frame.width / 2.0;
		view->frame.y      = screenHeight / 2.0 - view->frame.height / 2.0;
	}

	// The meaning of "parent" depends on the window type (WS_CHILD)
	PuglNativeView parent = view->parent ? view->parent : view->transientParent;

	// Calculate total window size to accommodate requested view size
	RECT wr = { (long)view->frame.x, (long)view->frame.y,
	            (long)view->frame.width, (long)view->frame.height };


@@ 105,7 127,7 @@ puglWinCreateWindow(const PuglView* const view,
	if (!(*hwnd = CreateWindowEx(winExFlags, className, title, winFlags,
	                             CW_USEDEFAULT, CW_USEDEFAULT,
	                             wr.right-wr.left, wr.bottom-wr.top,
	                             (HWND)view->parent, NULL, NULL, NULL))) {
	                             (HWND)parent, NULL, NULL, NULL))) {
		return PUGL_REALIZE_FAILED;
	} else if (!(*hdc = GetDC(*hwnd))) {
		DestroyWindow(*hwnd);

M pugl/detail/win_cairo.c => pugl/detail/win_cairo.c +4 -4
@@ 1,5 1,5 @@
/*
  Copyright 2012-2020 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,13 15,14 @@
*/

/**
   @file win_cairo.c Cairo graphics backend for Windows.
   @file win_cairo.c
   @brief Cairo graphics backend for Windows.
*/

#include "pugl/detail/stub.h"
#include "pugl/detail/types.h"
#include "pugl/detail/win.h"
#include "pugl/pugl_cairo.h"
#include "pugl/pugl_stub.h"

#include <cairo-win32.h>
#include <cairo.h>


@@ 152,7 153,6 @@ puglWinCairoLeave(PuglView* view, const PuglEventExpose* expose)

		PAINTSTRUCT ps;
		EndPaint(view->impl->hwnd, &ps);
		SwapBuffers(view->impl->hdc);
	}

	return PUGL_SUCCESS;

M pugl/detail/win_gl.c => pugl/detail/win_gl.c +5 -6
@@ 1,5 1,5 @@
/*
  Copyright 2012-2019 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,13 15,14 @@
*/

/**
   @file win_gl.c OpenGL graphics backend for Windows.
   @file win_gl.c
   @brief OpenGL graphics backend for Windows.
*/

#include "pugl/detail/stub.h"
#include "pugl/detail/types.h"
#include "pugl/detail/win.h"
#include "pugl/pugl_gl.h"
#include "pugl/pugl_stub.h"

#include <windows.h>



@@ 35,7 36,6 @@
#define WGL_SUPPORT_OPENGL_ARB    0x2010
#define WGL_DOUBLE_BUFFER_ARB     0x2011
#define WGL_PIXEL_TYPE_ARB        0x2013
#define WGL_COLOR_BITS_ARB        0x2014
#define WGL_RED_BITS_ARB          0x2015
#define WGL_GREEN_BITS_ARB        0x2017
#define WGL_BLUE_BITS_ARB         0x2019


@@ 49,7 49,6 @@

#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
#define WGL_CONTEXT_LAYER_PLANE_ARB   0x2093
#define WGL_CONTEXT_FLAGS_ARB         0x2094
#define WGL_CONTEXT_PROFILE_MASK_ARB  0x9126



@@ 295,7 294,7 @@ puglGetProcAddress(const char* name)
}

const PuglBackend*
puglGlBackend()
puglGlBackend(void)
{
	static const PuglBackend backend = {puglWinGlConfigure,
	                                    puglWinGlCreate,

M pugl/detail/x11.c => pugl/detail/x11.c +181 -56
@@ 1,5 1,5 @@
/*
  Copyright 2012-2020 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>
  Copyright 2013 Robin Gareus <robin@gareus.org>
  Copyright 2011-2012 Ben Loftis, Harrison Consoles



@@ 17,7 17,8 @@
*/

/**
   @file x11.c X11 implementation.
   @file x11.c
   @brief X11 implementation.
*/

#define _POSIX_C_SOURCE 199309L


@@ 25,6 26,7 @@
#include "pugl/detail/x11.h"

#include "pugl/detail/implementation.h"
#include "pugl/detail/stub.h"
#include "pugl/detail/types.h"
#include "pugl/pugl.h"
#include "pugl/pugl_stub.h"


@@ 40,6 42,11 @@
#	include <X11/extensions/syncconst.h>
#endif

#ifdef HAVE_XCURSOR
#	include <X11/Xcursor/Xcursor.h>
#	include <X11/cursorfont.h>
#endif

#include <sys/select.h>
#include <sys/time.h>



@@ 74,11 81,11 @@ static bool
puglInitXSync(PuglWorldInternals* impl)
{
#ifdef HAVE_XSYNC
	int                 syncMajor;
	int                 syncMinor;
	int                 errorBase;
	XSyncSystemCounter* counters;
	int                 numCounters;
	int                 syncMajor   = 0;
	int                 syncMinor   = 0;
	int                 errorBase   = 0;
	XSyncSystemCounter* counters    = NULL;
	int                 numCounters = 0;

	if (XSyncQueryExtension(impl->display, &impl->syncEventBase, &errorBase) &&
	    XSyncInitialize(impl->display, &syncMajor, &syncMinor) &&


@@ 151,7 158,13 @@ puglGetNativeWorld(PuglWorld* world)
PuglInternals*
puglInitViewInternals(void)
{
	return (PuglInternals*)calloc(1, sizeof(PuglInternals));
	PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));

#ifdef HAVE_XCURSOR
	impl->cursorShape = XC_arrow;
#endif

	return impl;
}

static PuglStatus


@@ 192,23 205,40 @@ puglFindView(PuglWorld* world, const Window window)
	return NULL;
}

static XSizeHints
getSizeHints(const PuglView* view)
static PuglStatus
updateSizeHints(const PuglView* view)
{
	if (!view->impl->win) {
		return PUGL_SUCCESS;
	}

	Display*   display   = view->world->impl->display;
	XSizeHints sizeHints = {0};

	if (!view->hints[PUGL_RESIZABLE]) {
		sizeHints.flags      = PMinSize|PMaxSize;
		sizeHints.min_width  = (int)view->frame.width;
		sizeHints.min_height = (int)view->frame.height;
		sizeHints.max_width  = (int)view->frame.width;
		sizeHints.max_height = (int)view->frame.height;
		sizeHints.flags       = PBaseSize | PMinSize | PMaxSize;
		sizeHints.base_width  = (int)view->frame.width;
		sizeHints.base_height = (int)view->frame.height;
		sizeHints.min_width   = (int)view->frame.width;
		sizeHints.min_height  = (int)view->frame.height;
		sizeHints.max_width   = (int)view->frame.width;
		sizeHints.max_height  = (int)view->frame.height;
	} else {
		if (view->defaultWidth || view->defaultHeight) {
			sizeHints.flags       = PBaseSize;
			sizeHints.base_width  = view->defaultWidth;
			sizeHints.base_height = view->defaultHeight;
		}
		if (view->minWidth || view->minHeight) {
			sizeHints.flags      = PMinSize;
			sizeHints.min_width  = view->minWidth;
			sizeHints.min_height = view->minHeight;
		}
		if (view->maxWidth || view->maxHeight) {
			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;


@@ 218,8 248,28 @@ getSizeHints(const PuglView* view)
		}
	}

	return sizeHints;
	XSetNormalHints(display, view->impl->win, &sizeHints);
	return PUGL_SUCCESS;
}

#ifdef HAVE_XCURSOR
static PuglStatus
puglDefineCursorShape(PuglView* view, unsigned shape)
{
	PuglInternals* const impl    = view->impl;
	PuglWorld* const     world   = view->world;
	Display* const       display = world->impl->display;
	const Cursor         cur     = XcursorShapeLoadCursor(display, shape);

	if (cur) {
		XDefineCursor(display, impl->win, cur);
		XFreeCursor(display, cur);
		return PUGL_SUCCESS;
	}

	return PUGL_FAILURE;
}
#endif

PuglStatus
puglRealize(PuglView* view)


@@ 234,6 284,18 @@ puglRealize(PuglView* view)

	if (!view->backend || !view->backend->configure) {
		return PUGL_BAD_BACKEND;
	} else if (view->frame.width == 0.0 && view->frame.height == 0.0) {
		if (view->defaultWidth == 0.0 && view->defaultHeight == 0.0) {
			return PUGL_BAD_CONFIGURATION;
		}

		const int screenWidth  = DisplayWidth(display, impl->screen);
		const int screenHeight = DisplayHeight(display, impl->screen);

		view->frame.width  = view->defaultWidth;
		view->frame.height = view->defaultHeight;
		view->frame.x      = screenWidth / 2.0 - view->frame.width / 2.0;
		view->frame.y      = screenHeight / 2.0 - view->frame.height / 2.0;
	}

	PuglStatus st = view->backend->configure(view);


@@ 263,8 325,7 @@ puglRealize(PuglView* view)
		return st;
	}

	XSizeHints sizeHints = getSizeHints(view);
	XSetNormalHints(display, win, &sizeHints);
	updateSizeHints(view);

	XClassHint classHint = { world->className, world->className };
	XSetClassHint(display, win, &classHint);


@@ 293,6 354,10 @@ puglRealize(PuglView* view)
		                     "XCreateID failed\n");
	}

#ifdef HAVE_XCURSOR
	puglDefineCursorShape(view, impl->cursorShape);
#endif

	puglDispatchSimpleEvent(view, PUGL_CREATE);

	return PUGL_SUCCESS;


@@ 450,10 515,10 @@ translateKey(PuglView* view, XEvent* xevent, PuglEvent* event)
static uint32_t
translateModifiers(const unsigned xstate)
{
	return (((xstate & ShiftMask)   ? PUGL_MOD_SHIFT  : 0) |
	        ((xstate & ControlMask) ? PUGL_MOD_CTRL   : 0) |
	        ((xstate & Mod1Mask)    ? PUGL_MOD_ALT    : 0) |
	        ((xstate & Mod4Mask)    ? PUGL_MOD_SUPER  : 0));
	return (((xstate & ShiftMask)   ? PUGL_MOD_SHIFT  : 0u) |
	        ((xstate & ControlMask) ? PUGL_MOD_CTRL   : 0u) |
	        ((xstate & Mod1Mask)    ? PUGL_MOD_ALT    : 0u) |
	        ((xstate & Mod4Mask)    ? PUGL_MOD_SUPER  : 0u));
}

static PuglEvent


@@ 500,22 565,23 @@ translateEvent(PuglView* view, XEvent xevent)
		event.expose.y      = xevent.xexpose.y;
		event.expose.width  = xevent.xexpose.width;
		event.expose.height = xevent.xexpose.height;
		event.expose.count  = xevent.xexpose.count;
		break;
	case MotionNotify:
		event.type           = PUGL_MOTION;
		event.motion.time    = xevent.xmotion.time / 1e3;
		event.motion.time    = (double)xevent.xmotion.time / 1e3;
		event.motion.x       = xevent.xmotion.x;
		event.motion.y       = xevent.xmotion.y;
		event.motion.xRoot   = xevent.xmotion.x_root;
		event.motion.yRoot   = xevent.xmotion.y_root;
		event.motion.state   = translateModifiers(xevent.xmotion.state);
		event.motion.isHint  = (xevent.xmotion.is_hint == NotifyHint);
		if (xevent.xmotion.is_hint == NotifyHint) {
			event.motion.flags |= PUGL_IS_HINT;
		}
		break;
	case ButtonPress:
		if (xevent.xbutton.button >= 4 && xevent.xbutton.button <= 7) {
			event.type           = PUGL_SCROLL;
			event.scroll.time    = xevent.xbutton.time / 1e3;
			event.scroll.time    = (double)xevent.xbutton.time / 1e3;
			event.scroll.x       = xevent.xbutton.x;
			event.scroll.y       = xevent.xbutton.y;
			event.scroll.xRoot   = xevent.xbutton.x_root;


@@ 524,10 590,22 @@ translateEvent(PuglView* view, XEvent xevent)
			event.scroll.dx      = 0.0;
			event.scroll.dy      = 0.0;
			switch (xevent.xbutton.button) {
			case 4: event.scroll.dy =  1.0; break;
			case 5: event.scroll.dy = -1.0; break;
			case 6: event.scroll.dx = -1.0; break;
			case 7: event.scroll.dx =  1.0; break;
			case 4:
				event.scroll.dy        = 1.0;
				event.scroll.direction = PUGL_SCROLL_UP;
				break;
			case 5:
				event.scroll.dy        = -1.0;
				event.scroll.direction = PUGL_SCROLL_DOWN;
				break;
			case 6:
				event.scroll.dx        = -1.0;
				event.scroll.direction = PUGL_SCROLL_LEFT;
				break;
			case 7:
				event.scroll.dx        = 1.0;
				event.scroll.direction = PUGL_SCROLL_RIGHT;
				break;
			}
			// fallthru
		}


@@ 537,7 615,7 @@ translateEvent(PuglView* view, XEvent xevent)
			event.button.type   = ((xevent.type == ButtonPress)
			                       ? PUGL_BUTTON_PRESS
			                       : PUGL_BUTTON_RELEASE);
			event.button.time   = xevent.xbutton.time / 1e3;
			event.button.time   = (double)xevent.xbutton.time / 1e3;
			event.button.x      = xevent.xbutton.x;
			event.button.y      = xevent.xbutton.y;
			event.button.xRoot  = xevent.xbutton.x_root;


@@ 551,7 629,7 @@ translateEvent(PuglView* view, XEvent xevent)
		event.type       = ((xevent.type == KeyPress)
		                    ? PUGL_KEY_PRESS
		                    : PUGL_KEY_RELEASE);
		event.key.time   = xevent.xkey.time / 1e3;
		event.key.time   = (double)xevent.xkey.time / 1e3;
		event.key.x      = xevent.xkey.x;
		event.key.y      = xevent.xkey.y;
		event.key.xRoot  = xevent.xkey.x_root;


@@ 564,7 642,7 @@ translateEvent(PuglView* view, XEvent xevent)
		event.type            = ((xevent.type == EnterNotify)
		                         ? PUGL_POINTER_IN
		                         : PUGL_POINTER_OUT);
		event.crossing.time   = xevent.xcrossing.time / 1e3;
		event.crossing.time   = (double)xevent.xcrossing.time / 1e3;
		event.crossing.x      = xevent.xcrossing.x;
		event.crossing.y      = xevent.xcrossing.y;
		event.crossing.xRoot  = xevent.xcrossing.x_root;


@@ 581,7 659,12 @@ translateEvent(PuglView* view, XEvent xevent)
	case FocusIn:
	case FocusOut:
		event.type = (xevent.type == FocusIn) ? PUGL_FOCUS_IN : PUGL_FOCUS_OUT;
		event.focus.grab = (xevent.xfocus.mode != NotifyNormal);
		event.focus.mode = PUGL_CROSSING_NORMAL;
		if (xevent.xfocus.mode == NotifyGrab) {
			event.focus.mode = PUGL_CROSSING_GRAB;
		} else if (xevent.xfocus.mode == NotifyUngrab) {
			event.focus.mode = PUGL_CROSSING_UNGRAB;
		}
		break;

	default:


@@ 646,7 729,8 @@ puglStartTimer(PuglView* view, uintptr_t id, double timeout)
		PuglWorldInternals*  w       = view->world->impl;
		Display* const       display = w->display;
		const XSyncCounter   counter = w->serverTimeCounter;
		const XSyncTrigger   trigger = {counter, XSyncRelative, value, 0};
		const XSyncTestType  type    = XSyncPositiveTransition;
		const XSyncTrigger   trigger = {counter, XSyncRelative, value, type};
		XSyncAlarmAttributes attr    = {trigger, value, True, XSyncAlarmActive};
		const XSyncAlarm     alarm   = XSyncCreateAlarm(display, 0x17, &attr);
		const PuglTimer      timer   = {alarm, view, id};


@@ 791,7 875,6 @@ mergeExposeEvents(PuglEvent* dst, const PuglEvent* src)
		dst->expose.y      = MIN(dst->expose.y, src->expose.y);
		dst->expose.width  = max_x - dst->expose.x;
		dst->expose.height = max_y - dst->expose.y;
		dst->expose.count  = MIN(dst->expose.count, src->expose.count);
	}
}



@@ 896,8 979,10 @@ handleTimerEvent(PuglWorld* world, XEvent xevent)

		for (size_t i = 0; i < world->impl->numTimers; ++i) {
			if (world->impl->timers[i].alarm == notify->alarm) {
				const PuglEventTimer ev = {PUGL_TIMER, 0, world->impl->timers[i].id};
				puglDispatchEvent(world->impl->timers[i].view, (const PuglEvent*)&ev);
				PuglEvent event = {{PUGL_TIMER, 0}};
				event.timer.id  = world->impl->timers[i].id;
				puglDispatchEvent(world->impl->timers[i].view,
				                  (const PuglEvent*)&event);
			}
		}



@@ 1033,7 1118,8 @@ puglGetTime(const PuglWorld* world)
{
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return ((double)ts.tv_sec + ts.tv_nsec / 1000000000.0) - world->startTime;
	return ((double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0) -
	       world->startTime;
}

PuglStatus


@@ 1048,7 1134,7 @@ PuglStatus
puglPostRedisplayRect(PuglView* view, PuglRect rect)
{
	const PuglEventExpose event = {
		PUGL_EXPOSE, 0, rect.x, rect.y, rect.width, rect.height, 0
		PUGL_EXPOSE, 0, rect.x, rect.y, rect.width, rect.height
	};

	if (view->world->impl->dispatchingEvents) {


@@ 1102,19 1188,27 @@ puglSetFrame(PuglView* view, const PuglRect frame)
}

PuglStatus
puglSetMinSize(PuglView* const view, const int width, const int height)
puglSetDefaultSize(PuglView* const view, const int width, const int height)
{
	Display* display = view->world->impl->display;
	view->defaultWidth  = width;
	view->defaultHeight = height;
	return updateSizeHints(view);
}

PuglStatus
puglSetMinSize(PuglView* const view, const int width, const int height)
{
	view->minWidth  = width;
	view->minHeight = height;
	return updateSizeHints(view);
}

	if (view->impl->win) {
		XSizeHints sizeHints = getSizeHints(view);
		XSetNormalHints(display, view->impl->win, &sizeHints);
	}

	return PUGL_SUCCESS;
PuglStatus
puglSetMaxSize(PuglView* const view, const int width, const int height)
{
	view->minWidth  = width;
	view->minHeight = height;
	return updateSizeHints(view);
}

PuglStatus


@@ 1124,19 1218,12 @@ puglSetAspectRatio(PuglView* const view,
                   const int       maxX,
                   const int       maxY)
{
	Display* display = view->world->impl->display;

	view->minAspectX = minX;
	view->minAspectY = minY;
	view->maxAspectX = maxX;
	view->maxAspectY = maxY;

	if (view->impl->win) {
		XSizeHints sizeHints = getSizeHints(view);
		XSetNormalHints(display, view->impl->win, &sizeHints);
	}

	return PUGL_SUCCESS;
	return updateSizeHints(view);
}

PuglStatus


@@ 1202,6 1289,44 @@ puglSetClipboard(PuglView* const   view,
	return PUGL_SUCCESS;
}

#ifdef HAVE_XCURSOR
static const unsigned cursor_nums[] = {
    XC_arrow,             // ARROW
    XC_xterm,             // CARET
    XC_crosshair,         // CROSSHAIR
    XC_hand2,             // HAND
    XC_pirate,            // NO
    XC_sb_h_double_arrow, // LEFT_RIGHT
    XC_sb_v_double_arrow, // UP_DOWN
};
#endif

PuglStatus
puglSetCursor(PuglView* view, PuglCursor cursor)
{
#ifdef HAVE_XCURSOR
	PuglInternals* const impl  = view->impl;
	const unsigned       index = (unsigned)cursor;
	const unsigned       count = sizeof(cursor_nums) / sizeof(cursor_nums[0]);
	if (index >= count) {
		return PUGL_BAD_PARAMETER;
	}

	const unsigned shape = cursor_nums[index];
	if (!impl->win || impl->cursorShape == shape) {
		return PUGL_SUCCESS;
	}

	impl->cursorShape = cursor_nums[index];

	return puglDefineCursorShape(view, impl->cursorShape);
#else
	(void)view;
	(void)cursor;
	return PUGL_FAILURE;
#endif
}

const PuglBackend*
puglStubBackend(void)
{

M pugl/detail/x11.h => pugl/detail/x11.h +8 -4
@@ 1,5 1,5 @@
/*
  Copyright 2012-2019 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file x11.h Shared definitions for X11 implementation.
   @file x11.h
   @brief Shared definitions for X11 implementation.
*/

#include "pugl/detail/types.h"


@@ 43,7 44,7 @@ typedef struct {
typedef struct {
	XID       alarm;
	PuglView* view;
	uint64_t  id;
	uintptr_t id;
} PuglTimer;

struct PuglWorldInternalsImpl {


@@ 60,13 61,16 @@ struct PuglWorldInternalsImpl {

struct PuglInternalsImpl {
	Display*     display;
	int          screen;
	XVisualInfo* vi;
	Window       win;
	XIC          xic;
	PuglSurface* surface;
	PuglEvent    pendingConfigure;
	PuglEvent    pendingExpose;
	int          screen;
#ifdef HAVE_XCURSOR
	unsigned     cursorShape;
#endif
};

static inline PuglStatus

M pugl/detail/x11_cairo.c => pugl/detail/x11_cairo.c +3 -2
@@ 1,5 1,5 @@
/*
  Copyright 2012-2019 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file x11_cairo.c Cairo graphics backend for X11.
   @file x11_cairo.c
   @brief Cairo graphics backend for X11.
*/

#include "pugl/detail/types.h"

M pugl/detail/x11_gl.c => pugl/detail/x11_gl.c +28 -25
@@ 1,5 1,5 @@
/*
  Copyright 2012-2019 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,14 15,15 @@
*/

/**
   @file x11_gl.c OpenGL graphics backend for X11.
   @file x11_gl.c
   @brief OpenGL graphics backend for X11.
*/

#include "pugl/detail/stub.h"
#include "pugl/detail/types.h"
#include "pugl/detail/x11.h"
#include "pugl/pugl.h"
#include "pugl/pugl_gl.h"
#include "pugl/pugl_stub.h"

#include <GL/glx.h>
#include <X11/X.h>


@@ 114,6 115,28 @@ puglX11GlConfigure(PuglView* view)
}

static PuglStatus
puglX11GlEnter(PuglView* view, const PuglEventExpose* PUGL_UNUSED(expose))
{
	PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface;
	glXMakeCurrent(view->impl->display, view->impl->win, surface->ctx);
	return PUGL_SUCCESS;
}

static PuglStatus
puglX11GlLeave(PuglView* view, const PuglEventExpose* expose)
{
	PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface;

	if (expose && surface->double_buffered) {
		glXSwapBuffers(view->impl->display, view->impl->win);
	}

	glXMakeCurrent(view->impl->display, None, NULL);

	return PUGL_SUCCESS;
}

static PuglStatus
puglX11GlCreate(PuglView* view)
{
	PuglInternals* const    impl      = view->impl;


@@ 152,7 175,9 @@ puglX11GlCreate(PuglView* view)

	const int swapInterval = view->hints[PUGL_SWAP_INTERVAL];
	if (glXSwapIntervalEXT && swapInterval != PUGL_DONT_CARE) {
		puglX11GlEnter(view, NULL);
		glXSwapIntervalEXT(display, impl->win, swapInterval);
		puglX11GlLeave(view, NULL);
	}

	glXGetConfig(impl->display,


@@ 175,28 200,6 @@ puglX11GlDestroy(PuglView* view)
	return PUGL_SUCCESS;
}

static PuglStatus
puglX11GlEnter(PuglView* view, const PuglEventExpose* PUGL_UNUSED(expose))
{
	PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface;
	glXMakeCurrent(view->impl->display, view->impl->win, surface->ctx);
	return PUGL_SUCCESS;
}

static PuglStatus
puglX11GlLeave(PuglView* view, const PuglEventExpose* expose)
{
	PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface;

	if (expose && surface->double_buffered) {
		glXSwapBuffers(view->impl->display, view->impl->win);
	}

	glXMakeCurrent(view->impl->display, None, NULL);

	return PUGL_SUCCESS;
}

PuglGlFunc
puglGetProcAddress(const char* name)
{

M pugl/gl.h => pugl/gl.h +3 -2
@@ 1,5 1,5 @@
/*
  Copyright 2012-2014 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file gl.h Portable header wrapper for gl.h.
   @file gl.h
   @brief Portable header wrapper for gl.h.

   Unfortunately, GL includes vary across platforms so this header allows for
   pure portable programs.

M pugl/glu.h => pugl/glu.h +3 -2
@@ 1,5 1,5 @@
/*
  Copyright 2012-2015 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file glu.h Portable header wrapper for glu.h.
   @file glu.h
   @brief Portable header wrapper for glu.h.

   Unfortunately, GL includes vary across platforms so this header allows for
   pure portable programs.

M pugl/pugl.h => pugl/pugl.h +115 -40
@@ 1,5 1,5 @@
/*
  Copyright 2012-2020 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file pugl.h Pugl API.
   @file pugl.h
   @brief Pugl API.
*/

#ifndef PUGL_PUGL_H


@@ 52,6 53,12 @@
#    endif
#endif

#if defined(__GNUC__)
#    define PUGL_CONST_FUNC __attribute__((const))
#else
#    define PUGL_CONST_FUNC
#endif

#ifdef __cplusplus
#	define PUGL_BEGIN_DECLS extern "C" {
#	define PUGL_END_DECLS }


@@ 63,9 70,13 @@
PUGL_BEGIN_DECLS

/**
   @defgroup pugl_api Pugl
   @defgroup pugl Pugl
   A minimal portable API for embeddable GUIs.
   @{

   @defgroup pugl_c C API
   Public C API.
   @{
*/

/**


@@ 87,7 98,7 @@ typedef struct {
   Event definitions.

   All updates to the view happen via events, which are dispatched to the
   view's #PuglEventFunc by Pugl.  Most events map directly to one from the
   view's event function by Pugl.  Most events map directly to one from the
   underlying window system, but some are constructed by Pugl itself so there
   is not necessarily a direct correspondence.



@@ 210,7 221,8 @@ typedef enum {
   Common flags for all event types.
*/
typedef enum {
	PUGL_IS_SEND_EVENT = 1 ///< Event is synthetic
	PUGL_IS_SEND_EVENT = 1, ///< Event is synthetic
	PUGL_IS_HINT       = 2  ///< Event is a hint (not direct user input)
} PuglEventFlag;

/**


@@ 228,6 240,22 @@ typedef enum {
} PuglCrossingMode;

/**
   Scroll direction.

   Describes the direction of a #PuglEventScroll along with whether the scroll
   is a "smooth" scroll.  The discrete directions are for devices like mouse
   wheels with constrained axes, while a smooth scroll is for those with
   arbitrary scroll direction freedom, like some touchpads.
*/
typedef enum {
	PUGL_SCROLL_UP,    ///< Scroll up
	PUGL_SCROLL_DOWN,  ///< Scroll down
	PUGL_SCROLL_LEFT,  ///< Scroll left
	PUGL_SCROLL_RIGHT, ///< Scroll right
	PUGL_SCROLL_SMOOTH ///< Smooth scroll in any direction
} PuglScrollDirection;

/**
   Common header for all event structs.
*/
typedef struct {


@@ 322,7 350,6 @@ typedef struct {
	double         y;      ///< View-relative Y coordinate
	double         width;  ///< Width of exposed region
	double         height; ///< Height of exposed region
	int            count;  ///< Number of expose events to follow
} PuglEventExpose;

/**


@@ 342,9 369,9 @@ typedef PuglEventAny PuglEventClose;
   view with the keyboard focus will receive any key press or release events.
*/
typedef struct {
	PuglEventType  type;  ///< #PUGL_FOCUS_IN or #PUGL_FOCUS_OUT
	PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
	bool           grab;  ///< True iff this is a grab/ungrab event
	PuglEventType    type;  ///< #PUGL_FOCUS_IN or #PUGL_FOCUS_OUT
	PuglEventFlags   flags; ///< Bitwise OR of #PuglEventFlag values
	PuglCrossingMode mode;  ///< Reason for focus change
} PuglEventFocus;

/**


@@ 355,12 382,12 @@ typedef struct {
   as text input.

   Keys are represented portably as Unicode code points, using the "natural"
   code point for the key where possible (see #PuglKey for details).  The #key
   code point for the key where possible (see #PuglKey for details).  The `key`
   field is the code for the pressed key, without any modifiers applied.  For
   example, a press or release of the 'A' key will have #key 97 ('a')
   example, a press or release of the 'A' key will have `key` 97 ('a')
   regardless of whether shift or control are being held.

   Alternatively, the raw #keycode can be used to work directly with physical
   Alternatively, the raw `keycode` can be used to work directly with physical
   keys, but note that this value is not portable and differs between platforms
   and hardware.
*/


@@ 407,7 434,7 @@ typedef struct {

   This event is sent when the pointer enters or leaves the view.  This can
   happen for several reasons (not just the user dragging the pointer over the
   window edge), as described by the #mode field.
   window edge), as described by the `mode` field.
*/
typedef struct {
	PuglEventType    type;  ///< #PUGL_POINTER_IN or #PUGL_POINTER_OUT


@@ 448,30 475,29 @@ typedef struct {
	double         xRoot;  ///< Root-relative X coordinate
	double         yRoot;  ///< Root-relative Y coordinate
	PuglMods       state;  ///< Bitwise OR of #PuglMod flags
	bool           isHint; ///< True iff this event is a motion hint
	bool           focus;  ///< True iff this is the focused view
} PuglEventMotion;

/**
   Scroll event.

   The scroll distance is expressed in "lines", an arbitrary unit that
   corresponds to a single tick of a detented mouse wheel.  For example, #dy =
   corresponds to a single tick of a detented mouse wheel.  For example, `dy` =
   1.0 scrolls 1 line up.  Some systems and devices support finer resolution
   and/or higher values for fast scrolls, so programs should handle any value
   gracefully.
*/
typedef struct {
	PuglEventType  type;  ///< #PUGL_SCROLL
	PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
	double         time;  ///< Time in seconds
	double         x;     ///< View-relative X coordinate
	double         y;     ///< View-relative Y coordinate
	double         xRoot; ///< Root-relative X coordinate
	double         yRoot; ///< Root-relative Y coordinate
	PuglMods       state; ///< Bitwise OR of #PuglMod flags
	double         dx;    ///< Scroll X distance in lines
	double         dy;    ///< Scroll Y distance in lines
	PuglEventType       type;      ///< #PUGL_SCROLL
	PuglEventFlags      flags;     ///< Bitwise OR of #PuglEventFlag values
	double              time;      ///< Time in seconds
	double              x;         ///< View-relative X coordinate
	double              y;         ///< View-relative Y coordinate
	double              xRoot;     ///< Root-relative X coordinate
	double              yRoot;     ///< Root-relative Y coordinate
	PuglMods            state;     ///< Bitwise OR of #PuglMod flags
	PuglScrollDirection direction; ///< Scroll direction
	double              dx;        ///< Scroll X distance in lines
	double              dy;        ///< Scroll Y distance in lines
} PuglEventScroll;

/**


@@ 494,7 520,7 @@ typedef struct {
   This event is sent at the regular interval specified in the call to
   puglStartTimer() that activated it.

   The #id is the application-specific ID given to puglStartTimer() which
   The `id` is the application-specific ID given to puglStartTimer() which
   distinguishes this timer from others.  It should always be checked in the
   event handler, even in applications that register only one timer.
*/


@@ 507,7 533,7 @@ typedef struct {
/**
   View event.

   This is a union of all event types.  The #type must be checked to determine
   This is a union of all event types.  The type must be checked to determine
   which fields are safe to access.  A pointer to PuglEvent can either be cast
   to the appropriate type, or the union members used.



@@ 548,6 574,7 @@ typedef enum {
	PUGL_FAILURE,               ///< Non-fatal failure
	PUGL_UNKNOWN_ERROR,         ///< Unknown system error
	PUGL_BAD_BACKEND,           ///< Invalid or missing backend
	PUGL_BAD_CONFIGURATION,     ///< Invalid view configuration
	PUGL_BAD_PARAMETER,         ///< Invalid parameter
	PUGL_BACKEND_FAILED,        ///< Backend initialisation failed
	PUGL_REGISTRATION_FAILED,   ///< Class registration failed


@@ 594,7 621,7 @@ typedef struct PuglWorldImpl PuglWorld;
typedef void* PuglWorldHandle;

/**
   The type of a PuglWorld.
   The type of a World.
*/
typedef enum {
	PUGL_PROGRAM, ///< Top-level application


@@ 728,19 755,18 @@ puglGetTime(const PuglWorld* world);
   This function is a single iteration of the main loop, and should be called
   repeatedly to update all views.

   If a positive timeout is given, then events will be processed for that
   amount of time, starting from when this function was called.  For purely
   event-driven programs, a timeout of -1.0 can be used to block indefinitely
   until something happens.  For continuously animating programs, a timeout
   that is a reasonable fraction of the ideal frame period should be used, to
   minimise input latency by ensuring that as many input events are consumed as
   possible before drawing.  Plugins should always use a timeout of 0.0 to
   avoid blocking the host.
   If `timeout` is zero, then this function will not block.  Plugins should
   always use a timeout of zero to avoid blocking the host.

   @param world The world to update.
   If a positive `timeout` is given, then events will be processed for that
   amount of time, starting from when this function was called.

   @param timeout Maximum time to wait, in seconds.  If zero, the call returns
   immediately, if negative, the call blocks indefinitely.
   If a negative `timeout` is given, this function will block indefinitely
   until an event occurs.

   For continuously animating programs, a timeout that is a reasonable fraction
   of the ideal frame period should be used, to minimise 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.
*/


@@ 937,6 963,16 @@ PUGL_API PuglStatus
puglSetFrame(PuglView* view, PuglRect frame);

/**
   Set the default size of the view.

   This should be called before puglResize() to set the default size of the
   view, which will be the initial size of the window if this is a top level
   view.
*/
PUGL_API PuglStatus
puglSetDefaultSize(PuglView* view, int width, int height);

/**
   Set the minimum size of the view.

   If an initial minimum size is known, this should be called before


@@ 946,6 982,15 @@ PUGL_API PuglStatus
puglSetMinSize(PuglView* view, int width, int height);

/**
   Set the maximum size of the view.

   If an initial maximum size is known, this should be called before
   puglRealize() to avoid stutter, though it can be called afterwards as well.
*/
PUGL_API PuglStatus
puglSetMaxSize(PuglView* view, int width, int height);

/**
   Set the view aspect ratio range.

   The x and y values here represent a ratio of width to height.  To set a


@@ 992,6 1037,9 @@ puglSetParentWindow(PuglView* view, PuglNativeView parent);
   Set this for transient children like dialogs, to have them properly
   associated with their parent window.  This should be called before
   puglRealize().

   A view can either have a parent (for embedding) or a transient parent (for
   top-level windows like dialogs), but not both.
*/
PUGL_API PuglStatus
puglSetTransientFor(PuglView* view, PuglNativeView parent);


@@ 1091,6 1139,22 @@ puglPostRedisplayRect(PuglView* view, PuglRect rect);
*/

/**
   A mouse cursor type.

   This is a portable subset of mouse cursors that exist on X11, MacOS, and
   Windows.
*/
typedef enum {
	PUGL_CURSOR_ARROW,      ///< Default pointing arrow
	PUGL_CURSOR_CARET,      ///< Caret (I-Beam) for text entry
	PUGL_CURSOR_CROSSHAIR,  ///< Cross-hair
	PUGL_CURSOR_HAND,       ///< Hand with a pointing finger
	PUGL_CURSOR_NO,         ///< Operation not allowed
	PUGL_CURSOR_LEFT_RIGHT, ///< Left/right arrow for horizontal resize
	PUGL_CURSOR_UP_DOWN,    ///< Up/down arrow for vertical resize
} PuglCursor;

/**
   Grab the keyboard input focus.
*/
PUGL_API PuglStatus


@@ 1134,6 1198,16 @@ PUGL_API const void*
puglGetClipboard(PuglView* view, const char** type, size_t* len);

/**
   Set the mouse cursor.

   This changes the system cursor that is displayed when the pointer is inside
   the view.  May fail if setting the cursor is not supported on this system,
   for example if compiled on X11 without Xcursor support.
 */
PUGL_API PuglStatus
puglSetCursor(PuglView* view, PuglCursor cursor);

/**
   Request user attention.

   This hints to the system that the window or application requires attention


@@ 1496,6 1570,7 @@ puglLeaveContext(PuglView* view, bool drawing);
/**
   @}
   @}
   @}
*/

PUGL_END_DECLS

M pugl/pugl.hpp => pugl/pugl.hpp +627 -47
@@ 1,5 1,5 @@
/*
  Copyright 2012-2019 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file pugl.hpp Pugl C++ API wrapper.
   @file pugl.hpp
   @brief Pugl C++ API wrapper.
*/

#ifndef PUGL_PUGL_HPP


@@ 23,12 24,18 @@

#include "pugl/pugl.h"

/**
   @defgroup puglxx C++
#include <cassert>
#include <chrono>
#include <functional>
#include <memory>
#include <stdexcept>
#include <type_traits>

/**
   @defgroup pugl_cxx C++ API
   C++ API wrapper.

   @ingroup pugl_api
   @ingroup pugl
   @{
*/



@@ 37,84 44,657 @@
*/
namespace pugl {

namespace detail {

/// Free function for a C object
template<typename T>
using FreeFunc = void (*)(T*);

/// Simple overhead-free deleter for a C object
template<typename T, FreeFunc<T> Free>
struct Deleter {
	void operator()(T* ptr) { Free(ptr); }
};

/// Generic C++ wrapper for a C object
template<class T, FreeFunc<T> Free>
class Wrapper
{
public:
	T*       cobj() { return _ptr.get(); }
	const T* cobj() const { return _ptr.get(); }

protected:
	explicit Wrapper(T* ptr)
	    : _ptr(ptr, Deleter<T, Free>{})
	{}

private:
	std::unique_ptr<T, Deleter<T, Free>> _ptr;
};

} // namespace detail

using Rect = PuglRect; ///< @copydoc PuglRect

/**
   A drawable region that receives events.
   @defgroup eventsxx Events
   @ingroup pugl_cxx
   @copydoc events
   @{
*/

   This is a thin wrapper for a PuglView that contains only a pointer.
/**
   A strongly-typed analogue of PuglEvent.

   @ingroup puglxx
   This is bit-for-bit identical to the corresponding PuglEvent, so events are
   simply cast to this type to avoid any copying overhead.

   @tparam t The `type` field of the corresponding PuglEvent.

   @tparam Base The specific struct type of the corresponding PuglEvent.
*/
class View {
template<PuglEventType t, class Base>
struct Event final : Base {
	using BaseEvent = Base;

	static constexpr const PuglEventType type = t;
};

using Mod          = PuglMod;          ///< @copydoc PuglMod
using Mods         = PuglMods;         ///< @copydoc PuglMods
using Key          = PuglKey;          ///< @copydoc PuglKey
using EventType    = PuglEventType;    ///< @copydoc PuglEventType
using EventFlag    = PuglEventFlag;    ///< @copydoc PuglEventFlag
using EventFlags   = PuglEventFlags;   ///< @copydoc PuglEventFlags
using CrossingMode = PuglCrossingMode; ///< @copydoc PuglCrossingMode

/// @copydoc PuglEventCreate
using CreateEvent = Event<PUGL_CREATE, PuglEventCreate>;

/// @copydoc PuglEventDestroy
using DestroyEvent = Event<PUGL_DESTROY, PuglEventDestroy>;

/// @copydoc PuglEventConfigure
using ConfigureEvent = Event<PUGL_CONFIGURE, PuglEventConfigure>;

/// @copydoc PuglEventMap
using MapEvent = Event<PUGL_MAP, PuglEventMap>;

/// @copydoc PuglEventUnmap
using UnmapEvent = Event<PUGL_UNMAP, PuglEventUnmap>;

/// @copydoc PuglEventUpdate
using UpdateEvent = Event<PUGL_UPDATE, PuglEventUpdate>;

/// @copydoc PuglEventExpose
using ExposeEvent = Event<PUGL_EXPOSE, PuglEventExpose>;

/// @copydoc PuglEventClose
using CloseEvent = Event<PUGL_CLOSE, PuglEventClose>;

/// @copydoc PuglEventFocus
using FocusInEvent = Event<PUGL_FOCUS_IN, PuglEventFocus>;

/// @copydoc PuglEventFocus
using FocusOutEvent = Event<PUGL_FOCUS_OUT, PuglEventFocus>;

/// @copydoc PuglEventKey
using KeyPressEvent = Event<PUGL_KEY_PRESS, PuglEventKey>;

/// @copydoc PuglEventKey
using KeyReleaseEvent = Event<PUGL_KEY_RELEASE, PuglEventKey>;

/// @copydoc PuglEventText
using TextEvent = Event<PUGL_TEXT, PuglEventText>;

/// @copydoc PuglEventCrossing
using PointerInEvent = Event<PUGL_POINTER_IN, PuglEventCrossing>;

/// @copydoc PuglEventCrossing
using PointerOutEvent = Event<PUGL_POINTER_OUT, PuglEventCrossing>;

/// @copydoc PuglEventButton
using ButtonPressEvent = Event<PUGL_BUTTON_PRESS, PuglEventButton>;

/// @copydoc PuglEventButton
using ButtonReleaseEvent = Event<PUGL_BUTTON_RELEASE, PuglEventButton>;

/// @copydoc PuglEventMotion
using MotionEvent = Event<PUGL_MOTION, PuglEventMotion>;

/// @copydoc PuglEventScroll
using ScrollEvent = Event<PUGL_SCROLL, PuglEventScroll>;

/// @copydoc PuglEventClient
using ClientEvent = Event<PUGL_CLIENT, PuglEventClient>;

/// @copydoc PuglEventTimer
using TimerEvent = Event<PUGL_TIMER, PuglEventTimer>;

/**
   @}
   @defgroup statusxx Status
   @ingroup pugl_cxx
   @copydoc status
   @{
*/

/// @copydoc PuglStatus
enum class Status {
	success,             ///< @copydoc PUGL_SUCCESS
	failure,             ///< @copydoc PUGL_FAILURE
	unknownError,        ///< @copydoc PUGL_UNKNOWN_ERROR
	badBackend,          ///< @copydoc PUGL_BAD_BACKEND
	badConfiguration,    ///< @copydoc PUGL_BAD_CONFIGURATION
	badParameter,        ///< @copydoc PUGL_BAD_PARAMETER
	backendFailed,       ///< @copydoc PUGL_BACKEND_FAILED
	registrationFailed,  ///< @copydoc PUGL_REGISTRATION_FAILED
	realizeFailed,       ///< @copydoc PUGL_REALIZE_FAILED
	setFormatFailed,     ///< @copydoc PUGL_SET_FORMAT_FAILED
	createContextFailed, ///< @copydoc PUGL_CREATE_CONTEXT_FAILED
	unsupportedType,     ///< @copydoc PUGL_UNSUPPORTED_TYPE
};

static_assert(Status(PUGL_UNSUPPORTED_TYPE) == Status::unsupportedType, "");

/// @copydoc puglStrerror
static inline const char*
strerror(const pugl::Status status)
{
	return puglStrerror(static_cast<PuglStatus>(status));
}

/**
   @}
   @defgroup worldxx World
   @ingroup pugl_cxx
   @copydoc world
   @{
*/

class World;

/// @copydoc PuglWorldType
enum class WorldType {
	program, ///< @copydoc PUGL_PROGRAM
	module,  ///< @copydoc PUGL_MODULE
};

static_assert(WorldType(PUGL_MODULE) == WorldType::module, "");

/// @copydoc PuglWorldFlag
enum class WorldFlag {
	threads = PUGL_WORLD_THREADS, ///< @copydoc PUGL_WORLD_THREADS
};

static_assert(WorldFlag(PUGL_WORLD_THREADS) == WorldFlag::threads, "");

using WorldFlags = PuglWorldFlags; ///< @copydoc PuglWorldFlags

/// @copydoc PuglLogLevel
enum class LogLevel {
	err     = PUGL_LOG_LEVEL_ERR,     ///< @copydoc PUGL_LOG_LEVEL_ERR
	warning = PUGL_LOG_LEVEL_WARNING, ///< @copydoc PUGL_LOG_LEVEL_WARNING
	info    = PUGL_LOG_LEVEL_INFO,    ///< @copydoc PUGL_LOG_LEVEL_INFO
	debug   = PUGL_LOG_LEVEL_DEBUG,   ///< @copydoc PUGL_LOG_LEVEL_DEBUG
};

static_assert(LogLevel(PUGL_LOG_LEVEL_DEBUG) == LogLevel::debug, "");

/// @copydoc PuglLogFunc
using LogFunc =
    std::function<void(World& world, LogLevel level, const char* msg)>;

/**
   A `std::chrono` compatible clock that uses Pugl time.
*/
class Clock
{
public:
	View(int* pargc, char** argv)
		: _view(puglInit(pargc, argv))
	using rep        = double;                         ///< Time representation
	using duration   = std::chrono::duration<double>;  ///< Duration in seconds
	using time_point = std::chrono::time_point<Clock>; ///< A Pugl time point

	static constexpr bool is_steady = true; ///< Steady clock flag, always true

	/// Construct a clock that uses time from puglGetTime()
	explicit Clock(World& world)
	    : _world{world}
	{}

	/// Return the current time
	time_point now() const;

private:
	const pugl::World& _world;
};

/// @copydoc PuglWorld
class World : public detail::Wrapper<PuglWorld, puglFreeWorld>
{
public:
	explicit World(WorldType type, WorldFlags flags)
	    : Wrapper{puglNewWorld(static_cast<PuglWorldType>(type), flags)}
	    , _clock(*this)
	{
		if (!cobj()) {
			throw std::runtime_error("Failed to create pugl::World");
		}
	}

	explicit World(WorldType type)
	    : World{type, {}}
	{
		puglSetHandle(_view, this);
		puglSetEventFunc(_view, _onEvent);
		if (!cobj()) {
			throw std::runtime_error("Failed to create pugl::World");
		}
	}

	virtual ~View() { puglDestroy(_view); }
	/// @copydoc puglGetNativeWorld
	void* nativeWorld() { return puglGetNativeWorld(cobj()); }

	// TODO: setLogFunc

	virtual void initWindowParent(PuglNativeWindow parent) {
		puglInitWindowParent(_view, parent);
	Status setLogLevel(const LogLevel level)
	{
		return static_cast<Status>(
		    puglSetLogLevel(cobj(), static_cast<PuglLogLevel>(level)));
	}

	virtual void initWindowSize(int width, int height) {
		puglInitWindowSize(_view, width, height);
	/// @copydoc puglSetClassName
	Status setClassName(const char* const name)
	{
		return static_cast<Status>(puglSetClassName(cobj(), name));
	}

	virtual void initWindowMinSize(int width, int height) {
		puglInitWindowMinSize(_view, width, height);
	/// @copydoc puglGetTime
	double time() const { return puglGetTime(cobj()); }

	/// @copydoc puglUpdate
	Status update(const double timeout)
	{
		return static_cast<Status>(puglUpdate(cobj(), timeout));
	}

	/// Return a clock that uses Pugl time
	const Clock& clock() { return _clock; }

private:
	Clock _clock;
};

inline Clock::time_point
Clock::now() const
{
	return time_point{duration{_world.time()}};
}

/**
   @}
   @defgroup viewxx View
   @ingroup pugl_cxx
   @copydoc view
   @{
*/

using Backend    = PuglBackend;    ///< @copydoc PuglBackend
using NativeView = PuglNativeView; ///< @copydoc PuglNativeView

/// @copydoc PuglViewHint
enum class ViewHint {
	useCompatProfile,    ///< @copydoc PUGL_USE_COMPAT_PROFILE
	useDebugContext,     ///< @copydoc PUGL_USE_DEBUG_CONTEXT
	contextVersionMajor, ///< @copydoc PUGL_CONTEXT_VERSION_MAJOR
	contextVersionMinor, ///< @copydoc PUGL_CONTEXT_VERSION_MINOR
	redBits,             ///< @copydoc PUGL_RED_BITS
	greenBits,           ///< @copydoc PUGL_GREEN_BITS
	blueBits,            ///< @copydoc PUGL_BLUE_BITS
	alphaBits,           ///< @copydoc PUGL_ALPHA_BITS
	depthBits,           ///< @copydoc PUGL_DEPTH_BITS
	stencilBits,         ///< @copydoc PUGL_STENCIL_BITS
	samples,             ///< @copydoc PUGL_SAMPLES
	doubleBuffer,        ///< @copydoc PUGL_DOUBLE_BUFFER
	swapInterval,        ///< @copydoc PUGL_SWAP_INTERVAL
	resizable,           ///< @copydoc PUGL_RESIZABLE
	ignoreKeyRepeat,     ///< @copydoc PUGL_IGNORE_KEY_REPEAT
};

static_assert(ViewHint(PUGL_IGNORE_KEY_REPEAT) == ViewHint::ignoreKeyRepeat,
              "");

using ViewHintValue = PuglViewHintValue; ///< @copydoc PuglViewHintValue

/// @copydoc PuglCursor
enum class Cursor {
	arrow,     ///< @copydoc PUGL_CURSOR_ARROW
	caret,     ///< @copydoc PUGL_CURSOR_CARET
	crosshair, ///< @copydoc PUGL_CURSOR_CROSSHAIR
	hand,      ///< @copydoc PUGL_CURSOR_HAND
	no,        ///< @copydoc PUGL_CURSOR_NO
	leftRight, ///< @copydoc PUGL_CURSOR_LEFT_RIGHT
	upDown,    ///< @copydoc PUGL_CURSOR_UP_DOWN
};

static_assert(Cursor(PUGL_CURSOR_UP_DOWN) == Cursor::upDown, "");

/// @copydoc PuglView
class View : protected detail::Wrapper<PuglView, puglFreeView>
{
public:
	/**
	   @name Setup
	   Methods for creating and destroying a view.
	   @{
	*/

	explicit View(World& world)
	    : Wrapper{puglNewView(world.cobj())}
	    , _world(world)
	{
		if (!cobj()) {
			throw std::runtime_error("Failed to create pugl::View");
		}

		puglSetHandle(cobj(), this);
		puglSetEventFunc(cobj(), dispatchEvent);
	}

	virtual ~View() = default;

	View(const View&) = delete;
	View& operator=(const View&) = delete;

	View(View&&)   = delete;
	View&& operator=(View&&) = delete;

	const pugl::World& world() const { return _world; }
	pugl::World&       world() { return _world; }

	/// @copydoc puglSetViewHint
	Status setHint(ViewHint hint, int value)
	{
		return static_cast<Status>(
		    puglSetViewHint(cobj(), static_cast<PuglViewHint>(hint), value));
	}

	/**
	   @}
	   @name Frame
	   Methods for working with the position and size of a view.
	   @{
	*/

	/// @copydoc puglGetFrame
	Rect frame() const { return puglGetFrame(cobj()); }

	/// @copydoc puglSetFrame
	Status setFrame(Rect frame)
	{
		return static_cast<Status>(puglSetFrame(cobj(), frame));
	}

	/// @copydoc puglSetDefaultSize
	Status setDefaultSize(int width, int height)
	{
		return static_cast<Status>(puglSetDefaultSize(cobj(), width, height));
	}

	/// @copydoc puglSetMinSize
	Status setMinSize(int width, int height)
	{
		return static_cast<Status>(puglSetMinSize(cobj(), width, height));
	}

	virtual void initWindowAspectRatio(int min_x, int min_y, int max_x, int max_y) {
		puglInitWindowAspectRatio(_view, min_x, min_y, max_x, max_y);
	/// @copydoc puglSetMaxSize
	Status setMaxSize(int width, int height)
	{
		return static_cast<Status>(puglSetMaxSize(cobj(), width, height));
	}

	virtual void initResizable(bool resizable) {
		puglInitResizable(_view, resizable);
	/// @copydoc puglSetAspectRatio
	Status setAspectRatio(int minX, int minY, int maxX, int maxY)
	{
		return static_cast<Status>(
		    puglSetAspectRatio(cobj(), minX, minY, maxX, maxY));
	}

	virtual void initTransientFor(uintptr_t parent) {
		puglInitTransientFor(_view, parent);
	/**
	   @}
	   @name Windows
	   Methods for working with top-level windows.
	   @{
	*/

	/// @copydoc puglSetWindowTitle
	Status setWindowTitle(const char* title)
	{
		return static_cast<Status>(puglSetWindowTitle(cobj(), title));
	}

	virtual void initBackend(const PuglBackend* backend) {
		puglInitBackend(_view, backend);
	/// @copydoc puglSetParentWindow
	Status setParentWindow(NativeView parent)
	{
		return static_cast<Status>(puglSetParentWindow(cobj(), parent));
	}

	/// @copydoc puglSetTransientFor
	Status setTransientFor(NativeView parent)
	{
		return static_cast<Status>(puglSetTransientFor(cobj(), parent));
	}

	/// @copydoc puglRealize
	Status realize() { return static_cast<Status>(puglRealize(cobj())); }

	/// @copydoc puglShowWindow
	Status showWindow() { return static_cast<Status>(puglShowWindow(cobj())); }

	/// @copydoc puglHideWindow
	Status hideWindow() { return static_cast<Status>(puglHideWindow(cobj())); }

	/// @copydoc puglGetVisible
	bool visible() const { return puglGetVisible(cobj()); }

	/// @copydoc puglGetNativeWindow
	NativeView nativeWindow() { return puglGetNativeWindow(cobj()); }

	/**
	   @}
	   @name Graphics
	   Methods for working with the graphics context and scheduling
	   redisplays.
	   @{
	*/

	/// @copydoc puglGetContext
	void* context() { return puglGetContext(cobj()); }

	/// @copydoc puglPostRedisplay
	Status postRedisplay()
	{
		return static_cast<Status>(puglPostRedisplay(cobj()));
	}

	virtual void createWindow(const char* title) {
		puglCreateWindow(_view, title);
	/// @copydoc puglPostRedisplayRect
	Status postRedisplayRect(const Rect rect)
	{
		return static_cast<Status>(puglPostRedisplayRect(cobj(), rect));
	}

	virtual void             showWindow()      { puglShowWindow(_view); }
	virtual void             hideWindow()      { puglHideWindow(_view); }
	virtual PuglNativeWindow getNativeWindow() { return puglGetNativeWindow(_view); }
	/**
	   @}
	   @name Interaction
	   Methods for interacting with the user and window system.
	   @{
	*/

	virtual void onEvent(const PuglEvent* event) = 0;
	/// @copydoc puglGrabFocus
	Status grabFocus() { return static_cast<Status>(puglGrabFocus(cobj())); }

	virtual void*      getContext()                 { return puglGetContext(_view); }
	virtual void       ignoreKeyRepeat(bool ignore) { puglIgnoreKeyRepeat(_view, ignore); }
	virtual void       grabFocus()                  { puglGrabFocus(_view); }
	virtual void       requestAttention()           { puglRequestAttention(_view); }
	virtual PuglStatus waitForEvent()               { return puglWaitForEvent(_view); }
	virtual PuglStatus processEvents()              { return puglProcessEvents(_view); }
	virtual void       postRedisplay()              { puglPostRedisplay(_view); }
	/// @copydoc puglHasFocus
	bool hasFocus() const { return puglHasFocus(cobj()); }

	PuglView* cobj() { return _view; }
	/// @copydoc puglSetBackend
	Status setBackend(const PuglBackend* backend)
	{
		return static_cast<Status>(puglSetBackend(cobj(), backend));
	}

	/// @copydoc puglSetCursor
	Status setCursor(const Cursor cursor)
	{
		return static_cast<Status>(
		    puglSetCursor(cobj(), static_cast<PuglCursor>(cursor)));
	}

	/// @copydoc puglRequestAttention
	Status requestAttention()
	{
		return static_cast<Status>(puglRequestAttention(cobj()));
	}

	/**
	   @}
	   @name Event Handlers
	   Methods called when events are dispatched to the view.
	   @{
	*/

	virtual Status onCreate(const CreateEvent&) PUGL_CONST_FUNC;
	virtual Status onDestroy(const DestroyEvent&) PUGL_CONST_FUNC;
	virtual Status onConfigure(const ConfigureEvent&) PUGL_CONST_FUNC;
	virtual Status onMap(const MapEvent&) PUGL_CONST_FUNC;
	virtual Status onUnmap(const UnmapEvent&) PUGL_CONST_FUNC;
	virtual Status onUpdate(const UpdateEvent&) PUGL_CONST_FUNC;
	virtual Status onExpose(const ExposeEvent&) PUGL_CONST_FUNC;
	virtual Status onClose(const CloseEvent&) PUGL_CONST_FUNC;
	virtual Status onFocusIn(const FocusInEvent&) PUGL_CONST_FUNC;
	virtual Status onFocusOut(const FocusOutEvent&) PUGL_CONST_FUNC;
	virtual Status onKeyPress(const KeyPressEvent&) PUGL_CONST_FUNC;
	virtual Status onKeyRelease(const KeyReleaseEvent&) PUGL_CONST_FUNC;
	virtual Status onText(const TextEvent&) PUGL_CONST_FUNC;
	virtual Status onPointerIn(const PointerInEvent&) PUGL_CONST_FUNC;
	virtual Status onPointerOut(const PointerOutEvent&) PUGL_CONST_FUNC;
	virtual Status onButtonPress(const ButtonPressEvent&) PUGL_CONST_FUNC;
	virtual Status onButtonRelease(const ButtonReleaseEvent&) PUGL_CONST_FUNC;
	virtual Status onMotion(const MotionEvent&) PUGL_CONST_FUNC;
	virtual Status onScroll(const ScrollEvent&) PUGL_CONST_FUNC;
	virtual Status onClient(const ClientEvent&) PUGL_CONST_FUNC;
	virtual Status onTimer(const TimerEvent&) PUGL_CONST_FUNC;

	/**
	   @}
	*/

	PuglView*       cobj() { return Wrapper::cobj(); }
	const PuglView* cobj() const { return Wrapper::cobj(); }

private:
	static void _onEvent(PuglView* view, const PuglEvent* event) {
		((View*)puglGetHandle(view))->onEvent(event);
	template<class Typed, class Base>
	static const Typed& typedEventRef(const Base& base)
	{
		const auto& event = static_cast<const Typed&>(base);
		static_assert(sizeof(event) == sizeof(typename Typed::BaseEvent), "");
		static_assert(std::is_standard_layout<Typed>::value, "");
		assert(event.type == Typed::type);
		return event;
	}

	PuglView* _view;
	static PuglStatus dispatchEvent(PuglView* view, const PuglEvent* event) noexcept {
		try {
			View* self = static_cast<View*>(puglGetHandle(view));

			return self->dispatch(event);
		} catch (...) {
			return PUGL_UNKNOWN_ERROR;
		}
	}

	PuglStatus dispatch(const PuglEvent* event)
	{
		switch (event->type) {
		case PUGL_NOTHING:
			return PUGL_SUCCESS;
		case PUGL_CREATE:
			return static_cast<PuglStatus>(
			    onCreate(typedEventRef<CreateEvent>(event->any)));
		case PUGL_DESTROY:
			return static_cast<PuglStatus>(
			    onDestroy(typedEventRef<DestroyEvent>(event->any)));
		case PUGL_CONFIGURE:
			return static_cast<PuglStatus>(onConfigure(
			    typedEventRef<ConfigureEvent>(event->configure)));
		case PUGL_MAP:
			return static_cast<PuglStatus>(
			    onMap(typedEventRef<MapEvent>(event->any)));
		case PUGL_UNMAP:
			return static_cast<PuglStatus>(
			    onUnmap(typedEventRef<UnmapEvent>(event->any)));
		case PUGL_UPDATE:
			return static_cast<PuglStatus>(
			    onUpdate(typedEventRef<UpdateEvent>(event->any)));
		case PUGL_EXPOSE:
			return static_cast<PuglStatus>(
			    onExpose(typedEventRef<ExposeEvent>(event->expose)));
		case PUGL_CLOSE:
			return static_cast<PuglStatus>(
			    onClose(typedEventRef<CloseEvent>(event->any)));
		case PUGL_FOCUS_IN:
			return static_cast<PuglStatus>(
			    onFocusIn(typedEventRef<FocusInEvent>(event->focus)));
		case PUGL_FOCUS_OUT:
			return static_cast<PuglStatus>(
			    onFocusOut(typedEventRef<FocusOutEvent>(event->focus)));
		case PUGL_KEY_PRESS:
			return static_cast<PuglStatus>(
			    onKeyPress(typedEventRef<KeyPressEvent>(event->key)));
		case PUGL_KEY_RELEASE:
			return static_cast<PuglStatus>(
			    onKeyRelease(typedEventRef<KeyReleaseEvent>(event->key)));
		case PUGL_TEXT:
			return static_cast<PuglStatus>(
			    onText(typedEventRef<TextEvent>(event->text)));
		case PUGL_POINTER_IN:
			return static_cast<PuglStatus>(onPointerIn(
			    typedEventRef<PointerInEvent>(event->crossing)));
		case PUGL_POINTER_OUT:
			return static_cast<PuglStatus>(onPointerOut(
			    typedEventRef<PointerOutEvent>(event->crossing)));
		case PUGL_BUTTON_PRESS:
			return static_cast<PuglStatus>(onButtonPress(
			    typedEventRef<ButtonPressEvent>(event->button)));
		case PUGL_BUTTON_RELEASE:
			return static_cast<PuglStatus>(onButtonRelease(
			    typedEventRef<ButtonReleaseEvent>(event->button)));
		case PUGL_MOTION:
			return static_cast<PuglStatus>(
			    onMotion(typedEventRef<MotionEvent>(event->motion)));
		case PUGL_SCROLL:
			return static_cast<PuglStatus>(
			    onScroll(typedEventRef<ScrollEvent>(event->scroll)));
		case PUGL_CLIENT:
			return static_cast<PuglStatus>(
			    onClient(typedEventRef<ClientEvent>(event->client)));
		case PUGL_TIMER:
			return static_cast<PuglStatus>(
			    onTimer(typedEventRef<TimerEvent>(event->timer)));
		}

		return PUGL_FAILURE;
	}

	World& _world;
};

}  // namespace pugl
/**
   @}
*/

} // namespace pugl

/**
   @}
*/

#endif  /* PUGL_PUGL_HPP */
#endif /* PUGL_PUGL_HPP */

A pugl/pugl.ipp => pugl/pugl.ipp +154 -0
@@ 0,0 1,154 @@
/*
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.

  THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

/**
   @file pugl.ipp
   @brief Pugl C++ API wrapper implementation.

   This file must be included exactly once in the application.
*/

#include "pugl/pugl.hpp"

namespace pugl {

Status
View::onCreate(const CreateEvent&)
{
	return pugl::Status::success;
}

Status
View::onDestroy(const DestroyEvent&)
{
	return pugl::Status::success;
}

Status
View::onConfigure(const ConfigureEvent&)
{
	return pugl::Status::success;
}

Status
View::onMap(const MapEvent&)
{
	return pugl::Status::success;
}

Status
View::onUnmap(const UnmapEvent&)
{
	return pugl::Status::success;
}

Status
View::onUpdate(const UpdateEvent&)
{
	return pugl::Status::success;
}

Status
View::onExpose(const ExposeEvent&)
{
	return pugl::Status::success;
}

Status
View::onClose(const CloseEvent&)
{
	return pugl::Status::success;
}

Status
View::onFocusIn(const FocusInEvent&)
{
	return pugl::Status::success;
}

Status
View::onFocusOut(const FocusOutEvent&)
{
	return pugl::Status::success;
}

Status
View::onKeyPress(const KeyPressEvent&)
{
	return pugl::Status::success;
}

Status
View::onKeyRelease(const KeyReleaseEvent&)
{
	return pugl::Status::success;
}

Status
View::onText(const TextEvent&)
{
	return pugl::Status::success;
}

Status
View::onPointerIn(const PointerInEvent&)
{
	return pugl::Status::success;
}

Status
View::onPointerOut(const PointerOutEvent&)
{
	return pugl::Status::success;
}

Status
View::onButtonPress(const ButtonPressEvent&)
{
	return pugl::Status::success;
}

Status
View::onButtonRelease(const ButtonReleaseEvent&)
{
	return pugl::Status::success;
}

Status
View::onMotion(const MotionEvent&)
{
	return pugl::Status::success;
}

Status
View::onScroll(const ScrollEvent&)
{
	return pugl::Status::success;
}

Status
View::onClient(const ClientEvent&)
{
	return pugl::Status::success;
}

Status
View::onTimer(const TimerEvent&)
{
	return pugl::Status::success;
}

}

M pugl/pugl_cairo.h => pugl/pugl_cairo.h +4 -3
@@ 1,5 1,5 @@
/*
  Copyright 2012-2019 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file pugl_cairo.h Declaration of Cairo backend accessor.
   @file pugl_cairo.h
   @brief Declaration of Cairo backend accessor.
*/

#ifndef PUGL_PUGL_CAIRO_H


@@ 28,7 29,7 @@ PUGL_BEGIN_DECLS
/**
   @defgroup cairo Cairo
   Cairo graphics support.
   @ingroup pugl_api
   @ingroup pugl_c
   @{
*/


A pugl/pugl_cairo.hpp => pugl/pugl_cairo.hpp +50 -0
@@ 0,0 1,50 @@
/*
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.

  THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

/**
   @file pugl_cairo.hpp
   @brief Declaration of Cairo backend accessor for C++.
*/

#ifndef PUGL_PUGL_CAIRO_HPP
#define PUGL_PUGL_CAIRO_HPP

#include "pugl/pugl.h"
#include "pugl/pugl_cairo.h"

namespace pugl {

/**
   @defgroup cairoxx Cairo
   Cairo graphics support.
   @ingroup pugl_cxx
   @{
*/

/// @copydoc puglCairoBackend
static inline const PuglBackend*
cairoBackend()
{
	return puglCairoBackend();
}

/**
   @}
*/

} // namespace pugl

#endif // PUGL_PUGL_CAIRO_HPP

M pugl/pugl_gl.h => pugl/pugl_gl.h +4 -3
@@ 1,5 1,5 @@
/*
  Copyright 2012-2019 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file pugl_gl.h OpenGL-specific API.
   @file pugl_gl.h
   @brief OpenGL-specific API.
*/

#ifndef PUGL_PUGL_GL_H


@@ 28,7 29,7 @@ PUGL_BEGIN_DECLS
/**
   @defgroup gl OpenGL
   OpenGL graphics support.
   @ingroup pugl_api
   @ingroup pugl_c
   @{
*/


A pugl/pugl_gl.hpp => pugl/pugl_gl.hpp +60 -0
@@ 0,0 1,60 @@
/*
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.

  THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

/**
   @file pugl_gl.hpp
   @brief OpenGL-specific C++ API.
*/

#ifndef PUGL_PUGL_GL_HPP
#define PUGL_PUGL_GL_HPP

#include "pugl/pugl.h"
#include "pugl/pugl_gl.h"

namespace pugl {

/**
   @defgroup glxx OpenGL
   OpenGL graphics support.
   @ingroup pugl_cxx
   @{
*/

/// @copydoc PuglGlFunc
using GlFunc = PuglGlFunc;

/// @copydoc puglGetProcAddress
static inline GlFunc
getProcAddress(const char* name)
{
	return puglGetProcAddress(name);
}

/// @copydoc puglGlBackend
static inline const PuglBackend*
glBackend()
{
	return puglGlBackend();
}

/**
   @}
*/

} // namespace pugl

#endif // PUGL_PUGL_GL_HPP

M pugl/pugl_stub.h => pugl/pugl_stub.h +4 -47
@@ 1,5 1,5 @@
/*
  Copyright 2019 David Robillard <http://drobilla.net>
  Copyright 2019-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 15,7 15,8 @@
*/

/**
   @file pugl_stub.h Stub backend functions and accessor declaration.
   @file pugl_stub.h
   @brief Stub backend functions and accessor declaration.
*/

#ifndef PUGL_PUGL_STUB_H


@@ 36,7 37,7 @@ PUGL_BEGIN_DECLS
   for other backends to reuse since not all need non-trivial implementations
   of every backend function.

   @ingroup pugl_api
   @ingroup pugl_c
   @{
*/



@@ 50,50 51,6 @@ PUGL_API
const PuglBackend*
puglStubBackend(void);

static inline PuglStatus
puglStubConfigure(PuglView* view)
{
	(void)view;
	return PUGL_SUCCESS;
}

static inline PuglStatus
puglStubCreate(PuglView* view)
{
	(void)view;
	return PUGL_SUCCESS;
}

static inline PuglStatus
puglStubDestroy(PuglView* view)
{
	(void)view;
	return PUGL_SUCCESS;
}

static inline PuglStatus
puglStubEnter(PuglView* view, const PuglEventExpose* expose)
{
	(void)view;
	(void)expose;
	return PUGL_SUCCESS;
}

static inline PuglStatus
puglStubLeave(PuglView* view, const PuglEventExpose* expose)
{
	(void)view;
	(void)expose;
	return PUGL_SUCCESS;
}

static inline void*
puglStubGetContext(PuglView* view)
{
	(void)view;
	return NULL;
}

/**
   @}
*/

A pugl/pugl_stub.hpp => pugl/pugl_stub.hpp +50 -0
@@ 0,0 1,50 @@
/*
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.

  THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

/**
   @file pugl_stub.hpp
   @brief Declaration of Stub backend accessor for C++.
*/

#ifndef PUGL_PUGL_STUB_HPP
#define PUGL_PUGL_STUB_HPP

#include "pugl/pugl.h"
#include "pugl/pugl_stub.h"

namespace pugl {

/**
   @defgroup stubxx Stub
   Stub graphics support.
   @ingroup pugl_cxx
   @{
*/

/// @copydoc puglStubBackend
static inline const PuglBackend*
stubBackend()
{
	return puglStubBackend();
}

/**
   @}
*/

} // namespace pugl

#endif // PUGL_PUGL_STUB_HPP

D pugl/pugl_stub_backend.h => pugl/pugl_stub_backend.h +0 -23
@@ 1,23 0,0 @@
/*
  Copyright 2012-2019 David Robillard <http://drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.

  THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#ifndef PUGL_PUGL_STUB_BACKEND_H
#define PUGL_PUGL_STUB_BACKEND_H

#warning "This header is deprecated, use pugl/pugl_stub.h instead."
#include "pugl/pugl_stub.h"

#endif // PUGL_PUGL_STUB_BACKEND_H

A shaders/header_330.glsl => shaders/header_330.glsl +5 -0
@@ 0,0 1,5 @@
#version 330 core

#define INTER(qualifiers)
#define UBO(qualifiers) layout(std140)


A shaders/header_420.glsl => shaders/header_420.glsl +5 -0
@@ 0,0 1,5 @@
#version 420 core

#define INTER(qualifiers) layout(qualifiers)
#define UBO(qualifiers) layout(std140, qualifiers)


M shaders/rect.frag => shaders/rect.frag +3 -5
@@ 1,5 1,3 @@
#version 330 core

/* The fragment shader uses the UV coordinates to calculate whether it is in
   the T, R, B, or L border.  These are then mixed with the border color, and
   their inverse is mixed with the fill color, to calculate the fragment color.


@@ 10,9 8,9 @@
   specified precisely in pixels to draw sharp lines.  The border width is just
   hardcoded, but could be made a uniform or vertex attribute easily enough. */

noperspective in vec2 f_uv;
noperspective in vec2 f_size;
noperspective in vec4 f_fillColor;
INTER(location = 0) noperspective in vec2 f_uv;
INTER(location = 1) noperspective in vec2 f_size;
INTER(location = 2) noperspective in vec4 f_fillColor;

layout(location = 0) out vec4 FragColor;


M shaders/rect.vert => shaders/rect.vert +8 -7
@@ 1,18 1,19 @@
#version 330 core

/* The vertex shader is trivial, but forwards scaled UV coordinates (in pixels)
   to the fragment shader for drawing the border. */

uniform mat4 u_projection;
UBO(binding = 0) uniform UniformBufferObject
{
	mat4 projection;
} ubo;

layout(location = 0) in vec2 v_position;
layout(location = 1) in vec2 v_origin;
layout(location = 2) in vec2 v_size;
layout(location = 3) in vec4 v_fillColor;

noperspective out vec2 f_uv;
noperspective out vec2 f_size;
noperspective out vec4 f_fillColor;
INTER(location = 0) noperspective out vec2 f_uv;
INTER(location = 1) noperspective out vec2 f_size;
INTER(location = 2) noperspective out vec4 f_fillColor;

void
main()


@@ 24,7 25,7 @@ main()
	              v_origin[0], v_origin[1], 0.0, 1.0);
	// clang-format on

	mat4 MVP = u_projection * m;
	mat4 MVP = ubo.projection * m;

	f_uv        = v_position * v_size;
	f_size      = v_size;

R pugl/pugl_cairo_backend.h => test/test_build.c +16 -5
@@ 1,5 1,5 @@
/*
  Copyright 2012-2019 David Robillard <http://drobilla.net>
  Copyright 2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 14,10 14,21 @@
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#ifndef PUGL_PUGL_CAIRO_BACKEND_H
#define PUGL_PUGL_CAIRO_BACKEND_H
/*
  Tests that C headers compile without any warnings.
*/

#define PUGL_DISABLE_DEPRECATED

#warning "This header is deprecated, use pugl/pugl_cairo.h instead."
#include "pugl/gl.h"
#include "pugl/glu.h"
#include "pugl/pugl.h"
#include "pugl/pugl_cairo.h"
#include "pugl/pugl_gl.h"
#include "pugl/pugl_stub.h"

#endif // PUGL_PUGL_CAIRO_BACKEND_H
int
main(void)
{
	return 0;
}

R pugl/pugl_gl_backend.h => test/test_build.cpp +19 -6
@@ 1,5 1,5 @@
/*
  Copyright 2012-2019 David Robillard <http://drobilla.net>
  Copyright 2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 14,10 14,23 @@
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#ifndef PUGL_PUGL_GL_BACKEND_H
#define PUGL_PUGL_GL_BACKEND_H
/*
  Tests that C++ headers compile without any warnings.
*/

#define PUGL_DISABLE_DEPRECATED

#warning "This header is deprecated, use pugl/pugl_gl.h instead."
#include "pugl/pugl_gl.h"
#include "pugl/gl.h"
#include "pugl/glu.h"
#include "pugl/pugl.h"
#include "pugl/pugl.hpp"
#include "pugl/pugl.ipp"
#include "pugl/pugl_cairo.hpp"
#include "pugl/pugl_gl.hpp"
#include "pugl/pugl_stub.hpp"

#endif // PUGL_PUGL_GL_BACKEND_H
int
main()
{
	return 0;
}

M test/test_redisplay.c => test/test_redisplay.c +18 -9
@@ 1,5 1,5 @@
/*
  Copyright 2020 David Robillard <http://drobilla.net>
  Copyright 2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 47,13 47,13 @@ typedef enum {

typedef struct
{
	PuglTestOptions opts;
	PuglWorld*      world;
	PuglView*       view;
	PuglTestOptions opts;
	State           state;
} PuglTest;

static const PuglRect  redisplayRect   = {1, 2, 3, 4};
static const PuglRect  redisplayRect   = {2, 4, 8, 16};
static const uintptr_t postRedisplayId = 42;

static PuglStatus


@@ 66,6 66,13 @@ onEvent(PuglView* view, const PuglEvent* event)
	}

	switch (event->type) {
	case PUGL_UPDATE:
		if (test->state == SHOULD_REDISPLAY) {
			puglPostRedisplayRect(view, redisplayRect);
			test->state = POSTED_REDISPLAY;
		}
		break;

	case PUGL_EXPOSE:
		if (test->state == START) {
			test->state = EXPOSED;


@@ 80,8 87,7 @@ onEvent(PuglView* view, const PuglEvent* event)

	case PUGL_CLIENT:
		if (event->client.data1 == postRedisplayId) {
			puglPostRedisplayRect(view, redisplayRect);
			test->state = POSTED_REDISPLAY;
			test->state = SHOULD_REDISPLAY;
		}
		break;



@@ 94,9 100,9 @@ onEvent(PuglView* view, const PuglEvent* event)
int
main(int argc, char** argv)
{
	PuglTest app = {puglParseTestOptions(&argc, &argv),
	                puglNewWorld(PUGL_PROGRAM, 0),
	PuglTest app = {puglNewWorld(PUGL_PROGRAM, 0),
	                NULL,
	                puglParseTestOptions(&argc, &argv),
	                START};

	// Set up view


@@ 105,6 111,7 @@ main(int argc, char** argv)
	puglSetBackend(app.view, puglStubBackend());
	puglSetHandle(app.view, &app);
	puglSetEventFunc(app.view, onEvent);
	puglSetDefaultSize(app.view, 512, 512);

	// Create and show window
	assert(!puglRealize(app.view));


@@ 114,8 121,10 @@ main(int argc, char** argv)
	}

	// Send a custom event to trigger a redisplay in the event loop
	const PuglEventClient client = { PUGL_CLIENT, 0, postRedisplayId, 0 };
	assert(!puglSendEvent(app.view, (const PuglEvent*)&client));
	PuglEvent client_event    = {{PUGL_CLIENT, 0}};
	client_event.client.data1 = postRedisplayId;
	client_event.client.data2 = 0;
	assert(!puglSendEvent(app.view, &client_event));

	// Loop until an expose happens in the same iteration as the redisplay
	app.state = SHOULD_REDISPLAY;

M test/test_show_hide.c => test/test_show_hide.c +6 -5
@@ 1,5 1,5 @@
/*
  Copyright 2020 David Robillard <http://drobilla.net>
  Copyright 2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 41,9 41,9 @@ typedef enum {
} State;

typedef struct {
	PuglTestOptions opts;
	PuglWorld*      world;
	PuglView*       view;
	PuglTestOptions opts;
	State           state;
} PuglTest;



@@ 71,7 71,7 @@ onEvent(PuglView* view, const PuglEvent* event)
		test->state = MAPPED;
		break;
	case PUGL_EXPOSE:
		assert(test->state == MAPPED);
		assert(test->state == MAPPED || test->state == EXPOSED);
		test->state = EXPOSED;
		break;
	case PUGL_UNMAP:


@@ 104,9 104,9 @@ tick(PuglWorld* world)
int
main(int argc, char** argv)
{
	PuglTest test = {puglParseTestOptions(&argc, &argv),
	                 puglNewWorld(PUGL_PROGRAM, 0),
	PuglTest test = {puglNewWorld(PUGL_PROGRAM, 0),
	                 NULL,
	                 puglParseTestOptions(&argc, &argv),
	                 START};

	// Set up view


@@ 115,6 115,7 @@ main(int argc, char** argv)
	puglSetBackend(test.view, puglStubBackend());
	puglSetHandle(test.view, &test);
	puglSetEventFunc(test.view, onEvent);
	puglSetDefaultSize(test.view, 512, 512);

	// Create initially invisible window
	assert(!puglRealize(test.view));

M test/test_timer.c => test/test_timer.c +5 -4
@@ 1,5 1,5 @@
/*
  Copyright 2020 David Robillard <http://drobilla.net>
  Copyright 2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 55,9 55,9 @@ typedef enum {
} State;

typedef struct {
	PuglTestOptions opts;
	PuglWorld*      world;
	PuglView*       view;
	PuglTestOptions opts;
	size_t          numAlarms;
	State           state;
} PuglTest;


@@ 97,9 97,9 @@ roundPeriod(const double period)
int
main(int argc, char** argv)
{
	PuglTest app = {puglParseTestOptions(&argc, &argv),
	                puglNewWorld(PUGL_PROGRAM, 0),
	PuglTest app = {puglNewWorld(PUGL_PROGRAM, 0),
	                NULL,
	                puglParseTestOptions(&argc, &argv),
	                0,
	                START};



@@ 109,6 109,7 @@ main(int argc, char** argv)
	puglSetBackend(app.view, puglStubBackend());
	puglSetHandle(app.view, &app);
	puglSetEventFunc(app.view, onEvent);
	puglSetDefaultSize(app.view, 512, 512);

	// Create and show window
	assert(!puglRealize(app.view));

M test/test_update.c => test/test_update.c +5 -4
@@ 1,5 1,5 @@
/*
  Copyright 2020 David Robillard <http://drobilla.net>
  Copyright 2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 44,9 44,9 @@ typedef enum {
} State;

typedef struct {
	PuglTestOptions opts;
	PuglWorld*      world;
	PuglView*       view;
	PuglTestOptions opts;
	State           state;
} PuglTest;



@@ 90,9 90,9 @@ onEvent(PuglView* view, const PuglEvent* event)
int
main(int argc, char** argv)
{
	PuglTest app = {puglParseTestOptions(&argc, &argv),
	                puglNewWorld(PUGL_PROGRAM, 0),
	PuglTest app = {puglNewWorld(PUGL_PROGRAM, 0),
	                NULL,
	                puglParseTestOptions(&argc, &argv),
	                START};

	// Set up view


@@ 101,6 101,7 @@ main(int argc, char** argv)
	puglSetBackend(app.view, puglStubBackend());
	puglSetHandle(app.view, &app);
	puglSetEventFunc(app.view, onEvent);
	puglSetDefaultSize(app.view, 512, 512);

	// Create and show window
	assert(!puglRealize(app.view));

M test/test_utils.h => test/test_utils.h +48 -12
@@ 1,5 1,5 @@
/*
  Copyright 2012-2020 David Robillard <http://drobilla.net>
  Copyright 2012-2020 David Robillard <d@drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above


@@ 67,6 67,40 @@ printModifiers(const uint32_t mods)
	               (mods & PUGL_MOD_SUPER) ? " Super" : "");
}

static inline const char*
crossingModeString(const PuglCrossingMode mode)
{
	switch (mode) {
	case PUGL_CROSSING_NORMAL:
		return "normal";
	case PUGL_CROSSING_GRAB:
		return "grab";
	case PUGL_CROSSING_UNGRAB:
		return "ungrab";
	}

	return "unknown";
}

static inline const char*
scrollDirectionString(const PuglScrollDirection direction)
{
	switch (direction) {
	case PUGL_SCROLL_UP:
		return "up";
	case PUGL_SCROLL_DOWN:
		return "down";
	case PUGL_SCROLL_LEFT:
		return "left";
	case PUGL_SCROLL_RIGHT:
		return "right";
	case PUGL_SCROLL_SMOOTH:
		return "smooth";
	}

	return "unknown";
}

static inline int
printEvent(const PuglEvent* event, const char* prefix, const bool verbose)
{


@@ 103,31 137,34 @@ printEvent(const PuglEvent* event, const char* prefix, const bool verbose)
		              event->button.y) +
		        printModifiers(event->scroll.state));
	case PUGL_SCROLL:
		return (PRINT("%sScroll %5.1f %5.1f at " PFMT " ",
		return (PRINT("%sScroll %5.1f %5.1f (%s) at " PFMT " ",
		              prefix,
		              event->scroll.dx,
		              event->scroll.dy,
		              scrollDirectionString(event->scroll.direction),
		              event->scroll.x,
		              event->scroll.y) +
		        printModifiers(event->scroll.state));
	case PUGL_POINTER_IN:
		return PRINT("%sMouse enter  at " PFMT "\n",
		return PRINT("%sMouse enter  at " PFMT " (%s)\n",
		             prefix,
		             event->crossing.x,
		             event->crossing.y);
		             event->crossing.y,
		             crossingModeString(event->crossing.mode));
	case PUGL_POINTER_OUT:
		return PRINT("%sMouse leave  at " PFMT "\n",
		return PRINT("%sMouse leave  at " PFMT " (%s)\n",
		             prefix,
		             event->crossing.x,
		             event->crossing.y);
		             event->crossing.y,
		             crossingModeString(event->crossing.mode));
	case PUGL_FOCUS_IN:
		return PRINT("%sFocus in%s\n",
		return PRINT("%sFocus in (%s)\n",
		             prefix,
		             event->focus.grab ? " (grab)" : "");
		             crossingModeString(event->crossing.mode));
	case PUGL_FOCUS_OUT:
		return PRINT("%sFocus out%s\n",
		return PRINT("%sFocus out (%s)\n",
		             prefix,
		             event->focus.grab ? " (ungrab)" : "");
		             crossingModeString(event->crossing.mode));
	case PUGL_CLIENT:
		return PRINT("%sClient %" PRIXPTR " %" PRIXPTR "\n",
		             prefix,


@@ 173,8 210,7 @@ printEvent(const PuglEvent* event, const char* prefix, const bool verbose)
			             event->motion.x,
			             event->motion.y);
		default:
			fprintf(stderr, "%sUnknown event type %u\n", prefix, event->type);
			break;
			return PRINT("%sUnknown event type %d\n", prefix, (int)event->type);
		}
	}


M waflib => waflib +1 -1
@@ 1,1 1,1 @@
Subproject commit 569d532e011bcad1ac308391e6d00b1b28789c10
Subproject commit f773f9a6a1ace4e3ddda5db05db4fa49bd0173ac

M wscript => wscript +230 -55
@@ 3,7 3,7 @@
import os
import sys

from waflib import Logs, Options, TaskGen
from waflib import Build, Logs, Options, TaskGen
from waflib.extras import autowaf

# Library and package version (UNIX style major, minor, micro)


@@ 22,6 22,7 @@ out     = 'build'       # Build directory

def options(ctx):
    ctx.load('compiler_c')
    ctx.load('compiler_cxx')

    opts = ctx.configuration_options()
    opts.add_option('--target', default=None, dest='target',


@@ 41,46 42,102 @@ def options(ctx):

def configure(conf):
    conf.load('compiler_c', cache=True)
    try:
        conf.load('compiler_cxx', cache=True)
    except Exception:
        pass

    conf.load('autowaf', cache=True)
    autowaf.set_c_lang(conf, 'c99')
    if 'COMPILER_CXX' in conf.env:
        autowaf.set_cxx_lang(conf, 'c++11')

    conf.env.ALL_HEADERS     = Options.options.all_headers
    conf.env.TARGET_PLATFORM = Options.options.target or sys.platform
    platform                 = conf.env.TARGET_PLATFORM

    if platform == 'darwin':
        conf.env.append_unique('CFLAGS', ['-Wno-deprecated-declarations'])
    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 conf.env.MSVC_COMPILER:
        conf.env.append_unique('CFLAGS', ['/wd4191'])
    else:
        conf.env.append_value('LINKFLAGS', ['-fvisibility=hidden'])
        conf.env.append_value('CFLAGS', ['-fvisibility=hidden'])
        if Options.options.strict:
            conf.env.append_value('CFLAGS', ['-Wunused-parameter',
                                             '-Wno-pedantic'])

    if conf.env.TARGET_PLATFORM == 'darwin':
        conf.env.append_unique('CFLAGS', ['-DGL_SILENCE_DEPRECATION'])
        conf.env.append_unique('CXXFLAGS', ['-DGL_SILENCE_DEPRECATION'])

    if Options.options.ultra_strict and 'clang' in conf.env.CC:
        for var in ['CFLAGS', 'CXXFLAGS']:
            flags = conf.env[var]
            conf.env[var] = [f for f in flags if not f.startswith('-W')]
            conf.env.append_value(var, [
                '-Weverything',
                '-Wno-bad-function-cast',
                '-Wno-float-equal',
                '-Wno-format-nonliteral',
    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',
            ])

        conf.env.append_value('CXXFLAGS', ['-Wno-c++98-compat',
                                           '-Wno-c++98-compat-pedantic'])

            ],
            'gcc': [
                '-Wno-padded',
                '-Wno-switch-enum',
            ],
            'msvc': [
                '/wd4061',  # enumerator in switch is not explicitly handled
                '/wd4514',  # unreferenced inline function has been removed
                '/wd4820',  # padding added after construct
                '/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',
            ],
            '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-documentation-unknown-command',
                '-Wno-old-style-cast',
            ],
            'gcc': [
                '-Wno-old-style-cast',
            ],
            'msvc': [
                '/wd4355',  # 'this' used in base member initializer list
                '/wd4571',  # structured exceptions (SEH) are no longer caught
                '/wd4625',  # copy constructor implicitly deleted
                '/wd4626',  # assignment operator implicitly deleted
                '/wd5026',  # move constructor implicitly deleted
                '/wd5027',  # move assignment operator implicitly deleted
            ],
        })

        # 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'],
            })
        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'],
            })

    # Check for base system libraries needed on some systems
    conf.check_cc(lib='m', uselib_store='M', mandatory=False)
    conf.check_cc(lib='dl', uselib_store='DL', mandatory=False)



@@ 114,6 171,11 @@ def configure(conf):
                         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 not Options.options.no_gl:
            glx_fragment = """#include <GL/glx.h>
                int main(void) { glXSwapBuffers(0, 0); return 0; }"""


@@ 138,7 200,18 @@ def configure(conf):
        'BUILD_SHARED': not Options.options.no_shared,
        'BUILD_STATIC': conf.env.BUILD_TESTS or not Options.options.no_static})

    autowaf.set_lib_env(conf, 'pugl', PUGL_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'

    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,


@@ 189,6 262,7 @@ def build(bld):
    includedir = '${INCLUDEDIR}/pugl-%s/pugl' % PUGL_MAJOR_VERSION
    bld.install_files(includedir, bld.path.ant_glob('pugl/*.h'))
    bld.install_files(includedir, bld.path.ant_glob('pugl/*.hpp'))
    bld.install_files(includedir, bld.path.ant_glob('pugl/*.ipp'))
    if bld.env.ALL_HEADERS:
        detaildir = os.path.join(includedir, 'detail')
        bld.install_files(detaildir, bld.path.ant_glob('pugl/detail/*.h'))


@@ 208,18 282,26 @@ def build(bld):
                     'install_path':    '${LIBDIR}',
                     'vnum':            PUGL_VERSION})

        flags = []
        if not bld.env.MSVC_COMPILER:
            flags = ['-fPIC', '-fvisibility=hidden']

        if bld.env.BUILD_SHARED:
            bld(features = 'c cshlib',
                name     = name,
                target   = 'pugl_' + name,
                defines  = ['PUGL_INTERNAL', 'PUGL_SHARED'],
            bld(features  = 'c cshlib',
                name      = name,
                target    = 'pugl_%s-%s' % (name, PUGL_MAJOR_VERSION),
                defines   = ['PUGL_INTERNAL', 'PUGL_SHARED'],
                cflags    = flags,
                linkflags = flags,
                **args)

        if bld.env.BUILD_STATIC:
            bld(features = 'c cstlib',
                name     = 'pugl_%s_static' % name,
                target   = 'pugl_' + name,
                defines  = ['PUGL_INTERNAL', 'PUGL_DISABLE_DEPRECATED'],
            bld(features  = 'c cstlib',
                name      = 'pugl_%s_static' % name,
                target    = 'pugl_%s-%s' % (name, PUGL_MAJOR_VERSION),
                defines   = ['PUGL_INTERNAL', 'PUGL_DISABLE_DEPRECATED'],
                cflags    = flags,
                linkflags = flags,
                **args)

    def build_platform(platform, **kwargs):


@@ 278,7 360,7 @@ def build(bld):
    else:
        platform = 'x11'
        build_platform('x11',
                       uselib=['M', 'X11', 'XSYNC'],
                       uselib=['M', 'X11', 'XSYNC', 'XCURSOR'],
                       source=lib_source + ['pugl/detail/x11.c'])

        if bld.env.HAVE_GL:


@@ 293,6 375,8 @@ def build(bld):
                          source=['pugl/detail/x11_cairo.c'])

    def build_example(prog, source, platform, backend, **kwargs):
        lang = 'cxx' if source[0].endswith('.cpp') else 'c'

        use = ['pugl_%s_static' % platform,
               'pugl_%s_%s_static' % (platform, backend)]



@@ 312,7 396,7 @@ def build(bld):
                               deps.get(platform, {}).get(k, []) +
                               deps.get(backend_lib, {}).get(k, []))})

        bld(features     = 'c cprogram',
        bld(features     = '%s %sprogram' % (lang, lang),
            source       = source,
            target       = target,
            use          = use,


@@ 328,16 412,22 @@ def build(bld):
                target   = 'shaders/%s' % s)

        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_gl3_demo',
                          ['examples/pugl_gl3_demo.c', 'examples/glad/glad.c'],
                          platform, 'gl', uselib=['DL', 'GL', 'M'])
            build_example('pugl_shader_demo',
                          ['examples/pugl_shader_demo.c',
                           'examples/glad/glad.c'],
                          platform, 'gl',
                          cflags=glad_cflags,
                          uselib=['DL', 'GL', 'M'])

        if bld.env.HAVE_CAIRO:
            build_example('pugl_cairo_demo', ['examples/pugl_cairo_demo.c'],


@@ 353,6 443,43 @@ def build(bld):
                                'pugl_%s_stub_static' % platform],
                uselib       = deps[platform]['uselib'] + ['CAIRO'])

        # 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'],
        })
        autowaf.add_compiler_flags(strict_env, 'cxx', {
            'clang': ['-Wno-documentation-unknown-command'],
        })

        # 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,
            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:
        autowaf.build_dox(bld, 'PUGL', PUGL_VERSION, top, out)



@@ 364,25 491,73 @@ def test(tst):
                check(['test/test_%s' % test])


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


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

    subprocess.call("flake8 wscript --ignore E221,W504,E251,E241",
                    shell=True)
    st = 0

    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']
    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"]
        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')]

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

        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=".*\\.hpp"',
                   "--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")

    subprocess.call(['clang-tidy'] + files, cwd='build')
    if st != 0:
        sys.exit(st)

    try:
        subprocess.call(['iwyu_tool.py', '-o', 'clang', '-p', 'build'])
    except Exception:
        Logs.warn('Failed to call iwyu_tool.py')

# Alias .m files to be compiled like .c files, gcc will do the right thing.
@TaskGen.extension('.m')