Class: CursesMenu
- Inherits:
-
Object
- Object
- CursesMenu
- Defined in:
- lib/curses_menu.rb,
lib/curses_menu/version.rb,
lib/curses_menu/curses_row.rb
Overview
Provide a menu using curses with keys navigation and selection
Defined Under Namespace
Classes: CursesRow
Constant Summary collapse
- COLORS_TITLE =
Define some color pairs names. The integer value is meaningless in itself but they all have to be different.
1
- COLORS_LINE =
2
- COLORS_MENU_ITEM =
3
- COLORS_MENU_ITEM_SELECTED =
4
- COLORS_INPUT =
5
- COLORS_GREEN =
6
- COLORS_RED =
7
- COLORS_YELLOW =
8
- COLORS_BLUE =
9
- COLORS_WHITE =
10
- KEY_ENTER =
curses keys that are not defined by Curses, but that are returned by getch
10
- KEY_ESCAPE =
27
- VERSION =
'0.2.0'
Instance Method Summary collapse
-
#initialize(title, key_presses: [], &menu_items_def) ⇒ CursesMenu
constructor
Constructor.
-
#item(title, actions: {}, &action) ⇒ Object
Register a new menu item.
Constructor Details
#initialize(title, key_presses: [], &menu_items_def) ⇒ CursesMenu
Constructor. Display a list of choices, ask for user input and execute the choice made. Repeat the operation unless one of the code returns the :menu_exit symbol.
- Parameters
-
title (String): Title of those choices
-
key_presses (Array<Object>): List of key presses to automatically apply [default: []] Can be characters or ascii values, as returned by curses’ getch. The list is modified in place along with its consumption, so that it can be reused in sub-menus if needed.
-
*&menu_items_def* (Proc): Code to be called to get the list of choices. This code can call the following methods to design the menu:
- Parameters
-
menu (CursesMenu): The CursesMenu instance
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/curses_menu.rb', line 36 def initialize(title, key_presses: [], &) @current_menu_items = nil @curses_initialized = false current_items = (&) selected_idx = 0 raise "Menu #{title} has no items to select" if selected_idx.nil? window = begin max_displayed_items = window.maxy - 5 display_first_idx = 0 display_first_char_idx = 0 loop do # TODO: Don't redraw fixed items for performance # Display the title window.setpos(0, 0) print(window, '', default_color_pair: COLORS_TITLE, pad: '=') print(window, "= #{title}", default_color_pair: COLORS_TITLE, pad: ' ', single_line: true) print(window, '', default_color_pair: COLORS_TITLE, pad: '-') # Display the menu current_items[display_first_idx..display_first_idx + max_displayed_items - 1].each.with_index do |item_info, idx| selected = display_first_idx + idx == selected_idx # Keep a cache of titles as they can be loaded in a lazy way for performance item_info[:title_cached] = item_info[:title].is_a?(Proc) ? item_info[:title].call : item_info[:title] unless item_info.key?(:title_cached) print( window, item_info[:title_cached], from: display_first_char_idx, default_color_pair: item_info.key?(:actions) ? COLORS_MENU_ITEM : COLORS_LINE, force_color_pair: selected ? COLORS_MENU_ITEM_SELECTED : nil, pad: selected ? ' ' : nil, single_line: true ) end # Display the footer window.setpos(window.maxy - 2, 0) print(window, '', default_color_pair: COLORS_TITLE, pad: '=') display_actions = { 'Arrows/Home/End' => 'Navigate', 'Esc' => 'Exit' } # Keep a cache of actions as they can be loaded in a lazy way for performance current_items[selected_idx][:actions_cached] = current_items[selected_idx][:actions].is_a?(Proc) ? current_items[selected_idx][:actions].call : current_items[selected_idx][:actions] unless current_items[selected_idx].key?(:actions_cached) if current_items[selected_idx][:actions_cached] display_actions.merge!( current_items[selected_idx][:actions_cached].to_h do |action_shortcut, action_info| [ case action_shortcut when KEY_ENTER 'Enter' else action_shortcut end, action_info[:name] ] end ) end print( window, "= #{display_actions.sort.map { |(shortcut, name)| "#{shortcut}: #{name}" }.join(' | ')}", from: display_first_char_idx, default_color_pair: COLORS_TITLE, pad: ' ', add_nl: false, single_line: true ) window.refresh user_choice = nil loop do user_choice = key_presses.empty? ? window.getch : key_presses.shift break unless user_choice.nil? sleep 0.01 end case user_choice when Curses::KEY_RIGHT display_first_char_idx += 1 when Curses::KEY_LEFT display_first_char_idx -= 1 when Curses::KEY_UP selected_idx -= 1 when Curses::KEY_PPAGE selected_idx -= max_displayed_items - 1 when Curses::KEY_DOWN selected_idx += 1 when Curses::KEY_NPAGE selected_idx += max_displayed_items - 1 when Curses::KEY_HOME selected_idx = 0 when Curses::KEY_END selected_idx = current_items.size - 1 when KEY_ESCAPE break else # Check actions # Keep a cache of actions as they can be loaded in a lazy way for performance current_items[selected_idx][:actions_cached] = current_items[selected_idx][:actions].is_a?(Proc) ? current_items[selected_idx][:actions].call : current_items[selected_idx][:actions] unless current_items[selected_idx].key?(:actions_cached) if current_items[selected_idx][:actions_cached]&.key?(user_choice) result = current_items[selected_idx][:actions_cached][user_choice][:execute].call if result.is_a?(Symbol) case result when :menu_exit break when :menu_refresh current_items = (&) end end window = window.clear end end # Stay in bounds display_first_char_idx = 0 if display_first_char_idx.negative? selected_idx = current_items.size - 1 if selected_idx >= current_items.size selected_idx = 0 if selected_idx.negative? if selected_idx < display_first_idx display_first_idx = selected_idx elsif selected_idx >= display_first_idx + max_displayed_items display_first_idx = selected_idx - max_displayed_items + 1 end end ensure end end |
Instance Method Details
#item(title, actions: {}, &action) ⇒ Object
Register a new menu item. This method is meant to be called from a choose_from call.
- Parameters
-
title (String, CursesRow or Proc): Text to be displayed for this item, or Proc returning this text when needed (lazy loading)
-
actions (Hash<Object, Hash<Symbol,Object> > or Proc): Associated actions to this item, per shortcut, or Proc returning those actions when needed (lazy loading) [default: {}]
-
name (String): Name of this action (displayed at the bottom of the menu)
-
execute (Proc): Code called when this action is selected
In case of lazy loading (with a Proc), the title Proc will always be called first.
-
-
*&action* (Proc): Code called if the item is selected (action for the enter key) [optional].
- Result
-
Symbol or Object: If the code returns a symbol, the menu will behave in a specific way:
-
menu_exit: the menu selection exits.
-
menu_refresh: The menu will compute again its items.
-
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/curses_menu.rb', line 178 def item(title, actions: {}, &action) = { title: title } all_actions = if action.nil? actions else mapped_default_action = { KEY_ENTER => { name: 'Select', execute: action } } if actions.is_a?(Proc) # Make sure we keep the lazyness proc do actions.call.merge(mapped_default_action) end else actions.merge(mapped_default_action) end end [:actions] = all_actions if all_actions.is_a?(Proc) || !all_actions.empty? @current_menu_items << end |