curses_menu 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []