aalib-ruby 0.7.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/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
|