feat: improve MTK input boxes, right-click context menus
This commit is contained in:
+87
-107
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user