luck 0.0.0 → 0.1.0

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