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,71 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ require File.expand_path File.join(File.dirname(__FILE__), %w{.. lib window_blessing})
4
+ include GuiGeo
5
+ include WindowBlessing
6
+ include Widgets
7
+ include Tools
8
+
9
+ class DragWindow < Window
10
+ include DraggableBackground
11
+ def initialize(loc,buffer)
12
+ super rect(loc,buffer.size)
13
+ @buffer.draw_buffer point, buffer
14
+ clean
15
+
16
+ on :key_press do |event|
17
+ XtermLog.log "key_press: #{event[:key]}"
18
+ r = area.clone
19
+ case event[:key]
20
+ when :home then r.loc.x = 0
21
+ when :page_up then r.loc.y = 0
22
+ when :page_down then r.loc.y = parent.size.y
23
+ when :end then r.loc.x = parent.size.x
24
+ when :left then r.loc.x -= 1
25
+ when :right then r.loc.x += 1
26
+ when :up then r.loc.y -= 1
27
+ when :down then r.loc.y += 1
28
+ end
29
+ self.area = r
30
+ self.move_onscreen
31
+ end
32
+ end
33
+ end
34
+
35
+ def color_window(r)
36
+ size = r.size
37
+ DragWindow.new r.loc, (Buffer.new(size).tap do |buffer|
38
+ size.y.times do |y|
39
+ size.x.times do |x|
40
+ c1 = rgb_screen_color y / (size.y-1).to_f, x / (size.x-1).to_f, 0
41
+ c2 = rgb_screen_color 0, 1 - y / (size.y-1).to_f, 1 - x / (size.x-1).to_f
42
+ buffer.draw_rect rect(point(x,y),point(1,1)), :bg => c1, :fg => c2, :string => "▒"
43
+ end
44
+ end
45
+ end)
46
+ end
47
+
48
+ def gray_window(r)
49
+ size = r.size
50
+ DragWindow.new r.loc, (Buffer.new(size).tap do |buffer|
51
+ size.y.times do |y|
52
+ size.x.times do |x|
53
+ g1 = gray_screen_color(x / (size.x-1).to_f)
54
+ g2 = gray_screen_color(y / (size.y-1).to_f)
55
+ buffer.draw_rect rect(point(x,y),point(1,1)), :bg => g1, :fg => g2, :string => "o"
56
+ end
57
+ end
58
+ end)
59
+ end
60
+
61
+ WindowedScreen.new.start(:full=>true, :utf8 => true) do |screen|
62
+
63
+ root_window = screen.root_window
64
+
65
+ root_window.add_child gray_win = gray_window(rect(10,10,25,13))
66
+ root_window.add_child color_win = color_window(rect(30,15,24,12))
67
+ root_window.add_child Label.new rect(point,point(1000,1)),
68
+ " Arrows, Home, End, PgUp, PgDown or drag with Mouse to move. Space to toggle. Ctrl-Q to quit.",
69
+ :bg => color(0.25)
70
+
71
+ end
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path File.join(File.dirname(__FILE__), %w{.. lib window_blessing})
3
+ include GuiGeo
4
+
5
+ WindowBlessing::XtermScreen.new.start(:full=>true) do |screen|
6
+ event_manager = screen.event_manager
7
+
8
+ last_event = nil
9
+ event_count = 0
10
+
11
+ event_manager.add_handler :tick do
12
+ screen.output.instance_eval do
13
+ cursor(0,0)
14
+ reset_color
15
+ puts Time.now
16
+ puts "Ctrl-Q to quit"
17
+ puts "size: #{screen.state.size.inspect}"
18
+
19
+ if last_event
20
+ e = last_event.inspect
21
+ e += "\nfailure_info (raw=#{last_event[:raw].inspect}): #{last_event[:failure_info]}" if last_event[:failure_info]
22
+ e += "\ntrace:\n "+last_event[:exception].backtrace.join("\n ") if last_event[:type]==:event_exception
23
+ puts "event #{event_count}: #{e} "
24
+ end
25
+ end
26
+ end
27
+
28
+ event_manager.add_handler do |event|
29
+ event_count += 1
30
+ WindowBlessing::XtermLog.log "last_event = #{event.inspect}"
31
+ last_event = event
32
+ end
33
+ end
@@ -0,0 +1,54 @@
1
+ def add_load_path(path)
2
+ full_path = File.expand_path path
3
+ $LOAD_PATH.unshift full_path unless $LOAD_PATH.include?(path) || $LOAD_PATH.include?(full_path)
4
+ end
5
+ add_load_path File.dirname(__FILE__)
6
+
7
+ =begin
8
+ Copyright 2013 Shane Brinkman-Davis
9
+ See README for licence information.
10
+ =end
11
+
12
+ %w{
13
+ constants
14
+ color
15
+ tools
16
+ buffer
17
+ version
18
+ event_queue
19
+ event_manager
20
+ evented
21
+ evented_variable
22
+ xterm_event_parser
23
+ xterm_log
24
+ xterm_output
25
+ xterm_state
26
+ xterm_input
27
+ xterm_screen
28
+ buffered_screen
29
+ window
30
+ windowed_screen
31
+ widgets/draggable_background
32
+ widgets/label
33
+ widgets/slider
34
+ widgets/text_field
35
+ }.each do |file|
36
+ require "window_blessing/#{file}"
37
+ end
38
+
39
+ module WindowBlessing
40
+ # Your code goes here...
41
+ class << self
42
+ include Tools
43
+ def main(&block)
44
+ main_window = Window.new
45
+ Screen.new.open do
46
+ instance_exec(main_window, &block)
47
+ on_tick do
48
+ main_window.area = rect(screen_buffer.size)
49
+ main_window.draw screen_buffer
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,216 @@
1
+ =begin
2
+ NOTE: Unlike bitmap graphics, it is often going to be faster to clone bitmap frames rather than do special logic to extract data from frames in-place. This is due the relatively small amount of data for a console-window "buffer" (cols * lines ~= a few kilobytes) and Ruby having optimized routines to handle strings as single units.
3
+
4
+ For example, it's actually going to be faster to draw a sub-region from one buffer onto another by first copying out the subbuffer from the source buffer and then copying it into the target buffer.
5
+
6
+ NOTE: I'm just making intelligent guesses here. Haven't actually profiled. It keeps the code simpler, too.
7
+
8
+ =end
9
+ module WindowBlessing
10
+ class Buffer
11
+ include Tools
12
+
13
+ attr_reader :size
14
+ attr_accessor :contents
15
+
16
+ # used as the default color when resizing
17
+ attr_accessor :bg, :fg
18
+
19
+ # color buffers. 2D arrays. Each element is a number, 0-255.
20
+ attr_accessor :fg_buffer, :bg_buffer
21
+
22
+ attr_accessor :crop_area
23
+ attr_reader :dirty_area
24
+
25
+ def Buffer.default_bg; 0; end
26
+ def Buffer.default_fg; 7; end
27
+
28
+ # init-options
29
+ # :bg_bufer => 2D array of 0-255 values
30
+ # :fg_bufer => 2D array of 0-255 values
31
+ # :contents => array of strings or string with new-lines
32
+ # fill options (will override init-options)
33
+ # :string, :bg, :fg -- see #fill
34
+ def initialize(size, options={})
35
+ @size = size
36
+
37
+ @contents = options[:contents]
38
+ @fg_buffer = options[:fg_buffer]
39
+ @bg_buffer = options[:bg_buffer]
40
+
41
+ fill options
42
+ normalize
43
+ clean
44
+ end
45
+
46
+ def each_line(&block)
47
+ @contents.zip(fg_buffer,bg_buffer).each &block
48
+ end
49
+
50
+ def fg_buffer=(fg_buffer)
51
+ @fg_buffer = fg_buffer
52
+ normalize
53
+ end
54
+
55
+ def bg_buffer=(bg_buffer)
56
+ @bg_buffer = bg_buffer
57
+ normalize
58
+ end
59
+
60
+ def contents=(contents)
61
+ @contents = contents
62
+ normalize
63
+ end
64
+
65
+ def sanitize_contents(range=0..-1)
66
+ @contents[range] = @contents[range].collect {|l| l.gsub(/[\x00-\x1f]/,'?')}
67
+ end
68
+
69
+ def normalize(range=0..-1)
70
+ @contents||=[]
71
+ @fg_buffer||=[]
72
+ @bg_buffer||=[]
73
+ @contents = @contents.gsub(/[\x00-\x09\x11-\x1f]/,'?').split("\n") if @contents.kind_of?(String)
74
+ @contents[range] = resize2d @contents[range] , size, " "
75
+ @fg_buffer[range] = resize2d @fg_buffer[range], size, Buffer.default_fg
76
+ @bg_buffer[range] = resize2d @bg_buffer[range], size, Buffer.default_bg
77
+ end
78
+
79
+ def on_dirty(&block)
80
+ @on_dirty = block
81
+ end
82
+
83
+ def crop_area
84
+ @crop_area || rect(size)
85
+ end
86
+
87
+ def cropped(area)
88
+ old_crop_area = @crop_area
89
+ @crop_area = area | crop_area
90
+ yield
91
+ self
92
+ ensure
93
+ @crop_area = old_crop_area
94
+ end
95
+
96
+ def cropped?
97
+ crop_area != rect(size)
98
+ end
99
+
100
+ def inspect
101
+ "<Buffer size:#{size.inspect}>"
102
+ end
103
+
104
+ def to_s
105
+ contents.join "\n"
106
+ end
107
+
108
+ def internal_area
109
+ rect(size)
110
+ end
111
+
112
+ def subbuffer(area)
113
+ area = internal_area | area
114
+ return buffer unless area.present?
115
+
116
+ x_range = area.x_range
117
+
118
+ buffer area.size,
119
+ :contents => subarray2d(contents,area),
120
+ :fg_buffer => subarray2d(fg_buffer,area),
121
+ :bg_buffer => subarray2d(bg_buffer,area),
122
+ :fg => fg,
123
+ :bg => bg
124
+ end
125
+
126
+ def dirty_subbuffer
127
+ @dirty_area && subbuffer(@dirty_area)
128
+ end
129
+
130
+ #########
131
+ # dirty?
132
+ #########
133
+ def dirty?
134
+ !!@dirty_area
135
+ end
136
+
137
+ def clean
138
+ @dirty_area = nil
139
+ end
140
+
141
+ def dirty(area = internal_area)
142
+ @dirty_area = (area & @dirty_area) | internal_area
143
+ @on_dirty.call if @on_dirty
144
+ @dirty_area
145
+ end
146
+
147
+ #########
148
+ # DRAWING
149
+ #########
150
+
151
+ def clear
152
+ @contents = @bg_buffer = @fg_buffer = nil
153
+ normalize
154
+ self
155
+ end
156
+
157
+ # Fills characters, foreground and/or background buffers with the specified values.
158
+ #
159
+ # If one of the buffers is not specified to be filled, it is not changed.
160
+ #
161
+ # For example, you can set the foreground color without changing the text:
162
+ #
163
+ # fill :fg => rgb_screen_color(1,0,0)
164
+ #
165
+ # options
166
+ # :area => Rectangle # only fill the specified area (intersected with the crop_area)
167
+ # :bg => background color OR 1d array of bg-color pattern - nil => don't touch bg
168
+ # :fg => foreground color OR 1d array of fg-color pattern - nil => don't touch fb
169
+ # :string => string - length 1 or more, use to fill-init @contents - nil => don't touch @contents
170
+ def fill(options = {})
171
+ area = crop_area
172
+ area = area | options[:area] if options[:area]
173
+ string = options[:string]
174
+ fg = options[:fg]
175
+ bg = options[:bg]
176
+ fg = fg.to_screen_color if fg.kind_of?(Color)
177
+ bg = bg.to_screen_color if bg.kind_of?(Color)
178
+
179
+ if area != internal_area
180
+ @contents = overlay2d(area.loc, gen_array2d(area.size, string), contents) if string
181
+ @fg_buffer = overlay2d(area.loc, gen_array2d(area.size, fg), fg_buffer) if fg
182
+ @bg_buffer = overlay2d(area.loc, gen_array2d(area.size, bg), bg_buffer) if bg
183
+ else
184
+ @contents = gen_array2d(size, string) if string
185
+ @fg_buffer = gen_array2d(size, fg) if fg
186
+ @bg_buffer = gen_array2d(size, bg) if bg
187
+ end
188
+ dirty area
189
+ self
190
+ end
191
+
192
+ # Just like #fill except all buffers are filled. Default values are used if not specified.
193
+ # options - see #fill options
194
+ def draw_rect(rectangle, options={})
195
+ fill({:area => rectangle, :string => " ", :fg => Buffer.default_fg, :bg => Buffer.default_bg}.merge options)
196
+ end
197
+
198
+ def draw_buffer(loc, buffer, source_area = nil)
199
+ source_area = (source_area || buffer.internal_area) | (crop_area - loc)
200
+ return unless source_area.present?
201
+
202
+ unless source_area == buffer.internal_area
203
+ loc += source_area.loc
204
+ buffer = buffer.subbuffer(source_area)
205
+ end
206
+
207
+ @contents = overlay2d(loc, buffer.contents, contents)
208
+ @fg_buffer = overlay2d(loc, buffer.fg_buffer, fg_buffer)
209
+ @bg_buffer = overlay2d(loc, buffer.bg_buffer, bg_buffer)
210
+
211
+ dirty rect(loc, buffer.size)
212
+ self
213
+ end
214
+
215
+ end
216
+ end
@@ -0,0 +1,29 @@
1
+ module WindowBlessing
2
+
3
+ class BufferedScreen < XtermScreen
4
+ attr_accessor :screen_buffer
5
+
6
+ def initialize
7
+ super
8
+ @screen_buffer = Buffer.new point(20,20)
9
+
10
+ event_manager.add_handler :tick do
11
+ update_from_screen_buffer
12
+ end
13
+
14
+ event_manager.add_handler :resize do |event|
15
+ @screen_buffer = Buffer.new event[:size]
16
+ @screen_buffer.dirty
17
+ end
18
+ end
19
+
20
+ def update_from_screen_buffer
21
+ if dirty_buffer = screen_buffer.dirty_subbuffer
22
+ # XtermLog.log "#{self.class}#update_from_screen_buffer() diry_area: #{screen_buffer.dirty_area}"
23
+ output.draw_buffer screen_buffer.dirty_area.loc, dirty_buffer
24
+ screen_buffer.clean
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,71 @@
1
+ module WindowBlessing
2
+ class Color < Struct.new(:r, :g, :b)
3
+ class <<self
4
+
5
+ def black; Color.new(0) end
6
+ def white; Color.new(1) end
7
+ def gray; Color.new(0.5) end
8
+
9
+ def red; Color.new(1,0,0) end
10
+ def green; Color.new(0,1,0) end
11
+ def blue; Color.new(0,0,1) end
12
+
13
+ def yellow; Color.new(1,1,0) end
14
+ def cyan; Color.new(0,1,1) end
15
+ def magenta;Color.new(1,0,1) end
16
+ end
17
+
18
+ def initialize(r=0.0, g=r, b=r)
19
+ case r
20
+ when String then self.hex=r
21
+ else super r, g, b
22
+ end
23
+ end
24
+
25
+ def br
26
+ (r + g + b) / 3.0
27
+ end
28
+
29
+ def hex=(hex)
30
+ raise "invalid hex color #{hex.inspect}" unless hex[/#?(((..)(..)(..))|((.)(.)(.)))/]
31
+ self.r = ($3 || ($7*2)).hex/255.0
32
+ self.g = ($4 || ($8*2)).hex/255.0
33
+ self.b = ($5 || ($9*2)).hex/255.0
34
+ end
35
+
36
+ def inspect; "color#{self}" end
37
+ def to_s; "(#{r},#{g},#{b})" end
38
+ def to_hex; "#%02x%02x%02x"%to_a256 end
39
+ def to_a256; [r256,g256,b256] end
40
+ def to_a; [r,g,b] end
41
+
42
+ # to xterm pallette color
43
+ def to_screen_color; rgb_screen_color(r,g,b) end
44
+
45
+ def [](i)
46
+ case i
47
+ when 0, :r then r
48
+ when 1, :g then g
49
+ when 2, :b then b
50
+ end
51
+ end
52
+
53
+ def []=(key,v)
54
+ case key
55
+ when 0, :r then self.r = v
56
+ when 1, :g then self.g = v
57
+ when 2, :b then self.b = v
58
+ end
59
+ end
60
+
61
+ def r256; (r*255).to_i end
62
+ def g256; (g*255).to_i end
63
+ def b256; (b*255).to_i end
64
+
65
+ def +(v) v.kind_of?(Color) ? Color.new(r + v.r, g + v.g, b + v.b) : Color.new(r + v, g + v, b + v) end
66
+ def -(v) v.kind_of?(Color) ? Color.new(r - v.r, g - v.g, b - v.b) : Color.new(r - v, g - v, b - v) end
67
+ def *(v) v.kind_of?(Color) ? Color.new(r * v.r, g * v.g, b * v.b) : Color.new(r * v, g * v, b * v) end
68
+ def /(v) v.kind_of?(Color) ? Color.new(r / v.r, g / v.g, b / v.b) : Color.new(r / v, g / v, b / v) end
69
+
70
+ end
71
+ end