recovery: simple graphical ui

Change-Id: If1635438af5b8456283c3a138ccdccea6054c478
diff --git a/recovery_ui/include/recovery_ui/screen_ui.h b/recovery_ui/include/recovery_ui/screen_ui.h
index 503a085..1df1daa 100644
--- a/recovery_ui/include/recovery_ui/screen_ui.h
+++ b/recovery_ui/include/recovery_ui/screen_ui.h
@@ -37,6 +37,7 @@
   MENU_SEL_BG,
   MENU_SEL_BG_ACTIVE,
   MENU_SEL_FG,
+  SCROLLBAR,
   LOG,
   TEXT_FILL,
   INFO
@@ -56,6 +57,9 @@
   // Draws a horizontal rule at Y. Returns the offset it should be moving along Y-axis.
   virtual int DrawHorizontalRule(int y) const = 0;
 
+  // Draws a vertical line from y to y + height, on the right of the screen.
+  virtual void DrawScrollBar(int y, int height) const = 0;
+
   // Draws a line of text. Returns the offset it should be moving along Y-axis.
   virtual int DrawTextLine(int x, int y, const std::string& line, bool bold) const = 0;
 
@@ -76,6 +80,11 @@
   // keeps symmetrical margins of 'x' at each end of a line. Returns the offset it should be moving
   // along Y-axis.
   virtual int DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const = 0;
+
+  virtual int MenuCharHeight() const = 0;
+  virtual int MenuCharWidth() const = 0;
+  virtual int MenuItemPadding() const = 0;
+  virtual int MenuItemHeight() const = 0;
 };
 
 // Interface for classes that maintain the menu selection and display.
@@ -83,7 +92,7 @@
  public:
   virtual ~Menu() = default;
   // Returns the current menu selection.
-  size_t selection() const;
+  int selection() const;
   // Sets the current selection to |sel|. Handle the overflow cases depending on if the menu is
   // scrollable.
   virtual int Select(int sel) = 0;
@@ -91,11 +100,12 @@
   virtual int DrawHeader(int x, int y) const = 0;
   // Iterates over the menu items and displays each of them at offset x, y.
   virtual int DrawItems(int x, int y, int screen_width, bool long_press) const = 0;
+  virtual bool IsMain() const = 0;
 
  protected:
   Menu(size_t initial_selection, const DrawInterface& draw_func);
   // Current menu selection.
-  size_t selection_;
+  int selection_;
   // Reference to the class that implements all the draw functions.
   const DrawInterface& draw_funcs_;
 };
@@ -105,7 +115,7 @@
  public:
   // Constructs a Menu instance with the given |headers|, |items| and properties. Sets the initial
   // selection to |initial_selection|.
-  TextMenu(bool scrollable, size_t max_items, size_t max_length,
+  TextMenu(bool wrappable, size_t max_items, size_t max_length,
            const std::vector<std::string>& headers, const std::vector<std::string>& items,
            size_t initial_selection, int char_height, const DrawInterface& draw_funcs);
 
@@ -113,8 +123,13 @@
   int DrawHeader(int x, int y) const override;
   int DrawItems(int x, int y, int screen_width, bool long_press) const override;
 
-  bool scrollable() const {
-    return scrollable_;
+  bool IsMain() const override {
+    // Main menus have no headers
+    return text_headers_.size() == 0;
+  }
+
+  bool wrappable() const {
+    return wrappable_;
   }
 
   // Returns count of menu items.
@@ -146,7 +161,7 @@
 
  private:
   // The menu is scrollable to display more items. Used on wear devices who have smaller screens.
-  const bool scrollable_;
+  const bool wrappable_;
   // The max number of menu items to fit vertically on a screen.
   const size_t max_display_items_;
   // The length of each item to fit horizontally on a screen.
@@ -173,6 +188,9 @@
   int Select(int sel) override;
   int DrawHeader(int x, int y) const override;
   int DrawItems(int x, int y, int screen_width, bool long_press) const override;
+  bool IsMain() const override {
+    return true;
+  }
 
   // Checks if all the header and items are valid GRSurface's; and that they can fit in the area
   // defined by |max_width| and |max_height|.
@@ -189,12 +207,56 @@
   std::vector<std::unique_ptr<GRSurface>> graphic_items_;
 };
 
+class MenuDrawFunctions : public DrawInterface {
+ public:
+  MenuDrawFunctions(const DrawInterface& wrappee);
+  void SetColor(UIElement e) const override {
+    wrappee_.SetColor(e);
+  }
+  void DrawHighlightBar(int x, int y, int width, int height) const override {
+    wrappee_.DrawHighlightBar(x, y, width, height);
+  };
+  void DrawScrollBar(int y, int height) const override {
+    wrappee_.DrawScrollBar(y, height);
+  }
+  int DrawHorizontalRule(int y) const override {
+    return wrappee_.DrawHorizontalRule(y);
+  }
+  void DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx,
+                   int dy) const override {
+    wrappee_.DrawSurface(surface, sx, sy, w, h, dx, dy);
+  }
+  void DrawFill(int x, int y, int w, int h) const override {
+    wrappee_.DrawFill(x, y, w, h);
+  }
+  void DrawTextIcon(int x, int y, const GRSurface* surface) const override {
+    wrappee_.DrawTextIcon(x, y, surface);
+  }
+  int MenuCharHeight() const override {
+    return wrappee_.MenuCharHeight();
+  };
+  int MenuCharWidth() const override {
+    return wrappee_.MenuCharWidth();
+  };
+  int MenuItemPadding() const override {
+    return wrappee_.MenuItemPadding();
+  };
+  int MenuItemHeight() const override {
+    return wrappee_.MenuItemHeight();
+  };
+  int DrawTextLine(int x, int y, const std::string& line, bool bold) const override;
+  int DrawTextLines(int x, int y, const std::vector<std::string>& lines) const override;
+  int DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const override;
+
+ private:
+  const DrawInterface& wrappee_;
+};
+
 // Implementation of RecoveryUI appropriate for devices with a screen
 // (shows an icon + a progress bar, text logging, menu, etc.)
 class ScreenRecoveryUI : public RecoveryUI, public DrawInterface {
  public:
   ScreenRecoveryUI();
-  explicit ScreenRecoveryUI(bool scrollable_menu);
   ~ScreenRecoveryUI() override;
 
   bool Init(const std::string& locale) override;
@@ -249,7 +311,7 @@
   int SetSwCallback(int code, int value) override;
 
  protected:
-  static constexpr int kMenuIndent = 4;
+  static constexpr int kMenuIndent = 24;
 
   // The margin that we don't want to use for showing texts (e.g. round screen, or screen with
   // rounded corners).
@@ -329,6 +391,7 @@
   // Implementation of the draw functions in DrawInterface.
   void SetColor(UIElement e) const override;
   void DrawHighlightBar(int x, int y, int width, int height) const override;
+  void DrawScrollBar(int y, int height) const override;
   int DrawHorizontalRule(int y) const override;
   void DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx,
                    int dy) const override;
@@ -337,6 +400,20 @@
   int DrawTextLine(int x, int y, const std::string& line, bool bold) const override;
   int DrawTextLines(int x, int y, const std::vector<std::string>& lines) const override;
   int DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const override;
+  int MenuCharHeight() const override {
+    return menu_char_height_;
+  }
+  int MenuCharWidth() const override {
+    return menu_char_width_;
+  }
+  int MenuItemPadding() const override {
+    return menu_char_height_ * 2 / 3;
+  }
+  int MenuItemHeight() const override {
+    return MenuCharHeight() + 2 * MenuItemPadding();
+  }
+
+  std::unique_ptr<MenuDrawFunctions> menu_draw_funcs_;
 
   // The layout to use.
   int layout_;
@@ -354,6 +431,9 @@
   std::unique_ptr<GRSurface> wipe_data_confirmation_text_;
   std::unique_ptr<GRSurface> wipe_data_menu_header_text_;
 
+  std::unique_ptr<GRSurface> lineage_logo_;
+  std::unique_ptr<GRSurface> back_icon_;
+  std::unique_ptr<GRSurface> back_icon_sel_;
   std::unique_ptr<GRSurface> fastbootd_logo_;
 
   // current_icon_ points to one of the frames in intro_frames_ or loop_frames_, indexed by
@@ -390,7 +470,6 @@
 
   std::vector<std::string> title_lines_;
 
-  bool scrollable_menu_;
   std::unique_ptr<Menu> menu_;
 
   // An alternate text screen, swapped with 'text_' when we're viewing a log file.
@@ -403,6 +482,8 @@
 
   int char_width_;
   int char_height_;
+  int menu_char_height_;
+  int menu_char_width_;
 
   // The locale that's used to show the rendered texts.
   std::string locale_;
diff --git a/recovery_ui/screen_ui.cpp b/recovery_ui/screen_ui.cpp
index 13f6213..82d51f9 100644
--- a/recovery_ui/screen_ui.cpp
+++ b/recovery_ui/screen_ui.cpp
@@ -63,27 +63,26 @@
 Menu::Menu(size_t initial_selection, const DrawInterface& draw_func)
     : selection_(initial_selection), draw_funcs_(draw_func) {}
 
-size_t Menu::selection() const {
+int Menu::selection() const {
   return selection_;
 }
 
-TextMenu::TextMenu(bool scrollable, size_t max_items, size_t max_length,
+TextMenu::TextMenu(bool wrappable, size_t max_items, size_t max_length,
                    const std::vector<std::string>& headers, const std::vector<std::string>& items,
                    size_t initial_selection, int char_height, const DrawInterface& draw_funcs)
     : Menu(initial_selection, draw_funcs),
-      scrollable_(scrollable),
+      wrappable_(wrappable),
       max_display_items_(max_items),
       max_item_length_(max_length),
       text_headers_(headers),
-      menu_start_(0),
       char_height_(char_height) {
   CHECK_LE(max_items, static_cast<size_t>(std::numeric_limits<int>::max()));
 
-  // It's fine to have more entries than text_rows_ if scrollable menu is supported.
-  size_t items_count = scrollable_ ? items.size() : std::min(items.size(), max_display_items_);
+  size_t items_count = items.size();
   for (size_t i = 0; i < items_count; ++i) {
     text_items_.emplace_back(items[i].substr(0, max_item_length_));
   }
+  menu_start_ = std::max(0, (int)selection_ - (int)max_display_items_ + 1);
 
   CHECK(!text_items_.empty());
 }
@@ -111,12 +110,12 @@
 }
 
 bool TextMenu::ItemsOverflow(std::string* cur_selection_str) const {
-  if (!scrollable_ || ItemsCount() <= max_display_items_) {
+  if (ItemsCount() <= max_display_items_) {
     return false;
   }
 
   *cur_selection_str =
-      android::base::StringPrintf("Current item: %zu/%zu", selection_ + 1, ItemsCount());
+      android::base::StringPrintf("Current item: %d/%zu", selection_ + 1, ItemsCount());
   return true;
 }
 
@@ -125,30 +124,22 @@
   CHECK_LE(ItemsCount(), static_cast<size_t>(std::numeric_limits<int>::max()));
   int count = ItemsCount();
 
-  // Wraps the selection at boundary if the menu is not scrollable.
-  if (!scrollable_) {
-    if (sel < 0) {
-      selection_ = count - 1;
-    } else if (sel >= count) {
-      selection_ = 0;
-    } else {
-      selection_ = sel;
-    }
+  int min = IsMain() ? 0 : -1; // -1 is back arrow
 
-    return selection_;
+  if (sel < min) {
+    selection_ = wrappable() ? count - 1 : min;
+  } else if (sel >= count) {
+    selection_ = wrappable() ? min : count - 1;
+  } else {
+    selection_ = sel;
   }
 
-  if (sel < 0) {
-    selection_ = 0;
-  } else if (sel >= count) {
-    selection_ = count - 1;
-  } else {
-    if (static_cast<size_t>(sel) < menu_start_) {
-      menu_start_--;
-    } else if (static_cast<size_t>(sel) >= MenuEnd()) {
-      menu_start_++;
+  if (selection_ >= 0) {
+    if (selection_ < menu_start_) {
+      menu_start_ = selection_;
+    } else if (static_cast<size_t>(selection_) >= MenuEnd()) {
+      menu_start_ = selection_ - max_display_items_ + 1;
     }
-    selection_ = sel;
   }
 
   return selection_;
@@ -158,37 +149,28 @@
   int offset = 0;
 
   draw_funcs_.SetColor(UIElement::HEADER);
-  if (!scrollable()) {
-    offset += draw_funcs_.DrawWrappedTextLines(x, y + offset, text_headers());
-  } else {
-    offset += draw_funcs_.DrawTextLines(x, y + offset, text_headers());
-    // Show the current menu item number in relation to total number if items don't fit on the
-    // screen.
-    std::string cur_selection_str;
-    if (ItemsOverflow(&cur_selection_str)) {
-      offset += draw_funcs_.DrawTextLine(x, y + offset, cur_selection_str, true);
-    }
-  }
+  offset += draw_funcs_.DrawWrappedTextLines(x, y + offset, text_headers());
 
   return offset;
 }
 
 int TextMenu::DrawItems(int x, int y, int screen_width, bool long_press) const {
   int offset = 0;
+  int padding = draw_funcs_.MenuItemPadding();
 
   draw_funcs_.SetColor(UIElement::MENU);
-  // Do not draw the horizontal rule for wear devices.
-  if (!scrollable()) {
-    offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4;
-  }
+  offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4;
+
+  int item_container_offset = offset; // store it for drawing scrollbar on most top
+
   for (size_t i = MenuStart(); i < MenuEnd(); ++i) {
     bool bold = false;
     if (i == selection()) {
       // Draw the highlight bar.
       draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG);
 
-      int bar_height = char_height_ + 4;
-      draw_funcs_.DrawHighlightBar(0, y + offset - 2, screen_width, bar_height);
+      int bar_height = padding + char_height_ + padding;
+      draw_funcs_.DrawHighlightBar(0, y + offset, screen_width, bar_height);
 
       // Bold white text for the selected item.
       draw_funcs_.SetColor(UIElement::MENU_SEL_FG);
@@ -200,6 +182,15 @@
   }
   offset += draw_funcs_.DrawHorizontalRule(y + offset);
 
+  std::string unused;
+  if (ItemsOverflow(&unused)) {
+    int container_height = max_display_items_ * (2 * padding + char_height_);
+    int bar_height = container_height / (text_items_.size() - max_display_items_ + 1);
+    int start_y = y + item_container_offset + bar_height * menu_start_;
+    draw_funcs_.SetColor(UIElement::SCROLLBAR);
+    draw_funcs_.DrawScrollBar(start_y, bar_height);
+  }
+
   return offset;
 }
 
@@ -305,13 +296,56 @@
   return true;
 }
 
-ScreenRecoveryUI::ScreenRecoveryUI() : ScreenRecoveryUI(false) {}
+MenuDrawFunctions::MenuDrawFunctions(const DrawInterface& wrappee)
+    : wrappee_(wrappee) {
+}
+
+int MenuDrawFunctions::DrawTextLine(int x, int y, const std::string& line, bool bold) const {
+  gr_text(gr_menu_font(), x, y + MenuItemPadding(), line.c_str(), bold);
+  return 2 * MenuItemPadding() + MenuCharHeight();
+}
+
+int MenuDrawFunctions::DrawTextLines(int x, int y, const std::vector<std::string>& lines) const {
+  int offset = 0;
+  for (const auto& line : lines) {
+    offset += DrawTextLine(x, y + offset, line, false);
+  }
+  return offset;
+}
+
+int MenuDrawFunctions::DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const {
+  // Keep symmetrical margins based on the given offset (i.e. x).
+  size_t text_cols = (gr_fb_width() - x * 2) / MenuCharWidth();
+  int offset = 0;
+  for (const auto& line : lines) {
+    size_t next_start = 0;
+    while (next_start < line.size()) {
+      std::string sub = line.substr(next_start, text_cols + 1);
+      if (sub.size() <= text_cols) {
+        next_start += sub.size();
+      } else {
+        // Line too long and must be wrapped to text_cols columns.
+        size_t last_space = sub.find_last_of(" \t\n");
+        if (last_space == std::string::npos) {
+          // No space found, just draw as much as we can.
+          sub.resize(text_cols);
+          next_start += text_cols;
+        } else {
+          sub.resize(last_space);
+          next_start += last_space + 1;
+        }
+      }
+      offset += DrawTextLine(x, y + offset, sub, false);
+    }
+  }
+  return offset;
+}
 
 constexpr int kDefaultMarginHeight = 0;
 constexpr int kDefaultMarginWidth = 0;
 constexpr int kDefaultAnimationFps = 30;
 
-ScreenRecoveryUI::ScreenRecoveryUI(bool scrollable_menu)
+ScreenRecoveryUI::ScreenRecoveryUI()
     : margin_width_(
           android::base::GetIntProperty("ro.recovery.ui.margin_width", kDefaultMarginWidth)),
       margin_height_(
@@ -336,7 +370,6 @@
       text_row_(0),
       show_text(false),
       show_text_ever(false),
-      scrollable_menu_(scrollable_menu),
       file_viewer_text_(nullptr),
       stage(-1),
       max_stage(-1),
@@ -500,6 +533,7 @@
   }
 }
 
+/* Lineage teal: #167c80 */
 void ScreenRecoveryUI::SetColor(UIElement e) const {
   switch (e) {
     case UIElement::INFO:
@@ -510,13 +544,14 @@
       break;
     case UIElement::MENU:
     case UIElement::MENU_SEL_BG:
-      gr_color(0, 106, 157, 255);
+      gr_color(0xd8, 0xd8, 0xd8, 255);
       break;
     case UIElement::MENU_SEL_BG_ACTIVE:
       gr_color(0, 156, 100, 255);
       break;
     case UIElement::MENU_SEL_FG:
-      gr_color(255, 255, 255, 255);
+    case UIElement::SCROLLBAR:
+      gr_color(0x16, 0x7c, 0x80, 255);
       break;
     case UIElement::LOG:
       gr_color(196, 196, 196, 255);
@@ -627,9 +662,17 @@
 }
 
 void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const {
+  if (y + height > ScreenHeight())
+    height = ScreenHeight() - y;
   gr_fill(x, y, x + width, y + height);
 }
 
+void ScreenRecoveryUI::DrawScrollBar(int y, int height) const {
+  int x = ScreenWidth() - margin_width_;
+  int width = 8;
+  gr_fill(x - width, y, x, y + height);
+}
+
 void ScreenRecoveryUI::DrawFill(int x, int y, int w, int h) const {
   gr_fill(x, y, w, h);
 }
@@ -731,11 +774,27 @@
 
     SetColor(UIElement::INFO);
 
-    for (size_t i = 0; i < title_lines_.size(); i++) {
-      y += DrawTextLine(x, y, title_lines_[i], i == 0);
-    }
+    if (lineage_logo_ && back_icon_) {
+      auto logo_width = gr_get_width(lineage_logo_.get());
+      auto logo_height = gr_get_height(lineage_logo_.get());
+      auto centered_x = ScreenWidth() / 2 - logo_width / 2;
+      DrawSurface(lineage_logo_.get(), 0, 0, logo_width, logo_height, centered_x, y);
+      y += logo_height;
 
-    y += DrawTextLines(x, y, help_message);
+      if (!menu_->IsMain()) {
+        auto icon_w = gr_get_width(back_icon_.get());
+        auto icon_h = gr_get_height(back_icon_.get());
+        auto icon_x = centered_x / 2 - icon_w / 2;
+        auto icon_y = y - logo_height / 2 - icon_h / 2;
+        gr_blit(back_icon_sel_ && menu_->selection() == -1 ? back_icon_sel_.get() : back_icon_.get(),
+                0, 0, icon_w, icon_h, icon_x, icon_y);
+      }
+    } else {
+      for (size_t i = 0; i < title_lines_.size(); i++) {
+        y += DrawTextLine(x, y, title_lines_[i], i == 0);
+      }
+      y += DrawTextLines(x, y, help_message);
+    }
 
     y += menu_->DrawHeader(x, y);
     y += menu_->DrawItems(x, y, ScreenWidth(), IsLongPress());
@@ -875,6 +934,7 @@
     return false;
   }
   gr_font_size(gr_sys_font(), &char_width_, &char_height_);
+  gr_font_size(gr_menu_font(), &menu_char_width_, &menu_char_height_);
   text_rows_ = (ScreenHeight() - margin_height_ * 2) / char_height_;
   text_cols_ = (ScreenWidth() - margin_width_ * 2) / char_width_;
   return true;
@@ -919,6 +979,7 @@
   if (!InitTextParams()) {
     return false;
   }
+  menu_draw_funcs_ = std::make_unique<MenuDrawFunctions>(*this);
 
   if (blank_unblank_on_init_) {
     gr_fb_blank(true);
@@ -949,6 +1010,9 @@
   no_command_text_ = LoadLocalizedBitmap("no_command_text");
   error_text_ = LoadLocalizedBitmap("error_text");
 
+  lineage_logo_ = LoadBitmap("logo_image");
+  back_icon_ = LoadBitmap("ic_back");
+  back_icon_sel_ = LoadBitmap("ic_back_sel");
   if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false) ||
       android::base::GetBoolProperty("ro.fastbootd.available", false)) {
     fastbootd_logo_ = LoadBitmap("fastbootd");
@@ -1214,14 +1278,15 @@
 std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu(const std::vector<std::string>& text_headers,
                                                    const std::vector<std::string>& text_items,
                                                    size_t initial_selection) const {
-  if (text_rows_ > 0 && text_cols_ > 1) {
-    return std::make_unique<TextMenu>(scrollable_menu_, text_rows_, text_cols_ - 1, text_headers,
-                                      text_items, initial_selection, char_height_, *this);
-  }
-
-  fprintf(stderr, "Failed to create text menu, text_rows %zu, text_cols %zu.\n", text_rows_,
-          text_cols_);
-  return nullptr;
+  int menu_char_width = MenuCharWidth();
+  int menu_char_height = MenuCharHeight();
+  int menu_item_padding = MenuItemPadding();
+  int menu_rows = (ScreenHeight() - margin_height_*2 - gr_get_height(lineage_logo_.get()))
+                  / (menu_char_height + 2 * menu_item_padding) - text_headers.size();
+  int menu_cols = (ScreenWidth() - margin_width_*2 - kMenuIndent) / menu_char_width;
+  bool wrap_selection = !HasThreeButtons() && !HasTouchScreen();
+  return std::make_unique<TextMenu>(wrap_selection, menu_rows, menu_cols, text_headers, text_items,
+                                    initial_selection, menu_char_height, *menu_draw_funcs_);
 }
 
 int ScreenRecoveryUI::SelectMenu(int sel) {
@@ -1281,7 +1346,11 @@
           selected = SelectMenu(++selected);
           break;
         case Device::kInvokeItem:
-          chosen_item = selected;
+          if (selected < 0) {
+            chosen_item = Device::kGoBack;
+          } else {
+            chosen_item = selected;
+          }
           break;
         case Device::kNoAction:
           break;
diff --git a/recovery_ui/wear_ui.cpp b/recovery_ui/wear_ui.cpp
index 8d8108f..7aaf59b 100644
--- a/recovery_ui/wear_ui.cpp
+++ b/recovery_ui/wear_ui.cpp
@@ -29,7 +29,7 @@
 constexpr int kDefaultMenuUnusableRows = 9;
 
 WearRecoveryUI::WearRecoveryUI()
-    : ScreenRecoveryUI(true),
+    : ScreenRecoveryUI(),
       progress_bar_baseline_(android::base::GetIntProperty("ro.recovery.ui.progress_bar_baseline",
                                                            kDefaultProgressBarBaseline)),
       menu_unusable_rows_(android::base::GetIntProperty("ro.recovery.ui.menu_unusable_rows",
@@ -99,7 +99,7 @@
                                                  const std::vector<std::string>& text_items,
                                                  size_t initial_selection) const {
   if (text_rows_ > 0 && text_cols_ > 0) {
-    return std::make_unique<TextMenu>(scrollable_menu_, text_rows_ - menu_unusable_rows_ - 1,
+    return std::make_unique<TextMenu>(false, text_rows_ - menu_unusable_rows_ - 1,
                                       text_cols_ - 1, text_headers, text_items, initial_selection,
                                       char_height_, *this);
   }