improve text input and add caret

This commit is contained in:
hippoz 2023-04-25 14:39:37 +03:00
parent 3b534ad231
commit d8601f23f5
Signed by: hippoz
GPG key ID: 56C4E02A85F2FBED
7 changed files with 100 additions and 19 deletions

View file

@ -140,7 +140,7 @@ int box_layout_handle(UINode *component, enum UIEvent ev, size_t d, void *p)
current_index++; current_index++;
} }
return 1; return 0;
} }
return 0; return 0;
} }

View file

@ -6,8 +6,28 @@
typedef struct UIWindow UIWindow; typedef struct UIWindow UIWindow;
int window_invalidate_node(UIWindow *window, UINode *node); int window_invalidate_node(UIWindow *window, UINode *node);
int node_send(UINode *node, enum UIEvent ev, size_t d, void *p)
{
if (node->flags & UI_NODE_DISABLED) {
return 0;
}
if (node->handle) {
int result = (node->handle)(node, ev, d, p);
if (result) return result;
}
if (node->handle_proto) {
return (node->handle_proto)(node, ev, d, p);
}
return 0;
}
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)
{ {
if (node->flags & UI_NODE_DISABLED) {
return 0;
}
for (int i = 0; i < node->nodes_count; i++) { for (int i = 0; i < node->nodes_count; i++) {
if (node->nodes[i]->flags & UI_NODE_COMPONENT) { if (node->nodes[i]->flags & UI_NODE_COMPONENT) {
int result = node_dispatch(node->nodes[i], ev, d, p); int result = node_dispatch(node->nodes[i], ev, d, p);
@ -27,10 +47,11 @@ 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)
{ {
for (int i = 0; i < root->nodes_count; i++) { for (int i = 0; i < root->nodes_count; i++) {
double local_x = x - root->nodes[i]->window_rel_x; UINode *node = root->nodes[i];
double local_y = y - root->nodes[i]->window_rel_y; double local_x = x - node->window_rel_x;
if (ui_rect_contains_point(&root->nodes[i]->rect, local_x, local_y)) { double local_y = y - node->window_rel_y;
return node_by_point(root->nodes[i], local_x, local_y); if (ui_rect_contains_point(&node->rect, local_x, local_y) && !(node->flags & UI_NODE_DISABLED)) {
return node_by_point(node, local_x, local_y);
} }
} }
return root; return root;
@ -72,7 +93,7 @@ void node_repaint(UINode *node, UIRect *rect, bool do_group, double window_rel_x
node->window_rel_x = window_rel_x; node->window_rel_x = window_rel_x;
node->window_rel_y = window_rel_y; node->window_rel_y = window_rel_y;
if (!ui_rect_overlap_rect(rect, &node->rect)) { if (!ui_rect_overlap_rect(rect, &node->rect) && !(node->flags & UI_NODE_COMPONENT)) {
return; return;
} }
@ -85,18 +106,15 @@ void node_repaint(UINode *node, UIRect *rect, bool do_group, double window_rel_x
cairo_translate(node->drw, node->rect.x, node->rect.y); cairo_translate(node->drw, node->rect.x, node->rect.y);
node_dispatch(node, UI_EVENT_REPAINT, 0, NULL); node_send(node, UI_EVENT_REPAINT, 0, 0);
UIRect local_rect = { rect->x - node->rect.x, rect->y - node->rect.y, rect->w, rect->h }; UIRect local_rect = { rect->x - node->rect.x, rect->y - node->rect.y, rect->w, rect->h };
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) { if (current->flags & UI_NODE_DISABLED) {
cairo_save(current->drw); continue;
node_dispatch(current, UI_EVENT_REPAINT, 0, NULL);
cairo_restore(current->drw);
} else {
node_repaint(node->nodes[i], &local_rect, false, window_rel_x + node->rect.x, window_rel_y + node->rect.y);
} }
node_repaint(current, &local_rect, false, window_rel_x + node->rect.x, window_rel_y + node->rect.y);
} }
cairo_restore(node->drw); cairo_restore(node->drw);

View file

@ -39,6 +39,7 @@ enum UIDirection {
#define UI_NODE_COMPONENT (1 << 0) #define UI_NODE_COMPONENT (1 << 0)
#define UI_NODE_RETAINS_PRESS (1 << 1) #define UI_NODE_RETAINS_PRESS (1 << 1)
#define UI_NODE_DISABLED (1 << 2)
typedef struct UINode { typedef struct UINode {
const char *text; const char *text;
@ -57,6 +58,7 @@ typedef struct UINode {
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;
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);
void node_init(UINode *node); void node_init(UINode *node);

View file

@ -1,5 +1,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-keysyms.h>
#include "node.h" #include "node.h"
#include "text-node.h" #include "text-node.h"
#include "text-input-node.h" #include "text-input-node.h"
@ -14,6 +15,7 @@ UITextInputNode *text_input_new(UINode *parent)
n->node.handle_proto = text_input_handle; n->node.handle_proto = text_input_handle;
n->text = calloc(1, 1); n->text = calloc(1, 1);
n->text_size = 1; n->text_size = 1;
n->text_cursor_index = 0;
node_attach(parent, (UINode*)n); node_attach(parent, (UINode*)n);
return n; return n;
} }
@ -24,21 +26,51 @@ int text_input_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
switch (ev) { switch (ev) {
case UI_EVENT_KEY_DOWN: { case UI_EVENT_KEY_DOWN: {
unsigned int keycode = d; xkb_keycode_t keycode = d;
struct xkb_state *xkb_state = (struct xkb_state*)p; struct xkb_state *xkb_state = (struct xkb_state*)p;
xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, keycode);
size_t key_size = xkb_state_key_get_utf8(xkb_state, keycode, NULL, 0) + 1; switch (keysym) {
size_t new_text_size = n->text_size + key_size - 1; case XKB_KEY_BackSpace: {
n->text = realloc(n->text, new_text_size); if (n->text_cursor_index > 0 && n->text_size > 1) {
xkb_state_key_get_utf8(xkb_state, keycode, n->text + n->text_size - 1, key_size); n->text[--n->text_cursor_index] = '\0';
n->text_size = new_text_size; n->text = realloc(n->text, n->text_size--);
}
break;
}
default: {
size_t key_size = xkb_state_key_get_utf8(xkb_state, keycode, NULL, 0);
size_t new_text_size = n->text_size + key_size;
n->text = realloc(n->text, new_text_size);
xkb_state_key_get_utf8(xkb_state, keycode, n->text + n->text_cursor_index, key_size + 1);
n->text_cursor_index += key_size;
n->text_size = new_text_size;
break;
}
}
if (n->text_node) { if (n->text_node) {
n->text_node->pending_text = n->text; n->text_node->pending_text = n->text;
n->text_node->caret_index = n->text_cursor_index;
node_request_relayout(&n->text_node->node); node_request_relayout(&n->text_node->node);
} }
return 1; return 1;
} }
case UI_EVENT_PRESSED: {
if (n->text_node) {
n->text_node->caret_index = n->text_cursor_index;
node_request_relayout(&n->text_node->node);
}
break;
}
case UI_EVENT_UNPRESSED: {
if (n->text_node) {
n->text_node->caret_index = -1;
node_request_relayout(&n->text_node->node);
}
break;
}
default: { default: {
break; break;
} }

View file

@ -9,6 +9,7 @@ typedef struct UITextInputNode {
UITextNode *text_node; UITextNode *text_node;
char *text; char *text;
size_t text_size; size_t text_size;
size_t text_cursor_index;
} UITextInputNode; } UITextInputNode;
UITextInputNode *text_input_new(UINode *parent); UITextInputNode *text_input_new(UINode *parent);

View file

@ -1,5 +1,9 @@
#include <pango/pango.h> #include <pango/pango.h>
#include <pango/pangocairo.h> #include <pango/pangocairo.h>
#include "background-node.h"
#include "node.h"
#include "pango/pango-layout.h"
#include "pango/pango-types.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)
@ -11,6 +15,8 @@ UITextNode *text_node_new(UINode *parent, PangoFontDescription *desc, UIRGBA col
n->desc = desc; n->desc = desc;
n->layout = NULL; n->layout = NULL;
n->color = color; n->color = color;
n->caret_index = -1;
n->caret_node = NULL;
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);
@ -76,6 +82,26 @@ int text_node_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
break; break;
} }
pango_cairo_update_layout(n->node.drw, n->layout); pango_cairo_update_layout(n->node.drw, n->layout);
if (n->caret_index >= 0) {
if (!n->caret_node) {
n->caret_node = node_new(&n->node, "caret");
background_node_new(n->caret_node, n->color, 0.0);
}
PangoRectangle rect;
pango_layout_get_cursor_pos(n->layout, n->caret_index, &rect, NULL);
n->caret_node->rect = (UIRect){
.x = pango_units_to_double(rect.x),
.y = pango_units_to_double(rect.y),
.w = 1.0,
.h = pango_units_to_double(rect.height),
};
n->caret_node->flags &= ~UI_NODE_DISABLED;
node_dispatch(n->caret_node, UI_EVENT_RELAYOUT, 0, 0);
} else {
if (n->caret_node) {
n->caret_node->flags |= UI_NODE_DISABLED;
}
}
break; break;
} }
case UI_EVENT_DESTROY: { case UI_EVENT_DESTROY: {

View file

@ -13,6 +13,8 @@ typedef struct UITextNode {
PangoLayout *layout; PangoLayout *layout;
char *pending_text; char *pending_text;
UIRGBA color; UIRGBA color;
ssize_t caret_index;
UINode *caret_node;
} 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);