forked from AbleOS/ableos
inline fb_driver, update to latest hblang
This commit is contained in:
parent
19992595fc
commit
e3f7a2d455
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -444,17 +444,17 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hbbytecode"
|
name = "hbbytecode"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#641be15703153386fef1a6b083a7e00c85414df9"
|
source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#f172c332478f54839ea8b3814883ffa7ac29b5b1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hbbytecode"
|
name = "hbbytecode"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://git.ablecorp.us/ableos/holey-bytes#641be15703153386fef1a6b083a7e00c85414df9"
|
source = "git+https://git.ablecorp.us/ableos/holey-bytes#f172c332478f54839ea8b3814883ffa7ac29b5b1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hblang"
|
name = "hblang"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#641be15703153386fef1a6b083a7e00c85414df9"
|
source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#f172c332478f54839ea8b3814883ffa7ac29b5b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hbvm 0.1.0 (git+https://git.ablecorp.us/AbleOS/holey-bytes.git)",
|
"hbvm 0.1.0 (git+https://git.ablecorp.us/AbleOS/holey-bytes.git)",
|
||||||
]
|
]
|
||||||
|
@ -462,7 +462,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hbvm"
|
name = "hbvm"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#641be15703153386fef1a6b083a7e00c85414df9"
|
source = "git+https://git.ablecorp.us/AbleOS/holey-bytes.git#f172c332478f54839ea8b3814883ffa7ac29b5b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hbbytecode 0.1.0 (git+https://git.ablecorp.us/AbleOS/holey-bytes.git)",
|
"hbbytecode 0.1.0 (git+https://git.ablecorp.us/AbleOS/holey-bytes.git)",
|
||||||
]
|
]
|
||||||
|
@ -470,7 +470,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hbvm"
|
name = "hbvm"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://git.ablecorp.us/ableos/holey-bytes#641be15703153386fef1a6b083a7e00c85414df9"
|
source = "git+https://git.ablecorp.us/ableos/holey-bytes#f172c332478f54839ea8b3814883ffa7ac29b5b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hbbytecode 0.1.0 (git+https://git.ablecorp.us/ableos/holey-bytes)",
|
"hbbytecode 0.1.0 (git+https://git.ablecorp.us/ableos/holey-bytes)",
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,19 +5,19 @@ receive_message := fn(buffer_id: int, memory_map_location: ^u8, length: int): ^u
|
||||||
}
|
}
|
||||||
|
|
||||||
send_message := fn(msg: ^u8, buffer_id: int): void {
|
send_message := fn(msg: ^u8, buffer_id: int): void {
|
||||||
msg_length := string.length(msg)
|
msg_length := @inline(string.length, msg)
|
||||||
@eca(i32, 3, buffer_id, msg, msg_length)
|
@eca(i32, 3, buffer_id, msg, msg_length)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
create := fn(msg: ^u8): int {
|
create := fn(msg: ^u8): int {
|
||||||
msg_length := string.length(msg);
|
msg_length := @inline(string.length, msg);
|
||||||
*msg = 0
|
*msg = 0
|
||||||
return @eca(int, 3, 0, msg, msg_length)
|
return @eca(int, 3, 0, msg, msg_length)
|
||||||
}
|
}
|
||||||
|
|
||||||
search := fn(msg: ^u8): int {
|
search := fn(msg: ^u8): int {
|
||||||
msg_length := string.length(msg);
|
msg_length := @inline(string.length, msg);
|
||||||
*msg = 3
|
*msg = 3
|
||||||
|
|
||||||
return @eca(int, 3, 0, msg, msg_length)
|
return @eca(int, 3, 0, msg, msg_length)
|
||||||
|
|
|
@ -2,7 +2,7 @@ string := @use("rel:string.hb")
|
||||||
buffer := @use("rel:buffer.hb")
|
buffer := @use("rel:buffer.hb")
|
||||||
|
|
||||||
log := fn(message: ^u8, level: u8): void {
|
log := fn(message: ^u8, level: u8): void {
|
||||||
message_length := string.length(message);
|
message_length := @inline(string.length, message);
|
||||||
*(message + message_length) = level
|
*(message + message_length) = level
|
||||||
@eca(i32, 3, 1, message, message_length + 1)
|
@eca(i32, 3, 1, message, message_length + 1)
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,51 +1,38 @@
|
||||||
length := fn(ptr: ^u8): int {
|
length := fn(ptr: ^u8): int {
|
||||||
len := 0
|
len := 0
|
||||||
loop if *ptr == 0 break else {
|
loop if *(ptr + len) == 0 break else len += 1
|
||||||
len += 1
|
|
||||||
ptr += 1
|
|
||||||
}
|
|
||||||
return len
|
return len
|
||||||
}
|
}
|
||||||
|
|
||||||
display_int := fn(num: int, p: ^u8): ^u8 {
|
display_int := fn(num: int, p: ^u8): ^u8 {
|
||||||
i := 0
|
i := 0
|
||||||
if num == 0 {
|
if num == 0 {
|
||||||
set(p, 48)
|
*p = 48
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
loop {
|
loop if num == 0 break else {
|
||||||
if num == 0 break
|
*(p + i) = num % 10 + 48
|
||||||
set(p + i, num % 10 + 48)
|
|
||||||
num /= 10
|
num /= 10
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
reverse(p)
|
@inline(reverse, p);
|
||||||
//null terminate
|
*(p + i) = 0
|
||||||
set(p + i, 0)
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
reverse := fn(s: ^u8): void {
|
reverse := fn(s: ^u8): void {
|
||||||
//reverse a string, don't remove digits
|
//reverse a string, don't remove digits
|
||||||
len := 0
|
len := 0
|
||||||
loop {
|
loop if *(s + len) == 0 break else len += 1
|
||||||
if *(s + len) == 0 break
|
|
||||||
len += 1
|
|
||||||
}
|
|
||||||
i := 0
|
i := 0
|
||||||
j := len - 1
|
j := len - 1
|
||||||
loop {
|
temp := 0
|
||||||
if i >= j break
|
loop if i >= j break else {
|
||||||
temp := *(s + i);
|
temp = *(s + i);
|
||||||
*(s + i) = *(s + j);
|
*(s + i) = *(s + j);
|
||||||
*(s + j) = temp
|
*(s + j) = temp
|
||||||
i += 1
|
i += 1
|
||||||
j -= 1
|
j -= 1
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
set := fn(change: ^u8, new: int): void {
|
|
||||||
*change = new
|
|
||||||
return
|
|
||||||
}
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
stn := @use("../../../libraries/stn/src/lib.hb");
|
.{memory, buffer} := @use("../../../libraries/stn/src/lib.hb")
|
||||||
.{log, string, memory, buffer} := stn
|
|
||||||
|
|
||||||
main := fn(): int {
|
main := fn(): int {
|
||||||
// shuts down ableOS
|
// shuts down ableOS
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
.{draw_pixel, screenidx, Transform, Point, Rect, Buffer, FB_WIDTH} := @use("rel:lib.hb")
|
.{draw_pixel, screenidx, Transform, Point, Rect, Buffer, FB_WIDTH} := @use("rel:lib.hb");
|
||||||
ColorBGRA := @use("rel:color.hb").ColorBGRA
|
.{math} := @use("../../../libraries/stn/src/lib.hb");
|
||||||
math := @use("../../../libraries/stn/src/lib.hb").math
|
.{ColorBGRA} := @use("rel:color.hb")
|
||||||
|
|
||||||
/* draws a filled rectangle to the screen
|
/* draws a filled rectangle to the screen
|
||||||
will be optimised later */
|
will be optimised later */
|
||||||
rect_fill := fn(buffer: Buffer, pos: Point, tr: Transform, color: ColorBGRA): void {
|
rect_fill := fn(buffer: Buffer, pos: Point, tr: Transform, color: ColorBGRA): void {
|
||||||
n := 0
|
n := 0
|
||||||
loop if n == tr.height * tr.width break else {
|
loop if n == tr.height * tr.width break else {
|
||||||
*(buffer.write + screenidx(.(n % tr.width + pos.x, n / tr.width + pos.y))) = color
|
*(buffer.write + @inline(screenidx, .(n % tr.width + pos.x, n / tr.width + pos.y))) = color
|
||||||
n += 1
|
n += 1
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -22,13 +22,13 @@ rect_line := fn(buffer: Buffer, pos: Point, tr: Transform, color: ColorBGRA, thi
|
||||||
y = pos.y
|
y = pos.y
|
||||||
x = pos.x
|
x = pos.x
|
||||||
loop if y == pos.y + tr.height break else {
|
loop if y == pos.y + tr.height break else {
|
||||||
*(buffer.write + pos.x + t + FB_WIDTH * y) = color;
|
*(buffer.write + @inline(screenidx, .(pos.x + t, y))) = color;
|
||||||
*(buffer.write + pos.x + tr.width - t + FB_WIDTH * y) = color
|
*(buffer.write + @inline(screenidx, .(pos.x + tr.width - t, y))) = color
|
||||||
y += 1
|
y += 1
|
||||||
}
|
}
|
||||||
loop if x == pos.x + tr.width break else {
|
loop if x == pos.x + tr.width break else {
|
||||||
*(buffer.write + x + (pos.y + t) * FB_WIDTH) = color;
|
*(buffer.write + @inline(screenidx, .(x, pos.y + t))) = color;
|
||||||
*(buffer.write + x + (pos.y + tr.height - t) * FB_WIDTH) = color
|
*(buffer.write + @inline(screenidx, .(x, pos.y + tr.height - t))) = color
|
||||||
x += 1
|
x += 1
|
||||||
}
|
}
|
||||||
t += 1
|
t += 1
|
||||||
|
@ -49,7 +49,7 @@ line_low := fn(buffer: Buffer, p0: Point, p1: Point, color: ColorBGRA): void {
|
||||||
y := p0.y
|
y := p0.y
|
||||||
x := p0.x
|
x := p0.x
|
||||||
loop if x == p1.x break else {
|
loop if x == p1.x break else {
|
||||||
*(buffer.write + x + y * FB_WIDTH) = color
|
*(buffer.write + @inline(screenidx, .(x, y))) = color
|
||||||
if D > 0 {
|
if D > 0 {
|
||||||
y += yi
|
y += yi
|
||||||
D += 2 * (dy - dx)
|
D += 2 * (dy - dx)
|
||||||
|
@ -73,7 +73,7 @@ line_high := fn(buffer: Buffer, p0: Point, p1: Point, color: ColorBGRA): void {
|
||||||
x := p0.x
|
x := p0.x
|
||||||
y := p0.y
|
y := p0.y
|
||||||
loop if y == p1.y break else {
|
loop if y == p1.y break else {
|
||||||
*(buffer.write + x + y * FB_WIDTH) = color
|
*(buffer.write + @inline(screenidx, .(x, y))) = color
|
||||||
if D > 0 {
|
if D > 0 {
|
||||||
x += xi
|
x += xi
|
||||||
D += 2 * (dx - dy)
|
D += 2 * (dx - dy)
|
||||||
|
@ -88,17 +88,17 @@ line_high := fn(buffer: Buffer, p0: Point, p1: Point, color: ColorBGRA): void {
|
||||||
/* implementation of Bresenham's line algorithm
|
/* implementation of Bresenham's line algorithm
|
||||||
TODO: thickness, might need better math library */
|
TODO: thickness, might need better math library */
|
||||||
line := fn(buffer: Buffer, p0: Point, p1: Point, color: ColorBGRA, thickness: int): void {
|
line := fn(buffer: Buffer, p0: Point, p1: Point, color: ColorBGRA, thickness: int): void {
|
||||||
if math.abs(p1.y - p0.y) < math.abs(p1.x - p0.x) {
|
if @inline(math.abs, p1.y - p0.y) < @inline(math.abs, p1.x - p0.x) {
|
||||||
if p0.x > p1.x {
|
if p0.x > p1.x {
|
||||||
line_low(buffer, p1, p0, color)
|
@inline(line_low, buffer, p1, p0, color)
|
||||||
} else {
|
} else {
|
||||||
line_low(buffer, p0, p1, color)
|
@inline(line_low, buffer, p0, p1, color)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if p0.y > p1.y {
|
if p0.y > p1.y {
|
||||||
line_high(buffer, p1, p0, color)
|
@inline(line_high, buffer, p1, p0, color)
|
||||||
} else {
|
} else {
|
||||||
line_high(buffer, p0, p1, color)
|
@inline(line_high, buffer, p0, p1, color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
19
sysdata/programs/fb_driver/src/examples/buffers.hb
Normal file
19
sysdata/programs/fb_driver/src/examples/buffers.hb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
.{front_buffer_ptr, front_buffer_copy, get_front_buffer, Buffer} := @use("../lib.hb");
|
||||||
|
|
||||||
|
example := fn(): void {
|
||||||
|
// you can get the raw frontbuffer pointer using
|
||||||
|
raw_buffer := front_buffer_ptr
|
||||||
|
// this buffer is the one that you write individual pixels to
|
||||||
|
|
||||||
|
// you can gete the copy frontbuffer pointer using
|
||||||
|
copy_buffer := front_buffer_copy
|
||||||
|
/* this buffer is used for massive writing
|
||||||
|
operations by taking advantage of
|
||||||
|
static copying */
|
||||||
|
|
||||||
|
// you can construct a buffer like so
|
||||||
|
buffer := Buffer.{write: raw_buffer, copy: copy_buffer}
|
||||||
|
// this is the operation that get_front_buffer does
|
||||||
|
same_buffer := get_front_buffer()
|
||||||
|
return
|
||||||
|
}
|
|
@ -1,19 +0,0 @@
|
||||||
.{front_buffer_ptr, front_buffer_copy, get_front_buffer, Buffer} := @use("../lib.hb");
|
|
||||||
|
|
||||||
example := fn(): void {
|
|
||||||
// you can get the raw frontbuffer pointer using
|
|
||||||
raw_buffer := front_buffer_ptr
|
|
||||||
// this buffer is the one that you write individual pixels to
|
|
||||||
|
|
||||||
// you can gete the copy frontbuffer pointer using
|
|
||||||
copy_buffer := copy_buffer_ptr
|
|
||||||
/* this buffer is used for massive writing
|
|
||||||
operations by taking advantage of
|
|
||||||
static copying */
|
|
||||||
|
|
||||||
// you can construct a buffer like so
|
|
||||||
buffer := Buffer.{write: raw_buffer, copy: copy_buffer}
|
|
||||||
// this is the operation that get_front_buffer does
|
|
||||||
same_buffer := get_front_buffer()
|
|
||||||
return
|
|
||||||
}
|
|
17
sysdata/programs/fb_driver/src/examples/random.hb
Normal file
17
sysdata/programs/fb_driver/src/examples/random.hb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
.{clear, get_front_buffer, screenidx} := @use("../lib.hb");
|
||||||
|
.{ColorBGRA} := @use("../color.hb");
|
||||||
|
.{random} := @use("../../../../libraries/stn/src/lib.hb")
|
||||||
|
|
||||||
|
example := fn(): void {
|
||||||
|
buffer := get_front_buffer()
|
||||||
|
clear(buffer)
|
||||||
|
loop {
|
||||||
|
x := random.integer(0, 1024)
|
||||||
|
y := random.integer(0, 768)
|
||||||
|
r := random.integer(0, 255)
|
||||||
|
g := random.integer(0, 75)
|
||||||
|
b := random.integer(0, 155);
|
||||||
|
*(buffer.write + @inline(screenidx, .(x, y))) = ColorBGRA.(b, g, r, 255)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
.{memory, log, math} := @use("../../../libraries/stn/src/lib.hb");
|
.{memory, math} := @use("../../../libraries/stn/src/lib.hb");
|
||||||
.{ColorBGRA, blend} := @use("rel:color.hb")
|
.{ColorBGRA, blend} := @use("rel:color.hb")
|
||||||
|
|
||||||
FB_WIDTH := 1024
|
FB_WIDTH := 1024
|
||||||
|
@ -14,29 +14,27 @@ TOTAL_PAGES := 1 + FB_BYTES >> 12
|
||||||
Buffer := struct {write: ^ColorBGRA, copy: ^[ColorBGRA; COPY_PIXELS]}
|
Buffer := struct {write: ^ColorBGRA, copy: ^[ColorBGRA; COPY_PIXELS]}
|
||||||
Point := struct {x: int, y: int}
|
Point := struct {x: int, y: int}
|
||||||
Transform := struct {width: int, height: int}
|
Transform := struct {width: int, height: int}
|
||||||
Rect := struct {p1: Point, p2: Point}
|
|
||||||
|
|
||||||
front_buffer_ptr := @as(^ColorBGRA, @bitcast(0xFFFF8000C0000000))
|
front_buffer_ptr := @as(^ColorBGRA, @bitcast(0xFFFF8000C0000000))
|
||||||
front_buffer_copy := @as(^[ColorBGRA; COPY_PIXELS], @bitcast(front_buffer_ptr))
|
front_buffer_copy := @as(^[ColorBGRA; COPY_PIXELS], @bitcast(front_buffer_ptr))
|
||||||
|
|
||||||
get_front_buffer := fn(): Buffer {
|
get_front_buffer := fn(): Buffer {
|
||||||
// trying to return front_buffer_ptr or front_buffer_copy causes reg id leak
|
// trying to return front_buffer_ptr or front_buffer_copy causes reg id leak
|
||||||
return Buffer.{write: @as(^ColorBGRA, @bitcast(0xFFFF8000C0000000)), copy: @as(^[ColorBGRA; COPY_PIXELS], @bitcast(0xFFFF8000C0000000))}
|
buffer := Buffer.{write: front_buffer_ptr, copy: front_buffer_copy}
|
||||||
|
return buffer
|
||||||
}
|
}
|
||||||
/* this is separate to create_raw_buffer because returning a Buffer from
|
/* this is separate to create_raw_buffer because returning a Buffer from
|
||||||
create_raw_buffer causes reg id leak */
|
create_raw_buffer causes reg id leak */
|
||||||
create_buffer := fn(): Buffer {
|
create_buffer := fn(): Buffer {
|
||||||
ptr := create_raw_buffer()
|
ptr := @inline(create_raw_buffer)
|
||||||
ptr_copy := @as(^[ColorBGRA; COPY_PIXELS], @bitcast(ptr))
|
buffer := Buffer.{write: ptr, copy: @as(^[ColorBGRA; COPY_PIXELS], @bitcast(ptr))}
|
||||||
// same here, bitcasting inside the struct literal causes reg id leak
|
|
||||||
buffer := Buffer.{write: ptr, copy: ptr_copy}
|
|
||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
create_raw_buffer := fn(): ^ColorBGRA {
|
create_raw_buffer := fn(): ^ColorBGRA {
|
||||||
if TOTAL_PAGES <= 0xFF {
|
if TOTAL_PAGES <= 0xFF {
|
||||||
return @bitcast(memory.request_page(TOTAL_PAGES))
|
return @bitcast(@inline(memory.request_page, TOTAL_PAGES))
|
||||||
}
|
}
|
||||||
ptr := memory.request_page(255)
|
ptr := @inline(memory.request_page, 255)
|
||||||
remaining := TOTAL_PAGES - 0xFF
|
remaining := TOTAL_PAGES - 0xFF
|
||||||
loop if remaining <= 0 break else {
|
loop if remaining <= 0 break else {
|
||||||
if remaining < 0xFF {
|
if remaining < 0xFF {
|
||||||
|
@ -66,11 +64,11 @@ clear := fn(buffer: Buffer, color: ColorBGRA): void {
|
||||||
}
|
}
|
||||||
// only required to be called when using a back buffer. if using single-buffered rendering, do not call this.
|
// only required to be called when using a back buffer. if using single-buffered rendering, do not call this.
|
||||||
present := fn(buffer: Buffer): void {
|
present := fn(buffer: Buffer): void {
|
||||||
offset := 0
|
n := 0
|
||||||
// copy chunks of the read buffer to the front buffer
|
// copy chunks of the read buffer to the front buffer
|
||||||
loop if offset >= PARTITIONS break else {
|
loop if n >= PARTITIONS break else {
|
||||||
*(front_buffer_copy + offset) = *(buffer.copy + offset)
|
*(front_buffer_copy + n) = *(buffer.copy + n)
|
||||||
offset += 1
|
n += 1
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -85,13 +83,6 @@ composite := fn(buffer1: Buffer, buffer2: Buffer): void {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// really need to be able to inline this please - aurlex
|
|
||||||
screenidx := fn(pos: Point): int {
|
screenidx := fn(pos: Point): int {
|
||||||
return pos.x + FB_WIDTH * pos.y
|
return pos.x + FB_WIDTH * pos.y
|
||||||
}
|
|
||||||
point2rect := fn(pos: Point, tr: Transform): Rect {
|
|
||||||
return .(pos, .(pos.x + tr.x, pos.y + tr.y))
|
|
||||||
}
|
|
||||||
rect2point := fn(rect: Rect): struct {point: Point, transform: Transform} {
|
|
||||||
return .(.(0, 0), .(0, 0))
|
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
.{example} := @use("./examples/colors.hb")
|
.{example} := @use("./examples/lines.hb")
|
||||||
|
|
||||||
main := fn(): int {
|
main := fn(): int {
|
||||||
example()
|
@inline(example)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
|
@ -5,7 +5,7 @@ log_info := fn(): void {
|
||||||
if a == 0 {
|
if a == 0 {
|
||||||
} else {
|
} else {
|
||||||
msg := "XABC\0"
|
msg := "XABC\0"
|
||||||
msg_length := string.length(msg)
|
msg_length := @inline(string.length, msg)
|
||||||
@eca(void, 3, a, msg, msg_length)
|
@eca(void, 3, a, msg, msg_length)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue