window_blessing 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +4 -0
  3. data/Guardfile +7 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +43 -0
  6. data/Rakefile +12 -0
  7. data/bin/buffered_screen_demo.rb +92 -0
  8. data/bin/color_picker_demo.rb +176 -0
  9. data/bin/foiled_demo.rb +27 -0
  10. data/bin/text_editor_demo.rb +292 -0
  11. data/bin/windowed_screen_demo.rb +71 -0
  12. data/bin/xterm_screen_demo.rb +33 -0
  13. data/lib/window_blessing.rb +54 -0
  14. data/lib/window_blessing/buffer.rb +216 -0
  15. data/lib/window_blessing/buffered_screen.rb +29 -0
  16. data/lib/window_blessing/color.rb +71 -0
  17. data/lib/window_blessing/constants.rb +3 -0
  18. data/lib/window_blessing/event_manager.rb +75 -0
  19. data/lib/window_blessing/event_queue.rb +20 -0
  20. data/lib/window_blessing/evented.rb +19 -0
  21. data/lib/window_blessing/evented_variable.rb +47 -0
  22. data/lib/window_blessing/tools.rb +124 -0
  23. data/lib/window_blessing/version.rb +3 -0
  24. data/lib/window_blessing/widgets/draggable_background.rb +13 -0
  25. data/lib/window_blessing/widgets/label.rb +23 -0
  26. data/lib/window_blessing/widgets/slider.rb +53 -0
  27. data/lib/window_blessing/widgets/text_field.rb +92 -0
  28. data/lib/window_blessing/window.rb +273 -0
  29. data/lib/window_blessing/windowed_screen.rb +53 -0
  30. data/lib/window_blessing/xterm_event_parser.rb +156 -0
  31. data/lib/window_blessing/xterm_input.rb +40 -0
  32. data/lib/window_blessing/xterm_log.rb +7 -0
  33. data/lib/window_blessing/xterm_output.rb +213 -0
  34. data/lib/window_blessing/xterm_screen.rb +109 -0
  35. data/lib/window_blessing/xterm_state.rb +27 -0
  36. data/spec/buffer_spec.rb +170 -0
  37. data/spec/color_spec.rb +36 -0
  38. data/spec/spec_helper.rb +6 -0
  39. data/spec/tools_spec.rb +142 -0
  40. data/spec/window_spec.rb +61 -0
  41. data/window_blessing.gemspec +28 -0
  42. metadata +226 -0
@@ -0,0 +1,40 @@
1
+
2
+ module WindowBlessing
3
+ class XtermInput
4
+ attr_reader :event_parser
5
+
6
+ def initialize
7
+ @event_parser = XtermEventParser.new
8
+ end
9
+
10
+ def read_events
11
+ events = []
12
+ if raw = read_pending_input
13
+ parsed = event_parser.parse(raw)
14
+ if parsed
15
+ new_events = parsed.events
16
+ events += new_events
17
+ new_events.length
18
+ else
19
+ events << {:type => :event_parser_failure, :raw => raw, :failure_info => event_parser.parser_failure_info}
20
+ end
21
+ end
22
+ events
23
+ end
24
+
25
+ private
26
+ def read_pending_input
27
+ read = nil
28
+ begin
29
+ while c = STDIN.read_nonblock(1000)
30
+ read = read ? read + c : c
31
+ end
32
+ rescue Errno::EAGAIN # nothing was ready to be read
33
+ rescue Errno::EINTR
34
+ rescue EOFError
35
+ end
36
+ read
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,7 @@
1
+ module WindowBlessing
2
+ class XtermLog
3
+ def self.log(str)
4
+ File.open("xterm.log","a+") {|f| f.puts str}
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,213 @@
1
+ # ref: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
2
+ # ref: http://www.vt100.net/docs/vt102-ug/chapter5.html
3
+
4
+ module WindowBlessing
5
+
6
+ class XtermOutput
7
+ module SetColor
8
+ def change_fg(fg)
9
+ set_fg(fg) if fg!=@current_fg
10
+ @current_fg = fg
11
+ end
12
+
13
+ def change_bg(bg)
14
+ set_bg(bg) if bg!=@current_bg
15
+ @current_bg = bg
16
+ end
17
+
18
+ def set_fg(fg)
19
+ out "\x1b[38;5;#{fg}m"
20
+ end
21
+
22
+ def set_bg(bg)
23
+ out "\x1b[48;5;#{bg}m"
24
+ end
25
+
26
+ attr_reader :current_fg, :current_bg
27
+
28
+ def set_color(fg, bg=nil)
29
+ set_fg(fg) if fg
30
+ set_bg(bg) if bg
31
+ end
32
+
33
+ # fg and bg are r-g-b arrays: [0..255, 0..255, 0..255]
34
+ # This is not supported by iTerm2: http://code.google.com/p/iterm2/issues/detail?id=218
35
+ # konsole supports it: https://github.com/robertknight/konsole/blob/master/user-doc/README.moreColors
36
+ def set_color_24bit(fg, bg=nil)
37
+ out "\x1b[38;2;#{fg.join(';')}m" if fg
38
+ out "\x1b[48;2;#{bg.join(';')}m" if bg
39
+ end
40
+
41
+ def reset_color
42
+ @current_fg = 7
43
+ @current_bg = 0
44
+ out "\x1b[0m"
45
+ end
46
+
47
+ def out_color(txt, fg, bg)
48
+ set_color(fg, bg)
49
+ out txt
50
+ reset_color
51
+ end
52
+
53
+ def set_bold; out "\x1b[1m" end
54
+ def set_underline; out "\x1b[4m" end
55
+ end
56
+ include SetColor
57
+
58
+ module SetState
59
+ def show_cursor; out "\e[?25h"; end
60
+ def hide_cursor; out "\e[?25l"; end
61
+
62
+ def enable_mouse; out "\e[?1003h"; end
63
+ def disable_mouse; out "\e[?1003l"; end
64
+
65
+ def enable_focus_events; out "\e[?1004h" end
66
+ def disable_focus_events; out "\e[?1004l" end
67
+
68
+ def enable_utf8; out "\e%G" end
69
+ def disable_utf8; out "\e%@" end
70
+
71
+ def enable_alternate_screen
72
+ out "\e7"
73
+ out "\e[?47h"
74
+ end
75
+
76
+ def disable_alternate_screen
77
+ out "\e[?47l"
78
+ out "\e8"
79
+ end
80
+
81
+ # TODO: find out what stty raw -echo sends to xterm
82
+ # INTERNAL NOTE: out "\e[12h" # should turn off echo according to doc, but didn't work
83
+ # http://stackoverflow.com/questions/174933/how-to-get-a-single-character-without-pressing-enter
84
+ def echo_off; system "stty raw -echo"; end
85
+
86
+ # INTERNAL NOTE: out "\e[12l" # should turn echo back on
87
+ def echo_on; system "stty -raw echo"; end
88
+
89
+ =begin
90
+ raw may set the following:
91
+ 00830 option(x + "-icrnl", ""); /* map CR to NL on input */
92
+ 00831 option(x + "-ixon", ""); /* enable start/stop output control */
93
+ 00832 option(x + "-opost", ""); /* perform output processing */
94
+ 00833 option(x + "-onlcr", ""); /* Map NL to CR-NL on output */
95
+ 00834 option(x + "-isig", ""); /* enable signals */
96
+ 00835 option(x + "-icanon", "");/* canonical input (erase and kill enabled) */
97
+ 00836 option(x + "-iexten", "");/* enable extended functions */
98
+ 00837 option(x + "-echo", ""); /* enable echoing of input characters */
99
+
100
+ =end
101
+
102
+ # This seems to work - or at least it does SOMETHING
103
+ # def insert_mode; out "\e[4m"; end
104
+ # def replace_mode; out "\e[4l"; end
105
+
106
+ end
107
+ include SetState
108
+
109
+ attr_accessor :xterm_state
110
+
111
+ def initialize(xterm_state)
112
+ @xterm_state = xterm_state
113
+ end
114
+
115
+ # cursor 0, 0 is the upper left hand corner
116
+ # cursor point(0, 0) is also accepted
117
+ def cursor(loc_or_x, y=nil)
118
+ loc = y ? point(loc_or_x,y) : loc_or_x
119
+ out "\e[#{loc.y+1};#{loc.x+1}H"
120
+ end
121
+
122
+ # raw screen output
123
+ def out(str)
124
+ $stdout.print str
125
+ str
126
+ end
127
+
128
+ def out_at(loc, str)
129
+ cursor(loc)
130
+ out str
131
+ end
132
+
133
+ def out_at_with_color(loc, str, fg, bg)
134
+ return unless str.length > 0
135
+ cursor loc
136
+
137
+ change_fg fg[0]
138
+ change_bg bg[0]
139
+
140
+ current_attrs = [current_fg, current_bg]
141
+
142
+ next_output_pos = 0
143
+ pos = 0
144
+
145
+ fg.zip(bg).each do |attrs|
146
+ if current_attrs!=attrs
147
+ out str[next_output_pos..pos-1]
148
+ change_fg attrs[0]
149
+ change_bg attrs[1]
150
+ current_attrs = attrs
151
+ next_output_pos = pos
152
+ end
153
+ pos += 1
154
+ end
155
+ out str[next_output_pos..-1]
156
+ end
157
+
158
+ def draw_buffer(loc, buffer)
159
+ loc = loc.clone
160
+ reset_color
161
+ buffer.each_line do |line,fg,bg|
162
+ out_at_with_color loc, line, fg, bg
163
+ loc.y += 1
164
+ end
165
+ end
166
+
167
+ # convert all \n to \n\r
168
+ def puts(s=nil)
169
+ width = xterm_state.size.x
170
+ lines = "#{s}\n".split("\n").collect do |line|
171
+ line + " " * (width - (line.length % width))
172
+ end
173
+ out lines.flatten.join "\n"
174
+ end
175
+
176
+ def clear; out "\e[2J"; end
177
+
178
+
179
+ # INTERNAL NOTE: xterm returns 3 numbers: ?, height, width
180
+ # Xterm sends back response as an escape sequence. XtermEventParser knows how to capture and interpret the result.
181
+ def request_xterm_size; out "\e[18t"; end
182
+
183
+ # INTERNAL NOTE: xterm returns 3 numbers: ?, height, width
184
+ # This returns the entire screen size in pixels - not just the pixel-size of the x-term
185
+ def request_display_pixel_size; out "\e[14t"; end
186
+
187
+ def request_cursor_position; out "\e[?6n"; end
188
+
189
+ def request_state_update
190
+ request_xterm_size
191
+ request_display_pixel_size
192
+ end
193
+
194
+ def enable_resize_events
195
+ Signal.trap "SIGWINCH" do
196
+ request_xterm_size
197
+ end
198
+ end
199
+
200
+ def disable_resize_events
201
+ Signal.trap "SIGWINCH", "DEFAULT"
202
+ end
203
+
204
+ def reset_all
205
+ disable_focus_events
206
+ disable_mouse
207
+ disable_resize_events
208
+ reset_color
209
+ show_cursor
210
+ echo_on
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,109 @@
1
+ module WindowBlessing
2
+ class XtermScreen
3
+ include GuiGeo
4
+ include GuiGeo::Tools
5
+
6
+ attr_accessor :input, :output, :event_manager, :state, :event_queue
7
+
8
+ def initialize
9
+ @event_manager = EventManager.new(self)
10
+ @state = XtermState.new @event_manager
11
+ @input = XtermInput.new
12
+ @output = XtermOutput.new(@state)
13
+ @running = true
14
+ @pending_events = []
15
+ @event_queue = EventQueue.new
16
+
17
+ @event_manager.add_handler :key_press do |event|
18
+ quit if event[:key]==:control_q
19
+ end
20
+ end
21
+
22
+ def inspect
23
+ "<#{self.class}:#{object_id}>"
24
+ end
25
+
26
+ def quit; @running = false; end
27
+ def running?; @running; end
28
+
29
+ def queue_event(e); event_queue << e end
30
+ def queued_events?; !event_queue.empty? end
31
+
32
+ def queue_pending_xterm_events
33
+ event_queue << input.read_events
34
+ end
35
+
36
+ def wait_for_events(max=100)
37
+ count = max
38
+ while !queued_events? && count > 0
39
+ queue_pending_xterm_events
40
+ count -= 1
41
+ sleep 0.01
42
+ end
43
+ raise "no events!" unless queued_events?
44
+ end
45
+
46
+ def process_queued_events
47
+ event_manager.handle_events event_queue.pop_all
48
+ end
49
+
50
+ def process_events
51
+ queue_pending_xterm_events
52
+ queue_event :type => :tick
53
+ process_queued_events
54
+ end
55
+
56
+ def event_loop
57
+ while running?
58
+ process_events
59
+ sleep 1/60.0
60
+ end
61
+ end
62
+
63
+ def initialize_screen
64
+ output.request_state_update
65
+ wait_for_events
66
+ process_events
67
+ end
68
+
69
+ # run xterm raw-session
70
+ # options
71
+ #
72
+ def start(options={})
73
+ in_xterm_state(options) do
74
+ initialize_screen
75
+ yield self
76
+ event_loop
77
+ end
78
+ end
79
+
80
+
81
+ # options
82
+ # :mouse => true
83
+ # :no_cursor => true
84
+ # :alternate_screen => true
85
+ # :full => true (enables all above features)
86
+ # :utf8
87
+ def in_xterm_state(options = {})
88
+ output.echo_off
89
+ output.enable_alternate_screen if options[:full] || options[:alternate_screen]
90
+ output.enable_mouse if options[:full] || options[:mouse]
91
+ output.hide_cursor if options[:full] || options[:no_cursor]
92
+ output.enable_utf8 if options[:utf8]
93
+ output.enable_focus_events
94
+ output.enable_resize_events
95
+ output.clear
96
+
97
+ yield self
98
+ ensure
99
+ output.reset_all
100
+ output.disable_utf8 if options[:utf8]
101
+ if options[:full] || options[:alternate_screen]
102
+ output.reset_color
103
+ output.clear
104
+ output.disable_alternate_screen
105
+ end
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,27 @@
1
+ module WindowBlessing
2
+ class XtermState
3
+ attr_accessor :state
4
+
5
+ def initialize(event_manager)
6
+ @state = {:size => point(-1,-1)}
7
+
8
+ event_manager.add_handler :xterm_state do |event|
9
+ state_type = event[:state_type]
10
+ old_state = state[state_type]
11
+ new_state = event[:state]
12
+ state[state_type] = new_state
13
+ if old_state!=new_state
14
+ case state_type
15
+ when :size
16
+ event_manager.handle_event :type => :resize, :old_size => old_state, :size => new_state, :raw => event[:raw]
17
+ else
18
+ event_manager.handle_event :type => :state_change, :state_type => state_type, :old_state => old_state, :state => new_state, :raw => event[:raw]
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+
25
+ def size; state[:size] end
26
+ end
27
+ end
@@ -0,0 +1,170 @@
1
+ require 'spec_helper'
2
+
3
+ module WindowBlessing
4
+ describe "Buffer" do
5
+ include Tools
6
+
7
+ def test_frame(options={})
8
+ buffer(point(4,4), {:contents => %w{1234 2345 3456 4567}}.merge(options))
9
+ end
10
+
11
+ it "blank fb" do
12
+ buffer(point(4,4)).to_s.should == " \n \n \n "
13
+ end
14
+
15
+ it "string init" do
16
+ buffer(point(4,4),:contents => ["hi","there"]).to_s.should == "hi \nther\n \n "
17
+ buffer(point(4,4),:contents => "hi\nthere").to_s.should == "hi \nther\n \n "
18
+ buffer(point(4,4),:contents => "hi\n\t\0here").to_s.should == "hi \n??he\n \n "
19
+ end
20
+
21
+ it "inspect" do
22
+ buffer(point 4,4).inspect.class.should == String
23
+ end
24
+
25
+ it "invalid init" do
26
+ expect { buffer(point(4,4),:contents => 1) }.to raise_error
27
+ end
28
+
29
+ it "subbuffer" do
30
+ test_frame.subbuffer(rect(1,1,2,2)).to_s.should == "34\n45"
31
+ end
32
+
33
+ it "array init" do
34
+ test_frame.to_s.should == "1234\n2345\n3456\n4567"
35
+ end
36
+
37
+ it "crop" do
38
+ (f=buffer(point 4,4)).cropped(rect(1,1,2,2)) do
39
+ f.crop_area.should == rect(1,1,2,2)
40
+ f.cropped?.should == true
41
+ end
42
+ end
43
+
44
+ it "fill" do
45
+ fb = buffer(point(4,4))
46
+ fb.fill :string => "-"
47
+ fb.to_s.should == "----\n----\n----\n----"
48
+ end
49
+
50
+ it "cropped fill" do
51
+ (f=test_frame).cropped(rect(1,1,2,1)) do
52
+ f.fill :string => '-'
53
+ end.to_s.should == "1234\n2--5\n3456\n4567"
54
+ end
55
+
56
+ it "draw_rect" do
57
+ (f=buffer(point(4,4))).to_s.should == " \n \n \n "
58
+ f.draw_rect(rect(1,0,2,2),:string => "-")
59
+ f.to_s.should == " -- \n -- \n \n "
60
+ end
61
+
62
+ it "clear" do
63
+ fb = buffer(point 2,2).fill(:string => '-')
64
+ fb.to_s.should == "--\n--"
65
+ fb.clear
66
+ fb.to_s.should == " \n "
67
+ end
68
+
69
+ it "draw_buffer" do
70
+ f1 = test_frame
71
+ f2 = buffer(point(2,2), :contents => "ab\nbc")
72
+ f1.draw_buffer(point(1,1),f2)
73
+ f1.to_s.should == "1234\n2ab5\n3bc6\n4567"
74
+ end
75
+
76
+ it "cropped draw_buffer" do
77
+ f1 = test_frame
78
+ f2 = buffer(point(2,2), :contents => "ab\nbc")
79
+ f1.cropped(rect(1,1,2,1)) do
80
+ f1.draw_buffer(point(1,1),f2)
81
+ end.to_s.should == "1234\n2ab5\n3456\n4567"
82
+ end
83
+
84
+ it "fill only overwrites what is provided" do
85
+ f0 = test_frame :bg => 9, :fg => 8
86
+
87
+ f1 = f0.clone
88
+ f1.to_s.should == "1234\n2345\n3456\n4567"
89
+ f1.fg_buffer.should == [[8, 8, 8, 8], [8, 8, 8, 8], [8, 8, 8, 8], [8, 8, 8, 8]]
90
+ f1.bg_buffer.should == [[9, 9, 9, 9], [9, 9, 9, 9], [9, 9, 9, 9], [9, 9, 9, 9]]
91
+
92
+ f1.fill :string => "!"
93
+ f1.to_s.should == "!!!!\n!!!!\n!!!!\n!!!!"
94
+ f1.fg_buffer.should == [[8, 8, 8, 8], [8, 8, 8, 8], [8, 8, 8, 8], [8, 8, 8, 8]]
95
+ f1.bg_buffer.should == [[9, 9, 9, 9], [9, 9, 9, 9], [9, 9, 9, 9], [9, 9, 9, 9]]
96
+
97
+ f1 = f0.clone
98
+ f1.fill :bg => 0
99
+ f1.to_s.should == "1234\n2345\n3456\n4567"
100
+ f1.fg_buffer.should == [[8, 8, 8, 8], [8, 8, 8, 8], [8, 8, 8, 8], [8, 8, 8, 8]]
101
+ f1.bg_buffer.should == [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
102
+
103
+ f1 = f0.clone
104
+ f1.fill :fg => 0
105
+ f1.to_s.should == "1234\n2345\n3456\n4567"
106
+ f1.fg_buffer.should == [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
107
+ f1.bg_buffer.should == [[9, 9, 9, 9], [9, 9, 9, 9], [9, 9, 9, 9], [9, 9, 9, 9]]
108
+ end
109
+
110
+ it "cropped fill only overwrites what is provided" do
111
+ f0 = test_frame :bg => 9, :fg => 8
112
+
113
+ f1 = f0.clone
114
+ f1.to_s.should == "1234\n2345\n3456\n4567"
115
+ f1.fg_buffer.should == [[8, 8, 8, 8], [8, 8, 8, 8], [8, 8, 8, 8], [8, 8, 8, 8]]
116
+ f1.bg_buffer.should == [[9, 9, 9, 9], [9, 9, 9, 9], [9, 9, 9, 9], [9, 9, 9, 9]]
117
+
118
+ f1.cropped(rect(1,1,2,2)) {f1.fill :string => "!"}
119
+ f1.to_s.should == "1234\n2!!5\n3!!6\n4567"
120
+ f1.fg_buffer.should == [[8, 8, 8, 8], [8, 8, 8, 8], [8, 8, 8, 8], [8, 8, 8, 8]]
121
+ f1.bg_buffer.should == [[9, 9, 9, 9], [9, 9, 9, 9], [9, 9, 9, 9], [9, 9, 9, 9]]
122
+
123
+ f1 = f0.clone
124
+ f1.cropped(rect(1,1,2,2)) {f1.fill :bg => 0}
125
+ f1.to_s.should == "1234\n2345\n3456\n4567"
126
+ f1.fg_buffer.should == [[8, 8, 8, 8], [8, 8, 8, 8], [8, 8, 8, 8], [8, 8, 8, 8]]
127
+ f1.bg_buffer.should == [[9, 9, 9, 9], [9, 0, 0, 9], [9, 0, 0, 9], [9, 9, 9, 9]]
128
+
129
+ f1 = f0.clone
130
+ f1.cropped(rect(1,1,2,2)) {f1.fill :fg => 0}
131
+ f1.to_s.should == "1234\n2345\n3456\n4567"
132
+ f1.fg_buffer.should == [[8, 8, 8, 8], [8, 0, 0, 8], [8, 0, 0, 8], [8, 8, 8, 8]]
133
+ f1.bg_buffer.should == [[9, 9, 9, 9], [9, 9, 9, 9], [9, 9, 9, 9], [9, 9, 9, 9]]
134
+ end
135
+
136
+ it "dirty" do
137
+ f1 = test_frame
138
+ f1.dirty?.should == false
139
+ f1.dirty rect(1,2,3,4)
140
+ f1.dirty?.should == true
141
+
142
+ f1.dirty_area.should == rect(1,2,3,2)
143
+
144
+ s = f1.dirty_subbuffer
145
+ s.to_s.should == "456\n567"
146
+
147
+ f1.clean
148
+ f1.dirty?.should == false
149
+ end
150
+
151
+ it "on_dirty" do
152
+ f1 = test_frame
153
+
154
+ is_now_dirty = false
155
+ f1.on_dirty do
156
+ is_now_dirty = true
157
+ end
158
+ is_now_dirty.should == false
159
+ f1.dirty
160
+ is_now_dirty.should == true
161
+ end
162
+
163
+ it "color buffers" do
164
+ f1 = test_frame
165
+
166
+ f1.fg_buffer.should == [[7, 7, 7, 7], [7, 7, 7, 7], [7, 7, 7, 7], [7, 7, 7, 7]]
167
+ f1.bg_buffer.should == [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
168
+ end
169
+ end
170
+ end