raven/src/window.c

411 lines
14 KiB
C
Raw Normal View History

2023-04-13 00:27:08 +03:00
#include "cairo.h"
#include "timeutil.h"
#include "window.h"
#include "node.h"
//#define PROF
#include "prof.c"
#include "time.h"
2023-04-13 04:11:15 +03:00
#include <stdint.h>
2023-04-13 00:27:08 +03:00
#include <stdlib.h>
#include <sys/poll.h>
#include <time.h>
#include <xcb/xcb.h>
void _window_process_xcb_event(UIWindow *window, xcb_generic_event_t *event);
2023-04-13 00:27:08 +03:00
int window_invalidate_node(UIWindow *window, UINode *node)
{
if (node->flags & UI_NODE_COMPONENT) {
if (!node->parent) {
return -1;
}
node = node->parent;
}
UIRect local_rect = (UIRect){
.x = node->rect.x + node->window_rel_x,
.y = node->rect.y + node->window_rel_y,
.w = node->rect.w,
.h = node->rect.h
};
window->invalid_region = ui_rect_united(&window->invalid_region, &local_rect);
return 0;
}
void window_free(UIWindow *window)
{
if (!window) return;
if (window->_cairo_surface) cairo_surface_destroy(window->_cairo_surface);
if (window->drw) cairo_destroy(window->drw);
if (window->_xcb_connection) xcb_disconnect(window->_xcb_connection);
node_free(window->root);
free(window);
}
UIWindow *window_new(int width, int height)
{
bool fail = false;
UIWindow *window = NULL;
window = calloc(1, sizeof(UIWindow));
if (!window) {
fail = true;
goto done;
}
window->hovered = NULL;
window->pressed = NULL;
window->root = NULL;
window->invalid_region = (UIRect){ 0, 0, 0, 0 };
window->_cairo_surface = NULL;
window->_xcb_connection = NULL;
window->_xcb_window_id = -1;
window->drw = NULL;
window->_xcb_connection = xcb_connect(NULL, NULL);
if (xcb_connection_has_error(window->_xcb_connection) != 0) {
fail = true;
goto done;
}
const xcb_setup_t *setup = xcb_get_setup(window->_xcb_connection);
xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
xcb_screen_t *screen = iter.data;
xcb_visualtype_t *visualtype = NULL;
for (xcb_depth_iterator_t iter_depth = xcb_screen_allowed_depths_iterator(screen); iter_depth.rem; xcb_depth_next(&iter_depth)) {
for (xcb_visualtype_iterator_t iter_visualtype = xcb_depth_visuals_iterator(iter_depth.data); iter_visualtype.rem; xcb_visualtype_next(&iter_visualtype)) {
if (iter_visualtype.data->visual_id == screen->root_visual) {
visualtype = iter_visualtype.data;
break;
}
}
if (visualtype) break;
}
if (!visualtype) {
fail = true;
goto done;
}
window->_xcb_window_id = xcb_generate_id(window->_xcb_connection);
if (window->_xcb_window_id == (unsigned)-1) {
fail = true;
goto done;
}
xcb_create_window(
window->_xcb_connection, // connection
XCB_COPY_FROM_PARENT, // depth
window->_xcb_window_id, // window id
screen->root, // parent window
0, // x
0, // y
width, // width
height, // height
0, // border_width
XCB_WINDOW_CLASS_INPUT_OUTPUT, // class
screen->root_visual, // visual
XCB_CW_EVENT_MASK, // value mask
(uint32_t[]){ XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE } // value list
);
xcb_map_window(window->_xcb_connection, window->_xcb_window_id);
if (xcb_flush(window->_xcb_connection) <= 0) {
fail = true;
goto done;
}
window->_cairo_surface = cairo_xcb_surface_create(window->_xcb_connection, window->_xcb_window_id, visualtype, width, height);
if (cairo_surface_status(window->_cairo_surface) != CAIRO_STATUS_SUCCESS) {
fail = true;
goto done;
}
window->drw = cairo_create(window->_cairo_surface);
if (cairo_status(window->drw) != CAIRO_STATUS_SUCCESS) {
fail = true;
goto done;
}
done:
if (fail) {
window_free(window);
return NULL;
}
return window;
}
bool window_flush_invalidated(UIWindow *window)
{
if (ui_rect_null(&window->invalid_region)) {
return false;
}
cairo_push_group(window->drw);
node_repaint(window->root, &window->invalid_region, true, 0, 0);
window->invalid_region = (UIRect){ 0, 0, 0, 0 };
cairo_pop_group_to_source(window->drw);
cairo_paint(window->drw);
cairo_surface_flush(window->_cairo_surface);
return true;
}
int window_attach_root(UIWindow *window, UINode *root)
{
if (!window || !root || window->root || !window->drw) {
return -1;
}
root->window = window;
root->drw = window->drw;
root->width_policy = UI_SIZE_POLICY_STATIC;
root->height_policy = UI_SIZE_POLICY_STATIC;
window->root = root;
return 0;
}
void _window_process_xcb_event(UIWindow *window, xcb_generic_event_t *event)
{
begin_clock("Process XCB event");
uint8_t event_type = event->response_type & ~0x80;
switch (event_type) {
case XCB_EXPOSE: {
window_invalidate_node(window, window->root);
break;
}
case XCB_CONFIGURE_NOTIFY: {
xcb_configure_notify_event_t *ev = (xcb_configure_notify_event_t*)event;
UIRect new_rect = { 0, 0, ev->width, ev->height };
if (!ui_rect_equals(&window->root->rect, &new_rect)) {
cairo_xcb_surface_set_size(window->_cairo_surface, ev->width, ev->height);
window->root->rect.x = 0;
window->root->rect.y = 0;
window->root->rect.w = ev->width;
window->root->rect.h = ev->height;
node_dispatch(window->root, UI_EVENT_RELAYOUT, 0, NULL);
window_invalidate_node(window, window->root);
node_dump(window->root, 0);
}
break;
}
case XCB_MOTION_NOTIFY: {
xcb_motion_notify_event_t *ev = (xcb_motion_notify_event_t*)event;
UINode *node = node_by_point(window->root, ev->event_x, ev->event_y);
if (node && node != window->hovered) {
UINode *previously_hovered = window->hovered;
window->hovered = node;
if (previously_hovered) {
node_dispatch(previously_hovered, UI_EVENT_UNHOVERED, 0, NULL);
}
node_dispatch(node, UI_EVENT_HOVERED, 0, NULL);
}
break;
}
case XCB_BUTTON_RELEASE: /* through */
case XCB_BUTTON_PRESS: {
xcb_button_press_event_t *ev = (xcb_button_press_event_t*)event;
UINode *node = node_by_point(window->root, ev->event_x, ev->event_y);
if (node) {
enum UIEvent ui_event = UI_EVENT_BUTTON_LEFT_UPDATE;
bool state = event_type == XCB_BUTTON_PRESS ? true : false;
double delta = 0.0;
switch (ev->detail) {
case XCB_BUTTON_INDEX_3: {
ui_event = UI_EVENT_BUTTON_RIGHT_UPDATE;
break;
}
case XCB_BUTTON_INDEX_4: {
// scroll up
ui_event = UI_EVENT_SCROLL;
delta = 20.0;
break;
}
case XCB_BUTTON_INDEX_5: {
// scroll down
ui_event = UI_EVENT_SCROLL;
delta = -20.0;
break;
}
}
UINode *n = node;
while (n) {
if (node_dispatch(n, ui_event, state, &delta) > 0) {
break;
}
n = n->parent;
}
if (ui_event == UI_EVENT_BUTTON_LEFT_UPDATE) {
if (state) {
UINode *previously_pressed = window->pressed;
window->pressed = node;
if (previously_pressed) {
node_dispatch(previously_pressed, UI_EVENT_UNPRESSED, 0, NULL);
}
node_dispatch(node, UI_EVENT_PRESSED, 0, NULL);
} else {
UINode *previously_pressed = window->pressed;
window->pressed = NULL;
if (previously_pressed) {
node_dispatch(previously_pressed, UI_EVENT_UNPRESSED, 0, NULL);
}
}
}
}
break;
}
default: {
break;
}
}
end_clock();
}
int window_turn(UIWindow *window)
{
if (!window || !window->root || !window->_xcb_connection || !window->_cairo_surface || !window->drw) {
return -1;
}
if (xcb_connection_has_error(window->_xcb_connection) != 0) {
return -1;
}
if (cairo_surface_status(window->_cairo_surface) != CAIRO_STATUS_SUCCESS || cairo_status(window->drw) != CAIRO_STATUS_SUCCESS) {
return -1;
}
bool has_looped_once = false;
struct pollfd fds[UI_WINDOW_MAX_POLL] = {0};
for (int i = 0; i < UI_WINDOW_MAX_POLL; i++) {
fds[i].fd = -1;
fds[i].events = 0;
fds[i].revents = 0;
}
fds[0].fd = xcb_get_file_descriptor(window->_xcb_connection);
fds[0].events = POLLIN;
int64_t frame_peak_ms = 0;
int64_t frame_peak_last_measurement_ms = 0;
2023-04-13 00:27:08 +03:00
for (;;) {
int64_t poll_timeout;
/* compute `poll_timeout` based on active timers */
{
int64_t now = time_current_ms();
2023-04-13 04:11:15 +03:00
int64_t lowest = INT64_MAX;
2023-04-13 00:27:08 +03:00
bool has_timer = false;
for (int i = 0; i < UI_WINDOW_MAX_TIMERS; i++) {
UIWindowTimer *timer = &window->timers[i];
if (timer->present) {
has_timer = true;
int64_t distance = (timer->started_at_ms + timer->duration_ms) - now;
if (distance < lowest) {
lowest = distance;
}
}
}
poll_timeout = has_timer ? lowest : -1;
if (!has_looped_once) {
// Makes sure that we handle any pending events before entering the loop for the first time
poll_timeout = 0;
}
}
{
if (poll(fds, UI_WINDOW_MAX_POLL, poll_timeout) < 0) {
fprintf(stderr, "err: window_turn(): poll() failed\n");
return -1;
}
}
clear_summary();
int64_t frame_start_ms = time_current_ms();
2023-04-13 00:27:08 +03:00
for (int i = 0; i < UI_WINDOW_MAX_POLL; i++) {
if (fds[i].revents & POLLNVAL) {
fprintf(stderr, "err: window_turn(): poll got POLLNVAL\n");
return -1;
}
if (fds[i].revents & POLLERR) {
fprintf(stderr, "err: window_turn(): poll got POLLERR\n");
return -1;
}
if (fds[i].revents & POLLHUP) {
fprintf(stderr, "err: window_turn(): poll got POLLHUP\n");
return -1;
}
if ((i == 0 && fds[i].revents & POLLIN) || !has_looped_once) {
xcb_generic_event_t *xcb_event;
while ((xcb_event = xcb_poll_for_event(window->_xcb_connection))) {
_window_process_xcb_event(window, xcb_event);
free(xcb_event);
}
}
}
/* process finished timers */
{
for (int j = 0; j < UI_WINDOW_MAX_TIMERS; j++) {
UIWindowTimer *timer = &window->timers[j];
if (timer->present) {
int64_t now = time_current_ms();
2023-04-13 00:27:08 +03:00
int64_t distance = (timer->started_at_ms + timer->duration_ms) - now;
if (distance <= 0) {
int64_t dt = time_current_ms() - timer->started_at_ms;
2023-04-13 00:27:08 +03:00
UINode *target = timer->target;
timer->present = false;
timer->started_at_ms = 0;
timer->duration_ms = 0;
timer->target = NULL;
if (target) {
node_dispatch(target, UI_EVENT_TIMER_END, 0, &dt);
}
}
}
}
}
begin_clock("Flush invalidated widgets");
if (window_flush_invalidated(window)) {
begin_clock("Flush painting results to XCB");
xcb_flush(window->_xcb_connection);
end_clock();
}
end_clock();
has_looped_once = true;
int64_t frame_end_ms = time_current_ms();
int64_t frame_delta_ms = frame_end_ms - frame_start_ms;
2023-04-13 00:27:08 +03:00
if (frame_delta_ms > frame_peak_ms || frame_end_ms - frame_peak_last_measurement_ms >= 3000) {
frame_peak_last_measurement_ms = frame_end_ms;
frame_peak_ms = frame_delta_ms;
printf("peak frametime: %ldms\n", frame_peak_ms);
}
dump_summary();
}
return 0;
}
UIWindowTimer *window_sched_timer(UIWindow *window, UINode *target, int64_t duration_ms)
2023-04-13 00:27:08 +03:00
{
for (int i = 0; i < UI_WINDOW_MAX_TIMERS; i++) {
if (!window->timers[i].present) {
UIWindowTimer *timer = &window->timers[i];
timer->present = true;
timer->duration_ms = duration_ms;
timer->target = target;
timer->started_at_ms = time_current_ms();
return timer;
}
}
return NULL;
}