@@ -164,6 +164,98 @@ void scene_txtin_close(struct scene *scn, struct scene_txtin *tin)
vidconsole_readline_end(scn->expo->cons, tin->ctx);
}
+/**
+ * scene_txtin_line_nav() - Navigate to previous/next line in multi-line input
+ *
+ * Moves the cursor to the previous or next line, trying to maintain the same
+ * horizontal pixel position. Uses the text measurement info attached to the
+ * edit text object.
+ *
+ * @cls: CLI line state
+ * @up: true to move to previous line, false for next line
+ * Return: New cursor position, or -ve if at boundary
+ */
+static int scene_txtin_line_nav(struct cli_line_state *cls, bool up)
+{
+ struct scene_txtin *tin = container_of(cls, struct scene_txtin, cls);
+ struct scene *scn = cls->priv;
+ struct scene_obj_txt *txt;
+ const struct vidconsole_mline *mline;
+ const struct vidconsole_mline *target;
+ struct vidconsole_bbox bbox;
+ uint pos = cls->num;
+ int cur_line, target_line;
+ int target_x, best_pos, best_diff;
+ int i, ret;
+
+ txt = scene_obj_find(scn, tin->edit_id, SCENEOBJT_NONE);
+ if (!txt || !txt->gen.lines.count)
+ return -ENOENT;
+
+ /* find which line the cursor is on */
+ cur_line = -1;
+ for (i = 0; i < txt->gen.lines.count; i++) {
+ mline = alist_get(&txt->gen.lines, i, struct vidconsole_mline);
+ if (pos >= mline->start && pos <= mline->start + mline->len) {
+ cur_line = i;
+ break;
+ }
+ }
+ if (cur_line < 0)
+ return -EINVAL;
+
+ /* find target line */
+ target_line = up ? cur_line - 1 : cur_line + 1;
+ if (target_line < 0 || target_line >= txt->gen.lines.count)
+ return -EINVAL;
+
+ /* measure text from line start to cursor to get x position */
+ ret = vidconsole_measure(scn->expo->cons, txt->gen.font_name,
+ txt->gen.font_size, cls->buf + mline->start,
+ pos - mline->start, -1, &bbox, NULL);
+ if (ret)
+ return ret;
+ target_x = bbox.x1;
+
+ /* find character position on target line closest to target_x */
+ target = alist_get(&txt->gen.lines, target_line, struct vidconsole_mline);
+ best_pos = target->start;
+ best_diff = target_x; /* diff from position 0 */
+
+ for (i = 1; i <= target->len; i++) {
+ int diff;
+
+ ret = vidconsole_measure(scn->expo->cons, txt->gen.font_name,
+ txt->gen.font_size,
+ cls->buf + target->start, i, -1,
+ &bbox, NULL);
+ if (ret)
+ break;
+ diff = abs(bbox.x1 - target_x);
+ if (diff < best_diff) {
+ best_diff = diff;
+ best_pos = target->start + i;
+ }
+ /* stop if we've gone past the target */
+ if (bbox.x1 > target_x)
+ break;
+ }
+
+ /* measure text to best_pos to get x coordinate for cursor */
+ ret = vidconsole_measure(scn->expo->cons, txt->gen.font_name,
+ txt->gen.font_size, cls->buf + target->start,
+ best_pos - target->start, -1, &bbox, NULL);
+ if (ret)
+ return ret;
+
+ /* set cursor position: text object position + line offset + char offset */
+ vidconsole_set_cursor_pos(scn->expo->cons, tin->ctx,
+ txt->obj.bbox.x0 + bbox.x1,
+ txt->obj.bbox.y0 + target->bbox.y0);
+
+ return best_pos;
+}
+
int scene_txtin_open(struct scene *scn, struct scene_obj *obj,
struct scene_txtin *tin)
{
@@ -196,6 +288,10 @@ int scene_txtin_open(struct scene *scn, struct scene_obj *obj,
cls->insert = true;
cls->putch = scene_txtin_putch;
cls->priv = scn;
+ if (obj->type == SCENEOBJT_TEXTEDIT) {
+ cls->multiline = true;
+ cls->line_nav = scene_txtin_line_nav;
+ }
cli_cread_add_initial(cls);
/* make sure the cursor is visible */
@@ -11,6 +11,7 @@
#include <membuf.h>
#include <menu.h>
#include <video.h>
+#include <video_console.h>
#include <linux/input.h>
#include <test/cedit-test.h>
#include <test/ut.h>
@@ -1541,6 +1542,7 @@ static int expo_render_textedit(struct unit_test_state *uts)
{
struct scene_obj_txtedit *ted;
struct scene_obj_menu *menu;
+ struct vidconsole_ctx *ctx;
struct abuf buf, logo_copy;
struct expo_action act;
struct scene *scn;
@@ -1590,15 +1592,20 @@ static int expo_render_textedit(struct unit_test_state *uts)
/* the cursor should be at the end */
ut_asserteq(100, ted->tin.cls.num);
ut_asserteq(100, ted->tin.cls.eol_num);
+ ctx = ted->tin.ctx;
+ ut_asserteq(343, VID_TO_PIXEL(ctx->xcur_frac));
+ ut_asserteq(260, ctx->ycur);
ut_asserteq(21526, video_compress_fb(uts, dev, false));
/* send a keypress to add a character */
ut_assertok(expo_send_key(exp, 'X'));
ut_asserteq(101, ted->tin.cls.num);
ut_asserteq(101, ted->tin.cls.eol_num);
+ ut_asserteq(353, VID_TO_PIXEL(ctx->xcur_frac));
+ ut_asserteq(260, ctx->ycur);
ut_assertok(scene_arrange(scn));
ut_assertok(expo_render(exp));
- ut_asserteq(21607, video_compress_fb(uts, dev, false));
+ ut_asserteq(21612, video_compress_fb(uts, dev, false));
ut_assertok(expo_send_key(exp, CTL_CH('b')));
ut_assertok(expo_send_key(exp, CTL_CH('b')));
@@ -1609,6 +1616,9 @@ static int expo_render_textedit(struct unit_test_state *uts)
ut_asserteq(101, ted->tin.cls.eol_num);
ut_assertok(scene_arrange(scn));
ut_assertok(expo_render(exp));
+ /* check cursor position after render (render_deps corrects it) */
+ ut_asserteq(329, VID_TO_PIXEL(ctx->xcur_frac));
+ ut_asserteq(260, ctx->ycur);
ut_asserteq(21623, video_compress_fb(uts, dev, false));
/* delete a character at the cursor (removes 'e') */
@@ -1619,8 +1629,23 @@ static int expo_render_textedit(struct unit_test_state *uts)
ut_asserteq(100, ted->tin.cls.eol_num);
ut_assertok(scene_arrange(scn));
ut_assertok(expo_render(exp));
+ /* check cursor position after render (render_deps corrects it) */
+ ut_asserteq(329, VID_TO_PIXEL(ctx->xcur_frac));
+ ut_asserteq(260, ctx->ycur);
ut_asserteq(21541, video_compress_fb(uts, dev, false));
+ /* move cursor to previous visual line at same x position */
+ ut_assertok(expo_send_key(exp, CTL_CH('p')));
+ ut_asserteq(67, ted->tin.cls.num);
+ ut_asserteq(100, ted->tin.cls.eol_num);
+ ut_asserteq(328, VID_TO_PIXEL(ctx->xcur_frac));
+ ut_asserteq(240, ctx->ycur);
+ ut_assertok(scene_arrange(scn));
+ ut_assertok(expo_render(exp));
+ ut_asserteq(327, VID_TO_PIXEL(ctx->xcur_frac));
+ ut_asserteq(240, ctx->ycur);
+ ut_asserteq(21538, video_compress_fb(uts, dev, false));
+
/* close the textedit with Enter (BKEY_SELECT) */
ut_assertok(expo_send_key(exp, BKEY_SELECT));
ut_assertok(expo_action_get(exp, &act));