It is useful to be able to edit text, e.g. to allow the user to edit the
environment or the command-line arguments for the OS.

Add the beginnings of an implementation. Future work is needed to finish
this: keypress handling and scrolling. For now it just displays the
text.

Signed-off-by: Simon Glass <s...@chromium.org>
---

 boot/Makefile         |  2 +-
 boot/cedit.c          |  5 ++++
 boot/scene.c          | 30 ++++++++++++++++++++--
 boot/scene_internal.h | 12 +++++++++
 boot/scene_textedit.c | 60 +++++++++++++++++++++++++++++++++++++++++++
 doc/develop/expo.rst  |  1 +
 include/expo.h        | 41 +++++++++++++++++++++++++++++
 test/boot/expo.c      | 16 +++++++++++-
 8 files changed, 163 insertions(+), 4 deletions(-)
 create mode 100644 boot/scene_textedit.c

diff --git a/boot/Makefile b/boot/Makefile
index 284ade3def0..44b962a04f7 100644
--- a/boot/Makefile
+++ b/boot/Makefile
@@ -59,7 +59,7 @@ obj-$(CONFIG_CMD_ADTIMG) += image-android-dt.o
 obj-$(CONFIG_$(PHASE_)LOAD_FIT) += common_fit.o
 
 obj-$(CONFIG_$(PHASE_)EXPO) += expo.o scene.o expo_build.o
-obj-$(CONFIG_$(PHASE_)EXPO) += scene_menu.o scene_textline.o
+obj-$(CONFIG_$(PHASE_)EXPO) += scene_menu.o scene_textline.o scene_textedit.o
 ifdef CONFIG_COREBOOT_SYSINFO
 obj-$(CONFIG_$(SPL_TPL_)EXPO) += expo_build_cb.o
 endif
diff --git a/boot/cedit.c b/boot/cedit.c
index bdf51b50cd4..ead54b47b5b 100644
--- a/boot/cedit.c
+++ b/boot/cedit.c
@@ -82,6 +82,7 @@ int cedit_arange(struct expo *exp, struct video_priv *vpriv, 
uint scene_id)
                case SCENEOBJT_IMAGE:
                case SCENEOBJT_TEXT:
                case SCENEOBJT_BOX:
+               case SCENEOBJT_TEXTEDIT:
                        break;
                case SCENEOBJT_MENU:
                        scene_obj_set_pos(scn, obj->id, 50, y);
@@ -378,6 +379,7 @@ static int h_write_settings(struct scene_obj *obj, void 
*vpriv)
        case SCENEOBJT_IMAGE:
        case SCENEOBJT_TEXT:
        case SCENEOBJT_BOX:
+       case SCENEOBJT_TEXTEDIT:
                break;
        case SCENEOBJT_TEXTLINE: {
                const struct scene_obj_textline *tline;
@@ -479,6 +481,7 @@ static int h_read_settings(struct scene_obj *obj, void 
*vpriv)
        case SCENEOBJT_IMAGE:
        case SCENEOBJT_TEXT:
        case SCENEOBJT_BOX:
+       case SCENEOBJT_TEXTEDIT:
                break;
        case SCENEOBJT_TEXTLINE: {
                const struct scene_obj_textline *tline;
@@ -551,6 +554,7 @@ static int h_write_settings_env(struct scene_obj *obj, void 
*vpriv)
        case SCENEOBJT_IMAGE:
        case SCENEOBJT_TEXT:
        case SCENEOBJT_BOX:
+       case SCENEOBJT_TEXTEDIT:
                break;
        case SCENEOBJT_MENU:
                menu = (struct scene_obj_menu *)obj;
@@ -635,6 +639,7 @@ static int h_read_settings_env(struct scene_obj *obj, void 
*vpriv)
        case SCENEOBJT_IMAGE:
        case SCENEOBJT_TEXT:
        case SCENEOBJT_BOX:
+       case SCENEOBJT_TEXTEDIT:
                break;
        case SCENEOBJT_MENU:
                menu = (struct scene_obj_menu *)obj;
diff --git a/boot/scene.c b/boot/scene.c
index 4f9d4a44e2b..1f410ede97c 100644
--- a/boot/scene.c
+++ b/boot/scene.c
@@ -416,13 +416,19 @@ int scene_obj_get_hw(struct scene *scn, uint id, int 
*widthp)
                        *widthp = width;
                return height;
        }
-       case SCENEOBJT_TEXT: {
-               struct scene_txt_generic *gen = &((struct scene_obj_txt 
*)obj)->gen;
+       case SCENEOBJT_TEXT:
+       case SCENEOBJT_TEXTEDIT: {
+               struct scene_txt_generic *gen;
                struct expo *exp = scn->expo;
                struct vidconsole_bbox bbox;
                int len, ret, limit;
                const char *str;
 
+               if (obj->type == SCENEOBJT_TEXT)
+                       gen = &((struct scene_obj_txt *)obj)->gen;
+               else
+                       gen = &((struct scene_obj_txtedit *)obj)->gen;
+
                str = expo_get_str(exp, gen->str_id);
                if (!str)
                        return log_msg_ret("str", -ENOENT);
@@ -652,6 +658,13 @@ static int scene_obj_render(struct scene_obj *obj, bool 
text_mode)
                               obj->bbox.y1, box->width, vid_priv->colour_fg);
                break;
        }
+       case SCENEOBJT_TEXTEDIT: {
+               struct scene_obj_txtedit *ted = (struct scene_obj_txtedit *)obj;
+
+               ret = scene_txt_render(exp, dev, cons, obj, &ted->gen, x, y,
+                                      theme->menu_inset);
+               break;
+       }
        }
 
        return 0;
@@ -671,6 +684,7 @@ int scene_calc_arrange(struct scene *scn, struct 
expo_arrange_info *arr)
                case SCENEOBJT_IMAGE:
                case SCENEOBJT_TEXT:
                case SCENEOBJT_BOX:
+               case SCENEOBJT_TEXTEDIT:
                        break;
                case SCENEOBJT_MENU: {
                        struct scene_obj_menu *menu;
@@ -730,6 +744,7 @@ int scene_arrange(struct scene *scn)
                case SCENEOBJT_IMAGE:
                case SCENEOBJT_TEXT:
                case SCENEOBJT_BOX:
+               case SCENEOBJT_TEXTEDIT:
                        break;
                case SCENEOBJT_MENU: {
                        struct scene_obj_menu *menu;
@@ -776,6 +791,7 @@ int scene_render_deps(struct scene *scn, uint id)
                case SCENEOBJT_IMAGE:
                case SCENEOBJT_TEXT:
                case SCENEOBJT_BOX:
+               case SCENEOBJT_TEXTEDIT:
                        break;
                case SCENEOBJT_MENU:
                        scene_menu_render_deps(scn,
@@ -915,6 +931,9 @@ int scene_send_key(struct scene *scn, int key, struct 
expo_action *event)
                                return log_msg_ret("key", ret);
                        break;
                }
+               case SCENEOBJT_TEXTEDIT:
+                       /* TODO(s...@chromium.org): Implement this */
+                       break;
                }
                return 0;
        }
@@ -941,6 +960,7 @@ int scene_obj_calc_bbox(struct scene_obj *obj, struct 
vidconsole_bbox bbox[])
        case SCENEOBJT_IMAGE:
        case SCENEOBJT_TEXT:
        case SCENEOBJT_BOX:
+       case SCENEOBJT_TEXTEDIT:
                return -ENOSYS;
        case SCENEOBJT_MENU: {
                struct scene_obj_menu *menu = (struct scene_obj_menu *)obj;
@@ -971,6 +991,7 @@ int scene_calc_dims(struct scene *scn, bool do_menus)
                case SCENEOBJT_NONE:
                case SCENEOBJT_TEXT:
                case SCENEOBJT_BOX:
+               case SCENEOBJT_TEXTEDIT:
                case SCENEOBJT_IMAGE: {
                        int width;
 
@@ -1032,6 +1053,10 @@ int scene_apply_theme(struct scene *scn, struct 
expo_theme *theme)
                case SCENEOBJT_BOX:
                case SCENEOBJT_TEXTLINE:
                        break;
+               case SCENEOBJT_TEXTEDIT:
+                       scene_txted_set_font(scn, obj->id, NULL,
+                                            theme->font_size);
+                       break;
                case SCENEOBJT_TEXT:
                        scene_txt_set_font(scn, obj->id, NULL,
                                           theme->font_size);
@@ -1073,6 +1098,7 @@ static int scene_obj_open(struct scene *scn, struct 
scene_obj *obj)
        case SCENEOBJT_MENU:
        case SCENEOBJT_TEXT:
        case SCENEOBJT_BOX:
+       case SCENEOBJT_TEXTEDIT:
                break;
        case SCENEOBJT_TEXTLINE:
                ret = scene_textline_open(scn,
diff --git a/boot/scene_internal.h b/boot/scene_internal.h
index ac2a36d6e4d..760cc629b86 100644
--- a/boot/scene_internal.h
+++ b/boot/scene_internal.h
@@ -414,4 +414,16 @@ int scene_textline_close(struct scene *scn, struct 
scene_obj_textline *tline);
  */
 int scene_calc_arrange(struct scene *scn, struct expo_arrange_info *arr);
 
+/**
+ * scene_txt_generic_init() - Set up the generic part of a text object
+ *
+ * @exp: Expo containing the object
+ * @gen: Generic text info
+ * @name: Object name
+ * @str_id: String ID for the text
+ * @str: Initial text string for the object, or NULL to just use str_id
+ */
+int scene_txt_generic_init(struct expo *exp, struct scene_txt_generic *gen,
+                          const char *name, uint str_id, const char *str);
+
 #endif /* __SCENE_INTERNAL_H */
diff --git a/boot/scene_textedit.c b/boot/scene_textedit.c
new file mode 100644
index 00000000000..8f4accc4d7c
--- /dev/null
+++ b/boot/scene_textedit.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Implementation of a menu in a scene
+ *
+ * Copyright 2025 Google LLC
+ * Written by Simon Glass <s...@chromium.org>
+ */
+
+#define LOG_CATEGORY   LOGC_EXPO
+
+#include <expo.h>
+#include <log.h>
+#include <linux/err.h>
+#include <linux/sizes.h>
+#include "scene_internal.h"
+
+enum {
+       INITIAL_SIZE    = SZ_4K,
+};
+
+int scene_texted(struct scene *scn, const char *name, uint id, uint str_id,
+                struct scene_obj_txtedit **teditp)
+{
+       struct scene_obj_txtedit *ted;
+       char *buf;
+       int ret;
+
+       ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXTEDIT,
+                           sizeof(struct scene_obj_txtedit),
+                           (struct scene_obj **)&ted);
+       if (ret < 0)
+               return log_msg_ret("obj", ret);
+
+       abuf_init(&ted->buf);
+       if (!abuf_realloc(&ted->buf, INITIAL_SIZE))
+               return log_msg_ret("buf", -ENOMEM);
+       buf = abuf_data(&ted->buf);
+       *buf = '\0';
+
+       LOGR("teg", scene_txt_generic_init(scn->expo, &ted->gen, name, str_id,
+                                          buf));
+       if (teditp)
+               *teditp = ted;
+
+       return ted->obj.id;
+}
+
+int scene_txted_set_font(struct scene *scn, uint id, const char *font_name,
+                        uint font_size)
+{
+       struct scene_obj_txtedit *ted;
+
+       ted = scene_obj_find(scn, id, SCENEOBJT_TEXTEDIT);
+       if (!ted)
+               return log_msg_ret("find", -ENOENT);
+       ted->gen.font_name = font_name;
+       ted->gen.font_size = font_size;
+
+       return 0;
+}
diff --git a/doc/develop/expo.rst b/doc/develop/expo.rst
index d6fc487e030..b94340e9a8d 100644
--- a/doc/develop/expo.rst
+++ b/doc/develop/expo.rst
@@ -560,6 +560,7 @@ Future ideas
 Some ideas for future work:
 
 - Default menu item and a timeout
+- Complete the text editor
 - Image formats other than BMP
 - Use of ANSI sequences to control a serial terminal
 - Colour selection
diff --git a/include/expo.h b/include/expo.h
index 001f7db2553..16f2f18c4fa 100644
--- a/include/expo.h
+++ b/include/expo.h
@@ -183,12 +183,14 @@ struct scene {
  * @SCENEOBJT_TEXT: Text line to render
  * @SCENEOBJT_MENU: Menu containing items the user can select
  * @SCENEOBJT_TEXTLINE: Line of text the user can edit
+ * @SCENEOBJT_TEXTEDIT: Simple text editor
  */
 enum scene_obj_t {
        SCENEOBJT_NONE          = 0,
        SCENEOBJT_IMAGE,
        SCENEOBJT_TEXT,
        SCENEOBJT_BOX,
+       SCENEOBJT_TEXTEDIT,
 
        /* types from here on can be highlighted */
        SCENEOBJT_MENU,
@@ -464,6 +466,21 @@ struct scene_obj_box {
        uint width;
 };
 
+/**
+ * struct scene_obj_txtedit - information about a box in a scene
+ *
+ * A text editor which allows users to edit a small text file
+ *
+ * @obj: Basic object information
+ * @gen: Generic information common to all objects which show text
+ * @buf: Text buffer containing current text
+ */
+struct scene_obj_txtedit {
+       struct scene_obj obj;
+       struct scene_txt_generic gen;
+       struct abuf buf;
+};
+
 /**
  * struct expo_arrange_info - Information used when arranging a scene
  *
@@ -741,6 +758,19 @@ int scene_textline(struct scene *scn, const char *name, 
uint id, uint max_chars,
 int scene_box(struct scene *scn, const char *name, uint id, uint width,
              struct scene_obj_box **boxp);
 
+/**
+ *  scene_texted() - create a text editor
+ *
+ * @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)
+ * @strid: ID of the string to edit
+ * @teditp: If non-NULL, returns the new object
+ * Returns: ID number for the object (typically @id), or -ve on error
+ */
+int scene_texted(struct scene *scn, const char *name, uint id, uint strid,
+                struct scene_obj_txtedit **teditp);
+
 /**
  * scene_txt_set_font() - Set the font for an object
  *
@@ -752,6 +782,17 @@ int scene_box(struct scene *scn, const char *name, uint 
id, uint width,
 int scene_txt_set_font(struct scene *scn, uint id, const char *font_name,
                       uint font_size);
 
+/**
+ * scene_txted_set_font() - Set the font for an object
+ *
+ * @scn: Scene to update
+ * @id: ID of object to update
+ * @font_name: Font name to use (allocated by caller)
+ * @font_size: Font size to use (nominal height in pixels)
+ */
+int scene_txted_set_font(struct scene *scn, uint id, const char *font_name,
+                        uint font_size);
+
 /**
  * scene_obj_set_pos() - Set the postion of an object
  *
diff --git a/test/boot/expo.c b/test/boot/expo.c
index 7bea189da04..515a50f72ec 100644
--- a/test/boot/expo.c
+++ b/test/boot/expo.c
@@ -30,6 +30,7 @@ enum {
        OBJ_MENU_TITLE,
        OBJ_BOX,
        OBJ_BOX2,
+       OBJ_TEXTED,
 
        /* strings */
        STR_SCENE_TITLE,
@@ -37,6 +38,7 @@ enum {
        STR_TEXT,
        STR_TEXT2,
        STR_TEXT3,
+       STR_TEXTED,
        STR_MENU_TITLE,
        STR_POINTER_TEXT,
 
@@ -462,6 +464,7 @@ static int expo_render_image(struct unit_test_state *uts)
 {
        struct scene_obj_menu *menu;
        struct scene *scn, *scn2;
+       struct abuf orig, *text;
        struct expo_action act;
        struct scene_obj *obj;
        struct udevice *dev;
@@ -556,6 +559,14 @@ static int expo_render_image(struct unit_test_state *uts)
        ut_assert(id > 0);
        ut_assertok(scene_obj_set_bbox(scn, OBJ_BOX, 500, 200, 1000, 350));
 
+       id = scene_texted(scn, "editor", OBJ_TEXTED, STR_TEXTED, NULL);
+       ut_assert(id > 0);
+       ut_assertok(scene_obj_set_bbox(scn, OBJ_TEXTED, 100, 200, 400, 650));
+       ut_assertok(expo_edit_str(exp, STR_TEXTED, &orig, &text));
+
+       abuf_printf(text, "This\nis the initial contents of the text editor "
+               "but it is quite likely that more will be added later");
+
        scn2 = expo_lookup_scene_id(exp, SCENE1);
        ut_asserteq_ptr(scn, scn2);
        scn2 = expo_lookup_scene_id(exp, SCENE2);
@@ -667,9 +678,12 @@ static int expo_render_image(struct unit_test_state *uts)
        ut_assertok(scene_arrange(scn));
        ut_assertok(expo_render(exp));
 
-       ut_asserteq(16450, video_compress_fb(uts, dev, false));
+       ut_asserteq(19780, video_compress_fb(uts, dev, false));
        ut_assertok(video_check_copy_fb(uts, dev));
 
+       /* hide the text editor since the following tets don't need it */
+       scene_obj_set_hide(scn, OBJ_TEXTED, true);
+
        /* do some alignment checks */
        ut_assertok(scene_obj_set_halign(scn, OBJ_TEXT3, SCENEOA_CENTRE));
        ut_assertok(expo_render(exp));
-- 
2.43.0

Reply via email to