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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/Guardfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +43 -0
- data/Rakefile +12 -0
- data/bin/buffered_screen_demo.rb +92 -0
- data/bin/color_picker_demo.rb +176 -0
- data/bin/foiled_demo.rb +27 -0
- data/bin/text_editor_demo.rb +292 -0
- data/bin/windowed_screen_demo.rb +71 -0
- data/bin/xterm_screen_demo.rb +33 -0
- data/lib/window_blessing.rb +54 -0
- data/lib/window_blessing/buffer.rb +216 -0
- data/lib/window_blessing/buffered_screen.rb +29 -0
- data/lib/window_blessing/color.rb +71 -0
- data/lib/window_blessing/constants.rb +3 -0
- data/lib/window_blessing/event_manager.rb +75 -0
- data/lib/window_blessing/event_queue.rb +20 -0
- data/lib/window_blessing/evented.rb +19 -0
- data/lib/window_blessing/evented_variable.rb +47 -0
- data/lib/window_blessing/tools.rb +124 -0
- data/lib/window_blessing/version.rb +3 -0
- data/lib/window_blessing/widgets/draggable_background.rb +13 -0
- data/lib/window_blessing/widgets/label.rb +23 -0
- data/lib/window_blessing/widgets/slider.rb +53 -0
- data/lib/window_blessing/widgets/text_field.rb +92 -0
- data/lib/window_blessing/window.rb +273 -0
- data/lib/window_blessing/windowed_screen.rb +53 -0
- data/lib/window_blessing/xterm_event_parser.rb +156 -0
- data/lib/window_blessing/xterm_input.rb +40 -0
- data/lib/window_blessing/xterm_log.rb +7 -0
- data/lib/window_blessing/xterm_output.rb +213 -0
- data/lib/window_blessing/xterm_screen.rb +109 -0
- data/lib/window_blessing/xterm_state.rb +27 -0
- data/spec/buffer_spec.rb +170 -0
- data/spec/color_spec.rb +36 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/tools_spec.rb +142 -0
- data/spec/window_spec.rb +61 -0
- data/window_blessing.gemspec +28 -0
- 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
|