--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Implementation of a menu in a scene
+ *
+ * Copyright 2023 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY LOGC_EXPO
+
+#include <common.h>
+#include <expo.h>
+#include <menu.h>
+#include <video_console.h>
+#include "scene_internal.h"
+
+int scene_textline(struct scene *scn, const char *name, uint id, uint max_chars,
+ struct scene_obj_textline **tlinep)
+{
+ struct scene_obj_textline *tline;
+ char *buf;
+ int ret;
+
+ if (max_chars >= EXPO_MAX_CHARS)
+ return log_msg_ret("chr", -E2BIG);
+
+ ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXTLINE,
+ sizeof(struct scene_obj_textline),
+ (struct scene_obj **)&tline);
+ if (ret < 0)
+ return log_msg_ret("obj", -ENOMEM);
+ abuf_init(&tline->buf);
+ if (!abuf_realloc(&tline->buf, max_chars + 1))
+ return log_msg_ret("buf", -ENOMEM);
+ buf = abuf_data(&tline->buf);
+ *buf = '\0';
+ tline->pos = max_chars;
+ tline->max_chars = max_chars;
+
+ if (tlinep)
+ *tlinep = tline;
+
+ return tline->obj.id;
+}
+
+void scene_textline_calc_bbox(struct scene_obj_textline *tline,
+ struct vidconsole_bbox *bbox,
+ struct vidconsole_bbox *edit_bbox)
+{
+ const struct expo_theme *theme = &tline->obj.scene->expo->theme;
+
+ bbox->valid = false;
+ scene_bbox_union(tline->obj.scene, tline->label_id, 0, bbox);
+ scene_bbox_union(tline->obj.scene, tline->edit_id, 0, bbox);
+
+ edit_bbox->valid = false;
+ scene_bbox_union(tline->obj.scene, tline->edit_id, theme->menu_inset,
+ edit_bbox);
+}
+
+int scene_textline_calc_dims(struct scene_obj_textline *tline)
+{
+ struct scene *scn = tline->obj.scene;
+ struct vidconsole_bbox bbox;
+ struct scene_obj_txt *txt;
+ int ret;
+
+ txt = scene_obj_find(scn, tline->edit_id, SCENEOBJT_NONE);
+ if (!txt)
+ return log_msg_ret("dim", -ENOENT);
+
+ ret = vidconsole_nominal(scn->expo->cons, txt->font_name,
+ txt->font_size, tline->max_chars, &bbox);
+ if (ret)
+ return log_msg_ret("nom", ret);
+
+ if (bbox.valid) {
+ tline->obj.dim.w = bbox.x1 - bbox.x0;
+ tline->obj.dim.h = bbox.y1 - bbox.y0;
+
+ scene_obj_set_size(scn, tline->edit_id, tline->obj.dim.w,
+ tline->obj.dim.h);
+ }
+
+ return 0;
+}
+
+int scene_textline_arrange(struct scene *scn, struct scene_obj_textline *tline)
+{
+ const bool open = tline->obj.flags & SCENEOF_OPEN;
+ bool point;
+ int x, y;
+ int ret;
+
+ x = tline->obj.dim.x;
+ y = tline->obj.dim.y;
+ if (tline->label_id) {
+ ret = scene_obj_set_pos(scn, tline->label_id, tline->obj.dim.x,
+ y);
+ if (ret < 0)
+ return log_msg_ret("tit", ret);
+
+ ret = scene_obj_set_pos(scn, tline->edit_id,
+ tline->obj.dim.x + 200, y);
+ if (ret < 0)
+ return log_msg_ret("tit", ret);
+
+ ret = scene_obj_get_hw(scn, tline->label_id, NULL);
+ if (ret < 0)
+ return log_msg_ret("hei", ret);
+
+ y += ret * 2;
+ }
+
+ point = scn->highlight_id == tline->obj.id;
+ point &= !open;
+ scene_obj_flag_clrset(scn, tline->edit_id, SCENEOF_POINT,
+ point ? SCENEOF_POINT : 0);
+
+ return 0;
+}
+
+int scene_textline_send_key(struct scene *scn, struct scene_obj_textline *tline,
+ int key, struct expo_action *event)
+{
+ const bool open = tline->obj.flags & SCENEOF_OPEN;
+
+ log_debug("key=%d\n", key);
+ switch (key) {
+ case BKEY_QUIT:
+ if (open) {
+ event->type = EXPOACT_CLOSE;
+ event->select.id = tline->obj.id;
+
+ /* Copy the backup text from the scene buffer */
+ memcpy(abuf_data(&tline->buf), abuf_data(&scn->buf),
+ abuf_size(&scn->buf));
+ } else {
+ event->type = EXPOACT_QUIT;
+ log_debug("menu quit\n");
+ }
+ break;
+ case BKEY_SELECT:
+ if (!open)
+ break;
+ event->type = EXPOACT_CLOSE;
+ event->select.id = tline->obj.id;
+ key = '\n';
+ fallthrough;
+ default: {
+ struct udevice *cons = scn->expo->cons;
+ int ret;
+
+ ret = vidconsole_entry_restore(cons, &scn->entry_save);
+ if (ret)
+ return log_msg_ret("sav", ret);
+ ret = cread_line_process_ch(&scn->cls, key);
+ ret = vidconsole_entry_save(cons, &scn->entry_save);
+ if (ret)
+ return log_msg_ret("sav", ret);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int scene_textline_render_deps(struct scene *scn,
+ struct scene_obj_textline *tline)
+{
+ const bool open = tline->obj.flags & SCENEOF_OPEN;
+ struct udevice *cons = scn->expo->cons;
+ struct scene_obj_txt *txt;
+ int ret;
+
+ scene_render_deps(scn, tline->label_id);
+ scene_render_deps(scn, tline->edit_id);
+
+ /* show the vidconsole cursor if open */
+ if (open) {
+ /* get the position within the field */
+ txt = scene_obj_find(scn, tline->edit_id, SCENEOBJT_NONE);
+ if (!txt)
+ return log_msg_ret("cur", -ENOENT);
+
+ if (txt->font_name || txt->font_size) {
+ ret = vidconsole_select_font(cons,
+ txt->font_name,
+ txt->font_size);
+ } else {
+ ret = vidconsole_select_font(cons, NULL, 0);
+ }
+
+ ret = vidconsole_entry_restore(cons, &scn->entry_save);
+ if (ret)
+ return log_msg_ret("sav", ret);
+
+ vidconsole_set_cursor_visible(cons, true, txt->obj.dim.x,
+ txt->obj.dim.y, scn->cls.num);
+ }
+
+ return 0;
+}
+
+int scene_textline_open(struct scene *scn, struct scene_obj_textline *tline)
+{
+ struct udevice *cons = scn->expo->cons;
+ struct scene_obj_txt *txt;
+ int ret;
+
+ /* Copy the text into the scene buffer in case the edit is cancelled */
+ memcpy(abuf_data(&scn->buf), abuf_data(&tline->buf),
+ abuf_size(&scn->buf));
+
+ /* get the position of the editable */
+ txt = scene_obj_find(scn, tline->edit_id, SCENEOBJT_NONE);
+ if (!txt)
+ return log_msg_ret("cur", -ENOENT);
+
+ vidconsole_set_cursor_pos(cons, txt->obj.dim.x, txt->obj.dim.y);
+ vidconsole_entry_start(cons);
+ cli_cread_init(&scn->cls, abuf_data(&tline->buf), tline->max_chars);
+ scn->cls.insert = true;
+ ret = vidconsole_entry_save(cons, &scn->entry_save);
+ if (ret)
+ return log_msg_ret("sav", ret);
+
+ return 0;
+}
+
+int scene_textline_close(struct scene *scn, struct scene_obj_textline *tline)
+{
+ return 0;
+}
* @SCENEOBJT_IMAGE: Image data to render
* @SCENEOBJT_TEXT: Text line to render
* @SCENEOBJT_MENU: Menu containing items the user can select
+ * @SCENEOBJT_TEXTLINE: Line of text the user can edit
*/
enum scene_obj_t {
SCENEOBJT_NONE = 0,
/* types from here on can be highlighted */
SCENEOBJT_MENU,
+ SCENEOBJT_TEXTLINE,
};
/**
struct list_head sibling;
};
+/**
+ * struct scene_obj_textline - information about a textline in a scene
+ *
+ * A textline has a prompt and a line of editable text
+ *
+ * @obj: Basic object information
+ * @label_id: ID of the label text, or 0 if none
+ * @edit_id: ID of the editable text
+ * @max_chars: Maximum number of characters allowed
+ * @buf: Text buffer containing current text
+ * @pos: Cursor position
+ */
+struct scene_obj_textline {
+ struct scene_obj obj;
+ uint label_id;
+ uint edit_id;
+ uint max_chars;
+ struct abuf buf;
+ uint pos;
+};
+
/**
* expo_new() - create a new expo
*
int scene_menu(struct scene *scn, const char *name, uint id,
struct scene_obj_menu **menup);
+/**
+ * scene_textline() - create a textline
+ *
+ * @scn: Scene to update
+ * @name: Name to use (this is allocated by this call)
+ * @id: ID to use for the new object (0 to allocate one)
+ * @max_chars: Maximum length of the textline in characters
+ * @tlinep: If non-NULL, returns the new object
+ * Returns: ID number for the object (typically @id), or -ve on error
+ */
+int scene_textline(struct scene *scn, const char *name, uint id, uint max_chars,
+ struct scene_obj_textline **tlinep);
+
/**
* scene_txt_set_font() - Set the font for an object
*