aalib-ruby 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +55 -0
- data/ChangeLog +4 -0
- data/GPL +340 -0
- data/INSTALL +7 -0
- data/README +62 -0
- data/Rakefile +76 -0
- data/example/hello_world.rb +36 -0
- data/example/save.rb +45 -0
- data/lib/aalib.rb +1088 -0
- data/setup.rb +1585 -0
- data/spec/aalib_context_spec.rb +95 -0
- data/spec/aalib_format_spec.rb +89 -0
- data/spec/aalib_save_spec.rb +34 -0
- data/spec/aalib_savedata_spec.rb +27 -0
- data/spec/aalib_spec.rb +168 -0
- data/spec/hello_world.txt +25 -0
- data/spec/scratch/test_save +25 -0
- data/spec/spec_helper.rb +23 -0
- metadata +77 -0
data/Rakefile
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'spec/rake/spectask'
|
5
|
+
require 'fileutils'
|
6
|
+
include FileUtils
|
7
|
+
|
8
|
+
class String
|
9
|
+
def first_line
|
10
|
+
first = ''
|
11
|
+
each_line { |line| first = line; break }
|
12
|
+
first
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
NAME = "aalib-ruby"
|
17
|
+
VERS = `darcs list tags`.first_line.chomp
|
18
|
+
PKG = "#{NAME}-#{VERS}"
|
19
|
+
RDOC_OPTS = ['--quiet', '--title', 'AA-lib for Ruby', '--main', 'README', '--inline-source']
|
20
|
+
PKG_FILES = `darcs list files`.split("\n")
|
21
|
+
GEMSPEC =
|
22
|
+
Gem::Specification.new do |s|
|
23
|
+
s.name = NAME
|
24
|
+
s.version = VERS
|
25
|
+
s.platform = Gem::Platform::RUBY
|
26
|
+
s.has_rdoc = true
|
27
|
+
s.rdoc_options += RDOC_OPTS
|
28
|
+
s.extra_rdoc_files = ["README", "ChangeLog", "COPYING"]
|
29
|
+
s.summary = 'a graphics context and input library for text terminals'
|
30
|
+
s.description = s.summary
|
31
|
+
s.author = 'Patrick Mahoney'
|
32
|
+
s.email = 'pat@polycrystal.org'
|
33
|
+
s.homepage = 'http://aalib-ruby.rubyforge.org/'
|
34
|
+
s.files = PKG_FILES
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "Run the tests"
|
38
|
+
task :default => [:test]
|
39
|
+
|
40
|
+
desc "Generates tar.gz and gem packages"
|
41
|
+
task :package
|
42
|
+
|
43
|
+
task :test => [:spec]
|
44
|
+
|
45
|
+
desc "Runs all tests"
|
46
|
+
Spec::Rake::SpecTask.new do |t|
|
47
|
+
t.spec_files = FileList['spec/**/*spec.rb']
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "Compile documentation with RDoc"
|
51
|
+
task :doc => [:rdoc]
|
52
|
+
|
53
|
+
Rake::RDocTask.new do |rdoc|
|
54
|
+
rdoc.rdoc_dir = 'doc/'
|
55
|
+
rdoc.options += RDOC_OPTS
|
56
|
+
rdoc.main = "README"
|
57
|
+
rdoc.rdoc_files.add ['README', 'ChangeLog', 'COPYING', 'lib/**/*.rb']
|
58
|
+
end
|
59
|
+
|
60
|
+
Rake::GemPackageTask.new(GEMSPEC) do |p|
|
61
|
+
p.need_tar = true
|
62
|
+
p.gem_spec = GEMSPEC
|
63
|
+
end
|
64
|
+
|
65
|
+
task "lib" do
|
66
|
+
directory "lib"
|
67
|
+
end
|
68
|
+
|
69
|
+
task :install do
|
70
|
+
sh %{rake package}
|
71
|
+
sh %{sudo gem install pkg/#{NAME}-#{VERS}}
|
72
|
+
end
|
73
|
+
|
74
|
+
task :uninstall do
|
75
|
+
sh %{sudo gem uninstall #{NAME}}
|
76
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'aalib'
|
2
|
+
|
3
|
+
hp = AAlib::HardwareParams.new
|
4
|
+
rp = AAlib::RenderParams.new
|
5
|
+
|
6
|
+
AAlib.parseoptions(hp, rp) or abort AAlib.help
|
7
|
+
aa = AAlib.autoinit(hp) or abort "failed to initialize AA-lib"
|
8
|
+
begin
|
9
|
+
aa.autoinitkbd # set up keyboard support
|
10
|
+
|
11
|
+
# Fill screen with diagonal gradient
|
12
|
+
width = aa.imgwidth
|
13
|
+
height = aa.imgheight
|
14
|
+
height.times do |y|
|
15
|
+
width.times do |x|
|
16
|
+
aa.putpixel(x, y, 127*(x.to_f/width) + 127*(y.to_f/height))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
rp.randomval = 25
|
21
|
+
aa.render(rp)
|
22
|
+
|
23
|
+
msg = ' AA-lib: the ascii-art library '
|
24
|
+
blank = ' ' * msg.size
|
25
|
+
attr = AAlib::Attr::BOLD
|
26
|
+
col = (aa.scrwidth - msg.size)/2
|
27
|
+
row = aa.scrheight/2
|
28
|
+
aa.puts(col, row - 1, attr, blank)
|
29
|
+
aa.puts(col, row, attr, msg)
|
30
|
+
aa.puts(col, row + 1, attr, blank)
|
31
|
+
aa.flush
|
32
|
+
|
33
|
+
aa.getkey # wait for any key to exit
|
34
|
+
ensure
|
35
|
+
aa.close
|
36
|
+
end
|
data/example/save.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'aalib'
|
2
|
+
|
3
|
+
hp = AAlib::HardwareParams.new
|
4
|
+
rp = AAlib::RenderParams.new
|
5
|
+
|
6
|
+
AAlib.parseoptions(hp, rp) or abort AAlib.help
|
7
|
+
|
8
|
+
file = "aalib-save-demo.txt"
|
9
|
+
savedata = AAlib::SaveData.new(file, "text", AAlib::SaveFormat::USE_PAGES)
|
10
|
+
aa = AAlib.init(AAlib.save_driver, hp, savedata)
|
11
|
+
|
12
|
+
aa or abort "failed to initialize AA-lib"
|
13
|
+
|
14
|
+
begin
|
15
|
+
# Fill screen with diagonal gradient
|
16
|
+
width = aa.imgwidth
|
17
|
+
height = aa.imgheight
|
18
|
+
height.times do |y|
|
19
|
+
width.times do |x|
|
20
|
+
aa.putpixel(x, y, 127*(x.to_f/width) + 127*(y.to_f/height))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
rp.randomval = 25
|
25
|
+
aa.render(rp)
|
26
|
+
|
27
|
+
msg = ' AA-lib: the ascii-art library '
|
28
|
+
blank = ' ' * msg.size
|
29
|
+
attr = AAlib::Attr::BOLD
|
30
|
+
col = (aa.scrwidth - msg.size)/2
|
31
|
+
row = aa.scrheight/2
|
32
|
+
aa.puts(col, row - 1, attr, blank)
|
33
|
+
aa.puts(col, row, attr, msg)
|
34
|
+
aa.puts(col, row + 1, attr, blank)
|
35
|
+
|
36
|
+
if File.exists?(file)
|
37
|
+
print "File exists: \"#{file}\" Will not overwrite.\n"
|
38
|
+
else
|
39
|
+
print "Saving \"#{file}\" ... "
|
40
|
+
aa.flush # Saves file to disk
|
41
|
+
print "ok.\n"
|
42
|
+
end
|
43
|
+
ensure
|
44
|
+
aa.close
|
45
|
+
end
|
data/lib/aalib.rb
ADDED
@@ -0,0 +1,1088 @@
|
|
1
|
+
# AAlib-Ruby brings graphics to the text terminal. AAlib-Ruby provides both a
|
2
|
+
# graphics context rendered as ascii-art and keyboard and mouse input.
|
3
|
+
# AAlib-Ruby supports a number of text-only display drivers such as Curses,
|
4
|
+
# SLang, and X11.
|
5
|
+
#
|
6
|
+
# This is a DL based wrapper around the AA-lib C library. C struct alignment
|
7
|
+
# issues may cause problems on certain platforms when accessing variables of
|
8
|
+
# some objects.
|
9
|
+
#
|
10
|
+
# Author:: Patrick Mahoney (mailto:pat@polycrystal.org)
|
11
|
+
# Copyright:: Copyright (c) 2007 Patrick Mahoney
|
12
|
+
# License:: Distributes under the same terms as Ruby
|
13
|
+
#
|
14
|
+
# See README for a usage overview.
|
15
|
+
|
16
|
+
require 'dl'
|
17
|
+
|
18
|
+
module DL #:nodoc:all
|
19
|
+
class << self
|
20
|
+
# Attempts to open each library in the Array libs with DL.dlopen. Returns
|
21
|
+
# the first that is successfully opened. This is a convenience function
|
22
|
+
# for compatibility with different platforms where the dynamic library may
|
23
|
+
# be named differently.
|
24
|
+
def tryopen(libs)
|
25
|
+
dllib = nil
|
26
|
+
errs = Array.new
|
27
|
+
|
28
|
+
libs.each do |lib|
|
29
|
+
begin
|
30
|
+
dllib = DL.dlopen lib
|
31
|
+
rescue => err
|
32
|
+
errs << err
|
33
|
+
end
|
34
|
+
break if dllib
|
35
|
+
end
|
36
|
+
|
37
|
+
unless dllib
|
38
|
+
msg = errs.collect{ |e| e.message }.join('; ')
|
39
|
+
raise RuntimeError.new("failed to open library: (#{msg})")
|
40
|
+
else
|
41
|
+
dllib
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class PtrData
|
47
|
+
def string?
|
48
|
+
p self.data_type
|
49
|
+
data_type[1] == 'S'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# This module provides initialization methods and other miscellaneous methods.
|
55
|
+
|
56
|
+
module AAlib
|
57
|
+
class Error < RuntimeError; end
|
58
|
+
|
59
|
+
module Foreign #:nodoc:all
|
60
|
+
class << self
|
61
|
+
|
62
|
+
# Defines a method named sym that calls the given function from DL:Handle
|
63
|
+
# lib using the DL function signature sig. This method is modeled after
|
64
|
+
# a similar method in Rubinius' FFI.
|
65
|
+
#
|
66
|
+
def attach_function(lib, func, sym, sig)
|
67
|
+
self.class.send(:define_method, sym) do |*args|
|
68
|
+
# print "calling #{func}("
|
69
|
+
# if args
|
70
|
+
# argstr = args.collect do |a|
|
71
|
+
# if a.kind_of? Array
|
72
|
+
# a.inspect
|
73
|
+
# elsif a.kind_of? DL::PtrData
|
74
|
+
# a.class
|
75
|
+
# else
|
76
|
+
# "'#{a}'"
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
# print argstr.join(', ')
|
80
|
+
# end
|
81
|
+
# print ")\n"
|
82
|
+
lib[func, sig].call(*args)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Defines a method named _sym_ that returns the global variable _global_
|
87
|
+
# from DL:Handle lib.
|
88
|
+
#
|
89
|
+
def attach_global(lib, global, sym)
|
90
|
+
self.class.send(:define_method, sym) do
|
91
|
+
lib[global]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
LIB = DL.tryopen ['libaa.so.1', 'libaa.so', 'libaa']
|
97
|
+
|
98
|
+
# Global vars
|
99
|
+
attach_global LIB, 'aa_help', :help
|
100
|
+
attach_global LIB, 'aa_defparams', :defparams
|
101
|
+
attach_global LIB, 'aa_defrenderparams', :defrenderparams
|
102
|
+
|
103
|
+
attach_global LIB, 'aa_drivers', :drivers
|
104
|
+
attach_global LIB, 'save_d', :save_driver
|
105
|
+
attach_global LIB, 'mem_d', :mem_driver
|
106
|
+
|
107
|
+
attach_global LIB, 'aa_formats', :formats
|
108
|
+
|
109
|
+
# Functions
|
110
|
+
attach_function LIB, 'aa_parseoptions', :parseoptions, 'IPPPa'
|
111
|
+
attach_function LIB, 'aa_init', :init, 'PPPP'
|
112
|
+
attach_function LIB, 'aa_close', :close, '0P'
|
113
|
+
|
114
|
+
attach_function LIB, 'aa_autoinit', :autoinit, 'PP'
|
115
|
+
attach_function LIB, 'aa_autoinitkbd', :autoinitkbd, 'IPI'
|
116
|
+
attach_function LIB, 'aa_autoinitmouse', :autoinitmouse, 'IPI'
|
117
|
+
|
118
|
+
attach_function LIB, 'aa_image', :image, 'PP'
|
119
|
+
attach_function LIB, 'aa_text', :text, 'PP'
|
120
|
+
attach_function LIB, 'aa_attrs', :attrs, 'PP'
|
121
|
+
attach_function LIB, 'aa_currentfont', :font, 'PP'
|
122
|
+
|
123
|
+
attach_function LIB, 'aa_scrwidth', :scrwidth, 'IP'
|
124
|
+
attach_function LIB, 'aa_scrheight', :scrheight, 'IP'
|
125
|
+
attach_function LIB, 'aa_imgwidth', :imgwidth, 'IP'
|
126
|
+
attach_function LIB, 'aa_imgheight', :imgheight, 'IP'
|
127
|
+
attach_function LIB, 'aa_mmwidth', :mmwidth, 'IP'
|
128
|
+
attach_function LIB, 'aa_mmheight', :mmheight, 'IP'
|
129
|
+
|
130
|
+
attach_function LIB, 'aa_fastrender', :fastrender, '0PIIII'
|
131
|
+
attach_function LIB, 'aa_render', :render, '0PPIIII'
|
132
|
+
attach_function LIB, 'aa_flush', :flush, '0P'
|
133
|
+
|
134
|
+
attach_function LIB, 'aa_putpixel', :putpixel, '0PIII'
|
135
|
+
attach_function LIB, 'aa_puts', :puts, '0PIIIS'
|
136
|
+
|
137
|
+
attach_function LIB, 'aa_getevent', :getevent, 'IPI'
|
138
|
+
attach_function LIB, 'aa_getkey', :getkey, 'IPI'
|
139
|
+
attach_function LIB, 'aa_resize', :resize, 'IP'
|
140
|
+
attach_function LIB, 'aa_resizehandler', :resize_handler, '0PP'
|
141
|
+
|
142
|
+
attach_function LIB, 'aa_gotoxy', :gotoxy, '0PII'
|
143
|
+
attach_function LIB, 'aa_hidecursor', :hidecursor, '0P'
|
144
|
+
attach_function LIB, 'aa_showcursor', :showcursor, '0P'
|
145
|
+
attach_function LIB, 'aa_getmouse', :getmouse, '0PPPP'
|
146
|
+
attach_function LIB, 'aa_hidemouse', :hidemouse, '0P'
|
147
|
+
end
|
148
|
+
|
149
|
+
# AAlib key code
|
150
|
+
|
151
|
+
module Key
|
152
|
+
RESIZE = 258
|
153
|
+
MOUSE = 259
|
154
|
+
UP = 300
|
155
|
+
DOWN = 301
|
156
|
+
LEFT = 302
|
157
|
+
RIGHT = 303
|
158
|
+
BACKSPACE = 304
|
159
|
+
UNKNOWN = 400
|
160
|
+
RELEASE = 65536
|
161
|
+
end
|
162
|
+
|
163
|
+
# AAlib mouse events
|
164
|
+
|
165
|
+
module Mouse
|
166
|
+
BUTTON1 = 1
|
167
|
+
BUTTON2 = 2
|
168
|
+
BUTTON3 = 4
|
169
|
+
MOVEMASK = 1
|
170
|
+
PRESSMASK = 2
|
171
|
+
PRESSEDMOVEMASK = 4
|
172
|
+
ALLMASK = 7
|
173
|
+
HIDECURSOR = 8
|
174
|
+
end
|
175
|
+
|
176
|
+
SENDRELEASE = 1
|
177
|
+
KBDALLMASK = 1
|
178
|
+
|
179
|
+
# Attribute masks
|
180
|
+
|
181
|
+
module AttrMask
|
182
|
+
NORMAL = 1
|
183
|
+
DIM = 2
|
184
|
+
BOLD = 4
|
185
|
+
BOLDFONT = 8
|
186
|
+
REVERSE = 16
|
187
|
+
ALL = 128
|
188
|
+
EIGHT = 256
|
189
|
+
EXTENDED = (ALL|EIGHT)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Text attributes for AAlib#puts and other text methods.
|
193
|
+
|
194
|
+
module Attr
|
195
|
+
NORMAL = 0
|
196
|
+
DIM = 1
|
197
|
+
BOLD = 2
|
198
|
+
BOLDFONT = 3
|
199
|
+
REVERSE = 4
|
200
|
+
SPECIAL = 5
|
201
|
+
end
|
202
|
+
|
203
|
+
# Dithering modes for AAlib#render.
|
204
|
+
|
205
|
+
module Dither
|
206
|
+
NONE = 0
|
207
|
+
ERRORDISTRIB = 1
|
208
|
+
FLOYD_S = 2
|
209
|
+
DITHERTYPES = 3 # Number of supported types
|
210
|
+
end
|
211
|
+
|
212
|
+
module ArgumentChecks #:nodoc:all
|
213
|
+
class << self
|
214
|
+
def included(receiver)
|
215
|
+
receiver.extend ClassMethods
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
module ClassMethods
|
220
|
+
def check_type(arg, argname, expected)
|
221
|
+
if not arg.kind_of?(expected)
|
222
|
+
msg = "#{argname}: wrong argument type #{arg.class} "
|
223
|
+
msg += "(expected #{expected})"
|
224
|
+
raise TypeError.new(msg)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def check_hardware_params(hp)
|
229
|
+
check_type(hp, "hardware_params", AAlib::HardwareParams)
|
230
|
+
end
|
231
|
+
|
232
|
+
def check_render_params(rp)
|
233
|
+
check_type(rp, "render_params", AAlib::RenderParams)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
include ArgumentChecks #:nodoc:
|
239
|
+
|
240
|
+
class << self
|
241
|
+
|
242
|
+
# Returns the AAlib command line options help text. Note that the text is
|
243
|
+
# formatted to 74 columns.
|
244
|
+
|
245
|
+
def help
|
246
|
+
help = Foreign.help
|
247
|
+
help.struct!('S', :text)
|
248
|
+
help[:text].to_s
|
249
|
+
end
|
250
|
+
|
251
|
+
# Parse commandline options from the given Array of Strings, _argv_,
|
252
|
+
# parsing AAlib options and removing them from the array. Fills in
|
253
|
+
# _hardware_params_ and _render_params_ with appropriate values based on
|
254
|
+
# the commandline arguments. It is expected that these parameters be used
|
255
|
+
# to initialize AAlib and render graphics.
|
256
|
+
#
|
257
|
+
# Note that this function replaces the strings in the given argv with new,
|
258
|
+
# duplicate strings.
|
259
|
+
|
260
|
+
def parseoptions(hardware_params, render_params, argv=ARGV)
|
261
|
+
check_hardware_params(hardware_params)
|
262
|
+
check_render_params(render_params)
|
263
|
+
|
264
|
+
cargv = [$0, argv].flatten # C argv includes $0
|
265
|
+
|
266
|
+
cargc = DL.malloc(DL.sizeof('I'))
|
267
|
+
cargc.struct!('I', :num)
|
268
|
+
cargc[:num] = cargv.size
|
269
|
+
|
270
|
+
r,rs = Foreign.parseoptions(hardware_params, render_params,
|
271
|
+
cargc, cargv)
|
272
|
+
|
273
|
+
if r == 1 # success
|
274
|
+
rs[2].struct!('I', :num)
|
275
|
+
len = rs[2][:num]
|
276
|
+
|
277
|
+
newargv = rs[3].to_a('S', len)
|
278
|
+
newargv.shift # remove $0 that we added previously
|
279
|
+
argv.replace newargv
|
280
|
+
true
|
281
|
+
else
|
282
|
+
false
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Initializes AA-lib. Attempts to find an available output driver
|
287
|
+
# supporting the optionally specified _hardware_params_. First attempts to
|
288
|
+
# initialize the recommended drivers and then in order drivers available in
|
289
|
+
# the AAlib.drivers array.
|
290
|
+
#
|
291
|
+
# Returns an AAlib::Context on success or nil on failure.
|
292
|
+
|
293
|
+
def autoinit(hardware_params=HardwareParams::DEFAULT)
|
294
|
+
check_hardware_params(hardware_params)
|
295
|
+
|
296
|
+
ptr, garbage = Foreign.autoinit(hardware_params)
|
297
|
+
Context.new(ptr)
|
298
|
+
end
|
299
|
+
|
300
|
+
# Initializes AA-lib using the specified _driver_ from AAlib.drivers and
|
301
|
+
# optionally specified _hardware_params_. Use _driver_opts_ to pass extra
|
302
|
+
# options to a hardware driver. For example, pass an instance of
|
303
|
+
# AAlib::SaveData when using AAlib.save_driver. Note that _driver_opts_ is
|
304
|
+
# a required argument when using the save driver.
|
305
|
+
#
|
306
|
+
# Returns an AAlib::Context on success or nil on failure.
|
307
|
+
|
308
|
+
def init(driver, hardware_params=HardwareParams::DEFAULT, driver_opts=nil)
|
309
|
+
check_type(driver, "driver", AAlib::Driver)
|
310
|
+
|
311
|
+
if driver == AAlib.save_driver
|
312
|
+
if driver_opts == nil
|
313
|
+
msg = "AAlib::SaveData required as third argument when using save driver"
|
314
|
+
raise ArgumentError.new(msg)
|
315
|
+
else
|
316
|
+
check_type(driver_opts, "driver_opts", AAlib::SaveData)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
check_hardware_params(hardware_params)
|
321
|
+
|
322
|
+
ptr, garbage = Foreign.init(driver, hardware_params, driver_opts)
|
323
|
+
Context.new(ptr)
|
324
|
+
end
|
325
|
+
|
326
|
+
# Returns array of supported display drivers. See AAlib::Driver.
|
327
|
+
|
328
|
+
def drivers
|
329
|
+
array_from_null_terminated_c(Foreign.drivers, Driver)
|
330
|
+
end
|
331
|
+
|
332
|
+
# Returns array of supported save formats. See AAlib::SaveFormat.
|
333
|
+
|
334
|
+
def formats
|
335
|
+
array_from_null_terminated_c(Foreign.formats, SaveFormat)
|
336
|
+
end
|
337
|
+
|
338
|
+
# Returns an AAlib::Driver for an in-memory context for custom ascii-art
|
339
|
+
# output. should be passed to AAlib.init.
|
340
|
+
|
341
|
+
def memory_driver
|
342
|
+
AAlib::Driver.new(Foreign.mem_driver)
|
343
|
+
end
|
344
|
+
|
345
|
+
# Returns an AAlib::Driver for saving a AAlib::Context to disk in various
|
346
|
+
# formats; should be passed to AAlib.init.
|
347
|
+
|
348
|
+
def save_driver
|
349
|
+
AAlib::Driver.new(Foreign.save_driver)
|
350
|
+
end
|
351
|
+
|
352
|
+
# Returns a grey value (0-255) approximating the brightness of the given
|
353
|
+
# RGB value where _r_, _g_, and _b_ range from 0-255.
|
354
|
+
|
355
|
+
#def color_from_rgb(r, g, b)
|
356
|
+
# (r*30 + g*59 +b*11) >> 8
|
357
|
+
#end
|
358
|
+
|
359
|
+
# Converts a NULL terminated C pointer array into a Ruby array of objects
|
360
|
+
# made by talling target_class.new() on each DL::PtrData in the C array.
|
361
|
+
|
362
|
+
def array_from_null_terminated_c(ptr, target_class) #:nodoc:
|
363
|
+
targets = Array.new
|
364
|
+
loop do
|
365
|
+
ptr.struct!('P', :target)
|
366
|
+
target = ptr[:target]
|
367
|
+
break unless target
|
368
|
+
targets << target_class.new(target)
|
369
|
+
ptr += DL.sizeof('P')
|
370
|
+
end
|
371
|
+
targets
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
# Adds initialization and some other features to DL::PtrData. On
|
376
|
+
# initialization, PtrData is defined (struct!) using the given TYPE and NAMES
|
377
|
+
# that may be used alone or
|
378
|
+
# via StructAccessors.
|
379
|
+
|
380
|
+
class CPtr < DL::PtrData
|
381
|
+
include ArgumentChecks #:nodoc:
|
382
|
+
# TYPE = '' # subclasses should define this
|
383
|
+
|
384
|
+
class << self
|
385
|
+
|
386
|
+
# Allocates memory based on the class constant TYPE specified in DL style
|
387
|
+
# returning an uninitialized CPtr object.
|
388
|
+
|
389
|
+
def new_ptr
|
390
|
+
malloc(DL.sizeof(const_get(:TYPE)))
|
391
|
+
end
|
392
|
+
|
393
|
+
private
|
394
|
+
|
395
|
+
def struct_field(sym, writable=false)
|
396
|
+
define_method(sym) do
|
397
|
+
self[sym]
|
398
|
+
end
|
399
|
+
|
400
|
+
if writable
|
401
|
+
define_method((sym.to_s + '=').to_sym) do |val|
|
402
|
+
self[sym] = val
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
def struct_reader(*syms)
|
408
|
+
syms.each { |sym| struct_field(sym) }
|
409
|
+
end
|
410
|
+
|
411
|
+
def struct_accessor(*syms)
|
412
|
+
syms.each { |sym| struct_field(sym, true) }
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
# Initializes a CPtr based on the TYPE and NAMES definitions of the current
|
417
|
+
# class. If a subclass of CPtr defines the NAMES constant, then _with_ptr_
|
418
|
+
# will be initialized using DL::PtrData#struct! with the defined TYPE and
|
419
|
+
# NAMES so that members may be accessed using CPtr#[] with string or symbol
|
420
|
+
# names.
|
421
|
+
#
|
422
|
+
# If no _with_ptr_ is given, a new pointer is allocated using the
|
423
|
+
# CPtr.new_ptr or the new_ptr method of a subclass of CPtr.
|
424
|
+
|
425
|
+
def initialize(with_ptr=nil)
|
426
|
+
with_ptr ||= self.class.new_ptr
|
427
|
+
super(with_ptr)
|
428
|
+
if self.class.const_defined? :NAMES
|
429
|
+
struct! self.class.const_get(:TYPE), *(self.class.const_get(:NAMES))
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
# When called with integral arguments, returns data from the pointer
|
434
|
+
# similar to String#[]. If passed one _int_, returns the integer value of
|
435
|
+
# the character stored at that index. If passed two _ints_, returns the
|
436
|
+
# substring of length _len_ starting at position _key_.
|
437
|
+
#
|
438
|
+
# When called with a Symbol or String, returns a DL::PtrData of the denoted
|
439
|
+
# member. If a subclass defines TYPE and NAMES and the type of the member
|
440
|
+
# is String, it will be returned as a String using DL::PtrData#to_s.
|
441
|
+
|
442
|
+
def [](key, len=0)
|
443
|
+
if key.kind_of? Integer
|
444
|
+
if len == 0
|
445
|
+
# We'd like this to behave more like String#[] where a single numeric
|
446
|
+
# index gets you the char type integer at the given index. Ruby
|
447
|
+
# 1.9's DL possible fixes the need for this.
|
448
|
+
super(key, 1)[0]
|
449
|
+
else
|
450
|
+
super(key, len)
|
451
|
+
end
|
452
|
+
elsif (names = self.class.const_get(:NAMES)) &&
|
453
|
+
(type = self.class.const_get(:TYPE)) &&
|
454
|
+
(type[(names.index(key)), 1] == 'S')
|
455
|
+
super(key).to_s
|
456
|
+
else
|
457
|
+
super(key)
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
# Identical to DL::PtrData#[]= except that it raises an error on frozen
|
462
|
+
# objects.
|
463
|
+
#
|
464
|
+
# With two _ints_, _key_ and _val_, sets the value at _key_ to _val_. With
|
465
|
+
# one _int_ and one _str_ sets the characters begining at _key_ to those of
|
466
|
+
# _str_. With two _ints_, _key_ and _num_, and one _str_, copies at most
|
467
|
+
# _num_ characters of _str_, extending with zeroes if _len_ is greater than
|
468
|
+
# the length of _str_.
|
469
|
+
#
|
470
|
+
# When called with a Symbol or String, sets the value of the denoted
|
471
|
+
# member. A subclass must have defined TYPE and NAMES so that DL::PtrData
|
472
|
+
# knows how to handle each member.
|
473
|
+
|
474
|
+
def []=(key, num, val=nil)
|
475
|
+
if frozen?
|
476
|
+
@assignment_will_raise_error = true
|
477
|
+
elsif val
|
478
|
+
super(key, num, val)
|
479
|
+
else
|
480
|
+
val = num
|
481
|
+
super(key, val)
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
def inspect #:nodoc:
|
486
|
+
if self.class.const_defined? :NAMES
|
487
|
+
values = Array.new
|
488
|
+
names = self.class.const_get(:NAMES)
|
489
|
+
names.each_with_index do |name, i|
|
490
|
+
label = name.to_s
|
491
|
+
value = self[name]
|
492
|
+
values << "%s=%s" % [label, value.inspect]
|
493
|
+
end
|
494
|
+
|
495
|
+
"#<%s:%x %s>" % [self.class, ~object_id, values.join(' ')]
|
496
|
+
else
|
497
|
+
super
|
498
|
+
end
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
class RenderParams < CPtr
|
503
|
+
TYPE = 'IIFIII' #:nodoc:
|
504
|
+
NAMES = [:bright, :contrast, :gamma, :dither, :inversion, :randomval] #:nodoc:
|
505
|
+
|
506
|
+
struct_accessor *NAMES
|
507
|
+
|
508
|
+
# Notably defined before we redifine initialize() to take no arguments
|
509
|
+
DEFAULT = new(Foreign.defrenderparams).freeze
|
510
|
+
|
511
|
+
def initialize
|
512
|
+
super
|
513
|
+
copy_from(DEFAULT)
|
514
|
+
end
|
515
|
+
|
516
|
+
def copy_from(other)
|
517
|
+
NAMES.each do |get|
|
518
|
+
set = (get.to_s + '=').to_sym
|
519
|
+
self.send(set, other.send(get))
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
class HardwareParams < CPtr
|
525
|
+
TYPE = 'P' + 'I'*11 + 'D'*2 #:nodoc:
|
526
|
+
NAMES = [:font, :supported, :minwidth, :minheight,
|
527
|
+
:maxwidth, :maxheight, :recwidth, :recheight,
|
528
|
+
:mmwidth, :mmheight, :width, :height, :dimmul, :boldmul] #:nodoc:
|
529
|
+
|
530
|
+
struct_accessor *NAMES
|
531
|
+
|
532
|
+
# Notably defined before we redifine initialize()
|
533
|
+
DEFAULT = new(Foreign.defparams).freeze #:nodoc:
|
534
|
+
|
535
|
+
def initialize(with_ptr=nil)
|
536
|
+
if with_ptr
|
537
|
+
super(with_ptr)
|
538
|
+
else
|
539
|
+
super()
|
540
|
+
copy_from(DEFAULT)
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
def copy_from(other)
|
545
|
+
NAMES.each do |get|
|
546
|
+
set = (get.to_s + '=').to_sym
|
547
|
+
self.send(set, other.send(get))
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
def font
|
552
|
+
self[:font] ? Font.new(self[:font]) : nil
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
class Font < CPtr
|
557
|
+
TYPE = 'PISS' #:nodoc:
|
558
|
+
NAMES = [:data, :height, :name, :shortname] #:nodoc:
|
559
|
+
struct_reader *(NAMES - [:data])
|
560
|
+
end
|
561
|
+
|
562
|
+
# Read-only output driver. If you wish to pass a driver option to
|
563
|
+
# AAlib::Context.init, select a driver from the Array returned by
|
564
|
+
# AAlib.drivers.
|
565
|
+
|
566
|
+
class Driver < CPtr
|
567
|
+
TYPE = 'SS' #:nodoc:
|
568
|
+
NAMES = [:shortname, :name] #:nodoc:
|
569
|
+
struct_reader :shortname, :name
|
570
|
+
|
571
|
+
def initialize(ptr) #:nodoc:
|
572
|
+
super(ptr)
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
# A graphics context opened using a particular driver along with any keyboard
|
577
|
+
# and mouse drivers. Two buffers are maintained: the image buffer and the
|
578
|
+
# screen or text buffer. AAlib::Context#putpixel is the primary method to
|
579
|
+
# draw on the image buffer. AAlib::Context#render converts the image buffer
|
580
|
+
# to text, writing the result to the text buffer. Text may be written
|
581
|
+
# directly to the text buffer using AAlib::Context#puts and similar. Note
|
582
|
+
# that text written this way may be overwritten by AAlib::Context#render.
|
583
|
+
# Finally, AAlib::Context#flush writes the text buffer to the screen.
|
584
|
+
|
585
|
+
class Context < CPtr
|
586
|
+
TYPE = 'PPP' + HardwareParams::TYPE*2 + 'IIIIPPPPPPIIIIIIIPPPP' #:nodoc:
|
587
|
+
|
588
|
+
NAMES = [:driver, :kbddriver, :mousedriver, #:nodoc:
|
589
|
+
HardwareParams::NAMES.collect {|sym| ("hardware_params_" + sym.to_s).to_sym },
|
590
|
+
HardwareParams::NAMES.collect {|sym| ("driver_hardware_params_" + sym.to_s).to_sym },
|
591
|
+
:mulx, :muly, :imgwidth, :imgheight,
|
592
|
+
:imagebuffer, :textbuffer, :attrbuffer, :table,
|
593
|
+
:filltable, :parameters, :cursorx, :cursory, :cursorstate,
|
594
|
+
:mousex, :mousey, :buttons, :mousemode, :resizehandler,
|
595
|
+
:driverdata, :kbddriverdata, :mousedriverdata].flatten
|
596
|
+
|
597
|
+
# Returns the ratio of the widths of the image and text buffers.
|
598
|
+
def mulx; end
|
599
|
+
|
600
|
+
# Returns the ratio of the heights of the image and text buffers.
|
601
|
+
def muly; end
|
602
|
+
|
603
|
+
struct_reader :kbddriver, :parameters, :mulx, :muly
|
604
|
+
|
605
|
+
# Closes the graphics context and resets the terminal if necessary. Also
|
606
|
+
# performs any keyboard and mouse uninitialization.
|
607
|
+
|
608
|
+
def close
|
609
|
+
Foreign.close(self)
|
610
|
+
nil
|
611
|
+
end
|
612
|
+
|
613
|
+
# Initializes the keyboard for capture of key press events. _Release_
|
614
|
+
# determines whether or not one is interested in key release events as
|
615
|
+
# well. Note that key releases are unavailable when using a text terminal
|
616
|
+
# (curses or slang drivers).
|
617
|
+
|
618
|
+
def autoinitkbd(release=false)
|
619
|
+
mode = 0
|
620
|
+
mode |= SENDRELEASE if release
|
621
|
+
r,rs = Foreign.autoinitkbd(self, mode)
|
622
|
+
unless r == 1
|
623
|
+
raise Error.new("failed to initialize keyboard")
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
def autoinitmouse
|
628
|
+
mode = 0
|
629
|
+
r,rs = Foreign.autoinitmouse(self, mode)
|
630
|
+
unless r == 1
|
631
|
+
raise Error.new("failed to initialize mouse")
|
632
|
+
end
|
633
|
+
end
|
634
|
+
|
635
|
+
# Returns a HardwareParams object representing the context's current
|
636
|
+
# requested hardware params.
|
637
|
+
|
638
|
+
def hardware_params
|
639
|
+
HardwareParams.new(self + DL.sizeof('PPP'))
|
640
|
+
end
|
641
|
+
|
642
|
+
# Returns a HardwareParams object representing the context's current
|
643
|
+
# hardware params as reported by the display driver.
|
644
|
+
|
645
|
+
def driver_hardware_params
|
646
|
+
HardwareParams.new(self + DL.sizeof('PPP') + DL.sizeof(HardwareParams::TYPE))
|
647
|
+
end
|
648
|
+
|
649
|
+
# Width of the screen and text buffer in characters.
|
650
|
+
|
651
|
+
def scrwidth
|
652
|
+
Foreign.scrwidth(self)[0]
|
653
|
+
end
|
654
|
+
|
655
|
+
# Height of the screen and text buffer in characters.
|
656
|
+
|
657
|
+
def scrheight
|
658
|
+
Foreign.scrheight(self)[0]
|
659
|
+
end
|
660
|
+
|
661
|
+
# Width of the image buffer in pixels. Note pixels are non-square. Use
|
662
|
+
# mmwidth and mmheight to get the size of the screen in millimeters.
|
663
|
+
|
664
|
+
def imgwidth
|
665
|
+
Foreign.imgwidth(self)[0]
|
666
|
+
end
|
667
|
+
|
668
|
+
# Height of the image buffer in pixels. Note pixels are non-square. Use
|
669
|
+
# mmwidth and mmheight to get the size of the screen in millimeters.
|
670
|
+
|
671
|
+
def imgheight
|
672
|
+
Foreign.imgheight(self)[0]
|
673
|
+
end
|
674
|
+
|
675
|
+
# Width of the screen in millimeters. Note this gives incorrect values on
|
676
|
+
# text terminals where AAlib cannot get screen size or font information.
|
677
|
+
|
678
|
+
def mmwidth
|
679
|
+
Foreign.mmwidth(self)[0]
|
680
|
+
end
|
681
|
+
|
682
|
+
# Height of the screen in millimeters. Note this gives incorrect values on
|
683
|
+
# text terminals where AAlib cannot get screen size or font information.
|
684
|
+
|
685
|
+
def mmheight
|
686
|
+
Foreign.mmheight(self)[0]
|
687
|
+
end
|
688
|
+
|
689
|
+
# Returns the editable image buffer as AAlib::CPtr (imgheight rows of
|
690
|
+
# imgwidth chars packed into a single string)
|
691
|
+
|
692
|
+
def image
|
693
|
+
CPtr.new(Foreign.image(self)[0])
|
694
|
+
end
|
695
|
+
|
696
|
+
# Returns the editable text buffer as AAlib::CPtr (scrheight rows of
|
697
|
+
# scrwidth characters packed in single string). Writes to this buffer will
|
698
|
+
# appear on screen after a #flush operation.
|
699
|
+
|
700
|
+
def text
|
701
|
+
CPtr.new(Foreign.text(self)[0])
|
702
|
+
end
|
703
|
+
|
704
|
+
# Returns the editable attr buffer as AAlib::CPtr (scrheight rows of
|
705
|
+
# scrwidth chars packed in a single string). Writes to this buffer will
|
706
|
+
# modify text attributes on screen after a #flush operation.
|
707
|
+
|
708
|
+
def attrs
|
709
|
+
CPtr.new(Foreign.attrs(self)[0])
|
710
|
+
end
|
711
|
+
|
712
|
+
# Returns the current font.
|
713
|
+
|
714
|
+
def font
|
715
|
+
Font.new(Foreign.font(self)[0])
|
716
|
+
end
|
717
|
+
|
718
|
+
# Converts image buffer coordinates into text buffer coordinates.
|
719
|
+
|
720
|
+
def img2scr(imgx, imgy)
|
721
|
+
[imgx/mulx, imgy/muly]
|
722
|
+
end
|
723
|
+
|
724
|
+
# Converts the image buffer to ASCII on the text buffer. _Renderparams_
|
725
|
+
# should be a RenderParams specifying the parameters. Screen coordinates
|
726
|
+
# (x1, y1) and (x2, y2) define the column and row of the top left and
|
727
|
+
# bottom right corners of the area to be rendered, respectively.
|
728
|
+
#
|
729
|
+
# Note that #flush must be called to flush to the screen.
|
730
|
+
#
|
731
|
+
# The first call may take some time as the rendering tables are produced.
|
732
|
+
|
733
|
+
def render(render_params, x1=0, y1=0, x2=scrwidth, y2=scrheight)
|
734
|
+
AAlib.check_render_params(render_params)
|
735
|
+
|
736
|
+
Foreign.render(self, render_params, x1, y1, x2, y2)
|
737
|
+
self
|
738
|
+
end
|
739
|
+
|
740
|
+
# See #render. This method performs a faster render using the default
|
741
|
+
# rendering parameters. Screen coordinates (x1, y1) and (x2, y2)
|
742
|
+
# define the column and row of the top left and bottom right corners of the
|
743
|
+
# area to be rendered, respectively.
|
744
|
+
|
745
|
+
def fastrender(x1=0, y1=0, x2=scrwidth, y2=scrheight)
|
746
|
+
Foreign.fastrender(self, x1, y1, x2, y2)
|
747
|
+
self
|
748
|
+
end
|
749
|
+
|
750
|
+
# Flushes the text buffer to the screen, making it visible. Note that the
|
751
|
+
# image buffer is transformed to ascii-art and written to the text buffer
|
752
|
+
# during #render.
|
753
|
+
#
|
754
|
+
# When using the save driver, this method causes the file to be written
|
755
|
+
# out.
|
756
|
+
|
757
|
+
def flush
|
758
|
+
Foreign.flush(self)
|
759
|
+
self
|
760
|
+
end
|
761
|
+
|
762
|
+
# Draw a pixel at _x_,_y_ in image coords in the desired _color_ (0-255).
|
763
|
+
|
764
|
+
def putpixel(x, y, color)
|
765
|
+
Foreign.putpixel(self, x, y, color)
|
766
|
+
self
|
767
|
+
end
|
768
|
+
|
769
|
+
# Draw starting at _x_,_y_ in image coords using the pixels from _str_.
|
770
|
+
|
771
|
+
def putpixels(x, y, str)
|
772
|
+
pos = x + y*imgwidth
|
773
|
+
|
774
|
+
# Sanity check
|
775
|
+
if pos > imgwidth*imgheight
|
776
|
+
pos = imgwidth*imgheight
|
777
|
+
elsif pos < 0
|
778
|
+
pos = 0
|
779
|
+
end
|
780
|
+
if pos+str.length > imgwidth*imgheight
|
781
|
+
str = str[0, imgwidth*imgheight - pos]
|
782
|
+
end
|
783
|
+
|
784
|
+
image[pos] = str
|
785
|
+
self
|
786
|
+
end
|
787
|
+
|
788
|
+
# Returns pixels from the imagebuffer from _x_,_y_ of length _len_ in
|
789
|
+
# image coords and returns them as a String.
|
790
|
+
|
791
|
+
def pixels(x, y, len)
|
792
|
+
pos = x + y*imgwidth
|
793
|
+
|
794
|
+
# Sanity check
|
795
|
+
if pos > imgwidth*imgheight
|
796
|
+
pos = imgwidth*imgheight
|
797
|
+
elsif pos < 0
|
798
|
+
pos = 0
|
799
|
+
end
|
800
|
+
if pos+len > imgwidth*imgheight
|
801
|
+
len = imgwidth*imgheight - pos
|
802
|
+
end
|
803
|
+
|
804
|
+
image[pos, len]
|
805
|
+
end
|
806
|
+
|
807
|
+
# Writes string _str_ at _x_,_y_ in screen coords with attribute _attr_, a
|
808
|
+
# AAlib::Attr.
|
809
|
+
|
810
|
+
def puts(x, y, attr, str)
|
811
|
+
Foreign.puts(self, x, y, attr, str)
|
812
|
+
self
|
813
|
+
end
|
814
|
+
|
815
|
+
# Text in the box bounded by x1, y1 and x2, y2 in screen coords
|
816
|
+
# is converted to an approximation of pixels on the image buffer to
|
817
|
+
# facilitate image based manipulation of text.
|
818
|
+
#
|
819
|
+
# Note that a render operation must have been performed prior to calling
|
820
|
+
# this method; this method will not work unless the rendering tables have
|
821
|
+
# been calculated.
|
822
|
+
#
|
823
|
+
# This is a hack based on the backconvert function used in BB, the AA-lib
|
824
|
+
# demo. This is the technique used by the pager at the end of BB that
|
825
|
+
# fades text in and out when paging. It may not work in different versions
|
826
|
+
# of AA-lib as it depends on some details not specified in the API.
|
827
|
+
#
|
828
|
+
# See also #pixels_from_text
|
829
|
+
|
830
|
+
def backconvert(x1=0, y1=0, x2=scrwidth, y2=scrheight)
|
831
|
+
sw = scrwidth
|
832
|
+
size = DL.sizeof('I'*5)
|
833
|
+
parameters = self[:parameters]
|
834
|
+
|
835
|
+
(y1...y2).each do |y| # "..." excludes last value; ".." includes it
|
836
|
+
(x1...x2).each do |x|
|
837
|
+
pos = x + y*sw
|
838
|
+
# n a unique int representing a char + attributes (ascii values range
|
839
|
+
# from 0-255; adding 256*a where a is 0,1,2,3,4 (no SPECIAL) makes 5
|
840
|
+
# pixel value for each ascii char depending on the attribute) and we
|
841
|
+
# somehow use that backwards in a lookup table
|
842
|
+
attr = attrs[pos, 1][0]
|
843
|
+
attr = Attr::REVERSE if attr == Attr::SPECIAL # Can't backconv SPECIAL
|
844
|
+
n = text[pos, 1][0] + 256*attr
|
845
|
+
|
846
|
+
p = parameters[n*size, size].unpack('I*')
|
847
|
+
|
848
|
+
putpixel(x*2, y*2, p[1])
|
849
|
+
putpixel(x*2+1, y*2, p[0])
|
850
|
+
putpixel(x*2, y*2+1, p[3])
|
851
|
+
putpixel(x*2+1, y*2+1, p[2])
|
852
|
+
end
|
853
|
+
end
|
854
|
+
self
|
855
|
+
end
|
856
|
+
|
857
|
+
# Returns two pixel-strings that, when drawn on the image buffer one above
|
858
|
+
# the other, will render to a rough approximation of the text of length
|
859
|
+
# _len_ currently on the text buffer at _x_, _y_ in screen coords. In
|
860
|
+
# other words, this converts text into pixels that may be painted and
|
861
|
+
# otherwise manipulated on the image buffer.
|
862
|
+
#
|
863
|
+
# Note that a render operation must have been performed prior to calling
|
864
|
+
# this method; this method will not work unless the rendering tables have
|
865
|
+
# been calculated.
|
866
|
+
#
|
867
|
+
# See also #backconvert
|
868
|
+
|
869
|
+
def pixels_from_text(x, y, len)
|
870
|
+
pos = x + y*scrwidth
|
871
|
+
|
872
|
+
# Sanity check
|
873
|
+
if pos > scrwidth*scrheight
|
874
|
+
pos = scrwidth*scrheight
|
875
|
+
elsif pos < 0
|
876
|
+
pos = 0
|
877
|
+
end
|
878
|
+
if pos+len > scrwidth*scrheight
|
879
|
+
len = scrwidth*scrheight - pos
|
880
|
+
end
|
881
|
+
|
882
|
+
sw = scrwidth
|
883
|
+
size = DL.sizeof('I'*5)
|
884
|
+
parameters = self[:parameters]
|
885
|
+
|
886
|
+
bytes1 = " "*len*2
|
887
|
+
bytes2 = " "*len*2
|
888
|
+
|
889
|
+
len.times do |i|
|
890
|
+
pos = i + x + y*sw
|
891
|
+
# n is a unique int representing a char with attributes (ascii values range
|
892
|
+
# from 0-255; adding 256*a where a is 0,1,2,3,4 (no SPECIAL) makes 5
|
893
|
+
# pixel value for each ascii char, one for each possible attribute) and we
|
894
|
+
# somehow use that backwards in a lookup table
|
895
|
+
attr = attrs[pos, 1][0]
|
896
|
+
attr = Attr::REVERSE if attr == Attr::SPECIAL # Can't backconv SPECIAL
|
897
|
+
n = text[pos, 1][0] + 256*attr
|
898
|
+
|
899
|
+
p = parameters[n*size, size].unpack('I*')
|
900
|
+
|
901
|
+
# These indices make no sense I know, but that's how aalib has them. I
|
902
|
+
# can't decipher the reason, but I'm sure it's a good one.
|
903
|
+
bytes1[i*2] = p[1]
|
904
|
+
bytes1[i*2+1] = p[0]
|
905
|
+
bytes2[i*2] = p[3]
|
906
|
+
bytes2[i*2+1] = p[2]
|
907
|
+
end
|
908
|
+
|
909
|
+
[bytes1, bytes2]
|
910
|
+
end
|
911
|
+
|
912
|
+
# Register a block to handle screen resize events. The block will be
|
913
|
+
# called with the Context when the screen size is changed and should
|
914
|
+
# perform any redrawing necessary.
|
915
|
+
|
916
|
+
def handle_resize
|
917
|
+
handler = DL.callback('0P') do |ptr|
|
918
|
+
resize
|
919
|
+
yield self
|
920
|
+
end
|
921
|
+
Foreign.resize_handler(self, handler)
|
922
|
+
end
|
923
|
+
|
924
|
+
# Gets the next event. If _wait_ is true, then #getevent will wait until
|
925
|
+
# an event occurs, otherwise it will return immediately.
|
926
|
+
|
927
|
+
def getevent(wait=true)
|
928
|
+
check_kbd_init
|
929
|
+
cwait = wait ? 1 : 0
|
930
|
+
r,rs = Foreign.getevent(self, cwait)
|
931
|
+
r
|
932
|
+
end
|
933
|
+
|
934
|
+
# Gets the next key event. If _wait_ is true, then #getevent will wait
|
935
|
+
# until an event occurs, otherwise it will return immediately.
|
936
|
+
|
937
|
+
def getkey(wait=true)
|
938
|
+
check_kbd_init
|
939
|
+
cwait = wait ? 1 : 0
|
940
|
+
r,rs = Foreign.getkey(self, cwait)
|
941
|
+
r
|
942
|
+
end
|
943
|
+
|
944
|
+
# Moves the hardware cursor (if any) to position _x_, _y_ in screen coords.
|
945
|
+
# To see the effect, #flush must be called.
|
946
|
+
|
947
|
+
def gotoxy(x, y)
|
948
|
+
Foreign.gotoxy(self, x, y)
|
949
|
+
self
|
950
|
+
end
|
951
|
+
|
952
|
+
# Hides the hardware cursor (if any). Returns self.
|
953
|
+
|
954
|
+
def hidecursor
|
955
|
+
Foreign.hidecursor(self)
|
956
|
+
self
|
957
|
+
end
|
958
|
+
|
959
|
+
# Shoes the hardware cursor (if any). Returns self.
|
960
|
+
|
961
|
+
def showcursor
|
962
|
+
Foreign.showcursor(self)
|
963
|
+
self
|
964
|
+
end
|
965
|
+
|
966
|
+
# Returns an array of three values: the x,y location of the mouse in screen
|
967
|
+
# coords and the button mask of the mouse.
|
968
|
+
|
969
|
+
def getmouse
|
970
|
+
Foreign.getmouse(self, x, y, b)
|
971
|
+
self
|
972
|
+
end
|
973
|
+
|
974
|
+
# Hides the mouse pointer (if any). Returns self.
|
975
|
+
|
976
|
+
def hidemouse
|
977
|
+
Foreign.hidemouse(self)
|
978
|
+
self
|
979
|
+
end
|
980
|
+
|
981
|
+
# Resizes the image and text buffers and performs any other necessary
|
982
|
+
# updates after a resize event. This is automaically called prior to
|
983
|
+
# running any resize handler.
|
984
|
+
|
985
|
+
def resize
|
986
|
+
Foreign.resize(self)
|
987
|
+
self
|
988
|
+
end
|
989
|
+
|
990
|
+
# Copies the image buffer from _other_ to the image buffer of _self_. Both
|
991
|
+
# image buffers should be the same size.
|
992
|
+
|
993
|
+
def copy_image_from(other)
|
994
|
+
imgheight.times do |row|
|
995
|
+
image[row*imgwidth] = other.image[other.imgwidth*row, imgwidth]
|
996
|
+
end
|
997
|
+
end
|
998
|
+
|
999
|
+
private
|
1000
|
+
|
1001
|
+
def check_kbd_init
|
1002
|
+
unless kbddriver
|
1003
|
+
raise Error.new("cannot get events with uninitialized keyboard")
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
# Output format used by AAlib.save
|
1009
|
+
|
1010
|
+
class SaveFormat < CPtr
|
1011
|
+
TYPE = 'IIIIIIPSS' #:nodoc:
|
1012
|
+
NAMES = [:width, :height, :pagewidth, :pageheight,
|
1013
|
+
:flags, :supported, :font, :formatname, :extension] #:nodoc:
|
1014
|
+
|
1015
|
+
# Flag to enable multiple pages
|
1016
|
+
USE_PAGES = 1
|
1017
|
+
# Flag to use normal spaces (?)
|
1018
|
+
NORMAL_SPACES = 8
|
1019
|
+
|
1020
|
+
# Returns the name of the format
|
1021
|
+
def formatname; end
|
1022
|
+
|
1023
|
+
# Returns the file extension including the dot.
|
1024
|
+
def extension; end
|
1025
|
+
|
1026
|
+
struct_accessor :width, :height, :pagewidth, :pageheight
|
1027
|
+
struct_accessor :flags, :supported
|
1028
|
+
struct_reader :formatname, :extension
|
1029
|
+
|
1030
|
+
class << self
|
1031
|
+
# Searches available save formats and returns the first that includes
|
1032
|
+
# _pattern_, a Regexp or String. Raises exception if none found.
|
1033
|
+
|
1034
|
+
def find(pattern)
|
1035
|
+
if pattern.kind_of? Regexp
|
1036
|
+
regexp = pattern
|
1037
|
+
else
|
1038
|
+
regexp = /#{pattern}/i
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
AAlib.formats.each do |format|
|
1042
|
+
return format if format.formatname =~ regexp
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
raise AAlib::Error.new("Could not find output format matching #{pattern}")
|
1046
|
+
end
|
1047
|
+
end
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
# Encapsulation of data required to save a file to disk.
|
1051
|
+
|
1052
|
+
class SaveData < CPtr
|
1053
|
+
TYPE = 'SPP' #:nodoc:
|
1054
|
+
NAMES = [:name, :format, :file] #:nodoc:
|
1055
|
+
|
1056
|
+
# Returns the file name that will be used for saving.
|
1057
|
+
def name; end
|
1058
|
+
|
1059
|
+
struct_accessor :name, :format
|
1060
|
+
|
1061
|
+
# Initializes a new SaveData to save to file _name_ with SaveFormat
|
1062
|
+
# _format_. Passing a string _format_ uses the format returned by
|
1063
|
+
# AAlib::SaveFormat.find. See AAlib::SaveFormat for options set with
|
1064
|
+
# flags; multiple flags should be logically OR'ed.
|
1065
|
+
#
|
1066
|
+
# The AA-lib docs claim that file extensions are added automatically, but
|
1067
|
+
# this appears to be false.
|
1068
|
+
|
1069
|
+
def initialize(name, format, formatflags=0)
|
1070
|
+
super()
|
1071
|
+
|
1072
|
+
self.name = name
|
1073
|
+
|
1074
|
+
if format.kind_of? AAlib::SaveFormat
|
1075
|
+
self.format = format
|
1076
|
+
else
|
1077
|
+
self.format = AAlib::SaveFormat.find(format)
|
1078
|
+
end
|
1079
|
+
|
1080
|
+
self.format.flags = formatflags
|
1081
|
+
end
|
1082
|
+
|
1083
|
+
# Returns the AAlib::SaveFormat that will be used for saving
|
1084
|
+
def format
|
1085
|
+
SaveFormat.new(self[:format])
|
1086
|
+
end
|
1087
|
+
end
|
1088
|
+
end
|