std1.0.0-beta-7 SafeC docs

Source gui/gui_backend.h

View in the repository

#pragma once
// SafeC Standard Library — GUI: the windowing-backend interface.
//
// Everything platform-specific in std/gui sits behind one table of
// function pointers, struct GuiBackend below. The portable layer
// (gui_window.sc: GuiWindow, the clipboard, the clock; gui_app.sc: the
// event loop) calls only through it, and a backend calls back only into
// the few gui_backend_*() functions declared here. A backend is therefore
// one .sc file that fills in a GuiBackend and never touches the canvas,
// text or widget code — those only ever see a GuiWindow's pixel buffer.
//
// Backends in std/gui today:
//   gui_x11.sc      Xlib (Linux, BSD)          GUI_NATIVE_X11
//   gui_wayland.sc  Wayland (Linux, dlopen)    GUI_NATIVE_WAYLAND
//   gui_win32.sc    Win32 (user32/gdi32)       GUI_NATIVE_WIN32
//   gui_cocoa.sc    AppKit via the ObjC runtime GUI_NATIVE_COCOA
//   gui_web_wasm.sc <canvas> (wasm32/wasm64)   GUI_NATIVE_WEB
//   gui_fb.sc       bare-metal framebuffer     GUI_NATIVE_FB
//   gui_headless.sc in memory (any target)     GUI_NATIVE_HEADLESS
// and the GPU presenters (gui_gpu.h): gui_gpu_gl_x11.sc (EGL/OpenGL ES on
// X11 and Wayland), gui_gpu_metal_cocoa.sc, gui_gpu_d3d11_win32.sc,
// gui_gpu_web_wasm.sc (WebGL).
//
// ── Choosing a backend ────────────────────────────────────────────────────
// Each platform file defines gui_platform_backend(); safeguard links the
// one whose filename suffix matches the target (safeguard/src/
// StdModules.cpp, stdVariantMatches: _x11 on Linux/BSD, _win32 on Windows,
// _cocoa on macOS, _wasm on wasm32/wasm64, _fb on bare metal). On Linux the
// X11 file's gui_platform_backend() picks Wayland instead at run time
// ($SAFEC_GUI_BACKEND, else $WAYLAND_DISPLAY: see gui_wayland.h), and every
// hosted platform's honors SAFEC_GUI_BACKEND=headless. gui_backend() returns the backend in
// use: the one installed with gui_set_backend() if any, else
// gui_platform_backend(). gui_set_backend() works only while no window
// exists — a program (or a backend module's own setup call, like
// gui_use_headless()) picks its backend before creating the first window.
//
// So a new backend is added one of two ways:
//   * As the platform's default: a suffix rule in stdVariantMatches and a
//     gui_<...>_<suffix>.sc defining gui_platform_backend() (as
//     gui_web_wasm.sc does for "_wasm"; name the header it implements with
//     '// safeguard: implements <std/gui/gui.h>' if its stem matches another).
//   * As an alternative on a platform that already has a default — as
//     gui_wayland.sc is next to X11: it defines gui_wayland_backend() (NOT
//     gui_platform_backend(), or both would define it) and
//     gui_use_wayland(), and the X11 file's gui_platform_backend() prefers
//     it at run time. A library such an alternative needs is best
//     dlopen()ed (the binary still starts without it), its package named
//     with '// safeguard: deb-recommends linux ...' (the .deb's
//     Recommends) rather than '// safeguard: link' / 'deb'.
//
// ── The contract ──────────────────────────────────────────────────────────
// Threading: every entry is called on the thread that created the first
// window ("the UI thread"), except wake(), which may be called from any
// thread.
//
// init()       Called once, lazily, before the first window_create (or the
//              first clipboard/clock call). Connect to the display server.
//              Return 1, or 0 if there is none (then every window creation
//              fails cleanly: GuiWindow.platform stays null).
// window_create(st, opts)
//              Create and (if opts.visible) show a window whose client area
//              is opts.width x opts.height logical units, honoring the
//              other options as far as the platform can. Before returning
//              1, set:
//                st.key       a nonzero value identifying the native window
//                             (X11 Window id, HWND, NSWindow*), used with
//                             gui_backend_find() to route native events;
//                st.native    the backend's own per-window data, if any;
//                st.scale     buffer pixels per logical unit (1.0 unless
//                             opts.hiDpi and the display is high-DPI);
//                st.pixelW/H  the buffer size in pixels (logical * scale);
//                st.x/st.y    the position if known (else GUI_POS_DEFAULT).
//              The portable layer allocates the pixel buffer itself.
// window_destroy(st)
//              Destroy the native window and free st.native. Events still
//              queued for it are discarded by the caller.
// window_present(st, pixels, bufW, bufH, x, y, w, h)
//              Show the rectangle (x,y,w,h) of 'pixels' (premultiplied
//              RGBA8888, R first in memory, bufW*4 bytes per row) in the
//              window. Convert to the native pixel order here (X11 and
//              Win32 want BGRA; Cocoa takes RGBA). The buffer may be
//              smaller or larger than the window for a moment around a
//              resize; present what overlaps.
// window_set_title / window_set_size (logical) / window_set_position
//              (logical) / window_set_visible / window_focus /
//              window_set_cursor (GUI_CURSOR_*) / window_update_limits
//              (st.minW/minH/maxW/maxH changed, logical units, 0 = none)
//              Apply the change. A resize the window system carries out is
//              reported back like a user resize: post GUI_EVENT_RESIZE.
// pump(timeoutMs)
//              Read every pending native event and post the resulting
//              GuiEvents with gui_backend_post(). If none was pending, wait
//              up to timeoutMs for one (0 = don't wait, negative = until an
//              event or wake()). Return 1 if anything was posted.
//              The events to post, all in buffer pixels:
//                CLOSE (the close button; do NOT destroy the window — the
//                application decides), RESIZE (width/height = new buffer
//                size; also update st.pixelW/H, st.logicalW/H), MOVE,
//                FOCUS_IN/OUT (also set st.focused), MOUSE_DOWN/UP/MOVE/
//                ENTER/LEAVE, SCROLL, KEY_DOWN/UP (keycode as GUI_KEY_*,
//                modifiers, and on KEY_DOWN the typed character in
//                codepoint), CHAR (text with no key event, e.g. from an
//                input method), SCALE (the scale factor changed; set
//                st.scale and st.pixelW/H first), EXPOSE (contents lost).
//              gui_backend_post() fills in windowId, timeMs and the
//              double-click count; the backend doesn't.
// wake()       Make a blocked pump() return soon. May be null.
// now_ns()     A monotonic clock in nanoseconds.
// clipboard_set(utf8, len) / clipboard_get(out)
//              The system clipboard as UTF-8 text (1 = ok / 0 = no text).
// display_scale()
//              The scale factor a hiDpi window would get now.
// native_handle(st)
//              The handles a GPU presenter needs (struct GuiNativeHandle).
// primaryMod   GUI_MOD_META on macOS, GUI_MOD_CTRL elsewhere.
//
// ── Presenters: GPU-accelerated presentation ─────────────────────────────
// Rendering stays on the CPU (that is what makes it identical everywhere),
// but getting the finished buffer onto the screen can go through the GPU
// instead of the backend's software blit (XPutImage, StretchDIBits, a
// CALayer's contents): upload it into a texture and draw one quad, with
// OpenGL/EGL (X11, Wayland), Metal (a CAMetalLayer on the Cocoa view) or
// Direct3D 11 (a DXGI swap chain on the HWND). That is a GuiPresenter: a
// separate table attached per window with gui_window_set_presenter(); the
// portable present() calls presenter.present instead of
// backend.window_present while one is attached. attach() gets the native
// handles from backend.native_handle() and keeps its own state in
// st.presenterData; present() gets exactly window_present's arguments
// (premultiplied RGBA8888, the damaged rectangle — upload only that);
// detach() runs when the presenter is replaced or the window destroyed.
// A presenter must cope with the buffer size changing between presents
// (recreate its texture / resize its swap chain when bufW/bufH change).
// One that fails for good while presenting (device or context lost) frees
// st.presenterData, sets st.presenter = null and hands that frame to
// backend.window_present: the window carries on with the software path.
//
// ── Web (WebAssembly <canvas>) ────────────────────────────────────────────
// A browser can't block: gui_web_wasm.sc's pump() never waits, and the page
// drives GuiApp::pump(0) from requestAnimationFrame instead of the program
// calling GuiApp::run() (gui_web.h's gui_web_run()). window_present hands
// the buffer to std/gui/gui_web.js (putImageData, un-premultiplied on the
// way; or WebGL through gui_gpu_web_wasm.sc); the DOM's input events come
// back through exported functions that call gui_backend_post() on the
// window found with gui_backend_find().

#include <std/erased.h>
#include <std/gui/gui.h>
#include <std/collections/vec.h>
#include <std/collections/string.h>

// GuiNativeHandle.kind
#define GUI_NATIVE_NONE     0
#define GUI_NATIVE_X11      1   // display = Display*, window = Window, visual = Visual*
#define GUI_NATIVE_WIN32    2   // display = HINSTANCE, window = HWND
#define GUI_NATIVE_COCOA    3   // display = NSApplication*, window = NSWindow*, view = NSView*
#define GUI_NATIVE_WAYLAND  4   // display = wl_display*, window = wl_surface*
#define GUI_NATIVE_WEB      5   // window = the canvas' id in the host page
#define GUI_NATIVE_FB       6   // display = framebuffer base address
#define GUI_NATIVE_HEADLESS 7

namespace std {

// Pointer-sized native handles, widened to 64 bits so the struct is the
// same on every target (Win64's unsigned long is 32 bits).
struct GuiNativeHandle {
    int kind;                    // GUI_NATIVE_*
    unsigned long long display;
    unsigned long long window;
    unsigned long long view;
    unsigned long long visual;
};

// The backend-neutral state behind GuiWindow.platform. Backends read and
// update the fields their contract above names; the rest is the portable
// layer's.
struct GuiWindowState {
    unsigned int id;              // == GuiWindow.id
    unsigned long long key;       // backend: native window identifier (see window_create)
    ?&Erased native;              // backend-private per-window data (see std/erased.h)
    ?&Erased presenterData;       // presenter-private per-window data
    ?&struct GuiPresenter presenter; // the attached presenter (gui_window_set_presenter); null = the backend's own window_present

    struct Vec<struct GuiEvent> queue;  // pending events, FIFO from 'qhead'
    unsigned long qhead;

    struct String title;
    int x; int y;                 // logical position, GUI_POS_DEFAULT if unknown
    int logicalW; int logicalH;   // logical client size
    int pixelW; int pixelH;       // buffer size the window currently wants
    double scale;
    int hiDpi;
    int minW; int minH; int maxW; int maxH;
    int visible;
    int focused;
    int hovered;
    int cursor;
    int resizable;
    int decorated;
    int topmost;

    // double-click detection (gui_backend_post)
    double lastDownMs; double lastDownX; double lastDownY; int lastDownButton; int clickCount;
};

struct GuiBackend {
    const &char name;
    int primaryMod;   // GUI_MOD_META on macOS, GUI_MOD_CTRL elsewhere

    fn int() init;
    fn int(&GuiWindowState, const &GuiWindowOptions) window_create;
    fn void(&GuiWindowState) window_destroy;
    fn void(&GuiWindowState, const &unsigned char, int, int, int, int, int, int) window_present;
    fn void(&GuiWindowState, const &char) window_set_title;
    fn void(&GuiWindowState, int, int) window_set_size;
    fn void(&GuiWindowState, int, int) window_set_position;
    fn void(&GuiWindowState) window_update_limits;
    fn void(&GuiWindowState, int) window_set_visible;
    fn void(&GuiWindowState) window_focus;
    fn void(&GuiWindowState, int) window_set_cursor;
    fn int(int) pump;
    fn void() wake;               // may be null
    fn long long() now_ns;
    fn int(const &char, unsigned long) clipboard_set;
    fn int(&String) clipboard_get;
    fn double() display_scale;
    fn struct GuiNativeHandle(&GuiWindowState) native_handle;
};

struct GuiPresenter {
    const &char name;
    fn int(&GuiWindowState, struct GuiNativeHandle) attach;   // 1 = ok
    fn void(&GuiWindowState, const &unsigned char, int, int, int, int, int, int) present;
    fn void(&GuiWindowState) detach;
};

// Defined by the platform backend file linked for the target (see above).
&GuiBackend gui_platform_backend();

// The backend in use (installed one, else the platform's), initialized.
&GuiBackend gui_backend();
// Installs 'b' as the backend for this process. Returns 0 (and changes
// nothing) once a window exists.
int gui_set_backend(&GuiBackend b);
// 1 once the backend's init() succeeded (calls it if it hasn't run yet).
int gui_backend_ready();

// For backends: queue 'ev' for the window (fills windowId, timeMs and, for
// GUI_EVENT_MOUSE_DOWN, clicks).
void gui_backend_post(&GuiWindowState st, struct GuiEvent ev);
// For backends: the live window whose st.key is 'key'.
?&GuiWindowState gui_backend_find(unsigned long long key);
// For backends and the event loop: the live windows, in creation order.
unsigned long gui_backend_window_count();
?&GuiWindowState gui_backend_window_at(unsigned long i);
// 1 if any live window has an event queued.
int gui_backend_has_events();

// Attaches 'p' (null: back to the backend's software present) to the
// window, detaching the previous presenter. Returns 0 if p.attach failed
// (the window then keeps the backend's present).
int gui_window_set_presenter(&GuiWindow win, ?&GuiPresenter p);
// The window's state (null for an offscreen window).
?&GuiWindowState gui_window_state(const &GuiWindow win);

} // namespace std