curses_menu 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c2226bebb142878eb4467c0471c3978f683746634acc51e6852f3b9298fbd3cf
4
+ data.tar.gz: 23f6626cf28f160149106907a307cdc49ce64741f62ffb6053e16ad276267223
5
+ SHA512:
6
+ metadata.gz: 31d298f6320962b6061936eff73ba7a517f10c0ec0b8b681455ae71df372b8ef5e0ee674d598fe094c76de18531a1d4a3a07b58ae2d405a6112a03b00bfd5275
7
+ data.tar.gz: 6f49adde82fac8dfd6e7f513184735b3f9e9f0024857b00604b9180bd926659a1a9ddece7ef112ad671f1921d60c236204359c8d6fe361a491c9fe393a31cd4f
@@ -0,0 +1,25 @@
1
+ require 'curses_menu'
2
+
3
+ nbr = 0
4
+ CursesMenu.new 'Items can have several actions. Look at the footer!' do |menu|
5
+ menu.item "Current number is #{nbr} - Use a or d", actions: {
6
+ 'd' => {
7
+ name: 'Increase',
8
+ execute: proc do
9
+ nbr += 1
10
+ :menu_refresh
11
+ end
12
+ },
13
+ 'a' => {
14
+ name: 'Decrease',
15
+ execute: proc do
16
+ nbr -= 1
17
+ :menu_refresh
18
+ end
19
+ }
20
+ }
21
+ menu.item 'Quit' do
22
+ puts 'Quitting...'
23
+ :menu_exit
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ require 'curses_menu'
2
+
3
+ keys = [
4
+ # Select first
5
+ CursesMenu::KEY_ENTER,
6
+ # Select second
7
+ Curses::KEY_DOWN,
8
+ 'a',
9
+ 'b',
10
+ # Select third (sub-menu)
11
+ Curses::KEY_DOWN,
12
+ CursesMenu::KEY_ENTER,
13
+ # Select sub-menu first
14
+ CursesMenu::KEY_ENTER,
15
+ # Exit sub-menu
16
+ CursesMenu::KEY_ESCAPE,
17
+ # Navigate a bit
18
+ Curses::KEY_NPAGE,
19
+ Curses::KEY_HOME,
20
+ # Select last
21
+ Curses::KEY_END,
22
+ CursesMenu::KEY_ENTER
23
+ ]
24
+ CursesMenu.new('Menu being used automatically', key_presses: keys) do |menu|
25
+ menu.item 'Simple item' do
26
+ puts 'Selected a simple item'
27
+ end
28
+ menu.item 'Several actions on this item', actions: {
29
+ 'a' => {
30
+ name: 'Action A',
31
+ execute: proc { puts 'Selected action A' }
32
+ },
33
+ 'b' => {
34
+ name: 'Action B',
35
+ execute: proc { puts 'Selected action B' }
36
+ }
37
+ }
38
+ menu.item 'Sub-menu' do
39
+ CursesMenu.new('Sub-menu!', key_presses: keys) do |sub_menu|
40
+ sub_menu.item 'Simple sub-menu item' do
41
+ puts 'Selected item from sub-menu'
42
+ end
43
+ end
44
+ end
45
+ menu.item 'Quit' do
46
+ puts 'Quitting...'
47
+ :menu_exit
48
+ end
49
+ end
@@ -0,0 +1,130 @@
1
+ require 'curses_menu'
2
+
3
+ dynamic_row_1 = CursesMenu::CursesRow.new(
4
+ first_cell: { text: 'Select to' },
5
+ second_cell: {
6
+ text: 'change the',
7
+ color_pair: CursesMenu::COLORS_GREEN
8
+ },
9
+ third_cell: {
10
+ text: 'cells order',
11
+ color_pair: CursesMenu::COLORS_RED
12
+ }
13
+ )
14
+ dynamic_row_2 = CursesMenu::CursesRow.new(
15
+ first_cell: { text: 'Select to change' },
16
+ second_cell: {
17
+ text: 'the cells properties',
18
+ color_pair: CursesMenu::COLORS_GREEN,
19
+ fixed_size: 40
20
+ }
21
+ )
22
+ CursesMenu.new 'Extended formatting available too!' do |menu|
23
+ menu.item CursesMenu::CursesRow.new(
24
+ default_cell: {
25
+ text: 'Simple color change - GREEN!',
26
+ color_pair: CursesMenu::COLORS_GREEN
27
+ }
28
+ )
29
+ menu.item CursesMenu::CursesRow.new(
30
+ green_cell: {
31
+ text: 'Several cells ',
32
+ color_pair: CursesMenu::COLORS_GREEN
33
+ },
34
+ red_cell: {
35
+ text: 'with different ',
36
+ color_pair: CursesMenu::COLORS_RED
37
+ },
38
+ blue_cell: {
39
+ text: 'formatting',
40
+ color_pair: CursesMenu::COLORS_BLUE
41
+ }
42
+ )
43
+ menu.item CursesMenu::CursesRow.new(
44
+ default_cell: {
45
+ text: 'Use prefixes and suffixes',
46
+ begin_with: '[ ',
47
+ end_with: ' ]'
48
+ }
49
+ )
50
+ menu.item CursesMenu::CursesRow.new(
51
+ first_cell: {
52
+ text: 'This will have a fixed size!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!',
53
+ begin_with: '[ ',
54
+ end_with: ' ]',
55
+ fixed_size: 40
56
+ },
57
+ second_cell: {
58
+ text: 'And other cells will be aligned',
59
+ color_pair: CursesMenu::COLORS_GREEN
60
+ }
61
+ )
62
+ menu.item CursesMenu::CursesRow.new(
63
+ first_cell: {
64
+ text: 'Pretty nice',
65
+ fixed_size: 40
66
+ },
67
+ second_cell: {
68
+ text: 'for alignment',
69
+ color_pair: CursesMenu::COLORS_GREEN
70
+ }
71
+ )
72
+ menu.item CursesMenu::CursesRow.new(
73
+ first_cell: {
74
+ text: 'And you can justify',
75
+ justify: :right,
76
+ fixed_size: 40
77
+ },
78
+ second_cell: {
79
+ text: 'your text when size is fixed!',
80
+ justify: :left,
81
+ color_pair: CursesMenu::COLORS_GREEN
82
+ }
83
+ )
84
+ menu.item CursesMenu::CursesRow.new(
85
+ first_cell: {
86
+ text: 'You can even',
87
+ justify: :right,
88
+ fixed_size: 40,
89
+ pad: '_-'
90
+ },
91
+ second_cell: {
92
+ text: 'pad it!',
93
+ justify: :left,
94
+ color_pair: CursesMenu::COLORS_GREEN,
95
+ fixed_size: 40,
96
+ pad: '*'
97
+ }
98
+ )
99
+ menu.item CursesMenu::CursesRow.new(
100
+ {
101
+ first_cell: { text: 'Use a' },
102
+ second_cell: {
103
+ text: 'different separator',
104
+ color_pair: CursesMenu::COLORS_GREEN
105
+ },
106
+ third_cell: { text: 'between cells' }
107
+ },
108
+ separator: '|'
109
+ )
110
+ menu.item dynamic_row_1 do
111
+ dynamic_row_1.cells_order([:first_cell, :second_cell, :third_cell].sort_by { rand })
112
+ :menu_refresh
113
+ end
114
+ menu.item dynamic_row_2 do
115
+ dynamic_row_2.change_cells(
116
+ first_cell: {
117
+ color_pair: [CursesMenu::COLORS_GREEN, CursesMenu::COLORS_RED, CursesMenu::COLORS_BLUE].sample
118
+ },
119
+ second_cell: {
120
+ color_pair: [CursesMenu::COLORS_GREEN, CursesMenu::COLORS_RED, CursesMenu::COLORS_BLUE].sample,
121
+ pad: ['*', ' ', '|', '='].sample
122
+ }
123
+ )
124
+ :menu_refresh
125
+ end
126
+ menu.item 'Quit' do
127
+ puts 'Quitting...'
128
+ :menu_exit
129
+ end
130
+ end
@@ -0,0 +1,8 @@
1
+ require 'curses_menu'
2
+
3
+ CursesMenu.new 'My awesome new menu!' do |menu|
4
+ menu.item 'How\'s life?' do
5
+ puts 'Couldn\'t be easier'
6
+ :menu_exit
7
+ end
8
+ end
@@ -0,0 +1,22 @@
1
+ require 'curses_menu'
2
+
3
+ nbr = 0
4
+ switch = false
5
+ CursesMenu.new 'Menu being refreshed when selecting things' do |menu|
6
+ menu.item "Current number is #{nbr} - Select me for +1" do
7
+ nbr += 1
8
+ :menu_refresh
9
+ end
10
+ menu.item "Current number is #{nbr} - Select me for -1" do
11
+ nbr -= 1
12
+ :menu_refresh
13
+ end
14
+ menu.item "[#{switch ? '*' : ' '}] Switch me!" do
15
+ switch = !switch
16
+ :menu_refresh
17
+ end
18
+ menu.item 'Quit' do
19
+ puts 'Quitting...'
20
+ :menu_exit
21
+ end
22
+ end
@@ -0,0 +1,9 @@
1
+ require 'curses_menu'
2
+
3
+ CursesMenu.new 'Use all arrows, Page up/down, Home and End keys!' do |menu|
4
+ menu.item('Quit') { :menu_exit }
5
+ menu.item 'That\'s a big menu item! ' * 20
6
+ 1000.times do |idx|
7
+ menu.item "Menu item #{idx}"
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ require 'curses_menu'
2
+
3
+ CursesMenu.new 'We have several items, some of them have no action' do |menu|
4
+ menu.item 'Nothing to do with me'
5
+ menu.item 'Select me - I\'m option A!' do
6
+ puts 'You have selected A. Press enter to continue.'
7
+ $stdin.gets
8
+ end
9
+ menu.item 'Or select me - Option B!' do
10
+ puts 'You have selected B. Press enter to continue.'
11
+ $stdin.gets
12
+ end
13
+ menu.item '---- Separator'
14
+ menu.item 'Quit' do
15
+ puts 'Quitting...'
16
+ :menu_exit
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ require 'curses_menu'
2
+
3
+ CursesMenu.new 'Top menu' do |menu|
4
+ menu.item 'Enter menu 1' do
5
+ CursesMenu.new 'Sub-menu 1' do |sub_menu|
6
+ sub_menu.item 'We are in sub-menu 1'
7
+ sub_menu.item('Back') { :menu_exit }
8
+ end
9
+ end
10
+ menu.item 'Enter menu 2' do
11
+ CursesMenu.new 'Sub-menu 2' do |sub_menu|
12
+ sub_menu.item 'We are in sub-menu 2'
13
+ sub_menu.item('Back') { :menu_exit }
14
+ end
15
+ end
16
+ menu.item 'Quit' do
17
+ puts 'Quitting...'
18
+ :menu_exit
19
+ end
20
+ end
@@ -0,0 +1,241 @@
1
+ require 'curses'
2
+ require 'curses_menu/curses_row'
3
+
4
+ class CursesMenu
5
+
6
+ # Define some color pairs names.
7
+ # The integer value is meaningless in itself but they all have to be different.
8
+ COLORS_TITLE = 1
9
+ COLORS_LINE = 2
10
+ COLORS_MENU_ITEM = 3
11
+ COLORS_MENU_ITEM_SELECTED = 4
12
+ COLORS_INPUT = 5
13
+ COLORS_GREEN = 6
14
+ COLORS_RED = 7
15
+ COLORS_YELLOW = 8
16
+ COLORS_BLUE = 9
17
+
18
+ # curses keys that are not defined by Curses, but that are returned by getch
19
+ KEY_ENTER = 10
20
+ KEY_ESCAPE = 27
21
+
22
+ # Constructor.
23
+ # Display a list of choices, ask for user input and execute the choice made.
24
+ # Repeat the operation unless one of the code returns the :menu_exit symbol.
25
+ #
26
+ # Parameters::
27
+ # * *title* (String): Title of those choices
28
+ # * *key_presses* (Array<Object>): List of key presses to automatically apply [default: []]
29
+ # Can be characters or ascii values, as returned by curses' getch.
30
+ # The list is modified in place along with its consumption, so that it can be reused in sub-menus if needed.
31
+ # * *&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:
32
+ # * Parameters::
33
+ # * *menu* (CursesMenu): The CursesMenu instance
34
+ def initialize(title, key_presses: [], &menu_items_def)
35
+ @current_menu_items = nil
36
+ @curses_initialized = false
37
+ current_items = gather_menu_items(&menu_items_def)
38
+ selected_idx = 0
39
+ raise "Menu #{title} has no items to select" if selected_idx.nil?
40
+ window = curses_menu_initialize
41
+ begin
42
+ max_displayed_items = window.maxy - 5
43
+ display_first_idx = 0
44
+ display_first_char_idx = 0
45
+ loop do
46
+ # TODO: Don't redraw fixed items for performance
47
+ # Display the title
48
+ window.setpos(0, 0)
49
+ print(window, '', default_color_pair: COLORS_TITLE, pad: '=')
50
+ print(window, "= #{title}", default_color_pair: COLORS_TITLE, pad: ' ', single_line: true)
51
+ print(window, '', default_color_pair: COLORS_TITLE, pad: '-')
52
+ # Display the menu
53
+ current_items[display_first_idx..display_first_idx + max_displayed_items - 1].each.with_index do |item_info, idx|
54
+ selected = display_first_idx + idx == selected_idx
55
+ print(
56
+ window,
57
+ item_info[:title],
58
+ from: display_first_char_idx,
59
+ default_color_pair: item_info.key?(:actions) ? COLORS_MENU_ITEM : COLORS_LINE,
60
+ force_color_pair: selected ? COLORS_MENU_ITEM_SELECTED : nil,
61
+ pad: selected ? ' ' : nil,
62
+ single_line: true
63
+ )
64
+ end
65
+ # Display the footer
66
+ window.setpos(window.maxy - 2, 0)
67
+ print(window, '', default_color_pair: COLORS_TITLE, pad: '=')
68
+ display_actions = {
69
+ 'Arrows/Home/End' => 'Navigate',
70
+ 'Esc' => 'Exit'
71
+ }
72
+ if current_items[selected_idx][:actions]
73
+ display_actions.merge!(Hash[current_items[selected_idx][:actions].map do |action_shortcut, action_info|
74
+ [
75
+ case action_shortcut
76
+ when KEY_ENTER
77
+ 'Enter'
78
+ else
79
+ action_shortcut
80
+ end,
81
+ action_info[:name]
82
+ ]
83
+ end])
84
+ end
85
+ print(
86
+ window,
87
+ "= #{display_actions.sort.map { |(shortcut, name)| "#{shortcut}: #{name}" }.join(' | ')}",
88
+ from: display_first_char_idx,
89
+ default_color_pair: COLORS_TITLE,
90
+ pad: ' ',
91
+ add_nl: false,
92
+ single_line: true
93
+ )
94
+ window.refresh
95
+ user_choice = nil
96
+ loop do
97
+ user_choice = key_presses.empty? ? window.getch : key_presses.shift
98
+ break unless user_choice.nil?
99
+ sleep 0.01
100
+ end
101
+ case user_choice
102
+ when Curses::KEY_RIGHT
103
+ display_first_char_idx += 1
104
+ when Curses::KEY_LEFT
105
+ display_first_char_idx -= 1
106
+ when Curses::KEY_UP
107
+ selected_idx -= 1
108
+ when Curses::KEY_PPAGE
109
+ selected_idx -= max_displayed_items - 1
110
+ when Curses::KEY_DOWN
111
+ selected_idx += 1
112
+ when Curses::KEY_NPAGE
113
+ selected_idx += max_displayed_items - 1
114
+ when Curses::KEY_HOME
115
+ selected_idx = 0
116
+ when Curses::KEY_END
117
+ selected_idx = current_items.size - 1
118
+ when KEY_ESCAPE
119
+ break
120
+ else
121
+ # Check actions
122
+ if current_items[selected_idx][:actions]&.key?(user_choice)
123
+ curses_menu_finalize
124
+ result = current_items[selected_idx][:actions][user_choice][:execute].call
125
+ if result.is_a?(Symbol)
126
+ case result
127
+ when :menu_exit
128
+ break
129
+ when :menu_refresh
130
+ current_items = gather_menu_items(&menu_items_def)
131
+ end
132
+ end
133
+ window = curses_menu_initialize
134
+ window.clear
135
+ end
136
+ end
137
+ # Stay in bounds
138
+ display_first_char_idx = 0 if display_first_char_idx < 0
139
+ selected_idx = current_items.size - 1 if selected_idx >= current_items.size
140
+ selected_idx = 0 if selected_idx < 0
141
+ if selected_idx < display_first_idx
142
+ display_first_idx = selected_idx
143
+ elsif selected_idx >= display_first_idx + max_displayed_items
144
+ display_first_idx = selected_idx - max_displayed_items + 1
145
+ end
146
+ end
147
+ ensure
148
+ curses_menu_finalize
149
+ end
150
+ end
151
+
152
+ # Register a new menu item.
153
+ # This method is meant to be called from a choose_from call.
154
+ #
155
+ # Parameters::
156
+ # * *title* (String or CursesRow): Text to be displayed for this item
157
+ # * *actions* (Hash<Object, Hash<Symbol,Object> >): Associated actions to this item, per shortcut [default: {}]
158
+ # * *name* (String): Name of this action (displayed at the bottom of the menu)
159
+ # * *execute* (Proc): Code called when this action is selected
160
+ # * *&action* (Proc): Code called if the item is selected (action for the enter key) [optional].
161
+ # * Result::
162
+ # * Symbol or Object: If the code returns a symbol, the menu will behave in a specific way:
163
+ # * *menu_exit*: the menu selection exits.
164
+ # * *menu_refresh*: The menu will compute again its items.
165
+ def item(title, actions: {}, &action)
166
+ menu_item_def = { title: title }
167
+ all_actions = action.nil? ? actions : actions.merge(KEY_ENTER => { name: 'Select', execute: action })
168
+ menu_item_def[:actions] = all_actions unless all_actions.empty?
169
+ @current_menu_items << menu_item_def
170
+ end
171
+
172
+ private
173
+
174
+ # Display a given curses string information.
175
+ #
176
+ # Parameters::
177
+ # * *window* (Window): The curses window in which we display.
178
+ # * *string* (String or CursesRow): The curses row, or as a single String.
179
+ # * See CursesRow#print_on for all the other parameters description
180
+ def print(window, string, from: 0, to: nil, default_color_pair: COLORS_LINE, force_color_pair: nil, pad: nil, add_nl: true, single_line: false)
181
+ string = CursesRow.new(default: { text: string }) if string.is_a?(String)
182
+ string.print_on(
183
+ window,
184
+ from: from,
185
+ to: to,
186
+ default_color_pair: default_color_pair,
187
+ force_color_pair: force_color_pair,
188
+ pad: pad,
189
+ add_nl: add_nl,
190
+ single_line: single_line
191
+ )
192
+ end
193
+
194
+ # Initialize and get the curses menu window
195
+ #
196
+ # Result::
197
+ # * Window: The curses menu window
198
+ def curses_menu_initialize
199
+ Curses.init_screen
200
+ # Use non-blocking key read, otherwise using Popen3 later blocks
201
+ Curses.timeout = 0
202
+ Curses.start_color
203
+ Curses.init_pair(COLORS_TITLE, Curses::COLOR_BLACK, Curses::COLOR_CYAN)
204
+ Curses.init_pair(COLORS_LINE, Curses::COLOR_WHITE, Curses::COLOR_BLACK)
205
+ Curses.init_pair(COLORS_MENU_ITEM, Curses::COLOR_WHITE, Curses::COLOR_BLACK)
206
+ Curses.init_pair(COLORS_MENU_ITEM_SELECTED, Curses::COLOR_BLACK, Curses::COLOR_WHITE)
207
+ Curses.init_pair(COLORS_INPUT, Curses::COLOR_WHITE, Curses::COLOR_BLUE)
208
+ Curses.init_pair(COLORS_GREEN, Curses::COLOR_GREEN, Curses::COLOR_BLACK)
209
+ Curses.init_pair(COLORS_RED, Curses::COLOR_RED, Curses::COLOR_BLACK)
210
+ Curses.init_pair(COLORS_YELLOW, Curses::COLOR_YELLOW, Curses::COLOR_BLACK)
211
+ Curses.init_pair(COLORS_BLUE, Curses::COLOR_BLUE, Curses::COLOR_BLACK)
212
+ window = Curses.stdscr
213
+ window.keypad = true
214
+ @curses_initialized = true
215
+ window
216
+ end
217
+
218
+ # Finalize the curses menu window
219
+ def curses_menu_finalize
220
+ Curses.close_screen if @curses_initialized
221
+ @curses_initialized = false
222
+ end
223
+
224
+ # Get menu items.
225
+ #
226
+ # Parameters::
227
+ # * Proc: Code defining the menu items
228
+ # * *menu* (CursesMenu): The menu for which we gather items.
229
+ # Result::
230
+ # * Array< Hash<Symbol,Object> >: List of items to be displayed
231
+ # * *title* (String): Item title to display
232
+ # * *actions* (Hash<Object, Hash<Symbol,Object> >): Associated actions to this item, per shortcut [optional]
233
+ # * *name* (String): Name of this action (displayed at the bottom of the menu)
234
+ # * *execute* (Proc): Code called when this action is selected
235
+ def gather_menu_items
236
+ @current_menu_items = []
237
+ yield self
238
+ @current_menu_items
239
+ end
240
+
241
+ end
@@ -0,0 +1,150 @@
1
+ class CursesMenu
2
+
3
+ # Definition of a row that stores for each cell the string and color information to be displayed
4
+ class CursesRow
5
+
6
+ # Constructor
7
+ #
8
+ # Parameters::
9
+ # * *cells* (Hash< Symbol, Hash<Symbol,Object> >): For each cell id (ordered), the cell info:
10
+ # * *text* (String): Text associated to this cell
11
+ # * *color_pair* (Integer): Associated color pair [optional]
12
+ # * *begin_with* (String): String to prepend to the text [default: '']
13
+ # * *end_with* (String): String to append to the text [default: '']
14
+ # * *fixed_size* (Integer): Number of characters this cell will take, or nil if no limit. [default: nil]
15
+ # * *justify* (Symbol): Text justification (only used when fixed_size is not nil). Values can be: [default: :left]
16
+ # * *left*: Left justified
17
+ # * *right*: Right justified
18
+ # * *pad* (String): Text to be used to pad the cell content (only used when fixed_size is not nil) [default: ' ']
19
+ # * *separator* (String): Separator used between cells [default: ' ']
20
+ def initialize(cells, separator: ' ')
21
+ @cells = cells
22
+ @separator = separator
23
+ end
24
+
25
+ # Change the cells order
26
+ #
27
+ # Parameters::
28
+ # * *cells* (Array<Symbol>): The ordered list of cells to filter
29
+ # * *unknown_cells* (String or Hash<Symbol,Object>): Content to put in unknown cells (as a String or properties like in #initialize), or nil to not add them. [default: nil]
30
+ def cells_order(cells, unknown_cells: nil)
31
+ new_cells = {}
32
+ cells.each do |cell_id|
33
+ if @cells.key?(cell_id)
34
+ new_cells[cell_id] = @cells[cell_id]
35
+ elsif !unknown_cells.nil?
36
+ new_cells[cell_id] = unknown_cells.is_a?(String) ? { text: unknown_cells } : unknown_cells
37
+ end
38
+ end
39
+ @cells = new_cells
40
+ end
41
+
42
+ # Change properties of a set of cells
43
+ #
44
+ # Parameters::
45
+ # * *cells* (Hash<Symbol, Hash<Symbol,Object> >): The cells properties to change, per cell id. Possible properties are the ones given in the #initialize method.
46
+ def change_cells(cells)
47
+ cells.each do |cell_id, cell_info|
48
+ raise "Unknown cell #{cell_id}" unless @cells.key?(cell_id)
49
+ @cells[cell_id].merge!(cell_info)
50
+ @cells[cell_id].delete(:cache_rendered_text)
51
+ end
52
+ end
53
+
54
+ # Get the size of the total string of such row.
55
+ #
56
+ # Parameters::
57
+ # * *cells* (Array<Symbol>): The list of cells to consider for the size [default: @cells.keys]
58
+ # Result::
59
+ # * Integer: Row size
60
+ def size(cells: @cells.keys)
61
+ result = @separator.size * (cells.size - 1)
62
+ cells.each do |cell_id|
63
+ result += cell_text(cell_id).size
64
+ end
65
+ result
66
+ end
67
+
68
+ # Print this row into a window
69
+ #
70
+ # Parameters::
71
+ # * *window* (Window): Curses window to print on
72
+ # * *from* (Integer): From index to be displayed [default: 0]
73
+ # * *to* (Integer): To index to be displayed [default: total size]
74
+ # * *default_color_pair* (Integer): Default color pair to use if no color information is provided [default: COLORS_LINE]
75
+ # * *force_color_pair* (Integer): Force color pair to use, or nil to not force [default: nil]
76
+ # * *pad* (String or nil): Pad the line to the row extent with the given string, or nil for no padding. [default: nil]
77
+ # * *add_nl* (Boolean): If true, then add a new line at the end [default: true]
78
+ # * *single_line* (Boolean): If true, then make sure the print does not exceed the line [default: false]
79
+ def print_on(window, from: 0, to: nil, default_color_pair: COLORS_LINE, force_color_pair: nil, pad: nil, add_nl: true, single_line: false)
80
+ text_size = size
81
+ from = text_size if from > text_size
82
+ to = text_size - 1 if to.nil?
83
+ to = window.maxx - window.curx + from - 2 if single_line && window.curx + to - from >= window.maxx - 1
84
+ current_idx = 0
85
+ @cells.each.with_index do |(cell_id, cell_info), cell_idx|
86
+ text = cell_text(cell_id)
87
+ full_substring_size = text.size + @separator.size
88
+ if from < current_idx + full_substring_size
89
+ # We have something to display from this substring
90
+ window.color_set(
91
+ if force_color_pair.nil?
92
+ cell_info[:color_pair] ? cell_info[:color_pair] : default_color_pair
93
+ else
94
+ force_color_pair
95
+ end
96
+ )
97
+ window << "#{text}#{cell_idx == @cells.size - 1 ? '' : @separator}"[(from < current_idx ? 0 : from - current_idx)..to - current_idx]
98
+ end
99
+ current_idx += full_substring_size
100
+ break if current_idx > to
101
+ end
102
+ window.color_set(force_color_pair.nil? ? default_color_pair : force_color_pair)
103
+ if pad && window.curx < window.maxx
104
+ nbr_chars = window.maxx - window.curx - 1
105
+ window << (pad * nbr_chars)[0..nbr_chars - 1]
106
+ end
107
+ window << "\n" if add_nl
108
+ end
109
+
110
+ private
111
+
112
+ # Get a cell's text.
113
+ # Cache it to not compute it several times.
114
+ #
115
+ # Parameters::
116
+ # * *cell_id* (Symbol): Cell id to get text for
117
+ # Result::
118
+ # * String: The cell's text
119
+ def cell_text(cell_id, cell_decorator: nil)
120
+ unless @cells[cell_id].key?(:cache_rendered_text)
121
+ begin_str = "#{@cells[cell_id][:begin_with] || ''}#{@cells[cell_id][:text]}"
122
+ end_str = @cells[cell_id][:end_with] || ''
123
+ @cells[cell_id][:cache_rendered_text] =
124
+ if @cells[cell_id][:fixed_size]
125
+ text = "#{begin_str[0..@cells[cell_id][:fixed_size] - end_str.size - 1]}#{end_str}"
126
+ remaining_size = @cells[cell_id][:fixed_size] - text.size
127
+ if remaining_size > 0
128
+ padding = ((@cells[cell_id][:pad] || ' ') * remaining_size)[0..remaining_size - 1]
129
+ justify = @cells[cell_id][:justify] || :left
130
+ case justify
131
+ when :left
132
+ "#{text}#{padding}"
133
+ when :right
134
+ "#{padding}#{text}"
135
+ else
136
+ raise "Unknown justify decorator: #{justify}"
137
+ end
138
+ else
139
+ text[0..@cells[cell_id][:fixed_size] - 1]
140
+ end
141
+ else
142
+ "#{begin_str}#{end_str}"
143
+ end
144
+ end
145
+ @cells[cell_id][:cache_rendered_text]
146
+ end
147
+
148
+ end
149
+
150
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: curses_menu
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Muriel Salvan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-07-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: curses
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ description:
28
+ email:
29
+ - muriel@x-aeon.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - examples/actions.rb
35
+ - examples/automatic_key_presses.rb
36
+ - examples/formatting.rb
37
+ - examples/hello.rb
38
+ - examples/refresh.rb
39
+ - examples/scrolling.rb
40
+ - examples/several_items.rb
41
+ - examples/sub_menus.rb
42
+ - lib/curses_menu.rb
43
+ - lib/curses_menu/curses_row.rb
44
+ homepage: http://x-aeon.com
45
+ licenses:
46
+ - BSD-3-Clause
47
+ metadata:
48
+ homepage_uri: http://x-aeon.com
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubyforge_project:
65
+ rubygems_version: 2.7.6
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: Simple menu offering choices with navigation keys using curses
69
+ test_files: []