Custom rendering almost works
This commit is contained in:
		
							
								
								
									
										10
									
								
								libs/core/enums.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								libs/core/enums.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -6,10 +6,12 @@
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    declare enum Draw {
 | 
			
		||||
    Normal = 0,
 | 
			
		||||
    Clear = (0x0004),  // DRAW_OPT_CLEAR_PIXELS
 | 
			
		||||
    Xor = (0x0018),  // DRAW_OPT_LOGICAL_XOR
 | 
			
		||||
    Fill = (0x0020),  // DRAW_OPT_FILL_SHAPE
 | 
			
		||||
    Normal = 0x00,
 | 
			
		||||
    Clear = 0x01,
 | 
			
		||||
    Xor = 0x02,
 | 
			
		||||
    Fill = 0x04,
 | 
			
		||||
    Transparent = 0x08,
 | 
			
		||||
    Double = 0x10,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,22 @@
 | 
			
		||||
* Drawing modes
 | 
			
		||||
*/
 | 
			
		||||
enum class Draw {
 | 
			
		||||
    Normal = 0, // set pixels to black, no fill
 | 
			
		||||
    Clear = DRAW_OPT_CLEAR_PIXELS,
 | 
			
		||||
    Xor = DRAW_OPT_LOGICAL_XOR,
 | 
			
		||||
    Fill = DRAW_OPT_FILL_SHAPE,
 | 
			
		||||
    Normal = 0x00, // set pixels to black, no fill
 | 
			
		||||
    Clear = 0x01,
 | 
			
		||||
    Xor = 0x02,
 | 
			
		||||
    Fill = 0x04,
 | 
			
		||||
    Transparent = 0x08,
 | 
			
		||||
    Double = 0x10,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
inline bool operator&(Draw a, Draw b) {
 | 
			
		||||
    return ((int)a & (int)b) != 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline Draw operator|(Draw a, Draw b) {
 | 
			
		||||
    return (Draw)((int)a | (int)b);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum class ScreenFont {
 | 
			
		||||
    Normal = FONTTYPE_NORMAL,
 | 
			
		||||
    Small = FONTTYPE_SMALL,
 | 
			
		||||
@@ -30,7 +40,6 @@ namespace screen {
 | 
			
		||||
 | 
			
		||||
static const uint8_t pixmap[] = {0x00, 0xE0, 0x1C, 0xFC, 0x03, 0xE3, 0x1F, 0xFF};
 | 
			
		||||
static uint8_t bitBuffer[ROW_SIZE * LCD_HEIGHT];
 | 
			
		||||
static uint8_t *mappedFrameBuffer;
 | 
			
		||||
static bool dirty;
 | 
			
		||||
 | 
			
		||||
static void bitBufferToFrameBuffer(uint8_t *bitBuffer, uint8_t *fb) {
 | 
			
		||||
@@ -53,7 +62,7 @@ static void bitBufferToFrameBuffer(uint8_t *bitBuffer, uint8_t *fb) {
 | 
			
		||||
        pixels = *bitBuffer++ << 0;
 | 
			
		||||
        pixels |= *bitBuffer++ << 8;
 | 
			
		||||
 | 
			
		||||
        bitBuffer += ROW_SIZE - 26;
 | 
			
		||||
        bitBuffer += ROW_SIZE - 23;
 | 
			
		||||
 | 
			
		||||
        int m = 4;
 | 
			
		||||
        while (m--) {
 | 
			
		||||
@@ -63,12 +72,9 @@ static void bitBufferToFrameBuffer(uint8_t *bitBuffer, uint8_t *fb) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void updateLCD() {
 | 
			
		||||
    bitBufferToFrameBuffer(bitBuffer, mappedFrameBuffer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define OFF(x, y) (((y) << 5) + ((x) >> 3))
 | 
			
		||||
#define MASK(x, y) (1 << ((x)&7))
 | 
			
		||||
#define PIX2BYTES(x) (((x) + 7) >> 3)
 | 
			
		||||
 | 
			
		||||
static inline void applyMask(int off, int mask, Draw mode) {
 | 
			
		||||
    if (mode & Draw::Clear)
 | 
			
		||||
@@ -85,24 +91,24 @@ void _setPixel(int x, int y, Draw mode) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void blitLineCore(int x, int y, int w, uint8_t *data, Draw mode) {
 | 
			
		||||
    if (y < 0 || y >= LMS.LCD_HEIGHT)
 | 
			
		||||
    if (y < 0 || y >= LCD_HEIGHT)
 | 
			
		||||
        return;
 | 
			
		||||
    if (x + w <= 0)
 | 
			
		||||
        return;
 | 
			
		||||
    if (x >= LMS.LCD_WIDTH)
 | 
			
		||||
    if (x >= LCD_WIDTH)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    int shift = x & 7;
 | 
			
		||||
    int off = OFF(x, y);
 | 
			
		||||
    int off0 = OFF(0, y);
 | 
			
		||||
    int off1 = OFF(LMS.LCD_WIDTH - 1, y);
 | 
			
		||||
    int off1 = OFF(LCD_WIDTH - 1, y);
 | 
			
		||||
    int x1 = x + w;
 | 
			
		||||
    int prev = 0;
 | 
			
		||||
 | 
			
		||||
    while (x < x1 - 8) {
 | 
			
		||||
        int curr = *data++ << shift;
 | 
			
		||||
        if (off0 <= off && off <= off1)
 | 
			
		||||
            applyMask(off, curr | prev);
 | 
			
		||||
            applyMask(off, curr | prev, mode);
 | 
			
		||||
        off++;
 | 
			
		||||
        prev = curr >> 8;
 | 
			
		||||
        x += 8;
 | 
			
		||||
@@ -112,8 +118,10 @@ void blitLineCore(int x, int y, int w, uint8_t *data, Draw mode) {
 | 
			
		||||
    if (left > 0) {
 | 
			
		||||
        int curr = *data << shift;
 | 
			
		||||
        if (off0 <= off && off <= off1)
 | 
			
		||||
            applyMask(off, (curr | prev) & ((1 << left) - 1));
 | 
			
		||||
            applyMask(off, (curr | prev) & ((1 << left) - 1), mode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dirty = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//%
 | 
			
		||||
@@ -126,23 +134,69 @@ static uint8_t ones[] = {
 | 
			
		||||
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
bool isValidIcon(Buffer buf) {
 | 
			
		||||
    return buf->length >= 3 && buf->data[0] == 0xf0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const uint8_t bitdouble[] = {
 | 
			
		||||
    0x00, 0x03, 0x0c, 0x0f, 0x30, 0x33, 0x3c, 0x3f, 0xc0, 0xc3, 0xcc, 0xcf, 0xf0, 0xf3, 0xfc, 0xff,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** Double size of an icon. */
 | 
			
		||||
//%
 | 
			
		||||
Buffer doubleIcon(Buffer buf) {
 | 
			
		||||
    if (!isValidIcon(buf))
 | 
			
		||||
        return NULL;
 | 
			
		||||
    int w = buf->data[1];
 | 
			
		||||
    if (w > 126)
 | 
			
		||||
        return NULL;
 | 
			
		||||
    int bw = PIX2BYTES(w);
 | 
			
		||||
    int h = (buf->length - 2) / bw;
 | 
			
		||||
    int bw2 = PIX2BYTES(w * 2);
 | 
			
		||||
    Buffer out = mkBuffer(NULL, 2 + bw2 * h * 2);
 | 
			
		||||
    out->data[0] = 0xf0;
 | 
			
		||||
    out->data[1] = w * 2;
 | 
			
		||||
    uint8_t *src = buf->data + 2;
 | 
			
		||||
    uint8_t *dst = out->data + 2;
 | 
			
		||||
    for (int i = 0; i < h; ++i) {
 | 
			
		||||
        for (int jj = 0; jj < 2; ++jj) {
 | 
			
		||||
            auto p = src;
 | 
			
		||||
            for (int j = 0; j < bw; ++j) {
 | 
			
		||||
                *dst++ = bitdouble[*p & 0xf];
 | 
			
		||||
                *dst++ = bitdouble[*p >> 4];
 | 
			
		||||
                p++;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        src += bw;
 | 
			
		||||
    }
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Draw an icon on the screen. */
 | 
			
		||||
//%
 | 
			
		||||
void drawIcon(int x, int y, Buffer buf, Draw mode) {
 | 
			
		||||
    if (buf->length < 2)
 | 
			
		||||
    if (!isValidIcon(buf))
 | 
			
		||||
        return;
 | 
			
		||||
    int pixwidth = buf->data[0];
 | 
			
		||||
    if (pixwidth > 100)
 | 
			
		||||
        return;
 | 
			
		||||
    int ptr = 1;
 | 
			
		||||
    int bytewidth = (pixwidth + 7) >> 3;
 | 
			
		||||
    if (mode & Draw::Double)
 | 
			
		||||
        buf = doubleIcon(buf);
 | 
			
		||||
 | 
			
		||||
    int pixwidth = buf->data[1];
 | 
			
		||||
    int ptr = 2;
 | 
			
		||||
    int bytewidth = PIX2BYTES(pixwidth);
 | 
			
		||||
    pixwidth = min(pixwidth, LCD_WIDTH);
 | 
			
		||||
    while (ptr + bytewidth <= buf->length) {
 | 
			
		||||
        if (mode == Draw::Normal)
 | 
			
		||||
        if (mode & (Draw::Clear | Draw::Xor | Draw::Transparent)) {
 | 
			
		||||
            // no erase of background
 | 
			
		||||
        } else {
 | 
			
		||||
            blitLineCore(x, y, pixwidth, ones, Draw::Clear);
 | 
			
		||||
        }
 | 
			
		||||
        blitLineCore(x, y, pixwidth, &buf->data[ptr], mode);
 | 
			
		||||
        y++;
 | 
			
		||||
        ptr += bytewidth;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (mode & Draw::Double)
 | 
			
		||||
        decrRC(buf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Clear screen and reset font to normal. */
 | 
			
		||||
@@ -151,23 +205,62 @@ void clear() {
 | 
			
		||||
    memset(bitBuffer, 0, sizeof(bitBuffer));
 | 
			
		||||
    dirty = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//%
 | 
			
		||||
void dump() {
 | 
			
		||||
    char buf[LCD_WIDTH + 1];
 | 
			
		||||
    FILE *f = fopen("/tmp/screen.txt", "w");
 | 
			
		||||
    for (int i = 0; i < LCD_HEIGHT; ++i) {
 | 
			
		||||
        for (int j = 0; j < LCD_WIDTH; ++j) {
 | 
			
		||||
            if (bitBuffer[OFF(j, i)] & MASK(j, i))
 | 
			
		||||
                buf[j] = '#';
 | 
			
		||||
            else
 | 
			
		||||
                buf[j] = '.';
 | 
			
		||||
        }
 | 
			
		||||
        buf[LCD_WIDTH] = 0;
 | 
			
		||||
        fprintf(f, "%s\n", buf);
 | 
			
		||||
    }
 | 
			
		||||
    fclose(f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace pxt {
 | 
			
		||||
static uint8_t *mappedFrameBuffer;
 | 
			
		||||
 | 
			
		||||
//%
 | 
			
		||||
void updateLCD() {
 | 
			
		||||
    if (dirty && mappedFrameBuffer != MAP_FAILED) {
 | 
			
		||||
        dirty = false;
 | 
			
		||||
        bitBufferToFrameBuffer(bitBuffer, mappedFrameBuffer);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void *screenRefresh(void *dummy) {
 | 
			
		||||
    while (true) {
 | 
			
		||||
        sleep_core_us(30000);
 | 
			
		||||
        LcdUpdate();
 | 
			
		||||
        updateLCD();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void screen_init() {
 | 
			
		||||
    LcdInitNoAutoRefresh();
 | 
			
		||||
    LcdClean();
 | 
			
		||||
void init() {
 | 
			
		||||
    DMESG("init screen");
 | 
			
		||||
    if (mappedFrameBuffer)
 | 
			
		||||
        return;
 | 
			
		||||
    int fd = open("/dev/fb0", O_RDWR);
 | 
			
		||||
    DMESG("init screen %d", fd);
 | 
			
		||||
    mappedFrameBuffer = (uint8_t *)mmap(NULL, FB_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
 | 
			
		||||
    DMESG("map %p", mappedFrameBuffer);
 | 
			
		||||
    if (mappedFrameBuffer == MAP_FAILED) {
 | 
			
		||||
        target_panic(111);
 | 
			
		||||
    }
 | 
			
		||||
    clear();
 | 
			
		||||
 | 
			
		||||
    pthread_t pid;
 | 
			
		||||
    pthread_create(&pid, NULL, screenRefresh, NULL);
 | 
			
		||||
    pthread_detach(pid);
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace pxt {
 | 
			
		||||
void screen_init() {
 | 
			
		||||
    screen::init();
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
namespace screen {
 | 
			
		||||
    //% shim=screen::setPixelCore
 | 
			
		||||
    function setPixelCore(p0: uint32, p1: uint32, mode: Draw): void { }
 | 
			
		||||
    //% shim=screen::_setPixel
 | 
			
		||||
    function _setPixel(p0: uint32, p1: uint32, mode: Draw): void { }
 | 
			
		||||
 | 
			
		||||
    //% shim=screen::blitLine
 | 
			
		||||
    function blitLine(xw: uint32, y: uint32, buf: Buffer, mode: Draw): void { }
 | 
			
		||||
    //% shim=screen::_blitLine
 | 
			
		||||
    function _blitLine(xw: uint32, y: uint32, buf: Buffer, mode: Draw): void { }
 | 
			
		||||
 | 
			
		||||
    function pack(x: number, y: number) {
 | 
			
		||||
        return Math.clamp(0, 512, x) | (Math.clamp(0, 512, y) << 16)
 | 
			
		||||
@@ -12,7 +12,7 @@ namespace screen {
 | 
			
		||||
    const ones = hex`ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`
 | 
			
		||||
 | 
			
		||||
    function setLineCore(x: number, x1: number, y: number, mode: Draw) {
 | 
			
		||||
        blitLine(pack(x, x1 - x), y, ones, mode)
 | 
			
		||||
        _blitLine(pack(x, x1 - x), y, ones, mode)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    export interface Font {
 | 
			
		||||
@@ -23,7 +23,7 @@ namespace screen {
 | 
			
		||||
    }
 | 
			
		||||
    let currFont: Font
 | 
			
		||||
 | 
			
		||||
    export const heart = hex`07 367f7f3e1c08`
 | 
			
		||||
    export const heart = hex`f007 367f7f3e1c08`
 | 
			
		||||
 | 
			
		||||
    export function defaultFont(): Font {
 | 
			
		||||
        return {
 | 
			
		||||
@@ -56,7 +56,7 @@ namespace screen {
 | 
			
		||||
        x |= 0
 | 
			
		||||
        y |= 0
 | 
			
		||||
        if (0 <= x && x < LMS.LCD_WIDTH && 0 <= y && y < LMS.LCD_HEIGHT)
 | 
			
		||||
            setPixelCore(x, y, mode)
 | 
			
		||||
            _setPixel(x, y, mode)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    export function drawText(x: number, y: number, text: string, mode = Draw.Normal) {
 | 
			
		||||
@@ -66,21 +66,25 @@ namespace screen {
 | 
			
		||||
        let x0 = x
 | 
			
		||||
        let cp = 0
 | 
			
		||||
        let byteWidth = (currFont.charWidth + 7) >> 3
 | 
			
		||||
        let iconBuf = output.createBuffer(1 + byteWidth * currFont.charHeight)
 | 
			
		||||
        iconBuf[0] = currFont.charWidth
 | 
			
		||||
        let charSize = byteWidth * currFont.charHeight
 | 
			
		||||
        let iconBuf = output.createBuffer(2 + charSize)
 | 
			
		||||
        let double = (mode & Draw.Double) ? 2 : 1
 | 
			
		||||
        iconBuf[0] = 0xf0
 | 
			
		||||
        iconBuf[1] = currFont.charWidth
 | 
			
		||||
        while (cp < text.length) {
 | 
			
		||||
            let ch = text.charCodeAt(cp++)
 | 
			
		||||
            if (ch == 10) {
 | 
			
		||||
                y += currFont.charHeight + 2
 | 
			
		||||
                y += double * currFont.charHeight + 2
 | 
			
		||||
                x = x0
 | 
			
		||||
            }
 | 
			
		||||
            if (ch < 32) continue
 | 
			
		||||
            let idx = (ch - currFont.firstChar) * byteWidth
 | 
			
		||||
            let idx = (ch - currFont.firstChar) * charSize
 | 
			
		||||
            if (idx < 0 || idx + iconBuf.length - 1 > currFont.data.length)
 | 
			
		||||
                iconBuf.fill(0, 1)
 | 
			
		||||
                iconBuf.fill(0, 2)
 | 
			
		||||
            else
 | 
			
		||||
                iconBuf.write(1, currFont.data.slice(idx, byteWidth))
 | 
			
		||||
                iconBuf.write(2, currFont.data.slice(idx, charSize))
 | 
			
		||||
            drawIcon(x, y, iconBuf, mode)
 | 
			
		||||
            x += double * currFont.charWidth
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -105,7 +109,7 @@ namespace screen {
 | 
			
		||||
        let y1 = Math.min(LMS.LCD_HEIGHT, y + h);
 | 
			
		||||
        if (w == 1) {
 | 
			
		||||
            while (y < y1)
 | 
			
		||||
                setPixelCore(x, y++, mode);
 | 
			
		||||
                _setPixel(x, y++, mode);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -114,8 +118,8 @@ namespace screen {
 | 
			
		||||
            if (mode & Draw.Fill) {
 | 
			
		||||
                setLineCore(x, x1, y, mode);
 | 
			
		||||
            } else {
 | 
			
		||||
                setPixelCore(x, y, mode);
 | 
			
		||||
                setPixelCore(x1 - 1, y, mode);
 | 
			
		||||
                _setPixel(x, y, mode);
 | 
			
		||||
                _setPixel(x1 - 1, y, mode);
 | 
			
		||||
            }
 | 
			
		||||
            y++;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								libs/core/shims.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								libs/core/shims.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -72,21 +72,17 @@ declare namespace serial {
 | 
			
		||||
}
 | 
			
		||||
declare namespace screen {
 | 
			
		||||
 | 
			
		||||
    /** Draw text. */
 | 
			
		||||
    //% mode.defl=0 shim=screen::drawText
 | 
			
		||||
    function drawText(x: int32, y: int32, text: string, mode?: Draw): void;
 | 
			
		||||
    /** Double size of an icon. */
 | 
			
		||||
    //% shim=screen::doubleIcon
 | 
			
		||||
    function doubleIcon(buf: Buffer): Buffer;
 | 
			
		||||
 | 
			
		||||
    /** Draw an icon on the screen. */
 | 
			
		||||
    //% shim=screen::drawIcon
 | 
			
		||||
    function drawIcon(x: int32, y: int32, buf: Buffer, mode: Draw): void;
 | 
			
		||||
 | 
			
		||||
    /** Clear screen and reset font to normal. */
 | 
			
		||||
    //% shim=screen::clear
 | 
			
		||||
    function clear(): void;
 | 
			
		||||
 | 
			
		||||
    /** Scroll screen vertically. */
 | 
			
		||||
    //% shim=screen::scroll
 | 
			
		||||
    function scroll(v: int32): void;
 | 
			
		||||
 | 
			
		||||
    /** Set font for drawText() */
 | 
			
		||||
    //% shim=screen::setFont
 | 
			
		||||
    function setFont(font: ScreenFont): void;
 | 
			
		||||
}
 | 
			
		||||
declare namespace output {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +1,30 @@
 | 
			
		||||
screen.clear()
 | 
			
		||||
screen.setFont(ScreenFont.Large)
 | 
			
		||||
screen.drawText(10, 30, "Welcome PXT!")
 | 
			
		||||
screen.drawText(10, 30, "Welcome PXT!", Draw.Double)
 | 
			
		||||
 | 
			
		||||
screen.drawEllipse(40, 40, 20, 10, Draw.Fill)
 | 
			
		||||
screen.drawRect(40, 40, 20, 10, Draw.Fill)
 | 
			
		||||
output.setLights(LightsPattern.Orange)
 | 
			
		||||
 | 
			
		||||
screen.drawIcon(100, 50, screen.heart, Draw.Double)
 | 
			
		||||
 | 
			
		||||
input.buttonEnter.onEvent(ButtonEvent.Click, () => {
 | 
			
		||||
    screen.clear()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
input.buttonLeft.onEvent(ButtonEvent.Click, () => {
 | 
			
		||||
    screen.drawRect(10, 70, 20, 10, Draw.Fill)
 | 
			
		||||
    output.setLights(LightsPattern.Red)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
input.buttonRight.onEvent(ButtonEvent.Click, () => {
 | 
			
		||||
    screen.setFont(ScreenFont.Normal)
 | 
			
		||||
    screen.drawText(10, 60, "Bang!")
 | 
			
		||||
    screen.drawText(10, 60, "Right!")
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
input.buttonDown.onEvent(ButtonEvent.Click, () => {
 | 
			
		||||
    screen.scroll(-10)
 | 
			
		||||
    screen.drawText(10, 60, "Down! ")
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
input.buttonUp.onEvent(ButtonEvent.Click, () => {
 | 
			
		||||
    screen.scroll(10)
 | 
			
		||||
    screen.drawText(10, 60, "Up!  ")
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user