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>
2023-04-13 03:47:38 +03:00
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 ;
2023-04-13 03:47:38 +03:00
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 */
{
2023-04-13 03:47:38 +03:00
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 ( ) ;
2023-04-13 03:47:38 +03:00
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 ) {
2023-04-13 03:47:38 +03:00
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 ) {
2023-04-13 03:47:38 +03:00
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 ;
2023-04-13 03:47:38 +03:00
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 ;
}
2023-04-13 03:47:38 +03:00
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 ;
}