add more graphics bindings, M5Canvas, and keyboard bindings to support the features required by Paper.

This commit is contained in:
wisplite
2025-12-25 20:10:39 -06:00
parent 0710e0a232
commit f7ffbb5e7d
11 changed files with 607 additions and 44 deletions
+6 -3
View File
@@ -1,7 +1,10 @@
Cardstock is a lightweight Lua-based OS for the M5Stack Cardputer. Cardstock is a lightweight Lua-based OS for the M5Stack Cardputer.
The project currently builds a Lua VM and loads a lua entrypoint (which will eventually be a launcher) This repository contains all the C/C++ infrastructure and bindings required to support the Lua portion of the OS.
I am currently writing Lua bindings for all the M5Cardputer libraries, starting with the GFX library right now. Currently supported modules are:
- M5GFX
- M5Canvas
- Keyboard
DISCLAIMER: A decent portion of the C++ is written by AI right now. The plan is to go back and check over/rewrite this later as I add more complex functionality to the "bootloader". The eventual goal is for every API to be built and passed through to Lua, with certain things like wireless being controlled globally across apps.
+185
View File
@@ -1,6 +1,33 @@
#include "lua_gfx.h" #include "lua_gfx.h"
#include "services/GfxService.h" #include "services/GfxService.h"
#include "M5Cardputer.h"
// -------------------------------
// gfx.sprite userdata (M5Canvas)
// -------------------------------
static const char* kSpriteMT = "gfx.sprite";
struct LuaSprite {
M5Canvas* canvas = nullptr;
};
static LuaSprite* lua_check_sprite(lua_State* L, int idx) {
return static_cast<LuaSprite*>(luaL_checkudata(L, idx, kSpriteMT));
}
static M5Canvas* lua_sprite_require_alive(lua_State* L, LuaSprite* s) {
if (!s || !s->canvas) luaL_error(L, "sprite is freed");
return s->canvas;
}
static void lua_sprite_free(LuaSprite* s) {
if (!s || !s->canvas) return;
s->canvas->deleteSprite();
delete s->canvas;
s->canvas = nullptr;
}
static uint16_t lua_check_u16(lua_State* L, int idx) { static uint16_t lua_check_u16(lua_State* L, int idx) {
lua_Integer v = luaL_checkinteger(L, idx); lua_Integer v = luaL_checkinteger(L, idx);
@@ -9,6 +36,114 @@ static uint16_t lua_check_u16(lua_State* L, int idx) {
return static_cast<uint16_t>(v); return static_cast<uint16_t>(v);
} }
static int l_sprite_gc(lua_State* L) {
LuaSprite* s = lua_check_sprite(L, 1);
lua_sprite_free(s);
return 0;
}
static int l_sprite_free(lua_State* L) {
LuaSprite* s = lua_check_sprite(L, 1);
lua_sprite_free(s);
return 0;
}
static int l_sprite_clear(lua_State* L) {
LuaSprite* s = lua_check_sprite(L, 1);
M5Canvas* c = lua_sprite_require_alive(L, s);
uint16_t color = 0x0000;
if (lua_gettop(L) >= 2 && !lua_isnil(L, 2)) color = lua_check_u16(L, 2);
c->fillScreen(color);
return 0;
}
static int l_sprite_set_text_color(lua_State* L) {
LuaSprite* s = lua_check_sprite(L, 1);
M5Canvas* c = lua_sprite_require_alive(L, s);
uint16_t fg = lua_check_u16(L, 2);
if (lua_gettop(L) >= 3 && !lua_isnil(L, 3)) {
uint16_t bg = lua_check_u16(L, 3);
c->setTextColor(fg, bg);
} else {
c->setTextColor(fg);
}
return 0;
}
static int l_sprite_set_text_size(lua_State* L) {
LuaSprite* s = lua_check_sprite(L, 1);
M5Canvas* c = lua_sprite_require_alive(L, s);
lua_Integer size = luaL_checkinteger(L, 2);
if (size < 1) size = 1;
if (size > 255) size = 255;
c->setTextSize(static_cast<uint8_t>(size));
return 0;
}
static int l_sprite_draw_string(lua_State* L) {
LuaSprite* s = lua_check_sprite(L, 1);
M5Canvas* c = lua_sprite_require_alive(L, s);
const char* text = luaL_checkstring(L, 2);
int32_t x = static_cast<int32_t>(luaL_checkinteger(L, 3));
int32_t y = static_cast<int32_t>(luaL_checkinteger(L, 4));
int32_t w = c->drawString(text, x, y);
lua_pushinteger(L, static_cast<lua_Integer>(w));
return 1;
}
static int l_sprite_draw_center_string(lua_State* L) {
LuaSprite* s = lua_check_sprite(L, 1);
M5Canvas* c = lua_sprite_require_alive(L, s);
const char* text = luaL_checkstring(L, 2);
int32_t x = static_cast<int32_t>(luaL_checkinteger(L, 3));
int32_t y = static_cast<int32_t>(luaL_checkinteger(L, 4));
int32_t w = c->drawCenterString(text, x, y);
lua_pushinteger(L, static_cast<lua_Integer>(w));
return 1;
}
static int l_sprite_push(lua_State* L) {
LuaSprite* s = lua_check_sprite(L, 1);
M5Canvas* c = lua_sprite_require_alive(L, s);
int32_t x = static_cast<int32_t>(luaL_checkinteger(L, 2));
int32_t y = static_cast<int32_t>(luaL_checkinteger(L, 3));
c->pushSprite(x, y);
return 0;
}
static const luaL_Reg kSpriteMethods[] = {
{"clear", l_sprite_clear},
{"setTextColor", l_sprite_set_text_color},
{"setTextSize", l_sprite_set_text_size},
{"drawString", l_sprite_draw_string},
{"drawCenterString", l_sprite_draw_center_string},
{"push", l_sprite_push},
{"free", l_sprite_free},
{nullptr, nullptr},
};
static int l_gfx_new_sprite(lua_State* L) {
int32_t w = static_cast<int32_t>(luaL_checkinteger(L, 1));
int32_t h = static_cast<int32_t>(luaL_checkinteger(L, 2));
if (w <= 0 || h <= 0) luaL_error(L, "newSprite: width and height must be > 0");
LuaSprite* ud = static_cast<LuaSprite*>(lua_newuserdatauv(L, sizeof(LuaSprite), 0));
new (ud) LuaSprite();
// Create canvas (parented to the real display).
ud->canvas = new (std::nothrow) M5Canvas(&M5Cardputer.Display);
if (!ud->canvas) luaL_error(L, "newSprite: OOM allocating canvas");
bool ok = ud->canvas->createSprite(w, h);
if (!ok) {
lua_sprite_free(ud);
luaL_error(L, "newSprite: createSprite failed");
}
luaL_setmetatable(L, kSpriteMT);
return 1;
}
static int l_gfx_clear(lua_State* L) { static int l_gfx_clear(lua_State* L) {
uint16_t color = 0x0000; uint16_t color = 0x0000;
if (lua_gettop(L) >= 1 && !lua_isnil(L, 1)) color = lua_check_u16(L, 1); if (lua_gettop(L) >= 1 && !lua_isnil(L, 1)) color = lua_check_u16(L, 1);
@@ -63,7 +198,41 @@ static int l_gfx_draw_string(lua_State* L) {
return 1; return 1;
} }
static int l_gfx_fill_rect(lua_State* L) {
int32_t x = static_cast<int32_t>(luaL_checkinteger(L, 1));
int32_t y = static_cast<int32_t>(luaL_checkinteger(L, 2));
int32_t w = static_cast<int32_t>(luaL_checkinteger(L, 3));
int32_t h = static_cast<int32_t>(luaL_checkinteger(L, 4));
uint16_t color = lua_check_u16(L, 5);
GfxService::fillRect(x, y, w, h, color);
return 0;
}
static int l_gfx_draw_center_string(lua_State* L) {
const char* s = luaL_checkstring(L, 1);
int32_t x = static_cast<int32_t>(luaL_checkinteger(L, 2));
int32_t y = static_cast<int32_t>(luaL_checkinteger(L, 3));
int32_t font = -1;
if (lua_gettop(L) >= 4 && !lua_isnil(L, 4)) font = static_cast<int32_t>(luaL_checkinteger(L, 4));
int32_t w = GfxService::drawCenterString(s, x, y, font);
lua_pushinteger(L, static_cast<lua_Integer>(w));
return 1;
}
static int l_gfx_width(lua_State* L) {
int32_t w = GfxService::width();
lua_pushinteger(L, static_cast<lua_Integer>(w));
return 1;
}
static int l_gfx_height(lua_State* L) {
int32_t h = GfxService::height();
lua_pushinteger(L, static_cast<lua_Integer>(h));
return 1;
}
static const luaL_Reg kGfxLib[] = { static const luaL_Reg kGfxLib[] = {
{"newSprite", l_gfx_new_sprite},
{"clear", l_gfx_clear}, {"clear", l_gfx_clear},
{"setCursor", l_gfx_set_cursor}, {"setCursor", l_gfx_set_cursor},
{"setTextSize", l_gfx_set_text_size}, {"setTextSize", l_gfx_set_text_size},
@@ -71,10 +240,26 @@ static const luaL_Reg kGfxLib[] = {
{"print", l_gfx_print}, {"print", l_gfx_print},
{"println", l_gfx_println}, {"println", l_gfx_println},
{"drawString", l_gfx_draw_string}, {"drawString", l_gfx_draw_string},
{"fillRect", l_gfx_fill_rect},
{"drawCenterString", l_gfx_draw_center_string},
{"width", l_gfx_width},
{"height", l_gfx_height},
{nullptr, nullptr}, {nullptr, nullptr},
}; };
int luaopen_gfx(lua_State* L) { int luaopen_gfx(lua_State* L) {
// Create gfx.sprite metatable.
if (luaL_newmetatable(L, kSpriteMT)) {
// metatable.__index = methods table
luaL_newlib(L, kSpriteMethods);
lua_setfield(L, -2, "__index");
// metatable.__gc = l_sprite_gc
lua_pushcfunction(L, l_sprite_gc);
lua_setfield(L, -2, "__gc");
}
lua_pop(L, 1); // pop metatable
luaL_newlib(L, kGfxLib); luaL_newlib(L, kGfxLib);
return 1; return 1;
} }
+39
View File
@@ -0,0 +1,39 @@
#include "lua_keyboard.h"
#include "services/KeyboardService.h"
static int l_keyboard_is_changed(lua_State* L) {
lua_pushboolean(L, KeyboardService::isChanged());
return 1;
}
static int l_keyboard_is_pressed(lua_State* L) {
lua_pushinteger(L, KeyboardService::isPressed());
return 1;
}
static int l_keyboard_is_key_pressed(lua_State* L) {
const char* c = luaL_checkstring(L, 1);
lua_pushboolean(L, KeyboardService::isKeyPressed(c[0]));
return 1;
}
static int l_keyboard_get_key(lua_State* L) {
int32_t x = static_cast<int32_t>(luaL_checkinteger(L, 1));
int32_t y = static_cast<int32_t>(luaL_checkinteger(L, 2));
lua_pushinteger(L, KeyboardService::getKey(x, y));
return 1;
}
static const luaL_Reg kKeyboardLib[] = {
{"isChanged", l_keyboard_is_changed},
{"isPressed", l_keyboard_is_pressed},
{"isKeyPressed", l_keyboard_is_key_pressed},
{"getKey", l_keyboard_get_key},
{nullptr, nullptr},
};
int luaopen_keyboard(lua_State* L) {
luaL_newlib(L, kKeyboardLib);
return 1;
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#include "lua.hpp"
// Lua module entrypoint: local keyboard = require("keyboard")
int luaopen_keyboard(lua_State* L);
+217
View File
@@ -0,0 +1,217 @@
#include "require_sd.h"
#include <Arduino.h>
#include <SD.h>
#include <new>
#include <memory>
namespace {
// Registry key for current app root.
static const char* kAppRootKey = "cardstock.app_root";
static const char* kSysLibRoot = "/syslib";
static String normalize_abs_path(const String& p) {
if (!p.length()) return "";
String out = p;
if (out[0] != '/') out = String("/") + out;
// Strip trailing slashes (except for "/")
while (out.length() > 1 && out[out.length() - 1] == '/') out.remove(out.length() - 1);
return out;
}
static bool contains_path_traversal(const char* s) {
if (!s) return false;
// Disallow any ".." path segment.
// (Accepts module names like "foo..bar"? That would be weird; still blocked.)
return strstr(s, "..") != nullptr;
}
static String module_name_to_rel_path(const char* modname) {
// Convert dotted module name to path: "a.b.c" -> "a/b/c"
String out;
if (!modname) return out;
for (const char* p = modname; *p; ++p) {
char c = *p;
if (c == '.') out += '/';
else if (c == '\\') out += '/';
else out += c;
}
// Strip any leading slashes to keep it relative.
while (out.length() && out[0] == '/') out.remove(0, 1);
return out;
}
static bool read_entire_file(const String& path, std::unique_ptr<uint8_t[]>& out_buf, size_t& out_len, String& out_err) {
const String normalized = normalize_abs_path(path);
File f = SD.open(normalized.c_str(), FILE_READ);
if (!f) {
out_err = "open failed: " + normalized;
return false;
}
size_t sz = static_cast<size_t>(f.size());
if (sz == 0) {
out_err = "empty file: " + normalized;
f.close();
return false;
}
std::unique_ptr<uint8_t[]> buf(new (std::nothrow) uint8_t[sz]);
if (!buf) {
out_err = "OOM reading: " + normalized;
f.close();
return false;
}
size_t read_total = 0;
while (read_total < sz) {
int n = f.read(buf.get() + read_total, sz - read_total);
if (n <= 0) break;
read_total += static_cast<size_t>(n);
}
f.close();
if (read_total != sz) {
out_err = "short read: " + normalized;
return false;
}
out_buf = std::move(buf);
out_len = sz;
return true;
}
static String get_app_root(lua_State* L) {
lua_pushstring(L, kAppRootKey);
lua_gettable(L, LUA_REGISTRYINDEX); // registry[kAppRootKey]
const char* s = lua_tostring(L, -1);
String out = s ? String(s) : String("");
lua_pop(L, 1);
return out;
}
static void push_searcher_error(lua_State* L, const String& msg) {
// Match Lua's style: searchers return a string starting with "\n\t..."
String m = "\n\t";
m += msg;
lua_pushstring(L, m.c_str());
}
static int l_cardstock_searcher(lua_State* L) {
const char* modname = luaL_checkstring(L, 1);
if (contains_path_traversal(modname)) {
push_searcher_error(L, String("invalid module name: ") + modname);
return 1;
}
const String rel = module_name_to_rel_path(modname);
if (!rel.length()) {
push_searcher_error(L, "invalid module name");
return 1;
}
const String app_root = get_app_root(L);
bool loaded = false;
// Build candidates.
String candidates[4];
int n = 0;
if (app_root.length()) {
candidates[n++] = app_root + "/" + rel + ".lua";
candidates[n++] = app_root + "/" + rel + "/init.lua";
}
candidates[n++] = String(kSysLibRoot) + "/" + rel + ".lua";
candidates[n++] = String(kSysLibRoot) + "/" + rel + "/init.lua";
// Try candidates in order.
for (int i = 0; i < n; i++) {
const String& p = candidates[i];
String err;
std::unique_ptr<uint8_t[]> buf;
size_t len = 0;
// Load full file into chunk.
if (!read_entire_file(p, buf, len, err)) continue;
int rc = luaL_loadbuffer(L, reinterpret_cast<const char*>(buf.get()), len, p.c_str());
if (rc != LUA_OK) {
const char* emsg = lua_tostring(L, -1);
String m = "load error in ";
m += p;
m += ": ";
m += emsg ? emsg : "(unknown)";
lua_pop(L, 1);
push_searcher_error(L, m);
return 1;
}
lua_pushstring(L, normalize_abs_path(p).c_str());
loaded = true;
break;
}
if (loaded) return 2;
// Not found. Provide a single aggregated message.
String msg = "module '";
msg += modname;
msg += "' not found (searched ";
if (app_root.length()) {
msg += app_root;
msg += " and ";
}
msg += kSysLibRoot;
msg += ")";
push_searcher_error(L, msg);
return 1;
}
static void insert_searcher(lua_State* L) {
// package.searchers is a table (Lua 5.2+).
lua_getglobal(L, "package");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return;
}
lua_getfield(L, -1, "searchers");
if (!lua_istable(L, -1)) {
lua_pop(L, 2);
return;
}
// Insert at index 2 (after preload searcher).
const int insert_at = 2;
int len = static_cast<int>(luaL_len(L, -1));
for (int i = len + 1; i > insert_at; --i) {
lua_rawgeti(L, -1, i - 1);
lua_rawseti(L, -2, i);
}
lua_pushcfunction(L, l_cardstock_searcher);
lua_rawseti(L, -2, insert_at);
lua_pop(L, 2); // pop searchers, package
}
} // namespace
void lua_cardstock_install_require(lua_State* L) {
if (!L) return;
insert_searcher(L);
}
void lua_cardstock_set_app_root(lua_State* L, const char* app_root) {
if (!L) return;
String root = app_root ? String(app_root) : String("");
root = normalize_abs_path(root);
lua_pushstring(L, kAppRootKey);
lua_pushstring(L, root.c_str());
lua_settable(L, LUA_REGISTRYINDEX);
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
// Custom SD-backed module loader ("require") for Cardstock.
//
// Installs a Lua `package.searcher` that loads modules from:
// 1) <app_root>/<module>.lua and <app_root>/<module>/init.lua
// 2) /syslib/<module>.lua and /syslib/<module>/init.lua
//
// The app root is set by the host (C++) per loaded entrypoint.
#include "lua.hpp"
// Install the Cardstock SD-backed searcher into `package.searchers`.
void lua_cardstock_install_require(lua_State* L);
// Set the current app root used by the searcher (absolute, e.g. "/apps/foo").
// Pass "" or nullptr to disable app-scoped lookup.
void lua_cardstock_set_app_root(lua_State* L, const char* app_root);
+45 -6
View File
@@ -26,13 +26,15 @@ extern "C" {
#include "M5Cardputer.h" #include "M5Cardputer.h"
#include "lua/bindings/lua_gfx.h" #include "lua/bindings/lua_gfx.h"
#include "lua/require_sd.h"
#include "lua/bindings/lua_keyboard.h"
// ------------------------------- // -------------------------------
// Build-time configuration knobs // Build-time configuration knobs
// ------------------------------- // -------------------------------
// Default Lua entrypoint on SD. Override with: -DCARDSTOCK_LUA_ENTRY=\"/my.lua\" // Default Lua entrypoint on SD. Override with: -DCARDSTOCK_LUA_ENTRY=\"/my.lua\"
#ifndef CARDSTOCK_LUA_ENTRY #ifndef CARDSTOCK_LUA_ENTRY
#define CARDSTOCK_LUA_ENTRY "/app.lua" #define CARDSTOCK_LUA_ENTRY "/launcher/main.lua"
#endif #endif
// SD init pins. If you don't define these, the code falls back to SD.begin(CS). // SD init pins. If you don't define these, the code falls back to SD.begin(CS).
@@ -57,6 +59,7 @@ struct LuaHost {
String pending_path; String pending_path;
bool reload_requested = false; bool reload_requested = false;
uint32_t last_ms = 0; uint32_t last_ms = 0;
String app_root;
}; };
static LuaHost g_host; static LuaHost g_host;
@@ -100,22 +103,25 @@ static bool init_sd_card() {
} }
static bool read_entire_file_from_sd(const String& path, std::unique_ptr<uint8_t[]>& out_buf, size_t& out_len, String& out_err) { static bool read_entire_file_from_sd(const String& path, std::unique_ptr<uint8_t[]>& out_buf, size_t& out_len, String& out_err) {
File f = SD.open(path.c_str(), FILE_READ); // ESP32 SD/FS paths are effectively absolute (no working-directory semantics).
// Root files often work without a leading '/', but subdirectories commonly won't.
const String normalized = (path.length() && path[0] == '/') ? path : (String("/") + path);
File f = SD.open(normalized.c_str(), FILE_READ);
if (!f) { if (!f) {
out_err = "SD.open failed: " + path; out_err = "SD.open failed: " + normalized;
return false; return false;
} }
size_t sz = static_cast<size_t>(f.size()); size_t sz = static_cast<size_t>(f.size());
if (sz == 0) { if (sz == 0) {
out_err = "Empty file: " + path; out_err = "Empty file: " + normalized;
f.close(); f.close();
return false; return false;
} }
std::unique_ptr<uint8_t[]> buf(new (std::nothrow) uint8_t[sz]); std::unique_ptr<uint8_t[]> buf(new (std::nothrow) uint8_t[sz]);
if (!buf) { if (!buf) {
out_err = "OOM reading: " + path; out_err = "OOM reading: " + normalized;
f.close(); f.close();
return false; return false;
} }
@@ -129,7 +135,7 @@ static bool read_entire_file_from_sd(const String& path, std::unique_ptr<uint8_t
f.close(); f.close();
if (read_total != sz) { if (read_total != sz) {
out_err = "Short read: " + path; out_err = "Short read: " + normalized;
return false; return false;
} }
@@ -138,6 +144,32 @@ static bool read_entire_file_from_sd(const String& path, std::unique_ptr<uint8_t
return true; return true;
} }
static String normalize_abs_path(const String& p) {
if (!p.length()) return "";
String out = p;
if (out[0] != '/') out = String("/") + out;
while (out.length() > 1 && out[out.length() - 1] == '/') out.remove(out.length() - 1);
return out;
}
static String compute_app_root_for_entrypoint(const String& entry_script_path) {
const String p = normalize_abs_path(entry_script_path);
// Preferred: apps live at /apps/<appID>/...
if (p.startsWith("/apps/")) {
const int app_id_start = 6; // strlen("/apps/")
const int slash_after_app_id = p.indexOf('/', app_id_start);
if (slash_after_app_id > app_id_start) {
return p.substring(0, slash_after_app_id); // "/apps/<appID>"
}
}
// Fallback: use directory of the entrypoint (useful for launcher scripts too).
const int last_slash = p.lastIndexOf('/');
if (last_slash > 0) return p.substring(0, last_slash);
return "";
}
static int l_print_serial(lua_State* L) { static int l_print_serial(lua_State* L) {
int n = lua_gettop(L); int n = lua_gettop(L);
String line; String line;
@@ -217,9 +249,16 @@ static bool lua_boot_and_load(LuaHost& host, const String& script_path) {
host_set_for_lua(host.L, &host); host_set_for_lua(host.L, &host);
luaL_openlibs(host.L); luaL_openlibs(host.L);
// Install SD-backed require searcher and set app root scope for this entrypoint.
lua_cardstock_install_require(host.L);
host.app_root = compute_app_root_for_entrypoint(script_path);
lua_cardstock_set_app_root(host.L, host.app_root.c_str());
// Register built-in modules (Lua: local gfx = require("gfx")). // Register built-in modules (Lua: local gfx = require("gfx")).
luaL_requiref(host.L, "gfx", luaopen_gfx, 1); luaL_requiref(host.L, "gfx", luaopen_gfx, 1);
lua_pop(host.L, 1); // pop returned module table lua_pop(host.L, 1); // pop returned module table
luaL_requiref(host.L, "keyboard", luaopen_keyboard, 1);
lua_pop(host.L, 1); // pop returned module table
// Override print() to go to Serial (handy on embedded). // Override print() to go to Serial (handy on embedded).
lua_pushcfunction(host.L, l_print_serial); lua_pushcfunction(host.L, l_print_serial);
+19
View File
@@ -45,6 +45,25 @@ int32_t drawString(const char* s, int32_t x, int32_t y, int32_t font) {
return M5Cardputer.Display.drawString(s, x, y, font); return M5Cardputer.Display.drawString(s, x, y, font);
} }
void fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t color) {
M5Cardputer.Display.fillRect(x, y, w, h, color);
}
int32_t width() {
return M5Cardputer.Display.width();
}
int32_t height() {
return M5Cardputer.Display.height();
}
int32_t drawCenterString(const char* s, int32_t x, int32_t y, int32_t font) {
if (!s) return 0;
if (font < 0) {
return M5Cardputer.Display.drawCenterString(s, x, y);
}
return M5Cardputer.Display.drawCenterString(s, x, y, font);
}
} // namespace GfxService } // namespace GfxService
+4
View File
@@ -11,10 +11,14 @@ void setTextSize(uint8_t size);
void setTextColor(uint16_t fg, int32_t bg = -1); // bg < 0 => don't set background void setTextColor(uint16_t fg, int32_t bg = -1); // bg < 0 => don't set background
void print(const char* s); void print(const char* s);
void println(const char* s); void println(const char* s);
void fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t color = 0x0000 /* BLACK */);
// Returns the pixel width of the rendered string (per M5GFX/LovyanGFX convention). // Returns the pixel width of the rendered string (per M5GFX/LovyanGFX convention).
int32_t drawString(const char* s, int32_t x, int32_t y, int32_t font = -1); int32_t drawString(const char* s, int32_t x, int32_t y, int32_t font = -1);
int32_t drawCenterString(const char* s, int32_t x, int32_t y, int32_t font = -1);
int32_t width();
int32_t height();
} // namespace GfxService } // namespace GfxService
+21
View File
@@ -0,0 +1,21 @@
#include "KeyboardService.h"
#include "M5Cardputer.h"
namespace KeyboardService {
bool isChanged() {
return M5Cardputer.Keyboard.isChange();
}
uint8_t isPressed() {
return M5Cardputer.Keyboard.isPressed();
}
bool isKeyPressed(char c) {
return M5Cardputer.Keyboard.isKeyPressed(c);
}
uint8_t getKey(int32_t x, int32_t y) {
Point2D_t keyCoord;
keyCoord.x = static_cast<int>(x);
keyCoord.y = static_cast<int>(y);
return M5Cardputer.Keyboard.getKey(keyCoord);
}
}
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include <Arduino.h>
namespace KeyboardService {
bool isChanged();
uint8_t isPressed(); // returns number of pressed keys
bool isKeyPressed(char c); // returns true if the key is pressed
uint8_t getKey(int32_t x, int32_t y); // returns the key code of the pressed key at (x,y)
}