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

COLORS, COLOR_MAP, DEFAULT_KEYS, ERASE_LINE, INPUT, 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, #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, #printables, read_key, read_line, #real_size, row, size, 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)


286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/stack.rb', line 286

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, 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 ||= "1234567890qwertyuiopasdfghjklzxcvbnm-=[];'\,./"
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

#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)


267
268
269
270
271
# File 'lib/stack.rb', line 267

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

#downObject

Step down by one row.

stack.down


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

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


92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/stack.rb', line 92

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


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

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
49
50
51
52
53
# File 'lib/stack.rb', line 48

def hit row, col
  return unless row and col
  rel = row - 1 - top - headspace
  return unless rel.between?(0, height - 1) and col.between?(left, right)
  page.to_a[rel]
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


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

def page_down; @start += height; return end

#page_upObject

Jump up by one viewport of rows.

stack.page_up


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

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.



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
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
# File 'lib/stack.rb', line 213

def pick type = :row, row=0 #, key=nil
  type = type.to_s
  multiple = type[-1] == ?s
  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)
        if key.wheel? and key.press?
          send( key.wheel_up? ? @keymap[:up] : @keymap[:down] )
          next
        elsif key.press? and key.left? and type['row'] and
          value = hit( key.y, key.x )
          relative = value if type['relative_']
        end
      elsif 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
      end unless type['none']
    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


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

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]


77
78
79
80
81
82
83
84
# File 'lib/stack.rb', line 77

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


148
149
150
151
152
153
# File 'lib/stack.rb', line 148

def send key#, map=nil
  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).



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/stack.rb', line 117

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


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

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

#to_topObject

Scroll to the top of the dataset.

stack.to_top


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

def to_top; @start = 0; return end

#upObject

Step up by one row.

stack.up


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

def up; @start -= 1; return end