window_blessing 0.0.1

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.
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