feat: switch text to UTF-8; add Unicode key events, dead keys & Czech layout; fix numpad navigation; add NumLock support
This commit is contained in:
1 parent
32660ddd1a
commit
2aa6360b3d
101 files changed
+3733
-1639
No files matched your search
@@ -296,15 +296,13 @@ inline bool text_input_has_selection(const TextInputState& state, const char* te
|
||||
return text_input_selection_start(state, len) != text_input_selection_end(state, len);
|
||||
}
|
||||
|
||||
// Width of the first `count` bytes (a character boundary). Masked fields
|
||||
// draw one '*' per character, however many bytes it takes.
|
||||
inline int text_input_prefix_width(const char* text, int count, bool masked) {
|
||||
char prefix[512];
|
||||
int len = 0;
|
||||
while (text && text[len] && len < count && len < (int)sizeof(prefix) - 1) {
|
||||
prefix[len] = masked ? '*' : text[len];
|
||||
len++;
|
||||
}
|
||||
prefix[len] = '\0';
|
||||
return text_width(prefix);
|
||||
while (text && text[len] && len < count) len++;
|
||||
if (!masked) return text_width_n(text, len);
|
||||
return montauk::utf8::count(text, len) * text_width("*");
|
||||
}
|
||||
|
||||
inline int text_input_index_from_x(const Rect& bounds, const char* value,
|
||||
@@ -314,11 +312,13 @@ inline int text_input_index_from_x(const Rect& bounds, const char* value,
|
||||
|
||||
int len = text_input_len(value);
|
||||
int prev_w = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
int cur_w = text_input_prefix_width(value, i + 1, masked);
|
||||
for (int i = 0; i < len;) {
|
||||
int next = montauk::utf8::next_boundary(value, len, i);
|
||||
int cur_w = text_input_prefix_width(value, next, masked);
|
||||
int midpoint = prev_w + (cur_w - prev_w) / 2;
|
||||
if (rel_x < midpoint) return i;
|
||||
prev_w = cur_w;
|
||||
i = next;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
@@ -360,8 +360,11 @@ inline int text_input_mouse(TextInputState& state,
|
||||
return TEXT_INPUT_NONE;
|
||||
}
|
||||
|
||||
// Bytes >= 0x80 (any non-ASCII character) count as word characters, so word
|
||||
// jumps never stop inside a multi-byte sequence.
|
||||
inline bool text_input_word_char(char ch) {
|
||||
return (ch >= 'a' && ch <= 'z') ||
|
||||
return (uint8_t)ch >= 0x80 ||
|
||||
(ch >= 'a' && ch <= 'z') ||
|
||||
(ch >= 'A' && ch <= 'Z') ||
|
||||
(ch >= '0' && ch <= '9') ||
|
||||
ch == '_';
|
||||
@@ -574,8 +577,10 @@ inline int text_input_key(TextInputState& state,
|
||||
if (!key.pressed || !text || cap <= 0) return TEXT_INPUT_NONE;
|
||||
|
||||
int len = text_input_len(text, cap);
|
||||
state.cursor = text_input_clamp_cursor(state.cursor, len);
|
||||
state.selection_anchor = text_input_clamp_cursor(state.selection_anchor, len);
|
||||
state.cursor = montauk::utf8::floor_boundary(
|
||||
text, len, text_input_clamp_cursor(state.cursor, len));
|
||||
state.selection_anchor = montauk::utf8::floor_boundary(
|
||||
text, len, text_input_clamp_cursor(state.selection_anchor, len));
|
||||
|
||||
if (state.context.open) {
|
||||
context_menu_close(state.context);
|
||||
@@ -606,20 +611,22 @@ inline int text_input_key(TextInputState& state,
|
||||
return TEXT_INPUT_CONSUMED | (changed ? TEXT_INPUT_CHANGED : 0);
|
||||
}
|
||||
|
||||
if (key.scancode == 0x4B || key.scancode == 0x4D ||
|
||||
key.scancode == 0x47 || key.scancode == 0x4F) {
|
||||
if (montauk::nav_scancode(key) == 0x4B || montauk::nav_scancode(key) == 0x4D ||
|
||||
montauk::nav_scancode(key) == 0x47 || montauk::nav_scancode(key) == 0x4F) {
|
||||
int next = state.cursor;
|
||||
if (!key.shift && sel_start != sel_end && key.scancode == 0x4B)
|
||||
if (!key.shift && sel_start != sel_end && montauk::nav_scancode(key) == 0x4B)
|
||||
next = sel_start;
|
||||
else if (!key.shift && sel_start != sel_end && key.scancode == 0x4D)
|
||||
else if (!key.shift && sel_start != sel_end && montauk::nav_scancode(key) == 0x4D)
|
||||
next = sel_end;
|
||||
else if (key.scancode == 0x4B)
|
||||
next = key.ctrl ? text_input_prev_word_boundary(text, state.cursor) : state.cursor - 1;
|
||||
else if (key.scancode == 0x4D)
|
||||
next = key.ctrl ? text_input_next_word_boundary(text, state.cursor) : state.cursor + 1;
|
||||
else if (key.scancode == 0x47)
|
||||
else if (montauk::nav_scancode(key) == 0x4B)
|
||||
next = key.ctrl ? text_input_prev_word_boundary(text, state.cursor)
|
||||
: montauk::utf8::prev_boundary(text, state.cursor);
|
||||
else if (montauk::nav_scancode(key) == 0x4D)
|
||||
next = key.ctrl ? text_input_next_word_boundary(text, state.cursor)
|
||||
: montauk::utf8::next_boundary(text, len, state.cursor);
|
||||
else if (montauk::nav_scancode(key) == 0x47)
|
||||
next = 0;
|
||||
else if (key.scancode == 0x4F)
|
||||
else if (montauk::nav_scancode(key) == 0x4F)
|
||||
next = len;
|
||||
|
||||
text_input_set_cursor(state, len, next, key.shift);
|
||||
@@ -641,15 +648,16 @@ inline int text_input_key(TextInputState& state,
|
||||
changed = true;
|
||||
}
|
||||
} else if (state.cursor > 0) {
|
||||
text_input_delete_range(text, &len, state.cursor - 1, state.cursor);
|
||||
state.cursor--;
|
||||
int start = montauk::utf8::prev_boundary(text, state.cursor);
|
||||
text_input_delete_range(text, &len, start, state.cursor);
|
||||
state.cursor = start;
|
||||
changed = true;
|
||||
}
|
||||
state.selection_anchor = state.cursor;
|
||||
return TEXT_INPUT_CONSUMED | (changed ? TEXT_INPUT_CHANGED : 0);
|
||||
}
|
||||
|
||||
if (key.scancode == 0x53) {
|
||||
if (montauk::nav_scancode(key) == 0x53) {
|
||||
if (sel_start != sel_end) {
|
||||
text_input_delete_range(text, &len, sel_start, sel_end);
|
||||
state.cursor = sel_start;
|
||||
@@ -661,15 +669,22 @@ inline int text_input_key(TextInputState& state,
|
||||
changed = true;
|
||||
}
|
||||
} else if (state.cursor < len) {
|
||||
text_input_delete_range(text, &len, state.cursor, state.cursor + 1);
|
||||
int end = montauk::utf8::next_boundary(text, len, state.cursor);
|
||||
text_input_delete_range(text, &len, state.cursor, end);
|
||||
changed = true;
|
||||
}
|
||||
state.selection_anchor = state.cursor;
|
||||
return TEXT_INPUT_CONSUMED | (changed ? TEXT_INPUT_CHANGED : 0);
|
||||
}
|
||||
|
||||
if (key.ctrl || key.alt || !text_input_char_allowed(key.ascii, filter, userdata))
|
||||
return TEXT_INPUT_NONE;
|
||||
// Typed text is inserted as UTF-8; the filter sees each encoded byte.
|
||||
char typed[montauk::utf8::MAX_BYTES];
|
||||
int typed_len = key.ctrl || key.alt ? 0 : montauk::utf8::key_utf8(key, typed);
|
||||
if (typed_len == 0) return TEXT_INPUT_NONE;
|
||||
for (int i = 0; i < typed_len; i++) {
|
||||
if (!text_input_char_allowed(typed[i], filter, userdata))
|
||||
return TEXT_INPUT_NONE;
|
||||
}
|
||||
|
||||
if (sel_start != sel_end) {
|
||||
text_input_delete_range(text, &len, sel_start, sel_end);
|
||||
@@ -677,11 +692,12 @@ inline int text_input_key(TextInputState& state,
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (len < cap - 1) {
|
||||
if (len + typed_len < cap) {
|
||||
for (int i = len; i >= state.cursor; i--)
|
||||
text[i + 1] = text[i];
|
||||
text[state.cursor++] = key.ascii;
|
||||
len++;
|
||||
text[i + typed_len] = text[i];
|
||||
for (int i = 0; i < typed_len; i++)
|
||||
text[state.cursor++] = typed[i];
|
||||
len += typed_len;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
@@ -1251,22 +1267,34 @@ inline void draw_badge(Canvas& c,
|
||||
c.text(x + theme.gap_sm, y + 2, label, fg);
|
||||
}
|
||||
|
||||
// One '*' per UTF-8 character.
|
||||
inline const char* mask_text(const char* value, char* buffer, int cap) {
|
||||
int len = 0;
|
||||
while (value && value[len] && len < cap - 1) {
|
||||
buffer[len] = '*';
|
||||
len++;
|
||||
const char* p = value ? value : "";
|
||||
while (*p && len < cap - 1) {
|
||||
montauk::utf8::next(&p);
|
||||
buffer[len++] = '*';
|
||||
}
|
||||
buffer[len] = '\0';
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// First `count` bytes of value (a character boundary), or one '*' per
|
||||
// character in them when masked.
|
||||
inline void copy_prefix(char* buffer, int cap, const char* value, int count, bool masked) {
|
||||
if (cap <= 0) return;
|
||||
int len = 0;
|
||||
while (value && value[len] && len < count && len < cap - 1) {
|
||||
buffer[len] = masked ? '*' : value[len];
|
||||
len++;
|
||||
if (masked) {
|
||||
const char* p = value ? value : "";
|
||||
while (*p && p - value < count && len < cap - 1) {
|
||||
montauk::utf8::next(&p);
|
||||
buffer[len++] = '*';
|
||||
}
|
||||
} else {
|
||||
while (value && value[len] && len < count && len < cap - 1) {
|
||||
buffer[len] = value[len];
|
||||
len++;
|
||||
}
|
||||
}
|
||||
buffer[len] = '\0';
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user