barkest_lcd 0.4.0

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.
@@ -0,0 +1,126 @@
1
+
2
+ BarkestLcd::PicoLcdGraphic.class_eval do
3
+
4
+ init_hook :init_flasher
5
+
6
+
7
+ ##
8
+ # Delay for commands.
9
+ COMMAND_DELAY = 0x64
10
+
11
+
12
+ ##
13
+ # Gets the current mode of the device.
14
+ attr_reader :mode
15
+
16
+
17
+ ##
18
+ # Is the device currently functioning as a flasher?
19
+ def is_flasher?
20
+ (mode == :flasher)
21
+ end
22
+
23
+
24
+ ##
25
+ # Switch between keyboard and flasher mode.
26
+ def switch_mode
27
+ next_mode = (mode == :keyboard) ? :flasher : :keyboard
28
+ mode = :switching
29
+
30
+ if next_mode == :flasher
31
+ write [ HID_REPORT.EXIT_KEYBOARD, timeout & 0xFF, (timeout >> 8) & 0xFF ]
32
+ else
33
+ write [ HID_REPORT.EXIT_FLASHER, timeout & 0xFF, (timeout >> 8) & 0xFF ]
34
+ end
35
+
36
+ loop_while { mode == :switching }
37
+
38
+ self
39
+ end
40
+
41
+
42
+ private
43
+
44
+
45
+ def init_flasher(_)
46
+ @mode = :keyboard
47
+
48
+ input_hook(HID_REPORT.EXIT_FLASHER) do |_,_,data|
49
+ data = data.getbyte(0)
50
+ if data == 0
51
+ @mode = :keyboard
52
+ HIDAPI.debug 'switch to KEYBOARD mode'
53
+ else
54
+ @mode = :unknown
55
+ log_error data, 'HID_REPORT.EXIT_FLASHER failed'
56
+ end
57
+ end
58
+
59
+ input_hook(HID_REPORT.EXIT_KEYBOARD) do |_,_,data|
60
+ data = data.getbyte(0)
61
+ if data == 0
62
+ @mode = :flasher
63
+ HIDAPI.debug 'switched to FLASHER mode'
64
+ else
65
+ @mode = :unknown
66
+ log_error data, 'HID_REPORT.EXIT_KEYBOARD failed'
67
+ end
68
+ end
69
+ end
70
+
71
+
72
+ def flash_is_enabled?(type)
73
+ if is_flasher?
74
+ return (FLASH_TYPE.keys.include?(type) || FLASH_TYPE.values.include?(type))
75
+ end
76
+ false
77
+ end
78
+
79
+ def get_flash_write_message(type)
80
+ if is_flasher?
81
+ return case type
82
+ when :CODE_MEMORY, FLASH_TYPE.CODE_MEMORY
83
+ FLASH_REPORT.WRITE_MEMORY
84
+
85
+ when :CODE_SPLASH, FLASH_TYPE.CODE_SPLASH
86
+ KEYBD_REPORT.WRITE_MEMORY
87
+
88
+ when :EPROM_EXTERNAL, FLASH_TYPE.EPROM_EXTERNAL
89
+ OUT_REPORT.EXT_EE_WRITE
90
+
91
+ when :EPROM_INTERNAL, FLASH_TYPE.EPROM_INTERNAL
92
+ OUT_REPORT.INT_EE_WRITE
93
+
94
+ else
95
+ 0
96
+ end
97
+ end
98
+ 0
99
+ end
100
+
101
+ def get_flash_read_message(type)
102
+ if is_flasher?
103
+ return case type
104
+ when :CODE_MEMORY, FLASH_TYPE.CODE_MEMORY
105
+ FLASH_REPORT.READ_MEMORY
106
+
107
+ when :CODE_SPLASH, FLASH_TYPE.CODE_SPLASH
108
+ KEYBD_REPORT.READ_MEMORY
109
+
110
+ when :EPROM_EXTERNAL, FLASH_TYPE.EPROM_EXTERNAL
111
+ OUT_REPORT.EXT_EE_READ
112
+
113
+ when :EPROM_INTERNAL, FLASH_TYPE.EPROM_INTERNAL
114
+ OUT_REPORT.INT_EE_READ
115
+
116
+ else
117
+ 0
118
+ end
119
+ end
120
+ 0
121
+ end
122
+
123
+
124
+
125
+
126
+ end
@@ -0,0 +1,31 @@
1
+ BarkestLcd::PicoLcdGraphic.class_eval do
2
+
3
+ init_hook :init_ir
4
+
5
+ ##
6
+ # Sets the code to run when IR data is received.
7
+ #
8
+ # Yields the bytes received as a string.
9
+ def on_ir_data(&block)
10
+ raise ArgumentError, 'Missing block.' unless block_given?
11
+ @on_ir_data = block
12
+ self
13
+ end
14
+
15
+ private
16
+
17
+ def init_ir(_)
18
+ @on_ir_data = nil
19
+ input_hook IN_REPORT.IR_DATA do |_, _, data|
20
+ process_ir_data data
21
+ end
22
+ end
23
+
24
+ def process_ir_data(data)
25
+ HIDAPI.debug "IR data: #{data.inspect}"
26
+ if @on_ir_data
27
+ @on_ir_data.call(data)
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,92 @@
1
+ BarkestLcd::PicoLcdGraphic.class_eval do
2
+
3
+ init_hook :init_key
4
+
5
+
6
+ ##
7
+ # Sets the code to run when a key is pressed down.
8
+ #
9
+ # Yields the key number as a single byte.
10
+ def on_key_down(&block)
11
+ raise ArgumentError, 'Missing block.' unless block_given?
12
+ @on_key_down = block
13
+ self
14
+ end
15
+
16
+
17
+ ##
18
+ # Sets the code to run when a key is released.
19
+ #
20
+ # Yields the key number as a single byte.
21
+ def on_key_up(&block)
22
+ raise ArgumentError, 'Missing block.' unless block_given?
23
+ @on_key_up = block
24
+ self
25
+ end
26
+
27
+
28
+ ##
29
+ # Gets the state of a specific key.
30
+ def key_state(key)
31
+ return false unless key
32
+ return false if key <= 0
33
+ return false if @keys.length <= key
34
+ @keys[key]
35
+ end
36
+
37
+
38
+ private
39
+
40
+
41
+ def init_key(_)
42
+ @keys = []
43
+ @on_key_up = @on_key_down = nil
44
+
45
+ # Hook the keyboard and IR data events.
46
+ input_hook IN_REPORT.KEY_STATE do |_, _, data|
47
+ if data.length < 2
48
+ log_error 2, 'not enough data for IN_REPORT.KEY_STATE'
49
+ else
50
+ process_key_state data
51
+ end
52
+ end
53
+ end
54
+
55
+
56
+ def process_key_state(data)
57
+ key1 = data.length >= 1 ? (data.getbyte(0) & 0xFF) : 0
58
+ key2 = data.length >= 2 ? (data.getbyte(1) & 0xFF) : 0
59
+
60
+ # make sure the array is big enough to represent the largest reported key.
61
+ max = (key1 < key2 ? key2 : key1) + 1
62
+ if @keys.length < max
63
+ @keys += [nil] * (max - @keys.length)
64
+ end
65
+
66
+ HIDAPI.debug "Pressed keys: #{key1} #{key2}"
67
+
68
+ # go through the array and process changes.
69
+ @keys.each_with_index do |state,index|
70
+ unless index == 0
71
+ if state && key1 != index && key2 != index
72
+ # key was pressed but is not one of the currently pressed keys.
73
+ if @on_key_up
74
+ @on_key_up.call(index)
75
+ end
76
+ @keys[index] = false
77
+ end
78
+ if key1 == index || key2 == index
79
+ unless state
80
+ # key was not pressed before but is one of the currently pressed keys.
81
+ if @on_key_down
82
+ @on_key_down.call(index)
83
+ end
84
+ @keys[index] = true
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ end
92
+
@@ -0,0 +1,40 @@
1
+ BarkestLcd::PicoLcdGraphic.class_eval do
2
+
3
+ init_hook :init_splash
4
+
5
+ ##
6
+ # Gets the size for the splash storage.
7
+ def splash_size(refresh = false)
8
+ @splash_size = nil if refresh
9
+
10
+ if @splash_size.nil?
11
+ write [ HID_REPORT.GET_MAX_STX_SIZE ]
12
+ loop_while { @splash_size.nil? }
13
+ end
14
+
15
+ { size: @splash_size, max: @splash_max_size }
16
+ end
17
+
18
+
19
+
20
+ private
21
+
22
+ def init_splash(_)
23
+ @splash_size = nil
24
+ @splash_max_size = nil
25
+
26
+ input_hook(HID_REPORT.GET_MAX_STX_SIZE) do |_,_,data|
27
+ if data.length < 4
28
+ @splash_size = 0
29
+ @splash_max_size = 0
30
+ log_error 2, 'not enough data for HID_REPORT.GET_MAX_STX_SIZE'
31
+ else
32
+ @splash_max_size = ((data.getbyte(1) << 8) & 0xFF00) | (data.getbyte(0) & 0xFF)
33
+ @splash_size = ((data.getbyte(3) << 8) & 0xFF00) | (data.getbyte(2) & 0xFF)
34
+ HIDAPI.debug "splash size=#{@splash_size}, max=#{@splash_max_size}"
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,40 @@
1
+ BarkestLcd::PicoLcdGraphic.class_eval do
2
+
3
+ init_hook :init_version
4
+
5
+ ##
6
+ # Gets the version of this device.
7
+ def version(refresh = false)
8
+ @version = nil if refresh
9
+
10
+ unless @version
11
+ @version = []
12
+ write [ HID_REPORT.GET_VERSION_1 ]
13
+ loop_while { @version.empty? }
14
+ end
15
+
16
+ @version
17
+ end
18
+
19
+ private
20
+
21
+ def process_version(type, data)
22
+ if data.length < 2
23
+ log_error 2, "not enough data for HID_REPORT.#{HID_REPORT.key(type)}"
24
+ @version[0] = 0
25
+ @version[1] = 0
26
+ else
27
+ @version[0] = data.getbyte(1)
28
+ @version[1] = data.getbyte(0)
29
+ end
30
+ end
31
+
32
+ def init_version(_)
33
+ @version = nil
34
+ input_hook(HID_REPORT.GET_VERSION_1) do |_,type,data|
35
+ process_version(type, data)
36
+ end
37
+ end
38
+
39
+
40
+ end
@@ -0,0 +1,467 @@
1
+ require 'models/font'
2
+
3
+ module BarkestLcd
4
+ ##
5
+ # Adds some simple graphics capabilities to a model.
6
+ module SimpleGraphic
7
+
8
+ ##
9
+ # An error occurring when an invalid 'x' or 'y' coordinate is specified.
10
+ InvalidPosition = Class.new(StandardError)
11
+
12
+ public
13
+
14
+ ##
15
+ # Gets the width of the graphic object.
16
+ def width
17
+ @graphic_width ||= 0
18
+ end
19
+
20
+ ##
21
+ # Gets the height of the graphic object.
22
+ def height
23
+ @graphic_height ||= 0
24
+ end
25
+
26
+ ##
27
+ # Gets a snapshot of the graphic data.
28
+ def graphic_data_snapshot(x = nil, y = nil, w = nil, h = nil)
29
+ if x.nil? && y.nil? && w.nil? && h.nil?
30
+ return @graphic_data.map{|row| row.dup.freeze}.freeze
31
+ end
32
+
33
+ x ||= 0
34
+ y ||= 0
35
+ w ||= width - x
36
+ h ||= height - y
37
+
38
+ return [] if h < 1
39
+ return [ [] * h ] if w < 1
40
+
41
+ ret = []
42
+ row = [ false ] * w
43
+ h.times { ret << row.dup }
44
+
45
+ y2 = y + h - 1
46
+ x2 = x + w - 1
47
+ (y..y2).each do |source_y|
48
+ if source_y >= 0 && source_y < height
49
+ dest_y = source_y - y
50
+ dest_row = ret[dest_y]
51
+ source_row = @graphic_data[source_y]
52
+ (x..x2).each do |source_x|
53
+ if source_x >= 0 && source_x < width
54
+ dest_x = source_x - x
55
+ dest_row[dest_x] = source_row[source_x]
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ ret.map{|r| r.freeze}.freeze
62
+ end
63
+
64
+ ##
65
+ # Is the graphic object dirty?
66
+ def dirty?
67
+ @graphic_dirty
68
+ end
69
+
70
+ ##
71
+ # Low level function to set a single bit.
72
+ def set_bit(x, y, bit = true)
73
+ raise BarkestLcd::SimpleGraphic::InvalidPosition, "'x' must be between 0 and #{width - 1}" if x < 0 || x >= width
74
+ raise BarkestLcd::SimpleGraphic::InvalidPosition, "'y' must be between 0 and #{height - 1}" if y < 0 || y >= height
75
+ unless @graphic_data[y][x] == bit
76
+ @graphic_data[y][x] = bit
77
+ @graphic_dirty = true
78
+ end
79
+ self
80
+ end
81
+
82
+ ##
83
+ # Low level function to get a single bit.
84
+ def get_bit(x, y)
85
+ return false if x < 0 || x >= width
86
+ return false if y < 0 || y >= height
87
+ @graphic_data[y][x]
88
+ end
89
+
90
+ ##
91
+ # Clears the screen.
92
+ def clear(bit = false)
93
+ row_data = [bit] * width
94
+ (0...height).each do |y|
95
+ @graphic_data[y] = row_data.dup
96
+ end
97
+ @graphic_dirty = true
98
+ self
99
+ end
100
+
101
+ ##
102
+ # Draws a horizontal line.
103
+ def draw_hline(y, start_x, end_x, bit = true)
104
+ raise BarkestLcd::SimpleGraphic::InvalidPosition, "'y' must be between 0 and #{height - 1}" if y < 0 || y >= height
105
+ row = @graphic_data[y]
106
+ (start_x..end_x).each do |x|
107
+ if x >= 0 && x < width
108
+ unless row[x] == bit
109
+ row[x] = bit
110
+ @graphic_dirty = true
111
+ end
112
+ end
113
+ end
114
+ self
115
+ end
116
+
117
+ ##
118
+ # Draws a vertical line.
119
+ def draw_vline(x, start_y, end_y, bit = true)
120
+ raise BarkestLcd::SimpleGraphic::InvalidPosition, "'x' must be between 0 and #{width - 1}" if x < 0 || x >= width
121
+ (start_y..end_y).each do |y|
122
+ if y >= 0 && y < height
123
+ unless @graphic_data[y][x] == bit
124
+ @graphic_data[y][x] = bit
125
+ @graphic_dirty = true
126
+ end
127
+ end
128
+ end
129
+ self
130
+ end
131
+
132
+ ##
133
+ # Draws a line.
134
+ def draw_line(start_x, start_y, end_x, end_y, bit = true)
135
+ if start_y == end_y
136
+ draw_hline(start_y, start_x, end_x, bit)
137
+ elsif start_x == end_x
138
+ draw_vline(start_x, start_y, end_y, bit)
139
+ else
140
+ # slope
141
+ m = (end_y - start_y).to_f / (end_x - start_x).to_f
142
+
143
+ # and y_offset
144
+ b = start_y - (m * start_x)
145
+
146
+ (start_x..end_x).each do |x|
147
+ # simple rounding, add 0.5 and trim to an integer.
148
+ x = x.to_i
149
+ y = ((m * x) + b + 0.5).to_i
150
+ if x >= 0 && x < width && y >= 0 && y < height
151
+ unless @graphic_data[y][x] == bit
152
+ @graphic_data[y][x] = bit
153
+ @graphic_dirty = true
154
+ end
155
+ end
156
+ end
157
+ end
158
+ self
159
+ end
160
+
161
+ ##
162
+ # Draws a rectangle.
163
+ def draw_rect(x, y, w, h, bit = true)
164
+ raise ArgumentError, '\'w\' must be at least 1' if w < 1
165
+ raise ArgumentError, '\'h\' must be at least 1' if h < 1
166
+ x2 = x + w - 1
167
+ y2 = y + h - 1
168
+ draw_hline(y, x, x2, bit) if y >= 0 && y <= height
169
+ draw_hline(y2, x, x2, bit) if y2 >= 0 && y2 <= height
170
+ draw_vline(x, y, y2, bit) if x >= 0 && x <= width
171
+ draw_vline(x2, y, y2, bit) if x2 >= 0 && x2 <= width
172
+ self
173
+ end
174
+
175
+ ##
176
+ # Draws a filled rectangle.
177
+ def fill_rect(x, y, w, h, bit = true)
178
+ raise ArgumentError, '\'w\' must be at least 1' if w < 1
179
+ raise ArgumentError, '\'h\' must be at least 1' if h < 1
180
+ x2 = x + w - 1
181
+ y2 = y + h - 1
182
+ (y..y2).each do |dest_y|
183
+ if dest_y >= 0 && dest_y < height
184
+ dest_row = @graphic_data[dest_y]
185
+ (x..x2).each do |dest_x|
186
+ if dest_x >= 0 && dest_x < width
187
+ unless dest_row[dest_x] == bit
188
+ dest_row[dest_x] = bit
189
+ @graphic_dirty = true
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ self
196
+ end
197
+
198
+ ##
199
+ # Copies data directly to the graphic.
200
+ #
201
+ # The +data+ parameter is special and can follow one of two paths.
202
+ #
203
+ # If no block is provided, then +data+ must be a two dimensional array of boolean or integers.
204
+ #
205
+ # If a block is provided, then +data+ is passed to the block as the third parameter. If +data+ is nil
206
+ # then the third parameter is nil. The block will receive the current x and y offset as the first two
207
+ # parameters. The block must return a boolean value, true to set a pixel or false to clear it. It can
208
+ # also return nil to leave a pixel as is.
209
+ #
210
+ # :yields: +offset_x+, +offset_y+, and +data+ to the block which must return a boolean.
211
+ #
212
+ # blit(10, 10, 20, 5, my_data) do |offset_x, offset_y, data|
213
+ # # offset_x will be in the range (0...20)
214
+ # # offset_y will be in the range (0...5)
215
+ # data.is_pixel_set?(offset_x, offset_y)
216
+ # end
217
+ #
218
+ def blit(x, y, w, h, data = nil, &block)
219
+ raise ArgumentError, 'data is required unless a block is provided' if data.nil? && !block_given?
220
+ raise ArgumentError, '\'w\' must be at least 1' if w < 1
221
+ raise ArgumentError, '\'h\' must be at least 1' if h < 1
222
+
223
+ x2 = x + w - 1
224
+ y2 = y + h - 1
225
+
226
+ # The default block simply looks up the bit in the data and returns true if the bit is true or 1.
227
+ unless block_given?
228
+ block = Proc.new do |b_x, b_y, b_data|
229
+ b_bit = b_data[b_y][b_x]
230
+ b_bit == true || b_bit == 1
231
+ end
232
+ end
233
+
234
+ (y..y2).each do |dest_y|
235
+ if dest_y >= 0 && dest_y < height
236
+ dest_row = @graphic_data[dest_y]
237
+ (x..x2).each do |dest_x|
238
+ if dest_x >= 0 && dest_x < width
239
+ offset_x = dest_x - x
240
+ offset_y = dest_y - y
241
+ bit = block.call(offset_x, offset_y, data)
242
+ unless bit.nil?
243
+ unless dest_row[dest_x] == bit
244
+ dest_row[dest_x] = bit
245
+ @graphic_dirty = true
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
252
+
253
+ self
254
+ end
255
+
256
+ ##
257
+ # Attribute used to store the horizontal text offset after the last call to draw_text.
258
+ attr_accessor :text_offset_left
259
+
260
+ ##
261
+ # Attribute used to store the vertical text offset after the last call to draw_text.
262
+ attr_accessor :text_offset_bottom
263
+
264
+ ##
265
+ # Draws a string of text to the graphic.
266
+ #
267
+ # The text will not wrap.
268
+ #
269
+ # Options:
270
+ # * x - The horizontal position for the text. Defaults to +text_offset_left+.
271
+ # * y - The vertical position for the text. Defaults to aligning the bottom with +text_offset_bottom+.
272
+ # * bold - Defaults to false.
273
+ # * bit - Defaults to true.
274
+ def draw_text(text, options = {})
275
+
276
+ if text.include?("\n")
277
+ if options[:y]
278
+ self.text_offset_bottom = options[:y]
279
+ else
280
+ font = options[:bold] ? BarkestLcd::Font.bold : BarkestLcd::Font.regular
281
+ self.text_offset_bottom = text_offset_bottom ? (text_offset_bottom - font.height) : 0
282
+ end
283
+ options[:x] ||= (text_offset_left || 0)
284
+ text.split("\n").each do |line|
285
+ draw_text(line, options.merge( { y: text_offset_bottom }))
286
+ end
287
+ return self
288
+ end
289
+
290
+ x = options.delete(:x)
291
+ y = options.delete(:y)
292
+
293
+ bold = options.delete(:bold)
294
+ bold = false if bold.nil?
295
+ bit = options.delete(:bit)
296
+ bit = true if bit.nil?
297
+ char_spacing = options.delete(:char_spacing) || -1
298
+
299
+ font = bold ? BarkestLcd::Font.bold : BarkestLcd::Font.regular
300
+
301
+ max_h = font.height
302
+
303
+ x ||= text_offset_left || 0
304
+
305
+ # we'll be aligning the bottoms of the glyphs.
306
+ if y
307
+ bottom = y + max_h
308
+ else
309
+ bottom = text_offset_bottom || max_h
310
+ end
311
+
312
+ # store the bottom offset and left offset for future use.
313
+ self.text_offset_bottom = bottom
314
+ self.text_offset_left = x
315
+
316
+ text = text.to_s
317
+ return self if text == ''
318
+
319
+ glyphs = font.glyphs(text)
320
+
321
+ glyphs.each do |glyph|
322
+ if x > width # no need to continue.
323
+ # update the left offset.
324
+ self.text_offset_left = x
325
+ return self
326
+ end
327
+ if glyph.width > 0 && glyph.height > 0
328
+ y = bottom - glyph.height
329
+ blit(x, y, glyph.width, glyph.height, glyph.data) { |_x, _y, _data| _data[_y][_x] ? bit : nil }
330
+ end
331
+ x += glyph.width > 0 ? (glyph.width + char_spacing) : 0
332
+ end
333
+
334
+ # update the left offset.
335
+ self.text_offset_left = x
336
+
337
+ self
338
+ end
339
+
340
+ ##
341
+ # Draws a string of text to a confined region of the graphic.
342
+ #
343
+ # Options:
344
+ # * bold - Defaults to false.
345
+ # * bit - Defaults to true.
346
+ # * align - Can be :left, :center, or :right. Defaults to :left.
347
+ # * border - Defaults to false.
348
+ # * fill - Defaults to false.
349
+ #
350
+ def draw_text_box(text, x, y, w, h, options = {})
351
+ bold = options.delete(:bold)
352
+ bold = false if bold.nil?
353
+ bit = options.delete(:bit)
354
+ bit = true if bit.nil?
355
+ align = options.delete(:align)
356
+ align = :left if align.nil?
357
+ border = options.delete(:border)
358
+ border = false if border.nil?
359
+ fill = options.delete(:fill)
360
+ fill = false if fill.nil?
361
+ char_spacing = options.delete(:char_spacing) || -1
362
+
363
+ raise ArgumentError, '\'w\' must be at least 1' if w < 1
364
+ raise ArgumentError, '\'h\' must be at least 1' if h < 1
365
+
366
+ if fill
367
+ fill_rect(x, y, w, h, !bit)
368
+ end
369
+
370
+ if border && w > 2
371
+ x2 = x + w - 1
372
+ draw_vline(x, y, y + h - 1, bit) if x >= 0 && x < width
373
+ draw_vline(x2, y, y + h - 1, bit) if x2 >= 0 && x2 < width
374
+ w -= 2
375
+ x += 1
376
+ end
377
+
378
+ if border && h > 2
379
+ y2 = y + h - 1
380
+ draw_hline(y, x, x + w - 1, bit) if y >= 0 && y < height
381
+ draw_hline(y2, x, x + w - 1, bit) if y2 >= 0 && y2 < height
382
+ h -= 2
383
+ y += 1
384
+ end
385
+
386
+ text = text.to_s
387
+ return self if text == ''
388
+
389
+ # We'll use an anonymous class to buffer the text box.
390
+ box = Class.new do
391
+ include BarkestLcd::SimpleGraphic
392
+ def initialize(w,h)
393
+ init_graphic w, h
394
+ end
395
+ end.new(w, h)
396
+
397
+ # copy the current contents of the area into the box.
398
+ box.blit(0, 0, w, h, graphic_data_snapshot(x, y, w, h))
399
+
400
+ font = bold ? BarkestLcd::Font.bold : BarkestLcd::Font.regular
401
+
402
+ # measure the text and split it into lines, grab the lines, ignore the size.
403
+ text_lines = font.measure(text, w)[2]
404
+
405
+ text_lines.each do |line|
406
+ lx = 0
407
+ if align == :center
408
+ lw = font.measure(line)[0]
409
+ lx = (w - lw) / 2
410
+ elsif align == :right
411
+ lw = font.measure(line)[0]
412
+ lx = (w - lw)
413
+ end
414
+ # each line uses the bottom offset from the previous line as the top of the current line.
415
+ box.draw_text(line, x: lx, y: box.text_offset_bottom, bold: bold, bit: bit, char_spacing: char_spacing)
416
+ break if box.text_offset_bottom > h
417
+ end
418
+
419
+ # copy the contents of the box back into the current graphic.
420
+ blit(x, y, w, h, box.graphic_data_snapshot)
421
+
422
+ self
423
+ end
424
+
425
+ protected
426
+
427
+ def init_graphic(width, height)
428
+ @graphic_width = width < 4 ? 4 : width
429
+ @graphic_height = height < 4 ? 4 : height
430
+ row_data = [false] * width
431
+ @graphic_data = []
432
+ @graphic_height.times { @graphic_data << row_data.dup }
433
+ @graphic_dirty = true
434
+ @graphic_clean_data = nil
435
+ end
436
+
437
+ def clear_dirty
438
+ @graphic_dirty = false
439
+ @graphic_clean_data = []
440
+ @graphic_height.times { |line| @graphic_clean_data << @graphic_data[line].dup }
441
+ end
442
+
443
+ def dirty_rect?(x, y, w, h)
444
+ raise InvalidPosition, "'x' must be between 0 and #{width - 1}" if x < 0 || x >= width
445
+ raise InvalidPosition, "'y' must be between 0 and #{height - 1}" if y < 0 || y >= height
446
+ raise InvalidPosition, "'w' cannot be less than 1" if w < 1
447
+ raise InvalidPosition, "'h' cannot be less than 1" if h < 1
448
+ x2 = x + w - 1
449
+ y2 = y + h - 1
450
+ raise InvalidPosition, "'w' must not be greater than #{width} - 'x'" if x2 >= width
451
+ raise InvalidPosition, "'h' must not be greater than #{height} - 'y'" if y2 >= height
452
+
453
+ return true unless @graphic_clean_data
454
+
455
+ (y..y2).each do |yy|
456
+ clean_row = @graphic_clean_data[yy]
457
+ test_row = @graphic_data[yy]
458
+ (x..x2).each do |xx|
459
+ return true unless test_row[xx] == clean_row[xx]
460
+ end
461
+ end
462
+
463
+ false
464
+ end
465
+
466
+ end
467
+ end