Input Events¶
This example shows native keyboard, pointer, wheel, and resize events.
Preview¶
Run And Adapt¶
Commands below assume a Datoviz source checkout and start at the repository root. Use your configured build environment; Python routes additionally require local bindings.
| Route | Availability | Command or action |
|---|---|---|
| C | Canonical native source | just example-c features/input_events (build and run), or rerun ./build/examples/c/features/input_events |
| Python | Available; direct-engine adaptation | python3 -m examples.python.gallery.features.input_events |
| Browser | Native only | demonstrates native hosted event injection APIs |
This example is approved as a starting point for user code and coding agents. Keep the object lifetimes and data shapes intact while adapting the data and styling.
What To Look For¶
The native app registers input callbacks and prints event details for pointer movement, button presses, scrolling, keyboard input, and window resize. The synthetic smoke path exercises the same event-handling code without requiring manual interaction. Run it live and compare terminal output with your mouse and keyboard actions; event logging is useful when building custom picking, navigation, or analysis controls.
Source¶
#!/usr/bin/env python3
"""Native input event logging with a synthetic offscreen smoke path."""
from __future__ import annotations
import sys
from dataclasses import dataclass
import numpy as np
import datoviz as dvz
from examples.python.gallery import common as ex
POINT_COUNT = 5
@dataclass
class InputEventsState:
subscription_id: int = 0
pointer_count: int = 0
keyboard_count: int = 0
resize_count: int = 0
verbose: bool = True
def _pointer_event_label(event_type: int) -> str:
labels = {
dvz.DVZ_POINTER_EVENT_RELEASE: "release",
dvz.DVZ_POINTER_EVENT_PRESS: "press",
dvz.DVZ_POINTER_EVENT_MOVE: "move",
dvz.DVZ_POINTER_EVENT_CLICK: "click",
dvz.DVZ_POINTER_EVENT_DOUBLE_CLICK: "double-click",
dvz.DVZ_POINTER_EVENT_DRAG_START: "drag-start",
dvz.DVZ_POINTER_EVENT_DRAG: "drag",
dvz.DVZ_POINTER_EVENT_DRAG_STOP: "drag-stop",
dvz.DVZ_POINTER_EVENT_WHEEL: "wheel",
dvz.DVZ_POINTER_EVENT_NONE: "none",
dvz.DVZ_POINTER_EVENT_ALL: "all",
}
return labels.get(event_type, "unknown")
def _pointer_button_label(button: int) -> str:
labels = {
dvz.DVZ_POINTER_BUTTON_NONE: "none",
dvz.DVZ_POINTER_BUTTON_LEFT: "left",
dvz.DVZ_POINTER_BUTTON_MIDDLE: "middle",
dvz.DVZ_POINTER_BUTTON_RIGHT: "right",
}
return labels.get(button, "unknown")
def _keyboard_event_label(event_type: int) -> str:
labels = {
dvz.DVZ_KEYBOARD_EVENT_NONE: "none",
dvz.DVZ_KEYBOARD_EVENT_PRESS: "press",
dvz.DVZ_KEYBOARD_EVENT_REPEAT: "repeat",
dvz.DVZ_KEYBOARD_EVENT_RELEASE: "release",
}
return labels.get(event_type, "unknown")
def _key_label(key: int) -> str:
if dvz.DVZ_KEY_A <= key <= dvz.DVZ_KEY_Z:
return chr(ord("A") + key - dvz.DVZ_KEY_A)
if dvz.DVZ_KEY_0 <= key <= dvz.DVZ_KEY_9:
return chr(ord("0") + key - dvz.DVZ_KEY_0)
labels = {
dvz.DVZ_KEY_UNKNOWN: "unknown",
dvz.DVZ_KEY_NONE: "none",
dvz.DVZ_KEY_SPACE: "space",
dvz.DVZ_KEY_ESCAPE: "escape",
dvz.DVZ_KEY_ENTER: "enter",
dvz.DVZ_KEY_TAB: "tab",
dvz.DVZ_KEY_BACKSPACE: "backspace",
dvz.DVZ_KEY_LEFT: "left",
dvz.DVZ_KEY_RIGHT: "right",
dvz.DVZ_KEY_UP: "up",
dvz.DVZ_KEY_DOWN: "down",
}
return labels.get(key, "unknown")
def _format_mods(mods: int) -> str:
if mods == dvz.DVZ_KEY_MODIFIER_NONE:
return "none"
labels = []
for bit, label in (
(dvz.DVZ_KEY_MODIFIER_SHIFT, "shift"),
(dvz.DVZ_KEY_MODIFIER_CONTROL, "control"),
(dvz.DVZ_KEY_MODIFIER_ALT, "alt"),
(dvz.DVZ_KEY_MODIFIER_SUPER, "super"),
):
if mods & int(bit):
labels.append(label)
known = (
int(dvz.DVZ_KEY_MODIFIER_SHIFT)
| int(dvz.DVZ_KEY_MODIFIER_CONTROL)
| int(dvz.DVZ_KEY_MODIFIER_ALT)
| int(dvz.DVZ_KEY_MODIFIER_SUPER)
)
if mods & ~known:
labels.append(f"0x{mods & ~known:x}")
return "+".join(labels)
def _handle_pointer(event, state: InputEventsState) -> None:
state.pointer_count += 1
if not state.verbose:
return
mods = _format_mods(event.mods)
if event.type == dvz.DVZ_POINTER_EVENT_WHEEL:
print(
"input_events: pointer wheel "
f"dx={event.content.w.dir[0]:.2f} dy={event.content.w.dir[1]:.2f} "
f"x={event.pos[0]:.1f} y={event.pos[1]:.1f} mods={mods}"
)
else:
print(
"input_events: pointer "
f"{_pointer_event_label(event.type)} button={_pointer_button_label(event.button)} "
f"x={event.pos[0]:.1f} y={event.pos[1]:.1f} mods={mods}"
)
def _handle_keyboard(event, state: InputEventsState) -> None:
state.keyboard_count += 1
if state.verbose:
print(
"input_events: key "
f"{_keyboard_event_label(event.type)} key={_key_label(event.key)} "
f"mods={_format_mods(event.mods)}"
)
def _handle_resize(event, state: InputEventsState) -> None:
state.resize_count += 1
if state.verbose:
print(
"input_events: resize "
f"framebuffer={event.framebuffer_width}x{event.framebuffer_height} "
f"window={event.window_width}x{event.window_height} "
f"scale={event.content_scale_x:.2f}x{event.content_scale_y:.2f}"
)
def _subscribe_events(router, state: InputEventsState) -> None:
def callback(_router, event_ptr, _user_data):
event = event_ptr.contents
if event.type == dvz.DVZ_INPUT_EVENT_POINTER:
_handle_pointer(event.content.pointer, state)
elif event.type == dvz.DVZ_INPUT_EVENT_KEYBOARD:
_handle_keyboard(event.content.keyboard, state)
elif event.type == dvz.DVZ_INPUT_EVENT_RESIZE:
_handle_resize(event.content.resize, state)
state.subscription_id = dvz.dvz_input_subscribe_event(router, callback, None)
if state.subscription_id == 0:
raise RuntimeError("dvz_input_subscribe_event() failed")
def _unsubscribe_events(router, state: InputEventsState) -> None:
if state.subscription_id:
dvz.dvz_input_unsubscribe(router, state.subscription_id)
state.subscription_id = 0
def _add_points(scene, panel) -> None:
positions = np.array(
[
[-0.60, -0.30, 0.0],
[-0.30, +0.25, 0.0],
[+0.00, -0.05, 0.0],
[+0.35, +0.32, 0.0],
[+0.62, -0.24, 0.0],
],
dtype=np.float32,
)
colors = ex.color_array(ex.CYAN, ex.GREEN, ex.TEXT, ex.YELLOW, ex.RED)
sizes = np.array([22.0, 34.0, 42.0, 30.0, 24.0], dtype=np.float32)
point = dvz.dvz_point(scene, 0)
if not point:
raise RuntimeError("dvz_point() failed")
if dvz.dvz_visual_set_data_many(
point,
{
"position": positions,
"color": colors,
"diameter_px": sizes,
},
) != 0:
raise RuntimeError("dvz_visual_set_data_many(point) failed")
ex.add_visual(panel, point)
def _build_scene():
scene, figure, panel = ex.scene_panel()
_add_points(scene, panel)
return scene, figure, panel
def _emit_synthetic_events(view) -> None:
if dvz.dvz_view_emit_resize(view, ex.WIDTH, ex.HEIGHT, ex.WIDTH, ex.HEIGHT, 1.0, 1.0) != 0:
raise RuntimeError("dvz_view_emit_resize() failed")
if (
dvz.dvz_view_emit_pointer(
view,
dvz.DVZ_POINTER_EVENT_MOVE,
140.0,
160.0,
ex.WIDTH,
ex.HEIGHT,
dvz.DVZ_POINTER_BUTTON_NONE,
dvz.DVZ_KEY_MODIFIER_NONE,
)
!= 0
):
raise RuntimeError("dvz_view_emit_pointer(move) failed")
if (
dvz.dvz_view_emit_pointer(
view,
dvz.DVZ_POINTER_EVENT_PRESS,
140.0,
160.0,
ex.WIDTH,
ex.HEIGHT,
dvz.DVZ_POINTER_BUTTON_LEFT,
dvz.DVZ_KEY_MODIFIER_NONE,
)
!= 0
):
raise RuntimeError("dvz_view_emit_pointer(press) failed")
if (
dvz.dvz_view_emit_wheel(
view, 140.0, 160.0, ex.WIDTH, ex.HEIGHT, 0.0, +1.0, dvz.DVZ_KEY_MODIFIER_NONE
)
!= 0
):
raise RuntimeError("dvz_view_emit_wheel() failed")
if (
dvz.dvz_view_emit_key(
view, dvz.DVZ_KEYBOARD_EVENT_PRESS, dvz.DVZ_KEY_A, dvz.DVZ_KEY_MODIFIER_NONE
)
!= 0
):
raise RuntimeError("dvz_view_emit_key(press) failed")
if (
dvz.dvz_view_emit_key(
view, dvz.DVZ_KEYBOARD_EVENT_RELEASE, dvz.DVZ_KEY_A, dvz.DVZ_KEY_MODIFIER_NONE
)
!= 0
):
raise RuntimeError("dvz_view_emit_key(release) failed")
def _run_synthetic(verbose: bool = True) -> InputEventsState:
scene, figure, _panel = _build_scene()
app = dvz.dvz_app(scene)
if not app:
dvz.dvz_scene_destroy(scene)
raise RuntimeError("dvz_app() failed")
try:
view = dvz.dvz_view_offscreen(app, figure, ex.WIDTH, ex.HEIGHT)
if not view:
raise RuntimeError("dvz_view_offscreen() failed")
router = dvz.dvz_view_input(view)
if not router:
raise RuntimeError("dvz_view_input() failed")
state = InputEventsState(verbose=verbose)
_subscribe_events(router, state)
try:
_emit_synthetic_events(view)
status = dvz.dvz_view_render_once(view)
if status != dvz.DVZ_CANVAS_FRAME_READY:
raise RuntimeError("dvz_view_render_once() failed")
ex.capture_smoke(view)
if state.pointer_count < 3 or state.keyboard_count != 2 or state.resize_count < 1:
raise RuntimeError("input event callbacks did not receive emitted events")
return state
finally:
_unsubscribe_events(router, state)
finally:
dvz.dvz_app_destroy(app)
dvz.dvz_scene_destroy(scene)
def main(argv: list[str] | None = None) -> None:
argv = sys.argv[1:] if argv is None else argv
if "--synthetic" in argv or ex.SMOKE_MODE:
_run_synthetic(verbose=True)
return
scene, figure, _panel = _build_scene()
app = dvz.dvz_app(scene)
if not app:
dvz.dvz_scene_destroy(scene)
raise RuntimeError("dvz_app() failed")
try:
view = dvz.dvz_view_window(app, figure, ex.WIDTH, ex.HEIGHT, b"Input Events")
if not view:
raise RuntimeError("dvz_view_window() failed")
router = dvz.dvz_view_input(view)
if not router:
raise RuntimeError("dvz_view_input() failed")
state = InputEventsState(verbose=True)
_subscribe_events(router, state)
try:
print("input_events: move/click/scroll/resize/type in the window; close it to exit")
ex.run_app(app, view)
finally:
_unsubscribe_events(router, state)
finally:
dvz.dvz_app_destroy(app)
dvz.dvz_scene_destroy(scene)
if __name__ == "__main__":
main()
/*
* Copyright (c) 2021 Cyrille Rossant and contributors. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project root for details.
* SPDX-License-Identifier: MIT
*/
/* input_events - This example shows native keyboard, pointer, wheel, and resize events.
*
* Scenario: features_input_events
* Style: features, native app
*
* Build: just example-c features/input_events
* Run: ./build/examples/c/features/input_events
* Smoke: ./build/examples/c/features/input_events --synthetic
*
* What to look for: the native app registers input callbacks and prints event details for pointer
* movement, button presses, scrolling, keyboard input, and window resize. The synthetic smoke path
* exercises the same event-handling code without requiring manual interaction. Run it live and
* compare terminal output with your mouse and keyboard actions; event logging is useful when
* building custom picking, navigation, or analysis controls.
*/
/*************************************************************************************************/
/* Includes */
/*************************************************************************************************/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "_assertions.h"
#include "datoviz/app.h"
#include "datoviz/canvas/enums.h"
#include "datoviz/input.h"
#include "datoviz/scene.h"
#include "example_common.h"
#include "example_style.h"
/*************************************************************************************************/
/* Constants */
/*************************************************************************************************/
#define WIDTH EXAMPLE_WINDOW_WIDTH
#define HEIGHT EXAMPLE_WINDOW_HEIGHT
/*************************************************************************************************/
/* Structs */
/*************************************************************************************************/
typedef struct InputEventsState
{
DvzCallbackId input_subscription_id;
uint32_t pointer_count;
uint32_t keyboard_count;
uint32_t resize_count;
bool verbose;
} InputEventsState;
/*************************************************************************************************/
/* Callbacks */
/*************************************************************************************************/
/**
* Return a human-readable pointer event type label.
*
* @param type pointer event type
* @return stable label
*/
static const char* _pointer_event_label(DvzPointerEventType type)
{
switch (type)
{
case DVZ_POINTER_EVENT_RELEASE:
return "release";
case DVZ_POINTER_EVENT_PRESS:
return "press";
case DVZ_POINTER_EVENT_MOVE:
return "move";
case DVZ_POINTER_EVENT_CLICK:
return "click";
case DVZ_POINTER_EVENT_DOUBLE_CLICK:
return "double-click";
case DVZ_POINTER_EVENT_DRAG_START:
return "drag-start";
case DVZ_POINTER_EVENT_DRAG:
return "drag";
case DVZ_POINTER_EVENT_DRAG_STOP:
return "drag-stop";
case DVZ_POINTER_EVENT_WHEEL:
return "wheel";
case DVZ_POINTER_EVENT_NONE:
return "none";
case DVZ_POINTER_EVENT_ALL:
return "all";
default:
return "unknown";
}
}
/**
* Return a human-readable pointer button label.
*
* @param button pointer button
* @return stable label
*/
static const char* _pointer_button_label(DvzPointerButton button)
{
switch (button)
{
case DVZ_POINTER_BUTTON_NONE:
return "none";
case DVZ_POINTER_BUTTON_LEFT:
return "left";
case DVZ_POINTER_BUTTON_MIDDLE:
return "middle";
case DVZ_POINTER_BUTTON_RIGHT:
return "right";
default:
return "unknown";
}
}
/**
* Return a human-readable keyboard event type label.
*
* @param type keyboard event type
* @return stable label
*/
static const char* _keyboard_event_label(DvzKeyboardEventType type)
{
switch (type)
{
case DVZ_KEYBOARD_EVENT_NONE:
return "none";
case DVZ_KEYBOARD_EVENT_PRESS:
return "press";
case DVZ_KEYBOARD_EVENT_REPEAT:
return "repeat";
case DVZ_KEYBOARD_EVENT_RELEASE:
return "release";
default:
return "unknown";
}
}
/**
* Return a human-readable key label for common Datoviz key codes.
*
* @param key key code
* @return stable label
*/
static const char* _key_label(DvzKeyCode key)
{
if (key >= DVZ_KEY_A && key <= DVZ_KEY_Z)
{
static const char* const letters[] = {
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};
return letters[key - DVZ_KEY_A];
}
if (key >= DVZ_KEY_0 && key <= DVZ_KEY_9)
{
static const char* const digits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
return digits[key - DVZ_KEY_0];
}
if (key >= DVZ_KEY_F1 && key <= DVZ_KEY_F25)
{
static const char* const function_keys[] = {
"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9",
"F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18",
"F19", "F20", "F21", "F22", "F23", "F24", "F25"};
return function_keys[key - DVZ_KEY_F1];
}
if (key >= DVZ_KEY_KP_0 && key <= DVZ_KEY_KP_9)
{
static const char* const keypad_digits[] = {
"keypad-0", "keypad-1", "keypad-2", "keypad-3", "keypad-4",
"keypad-5", "keypad-6", "keypad-7", "keypad-8", "keypad-9"};
return keypad_digits[key - DVZ_KEY_KP_0];
}
switch (key)
{
case DVZ_KEY_UNKNOWN:
return "unknown";
case DVZ_KEY_NONE:
return "none";
case DVZ_KEY_SPACE:
return "space";
case DVZ_KEY_APOSTROPHE:
return "'";
case DVZ_KEY_COMMA:
return ",";
case DVZ_KEY_MINUS:
return "-";
case DVZ_KEY_PERIOD:
return ".";
case DVZ_KEY_SLASH:
return "/";
case DVZ_KEY_SEMICOLON:
return ";";
case DVZ_KEY_EQUAL:
return "=";
case DVZ_KEY_LEFT_BRACKET:
return "[";
case DVZ_KEY_BACKSLASH:
return "\\";
case DVZ_KEY_RIGHT_BRACKET:
return "]";
case DVZ_KEY_GRAVE_ACCENT:
return "`";
case DVZ_KEY_WORLD_1:
return "world-1";
case DVZ_KEY_WORLD_2:
return "world-2";
case DVZ_KEY_ESCAPE:
return "escape";
case DVZ_KEY_ENTER:
return "enter";
case DVZ_KEY_TAB:
return "tab";
case DVZ_KEY_BACKSPACE:
return "backspace";
case DVZ_KEY_INSERT:
return "insert";
case DVZ_KEY_DELETE:
return "delete";
case DVZ_KEY_RIGHT:
return "right";
case DVZ_KEY_LEFT:
return "left";
case DVZ_KEY_DOWN:
return "down";
case DVZ_KEY_UP:
return "up";
case DVZ_KEY_PAGE_UP:
return "page-up";
case DVZ_KEY_PAGE_DOWN:
return "page-down";
case DVZ_KEY_HOME:
return "home";
case DVZ_KEY_END:
return "end";
case DVZ_KEY_CAPS_LOCK:
return "caps-lock";
case DVZ_KEY_SCROLL_LOCK:
return "scroll-lock";
case DVZ_KEY_NUM_LOCK:
return "num-lock";
case DVZ_KEY_PRINT_SCREEN:
return "print-screen";
case DVZ_KEY_PAUSE:
return "pause";
case DVZ_KEY_KP_DECIMAL:
return "keypad-decimal";
case DVZ_KEY_KP_DIVIDE:
return "keypad-divide";
case DVZ_KEY_KP_MULTIPLY:
return "keypad-multiply";
case DVZ_KEY_KP_SUBTRACT:
return "keypad-subtract";
case DVZ_KEY_KP_ADD:
return "keypad-add";
case DVZ_KEY_KP_ENTER:
return "keypad-enter";
case DVZ_KEY_KP_EQUAL:
return "keypad-equal";
case DVZ_KEY_LEFT_SHIFT:
return "left-shift";
case DVZ_KEY_LEFT_CONTROL:
return "left-control";
case DVZ_KEY_LEFT_ALT:
return "left-alt";
case DVZ_KEY_LEFT_SUPER:
return "left-super";
case DVZ_KEY_RIGHT_SHIFT:
return "right-shift";
case DVZ_KEY_RIGHT_CONTROL:
return "right-control";
case DVZ_KEY_RIGHT_ALT:
return "right-alt";
case DVZ_KEY_RIGHT_SUPER:
return "right-super";
case DVZ_KEY_MENU:
return "menu";
default:
return "unknown";
}
}
/**
* Format a modifier mask as a readable joined label.
*
* @param mods modifier mask
* @param out output string
* @param out_size output string size
*/
static void _format_mods(int mods, char* out, size_t out_size)
{
ANN(out);
ASSERT(out_size > 0);
out[0] = '\0';
if (mods == DVZ_KEY_MODIFIER_NONE)
{
snprintf(out, out_size, "none");
return;
}
bool first = true;
#define ADD_MOD(bit, label) \
do \
{ \
if ((mods & (bit)) != 0) \
{ \
snprintf(out + strlen(out), out_size - strlen(out), "%s%s", first ? "" : "+", label); \
first = false; \
} \
} while (0)
ADD_MOD(DVZ_KEY_MODIFIER_SHIFT, "shift");
ADD_MOD(DVZ_KEY_MODIFIER_CONTROL, "control");
ADD_MOD(DVZ_KEY_MODIFIER_ALT, "alt");
ADD_MOD(DVZ_KEY_MODIFIER_SUPER, "super");
#undef ADD_MOD
int known = DVZ_KEY_MODIFIER_SHIFT | DVZ_KEY_MODIFIER_CONTROL | DVZ_KEY_MODIFIER_ALT |
DVZ_KEY_MODIFIER_SUPER;
if ((mods & ~known) != 0)
snprintf(
out + strlen(out), out_size - strlen(out), "%s0x%x", first ? "" : "+", mods & ~known);
}
/**
* Count and log a routed pointer event received by the view input router.
*
* @param event pointer event
* @param state input example state
*/
static void _input_events_pointer(const DvzPointerEvent* event, InputEventsState* state)
{
if (state == NULL || event == NULL)
return;
state->pointer_count++;
if (state->verbose)
{
char mods[64] = {0};
_format_mods(event->mods, mods, sizeof(mods));
if (event->type == DVZ_POINTER_EVENT_WHEEL)
dvz_fprintf(
stdout, "input_events: pointer wheel dx=%.2f dy=%.2f x=%.1f y=%.1f mods=%s\n",
event->content.w.dir[0], event->content.w.dir[1], event->pos[0], event->pos[1],
mods);
else
dvz_fprintf(
stdout, "input_events: pointer %s button=%s x=%.1f y=%.1f mods=%s\n",
_pointer_event_label(event->type), _pointer_button_label(event->button),
event->pos[0], event->pos[1], mods);
}
}
/**
* Count and log a routed keyboard event received by the view input router.
*
* @param event keyboard event
* @param state input example state
*/
static void _input_events_keyboard(const DvzKeyboardEvent* event, InputEventsState* state)
{
if (state == NULL || event == NULL)
return;
state->keyboard_count++;
if (state->verbose)
{
char mods[64] = {0};
_format_mods(event->mods, mods, sizeof(mods));
dvz_fprintf(
stdout, "input_events: key %s key=%s mods=%s\n", _keyboard_event_label(event->type),
_key_label(event->key), mods);
}
}
/**
* Count and log a routed resize event received by the view input router.
*
* @param event resize event
* @param state input example state
*/
static void _input_events_resize(const DvzInputResizeEvent* event, InputEventsState* state)
{
if (state == NULL || event == NULL)
return;
state->resize_count++;
if (state->verbose)
dvz_fprintf(
stdout, "input_events: resize framebuffer=%ux%u window=%ux%u scale=%.2fx%.2f\n",
event->framebuffer_width, event->framebuffer_height, event->window_width,
event->window_height, event->content_scale_x, event->content_scale_y);
}
/**
* Count routed input events received by the view input router.
*
* The union event stream is the canonical stream for consumers that need high-level pointer
* gestures: the raw pointer callbacks receive backend-normalized pointer events, while the gesture
* handler emits click, double-click, drag-start, drag, and drag-stop events here.
*
* @param router input router
* @param event routed input event
* @param user_data input example state
*/
static void _input_events_event(DvzInputRouter* router, const DvzInputEvent* event, void* user_data)
{
(void)router;
InputEventsState* state = (InputEventsState*)user_data;
if (state == NULL || event == NULL)
return;
switch (event->type)
{
case DVZ_INPUT_EVENT_POINTER:
_input_events_pointer(&event->content.pointer, state);
break;
case DVZ_INPUT_EVENT_KEYBOARD:
_input_events_keyboard(&event->content.keyboard, state);
break;
case DVZ_INPUT_EVENT_RESIZE:
_input_events_resize(&event->content.resize, state);
break;
default:
break;
}
}
/*************************************************************************************************/
/* Helpers */
/*************************************************************************************************/
/**
* Subscribe all event callbacks used by the example.
*
* @param router input router
* @param state event counter state
*/
static void _subscribe_events(DvzInputRouter* router, InputEventsState* state)
{
ANN(router);
ANN(state);
state->input_subscription_id = dvz_input_subscribe_event(router, _input_events_event, state);
}
/**
* Unsubscribe all event callbacks used by the example.
*
* @param router input router
* @param state event counter state
*/
static void _unsubscribe_events(DvzInputRouter* router, InputEventsState* state)
{
if (router == NULL || state == NULL || state->input_subscription_id == DVZ_CALLBACK_ID_NONE)
return;
dvz_input_unsubscribe(router, state->input_subscription_id);
state->input_subscription_id = DVZ_CALLBACK_ID_NONE;
}
/**
* Add a small scene so the live input window has an obvious event target.
*
* @param scene scene owning the visual
* @param panel target panel
* @return whether setup succeeded
*/
static bool _add_points(DvzScene* scene, DvzPanel* panel)
{
ANN(scene);
ANN(panel);
DvzVisual* points = dvz_point(scene, 0);
if (points == NULL)
return false;
vec3 positions[5] = {
{-0.60f, -0.30f, 0.0f},
{-0.30f, +0.25f, 0.0f},
{+0.00f, -0.05f, 0.0f},
{+0.35f, +0.32f, 0.0f},
{+0.62f, -0.24f, 0.0f},
};
DvzColor colors[5] = {
example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_ACCENT_PRIMARY),
example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_ACCENT_SECONDARY),
example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_TEXT),
example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_WARNING),
example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_ERROR),
};
float sizes[5] = {22.0f, 34.0f, 42.0f, 30.0f, 24.0f};
DvzVisualDataUpdate updates[] = {
{.attr_name = "position", .data = positions, .item_count = 5},
{.attr_name = "color", .data = colors, .item_count = 5},
{.attr_name = "diameter_px", .data = sizes, .item_count = 5},
};
if (dvz_visual_set_data_many(points, updates, 3) != 0)
return false;
return dvz_panel_add_visual(panel, points, NULL) == 0;
}
/**
* Run the synthetic emitted-event smoke path.
*
* @return process exit code
*/
static int _run_synthetic(void)
{
int ret = 1;
DvzScene* scene = NULL;
DvzApp* app = NULL;
scene = dvz_scene();
EXAMPLE_CHECK(scene != NULL, "dvz_scene() failed");
DvzFigure* figure = dvz_figure(scene, WIDTH, HEIGHT, 0);
DvzPanel* panel = figure != NULL ? dvz_panel_full(figure) : NULL;
EXAMPLE_CHECK(figure != NULL && panel != NULL, "failed to create scene objects");
example_graphite_cyan_set_panel_background(panel);
EXAMPLE_CHECK(_add_points(scene, panel), "failed to add point visual");
app = dvz_app(scene);
EXAMPLE_CHECK(app != NULL, "dvz_app() failed (no GPU?)");
DvzView* view = dvz_view_offscreen(app, figure, WIDTH, HEIGHT);
EXAMPLE_CHECK(view != NULL, "dvz_view_offscreen() failed");
DvzInputRouter* router = dvz_view_input(view);
EXAMPLE_CHECK(router != NULL, "dvz_view_input() failed");
InputEventsState state = {.verbose = true};
_subscribe_events(router, &state);
EXAMPLE_CHECK(
dvz_view_emit_resize(view, WIDTH, HEIGHT, WIDTH, HEIGHT, 1.0f, 1.0f) == 0,
"dvz_view_emit_resize() failed");
EXAMPLE_CHECK(
dvz_view_emit_pointer(
view, DVZ_POINTER_EVENT_MOVE, 140.0f, 160.0f, WIDTH, HEIGHT, DVZ_POINTER_BUTTON_NONE,
DVZ_KEY_MODIFIER_NONE) == 0,
"dvz_view_emit_pointer(move) failed");
EXAMPLE_CHECK(
dvz_view_emit_pointer(
view, DVZ_POINTER_EVENT_PRESS, 140.0f, 160.0f, WIDTH, HEIGHT, DVZ_POINTER_BUTTON_LEFT,
DVZ_KEY_MODIFIER_NONE) == 0,
"dvz_view_emit_pointer(press) failed");
EXAMPLE_CHECK(
dvz_view_emit_wheel(
view, 140.0f, 160.0f, WIDTH, HEIGHT, 0.0f, +1.0f, DVZ_KEY_MODIFIER_NONE) == 0,
"dvz_view_emit_wheel() failed");
EXAMPLE_CHECK(
dvz_view_emit_key(view, DVZ_KEYBOARD_EVENT_PRESS, DVZ_KEY_A, DVZ_KEY_MODIFIER_NONE) == 0,
"dvz_view_emit_key(press) failed");
EXAMPLE_CHECK(
dvz_view_emit_key(view, DVZ_KEYBOARD_EVENT_RELEASE, DVZ_KEY_A, DVZ_KEY_MODIFIER_NONE) == 0,
"dvz_view_emit_key(release) failed");
EXAMPLE_CHECK(
dvz_view_render_once(view) == DVZ_CANVAS_FRAME_READY, "dvz_view_render_once() failed");
EXAMPLE_CHECK(
state.pointer_count >= 3 && state.keyboard_count == 2 && state.resize_count >= 1,
"input event callbacks did not receive the emitted events");
_unsubscribe_events(router, &state);
ret = 0;
cleanup:
if (app != NULL)
dvz_app_destroy(app);
if (scene != NULL)
dvz_scene_destroy(scene);
return ret;
}
/**
* Run the live native event logging path.
*
* @param argc command-line argument count
* @param argv command-line argument vector
* @return process exit code
*/
static int _run_live(int argc, char** argv)
{
int ret = 1;
DvzScene* scene = NULL;
DvzApp* app = NULL;
scene = dvz_scene();
EXAMPLE_CHECK(scene != NULL, "dvz_scene() failed");
DvzFigure* figure = dvz_figure(scene, WIDTH, HEIGHT, 0);
DvzPanel* panel = figure != NULL ? dvz_panel_full(figure) : NULL;
EXAMPLE_CHECK(figure != NULL && panel != NULL, "failed to create scene objects");
example_graphite_cyan_set_panel_background(panel);
EXAMPLE_CHECK(_add_points(scene, panel), "failed to add point visual");
app = dvz_app(scene);
EXAMPLE_CHECK(app != NULL, "dvz_app() failed (no GPU?)");
DvzView* view = dvz_view_window(app, figure, WIDTH, HEIGHT, "input_events");
EXAMPLE_CHECK(view != NULL, "dvz_view_window() failed");
DvzInputRouter* router = dvz_view_input(view);
EXAMPLE_CHECK(router != NULL, "dvz_view_input() failed");
InputEventsState state = {.verbose = true};
_subscribe_events(router, &state);
dvz_fprintf(
stdout,
"input_events: move/click/scroll/resize/type in the window; close it to exit\n");
dvz_app_run(app, example_frame_count_any(argc, argv));
_unsubscribe_events(dvz_view_input(view), &state);
ret = 0;
cleanup:
if (app != NULL)
dvz_app_destroy(app);
if (scene != NULL)
dvz_scene_destroy(scene);
return ret;
}
/*************************************************************************************************/
/* Main */
/*************************************************************************************************/
int main(int argc, char** argv)
{
if (example_arg_has(argc, argv, "--png"))
{
dvz_fprintf(
stderr, "input_events: --png is not supported; use --synthetic for headless smoke\n");
return 1;
}
if (example_arg_has(argc, argv, "--synthetic"))
return _run_synthetic();
return _run_live(argc, argv);
}
Example details
- ID:
features_input_events - Category:
feature - Lane:
features - Status:
supported - Source:
examples/c/features/input_events.c - Approved adaptation starter:
yes - Python source:
examples/python/gallery/features/input_events.py - Python adaptation: Available; direct-engine adaptation
- Browser support: Native only
- Browser note: demonstrates native hosted event injection APIs
- Browser capability tags:
native-view,input-events - Validation:
smoke+interaction
Data
| Field | Value |
|---|---|
kind |
synthetic |