diff options
author | Hanspeter Portner <dev@open-music-kontrollers.ch> | 2020-10-02 13:39:13 +0200 |
---|---|---|
committer | Hanspeter Portner <dev@open-music-kontrollers.ch> | 2020-10-02 13:39:13 +0200 |
commit | 0a053a5ac28cdb6ce6b73e7f7d6523d34071d832 (patch) | |
tree | 389e82c24a1c16f16bf619ad98507e17f0819c03 | |
parent | c35d2b411a7062745af07748eaaf4d42f178ac68 (diff) | |
parent | fc6c5a6882d9b0c76097d4720f06d21a226f1a77 (diff) | |
download | vm.lv2-0a053a5ac28cdb6ce6b73e7f7d6523d34071d832.tar.xz |
Merge commit 'fc6c5a6882d9b0c76097d4720f06d21a226f1a77' into master
64 files changed, 2883 insertions, 688 deletions
diff --git a/subprojects/nk_pugl/VERSION b/subprojects/nk_pugl/VERSION index a0e8ec1..010fbfb 100644 --- a/subprojects/nk_pugl/VERSION +++ b/subprojects/nk_pugl/VERSION @@ -1 +1 @@ -0.1.145 +0.1.149 diff --git a/subprojects/nk_pugl/meson.build b/subprojects/nk_pugl/meson.build index 65a0f74..afe0713 100644 --- a/subprojects/nk_pugl/meson.build +++ b/subprojects/nk_pugl/meson.build @@ -18,6 +18,10 @@ glew_dep = dependency('glew', version : '>=2.1.0', static : static_link, required : false) +glu_dep = dependency('glu', + version : '>=9.0.0', + static : static_link, + required : false) if not glew_dep.found() glew_dep = declare_dependency( compile_args : '-DGLEW_STATIC', @@ -25,7 +29,7 @@ if not glew_dep.found() sources : join_paths('glew-2.1.0', 'glew.c')) endif -deps = [m_dep, lv2_dep, glew_dep] +deps = [m_dep, lv2_dep, glu_dep, glew_dep] links = [] pugl_inc = include_directories('pugl') diff --git a/subprojects/nk_pugl/pugl/.clang-format b/subprojects/nk_pugl/pugl/.clang-format index b788676..043fd1f 100644 --- a/subprojects/nk_pugl/pugl/.clang-format +++ b/subprojects/nk_pugl/pugl/.clang-format @@ -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 diff --git a/subprojects/nk_pugl/pugl/.clang-tidy b/subprojects/nk_pugl/pugl/.clang-tidy index 055d63b..c8039e1 100644 --- a/subprojects/nk_pugl/pugl/.clang-tidy +++ b/subprojects/nk_pugl/pugl/.clang-tidy @@ -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 diff --git a/subprojects/nk_pugl/pugl/.editorconfig b/subprojects/nk_pugl/pugl/.editorconfig new file mode 100644 index 0000000..5213b6b --- /dev/null +++ b/subprojects/nk_pugl/pugl/.editorconfig @@ -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 diff --git a/subprojects/nk_pugl/pugl/.gitlab-ci.yml b/subprojects/nk_pugl/pugl/.gitlab-ci.yml index 29dc8be..42d14cd 100644 --- a/subprojects/nk_pugl/pugl/.gitlab-ci.yml +++ b/subprojects/nk_pugl/pugl/.gitlab-ci.yml @@ -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: diff --git a/subprojects/nk_pugl/pugl/AUTHORS b/subprojects/nk_pugl/pugl/AUTHORS index 1470491..18aadaf 100644 --- a/subprojects/nk_pugl/pugl/AUTHORS +++ b/subprojects/nk_pugl/pugl/AUTHORS @@ -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> diff --git a/subprojects/nk_pugl/pugl/COPYING b/subprojects/nk_pugl/pugl/COPYING index 4a287b9..63e6829 100644 --- a/subprojects/nk_pugl/pugl/COPYING +++ b/subprojects/nk_pugl/pugl/COPYING @@ -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 diff --git a/subprojects/nk_pugl/pugl/README.md b/subprojects/nk_pugl/pugl/README.md index ae9c420..e8794bc 100644 --- a/subprojects/nk_pugl/pugl/README.md +++ b/subprojects/nk_pugl/pugl/README.md @@ -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. diff --git a/subprojects/nk_pugl/pugl/doc/layout.xml b/subprojects/nk_pugl/pugl/doc/layout.xml index 1889302..2995c0e 100644 --- a/subprojects/nk_pugl/pugl/doc/layout.xml +++ b/subprojects/nk_pugl/pugl/doc/layout.xml @@ -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=""/> diff --git a/subprojects/nk_pugl/pugl/doc/mainpage.md b/subprojects/nk_pugl/pugl/doc/mainpage.md index aa6f925..c04bf9e 100644 --- a/subprojects/nk_pugl/pugl/doc/mainpage.md +++ b/subprojects/nk_pugl/pugl/doc/mainpage.md @@ -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). diff --git a/subprojects/nk_pugl/pugl/doc/reference.doxygen.in b/subprojects/nk_pugl/pugl/doc/reference.doxygen.in index 1357fe4..4e91ca2 100644 --- a/subprojects/nk_pugl/pugl/doc/reference.doxygen.in +++ b/subprojects/nk_pugl/pugl/doc/reference.doxygen.in @@ -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 diff --git a/subprojects/nk_pugl/pugl/doc/style.css b/subprojects/nk_pugl/pugl/doc/style.css index 28f0519..680fe77 100644 --- a/subprojects/nk_pugl/pugl/doc/style.css +++ b/subprojects/nk_pugl/pugl/doc/style.css @@ -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 { diff --git a/subprojects/nk_pugl/pugl/examples/cube_view.h b/subprojects/nk_pugl/pugl/examples/cube_view.h index 9fd2349..8a81f48 100644 --- a/subprojects/nk_pugl/pugl/examples/cube_view.h +++ b/subprojects/nk_pugl/pugl/examples/cube_view.h @@ -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 diff --git a/subprojects/nk_pugl/pugl/examples/demo_utils.h b/subprojects/nk_pugl/pugl/examples/demo_utils.h index 9a1cb7a..6d3bb66 100644 --- a/subprojects/nk_pugl/pugl/examples/demo_utils.h +++ b/subprojects/nk_pugl/pugl/examples/demo_utils.h @@ -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) { diff --git a/subprojects/nk_pugl/pugl/examples/pugl_cairo_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_cairo_demo.c index 483446f..5fe0661 100644 --- a/subprojects/nk_pugl/pugl/examples/pugl_cairo_demo.c +++ b/subprojects/nk_pugl/pugl/examples/pugl_cairo_demo.c @@ -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); diff --git a/subprojects/nk_pugl/pugl/examples/pugl_cursor_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_cursor_demo.c new file mode 100644 index 0000000..03ab5da --- /dev/null +++ b/subprojects/nk_pugl/pugl/examples/pugl_cursor_demo.c @@ -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; +} diff --git a/subprojects/nk_pugl/pugl/examples/pugl_cxx_demo.cpp b/subprojects/nk_pugl/pugl/examples/pugl_cxx_demo.cpp new file mode 100644 index 0000000..4addee2 --- /dev/null +++ b/subprojects/nk_pugl/pugl/examples/pugl_cxx_demo.cpp @@ -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; +} diff --git a/subprojects/nk_pugl/pugl/examples/pugl_embed_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_embed_demo.c index 3a7b051..774ac77 100644 --- a/subprojects/nk_pugl/pugl/examples/pugl_embed_demo.c +++ b/subprojects/nk_pugl/pugl/examples/pugl_embed_demo.c @@ -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()); diff --git a/subprojects/nk_pugl/pugl/examples/pugl_print_events.c b/subprojects/nk_pugl/pugl/examples/pugl_print_events.c index 52b58c4..08a4a86 100644 --- a/subprojects/nk_pugl/pugl/examples/pugl_print_events.c +++ b/subprojects/nk_pugl/pugl/examples/pugl_print_events.c @@ -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" diff --git a/subprojects/nk_pugl/pugl/examples/pugl_gl3_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_shader_demo.c index c49ed3d..50afb37 100644 --- a/subprojects/nk_pugl/pugl/examples/pugl_gl3_demo.c +++ b/subprojects/nk_pugl/pugl/examples/pugl_shader_demo.c @@ -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); diff --git a/subprojects/nk_pugl/pugl/examples/pugl_window_demo.c b/subprojects/nk_pugl/pugl/examples/pugl_window_demo.c index 183119c..f326f21 100644 --- a/subprojects/nk_pugl/pugl/examples/pugl_window_demo.c +++ b/subprojects/nk_pugl/pugl/examples/pugl_window_demo.c @@ -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)); } diff --git a/subprojects/nk_pugl/pugl/examples/rects.h b/subprojects/nk_pugl/pugl/examples/rects.h new file mode 100644 index 0000000..f760226 --- /dev/null +++ b/subprojects/nk_pugl/pugl/examples/rects.h @@ -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; +} diff --git a/subprojects/nk_pugl/pugl/examples/shader_utils.h b/subprojects/nk_pugl/pugl/examples/shader_utils.h index 834d8fc..10a7ace 100644 --- a/subprojects/nk_pugl/pugl/examples/shader_utils.h +++ b/subprojects/nk_pugl/pugl/examples/shader_utils.h @@ -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); diff --git a/subprojects/nk_pugl/pugl/pugl/detail/implementation.c b/subprojects/nk_pugl/pugl/pugl/detail/implementation.c index ee9b242..055e917 100644 --- a/subprojects/nk_pugl/pugl/pugl/detail/implementation.c +++ b/subprojects/nk_pugl/pugl/pugl/detail/implementation.c @@ -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) { diff --git a/subprojects/nk_pugl/pugl/pugl/detail/implementation.h b/subprojects/nk_pugl/pugl/pugl/detail/implementation.h index bcecd85..ff97fef 100644 --- a/subprojects/nk_pugl/pugl/pugl/detail/implementation.h +++ b/subprojects/nk_pugl/pugl/pugl/detail/implementation.h @@ -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, diff --git a/subprojects/nk_pugl/pugl/pugl/detail/mac.h b/subprojects/nk_pugl/pugl/pugl/detail/mac.h index 2243337..7b64cfe 100644 --- a/subprojects/nk_pugl/pugl/pugl/detail/mac.h +++ b/subprojects/nk_pugl/pugl/pugl/detail/mac.h @@ -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; }; diff --git a/subprojects/nk_pugl/pugl/pugl/detail/mac.m b/subprojects/nk_pugl/pugl/pugl/detail/mac.m index 501be02..23671ae 100644 --- a/subprojects/nk_pugl/pugl/pugl/detail/mac.m +++ b/subprojects/nk_pugl/pugl/pugl/detail/mac.m @@ -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, diff --git a/subprojects/nk_pugl/pugl/pugl/detail/mac_cairo.m b/subprojects/nk_pugl/pugl/pugl/detail/mac_cairo.m index 51c1c13..18209d9 100644 --- a/subprojects/nk_pugl/pugl/pugl/detail/mac_cairo.m +++ b/subprojects/nk_pugl/pugl/pugl/detail/mac_cairo.m @@ -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); diff --git a/subprojects/nk_pugl/pugl/pugl/detail/mac_gl.m b/subprojects/nk_pugl/pugl/pugl/detail/mac_gl.m index eda4371..4bf6fc1 100644 --- a/subprojects/nk_pugl/pugl/pugl/detail/mac_gl.m +++ b/subprojects/nk_pugl/pugl/pugl/detail/mac_gl.m @@ -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 { diff --git a/subprojects/nk_pugl/pugl/pugl/detail/mac_stub.m b/subprojects/nk_pugl/pugl/pugl/detail/mac_stub.m index 71a54b8..8271735 100644 --- a/subprojects/nk_pugl/pugl/pugl/detail/mac_stub.m +++ b/subprojects/nk_pugl/pugl/pugl/detail/mac_stub.m @@ -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]; diff --git a/subprojects/nk_pugl/pugl/pugl/detail/stub.h b/subprojects/nk_pugl/pugl/pugl/detail/stub.h new file mode 100644 index 0000000..acd3181 --- /dev/null +++ b/subprojects/nk_pugl/pugl/pugl/detail/stub.h @@ -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 diff --git a/subprojects/nk_pugl/pugl/pugl/detail/types.h b/subprojects/nk_pugl/pugl/pugl/detail/types.h index eb450e1..edd2bd0 100644 --- a/subprojects/nk_pugl/pugl/pugl/detail/types.h +++ b/subprojects/nk_pugl/pugl/pugl/detail/types.h @@ -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*); }; diff --git a/subprojects/nk_pugl/pugl/pugl/detail/win.c b/subprojects/nk_pugl/pugl/pugl/detail/win.c index 44ba6cd..4f7afee 100644 --- a/subprojects/nk_pugl/pugl/pugl/detail/win.c +++ b/subprojects/nk_pugl/pugl/pugl/detail/win.c @@ -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; diff --git a/subprojects/nk_pugl/pugl/pugl/detail/win.h b/subprojects/nk_pugl/pugl/pugl/detail/win.h index 949fa90..1b9b0c4 100644 --- a/subprojects/nk_pugl/pugl/pugl/detail/win.h +++ b/subprojects/nk_pugl/pugl/pugl/detail/win.h @@ -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); diff --git a/subprojects/nk_pugl/pugl/pugl/detail/win_cairo.c b/subprojects/nk_pugl/pugl/pugl/detail/win_cairo.c index a8b371f..1b9afb9 100644 --- a/subprojects/nk_pugl/pugl/pugl/detail/win_cairo.c +++ b/subprojects/nk_pugl/pugl/pugl/detail/win_cairo.c @@ -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; diff --git a/subprojects/nk_pugl/pugl/pugl/detail/win_gl.c b/subprojects/nk_pugl/pugl/pugl/detail/win_gl.c index f5acfd6..8cdad76 100644 --- a/subprojects/nk_pugl/pugl/pugl/detail/win_gl.c +++ b/subprojects/nk_pugl/pugl/pugl/detail/win_gl.c @@ -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, diff --git a/subprojects/nk_pugl/pugl/pugl/detail/x11.c b/subprojects/nk_pugl/pugl/pugl/detail/x11.c index e3fb264..7b8daf2 100644 --- a/subprojects/nk_pugl/pugl/pugl/detail/x11.c +++ b/subprojects/nk_pugl/pugl/pugl/detail/x11.c @@ -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) { diff --git a/subprojects/nk_pugl/pugl/pugl/detail/x11.h b/subprojects/nk_pugl/pugl/pugl/detail/x11.h index 6b7a150..cf647ed 100644 --- a/subprojects/nk_pugl/pugl/pugl/detail/x11.h +++ b/subprojects/nk_pugl/pugl/pugl/detail/x11.h @@ -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 diff --git a/subprojects/nk_pugl/pugl/pugl/detail/x11_cairo.c b/subprojects/nk_pugl/pugl/pugl/detail/x11_cairo.c index 0229d97..0112c4e 100644 --- a/subprojects/nk_pugl/pugl/pugl/detail/x11_cairo.c +++ b/subprojects/nk_pugl/pugl/pugl/detail/x11_cairo.c @@ -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" diff --git a/subprojects/nk_pugl/pugl/pugl/detail/x11_gl.c b/subprojects/nk_pugl/pugl/pugl/detail/x11_gl.c index 33a05df..f5e6b8d 100644 --- a/subprojects/nk_pugl/pugl/pugl/detail/x11_gl.c +++ b/subprojects/nk_pugl/pugl/pugl/detail/x11_gl.c @@ -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) { diff --git a/subprojects/nk_pugl/pugl/pugl/gl.h b/subprojects/nk_pugl/pugl/pugl/gl.h index 55a55c4..dbb2e60 100644 --- a/subprojects/nk_pugl/pugl/pugl/gl.h +++ b/subprojects/nk_pugl/pugl/pugl/gl.h @@ -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. diff --git a/subprojects/nk_pugl/pugl/pugl/glu.h b/subprojects/nk_pugl/pugl/pugl/glu.h index 0ade70c..94da8fc 100644 --- a/subprojects/nk_pugl/pugl/pugl/glu.h +++ b/subprojects/nk_pugl/pugl/pugl/glu.h @@ -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. diff --git a/subprojects/nk_pugl/pugl/pugl/pugl.h b/subprojects/nk_pugl/pugl/pugl/pugl.h index 57e23fa..c32a17d 100644 --- a/subprojects/nk_pugl/pugl/pugl/pugl.h +++ b/subprojects/nk_pugl/pugl/pugl/pugl.h @@ -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 diff --git a/subprojects/nk_pugl/pugl/pugl/pugl.hpp b/subprojects/nk_pugl/pugl/pugl/pugl.hpp index 73cfe2a..3072560 100644 --- a/subprojects/nk_pugl/pugl/pugl/pugl.hpp +++ b/subprojects/nk_pugl/pugl/pugl/pugl.hpp @@ -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 */ diff --git a/subprojects/nk_pugl/pugl/pugl/pugl.ipp b/subprojects/nk_pugl/pugl/pugl/pugl.ipp new file mode 100644 index 0000000..7c39a63 --- /dev/null +++ b/subprojects/nk_pugl/pugl/pugl/pugl.ipp @@ -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; +} + +} diff --git a/subprojects/nk_pugl/pugl/pugl/pugl_cairo.h b/subprojects/nk_pugl/pugl/pugl/pugl_cairo.h index e71072e..c68f6bb 100644 --- a/subprojects/nk_pugl/pugl/pugl/pugl_cairo.h +++ b/subprojects/nk_pugl/pugl/pugl/pugl_cairo.h @@ -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 @{ */ diff --git a/subprojects/nk_pugl/pugl/pugl/pugl_cairo.hpp b/subprojects/nk_pugl/pugl/pugl/pugl_cairo.hpp new file mode 100644 index 0000000..5b17ab7 --- /dev/null +++ b/subprojects/nk_pugl/pugl/pugl/pugl_cairo.hpp @@ -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 diff --git a/subprojects/nk_pugl/pugl/pugl/pugl_gl.h b/subprojects/nk_pugl/pugl/pugl/pugl_gl.h index 9c5fa94..d501b3c 100644 --- a/subprojects/nk_pugl/pugl/pugl/pugl_gl.h +++ b/subprojects/nk_pugl/pugl/pugl/pugl_gl.h @@ -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 @{ */ diff --git a/subprojects/nk_pugl/pugl/pugl/pugl_gl.hpp b/subprojects/nk_pugl/pugl/pugl/pugl_gl.hpp new file mode 100644 index 0000000..4bc5bbd --- /dev/null +++ b/subprojects/nk_pugl/pugl/pugl/pugl_gl.hpp @@ -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 diff --git a/subprojects/nk_pugl/pugl/pugl/pugl_stub.h b/subprojects/nk_pugl/pugl/pugl/pugl_stub.h index da918aa..ef48000 100644 --- a/subprojects/nk_pugl/pugl/pugl/pugl_stub.h +++ b/subprojects/nk_pugl/pugl/pugl/pugl_stub.h @@ -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; -} - /** @} */ diff --git a/subprojects/nk_pugl/pugl/pugl/pugl_stub_backend.h b/subprojects/nk_pugl/pugl/pugl/pugl_stub.hpp index e5aa513..c5f3901 100644 --- a/subprojects/nk_pugl/pugl/pugl/pugl_stub_backend.h +++ b/subprojects/nk_pugl/pugl/pugl/pugl_stub.hpp @@ -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 @@ -14,10 +14,37 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef PUGL_PUGL_STUB_BACKEND_H -#define PUGL_PUGL_STUB_BACKEND_H +/** + @file pugl_stub.hpp + @brief Declaration of Stub backend accessor for C++. +*/ + +#ifndef PUGL_PUGL_STUB_HPP +#define PUGL_PUGL_STUB_HPP -#warning "This header is deprecated, use pugl/pugl_stub.h instead." +#include "pugl/pugl.h" #include "pugl/pugl_stub.h" -#endif // PUGL_PUGL_STUB_BACKEND_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 diff --git a/subprojects/nk_pugl/pugl/shaders/header_330.glsl b/subprojects/nk_pugl/pugl/shaders/header_330.glsl new file mode 100644 index 0000000..bfe7a00 --- /dev/null +++ b/subprojects/nk_pugl/pugl/shaders/header_330.glsl @@ -0,0 +1,5 @@ +#version 330 core + +#define INTER(qualifiers) +#define UBO(qualifiers) layout(std140) + diff --git a/subprojects/nk_pugl/pugl/shaders/header_420.glsl b/subprojects/nk_pugl/pugl/shaders/header_420.glsl new file mode 100644 index 0000000..55fbe8a --- /dev/null +++ b/subprojects/nk_pugl/pugl/shaders/header_420.glsl @@ -0,0 +1,5 @@ +#version 420 core + +#define INTER(qualifiers) layout(qualifiers) +#define UBO(qualifiers) layout(std140, qualifiers) + diff --git a/subprojects/nk_pugl/pugl/shaders/rect.frag b/subprojects/nk_pugl/pugl/shaders/rect.frag index 5e3af9d..ecec50d 100644 --- a/subprojects/nk_pugl/pugl/shaders/rect.frag +++ b/subprojects/nk_pugl/pugl/shaders/rect.frag @@ -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; diff --git a/subprojects/nk_pugl/pugl/shaders/rect.vert b/subprojects/nk_pugl/pugl/shaders/rect.vert index bf2e951..09f1917 100644 --- a/subprojects/nk_pugl/pugl/shaders/rect.vert +++ b/subprojects/nk_pugl/pugl/shaders/rect.vert @@ -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; diff --git a/subprojects/nk_pugl/pugl/pugl/pugl_cairo_backend.h b/subprojects/nk_pugl/pugl/test/test_build.c index 3f8cec3..de2ed28 100644 --- a/subprojects/nk_pugl/pugl/pugl/pugl_cairo_backend.h +++ b/subprojects/nk_pugl/pugl/test/test_build.c @@ -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; +} diff --git a/subprojects/nk_pugl/pugl/pugl/pugl_gl_backend.h b/subprojects/nk_pugl/pugl/test/test_build.cpp index e1b9a15..79f1dfc 100644 --- a/subprojects/nk_pugl/pugl/pugl/pugl_gl_backend.h +++ b/subprojects/nk_pugl/pugl/test/test_build.cpp @@ -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; +} diff --git a/subprojects/nk_pugl/pugl/test/test_redisplay.c b/subprojects/nk_pugl/pugl/test/test_redisplay.c index 75006cb..91b606f 100644 --- a/subprojects/nk_pugl/pugl/test/test_redisplay.c +++ b/subprojects/nk_pugl/pugl/test/test_redisplay.c @@ -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; diff --git a/subprojects/nk_pugl/pugl/test/test_show_hide.c b/subprojects/nk_pugl/pugl/test/test_show_hide.c index cc2c972..ebbbee9 100644 --- a/subprojects/nk_pugl/pugl/test/test_show_hide.c +++ b/subprojects/nk_pugl/pugl/test/test_show_hide.c @@ -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)); diff --git a/subprojects/nk_pugl/pugl/test/test_timer.c b/subprojects/nk_pugl/pugl/test/test_timer.c index 58d0652..d567da0 100644 --- a/subprojects/nk_pugl/pugl/test/test_timer.c +++ b/subprojects/nk_pugl/pugl/test/test_timer.c @@ -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)); diff --git a/subprojects/nk_pugl/pugl/test/test_update.c b/subprojects/nk_pugl/pugl/test/test_update.c index 081fb9b..bdcb28b 100644 --- a/subprojects/nk_pugl/pugl/test/test_update.c +++ b/subprojects/nk_pugl/pugl/test/test_update.c @@ -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)); diff --git a/subprojects/nk_pugl/pugl/test/test_utils.h b/subprojects/nk_pugl/pugl/test/test_utils.h index 7d33601..977fba5 100644 --- a/subprojects/nk_pugl/pugl/test/test_utils.h +++ b/subprojects/nk_pugl/pugl/test/test_utils.h @@ -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); } } diff --git a/subprojects/nk_pugl/pugl/wscript b/subprojects/nk_pugl/pugl/wscript index 2fc48de..f799ddd 100644 --- a/subprojects/nk_pugl/pugl/wscript +++ b/subprojects/nk_pugl/pugl/wscript @@ -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') |