diff options
author | Přemysl Eric Janouch <p@janouch.name> | 2021-11-03 18:14:36 +0100 |
---|---|---|
committer | Přemysl Eric Janouch <p@janouch.name> | 2021-11-04 11:43:18 +0100 |
commit | 0d0ac40f967c3ae2c2f05c3837c3b8512366ffa6 (patch) | |
tree | 03ea166d47c5fdd75d902a46d4b842ff0fd99dae /src | |
parent | 181df7fbaeaa3ae070c724436c5a5f62fbcb9a32 (diff) | |
download | tdv-0d0ac40f967c3ae2c2f05c3837c3b8512366ffa6.tar.gz tdv-0d0ac40f967c3ae2c2f05c3837c3b8512366ffa6.tar.xz tdv-0d0ac40f967c3ae2c2f05c3837c3b8512366ffa6.zip |
sdtui: handle input field overflows reasonably
Diffstat (limited to 'src')
-rw-r--r-- | src/sdtui.c | 150 |
1 files changed, 122 insertions, 28 deletions
diff --git a/src/sdtui.c b/src/sdtui.c index 8de59b2..259cf39 100644 --- a/src/sdtui.c +++ b/src/sdtui.c @@ -167,6 +167,7 @@ struct application gchar * search_label; ///< Text of the "Search" label GArray * input; ///< The current search input guint input_pos; ///< Cursor position within input + guint input_offset; ///< Render offset in codepoints gboolean input_confirmed; ///< Input has been confirmed gfloat division; ///< Position of the division column @@ -533,7 +534,7 @@ app_init (Application *self, char **filenames) self->search_label = g_strdup_printf ("%s: ", _("Search")); self->input = g_array_new (TRUE, FALSE, sizeof (gunichar)); - self->input_pos = 0; + self->input_pos = self->input_offset = 0; self->input_confirmed = FALSE; self->division = 0.5; @@ -796,6 +797,20 @@ row_buffer_ellipsis (RowBuffer *self, int target, chtype attrs) } static void +row_buffer_align (RowBuffer *self, int target, chtype attrs) +{ + if (target >= 0 && self->total_width > target) + row_buffer_ellipsis (self, target, attrs); + + while (self->total_width < target) + { + struct row_char rc = { ' ', attrs, 1 }; + g_array_append_val (self->chars, rc); + self->total_width += rc.width; + } +} + +static void row_buffer_print (RowBuffer *self, gunichar *ucs4, size_t len, chtype attrs) { gsize locale_str_len; @@ -837,21 +852,82 @@ row_buffer_flush (RowBuffer *self) static void row_buffer_finish (RowBuffer *self, int width, chtype attrs) { - if (width >= 0 && self->total_width > width) - row_buffer_ellipsis (self, width, attrs); - while (self->total_width < width) - { - struct row_char rc = { ' ', attrs, 1 }; - g_array_append_val (self->chars, rc); - self->total_width += rc.width; - } - + row_buffer_align (self, width, attrs); row_buffer_flush (self); row_buffer_free (self); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +static gint +app_input_width (Application *self, guint begin, guint end) +{ + gint width = 0; + for (guint i = begin; i < end; i++) + width += app_char_width (self, + g_array_index (self->input, gunichar, i)); + return width; +} + +static guint +app_scroll_back_input (Application *self, guint from, gint target) +{ + guint last_spacing = from; + while (from--) + { + gint width = app_input_width (self, from, from + 1); + if (target < width) + break; + + if (width) + { + last_spacing = from; + target -= width; + } + } + return last_spacing; +} + +static guint +app_adjust_input_offset (Application *self, gint space) +{ + gint to_cursor = + app_input_width (self, 0, self->input_pos); + gint at_cursor = + app_input_width (self, self->input_pos, self->input_pos + 1); + gint past_cursor = + app_input_width (self, self->input_pos + 1, self->input->len); + + // 1. If everything fits, no scrolling is desired, and no arrows present + if (to_cursor + at_cursor + past_cursor <= space) + return 0; + + // TODO: try to prevent 2. and 3. from fighting with each other + + // 2. If everything up to and including the cursor, plus right arrow fits, + // start at the beginning + if (to_cursor + at_cursor + 1 /* right arrow */ <= space) + return 0; + + // 3. If everything from the cursor to the right fits, fill the line, + // but keep one extra space for a trailing caret + gint reserved = self->input_pos != self->input->len; + gint from_cursor_with_trailing_caret = at_cursor + past_cursor + reserved; + if (1 /* left arrow */ + from_cursor_with_trailing_caret <= space) + return app_scroll_back_input (self, self->input->len, space - 1 - 1); + + // At this point, we know there will be arrows on both sides + space -= 2; + + // 4. If the cursor has moved too much to either side, follow it + if (self->input_pos < self->input_offset + || app_input_width (self, self->input_offset, self->input_pos + 1) > space) + return app_scroll_back_input (self, self->input_pos, space / 2); + + // 5. Otherwise, don't fiddle with the offset at all, it's not necessary + return self->input_offset; +} + /// Render the top bar. static void app_redraw_top (Application *self) @@ -871,26 +947,37 @@ app_redraw_top (Application *self) buf = row_buffer_make (self); row_buffer_append (&buf, self->search_label, APP_ATTR (SEARCH)); - gsize indent = buf.total_width; + gint indent = buf.total_width; int word_attrs = APP_ATTR (SEARCH); if (self->input_confirmed) word_attrs |= A_BOLD; - gchar *input_utf8 = g_ucs4_to_utf8 - ((gunichar *) self->input->data, -1, NULL, NULL, NULL); + self->input_offset = app_adjust_input_offset (self, COLS - indent); + if (self->input_offset) + { + row_buffer_append (&buf, "<", word_attrs ^ A_BOLD); + indent++; + } + + gchar *input_utf8 = g_ucs4_to_utf8 ((gunichar *) self->input->data + + self->input_offset, -1, NULL, NULL, NULL); g_return_if_fail (input_utf8 != NULL); row_buffer_append (&buf, input_utf8, word_attrs); g_free (input_utf8); - row_buffer_finish (&buf, COLS, APP_ATTR (SEARCH)); + gint overflow = buf.total_width - COLS; + if (overflow > 0) + { + row_buffer_pop_cells (&buf, overflow + 1 /* right arrow */); + row_buffer_align (&buf, COLS - 1 /* right arrow */, APP_ATTR (SEARCH)); + row_buffer_append (&buf, ">", word_attrs ^ A_BOLD); + } - guint offset, i; - for (offset = i = 0; i < self->input_pos; i++) - offset += app_char_width (self, - g_array_index (self->input, gunichar, i)); + row_buffer_finish (&buf, COLS, APP_ATTR (SEARCH)); + gint offset = app_input_width (self, self->input_offset, self->input_pos); - move (1, MIN ((gint) (indent + offset), COLS - 1)); + move (1, MIN (indent + offset, COLS - 1)); refresh (); } @@ -1270,7 +1357,7 @@ app_set_input (Application *self, const gchar *text, gsize text_len) g_array_free (self->input, TRUE); self->input = g_array_new (TRUE, FALSE, sizeof (gunichar)); - self->input_pos = 0; + self->input_pos = self->input_offset = 0; gunichar *p = output; gboolean last_was_space = false; @@ -1782,7 +1869,7 @@ app_process_key (Application *self, termo_key_t *event) { if (self->input->len != 0) g_array_remove_range (self->input, 0, self->input->len); - self->input_pos = 0; + self->input_pos = self->input_offset = 0; self->input_confirmed = FALSE; } @@ -1815,16 +1902,23 @@ app_process_left_mouse_click (Application *self, int line, int column) else if (line == 1) { // FIXME: this is only an approximation - gsize label_len = g_utf8_strlen (self->search_label, -1); - gint pos = column - label_len; + glong label_width = g_utf8_strlen (self->search_label, -1); + + gint pos = column - label_width; if (pos >= 0) { - guint offset, i; - for (offset = i = 0; i < self->input->len; i++) + // On clicking the left arrow, go to that invisible character + // behind the arrow (skiping over non-spacing suffixes) + guint i = self->input_offset; + if (i && !pos--) + { + while (i-- && !app_input_width (self, i, i + 1)) + ; + } + for (gint occupied = 0; i < self->input->len; i++) { - size_t width = app_char_width - (self, g_array_index (self->input, gunichar, i)); - if ((offset += width) > (guint) pos) + size_t width = app_input_width (self, i, i + 1); + if ((occupied += width) > pos) break; } |