luck 0.0.0 → 0.1.0

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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.1.0
data/lib/luck/alert.rb ADDED
@@ -0,0 +1,36 @@
1
+ module Luck
2
+ class Alert < Pane
3
+ attr_accessor :width, :height, :handler
4
+
5
+ def initialize display, width, height, title, controls={}, &blck
6
+ super display, nil, nil, nil, nil, title, controls, &blck
7
+
8
+ @width, @height = width, height
9
+ end
10
+
11
+ def x1
12
+ (@display.width / 2).to_i - (@width / 2).to_i
13
+ end
14
+ def y1
15
+ (@display.height / 2).to_i - (@height / 2).to_i
16
+ end
17
+
18
+ def x2
19
+ x1 + @width
20
+ end
21
+ def y2
22
+ y1 + @height
23
+ end
24
+
25
+ def on_dismiss &blck
26
+ @handler = blck
27
+ end
28
+
29
+ def handle_char char
30
+ if char == "\n"
31
+ @handler.call @text if @handler
32
+ end
33
+ #redraw
34
+ end
35
+ end
36
+ end
data/lib/luck/ansi.rb ADDED
@@ -0,0 +1,222 @@
1
+ module Luck
2
+ class ANSIDriver
3
+ attr_reader :width, :height
4
+
5
+ def initialize
6
+ prepare_modes
7
+
8
+ @height, @width = terminal_size
9
+ end
10
+
11
+ def flush
12
+ $stdout.flush
13
+ end
14
+
15
+ def put row, col, text
16
+ text.each_line do |line|
17
+ print "\e[#{row.to_i};#{col.to_i}H#{line}"
18
+ row += 1
19
+ end
20
+ end
21
+
22
+ def color codes
23
+ "\e[#{codes}m"
24
+ end
25
+
26
+ def cursor=(show)
27
+ print "\e[?25" + (show ? 'h' : 'l')
28
+ end
29
+
30
+ def linedrawing=(toggle)
31
+ print(toggle ? "\x0E\e)0" : "\x0F")
32
+ end
33
+
34
+ def resized?
35
+ size = terminal_size
36
+
37
+ return false if [@height, @width] == size
38
+
39
+ @height, @width = size
40
+ true
41
+ end
42
+
43
+ def clear; print "\e[H"; end
44
+ def cursor_to_home; print "\e[J"; end
45
+
46
+ def save_cursor; print "\e[s"; end
47
+ def restore_cursor; print "\e[u"; end
48
+ def set_cursor row, col; put row, col, "\e[s"; end
49
+
50
+ def set_title title; print "\e]2;#{title}\007"; end
51
+
52
+ #~ BUTTONS = {
53
+ #~ 0 => :left,
54
+ #~ 1 => :middle,
55
+ #~ 2 => :right,
56
+ #~ 3 => :release,
57
+ #~ 64 => :scrollup,
58
+ #~ 65 => :scrolldown
59
+ #~ }
60
+ #~
61
+ #~ # alt-anything => \e*KEY* (same as Esc, key)
62
+ #~ # alt-[ would become \e[ which is an ANSI escape
63
+ #~ #
64
+ #~ # ctrl-stuff becomes weird stuff, i.e. ctrl-space = \x00, ctrl-a = \x01, ctrl-b = \x02
65
+ #~ #
66
+ #~ # super is not sent?
67
+ #~ def handle_stdin
68
+ #~ @escapes ||= 0
69
+ #~ @ebuff ||= ''
70
+ #~
71
+ #~ $stdin.read_nonblock(1024).each_char do |chr|
72
+ #~
73
+ #~ if @escapes == 0
74
+ #~ if chr == "\e"
75
+ #~ @escapes = 1
76
+ #~ elsif chr == "\t"
77
+ #~ cycle_controls
78
+ #~ elsif chr == "\177"
79
+ #~ route_key :backspace
80
+ #~ else
81
+ #~ route_key chr
82
+ #~ end
83
+ #~
84
+ #~ elsif @escapes == 1 && chr == '['
85
+ #~ @escapes = 2
86
+ #~ elsif @escapes == 1 && chr == 'O'
87
+ #~ @escapes = 5
88
+ #~
89
+ #~ elsif @escapes == 2
90
+ #~ if chr == 'A'
91
+ #~ route_key :up
92
+ #~ elsif chr == 'B'
93
+ #~ route_key :down
94
+ #~ elsif chr == 'C'
95
+ #~ route_key :right
96
+ #~ elsif chr == 'D'
97
+ #~ route_key :left
98
+ #~ elsif chr == 'E'
99
+ #~ route_key :center
100
+ #~ elsif chr == 'Z'
101
+ #~ cycle_controls_back
102
+ #~ else
103
+ #~ @ebuff = chr
104
+ #~ @escapes = 3
105
+ #~ end
106
+ #~ @escapes = 0 if @escapes == 2
107
+ #~
108
+ #~ elsif @escapes == 3
109
+ #~ if chr == '~' && @ebuff.to_i.to_s == @ebuff
110
+ #~ route_key case @ebuff.to_i
111
+ #~ when 2; :insert
112
+ #~ when 3; :delete
113
+ #~ when 5; :pageup
114
+ #~ when 6; :pagedown
115
+ #~ when 15; :f5
116
+ #~ when 17; :f6
117
+ #~ when 18; :f7
118
+ #~ when 19; :f8
119
+ #~ when 20; :f9
120
+ #~ when 24; :f12
121
+ #~ else; raise @ebuff.inspect
122
+ #~ end
123
+ #~ elsif @ebuff[0,1] == 'M' && @ebuff.size == 3
124
+ #~ @ebuff += chr
125
+ #~ info, x, y = @ebuff.unpack('xCCC').map{|i| i - 32}
126
+ #~ modifiers = []
127
+ #~ modifiers << :shift if info & 4 == 4
128
+ #~ modifiers << :meta if info & 8 == 8
129
+ #~ modifiers << :control if info & 16 == 16
130
+ #~ pane = pane_at(x, y)
131
+ #~
132
+ #~ unless modal && modal != pane
133
+ #~ pane.handle_click BUTTONS[info & 71], modifiers, x, y if pane
134
+ #~ end
135
+ #~ elsif @ebuff.size > 10
136
+ #~ raise "long ebuff #{@ebuff.inspect} - #{chr.inspect}"
137
+ #~ else
138
+ #~ @ebuff += chr
139
+ #~ @escapes = 4
140
+ #~ end
141
+ #~ @escapes = 0 if @escapes == 3
142
+ #~ @escapes = 3 if @escapes == 4
143
+ #~ @ebuff = '' if @escapes == 0
144
+ #~
145
+ #~ elsif @escapes == 5
146
+ #~ if chr == 'H'
147
+ #~ route_key :home
148
+ #~ elsif chr == 'F'
149
+ #~ route_key :end
150
+ #~ elsif chr == 'Q'
151
+ #~ route_key :f2
152
+ #~ elsif chr == 'R'
153
+ #~ route_key :f3
154
+ #~ elsif chr == 'S'
155
+ #~ route_key :f4
156
+ #~ else
157
+ #~ raise "escape 5 #{chr.inspect}"
158
+ #~ end
159
+ #~ @escapes = 0
160
+ #~
161
+ #~ else
162
+ #~ @escapes = 0
163
+ #~ end
164
+ #~ end
165
+ #~
166
+ #~ $stdout.flush
167
+ #~
168
+ #~ rescue Errno::EAGAIN
169
+ #~ rescue EOFError
170
+ #~ end
171
+
172
+
173
+ ######################################
174
+ # this is all copied from cashreg :P #
175
+ ######################################
176
+
177
+ # yay grep
178
+ TIOCGWINSZ = 0x5413
179
+ TCGETS = 0x5401
180
+ TCSETS = 0x5402
181
+ ECHO = 8 # 0000010
182
+ ICANON = 2 # 0000002
183
+
184
+ # thanks google for all of this
185
+ def terminal_size
186
+ rows, cols = 25, 80
187
+ buf = [0, 0, 0, 0].pack("SSSS")
188
+ if $stdout.ioctl(TIOCGWINSZ, buf) >= 0 then
189
+ rows, cols, row_pixels, col_pixels = buf.unpack("SSSS")
190
+ end
191
+ return [rows, cols]
192
+ end
193
+
194
+ # had to convert these from C... fun
195
+ def prepare_modes
196
+ buf = [0, 0, 0, 0, 0, 0, ''].pack("IIIICCA*")
197
+ $stdout.ioctl(TCGETS, buf)
198
+ @old_modes = buf.unpack("IIIICCA*")
199
+ new_modes = @old_modes.clone
200
+ new_modes[3] &= ~ECHO # echo off
201
+ new_modes[3] &= ~ICANON # one char @ a time
202
+ $stdout.ioctl(TCSETS, new_modes.pack("IIIICCA*"))
203
+ print "\e[2J" # clear screen
204
+ print "\e[H" # go home
205
+ print "\e[?47h" # kick xterm into the alt screen
206
+ print "\e[?1000h" # kindly ask for mouse positions to make up for it
207
+ self.cursor = false
208
+ flush
209
+ end
210
+
211
+ def undo_modes # restore previous terminal mode
212
+ $stdout.ioctl(TCSETS, @old_modes.pack("IIIICCA*"))
213
+ print "\e[2J" # clear screen
214
+ print "\e[H" # go home
215
+ print "\e[?47l" # kick xterm back into the normal screen
216
+ print "\e[?1000l" # turn off mouse reporting
217
+ self.linedrawing = false
218
+ self.cursor = true # show the mouse
219
+ flush
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,35 @@
1
+ module Luck
2
+ class Button < Label
3
+ attr_accessor :handler
4
+
5
+ def on_submit &blck
6
+ @handler = blck
7
+ end
8
+
9
+ def handle_char char
10
+ if char == "\n"
11
+ handler.call self, @text if handler
12
+ end
13
+ #redraw
14
+ end
15
+
16
+ def handle_click button, modifiers, x, y
17
+ handler.call self, @text if button == :left && handler
18
+ end
19
+
20
+ def text
21
+ "[ #{@text} ]"
22
+ end
23
+
24
+ def handler
25
+ @handler || @pane.handler
26
+ end
27
+
28
+ def redraw
29
+ @display.driver.cursor = false
30
+ print @display.color(@display.active_control == self ? '1;34' : '0;36')
31
+ super
32
+ print @display.color('0')
33
+ end
34
+ end
35
+ end
data/lib/luck/control.rb CHANGED
@@ -11,6 +11,10 @@ class Control
11
11
  instance_eval &blck if blck
12
12
  end
13
13
 
14
+ def focus!
15
+ @display.active_control = self
16
+ end
17
+
14
18
  def x1
15
19
  @pane.x1 + ((@x1 < 0) ? (@pane.width + @x1) : @x1)
16
20
  end
data/lib/luck/display.rb CHANGED
@@ -1,160 +1,244 @@
1
1
  module Luck
2
2
  class Display
3
- attr_accessor :width, :height, :client, :panes, :buffer, :dirty, :active_control
3
+ attr_accessor :driver, :client, :panes, :dirty, :active_pane, :active_control, :modal
4
4
 
5
5
  def initialize client
6
6
  @client = client
7
7
  @panes = {}
8
8
  @dirty = true
9
- prepare_modes
10
9
 
11
- size = terminal_size
12
- @width = size[1]
13
- @height = size[0]
10
+ @driver = ANSIDriver.new
14
11
  end
15
12
 
16
- def pane name, *args, &blck
17
- @panes[name] = Pane.new(self, *args, &blck)
18
- end
13
+ def width; @driver.width; end
14
+ def height; @driver.height; end
15
+
16
+ def place x,y,t; @driver.put x,y,t; end
17
+ def color c; @driver.color c; end
19
18
 
20
- def place row, col, text
21
- print "\e[#{row.to_i};#{col.to_i}H#{text}"
19
+ def close
20
+ @driver.undo_modes
22
21
  end
23
- def color codes
24
- "\e[#{codes}m"
22
+
23
+ def pane name, *args, &blck
24
+ @panes[name] = Pane.new(self, *args, &blck)
25
25
  end
26
26
 
27
- def cursor=(show)
28
- print "\e[?25h" if show
29
- print "\e[?25l" unless show
30
- $stdout.flush
27
+ def alert name, *args, &blck
28
+ @panes[name] = Alert.new(self, *args, &blck)
31
29
  end
32
30
 
33
31
  def dirty! pane=nil
34
- if pane && @dirty.is_a?(Array) && !(@dirty.include?(pane))
35
- @dirty << pane
36
- elsif pane && !@dirty
37
- @dirty = [pane]
38
- elsif !pane
32
+ if pane
33
+ @panes[pane].dirty!
34
+ else
39
35
  @dirty = true
40
36
  end
41
37
  end
42
38
 
39
+ def [] pane, control=nil
40
+ if control
41
+ @panes[pane].controls[control]
42
+ else
43
+ @panes[pane]
44
+ end
45
+ end
46
+
47
+ def focus *path
48
+ self.active_control = self[*path]
49
+ end
50
+
43
51
  def handle
44
52
  handle_stdin
45
53
 
46
- size = terminal_size
47
- if @width != size[1] || @height != size[0]
48
- @width = size[1]
49
- @height = size[0]
54
+ if @driver.resized?
50
55
  dirty!
51
56
  end
52
57
 
53
- return unless @dirty
54
- redraw @dirty
55
- @dirty = false
58
+ if @dirty
59
+ redraw
60
+ @dirty = false
61
+ else
62
+ panes = @panes.values.select {|pane| pane.dirty? && pane.visible? }
63
+ redraw panes if panes.any?
64
+ end
65
+
66
+ @panes.each_value {|pane| pane.dirty = false }
56
67
  end
57
68
 
58
- def redraw panes=true
59
- print "\e[H\e[J" if panes == true # clear all and go home
60
- self.cursor = active_control
61
-
62
- panes = @panes.keys if panes == true
63
-
64
- panes.each do |key|
65
- @panes[key].redraw
69
+ def redraw panes=nil
70
+ unless panes
71
+ @driver.clear
72
+ @driver.cursor_to_home
66
73
  end
67
- print "\e[u"
68
74
 
69
- $stdout.flush
75
+ panes ||= @panes.values
76
+ panes.each {|pane| pane.redraw if pane.visible? }
77
+ modal.redraw if modal
78
+ @panes.each_value {|pane| pane.draw_title if pane.visible? }
79
+
80
+ @driver.restore_cursor
81
+ @driver.flush
82
+ end
83
+
84
+ def active_control= control
85
+ @active_pane = control.pane
86
+ @active_control = control
87
+ end
88
+
89
+ def cycle_controls
90
+ index = @active_pane.controls.keys.index @active_pane.controls.key(@active_control)
91
+ begin
92
+ index += 1
93
+ index = 0 if index >= @active_pane.controls.size
94
+ end until @active_pane.controls[@active_pane.controls.keys[index]].respond_to? :handle_char
95
+ old = @active_control
96
+ @active_control = @active_pane.controls[@active_pane.controls.keys[index]]
97
+ old.redraw
98
+ @active_control.redraw
70
99
  end
71
100
 
101
+ def cycle_controls_back
102
+ index = @active_pane.controls.keys.index @active_pane.controls.key(@active_control)
103
+ begin
104
+ index -= 1
105
+ index = @active_pane.controls.size - 1 if index < 0
106
+ end until @active_pane.controls[@active_pane.controls.keys[index]].respond_to? :handle_char
107
+ old = @active_control
108
+ @active_control = @active_pane.controls[@active_pane.controls.keys[index]]
109
+ old.redraw
110
+ @active_control.redraw
111
+ end
112
+
113
+ BUTTONS = {
114
+ 0 => :left,
115
+ 1 => :middle,
116
+ 2 => :right,
117
+ 3 => :release,
118
+ 64 => :scrollup,
119
+ 65 => :scrolldown
120
+ }
121
+
122
+ # alt-anything => \e*KEY* (same as Esc, key)
123
+ # alt-[ would become \e[ which is an ANSI escape
124
+ #
125
+ # ctrl-stuff becomes weird stuff, i.e. ctrl-space = \x00, ctrl-a = \x01, ctrl-b = \x02
126
+ #
127
+ # super is not sent?
72
128
  def handle_stdin
129
+ @escapes ||= 0
130
+ @ebuff ||= ''
131
+
73
132
  $stdin.read_nonblock(1024).each_char do |chr|
74
- active_control.handle_char chr if active_control
75
- #~ if chr == "\n"
76
- #~ next if @buffer.empty?
77
- #~
78
- #~ if @buffer.to_i.to_s == @buffer && @results
79
- #~ index = @buffer.to_i - 1
80
- #~ next if index < 0 || index > @results.size
81
- #~
82
- #~ song = @results[index]
83
- #~ @client.queue << song
84
- #~
85
- #~ unless @client.now_playing
86
- #~ Thread.new do
87
- #~ @client.queue.play_radio
88
- #~ end
89
- #~ end
90
- #~
91
- #~ @buffer = ''
92
- #~ panes[:main].data[0] = @buffer.empty? ? (@search || '') : ''
93
- #~ self.cursor = false
94
- #~ else
95
- #~ @search = @buffer
96
- #~ @results = @client.search_songs(@search)['Return']
97
- #~ panes[:main].data = @results.map do |result|
98
- #~ "#{(@results.index(result)+1).to_s.rjust 2}) #{result['Name']} - #{result['ArtistName']} - #{result['AlbumName']}"
99
- #~ end
100
- #~ panes[:main].data.unshift @search
101
- #~ panes[:main].title = "Search Results for #{@search}"
102
- #~
103
- #~ @buffer = ''
104
- #~ self.cursor = false
105
- #~ end
106
- #~ else
107
- #~ @buffer << chr
108
- #~ @buffer.gsub!(/.\177/, '')
109
- #~ @buffer.gsub!("\177", '')
110
- #~ panes[:main].data[0] = @buffer.empty? ? (@search || '') : ''
111
- #~ self.cursor = !(@buffer.empty?)
112
- #~ end
133
+
134
+ if @escapes == 0
135
+ if chr == "\e"
136
+ @escapes = 1
137
+ elsif chr == "\t"
138
+ cycle_controls
139
+ elsif chr == "\177"
140
+ route_key :backspace
141
+ else
142
+ route_key chr
143
+ end
144
+
145
+ elsif @escapes == 1 && chr == '['
146
+ @escapes = 2
147
+ elsif @escapes == 1 && chr == 'O'
148
+ @escapes = 5
149
+
150
+ elsif @escapes == 2
151
+ if chr == 'A'
152
+ route_key :up
153
+ elsif chr == 'B'
154
+ route_key :down
155
+ elsif chr == 'C'
156
+ route_key :right
157
+ elsif chr == 'D'
158
+ route_key :left
159
+ elsif chr == 'E'
160
+ route_key :center
161
+ elsif chr == 'Z'
162
+ cycle_controls_back
163
+ else
164
+ @ebuff = chr
165
+ @escapes = 3
166
+ end
167
+ @escapes = 0 if @escapes == 2
168
+
169
+ elsif @escapes == 3
170
+ if chr == '~' && @ebuff.to_i.to_s == @ebuff
171
+ route_key case @ebuff.to_i
172
+ when 2; :insert
173
+ when 3; :delete
174
+ when 5; :pageup
175
+ when 6; :pagedown
176
+ when 15; :f5
177
+ when 17; :f6
178
+ when 18; :f7
179
+ when 19; :f8
180
+ when 20; :f9
181
+ when 24; :f12
182
+ else; raise @ebuff.inspect
183
+ end
184
+ elsif @ebuff[0,1] == 'M' && @ebuff.size == 3
185
+ @ebuff += chr
186
+ info, x, y = @ebuff.unpack('xCCC').map{|i| i - 32}
187
+ modifiers = []
188
+ modifiers << :shift if info & 4 == 4
189
+ modifiers << :meta if info & 8 == 8
190
+ modifiers << :control if info & 16 == 16
191
+ pane = pane_at(x, y)
192
+
193
+ unless modal && modal != pane
194
+ pane.handle_click BUTTONS[info & 71], modifiers, x, y if pane
195
+ end
196
+ elsif @ebuff.size > 10
197
+ raise "long ebuff #{@ebuff.inspect} - #{chr.inspect}"
198
+ else
199
+ @ebuff += chr
200
+ @escapes = 4
201
+ end
202
+ @escapes = 0 if @escapes == 3
203
+ @escapes = 3 if @escapes == 4
204
+ @ebuff = '' if @escapes == 0
205
+
206
+ elsif @escapes == 5
207
+ if chr == 'H'
208
+ route_key :home
209
+ elsif chr == 'F'
210
+ route_key :end
211
+ elsif chr == 'Q'
212
+ route_key :f2
213
+ elsif chr == 'R'
214
+ route_key :f3
215
+ elsif chr == 'S'
216
+ route_key :f4
217
+ else
218
+ raise "escape 5 #{chr.inspect}"
219
+ end
220
+ @escapes = 0
221
+
222
+ else
223
+ @escapes = 0
224
+ end
113
225
  end
114
226
 
115
- self.cursor = active_control
116
- $stdout.flush
227
+ @driver.flush
117
228
 
118
229
  rescue Errno::EAGAIN
119
230
  rescue EOFError
120
231
  end
121
232
 
122
- ######################################
123
- # this is all copied from cashreg :P #
124
- ######################################
125
-
126
- # yay grep
127
- TIOCGWINSZ = 0x5413
128
- TCGETS = 0x5401
129
- TCSETS = 0x5402
130
- ECHO = 8 # 0000010
131
- ICANON = 2 # 0000002
132
-
133
- # thanks google for all of this
134
- def terminal_size
135
- rows, cols = 25, 80
136
- buf = [0, 0, 0, 0].pack("SSSS")
137
- if $stdout.ioctl(TIOCGWINSZ, buf) >= 0 then
138
- rows, cols, row_pixels, col_pixels = buf.unpack("SSSS")
233
+ def pane_at x, y
234
+ @panes.values.reverse.each do |pane|
235
+ return pane if pane.visible? && (pane.x1..pane.x2).include?(x) && (pane.y1..pane.y2).include?(y)
139
236
  end
140
- return [rows, cols]
141
- end
142
-
143
- # had to convert these from C... fun
144
- def prepare_modes
145
- buf = [0, 0, 0, 0, 0, 0, ''].pack("IIIICCA*")
146
- $stdout.ioctl(TCGETS, buf)
147
- @old_modes = buf.unpack("IIIICCA*")
148
- new_modes = @old_modes.clone
149
- new_modes[3] &= ~ECHO # echo off
150
- new_modes[3] &= ~ICANON # one char @ a time
151
- $stdout.ioctl(TCSETS, new_modes.pack("IIIICCA*"))
152
- self.cursor = false
237
+ nil
153
238
  end
154
- def undo_modes # restore previous terminal mode
155
- $stdout.ioctl(TCSETS, @old_modes.pack("IIIICCA*"))
156
- print "\e[2J\e[H" # clear all and go home
157
- self.cursor = true # show the mouse
239
+
240
+ def route_key chr
241
+ @active_control.handle_char chr if @active_control
158
242
  end
159
243
  end
160
244
  end
data/lib/luck/listbox.rb CHANGED
@@ -1,10 +1,21 @@
1
1
  module Luck
2
2
  class ListBox < Control
3
- attr_accessor :data, :numbering, :hanging_indent
3
+ attr_accessor :data, :numbering, :hanging_indent, :offset, :index, :lastclick
4
+
5
+ def on_submit &blck
6
+ @handler = blck
7
+ end
8
+
9
+ def handler
10
+ @handler || @pane.handler
11
+ end
4
12
 
5
13
  def initialize *args
6
14
  @data = []
7
15
  @hanging_indent = 0
16
+ @offset = 0
17
+ @index = 0
18
+ @lastclick = Time.at 0
8
19
  super
9
20
  end
10
21
 
@@ -13,21 +24,144 @@ class ListBox < Control
13
24
  @hanging_indent = 4
14
25
  end
15
26
 
27
+ def fit_data offset=nil
28
+ lines = 0
29
+ items = 0
30
+
31
+ data[offset || @offset, height].each do |line|
32
+ _height = line_height(line)
33
+ if _height + lines > height
34
+ break
35
+ else
36
+ lines += _height
37
+ items += 1
38
+ end
39
+ end
40
+
41
+ [lines, items]
42
+ end
43
+
44
+ def line_height line
45
+ lines = 0
46
+
47
+ line = line.to_s
48
+ line = "##. #{line}" if @numbering
49
+ length = line.size
50
+ return 1 if length < 1
51
+ offset = 0
52
+ while offset < length
53
+ lines += 1
54
+ offset += width - ((offset > 0) ? @hanging_indent : 0)
55
+ end
56
+
57
+ lines
58
+ end
59
+
60
+ def fit_data_back offset=nil
61
+ lines = -1
62
+ items = 0
63
+
64
+ offset ||= @offset
65
+ data[([offset - height,0].max)..([offset,0].max)].reverse.each do |line|
66
+ _height = line_height(line)
67
+ if _height + lines >= height
68
+ break
69
+ else
70
+ lines += _height
71
+ items += 1
72
+ end
73
+ end
74
+
75
+ [lines, items]
76
+ end
77
+
16
78
  def redraw
79
+ @display.driver.cursor = false if @display.active_control == self
80
+
17
81
  row = y1
18
- data.first(height).each_with_index do |line, index|
82
+ return unless data
83
+ data[@offset, height].each_with_index do |line, index|
19
84
  line = line.to_s
20
- line = "#{(index + 1).to_s.rjust 2}. #{line}" if @numbering
85
+ number = index + @offset + 1
86
+ line = "#{number.to_s.rjust 2}. #{line}" if @numbering
87
+ print @display.color '1;34' if number == @index + 1
21
88
  length = line.size
22
89
  offset = 0
23
90
  while offset < length || offset == 0
24
- @display.place row, x1 + ((offset > 0) ? @hanging_indent : 0), line[offset, width - ((offset > 0) ? @hanging_indent : 0)]
91
+ @display.place row, x1, ((' ' * ((offset > 0) ? @hanging_indent : 0)) + line[offset, width - ((offset > 0) ? @hanging_indent : 0)]).ljust(width, ' ')
25
92
  row += 1
26
93
  offset += width - ((offset > 0) ? @hanging_indent : 0)
27
- break if row >= y2
94
+ if row >= y2
95
+ print @display.color '0' if number == @index + 1
96
+ break
97
+ end
28
98
  end
99
+ print @display.color '0' if number == @index + 1
29
100
  break if row >= y2
30
101
  end
102
+
103
+ until row >= y2
104
+ @display.place row, x1, ' ' * width
105
+ row += 1
106
+ end
107
+ end
108
+
109
+ def handle_char char
110
+ if char == "\n"
111
+ handler.call self, @data[@index] if handler
112
+ elsif char == :up
113
+ @index -= 1 if @index > 0
114
+ @offset -= 1 if @offset > @index
115
+ elsif char == :down
116
+ if @index < @data.size - 1
117
+ @index += 1
118
+ @offset += 1 if @offset + fit_data[1] <= @index
119
+ end
120
+ elsif char == :pageup
121
+ @offset = [0, @offset - fit_data_back[1]].max
122
+ @index = [@offset + fit_data[1] - 1, @index].min
123
+ elsif char == :pagedown
124
+ @offset = [@data.size - 1, @offset + fit_data[1]].min
125
+ @offset = [@offset, @data.size - fit_data_back(@data.size)[1]].min
126
+ @index = [@offset, @index].max
127
+ end
128
+ redraw
129
+ end
130
+
131
+ def handle_click button, modifiers, x, y
132
+ if button == :scrollup
133
+ @offset -= 1 if @offset > 0
134
+ @index = [@offset + fit_data[1] - 1, @index].min
135
+ elsif button == :scrolldown
136
+ @offset += 1 if @offset < (@data.size - fit_data_back(@data.size)[1])
137
+ @index = [@offset, @index].max
138
+
139
+ elsif button == :left
140
+ if Time.now - @lastclick < 0.5
141
+ button = :double
142
+ end
143
+ @lastclick = Time.now
144
+
145
+ previous = @index
146
+
147
+ row = y1
148
+ data[@offset, height].each_with_index do |line, index|
149
+ _height = line_height(line)
150
+ if _height + row > height
151
+ break
152
+ else
153
+ row += _height
154
+ end
155
+ if row > y
156
+ @index = index + @offset
157
+ break
158
+ end
159
+ end
160
+
161
+ handler.call self, @data[@index] if button == :double && previous == @index && handler
162
+
163
+ end
164
+ redraw
31
165
  end
32
166
  end
33
167
  end
data/lib/luck/pane.rb CHANGED
@@ -1,16 +1,45 @@
1
1
  module Luck
2
2
  class Pane
3
- attr_accessor :display, :x1, :y1, :x2, :y2, :title, :controls
3
+ attr_accessor :display, :x1, :y1, :x2, :y2, :title, :controls, :handler, :dirty, :visible
4
4
 
5
5
  def initialize display, x1, y1, x2, y2, title, controls={}, &blck
6
6
  @display = display
7
7
  @x1, @y1 = x1, y1
8
8
  @x2, @y2 = x2, y2
9
9
  @title, @controls = title, controls
10
+ @dirty = false
11
+ @visible = true
10
12
 
11
13
  instance_eval &blck if blck
12
14
  end
13
15
 
16
+ def [] control
17
+ @controls[control]
18
+ end
19
+
20
+ alias dirty? dirty
21
+ def dirty!
22
+ @dirty = true
23
+ end
24
+
25
+ alias visible? visible
26
+ def show!; @visible = true; end
27
+ def hide!; @visible = false; end
28
+
29
+ def yank_values
30
+ vals = {}
31
+ @controls.each_pair do |key, control|
32
+ next unless control.respond_to? :value
33
+ vals[key] = control.value
34
+ control.value = ''
35
+ end
36
+ vals
37
+ end
38
+
39
+ def on_submit &blck
40
+ @handler = blck
41
+ end
42
+
14
43
  def control name, type, *args, &blck
15
44
  @controls[name] = type.new(self, *args, &blck)
16
45
  end
@@ -39,6 +68,7 @@ class Pane
39
68
  def redraw
40
69
  draw_frame
41
70
  draw_contents
71
+ draw_title
42
72
  end
43
73
 
44
74
  def draw_contents
@@ -47,29 +77,55 @@ class Pane
47
77
  end
48
78
  end
49
79
 
50
- def topbar
51
- title = " * #{@title} * "
52
- left = (((width - 1).to_f / 2) - (title.size.to_f / 2)).to_i
53
-
54
- if title.size >= width
55
- "#{@display.color '1;34'} #{@title[0, width - 3].center(width - 3)} #{@display.color '0;2'}"
56
- else
57
- title_colored = "#{@display.color '1;34'}#{title}#{@display.color '0;2'}"
58
- ('-' * left) + title_colored + ('-' * (width - 1 - title.size - left))
80
+ def control_at x, y
81
+ @controls.values.each do |control|
82
+ return control if (control.x1..control.x2).include?(x) && (control.y1..control.y2).include?(y)
83
+ end
84
+ nil
85
+ end
86
+
87
+ def handle_click button, modifiers, x, y
88
+ control = control_at x, y
89
+ if control
90
+ if button == :left
91
+ control.focus! if control.respond_to? :handle_char
92
+ control.redraw
93
+ end
94
+ control.handle_click button, modifiers, x, y if control.respond_to?(:handle_click)
59
95
  end
60
96
  end
61
97
 
62
98
  def draw_frame
63
- bottombar = '-' * (width - 1)
99
+ linebar = 'q' * (width - 1)
64
100
  fillerbar = ' ' * (width - 1)
65
101
 
102
+ @display.driver.linedrawing = true
66
103
  print @display.color('0;2')
67
- @display.place y1, x1, "+#{topbar}+"
68
- @display.place y2, x1, "+#{bottombar}+"
104
+
105
+ @display.place y1, x1, "n#{linebar}n"
106
+ @display.place y2, x1, "n#{linebar}n"
107
+ #~ @display.place y1, x1, "l#{topbar}k"
108
+ #~ @display.place y2, x1, "m#{bottombar}j"
69
109
 
70
110
  (y1 + 1).upto y2 - 1 do |row|
71
- @display.place row, x1, "|#{fillerbar}|"
111
+ @display.place row, x1, "x#{fillerbar}x"
112
+ end
113
+
114
+ @display.driver.linedrawing = false
115
+ print @display.color('0')
116
+ end
117
+
118
+ def draw_title
119
+ title = " * #{@title} * "
120
+ left = (((width - 1).to_f / 2) - (title.size.to_f / 2)).to_i
121
+
122
+ if title.size >= width
123
+ title = @title[0, width - 3]
124
+ left = 0
72
125
  end
126
+
127
+ print @display.color('1;34')
128
+ @display.place y1, x1 + 1 + left, title
73
129
  print @display.color('0')
74
130
  end
75
131
  end
data/lib/luck/textbox.rb CHANGED
@@ -1,10 +1,12 @@
1
1
  module Luck
2
2
  class TextBox < Label
3
- attr_accessor :multiline, :handler, :label
3
+ attr_accessor :multiline, :handler, :label, :mask, :index
4
4
 
5
5
  def initialize *args
6
6
  @label = ''
7
7
  @text = 'Input box'
8
+ @index = 0
9
+
8
10
  super
9
11
  end
10
12
 
@@ -17,30 +19,73 @@ class TextBox < Label
17
19
  when :right
18
20
  text.rjust width
19
21
  else
20
- @display.place y1, x1 + text.size, "\e[s"
22
+ @display.driver.set_cursor y1, x1 + text.size - @text.size + @index if @display.active_control == self
21
23
  end
22
- #print "\e[s" # save
24
+ @display.driver.cursor = true if @display.active_control == self
23
25
  end
24
26
 
25
27
  def on_submit &blck
26
28
  @handler = blck
27
29
  end
28
30
 
31
+ def value= val
32
+ @text = val
33
+ @index = @text.size if @index > @text.size
34
+ end
35
+ def value
36
+ @text
37
+ end
38
+
29
39
  def text
30
- return @text if @label.empty?
31
- "#{@label}: #{@text}"
40
+ text = @mask ? (@mask * value.size) : value
41
+ text = "#{label}: #{text}" unless label.empty?
42
+ text
43
+ end
44
+
45
+ def handler
46
+ @handler || @pane.handler
32
47
  end
33
48
 
34
49
  def handle_char char
35
50
  if char == "\n" && !@multiline
36
- @handler.call @text if @handler
37
- @text = ''
38
- elsif char == "\177"
39
- @text.slice! -1
40
- else
41
- @text += char
51
+ handler.call self, @text if handler
52
+ self.value = ''
53
+ elsif char == :backspace
54
+ if @index > 0
55
+ @index -= 1
56
+ @text.slice! @index, 1
57
+ end
58
+ elsif char == :delete
59
+ if @index < @text.size
60
+ @text.slice! @index, 1
61
+ end
62
+ elsif char == :left
63
+ @index -= 1 if @index > 0
64
+ elsif char == :right
65
+ @index += 1 if @index < @text.size
66
+ elsif char == :home
67
+ @index = 0
68
+ elsif char == :end
69
+ @index = @text.size
70
+ elsif char.is_a? String
71
+ @text.insert @index, char
72
+ @index += 1
42
73
  end
43
74
  redraw
44
75
  end
45
76
  end
77
+
78
+ class CommandBox < TextBox
79
+ def command?
80
+ @text[0,1] == '/'
81
+ end
82
+
83
+ def label
84
+ command? ? 'Command' : @label
85
+ end
86
+
87
+ def value
88
+ command? ? super[1..-1] : super
89
+ end
90
+ end
46
91
  end
data/lib/luck.rb CHANGED
@@ -1,9 +1,12 @@
1
1
  Luck = Module.new
2
2
 
3
- require 'luck/display'
4
- require 'luck/pane'
3
+ require 'luck/alert'
4
+ require 'luck/ansi'
5
+ require 'luck/button'
5
6
  require 'luck/control'
7
+ require 'luck/display'
8
+ require 'luck/label'
6
9
  require 'luck/listbox'
10
+ require 'luck/pane'
7
11
  require 'luck/progressbar'
8
- require 'luck/label'
9
12
  require 'luck/textbox'
data/luck.gemspec ADDED
@@ -0,0 +1,57 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{luck}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Daniel Danopia"]
12
+ s.date = %q{2010-04-10}
13
+ s.description = %q{Pure-ruby CLI UI system. Includes multiple panes in a display and multiple controls in a pane. luck is lucky (as opposed to ncurses being cursed)}
14
+ s.email = %q{me.github@danopia.net}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "README.md",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "lib/luck.rb",
26
+ "lib/luck/alert.rb",
27
+ "lib/luck/ansi.rb",
28
+ "lib/luck/button.rb",
29
+ "lib/luck/control.rb",
30
+ "lib/luck/display.rb",
31
+ "lib/luck/label.rb",
32
+ "lib/luck/listbox.rb",
33
+ "lib/luck/pane.rb",
34
+ "lib/luck/progressbar.rb",
35
+ "lib/luck/textbox.rb",
36
+ "luck.gemspec"
37
+ ]
38
+ s.homepage = %q{http://github.com/danopia/luck}
39
+ s.rdoc_options = ["--charset=UTF-8"]
40
+ s.require_paths = ["lib"]
41
+ s.rubygems_version = %q{1.3.5}
42
+ s.summary = %q{Pure-ruby CLI UI system}
43
+
44
+ if s.respond_to? :specification_version then
45
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
49
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
50
+ else
51
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
52
+ end
53
+ else
54
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
55
+ end
56
+ end
57
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: luck
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Danopia
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-02-07 00:00:00 -05:00
12
+ date: 2010-04-10 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -38,6 +38,9 @@ files:
38
38
  - Rakefile
39
39
  - VERSION
40
40
  - lib/luck.rb
41
+ - lib/luck/alert.rb
42
+ - lib/luck/ansi.rb
43
+ - lib/luck/button.rb
41
44
  - lib/luck/control.rb
42
45
  - lib/luck/display.rb
43
46
  - lib/luck/label.rb
@@ -45,6 +48,7 @@ files:
45
48
  - lib/luck/pane.rb
46
49
  - lib/luck/progressbar.rb
47
50
  - lib/luck/textbox.rb
51
+ - luck.gemspec
48
52
  has_rdoc: true
49
53
  homepage: http://github.com/danopia/luck
50
54
  licenses: []