feat: improve MTK input boxes, right-click context menus

This commit is contained in:
2026-05-06 08:03:14 +02:00
parent f19e97966d
commit 8997164203
15 changed files with 1337 additions and 256 deletions
+87 -107
View File
@@ -38,7 +38,7 @@ enum Tab {
struct Field {
const char* label;
char value[FIELD_TEXT_CAP];
int cursor;
mtk::TextInputState input;
};
static const char* const kTabLabels[TAB_COUNT] = {
@@ -54,10 +54,10 @@ static int g_focus_field = -1;
static Montauk::NetCfg g_cfg = {};
static Montauk::NetStatus g_net = {};
static Field g_fields[FIELD_COUNT] = {
{"IP Address", {}, 0},
{"Subnet Mask", {}, 0},
{"Gateway", {}, 0},
{"DNS Server", {}, 0},
{"IP Address", {}, {}},
{"Subnet Mask", {}, {}},
{"Gateway", {}, {}},
{"DNS Server", {}, {}},
};
static bool g_dirty = false;
static char g_status[128] = {};
@@ -108,6 +108,11 @@ static bool is_ipv4_input_char(char ch) {
return (ch >= '0' && ch <= '9') || ch == '.';
}
static bool accept_ipv4_input_char(char ch, void* userdata) {
(void)userdata;
return is_ipv4_input_char(ch);
}
static void focus_field(int index, int cursor = -1) {
if (index < 0) index = 0;
if (index >= FIELD_COUNT) index = FIELD_COUNT - 1;
@@ -118,7 +123,10 @@ static void focus_field(int index, int cursor = -1) {
if (cursor > len) cursor = len;
g_focus_field = index;
field.cursor = cursor;
for (int i = 0; i < FIELD_COUNT; i++) {
if (i != index) mtk::context_menu_close(g_fields[i].input.context);
}
mtk::text_input_reset(field.input, cursor);
}
static bool mac_is_zero(const uint8_t* mac) {
@@ -233,7 +241,7 @@ static void copy_cfg_to_fields() {
format_ip(g_fields[2].value, sizeof(g_fields[2].value), g_cfg.gateway);
format_ip(g_fields[3].value, sizeof(g_fields[3].value), g_cfg.dnsServer);
for (int i = 0; i < FIELD_COUNT; i++) {
g_fields[i].cursor = str_len(g_fields[i].value);
mtk::text_input_reset(g_fields[i].input, str_len(g_fields[i].value));
}
g_dirty = false;
}
@@ -324,30 +332,6 @@ static int field_w(int index) {
return config_two_columns() ? (available - GAP) / 2 : available;
}
static int field_cursor_from_x(int index, int mx) {
if (index < 0 || index >= FIELD_COUNT) return 0;
Rect input = field_input_rect(index);
int rel_x = mx - (input.x + 8);
if (rel_x <= 0) return 0;
Field& field = g_fields[index];
int len = str_len(field.value);
char prefix[FIELD_TEXT_CAP] = {};
int prev_w = 0;
for (int i = 0; i < len; i++) {
prefix[i] = field.value[i];
prefix[i + 1] = '\0';
int cur_w = text_width(prefix);
int midpoint = prev_w + (cur_w - prev_w) / 2;
if (rel_x < midpoint) return i;
prev_w = cur_w;
}
return len;
}
static void draw_kv(Canvas& c, int* y, const char* key, const char* value,
const mtk::Theme& theme) {
int fh = system_font_height();
@@ -410,8 +394,19 @@ static void draw_config_tab(Canvas& c, const mtk::Theme& theme) {
for (int i = 0; i < FIELD_COUNT; i++) {
mtk::draw_labeled_text_field(c, field_x(i), field_label_y(i), field_w(i),
g_fields[i].label, g_fields[i].value,
g_fields[i].cursor, g_focus_field == i,
false, theme, FIELD_H);
g_fields[i].input.cursor, g_focus_field == i,
false, theme, FIELD_H,
g_fields[i].input.selection_anchor);
}
}
static void draw_config_context_menus(Canvas& c, const mtk::Theme& theme) {
for (int i = 0; i < FIELD_COUNT; i++) {
mtk::draw_text_input_context_menu(
c, g_fields[i].input, theme,
mtk::text_input_has_selection(g_fields[i].input,
g_fields[i].value,
(int)sizeof(g_fields[i].value)));
}
}
@@ -464,6 +459,8 @@ static void render() {
draw_config_tab(c, theme);
}
draw_footer(c, theme);
if (g_tab == TAB_CONFIG)
draw_config_context_menus(c, theme);
host.present();
}
@@ -530,17 +527,27 @@ static void set_tab(Tab tab) {
focus_field(0);
} else {
g_focus_field = -1;
for (int i = 0; i < FIELD_COUNT; i++) {
g_fields[i].input.dragging = false;
mtk::context_menu_close(g_fields[i].input.context);
}
}
}
static bool handle_click(int mx, int my) {
int tab = mtk::hit_tab_bar(tab_bar_rect(), TAB_COUNT, mx, my);
if (tab >= 0) {
set_tab((Tab)tab);
return true;
static bool handle_mouse(int mx, int my, uint8_t buttons, uint8_t prev_buttons) {
bool left_pressed = (buttons & 1) && !(prev_buttons & 1);
bool right_pressed = (buttons & 2) && !(prev_buttons & 2);
if (left_pressed) {
int tab = mtk::hit_tab_bar(tab_bar_rect(), TAB_COUNT, mx, my);
if (tab >= 0) {
set_tab((Tab)tab);
return true;
}
}
if (g_tab == TAB_STATUS) {
if (!left_pressed) return false;
if (status_refresh_button().contains(mx, my)) {
refresh_state(!g_dirty);
set_status("Network status refreshed");
@@ -558,12 +565,44 @@ static bool handle_click(int mx, int my) {
}
for (int i = 0; i < FIELD_COUNT; i++) {
if (field_input_rect(i).contains(mx, my)) {
focus_field(i, field_cursor_from_x(i, mx));
if (!g_fields[i].input.context.open && !g_fields[i].input.dragging) continue;
int result = mtk::text_input_handle_mouse(g_fields[i].input,
field_input_rect(i),
g_fields[i].value,
(int)sizeof(g_fields[i].value),
mx, my, buttons, prev_buttons,
g_win.width, g_win.height,
g_focus_field == i,
false, accept_ipv4_input_char);
if (result & mtk::TEXT_INPUT_CHANGED)
g_dirty = true;
return result != mtk::TEXT_INPUT_NONE;
}
if (!left_pressed && !right_pressed) return false;
for (int i = 0; i < FIELD_COUNT; i++) {
Rect input = field_input_rect(i);
if (input.contains(mx, my)) {
g_focus_field = i;
for (int j = 0; j < FIELD_COUNT; j++) {
if (j != i) mtk::context_menu_close(g_fields[j].input.context);
}
int result = mtk::text_input_handle_mouse(g_fields[i].input, input,
g_fields[i].value,
(int)sizeof(g_fields[i].value),
mx, my, buttons, prev_buttons,
g_win.width, g_win.height,
true, false,
accept_ipv4_input_char);
if (result & mtk::TEXT_INPUT_CHANGED)
g_dirty = true;
return true;
}
}
if (!left_pressed) return false;
if (config_dhcp_button().contains(mx, my)) {
launch_dhcp();
return true;
@@ -584,38 +623,6 @@ static bool handle_click(int mx, int my) {
return true;
}
static void delete_before_cursor(Field* field) {
if (!field || field->cursor <= 0) return;
int len = str_len(field->value);
for (int i = field->cursor - 1; i < len; i++) {
field->value[i] = field->value[i + 1];
}
field->cursor--;
g_dirty = true;
}
static void delete_at_cursor(Field* field) {
if (!field) return;
int len = str_len(field->value);
if (field->cursor >= len) return;
for (int i = field->cursor; i < len; i++) {
field->value[i] = field->value[i + 1];
}
g_dirty = true;
}
static void insert_char(Field* field, char ch) {
if (!field) return;
if (!((ch >= '0' && ch <= '9') || ch == '.')) return;
int len = str_len(field->value);
if (len >= FIELD_TEXT_CAP - 1) return;
for (int i = len; i >= field->cursor; i--) {
field->value[i + 1] = field->value[i];
}
field->value[field->cursor++] = ch;
g_dirty = true;
}
static bool handle_key(const Montauk::KeyEvent& key) {
if (!key.pressed) return false;
@@ -664,38 +671,12 @@ static bool handle_key(const Montauk::KeyEvent& key) {
}
Field* field = &g_fields[g_focus_field];
int len = str_len(field->value);
if (key.ascii == '\b' || key.scancode == 0x0E) {
delete_before_cursor(field);
return true;
}
if (key.scancode == 0x53) {
delete_at_cursor(field);
return true;
}
if (key.scancode == 0x4B) {
if (field->cursor > 0) field->cursor--;
return true;
}
if (key.scancode == 0x4D) {
if (field->cursor < len) field->cursor++;
return true;
}
if (key.scancode == 0x47) {
field->cursor = 0;
return true;
}
if (key.scancode == 0x4F) {
field->cursor = len;
return true;
}
if (is_ipv4_input_char(key.ascii)) {
insert_char(field, key.ascii);
return true;
}
return false;
int result = mtk::text_input_key(field->input, field->value,
(int)sizeof(field->value), key,
accept_ipv4_input_char);
if (result & mtk::TEXT_INPUT_CHANGED)
g_dirty = true;
return (result & mtk::TEXT_INPUT_CONSUMED) != 0;
}
extern "C" void _start() {
@@ -737,9 +718,8 @@ extern "C" void _start() {
g_mouse_x = ev.mouse.x;
g_mouse_y = ev.mouse.y;
redraw = true;
bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
if (clicked && handle_click(ev.mouse.x, ev.mouse.y)) {
if (handle_mouse(ev.mouse.x, ev.mouse.y,
ev.mouse.buttons, ev.mouse.prev_buttons)) {
redraw = true;
}
} else if (ev.type == 0) {