barkest_lcd 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +112 -0
- data/Rakefile +12 -0
- data/barkest_lcd.gemspec +29 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/fonts/label.png +0 -0
- data/fonts/slkscr.ttf +0 -0
- data/fonts/slkscrb.ttf +0 -0
- data/lib/barkest_lcd.rb +10 -0
- data/lib/barkest_lcd/version.rb +3 -0
- data/lib/models/error_logger.rb +21 -0
- data/lib/models/font.rb +404 -0
- data/lib/models/font/silkscr.rb +1628 -0
- data/lib/models/font/silkscrb.rb +1627 -0
- data/lib/models/hash_enum.rb +91 -0
- data/lib/models/pico_lcd_graphic.rb +184 -0
- data/lib/models/pico_lcd_graphic/display.rb +97 -0
- data/lib/models/pico_lcd_graphic/enums.rb +107 -0
- data/lib/models/pico_lcd_graphic/flasher.rb +126 -0
- data/lib/models/pico_lcd_graphic/ir.rb +31 -0
- data/lib/models/pico_lcd_graphic/key.rb +92 -0
- data/lib/models/pico_lcd_graphic/splash.rb +40 -0
- data/lib/models/pico_lcd_graphic/version.rb +40 -0
- data/lib/models/simple_graphic.rb +467 -0
- metadata +128 -0
@@ -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
|