Class: Typr::Stack

Inherits:
Space
  • Object
show all
Defined in:
lib/stack.rb

Overview

A vertically scrollable list of items displayed one per row or column.

Provides paging, keyboard navigation, key-hint overlays, and alternating row colors. Subclasses Grid, Text, and Browser add domain-specific rendering on top of this foundation.

Key bindings (overridable via keymap):

KEY_TAB    -> cycle       (advance hint numbering to the next chunk)
KEY_RETURN -> confirm     (returns currently selected items, or nil)
KEY_ESCAPE -> exit        (abort selection and return nil)
KEY_UP / KEY_DOWN            (step by one row in the given direction)
KEY_PAGEUP / KEY_PAGEDOWN  (jump by one viewport height)
KEY_HOME / KEY_END           (jump to first or last pageable row)

Direct Known Subclasses

Grid, Text

Constant Summary

Constants included from Typr

ALT_SCREEN_OFF, ALT_SCREEN_ON, COLORS, COLOR_MAP, DEFAULT_KEYS, ERASE_LINE, INPUT, KEY_ALT_BACKSPACE, KEY_ALT_LEFT, KEY_ALT_RIGHT, KEY_BACK_TAB, KEY_ESCAPE, KEY_PAGEDOWN, KEY_PAGEUP, KEY_RETURN, KEY_TAB, MIMETYPES, MODES, MOUSE_OFF, MOUSE_ON, ORIG_COLORS, TERMINFO

Instance Attribute Summary collapse

Attributes inherited from Space

#borders, #bottom, #colors, #interval, #left, #margin, #right, #top

Instance Method Summary collapse

Methods inherited from Space

#border=, #draw_border, #extract_key, #stop, #symbolize, #width

Methods included from Typr

#background, clear, #clip, #coerce_type, #color, #color_code, column, decode_mouse, #draw, exit, #fade, #foreground, #get_background, #get_foreground, height, init, #mode, #mode_code, #move, #move_code, on_resize, position, #prepare, read_key, read_line, #real_size, row, #sanitize, size, slice_width, text_width, width, word_next, word_prev

Constructor Details

#initialize(args = {}) ⇒ Stack

Constructs a Stack widget.

All positional and visual parameters from Typr::Space#initialize are available. Additionally:

Option          | Default | Description
----------------|---------|-------------------------------------------
+keysyms+       | {}      | Custom key-to-method mappings
+hints+         | +"1234..."+ | Characters used in the hint overlay
+alternate+     | true    | Striped row backgrounds
+header+        | nil     | Label shown in a header bar
+separator+     | +" "+   | String drawn between columns (used by Grid)


319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/stack.rb', line 319

def initialize args={}
  @keymap, @separator, @selected = {}, " ", {}
  @start, @hints_start = 0, 0
  @alternate = true
  super
  @colors = { hints: [255, :black ], header: [232,:grey60 ],
    selected: :grey25, alternate: :grey10, rows: {} }.merge @colors
  @selected = { fields:[], columns:[], rows:[] }.merge @selected
  @keymap = { cycle: KEY_TAB, cycle_back: KEY_BACK_TAB, confirm: KEY_RETURN, exit: KEY_ESCAPE,
    page_down: KEY_PAGEDOWN, page_up: KEY_PAGEUP, up: KEY_UP,
    down: KEY_DOWN, to_top: KEY_HOME, to_bottom: KEY_END }.merge @keymap
  @hints ||= "1234567890"
end

Instance Attribute Details

#headerObject

Widget configuration. left, top, right, bottom set the viewport; see Typr::Space. alternate toggles striped rows (+:grey10+ background alternating with the default bg).



23
24
25
# File 'lib/stack.rb', line 23

def header
  @header
end

#hintsObject

Returns the value of attribute hints.



24
25
26
# File 'lib/stack.rb', line 24

def hints
  @hints
end

#hints_startObject

Returns the value of attribute hints_start.



24
25
26
# File 'lib/stack.rb', line 24

def hints_start
  @hints_start
end

#keymapObject

Widget configuration. left, top, right, bottom set the viewport; see Typr::Space. alternate toggles striped rows (+:grey10+ background alternating with the default bg).



23
24
25
# File 'lib/stack.rb', line 23

def keymap
  @keymap
end

#modifierObject

Returns the value of attribute modifier.



24
25
26
# File 'lib/stack.rb', line 24

def modifier
  @modifier
end

#selectedObject

Widget configuration. left, top, right, bottom set the viewport; see Typr::Space. alternate toggles striped rows (+:grey10+ background alternating with the default bg).



23
24
25
# File 'lib/stack.rb', line 23

def selected
  @selected
end

#separatorObject

Returns the value of attribute separator.



24
25
26
# File 'lib/stack.rb', line 24

def separator
  @separator
end

#startObject

Widget configuration. left, top, right, bottom set the viewport; see Typr::Space. alternate toggles striped rows (+:grey10+ background alternating with the default bg).



23
24
25
# File 'lib/stack.rb', line 23

def start
  @start
end

Instance Method Details

#cycleObject

Advance the numeric hints by one full set of hint characters so that newly-fitted items beyond the first viewport also get labeled numbers.

stack.cycle   # hints now start at index 10 (or wrap back to 0)


293
294
295
296
297
# File 'lib/stack.rb', line 293

def cycle
  @hints_start += @hints.size
  @hints_start = 0 if @hints_start > [height-1, rows-1+headspace].min
  return
end

#cycle_backObject



299
300
301
302
303
304
# File 'lib/stack.rb', line 299

def cycle_back
  max = [height-1, rows-1+headspace].min
  @hints_start -= @hints.size
  @hints_start = (max / @hints.size) * @hints.size if @hints_start < 0
  return
end

#data_at(x, y) ⇒ Object

The data row id at terminal (x, y), or nil when the click is outside the viewport (see Typr::Space#data_at).

stack.data_at 5, 3   # => 0


56
57
58
59
60
61
# File 'lib/stack.rb', line 56

def data_at x, y
  return unless x and y
  rel = y - 1 - top - headspace
  return unless rel.between?(0, height - 1) and x.between?(left, right)
  page.to_a[rel]
end

#downObject

Step down by one row.

stack.down


191
# File 'lib/stack.rb', line 191

def down; @start += 1; return end

#draw_hints(type = nil, row_id = 0) ⇒ Object

Renders the numeric key-hint overlay for items in the current page.

stack.draw_hints            # vertical hints along left margin
stack.draw_hints :column, 2 # horizontal hints across columns of row 2


100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/stack.rb', line 100

def draw_hints type=nil, row_id=0
  color @colors[ :hints ]
  if type.to_s[ /column/ ]
    positions( row_id )[ @hints_start..-1 ].each_with_index{ |pos, idx|
      move( left + pos, top + row_id )
      draw @hints[idx] }
  else
    bottom = top + headspace + [height, rows].min - 1
    (top + headspace..bottom).each{ |pos|
      move( left, pos ); draw ' ' }
    @hints.chars.each_with_index{ |char, idx|
      pos = idx + @hints_start + headspace
      break if top + pos > bottom
      move( left, top + pos )
      draw char }
  end
end

#headspaceObject

The number of rows reserved for a header bar within the widget's footprint.

stack.headspace   #=> 1  (if header is set)
stack.headspace   #=> 0  (if no header)


39
# File 'lib/stack.rb', line 39

def headspace; @header ? 1 : 0 end

#heightObject

The usable height after subtracting any header space.

stack.height   #=> 14


68
# File 'lib/stack.rb', line 68

def height; super - headspace end

#hit(row, col) ⇒ Object

Map a mouse click at 1-based terminal (row, col) to a data row id on the current page, or nil when the click lands outside the widget (border, header, margins). Row ids match what hint presses return from pick.

stack.hit 3, 5   # => 0  (row 3, col 5 hits data row 0)


48
# File 'lib/stack.rb', line 48

def hit row, col; data_at col, row end

#pageObject

Returns a Range of internal data indices visible on the current page.

stack.page   #=> 0..14


31
# File 'lib/stack.rb', line 31

def page; @start..(@start + height - 1) end

#page_downObject

Jump down by one viewport of rows.

stack.page_down


203
# File 'lib/stack.rb', line 203

def page_down; @start += height; return end

#page_upObject

Jump up by one viewport of rows.

stack.page_up


209
# File 'lib/stack.rb', line 209

def page_up; @start -= height; return end

#pick(type = :row, row = 0) ⇒ Object

Blocks the calling thread and lets the user interactively pick items from the current page using hint characters or arrow keys. After selection, the user must press Return to confirm (or Escape to abort).

The type argument controls what kind of picker is shown:

Symbol/String type           | Purpose
-----------------------------|-------------------------------------------
+:row, +"file"+              | Pick a single row id
+"rows"+                     | Multi-select rows
+:column, +"field"+          | Nested: pick a row then a column
+:fields+                    | Multi-select fields
+:none+                      | Navigation-only mode
anything with +relative_|    | Returns the internal integer index

stack.pick                # single row picker
stack.pick :column, 3     # pick a column in row 3
stack.pick "rows"         # multi-select rows

Mouse: left-click a row to pick it; wheel up/down scrolls the page.



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/stack.rb', line 233

def pick type = :row, row=0 #, key=nil
  type = type.to_s
  multiple = type[-1] == ?s
  @modifier = nil
  loop do
    if type['field']
      row = pick( :row ) or return
      column = pick( :column, page.to_a.index( row  + headspace)) or return
      value = [ row , column ]
    else
      show
      draw_hints type, row unless type['none']
      limit = type['row'] ? height : positions(row).count
      key = Typr.read_key
      if key.is_a?(Typr::Mouse)
        value = send( key )
        next if value == :scroll
        return if value == :outside
        value = nil unless type['row'] and key.left?
        relative = value if value and type['relative_']
      elsif !type['none'] and key.is_a?(String) and value =
        @hints[0..limit-@hints_start-1].index(key)
        value += @hints_start
        relative = value + @start if type['relative_']
        if type['row'] then value = page.to_a[ value ]
        elsif type['column'] and @sequence; value = @sequence[value] end
      elsif !type['none'] and key.is_a?(String) and digit = key[/\A\e(\d)\z/, 1] and
        value = @hints[0..limit-@hints_start-1].index(digit)
        @modifier = :alt
        value += @hints_start
        relative = value + @start if type['relative_']
        if type['row'] then value = page.to_a[ value ]
        elsif type['column'] and @sequence; value = @sequence[value] end
      end
    end

    if value
      type = type[9..-1] if type['relative_']
      type += ?s unless multiple
      type = type.to_sym
      if @selected[type].include? value
        @selected[type].delete value
      else @selected[type] << value end if multiple
      return relative || value unless multiple
    else
      case key
        when @keymap[:exit]; return
        when @keymap[:confirm]; return @selected[type]
        else send key #if type['id']
      end
    end
  end
end

Placeholder renderer used when no subclass overrides print for an item id.

stack.print 42   # renders a blank row


75
# File 'lib/stack.rb', line 75

def print id; draw " " * (width-@margin.size) end

#reset(type = :all) ⇒ Object

Resets widget state by one or more categories.

stack.reset              # resets position and selection
stack.reset :position    # only scrolls back to top
stack.reset :selection   # clears all selected rows/columns/fields
stack.reset [:display, :format]


85
86
87
88
89
90
91
92
# File 'lib/stack.rb', line 85

def reset type=:all
  case type
    when Array; type.each{ |type| reset type }
    when :position; @start = @hints_start = 0
    when :selection; @selected = @selected.keys.map{ |type| [ type, [] ] }.to_h
    when :all; reset [ :position, :selection ]
  end
end

#send(key) ⇒ Object

Dispatches a key from the terminal to its semantic action and clamps the current page offset.

stack.send Typr::KEY_UP      # scrolls up one row
stack.send Typr::KEY_RETURN   # confirms selection


156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/stack.rb', line 156

def send key#, map=nil
  if key.is_a?(Typr::Mouse)
    if key.wheel? and key.press?
      send( key.wheel_up? ? @keymap[:up] : @keymap[:down] )
      return :scroll
    end
    if key.press? and ( key.left? or key.right? )
      return :outside unless key.x and key.y and
        key.x.between?( left, right ) and key.y.between?( top + 1, bottom + 1 )
      return data_at( key.x, key.y )
    end
    return
  end
  response = eval @keymap.invert[key].to_s if @keymap.values.include? key
  @start = rows - height if @start > rows - height
  @start = 0 if @start < 0
  return response
end

#showObject

Renders the full widget: header bar (if present), all visible items with background coloring and alternating stripes, a selected-item highlight, and bottom borders.

This method is called by your application's main loop each frame; it does not handle events itself (see #send).



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/stack.rb', line 125

def show
  if @header
    color @colors[ :header ]
    move left,top
    draw @margin
    print :header
  end
  list = page.to_a
  list[ height-1 ] = nil if list.length < height
  for id, relative_id in list.each_with_index
    move left,top + relative_id + headspace
    foreground ( @colors[:rows][id] || @colors[:default][0] )
    select = @selected[ :rows ].include?( id )
    dark=!dark if @alternate
    background ( select ? @colors[:selected] :
      (@alternate and dark) ? @colors[:alternate] : @colors[:default][1] )
    draw @margin
    print id
    background if select
  end
  background
  super
end

#to_bottomObject

Scroll to the bottom pageable position (last row minus viewport height).

stack.to_bottom


185
# File 'lib/stack.rb', line 185

def to_bottom; @start = rows - height; return end

#to_topObject

Scroll to the top of the dataset.

stack.to_top


179
# File 'lib/stack.rb', line 179

def to_top; @start = 0; return end

#upObject

Step up by one row.

stack.up


197
# File 'lib/stack.rb', line 197

def up; @start -= 1; return end