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.
- 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
|