overhaul and improve performance of computed extents

This commit is contained in:
hippoz 2023-04-27 00:39:31 +03:00
parent 2ca13c6334
commit 1e2e11cd2b
Signed by: hippoz
GPG key ID: 56C4E02A85F2FBED
8 changed files with 68 additions and 75 deletions

View file

@ -13,10 +13,10 @@ OBJS=$(patsubst $(SRC)/%.c, $(OBJ)/%.o, $(SRCS))
DEPS=$(OBJS:%.o=%.d) DEPS=$(OBJS:%.o=%.d)
all: CFLAGS+=-Og -ggdb -DNDEBUG all: CFLAGS+=-fsanitize=address -Og -ggdb
all: $(BUILD) $(BIN) all: $(BUILD) $(BIN)
release: CFLAGS+=-O2 -flto=auto -DNDEBUG release: CFLAGS+=-O2 -flto=auto -DNDEBUG -ggdb
release: clean $(BUILD) $(BIN) release: clean $(BUILD) $(BIN)
$(BIN): $(OBJS) $(BIN): $(OBJS)

View file

@ -2,6 +2,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <math.h> #include <math.h>
#include "box-layout-node.h" #include "box-layout-node.h"
#include "node.h"
#include "rect.h"
int box_layout_handle(UINode *component, enum UIEvent ev, size_t d, void *p) int box_layout_handle(UINode *component, enum UIEvent ev, size_t d, void *p)
{ {
@ -27,38 +29,33 @@ int box_layout_handle(UINode *component, enum UIEvent ev, size_t d, void *p)
int growing_widgets = 0; int growing_widgets = 0;
int current_index = 0; int current_index = 0;
if (ev == UI_EVENT_RELAYOUT || ev == UI_EVENT_GET_HEIGHT || ev == UI_EVENT_GET_WIDTH) { if (ev == UI_EVENT_RELAYOUT || ev == UI_EVENT_GET_EXTENTS) {
for (int i = 0; i < node->nodes_count; i++) { for (int i = 0; i < node->nodes_count; i++) {
UINode *current = node->nodes[i]; UINode *current = node->nodes[i];
if (current->flags & UI_NODE_COMPONENT) continue; if (current->flags & UI_NODE_COMPONENT) continue;
double w = current->rect.w; UIRect current_rect = node_get_computed_rect(current);
double h = current->rect.h;
if (current->width_policy == UI_SIZE_POLICY_DYNAMIC) { current->rect.w = current_rect.w;
node_dispatch(current, UI_EVENT_GET_WIDTH, 0, &w); current->rect.h = current_rect.h;
current->rect.w = w;
}
if (current->height_policy == UI_SIZE_POLICY_DYNAMIC) {
node_dispatch(current, UI_EVENT_GET_HEIGHT, 0, &h);
current->rect.h = h;
}
if (is_horizontal) { if (is_horizontal) {
if (h > maximum_secondary_position) { if (current_rect.h > maximum_secondary_position) {
maximum_secondary_position = h; maximum_secondary_position = current_rect.h;
} }
if (current->width_policy == UI_SIZE_POLICY_GROW) { if (current->width_policy == UI_SIZE_POLICY_GROW) {
growing_widgets++; growing_widgets++;
} else { } else {
size += w; size += current_rect.w;
} }
} else { } else {
if (w > maximum_secondary_position) { if (current_rect.w > maximum_secondary_position) {
maximum_secondary_position = w; maximum_secondary_position = current_rect.w;
} }
if (current->height_policy == UI_SIZE_POLICY_GROW) { if (current->height_policy == UI_SIZE_POLICY_GROW) {
growing_widgets++; growing_widgets++;
} else { } else {
size += h; size += current_rect.h;
} }
} }
if (current_index) { if (current_index) {
@ -70,18 +67,14 @@ int box_layout_handle(UINode *component, enum UIEvent ev, size_t d, void *p)
maximum_secondary_position += is_horizontal ? margin_top + margin_bottom : margin_left + margin_right; maximum_secondary_position += is_horizontal ? margin_top + margin_bottom : margin_left + margin_right;
size += is_vertical ? margin_top + margin_bottom : margin_left + margin_right; size += is_vertical ? margin_top + margin_bottom : margin_left + margin_right;
if (ev == UI_EVENT_GET_WIDTH) { if (ev == UI_EVENT_GET_EXTENTS) {
*(double*)p = is_horizontal ? size : maximum_secondary_position; ((UIRect*)p)->w = is_horizontal ? size : maximum_secondary_position;
return 1; ((UIRect*)p)->h = is_vertical ? size : maximum_secondary_position;
return UI_NODE_COMPUTED_EXTENTS_OK;
} }
if (ev == UI_EVENT_GET_HEIGHT) {
*(double*)p = is_vertical ? size : maximum_secondary_position;
return 1;
}
// we can distribute growing layout children only if we have a fixed sized policy in the layout primary direction // we can distribute growing layout children only if we have a fixed sized policy in the layout primary direction
bool is_fixed_for_primary_direction = (is_horizontal && node->width_policy != UI_SIZE_POLICY_DYNAMIC) || (is_vertical && node->height_policy != UI_SIZE_POLICY_DYNAMIC); bool is_fixed_for_primary_direction = (is_horizontal && node->width_policy != UI_SIZE_POLICY_COMPUTED) || (is_vertical && node->height_policy != UI_SIZE_POLICY_COMPUTED);
double primary_direction = is_horizontal ? node->rect.w - margin_left - margin_right : node->rect.h - margin_top - margin_bottom; double primary_direction = is_horizontal ? node->rect.w - margin_left - margin_right : node->rect.h - margin_top - margin_bottom;
bool distributes_growing_widgets = growing_widgets && is_fixed_for_primary_direction; bool distributes_growing_widgets = growing_widgets && is_fixed_for_primary_direction;
double size_per_growing_widget = 0; double size_per_growing_widget = 0;
@ -95,7 +88,7 @@ int box_layout_handle(UINode *component, enum UIEvent ev, size_t d, void *p)
} }
} }
bool is_fixed_for_secondary_direction = (is_horizontal && node->height_policy != UI_SIZE_POLICY_DYNAMIC) || (is_vertical && node->width_policy != UI_SIZE_POLICY_DYNAMIC); bool is_fixed_for_secondary_direction = (is_horizontal && node->height_policy != UI_SIZE_POLICY_COMPUTED) || (is_vertical && node->width_policy != UI_SIZE_POLICY_COMPUTED);
double secondary_direction = is_horizontal ? node->rect.h - margin_top - margin_bottom : node->rect.w - margin_left - margin_right; double secondary_direction = is_horizontal ? node->rect.h - margin_top - margin_bottom : node->rect.w - margin_left - margin_right;
// if our layout secondary direction is fixed, we set the maximum known secondary position to it // if our layout secondary direction is fixed, we set the maximum known secondary position to it
if (is_fixed_for_secondary_direction) { if (is_fixed_for_secondary_direction) {

View file

@ -125,20 +125,18 @@ int app_handle(struct UINode *node, void *data, int event_type, size_t d, void *
UIScrollableNode *scroll = scrollable_new(scroll_container, NULL); UIScrollableNode *scroll = scrollable_new(scroll_container, NULL);
UINode *buttons = node_new(scroll_container, "buttons"); UINode *buttons = node_new(scroll_container, "buttons");
buttons->width_policy = UI_SIZE_POLICY_GROW; buttons->width_policy = UI_SIZE_POLICY_GROW;
buttons->height_policy = UI_SIZE_POLICY_DYNAMIC; buttons->height_policy = UI_SIZE_POLICY_COMPUTED;
scroll->target = buttons; scroll->target = buttons;
box_layout_new(buttons, UI_DIRECTION_VERTICAL); box_layout_new(buttons, UI_DIRECTION_VERTICAL);
for (int i = 0; i < 10000; i++) { for (int i = 0; i < 3; i++) {
UINode *button = node_new(buttons, "button"); UINode *button = node_new(buttons, "button");
state_background_node_new(button, UIPurple600, UIPurple700, UIPurple800, 6.0); state_background_node_new(button, UIPurple600, UIPurple700, UIPurple800, 6.0);
text_node_new(button, state->font, UINeutral50, "button"); text_node_new(button, state->font, UINeutral50, "button");
dispatcher_new(button, UI_EVENT_UNPRESSED, INCREMENT_COUNT, state, app_handle); dispatcher_new(button, UI_EVENT_UNPRESSED, INCREMENT_COUNT, state, app_handle);
button->width_policy = UI_SIZE_POLICY_GROW; button->width_policy = UI_SIZE_POLICY_GROW;
} }
printf("%d\n", buttons->last_component_index);
} }
break; break;

View file

@ -6,6 +6,24 @@
typedef struct UIWindow UIWindow; typedef struct UIWindow UIWindow;
int window_invalidate_node(UIWindow *window, UINode *node); int window_invalidate_node(UIWindow *window, UINode *node);
UIRect node_get_computed_rect(UINode *node) {
UIRect rect = node->rect;
UIRect extents = node->rect;
if (!node->cached_computed_extents && (node->width_policy == UI_SIZE_POLICY_COMPUTED || node->height_policy == UI_SIZE_POLICY_COMPUTED)) {
if (node_dispatch(node, UI_EVENT_GET_EXTENTS, 0, &extents) == UI_NODE_COMPUTED_EXTENTS_CACHED) {
// Whoever returned UI_NODE_COMPUTED_EXTENTS_CACHED has the responsibility to set
// cached_computed_extents back to false when the cache is no longer valid.
node->cached_computed_extents = true;
}
}
rect.w = node->width_policy == UI_SIZE_POLICY_COMPUTED ? extents.w : rect.w;
rect.h = node->height_policy == UI_SIZE_POLICY_COMPUTED ? extents.h : rect.h;
return rect;
}
int node_send(UINode *node, enum UIEvent ev, size_t d, void *p) int node_send(UINode *node, enum UIEvent ev, size_t d, void *p)
{ {
if (node->flags & UI_NODE_DISABLED) { if (node->flags & UI_NODE_DISABLED) {
@ -36,7 +54,7 @@ int node_dispatch(UINode *node, enum UIEvent ev, size_t d, void *p)
} }
if (node->nodes[i]->flags & UI_NODE_COMPONENT) { if (node->nodes[i]->flags & UI_NODE_COMPONENT) {
component_index = i; component_index = i;
result = node_dispatch(node->nodes[i], ev, d, p); result = node_send(node->nodes[i], ev, d, p);
if (result) break; if (result) break;
} }
} }
@ -78,11 +96,12 @@ void node_init(UINode *node)
node->nodes_count = 0; node->nodes_count = 0;
node->nodes_capacity = 0; node->nodes_capacity = 0;
node->flags = 0; node->flags = 0;
node->width_policy = UI_SIZE_POLICY_DYNAMIC; node->width_policy = UI_SIZE_POLICY_COMPUTED;
node->height_policy = UI_SIZE_POLICY_DYNAMIC; node->height_policy = UI_SIZE_POLICY_COMPUTED;
node->handle = NULL; node->handle = NULL;
node->handle_proto = NULL; node->handle_proto = NULL;
node->last_component_index = UI_NODE_LAST_COMPONENT_INDEX_NONE; node->last_component_index = UI_NODE_LAST_COMPONENT_INDEX_NONE;
node->cached_computed_extents = false;
} }
void node_free(UINode *node) void node_free(UINode *node)

View file

@ -6,11 +6,12 @@
#include "rect.h" #include "rect.h"
#include <cairo.h> #include <cairo.h>
#define UI_NODE_COMPUTED_EXTENTS_OK (1)
#define UI_NODE_COMPUTED_EXTENTS_CACHED (2)
enum UIEvent { enum UIEvent {
UI_EVENT_REPAINT, UI_EVENT_REPAINT,
UI_EVENT_RELAYOUT, UI_EVENT_RELAYOUT,
UI_EVENT_GET_WIDTH, UI_EVENT_GET_EXTENTS,
UI_EVENT_GET_HEIGHT,
UI_EVENT_HOVERED, UI_EVENT_HOVERED,
UI_EVENT_UNHOVERED, UI_EVENT_UNHOVERED,
UI_EVENT_PRESSED, UI_EVENT_PRESSED,
@ -26,7 +27,7 @@ enum UIEvent {
}; };
enum UISizePolicy { enum UISizePolicy {
UI_SIZE_POLICY_DYNAMIC, UI_SIZE_POLICY_COMPUTED,
UI_SIZE_POLICY_GROW, UI_SIZE_POLICY_GROW,
UI_SIZE_POLICY_FIXED, UI_SIZE_POLICY_FIXED,
UI_SIZE_POLICY_STATIC UI_SIZE_POLICY_STATIC
@ -56,11 +57,13 @@ typedef struct UINode {
enum UISizePolicy width_policy; enum UISizePolicy width_policy;
enum UISizePolicy height_policy; enum UISizePolicy height_policy;
int last_component_index; int last_component_index;
bool cached_computed_extents;
int (*handle)(struct UINode *node, enum UIEvent ev, size_t d, void *p); int (*handle)(struct UINode *node, enum UIEvent ev, size_t d, void *p);
int (*handle_proto)(struct UINode *node, enum UIEvent ev, size_t d, void *p); int (*handle_proto)(struct UINode *node, enum UIEvent ev, size_t d, void *p);
} UINode; } UINode;
UIRect node_get_computed_rect(UINode *node);
int node_send(UINode *node, enum UIEvent ev, size_t d, void *p); int node_send(UINode *node, enum UIEvent ev, size_t d, void *p);
int node_dispatch(UINode *node, enum UIEvent ev, size_t d, void *p); int node_dispatch(UINode *node, enum UIEvent ev, size_t d, void *p);
UINode *node_by_point(UINode *root, double x, double y); UINode *node_by_point(UINode *root, double x, double y);

View file

@ -1,5 +1,6 @@
#include "scrollable-node.h" #include "scrollable-node.h"
#include "node.h" #include "node.h"
#include "rect.h"
#include "window.h" #include "window.h"
#include <math.h> #include <math.h>
#include <stdio.h> #include <stdio.h>
@ -49,20 +50,10 @@ int scrollable_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
break; break;
} }
case UI_EVENT_RELAYOUT: { case UI_EVENT_RELAYOUT: {
double w = target->rect.w; UIRect rect = {0};
double h = target->rect.h; node_dispatch(target, UI_EVENT_GET_EXTENTS, 0, &rect);
if (target->width_policy == UI_SIZE_POLICY_DYNAMIC) { target->rect.w = target->width_policy == UI_SIZE_POLICY_GROW ? node->parent->rect.w : rect.w;
node_dispatch(target, UI_EVENT_GET_WIDTH, 0, &w); target->rect.h = target->height_policy == UI_SIZE_POLICY_GROW ? node->parent->rect.h : rect.h;
target->rect.w = w;
} else if (target->width_policy == UI_SIZE_POLICY_GROW) {
target->rect.w = node->parent->rect.w;
}
if (target->height_policy == UI_SIZE_POLICY_DYNAMIC) {
node_dispatch(target, UI_EVENT_GET_HEIGHT, 0, &h);
target->rect.h = h;
} else if (target->height_policy == UI_SIZE_POLICY_GROW) {
target->rect.h = node->parent->rect.h;
}
node_dispatch(target, UI_EVENT_RELAYOUT, 0, NULL); node_dispatch(target, UI_EVENT_RELAYOUT, 0, NULL);
break; break;
} }

View file

@ -4,6 +4,7 @@
#include "node.h" #include "node.h"
#include "pango/pango-layout.h" #include "pango/pango-layout.h"
#include "pango/pango-types.h" #include "pango/pango-types.h"
#include "rect.h"
#include "text-node.h" #include "text-node.h"
UITextNode *text_node_new(UINode *parent, PangoFontDescription *desc, UIRGBA color, char *text) UITextNode *text_node_new(UINode *parent, PangoFontDescription *desc, UIRGBA color, char *text)
@ -17,9 +18,6 @@ UITextNode *text_node_new(UINode *parent, PangoFontDescription *desc, UIRGBA col
n->color = color; n->color = color;
n->caret_index = -1; n->caret_index = -1;
n->caret_node = NULL; n->caret_node = NULL;
n->computed_dimensions_invalid = true;
n->computed_text_width = 0;
n->computed_text_height = 0;
n->node.text = "text"; n->node.text = "text";
n->node.handle_proto = text_node_handle; n->node.handle_proto = text_node_handle;
node_attach(parent, (UINode*)n); node_attach(parent, (UINode*)n);
@ -50,7 +48,7 @@ int text_node_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
n->layout = layout; n->layout = layout;
n->pending_text = NULL; n->pending_text = NULL;
} }
n->computed_dimensions_invalid = true; node->parent->cached_computed_extents = false;
} }
switch (ev) { switch (ev) {
@ -63,22 +61,16 @@ int text_node_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
cairo_fill(n->node.drw); cairo_fill(n->node.drw);
break; break;
} }
case UI_EVENT_GET_WIDTH: /* through */ case UI_EVENT_GET_EXTENTS: {
case UI_EVENT_GET_HEIGHT: { if (!n->layout) {
if (n->computed_dimensions_invalid) { break;
int w, h = 0;
pango_layout_get_size(n->layout, &w, &h);
n->computed_text_width = pango_units_to_double(w);
n->computed_text_height = pango_units_to_double(h);
n->computed_dimensions_invalid = false;
} }
if (ev == UI_EVENT_GET_WIDTH) { int w, h = 0;
*(double*)p = n->computed_text_width; pango_layout_get_size(n->layout, &w, &h);
}
if (ev == UI_EVENT_GET_HEIGHT) { ((UIRect*)p)->w = pango_units_to_double(w);
*(double*)p = n->computed_text_height; ((UIRect*)p)->h = pango_units_to_double(h);
} return UI_NODE_COMPUTED_EXTENTS_CACHED;
break;
} }
case UI_EVENT_RELAYOUT: { case UI_EVENT_RELAYOUT: {
if (!n->layout) { if (!n->layout) {

View file

@ -15,9 +15,6 @@ typedef struct UITextNode {
UIRGBA color; UIRGBA color;
ssize_t caret_index; ssize_t caret_index;
UINode *caret_node; UINode *caret_node;
double computed_text_width;
double computed_text_height;
bool computed_dimensions_invalid;
} UITextNode; } UITextNode;
int text_node_handle(UINode *node, enum UIEvent ev, size_t d, void *p); int text_node_handle(UINode *node, enum UIEvent ev, size_t d, void *p);