#ifndef YASL_H #define YASL_H #include #include #include #include #include #include "yasl/timing.h" #ifdef __cplusplus extern "C" { #endif #if defined(_WIN32) || defined(__CYGWIN__) #if defined(YASL_BUILD_SHARED) #define YASL_API __declspec(dllexport) #elif defined(YASL_SHARED) #define YASL_API __declspec(dllimport) #else #define YASL_API #endif #elif defined(__GNUC__) && __GNUC__ >= 4 #define YASL_API __attribute__((visibility("default"))) #else #define YASL_API #endif struct yasl_display; struct yasl_surface; struct yasl_loop; /* Geometry */ struct yasl_vector2i { int x; int y; }; struct yasl_rect2i { int x; int y; int width; int height; }; #define YASL_SCALE_UNIT 120 #define YASL_MIN_LOGICAL_PX 64 /* Nearest integer, ties away from zero. Zero scale returns zero; finite * out-of-range results saturate. Non-finite logical coordinates return zero. */ static inline int yasl_scale_to_device(double logical, uint32_t scale_120) { if (!scale_120 || !(logical >= -DBL_MAX && logical <= DBL_MAX)) return 0; double px = logical * ((double)scale_120 / YASL_SCALE_UNIT); if (px >= INT_MAX) return INT_MAX; if (px <= INT_MIN) return INT_MIN; return (int)(px < 0 ? px - 0.5 : px + 0.5); } static inline int yasl_scale_to_logical(int device, uint32_t scale_120) { if (!scale_120) return 0; int64_t px = (int64_t)device * YASL_SCALE_UNIT; px = (px + (px < 0 ? -(int64_t)(scale_120 / 2) : scale_120 / 2)) / scale_120; if (px > INT_MAX) return INT_MAX; if (px < INT_MIN) return INT_MIN; return (int)px; } /* Capabilities */ enum yasl_display_cap : uint32_t { YASL_CAP_POPUPS = 1 << 0, YASL_CAP_DRAG = 1 << 1, YASL_CAP_DRAG_CARRY = 1 << 2, YASL_CAP_PLACE_WINDOW = 1 << 3, YASL_CAP_PRIMARY_SELECTION = 1 << 4, YASL_CAP_CSD = 1 << 5, YASL_CAP_NATIVE_GRAPHICS = 1 << 6, YASL_CAP_FRAME_PACING = 1 << 7, /* compositor/display-driven draws */ }; enum yasl_selection : uint8_t { YASL_CLIPBOARD, YASL_PRIMARY, }; enum yasl_surface_state : uint32_t { YASL_STATE_FULLSCREEN = 1 << 0, YASL_STATE_MAXIMIZED = 1 << 1, YASL_STATE_ACTIVATED = 1 << 2, YASL_STATE_SUSPENDED = 1 << 3, YASL_STATE_CSD = 1 << 4, }; enum yasl_cursor { YASL_CURSOR_ARROW, YASL_CURSOR_TEXT, YASL_CURSOR_LINK, YASL_CURSOR_CROSSHAIR, YASL_CURSOR_MOVE, YASL_CURSOR_RESIZE_NWSE, YASL_CURSOR_RESIZE_NESW, YASL_CURSOR_RESIZE_EW, YASL_CURSOR_RESIZE_NS, YASL_CURSOR_NONE, }; enum yasl_resize_edge : uint32_t { YASL_EDGE_NONE = 0, YASL_EDGE_TOP = 1, YASL_EDGE_BOTTOM = 2, YASL_EDGE_LEFT = 4, YASL_EDGE_TOP_LEFT = 5, YASL_EDGE_BOTTOM_LEFT = 6, YASL_EDGE_RIGHT = 8, YASL_EDGE_TOP_RIGHT = 9, YASL_EDGE_BOTTOM_RIGHT = 10, }; /* Damage & Framebuffer * CPU pixels are tightly packed premultiplied ARGB8888 (0xAARRGGBB). * Size, pointer positions, IME caret and damage use device pixels; surface * creation sizes use logical pixels. Damage is a hint: backends may upload * the whole frame. Every pixel must contain valid content on each draw. */ #define YASL_DAMAGE_MAX 64 struct yasl_damage_list { int n; /* -1 means full repaint */ struct yasl_rect2i bounds; struct yasl_rect2i rects[YASL_DAMAGE_MAX]; }; struct yasl_framebuffer { uint64_t generation; /* Opaque draw identity; preserve when copying target. */ uint32_t *pixels; struct yasl_vector2i size; int buffer; bool cleared; struct yasl_damage_list *damage; void *native_drawable; bool transaction; }; struct yasl_frame { struct yasl_surface *surface; struct yasl_framebuffer target; }; /* Drag and Drop */ enum yasl_drag_event { YASL_DRAG_OVER, YASL_DRAG_LEAVE, YASL_DRAG_DROP, }; enum yasl_drop_event { YASL_DROP_OVER, YASL_DROP_LEAVE, }; enum yasl_drag_end { YASL_DRAG_DROPPED, YASL_DRAG_CANCELLED, }; struct yasl_drag_image { const uint32_t *pixels; struct yasl_vector2i size; struct yasl_vector2i hotspot; }; struct yasl_drag_listener { void (*end)(void *ud, enum yasl_drag_end how); }; struct yasl_geometry { struct yasl_vector2i size; uint32_t scale_120; }; /* Input Model */ enum yasl_key_action { YASL_KEY_PRESS, YASL_KEY_RELEASE, YASL_KEY_REPEAT, }; enum yasl_mod : uint32_t { YASL_MOD_SHIFT = 1 << 0, YASL_MOD_ALT = 1 << 1, YASL_MOD_CTRL = 1 << 2, YASL_MOD_SUPER = 1 << 3, YASL_MOD_CAPS = 1 << 4, }; enum yasl_pointer_kind { YASL_PTR_PRESS, YASL_PTR_RELEASE, YASL_PTR_MOTION, YASL_PTR_LEAVE, YASL_PTR_SCROLL, }; enum yasl_pointer_device { YASL_PTR_DEV_MOUSE, YASL_PTR_DEV_TOUCH, YASL_PTR_DEV_PEN, YASL_PTR_DEV_ERASER, }; struct yasl_pointer_pen { uint16_t pressure; int8_t tilt_x; int8_t tilt_y; }; struct yasl_key_event { uint32_t key; uint32_t keypad; enum yasl_key_action action; uint32_t mods; uint32_t codepoint; uint32_t shifted; uint32_t base; uint32_t text; }; struct yasl_mouse_event { struct yasl_vector2i pos; int16_t button; int16_t scroll_lines; enum yasl_pointer_kind kind; enum yasl_pointer_device device; uint32_t mods; float scroll_delta; struct yasl_pointer_pen pen; }; enum yasl_input_kind { YASL_INPUT_KEY, YASL_INPUT_MOUSE, YASL_INPUT_FOCUS, YASL_INPUT_PASTE, YASL_INPUT_DROP_FILES, YASL_INPUT_TEXT, /* Allocation failure broke the input stream. Clear all held keys/buttons * and composition state before processing subsequent events. */ YASL_INPUT_RESET, }; struct yasl_input_event { struct yasl_surface *surface; enum yasl_input_kind kind; uint32_t reserved; union { struct yasl_key_event key; struct yasl_mouse_event mouse; struct { bool focused; } focus; struct { const char *text; size_t len; } text; struct { const char *text; size_t len; } paste; struct { const char *files; size_t len; } drop_files; }; }; /* Ownership and threading * All calls and callbacks run on the display's opening thread (main thread on * Cocoa). Displays borrow supplied loops; destroy surfaces before displays, * popups before parents, and displays before their supplied loop. * Specs, strings, callback tables and drag listeners are copied during calls. * Userdata is borrowed until callbacks are removed or the object/drag ends. * Native handles are borrowed and expire when their owning object is destroyed. * Surface destruction is allowed from its callbacks; display/loop destruction * during dispatch or callbacks is not supported. */ /* Callbacks */ struct yasl_display_cbs { void (*quit)(void *ud); void (*reopen)(void *ud); void (*lost)(void *ud); }; struct yasl_surface_cbs { void (*draw)(struct yasl_surface *s, struct yasl_frame *frame, void *ud); void (*configure)(struct yasl_surface *s, struct yasl_geometry geometry, void *ud); void (*close)(struct yasl_surface *s, void *ud); void (*state)(struct yasl_surface *s, uint32_t state, void *ud); void (*drag)(struct yasl_surface *s, enum yasl_drag_event ev, struct yasl_vector2i pos, void *ud); void (*drop)(struct yasl_surface *s, enum yasl_drop_event ev, struct yasl_vector2i pos, void *ud); }; /* Display Lifecycle */ enum yasl_backend { YASL_BACKEND_AUTO = 0, YASL_BACKEND_WAYLAND, YASL_BACKEND_X11, YASL_BACKEND_COCOA, }; struct yasl_display_spec { const char *app_id; const struct yasl_display_cbs *cbs; void *ud; /* Zero selects the native backend. An explicit choice never falls back. */ enum yasl_backend backend; }; /* Return NULL and set errno if the requested backend cannot open. */ YASL_API struct yasl_display *yasl_display_open(struct yasl_loop *loop, const struct yasl_display_spec *spec); YASL_API enum yasl_backend yasl_display_backend(const struct yasl_display *d); YASL_API void yasl_display_close(struct yasl_display *d); YASL_API uint32_t yasl_display_caps(const struct yasl_display *d); YASL_API void yasl_display_dispatch(struct yasl_display *d); YASL_API int yasl_display_dispatch_timeout(struct yasl_display *d, int timeout_ms); YASL_API void yasl_display_flush(struct yasl_display *d); YASL_API int yasl_display_modifiers(const struct yasl_display *d); /* Unsupported/invalid selections are no-ops, never clipboard aliases. */ YASL_API void yasl_display_copy(struct yasl_display *d, enum yasl_selection sel, const char *text); YASL_API void yasl_display_paste(struct yasl_display *d, enum yasl_selection sel, struct yasl_surface *target); /* A successful drag invokes the copied listener's non-NULL end once, including * cancel and display close. Failed starts do not invoke it. */ YASL_API bool yasl_display_drag_start(struct yasl_display *d, struct yasl_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud); YASL_API void yasl_display_drag_cancel(struct yasl_display *d); /* Surface Lifecycle */ struct yasl_surface_spec { const char *title; struct yasl_vector2i size; bool fullscreen; bool tearing; /* Optional native graphics device (retained on Cocoa). Requires * YASL_CAP_NATIVE_GRAPHICS; other backends reject it with ENOTSUP. * NULL selects CPU pixel presentation. */ void *graphics_device; }; struct yasl_popup_spec { struct yasl_vector2i size; struct yasl_rect2i anchor; bool beside; struct yasl_vector2i content_offset; struct yasl_vector2i content_size; }; YASL_API struct yasl_surface *yasl_surface_create_toplevel(struct yasl_display *d, const struct yasl_surface_spec *spec); YASL_API struct yasl_surface *yasl_surface_create_popup(struct yasl_surface *parent, const struct yasl_popup_spec *spec); YASL_API bool yasl_surface_popup_reposition(struct yasl_surface *popup, const struct yasl_popup_spec *spec); YASL_API void yasl_surface_destroy(struct yasl_surface *s); YASL_API struct yasl_display *yasl_surface_display(const struct yasl_surface *s); YASL_API void yasl_surface_set_callbacks(struct yasl_surface *s, const struct yasl_surface_cbs *cbs, void *ud); YASL_API void yasl_surface_set_title(struct yasl_surface *s, const char *title); YASL_API void yasl_surface_schedule_redraw(struct yasl_surface *s); /* Present exactly the target supplied to the current draw callback, at most * once. Targets and native drawables expire when that callback returns or * the surface is destroyed. * damage is required: n must be -1 or 0..YASL_DAMAGE_MAX. Rectangles must have * nonnegative extents; they are clipped to the target. Invalid input returns * false without consuming the frame. Native renderers submit/present their * drawable themselves, then call this function to record the submission. */ YASL_API bool yasl_surface_present(struct yasl_surface *s, const struct yasl_framebuffer *fb, const struct yasl_damage_list *damage); /* Event payload strings are library-owned, NUL-terminated, and valid until * the next SUCCESSFUL poll on this surface or surface destruction. Dispatch, * flush and unsuccessful polls do not invalidate them. Copy to keep longer. */ YASL_API bool yasl_surface_input_poll(struct yasl_surface *s, struct yasl_input_event *ev); YASL_API struct yasl_vector2i yasl_surface_size(const struct yasl_surface *s); YASL_API uint32_t yasl_surface_scale_120(const struct yasl_surface *s); YASL_API uint32_t yasl_surface_state(const struct yasl_surface *s); /* Timing uses monotonic nanoseconds. Check source before treating timestamps * as presentation confirmation. refresh/next_present are estimates. Without * YASL_CAP_FRAME_PACING, redraws run on flush without display synchronization. * Tearing may bypass pacing even when the capability is present. */ YASL_API struct yasl_timing yasl_surface_timing(const struct yasl_surface *s); YASL_API uint64_t yasl_surface_frame_time_ns(struct yasl_surface *s, uint64_t now); YASL_API void yasl_surface_set_cursor(struct yasl_surface *s, enum yasl_cursor cursor); YASL_API void yasl_surface_set_background_color(struct yasl_surface *s, uint32_t bg); /* IME Support: preedit is borrowed until the next dispatch or destruction. */ YASL_API const char *yasl_surface_ime_preedit(const struct yasl_surface *s); YASL_API bool yasl_surface_ime_preedit_cursor(const struct yasl_surface *s, int *begin, int *end); YASL_API void yasl_surface_set_ime_caret(struct yasl_surface *s, struct yasl_rect2i caret); /* Client-Side Decorations (CSD) Actions */ YASL_API bool yasl_surface_start_interactive_move(struct yasl_surface *s); YASL_API bool yasl_surface_start_interactive_resize(struct yasl_surface *s, enum yasl_resize_edge edge); YASL_API bool yasl_surface_show_window_menu(struct yasl_surface *s, struct yasl_vector2i pos); /* Drag Support */ YASL_API bool yasl_surface_drag_carry(struct yasl_surface *s, struct yasl_vector2i offset); YASL_API bool yasl_surface_place_under_pointer(struct yasl_surface *s, struct yasl_vector2i at); /* Native Interoperability Handles */ YASL_API void *yasl_display_get_native_handle(struct yasl_display *d); YASL_API void *yasl_surface_get_native_handle(struct yasl_surface *s); /* Integrated Event Loop */ YASL_API struct yasl_loop *yasl_loop_create(void); YASL_API void yasl_loop_destroy(struct yasl_loop *l); YASL_API int yasl_loop_run(struct yasl_loop *l); YASL_API int yasl_loop_step(struct yasl_loop *l, int timeout_ms); YASL_API void yasl_loop_quit(struct yasl_loop *l); YASL_API bool yasl_loop_is_running(const struct yasl_loop *l); YASL_API void yasl_loop_kick(struct yasl_loop *l); YASL_API uint64_t yasl_loop_now_ns(void); YASL_API int yasl_loop_add_fd(struct yasl_loop *l, int fd, short events, int (*fn)(int fd, short revents, void *ud), void *ud); YASL_API int yasl_loop_del_fd(struct yasl_loop *l, int id); YASL_API int yasl_loop_set_fd_events(struct yasl_loop *l, int id, short events); YASL_API int yasl_loop_next_timeout_ms(const struct yasl_loop *l); /* Timers (SDL3-compatible callback model) * Timer callbacks return the next interval in nanoseconds (or milliseconds for _ms), * or 0 to disarm and remove the timer. Returning the same interval repeats the timer; * returning a different non-zero interval reschedules it with the new interval. * * All timers fire on the display's opening thread during event dispatch/step. * Surface timers are bound to a specific surface and are automatically canceled * if that surface is destroyed. */ typedef uint32_t yasl_timer_id; typedef uint64_t (*yasl_timer_cb)(void *ud, yasl_timer_id id, uint64_t interval_ns); typedef uint32_t (*yasl_timer_ms_cb)(void *ud, yasl_timer_id id, uint32_t interval_ms); #define YASL_MS_TO_NS(ms) ((uint64_t)(ms) * 1000000ull) #define YASL_NS_TO_MS(ns) ((uint64_t)(ns) / 1000000ull) /* Surface-bound Timers */ YASL_API yasl_timer_id yasl_surface_add_timer(struct yasl_surface *s, uint64_t interval_ns, yasl_timer_cb cb, void *ud); YASL_API yasl_timer_id yasl_surface_add_timer_ms(struct yasl_surface *s, uint32_t interval_ms, yasl_timer_ms_cb cb, void *ud); YASL_API bool yasl_surface_del_timer(struct yasl_surface *s, yasl_timer_id id); /* Display-bound Timers */ YASL_API yasl_timer_id yasl_display_add_timer(struct yasl_display *d, uint64_t interval_ns, yasl_timer_cb cb, void *ud); YASL_API yasl_timer_id yasl_display_add_timer_ms(struct yasl_display *d, uint32_t interval_ms, yasl_timer_ms_cb cb, void *ud); YASL_API bool yasl_display_del_timer(struct yasl_display *d, yasl_timer_id id); YASL_API struct yasl_loop *yasl_display_loop(struct yasl_display *d); YASL_API struct yasl_loop *yasl_surface_loop(struct yasl_surface *s); /* Loop Timers */ YASL_API yasl_timer_id yasl_loop_add_timer(struct yasl_loop *l, uint64_t interval_ns, yasl_timer_cb cb, void *ud); YASL_API yasl_timer_id yasl_loop_add_timer_ms(struct yasl_loop *l, uint32_t interval_ms, yasl_timer_ms_cb cb, void *ud); YASL_API bool yasl_loop_del_timer(struct yasl_loop *l, yasl_timer_id id); #ifdef __cplusplus } #endif #endif /* YASL_H */