]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[hci] Provide a general concept of a text widget set
authorMichael Brown <mcb30@ipxe.org>
Wed, 15 May 2024 13:10:33 +0000 (14:10 +0100)
committerMichael Brown <mcb30@ipxe.org>
Wed, 15 May 2024 13:22:01 +0000 (14:22 +0100)
Create a generic abstraction of a text widget, refactor the existing
editable text box widget to use this abstraction, add an
implementation of a non-editable text label widget, and generalise the
login user interface to use this generic widget abstraction.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/hci/mucurses/widgets/editbox.c
src/hci/mucurses/widgets/label.c [new file with mode: 0644]
src/hci/tui/login_ui.c
src/hci/tui/settings_ui.c
src/hci/tui/widget_ui.c [new file with mode: 0644]
src/include/ipxe/editbox.h
src/include/ipxe/errfile.h
src/include/ipxe/label.h [new file with mode: 0644]
src/include/ipxe/widget.h [new file with mode: 0644]

index fddd2742e94307c95e2827f16997d2b755d7f58b..79e7cdd1678ec8fccb3fd4ac74ffdc59b4ad1992 100644 (file)
@@ -25,6 +25,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 
 #include <string.h>
 #include <assert.h>
+#include <ipxe/ansicol.h>
 #include <ipxe/editbox.h>
 
 /** @file
@@ -35,39 +36,16 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 
 #define EDITBOX_MIN_CHARS 3
 
-/**
- * Initialise text box widget
- *
- * @v box              Editable text box widget
- * @v buf              Dynamically allocated string buffer
- * @v win              Containing window
- * @v row              Row
- * @v col              Starting column
- * @v width            Width
- * @v flags            Flags
- */
-void init_editbox ( struct edit_box *box, char **buf,
-                   WINDOW *win, unsigned int row, unsigned int col,
-                   unsigned int width, unsigned int flags ) {
-       memset ( box, 0, sizeof ( *box ) );
-       init_editstring ( &box->string, buf );
-       box->string.cursor = ( *buf ? strlen ( *buf ) : 0 );
-       box->win = ( win ? win : stdscr );
-       box->row = row;
-       box->col = col;
-       box->width = width;
-       box->flags = flags;
-}
-
 /**
  * Draw text box widget
  *
- * @v box              Editable text box widget
- *
+ * @v widgets          Text widget set
+ * @v widget           Text widget
  */
-void draw_editbox ( struct edit_box *box ) {
+static void draw_editbox ( struct widgets *widgets, struct widget *widget ) {
+       struct edit_box *box = container_of ( widget, struct edit_box, widget );
        const char *content = *(box->string.buf);
-       size_t width = box->width;
+       size_t width = widget->width;
        char buf[ width + 1 ];
        signed int cursor_offset, underflow, overflow, first;
        size_t len;
@@ -93,15 +71,36 @@ void draw_editbox ( struct edit_box *box ) {
        len = ( content ? ( strlen ( content ) - first ) : 0 );
        if ( len > width )
                len = width;
-       if ( box->flags & EDITBOX_STARS ) {
+       if ( widget->flags & WIDGET_SECRET ) {
                memset ( buf, '*', len );
        } else {
                memcpy ( buf, ( content + first ), len );
        }
 
        /* Print box content and move cursor */
-       if ( ! box->win )
-               box->win = stdscr;
-       mvwprintw ( box->win, box->row, box->col, "%s", buf );
-       wmove ( box->win, box->row, ( box->col + cursor_offset ) );
+       color_set ( CPAIR_EDIT, NULL );
+       mvwprintw ( widgets->win, widget->row, widget->col, "%s", buf );
+       wmove ( widgets->win, widget->row, ( widget->col + cursor_offset ) );
+       color_set ( CPAIR_NORMAL, NULL );
 }
+
+/**
+ * Edit text box widget
+ *
+ * @v widgets          Text widget set
+ * @v widget           Text widget
+ * @v key              Key pressed by user
+ * @ret key            Key returned to application, or zero
+ */
+static int edit_editbox ( struct widgets *widgets __unused,
+                         struct widget *widget, int key ) {
+       struct edit_box *box = container_of ( widget, struct edit_box, widget );
+
+       return edit_string ( &box->string, key );
+}
+
+/** Text box widget operations */
+struct widget_operations editbox_operations = {
+       .draw = draw_editbox,
+       .edit = edit_editbox,
+};
diff --git a/src/hci/mucurses/widgets/label.c b/src/hci/mucurses/widgets/label.c
new file mode 100644 (file)
index 0000000..29645a6
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <string.h>
+#include <assert.h>
+#include <ipxe/label.h>
+
+/** @file
+ *
+ * Text label widget
+ *
+ */
+
+/**
+ * Draw text label widget
+ *
+ * @v widgets          Text widget set
+ * @v widget           Text widget
+ */
+static void draw_label ( struct widgets *widgets, struct widget *widget ) {
+       struct label *label = container_of ( widget, struct label, widget );
+       unsigned int width = widget->width;
+       unsigned int col = widget->col;
+       const char *text = label->text;
+
+       /* Centre label if width is non-zero */
+       if ( width )
+               col += ( ( width - strlen ( text ) ) / 2 );
+
+       /* Print label content */
+       attron ( A_BOLD );
+       mvwprintw ( widgets->win, widget->row, col, "%s", text );
+       attroff ( A_BOLD );
+}
+
+/**
+ * Edit text label widget
+ *
+ * @v widgets          Text widget set
+ * @v widget           Text widget
+ * @v key              Key pressed by user
+ * @ret key            Key returned to application, or zero
+ */
+static int edit_label ( struct widgets *widgets __unused,
+                       struct widget *widget __unused, int key ) {
+
+       /* Cannot be edited */
+       return key;
+}
+
+/** Text label widget operations */
+struct widget_operations label_operations = {
+       .draw = draw_label,
+       .edit = edit_label,
+};
index bd24991cd4ba3f836f06878ec7eb5f1f9105c5d3..b76afb92e2443a50a484f86ef7f9fc856276ae44 100644 (file)
@@ -35,6 +35,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 #include <curses.h>
 #include <ipxe/console.h>
 #include <ipxe/settings.h>
+#include <ipxe/label.h>
 #include <ipxe/editbox.h>
 #include <ipxe/keys.h>
 #include <ipxe/ansicol.h>
@@ -45,90 +46,55 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 #define USERNAME_ROW           ( ( LINES / 2U ) - 2U )
 #define PASSWORD_LABEL_ROW     ( ( LINES / 2U ) + 2U )
 #define PASSWORD_ROW           ( ( LINES / 2U ) + 4U )
-#define LABEL_COL              ( ( COLS / 2U ) - 4U )
-#define EDITBOX_COL            ( ( COLS / 2U ) - 10U )
-#define EDITBOX_WIDTH          20U
+#define WIDGET_COL             ( ( COLS / 2U ) - 10U )
+#define WIDGET_WIDTH           20U
 
 int login_ui ( void ) {
        char *username;
        char *password;
-       struct edit_box username_box;
-       struct edit_box password_box;
-       struct edit_box *current_box = &username_box;
-       int key;
-       int rc = -EINPROGRESS;
+       struct {
+               struct widgets widgets;
+               struct label username_label;
+               struct label password_label;
+               struct edit_box username_box;
+               struct edit_box password_box;
+       } widgets;
+       int rc;
 
        /* Fetch current setting values */
        fetchf_setting_copy ( NULL, &username_setting, NULL, NULL, &username );
        fetchf_setting_copy ( NULL, &password_setting, NULL, NULL, &password );
 
-       /* Initialise UI */
-       initscr();
-       start_color();
-       init_editbox ( &username_box, &username, NULL, USERNAME_ROW,
-                      EDITBOX_COL, EDITBOX_WIDTH, 0 );
-       init_editbox ( &password_box, &password, NULL, PASSWORD_ROW,
-                      EDITBOX_COL, EDITBOX_WIDTH, EDITBOX_STARS );
+       /* Construct user interface */
+       memset ( &widgets, 0, sizeof ( widgets ) );
+       init_widgets ( &widgets.widgets, NULL );
+       init_label ( &widgets.username_label, USERNAME_LABEL_ROW, WIDGET_COL,
+                    WIDGET_WIDTH, "Username" );
+       init_label ( &widgets.password_label, PASSWORD_LABEL_ROW, WIDGET_COL,
+                    WIDGET_WIDTH, "Password" );
+       init_editbox ( &widgets.username_box, USERNAME_ROW, WIDGET_COL,
+                      WIDGET_WIDTH, 0, &username );
+       init_editbox ( &widgets.password_box, PASSWORD_ROW, WIDGET_COL,
+                      WIDGET_WIDTH, WIDGET_SECRET, &password );
+       add_widget ( &widgets.widgets, &widgets.username_label.widget );
+       add_widget ( &widgets.widgets, &widgets.password_label.widget );
+       add_widget ( &widgets.widgets, &widgets.username_box.widget );
+       add_widget ( &widgets.widgets, &widgets.password_box.widget );
 
-       /* Draw initial UI */
-       color_set ( CPAIR_NORMAL, NULL );
-       erase();
-       attron ( A_BOLD );
-       mvprintw ( USERNAME_LABEL_ROW, LABEL_COL, "Username:" );
-       mvprintw ( PASSWORD_LABEL_ROW, LABEL_COL, "Password:" );
-       attroff ( A_BOLD );
-       color_set ( CPAIR_EDIT, NULL );
-       draw_editbox ( &username_box );
-       draw_editbox ( &password_box );
-
-       /* Main loop */
-       while ( rc == -EINPROGRESS ) {
-
-               draw_editbox ( current_box );
-
-               key = getkey ( 0 );
-               switch ( key ) {
-               case KEY_DOWN:
-                       current_box = &password_box;
-                       break;
-               case KEY_UP:
-                       current_box = &username_box;
-                       break;
-               case TAB:
-                       current_box = ( ( current_box == &username_box ) ?
-                                       &password_box : &username_box );
-                       break;
-               case KEY_ENTER:
-                       if ( current_box == &username_box ) {
-                               current_box = &password_box;
-                       } else {
-                               rc = 0;
-                       }
-                       break;
-               case CTRL_C:
-               case ESC:
-                       rc = -ECANCELED;
-                       break;
-               default:
-                       edit_editbox ( current_box, key );
-                       break;
-               }
-       }
-
-       /* Terminate UI */
-       color_set ( CPAIR_NORMAL, NULL );
-       erase();
-       endwin();
+       /* Present user interface */
+       if ( ( rc = widget_ui ( &widgets.widgets ) ) != 0 )
+               goto err_ui;
 
        /* Store settings on successful completion */
-       if ( rc == 0 )
-               rc = storef_setting ( NULL, &username_setting, username );
-       if ( rc == 0 )
-               rc = storef_setting ( NULL, &password_setting, password );
+       if ( ( rc = storef_setting ( NULL, &username_setting, username ) ) !=0)
+               goto err_store_username;
+       if ( ( rc = storef_setting ( NULL, &password_setting, password ) ) !=0)
+               goto err_store_password;
 
-       /* Free setting values */
+ err_store_username:
+ err_store_password:
+ err_ui:
        free ( username );
        free ( password );
-
        return rc;
 }
index 95cbd7758b494163975e3af160f7bb40ed39aa21..53bf24d1af02a82c734e46aa4db8a1814bd9a6a9 100644 (file)
@@ -108,6 +108,8 @@ struct settings_ui {
        struct jump_scroller scroll;
        /** Current row */
        struct settings_ui_row row;
+       /** Widget set used for editing setting */
+       struct widgets widgets;
 };
 
 /**
@@ -164,10 +166,11 @@ static unsigned int select_setting_row ( struct settings_ui *ui,
        }
 
        /* Initialise edit box */
-       init_editbox ( &ui->row.editbox, &ui->row.buf, NULL, ui->row.row,
+       memset ( &ui->row.editbox, 0, sizeof ( ui->row.editbox ) );
+       init_editbox ( &ui->row.editbox, ui->row.row,
                       ( SETTINGS_LIST_COL +
                         offsetof ( typeof ( *text ), u.setting.value ) ),
-                      sizeof ( text->u.setting.value ), 0 );
+                      sizeof ( text->u.setting.value ), 0, &ui->row.buf );
 
        return count;
 }
@@ -250,7 +253,7 @@ static void draw_setting_row ( struct settings_ui *ui ) {
 static int edit_setting ( struct settings_ui *ui, int key ) {
        assert ( ui->row.setting.name != NULL );
        ui->row.editing = 1;
-       return edit_editbox ( &ui->row.editbox, key );
+       return edit_widget ( &ui->widgets, &ui->row.editbox.widget, key );
 }
 
 /**
@@ -454,6 +457,7 @@ static int main_loop ( struct settings *settings ) {
        /* Print initial screen content */
        color_set ( CPAIR_NORMAL, NULL );
        memset ( &ui, 0, sizeof ( ui ) );
+       init_widgets ( &ui.widgets, NULL );
        select_settings ( &ui, settings );
 
        while ( 1 ) {
@@ -477,9 +481,7 @@ static int main_loop ( struct settings *settings ) {
                        assert ( ui.row.setting.name != NULL );
 
                        /* Redraw edit box */
-                       color_set ( CPAIR_EDIT, NULL );
-                       draw_editbox ( &ui.row.editbox );
-                       color_set ( CPAIR_NORMAL, NULL );
+                       draw_widget ( &ui.widgets, &ui.row.editbox.widget );
 
                        /* Process keypress */
                        key = edit_setting ( &ui, getkey ( 0 ) );
diff --git a/src/hci/tui/widget_ui.c b/src/hci/tui/widget_ui.c
new file mode 100644 (file)
index 0000000..5079eef
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+/** @file
+ *
+ * Text widget UI
+ *
+ */
+
+#include <errno.h>
+#include <curses.h>
+#include <ipxe/ansicol.h>
+#include <ipxe/widget.h>
+
+/**
+ * Find editable widget in widget set
+ *
+ * @v widgets          Text widget set
+ * @v index            Editable widget index
+ * @ret widget         Editable widget, or NULL
+ */
+static struct widget * find_widget ( struct widgets *widgets,
+                                    unsigned int index ) {
+       struct widget *widget;
+
+       list_for_each_entry ( widget, &widgets->list, list ) {
+               if ( ! ( widget->flags & WIDGET_EDITABLE ) )
+                       continue;
+               if ( index-- == 0 )
+                       return widget;
+       }
+       return NULL;
+}
+
+/**
+ * Text widget user interface main loop
+ *
+ * @v widgets          Text widget set
+ * @ret rc             Return status code
+ */
+static int widget_ui_loop ( struct widgets *widgets ) {
+       struct widget *widget;
+       unsigned int current;
+       unsigned int count;
+       int key;
+
+       /* Draw all widgets */
+       list_for_each_entry ( widget, &widgets->list, list )
+               draw_widget ( widgets, widget );
+
+       /* Count editable widgets */
+       count = 0;
+       while ( find_widget ( widgets, count ) != NULL )
+               count++;
+
+       /* Main loop */
+       current = 0;
+       while ( 1 ) {
+
+               /* Identify current widget */
+               widget = find_widget ( widgets, current );
+               if ( ! widget )
+                       return -ENOENT;
+
+               /* Redraw current widget */
+               draw_widget ( widgets, widget );
+
+               /* Process keypress */
+               key = edit_widget ( widgets, widget, getkey ( 0 ) );
+               switch ( key ) {
+               case KEY_UP:
+                       if ( current > 0 )
+                               current--;
+                       break;
+               case KEY_DOWN:
+                       if ( ++current == count )
+                               current--;
+                       break;
+               case TAB:
+                       if ( ++current == count )
+                               current = 0;
+                       break;
+               case KEY_ENTER:
+                       current++;
+                       if ( current >= count )
+                               return 0;
+                       break;
+               case CTRL_C:
+               case ESC:
+                       return -ECANCELED;
+               default:
+                       /* Do nothing for unrecognised keys or edit errors */
+                       break;
+               }
+       }
+}
+
+/**
+ * Present text widget user interface
+ *
+ * @v widgets          Text widget set
+ * @ret rc             Return status code
+ */
+int widget_ui ( struct widgets *widgets ) {
+       int rc;
+
+       /* Initialise UI */
+       initscr();
+       start_color();
+       color_set ( CPAIR_NORMAL, NULL );
+       erase();
+
+       /* Run main loop */
+       rc = widget_ui_loop ( widgets );
+
+       /* Terminate UI */
+       color_set ( CPAIR_NORMAL, NULL );
+       endwin();
+
+       return rc;
+}
index c14bbdc5b4413036b32a1721b7b32649a10e420f..1f62485fece43be34c57c2dbae963664837e3842 100644 (file)
@@ -11,51 +11,39 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 
 #include <curses.h>
 #include <ipxe/editstring.h>
+#include <ipxe/widget.h>
 
 /** An editable text box widget */
 struct edit_box {
+       /** Text widget */
+       struct widget widget;
        /** Editable string */
        struct edit_string string;
-       /** Containing window */
-       WINDOW *win;
-       /** Row */
-       unsigned int row;
-       /** Starting column */
-       unsigned int col;
-       /** Width */
-       unsigned int width;
        /** First displayed character */
        unsigned int first;
-       /** Flags */
-       unsigned int flags;
 };
 
-/** Editable text box widget flags */
-enum edit_box_flags {
-       /** Show stars instead of contents (for password widgets) */
-       EDITBOX_STARS = 0x0001,
-};
-
-extern void init_editbox ( struct edit_box *box, char **buf,
-                          WINDOW *win, unsigned int row, unsigned int col,
-                          unsigned int width, unsigned int flags )
-                          __attribute__ (( nonnull (1, 2) ));
-extern void draw_editbox ( struct edit_box *box ) __nonnull;
-static inline int edit_editbox ( struct edit_box *box, int key ) __nonnull;
+extern struct widget_operations editbox_operations;
 
 /**
- * Edit text box widget
+ * Initialise text box widget
  *
  * @v box              Editable text box widget
- * @v key              Key pressed by user
- * @ret key            Key returned to application, or zero
- *
- * You must call draw_editbox() to update the display after calling
- * edit_editbox().
- *
+ * @v row              Row
+ * @v col              Starting column
+ * @v width            Width
+ * @v flags            Flags
+ * @v buf              Dynamically allocated string buffer
  */
-static inline int edit_editbox ( struct edit_box *box, int key ) {
-       return edit_string ( &box->string, key );
+static inline __attribute__ (( always_inline )) void
+init_editbox ( struct edit_box *box, unsigned int row, unsigned int col,
+              unsigned int width, unsigned int flags, char **buf ) {
+
+       init_widget ( &box->widget, &editbox_operations, row, col,
+                     width, ( flags | WIDGET_EDITABLE ) );
+       init_editstring ( &box->string, buf );
+       if ( *buf )
+               box->string.cursor = strlen ( *buf );
 }
 
 #endif /* _IPXE_EDITBOX_H */
index 96981dd06971d23572d4a31c2c671d10890c55ca..a9ce6569b1131daf83e28375ef54b347f8963564 100644 (file)
@@ -417,6 +417,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 #define ERRFILE_x25519               ( ERRFILE_OTHER | 0x005f0000 )
 #define ERRFILE_des                  ( ERRFILE_OTHER | 0x00600000 )
 #define ERRFILE_editstring           ( ERRFILE_OTHER | 0x00610000 )
+#define ERRFILE_widget_ui            ( ERRFILE_OTHER | 0x00620000 )
 
 /** @} */
 
diff --git a/src/include/ipxe/label.h b/src/include/ipxe/label.h
new file mode 100644 (file)
index 0000000..48e36cb
--- /dev/null
@@ -0,0 +1,42 @@
+#ifndef _IPXE_LABEL_H
+#define _IPXE_LABEL_H
+
+/** @file
+ *
+ * Text label widget
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <curses.h>
+#include <ipxe/widget.h>
+
+/** A text label widget */
+struct label {
+       /** Text widget */
+       struct widget widget;
+       /** Label text */
+       const char *text;
+};
+
+extern struct widget_operations label_operations;
+
+/**
+ * Initialise text label widget
+ *
+ * @v label            Text label widget
+ * @v row              Row
+ * @v col              Starting column
+ * @v width            Width
+ * @v text             Label text
+ */
+static inline __attribute__ (( always_inline )) void
+init_label ( struct label *label, unsigned int row, unsigned int col,
+            unsigned int width, const char *text ) {
+
+       init_widget ( &label->widget, &label_operations, row, col, width, 0 );
+       label->text = text;
+}
+
+#endif /* _IPXE_LABEL_H */
diff --git a/src/include/ipxe/widget.h b/src/include/ipxe/widget.h
new file mode 100644 (file)
index 0000000..fe93b9f
--- /dev/null
@@ -0,0 +1,151 @@
+#ifndef _IPXE_WIDGET_H
+#define _IPXE_WIDGET_H
+
+/** @file
+ *
+ * Text widgets
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <curses.h>
+#include <ipxe/list.h>
+
+/** A text widget set */
+struct widgets {
+       /** List of widgets (in tab order) */
+       struct list_head list;
+       /** Containing window */
+       WINDOW *win;
+};
+
+/** A text widget */
+struct widget {
+       /** List of widgets (in tab order) */
+       struct list_head list;
+       /** Widget operations */
+       struct widget_operations *op;
+
+       /** Row */
+       unsigned int row;
+       /** Starting column */
+       unsigned int col;
+       /** Width */
+       unsigned int width;
+       /** Flags */
+       unsigned int flags;
+};
+
+/** Text widget flags */
+enum widget_flags {
+       /** Widget may have input focus */
+       WIDGET_EDITABLE = 0x0001,
+       /** Widget contains a secret */
+       WIDGET_SECRET = 0x0002,
+};
+
+/** Text widget operations */
+struct widget_operations {
+       /**
+        * Draw widget
+        *
+        * @v widgets           Text widget set
+        * @v widget            Text widget
+        */
+       void ( * draw ) ( struct widgets *widgets, struct widget *widget );
+       /**
+        * Edit widget
+        *
+        * @v widgets           Text widget set
+        * @v widget            Text widget
+        * @v key               Key pressed by user
+        * @ret key             Key returned to application, or zero
+        *
+        * This will not update the display: you must call the draw()
+        * method to ensure that any changes to an editable widget are
+        * displayed to the user.
+        */
+       int ( * edit ) ( struct widgets *widgets, struct widget *widget,
+                        int key );
+};
+
+/**
+ * Initialise text widget set
+ *
+ * @v widgets          Text widget set
+ * @v win              Containing window
+ */
+static inline __attribute__ (( always_inline )) void
+init_widgets ( struct widgets *widgets, WINDOW *win ) {
+
+       INIT_LIST_HEAD ( &widgets->list );
+       widgets->win = ( win ? win : stdscr );
+}
+
+/**
+ * Initialise text widget
+ *
+ * @v widget           Text widget
+ * @v op               Text widget operations
+ * @v row              Row
+ * @v col              Starting column
+ * @v width            Width
+ */
+static inline __attribute__ (( always_inline )) void
+init_widget ( struct widget *widget, struct widget_operations *op,
+             unsigned int row, unsigned int col, unsigned int width,
+             unsigned int flags ) {
+
+       widget->op = op;
+       widget->row = row;
+       widget->col = col;
+       widget->width = width;
+       widget->flags = flags;
+}
+
+/**
+ * Append text widget
+ *
+ * @v widgets          Text widget set
+ * @v widget           Text widget
+ */
+static inline __attribute__ (( always_inline )) void
+add_widget ( struct widgets *widgets, struct widget *widget ) {
+
+       list_add_tail ( &widget->list, &widgets->list );
+}
+
+/**
+ * Draw text widget
+ *
+ * @v widgets          Text widget set
+ * @v widget           Text widget
+ */
+static inline __attribute__ (( always_inline )) void
+draw_widget ( struct widgets *widgets, struct widget *widget ) {
+
+       widget->op->draw ( widgets, widget );
+}
+
+/**
+ * Edit text widget
+ *
+ * @v widgets          Text widget set
+ * @v widget           Text widget
+ * @v key              Key pressed by user
+ * @ret key            Key returned to application, or zero
+ *
+ * This will not update the display: you must call draw_widget() to
+ * ensure that any changes to an editable widget are displayed to the
+ * user.
+ */
+static inline __attribute__ (( always_inline )) int
+edit_widget ( struct widgets *widgets, struct widget *widget, int key ) {
+
+       return widget->op->edit ( widgets, widget, key );
+}
+
+extern int widget_ui ( struct widgets *widgets );
+
+#endif /* _IPXE_WIDGET_H */