diff options
Diffstat (limited to 'subprojects/d2tk/pugl/doc')
34 files changed, 1255 insertions, 1412 deletions
diff --git a/subprojects/d2tk/pugl/doc/_static/custom.css b/subprojects/d2tk/pugl/doc/_static/custom.css deleted file mode 100644 index 60aa759e..00000000 --- a/subprojects/d2tk/pugl/doc/_static/custom.css +++ /dev/null @@ -1,95 +0,0 @@ -div.document { - margin: 0; -} - -div.body { - margin-top: 2em; -} - -div.sphinxsidebarwrapper { - background: #EEE; -} - -div.sphinxsidebarwrapper p.blurb { - text-align: center; -} - -div.sphinxsidebarwrapper span.logo { - display: block; - text-align: center; - font-family: Georgia, serif; - padding: 0; - font-size: 180%; -} - -div.sphinxsidebar a { - border-width: 0; -} - -div.sphinxsidebar li { - color: #444; -} - -div.section { - margin-top: 2.5em; -} - -a.reference { - border-bottom: none; -} - -code.xref { - font-weight: normal; - background-color: #F8F8F8; - padding: 0.1em 0 0.1em 0; -} - -div.section > dl.c > dt:first-child, -div.section > dl.cpp > dt:first-child { - background-color: #F8F8F8; - font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.9em; - font-weight: normal; - margin-bottom: 0.5em; - padding: 0.1em 0 0.1em 0; -} - -tt.descname, tt.descclassname, code.descname, code.descclassname { - font-size: 0.9em; -} - -dl.member { - margin-top: 0.5em; -} - -dl.enumerator { - margin-top: 0.5em; -} - -dl.field-list > dt { - padding-left: 0; -} - -pre, tt, code { - background-color: #F8F8F8; -} - -.toctree-l1 { - margin-top: 1.0em; -} - -img.logo { - width: 6em; -} - -.class { - padding-top: 1.5em; -} - -.exception { - padding-top: 1.5em; -} - -.class > dd > dl.function { - padding-top: 1.0em; -} diff --git a/subprojects/d2tk/pugl/doc/_static/meson.build b/subprojects/d2tk/pugl/doc/_static/meson.build new file mode 100644 index 00000000..fc7792cc --- /dev/null +++ b/subprojects/d2tk/pugl/doc/_static/meson.build @@ -0,0 +1,2 @@ +configure_file(copy: true, input: '../../resources/pugl.svg', output: 'pugl.svg') + diff --git a/subprojects/d2tk/pugl/doc/_templates/about.html b/subprojects/d2tk/pugl/doc/_templates/about.html deleted file mode 100644 index 5bbadbe9..00000000 --- a/subprojects/d2tk/pugl/doc/_templates/about.html +++ /dev/null @@ -1,57 +0,0 @@ -{% if theme_logo %} -<p class="logo"> - <a href="{{ pathto(master_doc) }}"> - <img class="logo" src="{{ pathto('_static/' ~ theme_logo, 1) }}" alt="Logo"/> - {% if theme_logo_name|lower == 'true' %} - <span class="logo logo-name">{{ project }}</span> - {% endif %} - </a> -</p> -{% else %} -<h1 class="logo"><a href="{{ pathto(master_doc) }}">{{ project }}</a></h1> -{% endif %} - -{% if theme_description %} -<p class="blurb">{{ theme_description }}</p> -{% endif %} - -{% if theme_github_user and theme_github_repo %} -{% if theme_github_button|lower == 'true' %} -<p> -<iframe src="https://ghbtns.com/github-btn.html?user={{ theme_github_user }}&repo={{ theme_github_repo }}&type={{ theme_github_type }}&count={{ theme_github_count }}&size=large&v=2" - allowtransparency="true" frameborder="0" scrolling="0" width="200px" height="35px"></iframe> -</p> -{% endif %} -{% endif %} - -{% if theme_travis_button|lower != 'false' %} -{% if theme_travis_button|lower == 'true' %} - {% set path = theme_github_user + '/' + theme_github_repo %} -{% else %} - {% set path = theme_travis_button %} -{% endif %} -<p> -<a class="badge" href="https://travis-ci.org/{{ path }}"> - <img - alt="https://secure.travis-ci.org/{{ path }}.svg?branch={{ theme_badge_branch }}" - src="https://secure.travis-ci.org/{{ path }}.svg?branch={{ theme_badge_branch }}" - /> -</a> -</p> -{% endif %} - -{% if theme_codecov_button|lower != 'false' %} -{% if theme_codecov_button|lower == 'true' %} - {% set path = theme_github_user + '/' + theme_github_repo %} -{% else %} - {% set path = theme_codecov_button %} -{% endif %} -<p> -<a class="badge" href="https://codecov.io/github/{{ path }}"> - <img - alt="https://codecov.io/github/{{ path }}/coverage.svg?branch={{ theme_badge_branch }}" - src="https://codecov.io/github/{{ path }}/coverage.svg?branch={{ theme_badge_branch }}" - /> -</a> -</p> -{% endif %} diff --git a/subprojects/d2tk/pugl/doc/c/Doxyfile b/subprojects/d2tk/pugl/doc/c/Doxyfile.in index bdc3a464..96bbf632 100644 --- a/subprojects/d2tk/pugl/doc/c/Doxyfile +++ b/subprojects/d2tk/pugl/doc/c/Doxyfile.in @@ -2,12 +2,13 @@ PROJECT_NAME = Pugl PROJECT_BRIEF = "A minimal portable API for embeddable GUIs" QUIET = YES -WARN_AS_ERROR = NO +WARN_AS_ERROR = YES WARN_IF_UNDOCUMENTED = NO WARN_NO_PARAMDOC = NO JAVADOC_AUTOBRIEF = YES +FULL_PATH_NAMES = NO CASE_SENSE_NAMES = YES HIDE_IN_BODY_DOCS = YES REFERENCES_LINK_SOURCE = NO @@ -21,10 +22,7 @@ SHOW_FILES = NO MACRO_EXPANSION = YES PREDEFINED = PUGL_API PUGL_DISABLE_DEPRECATED PUGL_CONST_API= PUGL_CONST_FUNC= -INPUT = ../../include/pugl/cairo.h \ - ../../include/pugl/gl.h \ - ../../include/pugl/pugl.h \ - ../../include/pugl/stub.h \ - ../../include/pugl/vulkan.h +STRIP_FROM_PATH = @PUGL_SRCDIR@ +INPUT = @PUGL_HEADERS@ -OUTPUT_DIRECTORY = . +OUTPUT_DIRECTORY = doc/c diff --git a/subprojects/d2tk/pugl/doc/c/api/meson.build b/subprojects/d2tk/pugl/doc/c/api/meson.build new file mode 100644 index 00000000..5c1e30ef --- /dev/null +++ b/subprojects/d2tk/pugl/doc/c/api/meson.build @@ -0,0 +1,5 @@ +c_pugl_rst = custom_target( + 'C API ReST Documentation', + command: [dox_to_sphinx, '-f', '@INPUT0@', 'doc/c/api'], + input: [c_index_xml] + c_rst_files, + output: 'pugl.rst') diff --git a/subprojects/d2tk/pugl/doc/c/event-loop.rst b/subprojects/d2tk/pugl/doc/c/event-loop.rst new file mode 100644 index 00000000..3b9915fd --- /dev/null +++ b/subprojects/d2tk/pugl/doc/c/event-loop.rst @@ -0,0 +1,101 @@ +.. default-domain:: c +.. highlight:: c + +###################### +Driving the Event Loop +###################### + +Pugl does not contain any threads or other event loop "magic". +For flexibility, the event loop is driven explicitly by repeatedly calling :func:`puglUpdate`, +which processes events from the window system and dispatches them to views when necessary. + +The exact use of :func:`puglUpdate` depends on the application. +Plugins should call it with a ``timeout`` of 0 in a callback driven by the host. +This avoids blocking the main loop, +since other plugins and the host itself need to run as well. + +A program can use whatever timeout is appropriate: +event-driven applications may wait forever by using a ``timeout`` of -1, +while those that draw continuously may use a significant fraction of the frame period +(with enough time left over to render). + +********* +Redrawing +********* + +Occasional redrawing can be requested by calling :func:`puglPostRedisplay` or :func:`puglPostRedisplayRect`. +After these are called, +a :struct:`PuglEventExpose` will be dispatched on the next call to :func:`puglUpdate`. + +For continuous redrawing, +call :func:`puglPostRedisplay` while handling a :struct:`PuglEventUpdate` event. +This event is sent just before views are redrawn, +so it can be used as a hook to expand the update region right before the view is exposed. +Anything else that needs to be done every frame can be handled similarly. + +***************** +Event Dispatching +***************** + +Ideally, pending events are dispatched during a call to :func:`puglUpdate`, +directly within the scope of that call. + +Unfortunately, this is not universally true due to differences between platforms. + +MacOS +===== + +On MacOS, drawing is handled specially and not by the normal event queue mechanism. +This means that configure and expose events, +and possibly others, +may be dispatched to a view outside the scope of a :func:`puglUpdate` call. +In general, you can not rely on coherent event dispatching semantics on MacOS: +the operating system can call into application code at "random" times, +and these calls may result in Pugl events being dispatched. + +An application that follows the Pugl guidelines should work fine, +but there is one significant inconsistency you may encounter on MacOS: +posting a redisplay will not wake up a blocked :func:`puglUpdate` call. + +Windows +======= + +On Windows, the application has relatively tight control over the event loop, +so events are typically dispatched explicitly by :func:`puglUpdate`. +Drawing is handled by events, +so posting a redisplay will wake up a blocked :func:`puglUpdate` call. + +However, it is possible for the system to dispatch events at other times. +So, +it is possible for events to be dispatched outside the scope of a :func:`puglUpdate` call, +but this does not happen in normal circumstances and can largely be ignored. + +X11 +=== + +On X11, the application strictly controls event dispatching, +and there is no way for the system to call into application code at surprising times. +So, all events are dispatched in the scope of a :func:`puglUpdate` call. + +********************* +Recursive Event Loops +********************* + +On Windows and MacOS, +the event loop is stalled while the user is resizing the window or, +on Windows, +has displayed the window menu. +This means that :func:`puglUpdate` will block until the resize is finished, +or the menu is closed. + +Pugl dispatches :struct:`PuglEventLoopEnter` and :struct:`PuglEventLoopLeave` events to notify the application of this situation. +If you want to continuously redraw during resizing on these platforms, +you can schedule a timer with :func:`puglStartTimer` when the recursive loop is entered, +and post redisplays when handling the :struct:`PuglEventTimer`. +Be sure to remove the timer with :func:`puglStopTimer` when the recursive loop is finished. + +On X11, there are no recursive event loops, +and everything works as usual while the user is resizing the window. +There is nothing special about a "live resize" on X11, +and the above loop events will never be dispatched. + diff --git a/subprojects/d2tk/pugl/doc/c/events.rst b/subprojects/d2tk/pugl/doc/c/events.rst new file mode 100644 index 00000000..bf964db5 --- /dev/null +++ b/subprojects/d2tk/pugl/doc/c/events.rst @@ -0,0 +1,84 @@ +.. default-domain:: c +.. highlight:: c + +*************** +Handling Events +*************** + +Events are sent to a view when it has received user input, +must be drawn, or in other situations that may need to be handled such as resizing. + +Events are sent to the event handler as a :union:`PuglEvent` union. +The ``type`` field defines the type of the event and which field of the union is active. +The application must handle at least :enumerator:`PUGL_CONFIGURE <PuglEventType.PUGL_CONFIGURE>` +and :enumerator:`PUGL_EXPOSE <PuglEventType.PUGL_EXPOSE>` to draw anything, +but there are many other :enum:`event types <PuglEventType>`. + +For example, a basic event handler might look something like this: + +.. code-block:: c + + static PuglStatus + onEvent(PuglView* view, const PuglEvent* event) + { + MyApp* app = (MyApp*)puglGetHandle(view); + + switch (event->type) { + case PUGL_CREATE: + return setupGraphics(app); + case PUGL_DESTROY: + return teardownGraphics(app); + case PUGL_CONFIGURE: + return resize(app, event->configure.width, event->configure.height); + case PUGL_EXPOSE: + return draw(app, view); + case PUGL_CLOSE: + return quit(app); + case PUGL_BUTTON_PRESS: + return onButtonPress(app, view, event->button); + default: + break; + } + + return PUGL_SUCCESS; + } + +Using the Graphics Context +========================== + +Drawing +------- + +Note that Pugl uses a different drawing model than many libraries, +particularly those designed for game-style main loops like `SDL <https://libsdl.org/>`_ and `GLFW <https://www.glfw.org/>`_. + +In that style of code, drawing is performed imperatively in the main loop, +but with Pugl, the application must draw only while handling an expose event. +This is because Pugl supports event-driven applications that only draw the damaged region when necessary, +and handles exposure internally to provide optimized and consistent behavior across platforms. + +Cairo Context +------------- + +A Cairo context is created for each :struct:`PuglEventExpose`, +and only exists during the handling of that event. +Null is returned by :func:`puglGetContext` at any other time. + +OpenGL Context +-------------- + +The OpenGL context is only active during the handling of these events: + +- :struct:`PuglEventCreate` +- :struct:`PuglEventDestroy` +- :struct:`PuglEventConfigure` +- :struct:`PuglEventExpose` + +As always, drawing is only possible during an expose. + +Vulkan Context +-------------- + +With Vulkan, the graphics context is managed by the application rather than Pugl. +However, drawing must still only be performed during an expose. + diff --git a/subprojects/d2tk/pugl/doc/c/index.rst b/subprojects/d2tk/pugl/doc/c/index.rst index 6f046de1..020cf324 100644 --- a/subprojects/d2tk/pugl/doc/c/index.rst +++ b/subprojects/d2tk/pugl/doc/c/index.rst @@ -1,6 +1,11 @@ +#### +Pugl +#### + +.. include:: summary.rst + .. toctree:: - pugl deployment overview - reference + api/pugl diff --git a/subprojects/d2tk/pugl/doc/c/meson.build b/subprojects/d2tk/pugl/doc/c/meson.build new file mode 100644 index 00000000..df9363ed --- /dev/null +++ b/subprojects/d2tk/pugl/doc/c/meson.build @@ -0,0 +1,44 @@ +config = configuration_data() +config.set('PUGL_VERSION', meson.project_version()) + +conf_py = configure_file(configuration: config, + input: '../conf.py.in', + output: 'conf.py') + +configure_file(copy: true, input: '../deployment.rst', output: 'deployment.rst') +configure_file(copy: true, input: '../summary.rst', output: 'summary.rst') + +c_rst_files = files( + 'index.rst', + 'overview.rst', + 'world.rst', + 'view.rst', + 'events.rst', + 'event-loop.rst', + 'shutting-down.rst' +) + +foreach f : c_rst_files + configure_file(copy: true, input: f, output: '@PLAINNAME@') +endforeach + +subdir('xml') +subdir('api') + +docs = custom_target( + 'C API Documentation (singlehtml)', + command: [sphinx_build, '-M', 'singlehtml', 'doc/c/', 'doc/c/', '-E', '-q', '-t', 'singlehtml'], + input: [c_rst_files, c_pugl_rst, c_index_xml], + output: 'singlehtml', + build_by_default: true, + install: true, + install_dir: docdir / 'pugl-0') + +docs = custom_target( + 'C API Documentation (html)', + command: [sphinx_build, '-M', 'html', 'doc/c/', 'doc/c/', '-E', '-q', '-t', 'html'], + input: [c_rst_files, c_pugl_rst, c_index_xml], + output: 'html', + build_by_default: true, + install: true, + install_dir: docdir / 'pugl-0') diff --git a/subprojects/d2tk/pugl/doc/c/overview.rst b/subprojects/d2tk/pugl/doc/c/overview.rst index 3ebe21e9..4bd024df 100644 --- a/subprojects/d2tk/pugl/doc/c/overview.rst +++ b/subprojects/d2tk/pugl/doc/c/overview.rst @@ -1,579 +1,26 @@ .. default-domain:: c .. highlight:: c -The core API (excluding backend-specific components) is declared in ``pugl.h``: - -.. code-block:: c +######## +Overview +######## - #include <pugl/pugl.h> - -The API revolves around two main objects: the `world` and the `view`. +The Pugl API revolves around two main objects: the `world` and the `view`. An application creates a world to manage top-level state, then creates one or more views to display. -**************** -Creating a World -**************** - -The world is the top-level object which represents an instance of Pugl. -It handles the connection to the window system, -and manages views and the event loop. - -An application typically has a single world, -which is constructed once on startup and used to drive the main event loop. - -Construction -============ - -A world must be created before any views, and it must outlive all of its views. -A world is created with :func:`puglNewWorld`, for example: - -.. code-block:: c - - PuglWorld* world = puglNewWorld(PUGL_PROGRAM, 0); - -For a plugin, specify :enumerator:`PUGL_MODULE <PuglWorldType.PUGL_MODULE>` instead. -In some cases, it is necessary to pass additional flags. -For example, Vulkan requires thread support: - -.. code-block:: c - - PuglWorld* world = puglNewWorld(PUGL_MODULE, PUGL_WORLD_THREADS) - -It is a good idea to set a class name for your project with :func:`puglSetClassName`. -This allows the window system to distinguish different applications and, -for example, users to set up rules to manage their windows nicely: - -.. code-block:: c - - puglSetClassName(world, "MyAwesomeProject") - -Setting Application Data -======================== - -Pugl will call an event handler in the application with only a view pointer and an event, -so there needs to be some way to access the data you use in your application. -This is done by setting an opaque handle on the world with :func:`puglSetWorldHandle`, -for example: - -.. code-block:: c - - puglSetWorldHandle(world, myApp); - -The handle can be later retrieved with :func:`puglGetWorldHandle`: - -.. code-block:: c - - MyApp* app = (MyApp*)puglGetWorldHandle(world); - -All non-constant data should be accessed via this handle, -to avoid problems associated with static mutable data. - -*************** -Creating a View -*************** - -A view is a drawable region that receives events. -You may think of it as a window, -though it may be embedded and not represent a top-level system window. [#f1]_ - -Creating a visible view is a multi-step process. -When a new view is created with :func:`puglNewView`, -it does not yet represent a "real" system view: - -.. code-block:: c - - PuglView* view = puglNewView(world); - -Configuring the Frame -===================== - -Before display, -the necessary :doc:`frame <api/frame>` and :doc:`window <api/window>` attributes should be set. -These allow the window system (or plugin host) to arrange the view properly. -For example: - -.. code-block:: c - - const double defaultWidth = 1920.0; - const double defaultHeight = 1080.0; - - puglSetWindowTitle(view, "My Window"); - puglSetDefaultSize(view, defaultWidth, defaultHeight); - puglSetMinSize(view, defaultWidth / 4.0, defaultHeight / 4.0); - puglSetAspectRatio(view, 1, 1, 16, 9); - -There are also several :enum:`hints <PuglViewHint>` for basic attributes that can be set: - -.. code-block:: c - - puglSetViewHint(view, PUGL_RESIZABLE, PUGL_TRUE); - puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, PUGL_TRUE); - -Embedding -========= - -To embed the view in another window, -you will need to somehow get the :type:`native view handle <PuglNativeView>` for the parent, -then set it with :func:`puglSetParentWindow`. -If the parent is a Pugl view, -the native handle can be accessed with :func:`puglGetNativeWindow`. -For example: - -.. code-block:: c - - puglSetParentWindow(view, puglGetNativeWindow(parent)); - -Setting an Event Handler -======================== - -In order to actually do anything, a view must process events from the system. -Pugl dispatches all events to a single :type:`event handling function <PuglEventFunc>`, -which is set with :func:`puglSetEventFunc`: - -.. code-block:: c - - puglSetEventFunc(view, onEvent); - -See `Handling Events`_ below for details on writing the event handler itself. - -Setting View Data -================= - -Since the event handler is called with only a view pointer and an event, -there needs to be some way to access application data associated with the view. -Similar to `Setting Application Data`_ above, -this is done by setting an opaque handle on the view with :func:`puglSetHandle`, -for example: - -.. code-block:: c - - puglSetHandle(view, myViewData); - -The handle can be later retrieved, -likely in the event handler, -with :func:`puglGetHandle`: - -.. code-block:: c - - MyViewData* data = (MyViewData*)puglGetHandle(view); - -All non-constant data should be accessed via this handle, -to avoid problems associated with static mutable data. - -If data is also associated with the world, -it can be retrieved via the view using :func:`puglGetWorld`: - -.. code-block:: c - - PuglWorld* world = puglGetWorld(view); - MyApp* app = (MyApp*)puglGetWorldHandle(world); - -Setting a Backend -================= - -Before being realized, the view must have a backend set with :func:`puglSetBackend`. - -The backend manages the graphics API that will be used for drawing. -Pugl includes backends and supporting API for -:doc:`Cairo <api/cairo>`, :doc:`OpenGL <api/gl>`, and :doc:`Vulkan <api/vulkan>`. - -Using Cairo ------------ - -Cairo-specific API is declared in the ``cairo.h`` header: - -.. code-block:: c - - #include <pugl/cairo.h> - -The Cairo backend is provided by :func:`puglCairoBackend()`: - -.. code-block:: c - - puglSetBackend(view, puglCairoBackend()); - -No additional configuration is required for Cairo. -To draw when handling an expose event, -the `Cairo context <https://www.cairographics.org/manual/cairo-cairo-t.html>`_ can be accessed with :func:`puglGetContext`: - -.. code-block:: c - - cairo_t* cr = (cairo_t*)puglGetContext(view); - -Using OpenGL ------------- - -OpenGL-specific API is declared in the ``gl.h`` header: - -.. code-block:: c - - #include <pugl/gl.h> - -The OpenGL backend is provided by :func:`puglGlBackend()`: - -.. code-block:: c - - puglSetBackend(view, puglGlBackend()); - -Some hints must also be set so that the context can be set up correctly. -For example, to use OpenGL 3.3 Core Profile: - -.. code-block:: c - - puglSetViewHint(view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE); - puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 3); - puglSetViewHint(view, PUGL_CONTEXT_VERSION_MINOR, 3); - -If you need to perform some setup using the OpenGL API, -there are two ways to do so. - -The OpenGL context is active when -:enumerator:`PUGL_CREATE <PuglEventType.PUGL_CREATE>` and -:enumerator:`PUGL_DESTROY <PuglEventType.PUGL_DESTROY>` -events are dispatched, -so things like creating and destroying shaders and textures can be done then. - -Alternatively, if it is cumbersome to set up and tear down OpenGL in the event handler, -:func:`puglEnterContext` and :func:`puglLeaveContext` can be used to manually activate the OpenGL context during application setup. -Note, however, that unlike many other APIs, these functions must not be used for drawing. -It is only valid to use the OpenGL API for configuration in a manually entered context, -rendering will not work. -For example: - -.. code-block:: c - - puglEnterContext(view); - setupOpenGL(myApp); - puglLeaveContext(view); - - while (!myApp->quit) { - puglUpdate(world, 0.0); - } - - puglEnterContext(view); - teardownOpenGL(myApp); - puglLeaveContext(view); - -Using Vulkan ------------- - -Vulkan-specific API is declared in the ``vulkan.h`` header. -This header includes Vulkan headers, -so if you are dynamically loading Vulkan at runtime, -you should define ``VK_NO_PROTOTYPES`` before including it. - -.. code-block:: c - - #define VK_NO_PROTOTYPES - - #include <pugl/vulkan.h> - -The Vulkan backend is provided by :func:`puglVulkanBackend()`: - -.. code-block:: c - - puglSetBackend(view, puglVulkanBackend()); - -Unlike OpenGL, almost all Vulkan configuration is done using the Vulkan API directly. -Pugl only provides a portable mechanism to load the Vulkan library and get the functions used to load the rest of the Vulkan API. - -Loading Vulkan -^^^^^^^^^^^^^^ - -For maximum compatibility, -it is best to not link to Vulkan at compile-time, -but instead load the Vulkan API at run-time. -To do so, first create a :struct:`PuglVulkanLoader`: - -.. code-block:: c - - PuglVulkanLoader* loader = puglNewVulkanLoader(world); - -The loader manages the dynamically loaded Vulkan library, -so it must be kept alive for as long as the application is using Vulkan. -You can get the function used to load Vulkan functions with :func:`puglGetInstanceProcAddrFunc`: - -.. code-block:: c - - PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = - puglGetInstanceProcAddrFunc(loader); - -This vkGetInstanceProcAddr_ function can be used to load the rest of the Vulkan API. -For example, you can use it to get the vkCreateInstance_ function, -then use that to create your Vulkan instance. -In practice, you will want to use some loader or wrapper API since there are many Vulkan functions. - -For advanced situations, -there is also :func:`puglGetDeviceProcAddrFunc` which retrieves the vkGetDeviceProcAddr_ function instead. - -The Vulkan loader is provided for convenience, -so that applications to not need to write platform-specific code to load Vulkan. -Its use it not mandatory and Pugl can be used with Vulkan loaded by some other method. - -Linking with Vulkan -^^^^^^^^^^^^^^^^^^^ - -If you do want to link to the Vulkan library at compile time, -note that the Pugl Vulkan backend does not depend on it, -so you will have to do so explicitly. - -Creating a Surface -^^^^^^^^^^^^^^^^^^ - -The details of using Vulkan are far beyond the scope of this documentation, -but Pugl provides a portable function, :func:`puglCreateSurface`, -to get the Vulkan surface for a view. -Assuming you have somehow created your ``VkInstance``, -you can get the surface for a view using :func:`puglCreateSurface`: - -.. code-block:: c - - VkSurfaceKHR* surface = NULL; - puglCreateSurface(puglGetDeviceProcAddrFunc(loader), - view, - vulkanInstance, - NULL, - &surface); - -Showing the View -================ - -Once the view is configured, it can be "realized" with :func:`puglRealize`. -This creates a "real" system view, for example: - -.. code-block:: c - - PuglStatus status = puglRealize(view); - if (status) { - fprintf(stderr, "Error realizing view (%s)\n", puglStrerror(status)); - } - -Note that realizing a view can fail for many reasons, -so the return code should always be checked. -This is generally the case for any function that interacts with the window system. -Most functions also return a :enum:`PuglStatus`, -but these checks are omitted for brevity in the rest of this documentation. - -A realized view is not initially visible, -but can be shown with :func:`puglShow`: - -.. code-block:: c - - puglShow(view); - -To create an initially visible view, -it is also possible to simply call :func:`puglShow` right away. -The view will be automatically realized if necessary. - -*************** -Handling Events -*************** - -Events are sent to a view when it has received user input, -must be drawn, or in other situations that may need to be handled such as resizing. - -Events are sent to the event handler as a :union:`PuglEvent` union. -The ``type`` field defines the type of the event and which field of the union is active. -The application must handle at least :enumerator:`PUGL_CONFIGURE <PuglEventType.PUGL_CONFIGURE>` -and :enumerator:`PUGL_EXPOSE <PuglEventType.PUGL_EXPOSE>` to draw anything, -but there are many other :enum:`event types <PuglEventType>`. - -For example, a basic event handler might look something like this: +The core API (excluding backend-specific components) is declared in ``pugl.h``: .. code-block:: c - static PuglStatus - onEvent(PuglView* view, const PuglEvent* event) - { - MyApp* app = (MyApp*)puglGetHandle(view); - - switch (event->type) { - case PUGL_CREATE: - return setupGraphics(app); - case PUGL_DESTROY: - return teardownGraphics(app); - case PUGL_CONFIGURE: - return resize(app, event->configure.width, event->configure.height); - case PUGL_EXPOSE: - return draw(app, view); - case PUGL_CLOSE: - return quit(app); - case PUGL_BUTTON_PRESS: - return onButtonPress(app, view, event->button); - default: - break; - } - - return PUGL_SUCCESS; - } - -Using the Graphics Context -========================== - -Drawing -------- - -Note that Pugl uses a different drawing model than many libraries, -particularly those designed for game-style main loops like `SDL <https://libsdl.org/>`_ and `GLFW <https://www.glfw.org/>`_. - -In that style of code, drawing is performed imperatively in the main loop, -but with Pugl, the application must draw only while handling an expose event. -This is because Pugl supports event-driven applications that only draw the damaged region when necessary, -and handles exposure internally to provide optimized and consistent behavior across platforms. - -Cairo Context -------------- - -A Cairo context is created for each :struct:`PuglEventExpose`, -and only exists during the handling of that event. -Null is returned by :func:`puglGetContext` at any other time. - -OpenGL Context --------------- - -The OpenGL context is only active during the handling of these events: - -- :struct:`PuglEventCreate` -- :struct:`PuglEventDestroy` -- :struct:`PuglEventConfigure` -- :struct:`PuglEventExpose` - -As always, drawing is only possible during an expose. - -Vulkan Context --------------- - -With Vulkan, the graphics context is managed by the application rather than Pugl. -However, drawing must still only be performed during an expose. - -********************** -Driving the Event Loop -********************** - -Pugl does not contain any threads or other event loop "magic". -For flexibility, the event loop is driven explicitly by repeatedly calling :func:`puglUpdate`, -which processes events from the window system and dispatches them to views when necessary. - -The exact use of :func:`puglUpdate` depends on the application. -Plugins should call it with a ``timeout`` of 0 in a callback driven by the host. -This avoids blocking the main loop, -since other plugins and the host itself need to run as well. - -A program can use whatever timeout is appropriate: -event-driven applications may wait forever by using a ``timeout`` of -1, -while those that draw continuously may use a significant fraction of the frame period -(with enough time left over to render). - -Redrawing -========= - -Occasional redrawing can be requested by calling :func:`puglPostRedisplay` or :func:`puglPostRedisplayRect`. -After these are called, -a :struct:`PuglEventExpose` will be dispatched on the next call to :func:`puglUpdate`. - -For continuous redrawing, -call :func:`puglPostRedisplay` while handling a :struct:`PuglEventUpdate` event. -This event is sent just before views are redrawn, -so it can be used as a hook to expand the update region right before the view is exposed. -Anything else that needs to be done every frame can be handled similarly. - -Event Dispatching -================= - -Ideally, pending events are dispatched during a call to :func:`puglUpdate`, -directly within the scope of that call. - -Unfortunately, this is not universally true due to differences between platforms. - -MacOS ------ - -On MacOS, drawing is handled specially and not by the normal event queue mechanism. -This means that configure and expose events, -and possibly others, -may be dispatched to a view outside the scope of a :func:`puglUpdate` call. -In general, you can not rely on coherent event dispatching semantics on MacOS: -the operating system can call into application code at "random" times, -and these calls may result in Pugl events being dispatched. - -An application that follows the Pugl guidelines should work fine, -but there is one significant inconsistency you may encounter on MacOS: -posting a redisplay will not wake up a blocked :func:`puglUpdate` call. - -Windows -------- - -On Windows, the application has relatively tight control over the event loop, -so events are typically dispatched explicitly by :func:`puglUpdate`. -Drawing is handled by events, -so posting a redisplay will wake up a blocked :func:`puglUpdate` call. - -However, it is possible for the system to dispatch events at other times. -So, -it is possible for events to be dispatched outside the scope of a :func:`puglUpdate` call, -but this does not happen in normal circumstances and can largely be ignored. - -X11 ---- - -On X11, the application strictly controls event dispatching, -and there is no way for the system to call into application code at surprising times. -So, all events are dispatched in the scope of a :func:`puglUpdate` call. - -Recursive Event Loops ---------------------- - -On Windows and MacOS, -the event loop is stalled while the user is resizing the window or, -on Windows, -has displayed the window menu. -This means that :func:`puglUpdate` will block until the resize is finished, -or the menu is closed. - -Pugl dispatches :struct:`PuglEventLoopEnter` and :struct:`PuglEventLoopLeave` events to notify the application of this situation. -If you want to continuously redraw during resizing on these platforms, -you can schedule a timer with :func:`puglStartTimer` when the recursive loop is entered, -and post redisplays when handling the :struct:`PuglEventTimer`. -Be sure to remove the timer with :func:`puglStopTimer` when the recursive loop is finished. - -On X11, there are no recursive event loops, -and everything works as usual while the user is resizing the window. -There is nothing special about a "live resize" on X11, -and the above loop events will never be dispatched. - -************* -Shutting Down -************* - -When a view is closed, -it will receive a :struct:`PuglEventClose`. -An application may also set a flag based on user input or other conditions, -which can be used to break out of the main loop and stop calling :func:`puglUpdate`. - -When the main event loop has finished running, -any views and the world need to be destroyed, in that order. -For example: + #include <pugl/pugl.h> -.. code-block:: c +.. toctree:: - puglFreeView(view); - puglFreeWorld(world); + world + view + events + event-loop + shutting-down .. _pkg-config: https://www.freedesktop.org/wiki/Software/pkg-config/ - -.. _vkCreateInstance: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCreateInstance.html - -.. _vkGetDeviceProcAddr: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkGetDeviceProcAddr.html - -.. _vkGetInstanceProcAddr: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkGetInstanceProcAddr.html - -.. rubric:: Footnotes - -.. [#f1] MacOS has a strong distinction between - `views <https://developer.apple.com/documentation/appkit/nsview>`_, - which may be nested, and - `windows <https://developer.apple.com/documentation/appkit/nswindow>`_, - which may not. - On Windows and X11, everything is a nestable window, - but top-level windows are configured differently. diff --git a/subprojects/d2tk/pugl/doc/c/reference.rst b/subprojects/d2tk/pugl/doc/c/reference.rst deleted file mode 100644 index 21a187f5..00000000 --- a/subprojects/d2tk/pugl/doc/c/reference.rst +++ /dev/null @@ -1,18 +0,0 @@ -############# -API Reference -############# - -This section contains the generated documentation for all symbols in the public -API. - -.. toctree:: - - api/status - api/world - api/view - api/events - - api/cairo - api/gl - api/stub - api/vulkan diff --git a/subprojects/d2tk/pugl/doc/c/shutting-down.rst b/subprojects/d2tk/pugl/doc/c/shutting-down.rst new file mode 100644 index 00000000..dfb56cd9 --- /dev/null +++ b/subprojects/d2tk/pugl/doc/c/shutting-down.rst @@ -0,0 +1,20 @@ +.. default-domain:: c +.. highlight:: c + +############# +Shutting Down +############# + +When a view is closed, +it will receive a :struct:`PuglEventClose`. +An application may also set a flag based on user input or other conditions, +which can be used to break out of the main loop and stop calling :func:`puglUpdate`. + +When the main event loop has finished running, +any views and the world need to be destroyed, in that order. +For example: + +.. code-block:: c + + puglFreeView(view); + puglFreeWorld(world); diff --git a/subprojects/d2tk/pugl/doc/c/view.rst b/subprojects/d2tk/pugl/doc/c/view.rst new file mode 100644 index 00000000..12f146d3 --- /dev/null +++ b/subprojects/d2tk/pugl/doc/c/view.rst @@ -0,0 +1,321 @@ +.. default-domain:: c +.. highlight:: c + +############### +Creating a View +############### + +A view is a drawable region that receives events. +You may think of it as a window, +though it may be embedded and not represent a top-level system window. [#f1]_ + +Creating a visible view is a multi-step process. +When a new view is created with :func:`puglNewView`, +it does not yet represent a "real" system view: + +.. code-block:: c + + PuglView* view = puglNewView(world); + +********************* +Configuring the Frame +********************* + +Before display, +the necessary :doc:`frame <api/frame>` and :doc:`window <api/window>` attributes should be set. +These allow the window system (or plugin host) to arrange the view properly. +For example: + +.. code-block:: c + + const double defaultWidth = 1920.0; + const double defaultHeight = 1080.0; + + puglSetWindowTitle(view, "My Window"); + puglSetDefaultSize(view, defaultWidth, defaultHeight); + puglSetMinSize(view, defaultWidth / 4.0, defaultHeight / 4.0); + puglSetAspectRatio(view, 1, 1, 16, 9); + +There are also several :enum:`hints <PuglViewHint>` for basic attributes that can be set: + +.. code-block:: c + + puglSetViewHint(view, PUGL_RESIZABLE, PUGL_TRUE); + puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, PUGL_TRUE); + +********* +Embedding +********* + +To embed the view in another window, +you will need to somehow get the :type:`native view handle <PuglNativeView>` for the parent, +then set it with :func:`puglSetParentWindow`. +If the parent is a Pugl view, +the native handle can be accessed with :func:`puglGetNativeWindow`. +For example: + +.. code-block:: c + + puglSetParentWindow(view, puglGetNativeWindow(parent)); + +************************ +Setting an Event Handler +************************ + +In order to actually do anything, a view must process events from the system. +Pugl dispatches all events to a single :type:`event handling function <PuglEventFunc>`, +which is set with :func:`puglSetEventFunc`: + +.. code-block:: c + + puglSetEventFunc(view, onEvent); + +See :doc:`events` for details on writing the event handler itself. + +***************** +Setting View Data +***************** + +Since the event handler is called with only a view pointer and an event, +there needs to be some way to access application data associated with the view. +Similar to :ref:`setting application data <setting-application-data>`, +this is done by setting an opaque handle on the view with :func:`puglSetHandle`, +for example: + +.. code-block:: c + + puglSetHandle(view, myViewData); + +The handle can be later retrieved, +likely in the event handler, +with :func:`puglGetHandle`: + +.. code-block:: c + + MyViewData* data = (MyViewData*)puglGetHandle(view); + +All non-constant data should be accessed via this handle, +to avoid problems associated with static mutable data. + +If data is also associated with the world, +it can be retrieved via the view using :func:`puglGetWorld`: + +.. code-block:: c + + PuglWorld* world = puglGetWorld(view); + MyApp* app = (MyApp*)puglGetWorldHandle(world); + +***************** +Setting a Backend +***************** + +Before being realized, the view must have a backend set with :func:`puglSetBackend`. + +The backend manages the graphics API that will be used for drawing. +Pugl includes backends and supporting API for +:doc:`Cairo <api/cairo>`, :doc:`OpenGL <api/gl>`, and :doc:`Vulkan <api/vulkan>`. + +Using Cairo +=========== + +Cairo-specific API is declared in the ``cairo.h`` header: + +.. code-block:: c + + #include <pugl/cairo.h> + +The Cairo backend is provided by :func:`puglCairoBackend()`: + +.. code-block:: c + + puglSetBackend(view, puglCairoBackend()); + +No additional configuration is required for Cairo. +To draw when handling an expose event, +the `Cairo context <https://www.cairographics.org/manual/cairo-cairo-t.html>`_ can be accessed with :func:`puglGetContext`: + +.. code-block:: c + + cairo_t* cr = (cairo_t*)puglGetContext(view); + +Using OpenGL +============ + +OpenGL-specific API is declared in the ``gl.h`` header: + +.. code-block:: c + + #include <pugl/gl.h> + +The OpenGL backend is provided by :func:`puglGlBackend()`: + +.. code-block:: c + + puglSetBackend(view, puglGlBackend()); + +Some hints must also be set so that the context can be set up correctly. +For example, to use OpenGL 3.3 Core Profile: + +.. code-block:: c + + puglSetViewHint(view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE); + puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 3); + puglSetViewHint(view, PUGL_CONTEXT_VERSION_MINOR, 3); + +If you need to perform some setup using the OpenGL API, +there are two ways to do so. + +The OpenGL context is active when +:enumerator:`PUGL_CREATE <PuglEventType.PUGL_CREATE>` and +:enumerator:`PUGL_DESTROY <PuglEventType.PUGL_DESTROY>` +events are dispatched, +so things like creating and destroying shaders and textures can be done then. + +Alternatively, if it is cumbersome to set up and tear down OpenGL in the event handler, +:func:`puglEnterContext` and :func:`puglLeaveContext` can be used to manually activate the OpenGL context during application setup. +Note, however, that unlike many other APIs, these functions must not be used for drawing. +It is only valid to use the OpenGL API for configuration in a manually entered context, +rendering will not work. +For example: + +.. code-block:: c + + puglEnterContext(view); + setupOpenGL(myApp); + puglLeaveContext(view); + + while (!myApp->quit) { + puglUpdate(world, 0.0); + } + + puglEnterContext(view); + teardownOpenGL(myApp); + puglLeaveContext(view); + +Using Vulkan +============ + +Vulkan-specific API is declared in the ``vulkan.h`` header. +This header includes Vulkan headers, +so if you are dynamically loading Vulkan at runtime, +you should define ``VK_NO_PROTOTYPES`` before including it. + +.. code-block:: c + + #define VK_NO_PROTOTYPES + + #include <pugl/vulkan.h> + +The Vulkan backend is provided by :func:`puglVulkanBackend()`: + +.. code-block:: c + + puglSetBackend(view, puglVulkanBackend()); + +Unlike OpenGL, almost all Vulkan configuration is done using the Vulkan API directly. +Pugl only provides a portable mechanism to load the Vulkan library and get the functions used to load the rest of the Vulkan API. + +Loading Vulkan +-------------- + +For maximum compatibility, +it is best to not link to Vulkan at compile-time, +but instead load the Vulkan API at run-time. +To do so, first create a :struct:`PuglVulkanLoader`: + +.. code-block:: c + + PuglVulkanLoader* loader = puglNewVulkanLoader(world); + +The loader manages the dynamically loaded Vulkan library, +so it must be kept alive for as long as the application is using Vulkan. +You can get the function used to load Vulkan functions with :func:`puglGetInstanceProcAddrFunc`: + +.. code-block:: c + + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = + puglGetInstanceProcAddrFunc(loader); + +This vkGetInstanceProcAddr_ function can be used to load the rest of the Vulkan API. +For example, you can use it to get the vkCreateInstance_ function, +then use that to create your Vulkan instance. +In practice, you will want to use some loader or wrapper API since there are many Vulkan functions. + +For advanced situations, +there is also :func:`puglGetDeviceProcAddrFunc` which retrieves the vkGetDeviceProcAddr_ function instead. + +The Vulkan loader is provided for convenience, +so that applications to not need to write platform-specific code to load Vulkan. +Its use it not mandatory and Pugl can be used with Vulkan loaded by some other method. + +Linking with Vulkan +------------------- + +If you do want to link to the Vulkan library at compile time, +note that the Pugl Vulkan backend does not depend on it, +so you will have to do so explicitly. + +Creating a Surface +------------------ + +The details of using Vulkan are far beyond the scope of this documentation, +but Pugl provides a portable function, :func:`puglCreateSurface`, +to get the Vulkan surface for a view. +Assuming you have somehow created your ``VkInstance``, +you can get the surface for a view using :func:`puglCreateSurface`: + +.. code-block:: c + + VkSurfaceKHR* surface = NULL; + puglCreateSurface(puglGetDeviceProcAddrFunc(loader), + view, + vulkanInstance, + NULL, + &surface); + +**************** +Showing the View +**************** + +Once the view is configured, it can be "realized" with :func:`puglRealize`. +This creates a "real" system view, for example: + +.. code-block:: c + + PuglStatus status = puglRealize(view); + if (status) { + fprintf(stderr, "Error realizing view (%s)\n", puglStrerror(status)); + } + +Note that realizing a view can fail for many reasons, +so the return code should always be checked. +This is generally the case for any function that interacts with the window system. +Most functions also return a :enum:`PuglStatus`, +but these checks are omitted for brevity in the rest of this documentation. + +A realized view is not initially visible, +but can be shown with :func:`puglShow`: + +.. code-block:: c + + puglShow(view); + +To create an initially visible view, +it is also possible to simply call :func:`puglShow` right away. +The view will be automatically realized if necessary. + +.. rubric:: Footnotes + +.. [#f1] MacOS has a strong distinction between + `views <https://developer.apple.com/documentation/appkit/nsview>`_, + which may be nested, and + `windows <https://developer.apple.com/documentation/appkit/nswindow>`_, + which may not. + On Windows and X11, everything is a nestable window, + but top-level windows are configured differently. + +.. _vkCreateInstance: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCreateInstance.html + +.. _vkGetDeviceProcAddr: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkGetDeviceProcAddr.html + +.. _vkGetInstanceProcAddr: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkGetInstanceProcAddr.html diff --git a/subprojects/d2tk/pugl/doc/c/world.rst b/subprojects/d2tk/pugl/doc/c/world.rst new file mode 100644 index 00000000..83d9dbd6 --- /dev/null +++ b/subprojects/d2tk/pugl/doc/c/world.rst @@ -0,0 +1,65 @@ +################ +Creating a World +################ + +.. default-domain:: c +.. highlight:: c + +The world is the top-level object which represents an instance of Pugl. +It handles the connection to the window system, +and manages views and the event loop. + +An application typically has a single world, +which is constructed once on startup and used to drive the main event loop. + +************ +Construction +************ + +A world must be created before any views, and it must outlive all of its views. +A world is created with :func:`puglNewWorld`, for example: + +.. code-block:: c + + PuglWorld* world = puglNewWorld(PUGL_PROGRAM, 0); + +For a plugin, specify :enumerator:`PUGL_MODULE <PuglWorldType.PUGL_MODULE>` instead. +In some cases, it is necessary to pass additional flags. +For example, Vulkan requires thread support: + +.. code-block:: c + + PuglWorld* world = puglNewWorld(PUGL_MODULE, PUGL_WORLD_THREADS) + +It is a good idea to set a class name for your project with :func:`puglSetClassName`. +This allows the window system to distinguish different applications and, +for example, users to set up rules to manage their windows nicely: + +.. code-block:: c + + puglSetClassName(world, "MyAwesomeProject") + +.. _setting-application-data: + +************************ +Setting Application Data +************************ + +Pugl will call an event handler in the application with only a view pointer and an event, +so there needs to be some way to access the data you use in your application. +This is done by setting an opaque handle on the world with :func:`puglSetWorldHandle`, +for example: + +.. code-block:: c + + puglSetWorldHandle(world, myApp); + +The handle can be later retrieved with :func:`puglGetWorldHandle`: + +.. code-block:: c + + MyApp* app = (MyApp*)puglGetWorldHandle(world); + +All non-constant data should be accessed via this handle, +to avoid problems associated with static mutable data. + diff --git a/subprojects/d2tk/pugl/doc/c/wscript b/subprojects/d2tk/pugl/doc/c/wscript deleted file mode 100644 index 4e0fbc92..00000000 --- a/subprojects/d2tk/pugl/doc/c/wscript +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python - -def build(bld): - dox_to_sphinx = bld.path.find_node("../../scripts/dox_to_sphinx.py") - index_xml = bld.path.get_bld().make_node("xml/index.xml") - - files = [ - ("../../resources/pugl.svg", "sphinx/_static/pugl.svg"), - ("../_static/custom.css", "sphinx/_static/custom.css"), - ("../_templates/about.html", "sphinx/_templates/about.html"), - ("../deployment.rst", "sphinx/deployment.rst"), - ("../pugl.rst", "sphinx/pugl.rst"), - ("index.rst", "sphinx/index.rst"), - ("overview.rst", "sphinx/overview.rst"), - ("reference.rst", "sphinx/reference.rst"), - ] - - # Run Doxygen to generate XML documentation - bld(features="doxygen", doxyfile="Doxyfile") - - # Substitute variables to make Sphinx configuration file - bld(features="subst", - source="../conf.py.in", - target="sphinx/conf.py", - PUGL_VERSION=bld.env.PUGL_VERSION) - - # Copy static documentation files to Sphinx build directory - for f in files: - bld(features="subst", is_copy=True, source=f[0], target=f[1]) - - # Generate Sphinx markup from Doxygen XML - bld.add_group() - bld(rule="${PYTHON} " + dox_to_sphinx.abspath() + " -f ${SRC} ${TGT}", - source=index_xml, - target="sphinx/api/") - - # Run Sphinx to generate HTML documentation - doc_dir = bld.env.DOCDIR + "/pugl-%s/" % bld.env.PUGL_MAJOR_VERSION - bld(features="sphinx", - sphinx_source=bld.path.get_bld().make_node("sphinx"), - sphinx_output_format="singlehtml", - sphinx_options=["-E", "-q"], - install_path=doc_dir + "c/singlehtml/") diff --git a/subprojects/d2tk/pugl/doc/c/xml/meson.build b/subprojects/d2tk/pugl/doc/c/xml/meson.build new file mode 100644 index 00000000..d79d59a6 --- /dev/null +++ b/subprojects/d2tk/pugl/doc/c/xml/meson.build @@ -0,0 +1,19 @@ +doxygen = find_program('doxygen') + +c_doxygen_input = [] +foreach h : c_headers + c_doxygen_input += ['..' / h] +endforeach + +config = configuration_data() +config.set('PUGL_HEADERS', ' '.join(c_doxygen_input)) +config.set('PUGL_SRCDIR', pugl_src_root) + +c_doxyfile = configure_file(configuration: config, + input: '../Doxyfile.in', + output: 'Doxyfile') + +c_index_xml = custom_target('c-index.xml', + command: [doxygen, '@INPUT0@'], + input: [c_doxyfile] + c_header_files, + output: 'index.xml') diff --git a/subprojects/d2tk/pugl/doc/conf.py.in b/subprojects/d2tk/pugl/doc/conf.py.in index 9ca7bfae..3fa8ea21 100644 --- a/subprojects/d2tk/pugl/doc/conf.py.in +++ b/subprojects/d2tk/pugl/doc/conf.py.in @@ -7,20 +7,12 @@ release = "@PUGL_VERSION@" # General configuration +exclude_patterns = ["xml"] language = "en" - -extensions = [ - # 'breathe', - # 'sphinx_rtd_theme', - # 'sphinx.ext.autodoc', - # 'sphinx.ext.doctest', - # 'sphinx.ext.napoleon', - # 'sphinx.ext.viewcode', -] - -# Enable nitpicky mode to get warnings about broken links -# Unfortunately this means we need to explicitly ignore everything external nitpicky = True +pygments_style = "friendly" + +# Ignore everything opaque or external for nitpicky mode _opaque = [ "PFN_vkGetDeviceProcAddr", "PFN_vkGetInstanceProcAddr", @@ -36,53 +28,58 @@ _opaque = [ "uint32_t", "uintptr_t", ] + _c_nitpick_ignore = map(lambda x: ("c:identifier", x), _opaque) _cpp_nitpick_ignore = map(lambda x: ("cpp:identifier", x), _opaque) nitpick_ignore = list(_c_nitpick_ignore) + list(_cpp_nitpick_ignore) -templates_path = ["_templates"] - -pygments_style = "friendly" - # C++ cpp_index_common_prefix = ["pugl::"] # HTML output -exclude_patterns = ["xml"] -html_static_path = ["_static"] - -html_theme = "alabaster" -# html_theme = "sphinx_rtd_theme" +html_copy_source = False +html_short_title = "Pugl" +html_static_path = ["../_static"] +html_theme = "sphinx_lv2_theme" -if html_theme == "alabaster": +if tags.has('singlehtml'): + html_sidebars = { + "**": [ + "globaltoc.html", + ] + } html_theme_options = { + "body_max_width": "51em", + "body_min_width": "51em", "description": "A minimal portable API for embeddable GUIs.", - "donate_url": "http://drobilla.net/pages/donate.html", - # "github_repo": "pugl", - # "github_user": "lv2", + "show_footer_version": True, + "show_logo_version": False, "logo": "pugl.svg", "logo_name": True, - "logo_text_align": "center", - "page_width": "80em - 20em", - "sidebar_width": "20em", - } - - html_sidebars = { - "**": [ - "about.html", - "localtoc.html", - "donate.html", - ] + "logo_width": "8em", + "nosidebar": False, + "page_width": "80em", + "sidebar_width": "16em", + "globaltoc_maxdepth": 3, + "globaltoc_collapse": False, } -elif html_theme == "sphinx_rtd_theme": - +else: html_theme_options = { - "sticky_navigation": False, - "collapse_navigation": False, - "navigation_depth": 4, - "display_version": True, + "body_max_width": "60em", + "body_min_width": "40em", + "description": "A minimal portable API for embeddable GUIs.", + "show_footer_version": True, + "show_logo_version": False, + "logo": "pugl.svg", + "logo_name": True, + "logo_width": "8em", + "nosidebar": True, + "page_width": "60em", + "sidebar_width": "14em", + "globaltoc_maxdepth": 1, + "globaltoc_collapse": True, } diff --git a/subprojects/d2tk/pugl/doc/cpp/Doxyfile b/subprojects/d2tk/pugl/doc/cpp/Doxyfile.in index 0f5f6360..889ac0b4 100644 --- a/subprojects/d2tk/pugl/doc/cpp/Doxyfile +++ b/subprojects/d2tk/pugl/doc/cpp/Doxyfile.in @@ -2,7 +2,7 @@ PROJECT_NAME = Pugl PROJECT_BRIEF = "A minimal portable API for embeddable GUIs" QUIET = YES -WARN_AS_ERROR = NO +WARN_AS_ERROR = YES WARN_IF_UNDOCUMENTED = NO WARN_NO_PARAMDOC = NO @@ -26,15 +26,7 @@ SHOW_FILES = NO MACRO_EXPANSION = YES PREDEFINED = PUGL_API PUGL_DISABLE_DEPRECATED PUGL_CONST_API= PUGL_CONST_FUNC= -INPUT = ../../include/pugl/cairo.h \ - ../../include/pugl/gl.h \ - ../../include/pugl/pugl.h \ - ../../include/pugl/stub.h \ - ../../include/pugl/vulkan.h \ - ../../bindings/cxx/include/pugl/cairo.hpp \ - ../../bindings/cxx/include/pugl/gl.hpp \ - ../../bindings/cxx/include/pugl/pugl.hpp \ - ../../bindings/cxx/include/pugl/stub.hpp \ - ../../bindings/cxx/include/pugl/vulkan.hpp +STRIP_FROM_PATH = @PUGL_SRCDIR@ +INPUT = @PUGL_HEADERS@ -OUTPUT_DIRECTORY = . +OUTPUT_DIRECTORY = doc/cpp diff --git a/subprojects/d2tk/pugl/doc/cpp/api/meson.build b/subprojects/d2tk/pugl/doc/cpp/api/meson.build new file mode 100644 index 00000000..4bbbec23 --- /dev/null +++ b/subprojects/d2tk/pugl/doc/cpp/api/meson.build @@ -0,0 +1,5 @@ +cpp_pugl_rst = custom_target( + 'C++ API ReST Documentation', + command: [dox_to_sphinx, '-l', 'cpp', '-f', '@INPUT@', 'doc/cpp/api'], + input: cpp_index_xml, + output: 'pugl.rst') diff --git a/subprojects/d2tk/pugl/doc/cpp/c-reference.rst b/subprojects/d2tk/pugl/doc/cpp/c-reference.rst deleted file mode 100644 index 546e4d30..00000000 --- a/subprojects/d2tk/pugl/doc/cpp/c-reference.rst +++ /dev/null @@ -1,20 +0,0 @@ -############### -C API Reference -############### - -This section contains the generated documentation for all symbols in the public -C API. -It is included here because some C++ wrapper definitions refer to the underlying C symbols, -but direct use of the C API should not be necessary in C++ applications. - -.. toctree:: - - api/status - api/world - api/view - api/events - - api/cairo - api/gl - api/stub - api/vulkan diff --git a/subprojects/d2tk/pugl/doc/cpp/cpp-reference.rst b/subprojects/d2tk/pugl/doc/cpp/cpp-reference.rst deleted file mode 100644 index 96c523c4..00000000 --- a/subprojects/d2tk/pugl/doc/cpp/cpp-reference.rst +++ /dev/null @@ -1,18 +0,0 @@ -################# -C++ API Reference -################# - -This section contains the generated documentation for all symbols in the public -C++ API. - -.. toctree:: - - api/statusxx - api/worldxx - api/viewxx - api/eventsxx - - api/cairoxx - api/glxx - api/stubxx - api/vulkanxx diff --git a/subprojects/d2tk/pugl/doc/cpp/event-loop.rst b/subprojects/d2tk/pugl/doc/cpp/event-loop.rst new file mode 100644 index 00000000..1d2ac417 --- /dev/null +++ b/subprojects/d2tk/pugl/doc/cpp/event-loop.rst @@ -0,0 +1,37 @@ +.. default-domain:: cpp +.. highlight:: cpp +.. namespace:: pugl + +###################### +Driving the Event Loop +###################### + +Pugl does not contain any threads or other event loop "magic". +For flexibility, the event loop is driven manually by repeatedly calling :func:`World::update`, +which processes events from the window system and dispatches them to views when necessary. + +The exact use of :func:`World::update` depends on the application. +Plugins typically call it with a ``timeout`` of 0 in a callback driven by the host. +This avoids blocking the main loop, +since other plugins and the host itself need to run as well. + +A program can use whatever timeout is appropriate: +event-driven applications may wait forever by using a ``timeout`` of -1, +while those that draw continuously may use a significant fraction of the frame period +(with enough time left over to render). + +********* +Redrawing +********* + +Occasional redrawing can be requested by calling :func:`View::postRedisplay` or :func:`View::postRedisplayRect`. +After these are called, +a :type:`ExposeEvent` will be dispatched on the next call to :func:`World::update`. +Note, however, that this will not wake up a blocked :func:`World::update` call on MacOS +(which does not handle drawing via events). + +For continuous redrawing, +call :func:`View::postRedisplay` while handling a :type:`UpdateEvent`. +This event is sent just before views are redrawn, +so it can be used as a hook to expand the update region right before the view is exposed. +Anything else that needs to be done every frame can be handled similarly. diff --git a/subprojects/d2tk/pugl/doc/cpp/events.rst b/subprojects/d2tk/pugl/doc/cpp/events.rst new file mode 100644 index 00000000..72c396cc --- /dev/null +++ b/subprojects/d2tk/pugl/doc/cpp/events.rst @@ -0,0 +1,43 @@ +.. default-domain:: cpp +.. highlight:: cpp +.. namespace:: pugl + +############### +Handling Events +############### + +Events are sent to a view when it has received user input, +must be drawn, or in other situations that may need to be handled such as resizing. + +Events are sent to the ``onEvent`` method that takes the matching event type. +The application must handle at least :type:`ConfigureEvent` +and :type:`ExposeEvent` to draw anything, +but there are many other :type:`event types <pugl::EventType>`. + +For example, basic event handling for our above class might look something like: + +.. code-block:: cpp + + pugl::Status + MyView::onEvent(const pugl::ConfigureEvent& event) noexcept + { + return resize(event.width, event.height); + } + + pugl::Status + MyView::onEvent(const pugl::ExposeEvent& event) noexcept + { + return drawMyAwesomeInterface(event.x, event.y, event.width, event.height); + } + +******* +Drawing +******* + +Note that Pugl uses a different drawing model than many libraries, +particularly those designed for game-style main loops like `SDL <https://libsdl.org/>`_ and `GLFW <https://www.glfw.org/>`_. + +In that style of code, drawing is performed imperatively in the main loop, +but with Pugl, the application must draw only while handling an expose event. +This is because Pugl supports event-driven applications that only draw the damaged region when necessary, +and handles exposure internally to provide optimized and consistent behavior across platforms. diff --git a/subprojects/d2tk/pugl/doc/cpp/index.rst b/subprojects/d2tk/pugl/doc/cpp/index.rst index c3d330e6..b11d0286 100644 --- a/subprojects/d2tk/pugl/doc/cpp/index.rst +++ b/subprojects/d2tk/pugl/doc/cpp/index.rst @@ -1,7 +1,12 @@ +#### +Pugl +#### + +.. include:: summary.rst + .. toctree:: - pugl deployment overview - cpp-reference - c-reference + api/pugl + api/puglxx diff --git a/subprojects/d2tk/pugl/doc/cpp/meson.build b/subprojects/d2tk/pugl/doc/cpp/meson.build new file mode 100644 index 00000000..d8bae11c --- /dev/null +++ b/subprojects/d2tk/pugl/doc/cpp/meson.build @@ -0,0 +1,43 @@ +config = configuration_data() +config.set('PUGL_VERSION', meson.project_version()) + +conf_py = configure_file(configuration: config, + input: '../conf.py.in', + output: 'conf.py') + +configure_file(copy: true, input: '../deployment.rst', output: 'deployment.rst') +configure_file(copy: true, input: '../summary.rst', output: 'summary.rst') + +cpp_rst_files = files( + 'index.rst', + 'overview.rst', + 'world.rst', + 'view.rst', + 'events.rst', + 'event-loop.rst', +) + +foreach f : cpp_rst_files + configure_file(copy: true, input: f, output: '@PLAINNAME@') +endforeach + +subdir('xml') +subdir('api') + +docs = custom_target( + 'C++ API Documentation (singlehtml)', + command: [sphinx_build, '-M', 'singlehtml', 'doc/cpp/', 'doc/cpp/', '-E', '-q', '-t', 'singlehtml'], + input: [cpp_rst_files, cpp_pugl_rst, cpp_index_xml], + output: 'singlehtml', + build_by_default: true, + install: true, + install_dir: docdir / 'puglxx-0') + +docs = custom_target( + 'C++ API Documentation (html)', + command: [sphinx_build, '-M', 'html', 'doc/cpp/', 'doc/cpp/', '-E', '-q', '-t', 'html'], + input: [cpp_rst_files, cpp_pugl_rst, cpp_index_xml], + output: 'html', + build_by_default: true, + install: true, + install_dir: docdir / 'puglxx-0') diff --git a/subprojects/d2tk/pugl/doc/cpp/overview.rst b/subprojects/d2tk/pugl/doc/cpp/overview.rst index 5fffe37c..1928fbac 100644 --- a/subprojects/d2tk/pugl/doc/cpp/overview.rst +++ b/subprojects/d2tk/pugl/doc/cpp/overview.rst @@ -2,6 +2,10 @@ .. highlight:: cpp .. namespace:: pugl +######## +Overview +######## + Pugl is a C library, but the bindings documented here provide a more idiomatic and type-safe API for C++. If you would rather use C, @@ -23,401 +27,9 @@ The API revolves around two main objects: the `world` and the `view`. An application creates a world to manage top-level state, then creates one or more views to display. -Creating a World -================ - -The world is the top-level object which represents an instance of Pugl. -It handles the connection to the window system, -and manages views and the event loop. - -An application typically has a single world, -which is constructed once on startup and used to drive the main event loop. - -Construction ------------- - -A world must be created before any views, and it must outlive all of its views. -The world constructor requires an argument to specify the application type: - -.. code-block:: cpp - - pugl::World world{pugl::WorldType::program}; - -For a plugin, specify :enumerator:`WorldType::module` instead. -In some cases, it is necessary to pass additional flags. -For example, Vulkan requires thread support: - -.. code-block:: cpp - - pugl::World world{pugl::WorldType::program, pugl::WorldFlag::threads}; - -It is a good idea to set a class name for your project with :func:`World::setClassName`. -This allows the window system to distinguish different applications and, -for example, users to set up rules to manage their windows nicely: - -.. code-block:: cpp - - world.setClassName("MyAwesomeProject"); - -Creating a View -=============== - -A `view` is a drawable region that receives events. -You may think of it as a window, -though it may be embedded and not represent a top-level system window. [#f1]_ - -Pugl communicates with views by dispatching events. -For flexibility, the event handler can be a different object than the view. -This allows using :class:`View` along with a separate event handler class. -Alternatively, a view class can inherit from :class:`View` and set itself as its event handler, -for a more object-oriented style. - -This documentation will use the latter approach, -so we will define a class for our view that contains everything needed: - -.. code-block:: cpp - - class MyView : public pugl::View - { - public: - explicit MyView(pugl::World& world) - : pugl::View{world} - { - setEventHandler(*this); - } - - pugl::Status onEvent(const pugl::ConfigureEvent& event) noexcept; - pugl::Status onEvent(const pugl::ExposeEvent& event) noexcept; - - // With other handlers here as needed... - - // Fallback handler for all other events - template<PuglEventType t, class Base> - pugl::Status onEvent(const pugl::Event<t, Base>&) noexcept - { - return pugl::Status::success; - } - - private: - // Some data... - }; - -Pugl will call an ``onEvent`` method of the event handler (the view in this case) for every event. - -Note that Pugl uses a static dispatching mechanism rather than virtual functions to minimize overhead. -It is therefore necessary for the final class to define a handler for every event type. -A terse way to do this without writing every implementation is to define a fallback handler as a template, -as in the example above. -Alternatively, you can define an explicit handler for each event that simply returns :enumerator:`Status::success`. -This way, it will be a compile error if any event is not explicitly handled. - -Configuring the Frame ---------------------- - -Before display, -the necessary :doc:`frame <api/frame>` and :doc:`window <api/window>` attributes should be set. -These allow the window system (or plugin host) to arrange the view properly. - -Derived classes can configure themselves during construction, -but we assume here that configuration is being done outside the view. -For example: - -.. code-block:: cpp - - const double defaultWidth = 1920.0; - const double defaultHeight = 1080.0; - - view.setWindowTitle("My Window"); - view.setDefaultSize(defaultWidth, defaultHeight); - view.setMinSize(defaultWidth / 4.0, defaultHeight / 4.0); - view.setAspectRatio(1, 1, 16, 9); - -There are also several :type:`hints <PuglViewHint>` for basic attributes that can be set: - -.. code-block:: cpp - - view.setHint(pugl::ViewHint::resizable, true); - view.setHint(pugl::ViewHint::ignoreKeyRepeat, true); - -Embedding ---------- - -To embed the view in another window, -you will need to somehow get the :type:`native view handle <pugl::NativeView>` for the parent, -then set it with :func:`View::setParentWindow`. -If the parent is a Pugl view, -the native handle can be accessed with :func:`View::nativeWindow`. -For example: - -.. code-block:: cpp - - view.setParentWindow(view, parent.getNativeWindow()); - -Setting a Backend ------------------ - -Before being realized, the view must have a backend set with :func:`View::setBackend`. - -The backend manages the graphics API that will be used for drawing. -Pugl includes backends and supporting API for -:doc:`Cairo <api/cairo>`, :doc:`OpenGL <api/gl>`, and :doc:`Vulkan <api/vulkan>`. - -Using Cairo -^^^^^^^^^^^ - -Cairo-specific API is declared in the ``cairo.hpp`` header: - -.. code-block:: cpp - - #include <pugl/cairo.hpp> - -The Cairo backend is provided by :func:`cairoBackend()`: - -.. code-block:: cpp - - view.setBackend(pugl::cairoBackend()); - -No additional configuration is required for Cairo. -To draw when handling an expose event, -the `Cairo context <https://www.cairographics.org/manual/cairo-cairo-t.html>`_ can be accessed with :func:`View::context`: - -.. code-block:: cpp - - cairo_t* cr = static_cast<cairo_t*>(view.context()); - -Using OpenGL -^^^^^^^^^^^^ - -OpenGL-specific API is declared in the ``gl.hpp`` header: - -.. code-block:: cpp - - #include <pugl/gl.hpp> - -The OpenGL backend is provided by :func:`glBackend()`: - -.. code-block:: cpp - - view.setBackend(pugl::glBackend()); - -Some hints must also be set so that the context can be set up correctly. -For example, to use OpenGL 3.3 Core Profile: - -.. code-block:: cpp - - view.setHint(pugl::ViewHint::useCompatProfile, false); - view.setHint(pugl::ViewHint::contextVersionMajor, 3); - view.setHint(pugl::ViewHint::contextVersionMinor, 3); - -If you need to perform some setup using the OpenGL API, -there are two ways to do so. - -The OpenGL context is active when -:type:`CreateEvent` and -:type:`DestroyEvent` -events are dispatched, -so things like creating and destroying shaders and textures can be done then. - -Alternatively, if it is cumbersome to set up and tear down OpenGL in the event handler, -:func:`enterContext` and :func:`leaveContext` can be used to manually activate the OpenGL context during application setup. -Note, however, that unlike many other APIs, these functions must not be used for drawing. -It is only valid to use the OpenGL API for configuration in a manually entered context, -rendering will not work. -For example: - -.. code-block:: cpp - - pugl::enterContext(view); - myApp.setupOpenGL(); - pugl::leaveContext(view); - - while (!myApp.quit()) { - world.update(0.0); - } - - pugl::enterContext(view); - myApp.teardownOpenGL(); - pugl::leaveContext(view); - -Using Vulkan -^^^^^^^^^^^^ - -Vulkan-specific API is declared in the ``vulkan.hpp`` header. -This header includes Vulkan headers, -so if you are dynamically loading Vulkan at runtime, -you should define ``VK_NO_PROTOTYPES`` before including it. - -.. code-block:: cpp - - #define VK_NO_PROTOTYPES - - #include <pugl/vulkan.hpp> - -The Vulkan backend is provided by :func:`vulkanBackend()`: - -.. code-block:: cpp - - view.setBackend(pugl::vulkanBackend()); - -Unlike OpenGL, almost all Vulkan configuration is done using the Vulkan API directly. -Pugl only provides a portable mechanism to load the Vulkan library and get the functions used to load the rest of the Vulkan API. - -Loading Vulkan -^^^^^^^^^^^^^^ - -For maximum compatibility, -it is best to not link to Vulkan at compile-time, -but instead load the Vulkan API at run-time. -To do so, first create a :class:`VulkanLoader`: - -.. code-block:: cpp - - pugl::VulkanLoader loader{world}; - -The loader manages the dynamically loaded Vulkan library, -so it must be kept alive for as long as the application is using Vulkan. -You can get the function used to load Vulkan functions with :func:`VulkanLoader::getInstanceProcAddrFunc`: - -.. code-block:: cpp - - auto vkGetInstanceProcAddr = loader.getInstanceProcAddrFunc(); - -It is best to use this function to load everything at run time, -rather than link to the Vulkan library at run time. -You can, for example, pass this to get the ``vkCreateInstance`` function using this, -then use that to create your Vulkan instance. -In practice, you will want to use some loader or wrapper API since there are many Vulkan functions. - -It is not necessary to use :class:`VulkanLoader`, -you can, for example, use the ``DynamicLoader`` from ``vulkan.hpp`` in the Vulkan SDK instead. - -The details of using Vulkan are far beyond the scope of this documentation, -but Pugl provides a portable function, :func:`createSurface`, -to get the Vulkan surface for a view. -Assuming you have somehow created your ``VkInstance``, -you can get the surface for a view using :func:`createSurface`: - -.. code-block:: cpp - - VkSurfaceKHR* surface = nullptr; - puglCreateSurface(loader.getDeviceProcAddrFunc(), - view, - vulkanInstance, - nullptr, - &surface); - -Pugl does not provide API that uses ``vulkan.hpp`` to avoid the onerous dependency, -but if you are using it with exceptions and unique handles, -it is straightforward to wrap the surface handle yourself. - -Showing the View ----------------- - -Once the view is configured, it can be "realized" with :func:`View::realize`. -This creates a "real" system view, for example: - -.. code-block:: cpp - - pugl::Status status = view.realize(); - if (status != pugl::Status::success) { - std::cerr << "Error realizing view: " << pugl::strerror(status) << "\n"; - } - -Note that realizing a view can fail for many reasons, -so the return code should always be checked. -This is generally the case for any function that interacts with the window system. -Most functions also return a :enum:`Status`, -but these checks are omitted for brevity in the rest of this documentation. - -A realized view is not initially visible, -but can be shown with :func:`View::show`: - -.. code-block:: cpp - - view.show(); - -To create an initially visible view, -it is also possible to simply call :func:`View::show()` right away. -The view will be automatically realized if necessary. - -Handling Events -=============== - -Events are sent to a view when it has received user input, -must be drawn, or in other situations that may need to be handled such as resizing. - -Events are sent to the ``onEvent`` method that takes the matching event type. -The application must handle at least :type:`ConfigureEvent` -and :type:`ExposeEvent` to draw anything, -but there are many other :type:`event types <pugl::EventType>`. - -For example, basic event handling for our above class might look something like: - -.. code-block:: cpp - - pugl::Status - MyView::onEvent(const pugl::ConfigureEvent& event) noexcept - { - return resize(event.width, event.height); - } - - pugl::Status - MyView::onEvent(const pugl::ExposeEvent& event) noexcept - { - return drawMyAwesomeInterface(event.x, event.y, event.width, event.height); - } - -Drawing -------- - -Note that Pugl uses a different drawing model than many libraries, -particularly those designed for game-style main loops like `SDL <https://libsdl.org/>`_ and `GLFW <https://www.glfw.org/>`_. - -In that style of code, drawing is performed imperatively in the main loop, -but with Pugl, the application must draw only while handling an expose event. -This is because Pugl supports event-driven applications that only draw the damaged region when necessary, -and handles exposure internally to provide optimized and consistent behavior across platforms. - -Driving the Event Loop -====================== - -Pugl does not contain any threads or other event loop "magic". -For flexibility, the event loop is driven manually by repeatedly calling :func:`World::update`, -which processes events from the window system and dispatches them to views when necessary. - -The exact use of :func:`World::update` depends on the application. -Plugins typically call it with a ``timeout`` of 0 in a callback driven by the host. -This avoids blocking the main loop, -since other plugins and the host itself need to run as well. - -A program can use whatever timeout is appropriate: -event-driven applications may wait forever by using a ``timeout`` of -1, -while those that draw continuously may use a significant fraction of the frame period -(with enough time left over to render). - -Redrawing ---------- - -Occasional redrawing can be requested by calling :func:`View::postRedisplay` or :func:`View::postRedisplayRect`. -After these are called, -a :type:`ExposeEvent` will be dispatched on the next call to :func:`World::update`. -Note, however, that this will not wake up a blocked :func:`World::update` call on MacOS -(which does not handle drawing via events). - -For continuous redrawing, -call :func:`View::postRedisplay` while handling a :type:`UpdateEvent`. -This event is sent just before views are redrawn, -so it can be used as a hook to expand the update region right before the view is exposed. -Anything else that needs to be done every frame can be handled similarly. - -.. _pkg-config: https://www.freedesktop.org/wiki/Software/pkg-config/ - -.. rubric:: Footnotes +.. toctree:: -.. [#f1] MacOS has a strong distinction between - `views <https://developer.apple.com/documentation/appkit/nsview>`_, - which may be nested, and - `windows <https://developer.apple.com/documentation/appkit/nswindow>`_, - which may not. - On Windows and X11, everything is a nestable window, - but top-level windows are configured differently. + world + view + events + event-loop diff --git a/subprojects/d2tk/pugl/doc/cpp/view.rst b/subprojects/d2tk/pugl/doc/cpp/view.rst new file mode 100644 index 00000000..3f5aee8a --- /dev/null +++ b/subprojects/d2tk/pugl/doc/cpp/view.rst @@ -0,0 +1,299 @@ +.. default-domain:: cpp +.. highlight:: cpp +.. namespace:: pugl + +############### +Creating a View +############### + +A `view` is a drawable region that receives events. +You may think of it as a window, +though it may be embedded and not represent a top-level system window. [#f1]_ + +Pugl communicates with views by dispatching events. +For flexibility, the event handler can be a different object than the view. +This allows using :class:`View` along with a separate event handler class. +Alternatively, a view class can inherit from :class:`View` and set itself as its event handler, +for a more object-oriented style. + +This documentation will use the latter approach, +so we will define a class for our view that contains everything needed: + +.. code-block:: cpp + + class MyView : public pugl::View + { + public: + explicit MyView(pugl::World& world) + : pugl::View{world} + { + setEventHandler(*this); + } + + pugl::Status onEvent(const pugl::ConfigureEvent& event) noexcept; + pugl::Status onEvent(const pugl::ExposeEvent& event) noexcept; + + // With other handlers here as needed... + + // Fallback handler for all other events + template<PuglEventType t, class Base> + pugl::Status onEvent(const pugl::Event<t, Base>&) noexcept + { + return pugl::Status::success; + } + + private: + // Some data... + }; + +Pugl will call an ``onEvent`` method of the event handler (the view in this case) for every event. + +Note that Pugl uses a static dispatching mechanism rather than virtual functions to minimize overhead. +It is therefore necessary for the final class to define a handler for every event type. +A terse way to do this without writing every implementation is to define a fallback handler as a template, +as in the example above. +Alternatively, you can define an explicit handler for each event that simply returns :enumerator:`Status::success`. +This way, it will be a compile error if any event is not explicitly handled. + +********************* +Configuring the Frame +********************* + +Before display, +the necessary :doc:`frame <api/frame>` and :doc:`window <api/window>` attributes should be set. +These allow the window system (or plugin host) to arrange the view properly. + +Derived classes can configure themselves during construction, +but we assume here that configuration is being done outside the view. +For example: + +.. code-block:: cpp + + const double defaultWidth = 1920.0; + const double defaultHeight = 1080.0; + + view.setWindowTitle("My Window"); + view.setDefaultSize(defaultWidth, defaultHeight); + view.setMinSize(defaultWidth / 4.0, defaultHeight / 4.0); + view.setAspectRatio(1, 1, 16, 9); + +There are also several :type:`hints <PuglViewHint>` for basic attributes that can be set: + +.. code-block:: cpp + + view.setHint(pugl::ViewHint::resizable, true); + view.setHint(pugl::ViewHint::ignoreKeyRepeat, true); + +********* +Embedding +********* + +To embed the view in another window, +you will need to somehow get the :type:`native view handle <pugl::NativeView>` for the parent, +then set it with :func:`View::setParentWindow`. +If the parent is a Pugl view, +the native handle can be accessed with :func:`View::nativeWindow`. +For example: + +.. code-block:: cpp + + view.setParentWindow(view, parent.getNativeWindow()); + +***************** +Setting a Backend +***************** + +Before being realized, the view must have a backend set with :func:`View::setBackend`. + +The backend manages the graphics API that will be used for drawing. +Pugl includes backends and supporting API for +:doc:`Cairo <api/cairo>`, :doc:`OpenGL <api/gl>`, and :doc:`Vulkan <api/vulkan>`. + +Using Cairo +=========== + +Cairo-specific API is declared in the ``cairo.hpp`` header: + +.. code-block:: cpp + + #include <pugl/cairo.hpp> + +The Cairo backend is provided by :func:`cairoBackend()`: + +.. code-block:: cpp + + view.setBackend(pugl::cairoBackend()); + +No additional configuration is required for Cairo. +To draw when handling an expose event, +the `Cairo context <https://www.cairographics.org/manual/cairo-cairo-t.html>`_ can be accessed with :func:`View::context`: + +.. code-block:: cpp + + cairo_t* cr = static_cast<cairo_t*>(view.context()); + +Using OpenGL +============ + +OpenGL-specific API is declared in the ``gl.hpp`` header: + +.. code-block:: cpp + + #include <pugl/gl.hpp> + +The OpenGL backend is provided by :func:`glBackend()`: + +.. code-block:: cpp + + view.setBackend(pugl::glBackend()); + +Some hints must also be set so that the context can be set up correctly. +For example, to use OpenGL 3.3 Core Profile: + +.. code-block:: cpp + + view.setHint(pugl::ViewHint::useCompatProfile, false); + view.setHint(pugl::ViewHint::contextVersionMajor, 3); + view.setHint(pugl::ViewHint::contextVersionMinor, 3); + +If you need to perform some setup using the OpenGL API, +there are two ways to do so. + +The OpenGL context is active when +:type:`CreateEvent` and +:type:`DestroyEvent` +events are dispatched, +so things like creating and destroying shaders and textures can be done then. + +Alternatively, if it is cumbersome to set up and tear down OpenGL in the event handler, +:func:`enterContext` and :func:`leaveContext` can be used to manually activate the OpenGL context during application setup. +Note, however, that unlike many other APIs, these functions must not be used for drawing. +It is only valid to use the OpenGL API for configuration in a manually entered context, +rendering will not work. +For example: + +.. code-block:: cpp + + pugl::enterContext(view); + myApp.setupOpenGL(); + pugl::leaveContext(view); + + while (!myApp.quit()) { + world.update(0.0); + } + + pugl::enterContext(view); + myApp.teardownOpenGL(); + pugl::leaveContext(view); + +Using Vulkan +============ + +Vulkan-specific API is declared in the ``vulkan.hpp`` header. +This header includes Vulkan headers, +so if you are dynamically loading Vulkan at runtime, +you should define ``VK_NO_PROTOTYPES`` before including it. + +.. code-block:: cpp + + #define VK_NO_PROTOTYPES + + #include <pugl/vulkan.hpp> + +The Vulkan backend is provided by :func:`vulkanBackend()`: + +.. code-block:: cpp + + view.setBackend(pugl::vulkanBackend()); + +Unlike OpenGL, almost all Vulkan configuration is done using the Vulkan API directly. +Pugl only provides a portable mechanism to load the Vulkan library and get the functions used to load the rest of the Vulkan API. + +Loading Vulkan +-------------- + +For maximum compatibility, +it is best to not link to Vulkan at compile-time, +but instead load the Vulkan API at run-time. +To do so, first create a :class:`VulkanLoader`: + +.. code-block:: cpp + + pugl::VulkanLoader loader{world}; + +The loader manages the dynamically loaded Vulkan library, +so it must be kept alive for as long as the application is using Vulkan. +You can get the function used to load Vulkan functions with :func:`VulkanLoader::getInstanceProcAddrFunc`: + +.. code-block:: cpp + + auto vkGetInstanceProcAddr = loader.getInstanceProcAddrFunc(); + +It is best to use this function to load everything at run time, +rather than link to the Vulkan library at run time. +You can, for example, pass this to get the ``vkCreateInstance`` function using this, +then use that to create your Vulkan instance. +In practice, you will want to use some loader or wrapper API since there are many Vulkan functions. + +It is not necessary to use :class:`VulkanLoader`, +you can, for example, use the ``DynamicLoader`` from ``vulkan.hpp`` in the Vulkan SDK instead. + +The details of using Vulkan are far beyond the scope of this documentation, +but Pugl provides a portable function, :func:`createSurface`, +to get the Vulkan surface for a view. +Assuming you have somehow created your ``VkInstance``, +you can get the surface for a view using :func:`createSurface`: + +.. code-block:: cpp + + VkSurfaceKHR* surface = nullptr; + puglCreateSurface(loader.getDeviceProcAddrFunc(), + view, + vulkanInstance, + nullptr, + &surface); + +Pugl does not provide API that uses ``vulkan.hpp`` to avoid the onerous dependency, +but if you are using it with exceptions and unique handles, +it is straightforward to wrap the surface handle yourself. + +**************** +Showing the View +**************** + +Once the view is configured, it can be "realized" with :func:`View::realize`. +This creates a "real" system view, for example: + +.. code-block:: cpp + + pugl::Status status = view.realize(); + if (status != pugl::Status::success) { + std::cerr << "Error realizing view: " << pugl::strerror(status) << "\n"; + } + +Note that realizing a view can fail for many reasons, +so the return code should always be checked. +This is generally the case for any function that interacts with the window system. +Most functions also return a :enum:`Status`, +but these checks are omitted for brevity in the rest of this documentation. + +A realized view is not initially visible, +but can be shown with :func:`View::show`: + +.. code-block:: cpp + + view.show(); + +To create an initially visible view, +it is also possible to simply call :func:`View::show()` right away. +The view will be automatically realized if necessary. + +.. rubric:: Footnotes + +.. [#f1] MacOS has a strong distinction between + `views <https://developer.apple.com/documentation/appkit/nsview>`_, + which may be nested, and + `windows <https://developer.apple.com/documentation/appkit/nswindow>`_, + which may not. + On Windows and X11, everything is a nestable window, + but top-level windows are configured differently. diff --git a/subprojects/d2tk/pugl/doc/cpp/world.rst b/subprojects/d2tk/pugl/doc/cpp/world.rst new file mode 100644 index 00000000..1a3b432d --- /dev/null +++ b/subprojects/d2tk/pugl/doc/cpp/world.rst @@ -0,0 +1,41 @@ +.. default-domain:: cpp +.. highlight:: cpp +.. namespace:: pugl + +################ +Creating a World +################ + +The world is the top-level object which represents an instance of Pugl. +It handles the connection to the window system, +and manages views and the event loop. + +An application typically has a single world, +which is constructed once on startup and used to drive the main event loop. + +************ +Construction +************ + +A world must be created before any views, and it must outlive all of its views. +The world constructor requires an argument to specify the application type: + +.. code-block:: cpp + + pugl::World world{pugl::WorldType::program}; + +For a plugin, specify :enumerator:`WorldType::module` instead. +In some cases, it is necessary to pass additional flags. +For example, Vulkan requires thread support: + +.. code-block:: cpp + + pugl::World world{pugl::WorldType::program, pugl::WorldFlag::threads}; + +It is a good idea to set a class name for your project with :func:`World::setClassName`. +This allows the window system to distinguish different applications and, +for example, users to set up rules to manage their windows nicely: + +.. code-block:: cpp + + world.setClassName("MyAwesomeProject"); diff --git a/subprojects/d2tk/pugl/doc/cpp/wscript b/subprojects/d2tk/pugl/doc/cpp/wscript deleted file mode 100644 index a7aefc1f..00000000 --- a/subprojects/d2tk/pugl/doc/cpp/wscript +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python - -def build(bld): - dox_to_sphinx = bld.path.find_node("../../scripts/dox_to_sphinx.py") - index_xml = bld.path.get_bld().make_node("xml/index.xml") - - files = [ - ("../../resources/pugl.svg", "sphinx/_static/pugl.svg"), - ("../_static/custom.css", "sphinx/_static/custom.css"), - ("../_templates/about.html", "sphinx/_templates/about.html"), - ("../deployment.rst", "sphinx/deployment.rst"), - ("../pugl.rst", "sphinx/pugl.rst"), - ("c-reference.rst", "sphinx/c-reference.rst"), - ("cpp-reference.rst", "sphinx/cpp-reference.rst"), - ("index.rst", "sphinx/index.rst"), - ("overview.rst", "sphinx/overview.rst"), - ] - - # Run Doxygen to generate XML documentation - bld(features="doxygen", doxyfile="Doxyfile") - - # Substitute variables to make Sphinx configuration file - bld(features="subst", - source="../conf.py.in", - target="sphinx/conf.py", - PUGL_VERSION=bld.env.PUGL_VERSION) - - # Copy static documentation files to Sphinx build directory - for f in files: - bld(features="subst", is_copy=True, source=f[0], target=f[1]) - - # Generate Sphinx markup from Doxygen XML - bld.add_group() - bld(rule="${PYTHON} " + dox_to_sphinx.abspath() + " -l cpp -f ${SRC} ${TGT}", - source=index_xml, - target="sphinx/api/") - - # Run Sphinx to generate HTML documentation - doc_dir = bld.env.DOCDIR + "/pugl-%s/" % bld.env.PUGL_MAJOR_VERSION - bld(features="sphinx", - sphinx_source=bld.path.get_bld().make_node("sphinx"), - sphinx_output_format="singlehtml", - sphinx_options=["-E", "-q"], - install_path=doc_dir + "cpp/singlehtml/") diff --git a/subprojects/d2tk/pugl/doc/cpp/xml/meson.build b/subprojects/d2tk/pugl/doc/cpp/xml/meson.build new file mode 100644 index 00000000..3f87f2ac --- /dev/null +++ b/subprojects/d2tk/pugl/doc/cpp/xml/meson.build @@ -0,0 +1,21 @@ +doxygen = find_program('doxygen') + +cpp_doxygen_input = [] +foreach h : c_headers + cpp_headers + cpp_doxygen_input += ['..' / h] +endforeach + +config = configuration_data() +config.set('PUGL_HEADERS', ' '.join(cpp_doxygen_input)) +config.set('PUGL_SRCDIR', pugl_src_root) + +cpp_doxyfile = configure_file(configuration: config, + input: '../Doxyfile.in', + output: 'Doxyfile') + +cpp_index_xml = custom_target( + 'cpp-index.xml', + command: [doxygen, '@INPUT0@'], + input: [cpp_doxyfile] + c_header_files + cpp_header_files, + output: 'index.xml') + diff --git a/subprojects/d2tk/pugl/doc/deployment.rst b/subprojects/d2tk/pugl/doc/deployment.rst index ffbeae93..4afc51ad 100644 --- a/subprojects/d2tk/pugl/doc/deployment.rst +++ b/subprojects/d2tk/pugl/doc/deployment.rst @@ -1,13 +1,13 @@ -########## -Using Pugl -########## +##### +Usage +##### -Pugl is designed for flexible deployment, -so the exact method of building against it depends on your approach. +********************* +Building Against Pugl +********************* -When targeting systems with pkg-config_, -packages are provided that link against the core platform library and the desired backend, -along with any backend dependencies: +When Pugl is installed, +pkg-config_ packages are provided that link with the core platform library and desired backend: - ``pugl-cairo-0`` - ``pugl-gl-0`` diff --git a/subprojects/d2tk/pugl/doc/mainpage.md b/subprojects/d2tk/pugl/doc/mainpage.md deleted file mode 100644 index 92f7409b..00000000 --- a/subprojects/d2tk/pugl/doc/mainpage.md +++ /dev/null @@ -1,77 +0,0 @@ -This is the documentation for Pugl, a minimal API for writing GUIs. - -## Reference - -Pugl is implemented in C, but also provides a header-only C++ API wrapper. - - * [C API reference](@ref pugl) - * [C++ API reference](@ref puglxx) - -## Overview - -The Pugl API revolves around two main objects: the World and the View. -An application creates a single world to manage top-level state, -then creates one or more views to display. - -### World - -The [World](@ref PuglWorld) contains all top-level state, -and manages views and the event loop. - -A world must be [created](@ref puglNewWorld) before any views, -and it must outlive all views. - -### View - -A [View](@ref PuglView) is a drawable region that receives events. - -Creating a visible view is a multi-step process. -When a new view is [created](@ref puglNewView), -it does not yet represent a real system view or window. -To display, it must first have a [backend](@ref puglSetBackend) -and [event handler](@ref puglSetEventFunc) set, -and be configured by [setting hints](@ref puglSetViewHint) -and optionally [adjusting the frame](@ref frame). - -The [Backend](@ref PuglBackend) controls drawing for a view. -Pugl includes [Cairo](@ref cairo), [OpenGL](@ref gl), and [Vulkan](@ref vulkan) backends, -as well as a [stub](@ref stub) backend that creates a native window with no portable drawing context. - -Once the view is configured, -it can be [realized](@ref puglRealize) and [shown](@ref puglShow). -By default a view will correspond to a top-level system window. -To create a view within another window, -it must have a [parent window set](@ref puglSetParentWindow) before being created. - -### Events - -[Events](@ref PuglEvent) are sent to a view when it has received user input or must be drawn. - -Events are handled by the [event handler](@ref PuglEventFunc) set during initialisation. -This function is called whenever something happens that the view must respond to. -This includes user interaction like mouse and keyboard input, -and system events like window resizing and exposure. - -### Event Loop - -The event loop is driven by repeatedly calling #puglUpdate which processes events from the window system, -and dispatches them to views when necessary. - -Typically, a plugin calls #puglUpdate with timeout 0 in some callback driven by the host. -A program can use whatever timeout is appropriate: -event-driven applications may wait forever, -or for continuous animation, -use a timeout that is a significant fraction of the frame period -(with enough time left over to render). - -Redrawing can be requested by calling #puglPostRedisplay or #puglPostRedisplayRect, -which post expose events to the queue. -Note, however, that this will not wake up a blocked #puglUpdate call on MacOS -(which does not handle drawing via events). -For continuous redrawing, call #puglPostRedisplay when a #PUGL_UPDATE event is received. -This event is sent before views are redrawn, -so can be used as a hook to expand the update region right before the view is exposed. - -### Error Handling - -Most functions return a [Status](@ref PuglStatus) which should be checked to detect failure. diff --git a/subprojects/d2tk/pugl/doc/meson.build b/subprojects/d2tk/pugl/doc/meson.build new file mode 100644 index 00000000..583f09da --- /dev/null +++ b/subprojects/d2tk/pugl/doc/meson.build @@ -0,0 +1,13 @@ +docdir = get_option('datadir') / 'doc' + +doxygen = find_program('doxygen', required: get_option('docs')) +dox_to_sphinx = find_program('../scripts/dox_to_sphinx.py') +sphinx_build = find_program('sphinx-build', required: get_option('docs')) + +build_docs = doxygen.found() and sphinx_build.found() + +if build_docs + subdir('_static') + subdir('c') + subdir('cpp') +endif diff --git a/subprojects/d2tk/pugl/doc/pugl.rst b/subprojects/d2tk/pugl/doc/summary.rst index c48021bc..f05515fb 100644 --- a/subprojects/d2tk/pugl/doc/pugl.rst +++ b/subprojects/d2tk/pugl/doc/summary.rst @@ -1,7 +1,3 @@ -#### -Pugl -#### - Pugl is an API for writing portable and embeddable GUIs. Pugl is not a toolkit or framework, but a minimal portability layer that sets up a drawing context and delivers events. |