Before Width: | Height: | Size: 623 B After Width: | Height: | Size: 623 B |
Before Width: | Height: | Size: 617 B After Width: | Height: | Size: 617 B |
Before Width: | Height: | Size: 617 B After Width: | Height: | Size: 617 B |
Before Width: | Height: | Size: 618 B After Width: | Height: | Size: 618 B |
Before Width: | Height: | Size: 622 B After Width: | Height: | Size: 622 B |
Before Width: | Height: | Size: 614 B After Width: | Height: | Size: 614 B |
Before Width: | Height: | Size: 618 B After Width: | Height: | Size: 618 B |
After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 654 B After Width: | Height: | Size: 654 B |
Before Width: | Height: | Size: 608 B After Width: | Height: | Size: 608 B |
Before Width: | Height: | Size: 616 B After Width: | Height: | Size: 616 B |
@ -0,0 +1,37 @@ |
|||||||
|
local lvgl = require("lvgl") |
||||||
|
|
||||||
|
local backstack = { |
||||||
|
root = lvgl.Object(nil, { |
||||||
|
w = lvgl.HOR_RES(), |
||||||
|
h = lvgl.VER_RES(), |
||||||
|
}), |
||||||
|
stack = {}, |
||||||
|
} |
||||||
|
|
||||||
|
function backstack:Top() |
||||||
|
return self.stack[#self.stack] |
||||||
|
end |
||||||
|
|
||||||
|
function backstack:SetTopParent(parent) |
||||||
|
local top = self:Top() |
||||||
|
if top and top.root then |
||||||
|
top.root:set_parent(parent) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function backstack:Push(screen) |
||||||
|
self:SetTopParent(nil) |
||||||
|
table.insert(self.stack, screen) |
||||||
|
self:SetTopParent(self.root) |
||||||
|
end |
||||||
|
|
||||||
|
function backstack:Pop(num) |
||||||
|
num = num or 1 |
||||||
|
for _ = 1, num do |
||||||
|
local removed = table.remove(self.stack) |
||||||
|
removed.root:delete() |
||||||
|
end |
||||||
|
self:SetTopParent(self.root) |
||||||
|
end |
||||||
|
|
||||||
|
return backstack |
@ -1 +1,3 @@ |
|||||||
require("main_menu"):Create() |
local backstack = require("backstack") |
||||||
|
local main_menu = require("main_menu"):Create(backstack.root) |
||||||
|
backstack:Push(main_menu) |
||||||
|
@ -0,0 +1,47 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <memory> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "lua.hpp" |
||||||
|
#include "lvgl.h" |
||||||
|
#include "service_locator.hpp" |
||||||
|
|
||||||
|
namespace lua { |
||||||
|
|
||||||
|
using LuaValue = std::variant<std::monostate, int, float, bool, std::string>; |
||||||
|
|
||||||
|
class Property { |
||||||
|
public: |
||||||
|
Property() : Property(std::monostate{}) {} |
||||||
|
Property(const LuaValue&); |
||||||
|
Property(const LuaValue&, std::function<bool(const LuaValue&)>); |
||||||
|
|
||||||
|
auto IsTwoWay() -> bool { return cb_.has_value(); } |
||||||
|
|
||||||
|
auto PushValue(lua_State& s) -> int; |
||||||
|
auto PopValue(lua_State& s) -> bool; |
||||||
|
auto Update(const LuaValue& new_val) -> void; |
||||||
|
|
||||||
|
auto AddLuaBinding(lua_State*, int ref) -> void; |
||||||
|
|
||||||
|
private: |
||||||
|
LuaValue value_; |
||||||
|
std::optional<std::function<bool(const LuaValue&)>> cb_; |
||||||
|
std::vector<std::pair<lua_State*, int>> bindings_; |
||||||
|
}; |
||||||
|
|
||||||
|
class PropertyBindings { |
||||||
|
public: |
||||||
|
PropertyBindings(lua_State&); |
||||||
|
|
||||||
|
auto Register(lua_State*, Property*) -> void; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace lua
|
@ -0,0 +1,196 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "property.hpp" |
||||||
|
|
||||||
|
#include <memory> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "lua.h" |
||||||
|
#include "lua.hpp" |
||||||
|
#include "lvgl.h" |
||||||
|
#include "service_locator.hpp" |
||||||
|
|
||||||
|
namespace lua { |
||||||
|
|
||||||
|
static const char kMetatableName[] = "property"; |
||||||
|
static const char kBindingsTable[] = "bindings"; |
||||||
|
|
||||||
|
static auto check_property(lua_State* state) -> Property* { |
||||||
|
void* data = luaL_checkudata(state, 1, kMetatableName); |
||||||
|
luaL_argcheck(state, data != NULL, 1, "`property` expected"); |
||||||
|
return *reinterpret_cast<Property**>(data); |
||||||
|
} |
||||||
|
|
||||||
|
static auto property_get(lua_State* state) -> int { |
||||||
|
Property* p = check_property(state); |
||||||
|
p->PushValue(*state); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
static auto property_set(lua_State* state) -> int { |
||||||
|
Property* p = check_property(state); |
||||||
|
luaL_argcheck(state, p->IsTwoWay(), 1, "property is read-only"); |
||||||
|
bool valid = p->PopValue(*state); |
||||||
|
lua_pushboolean(state, valid); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
static auto property_bind(lua_State* state) -> int { |
||||||
|
Property* p = check_property(state); |
||||||
|
luaL_checktype(state, 2, LUA_TFUNCTION); |
||||||
|
|
||||||
|
// Copy the function, as we need to invoke it then store our reference.
|
||||||
|
lua_pushvalue(state, 2); |
||||||
|
// ...and another copy, since we return the original closure.
|
||||||
|
lua_pushvalue(state, 2); |
||||||
|
|
||||||
|
// FIXME: This should ideally be lua_pcall, for safety.
|
||||||
|
p->PushValue(*state); |
||||||
|
lua_call(state, 1, 0); // Invoke the initial binding.
|
||||||
|
|
||||||
|
lua_pushstring(state, kBindingsTable); |
||||||
|
lua_gettable(state, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable]
|
||||||
|
lua_insert(state, -2); // Move bindings to the bottom, with fn above.
|
||||||
|
int ref = luaL_ref(state, -2); // bindings[ref] = fn
|
||||||
|
|
||||||
|
p->AddLuaBinding(state, ref); |
||||||
|
|
||||||
|
// Pop the bindings table, leaving one of the copiesw of the callback fn at
|
||||||
|
// the top of the stack.
|
||||||
|
lua_pop(state, 1); |
||||||
|
|
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
static const struct luaL_Reg kPropertyBindingFuncs[] = {{"get", property_get}, |
||||||
|
{"set", property_set}, |
||||||
|
{"bind", property_bind}, |
||||||
|
{NULL, NULL}}; |
||||||
|
|
||||||
|
PropertyBindings::PropertyBindings(lua_State& s) { |
||||||
|
// Create the metatable responsible for the Property API.
|
||||||
|
luaL_newmetatable(&s, kMetatableName); |
||||||
|
|
||||||
|
lua_pushliteral(&s, "__index"); |
||||||
|
lua_pushvalue(&s, -2); |
||||||
|
lua_settable(&s, -3); // metatable.__index = metatable
|
||||||
|
|
||||||
|
// Add our binding funcs (get, set, bind) to the metatable.
|
||||||
|
luaL_setfuncs(&s, kPropertyBindingFuncs, 0); |
||||||
|
|
||||||
|
// Create a weak table in the registry to hold live bindings.
|
||||||
|
lua_pushstring(&s, kBindingsTable); |
||||||
|
lua_newtable(&s); // bindings = {}
|
||||||
|
|
||||||
|
// Metatable for the weak table. Values are weak.
|
||||||
|
lua_newtable(&s); // meta = {}
|
||||||
|
lua_pushliteral(&s, "__mode"); |
||||||
|
lua_pushliteral(&s, "v"); |
||||||
|
lua_settable(&s, -3); // meta.__mode='v'
|
||||||
|
lua_setmetatable(&s, -2); // setmetatable(bindings, meta)
|
||||||
|
|
||||||
|
lua_settable(&s, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable] = bindings
|
||||||
|
} |
||||||
|
|
||||||
|
auto PropertyBindings::Register(lua_State* s, Property* prop) -> void { |
||||||
|
Property** data = |
||||||
|
reinterpret_cast<Property**>(lua_newuserdata(s, sizeof(Property*))); |
||||||
|
*data = prop; |
||||||
|
|
||||||
|
luaL_setmetatable(s, kMetatableName); |
||||||
|
} |
||||||
|
|
||||||
|
template <class... Ts> |
||||||
|
inline constexpr bool always_false_v = false; |
||||||
|
|
||||||
|
Property::Property(const LuaValue& val) : value_(val), cb_() {} |
||||||
|
|
||||||
|
Property::Property(const LuaValue& val, |
||||||
|
std::function<bool(const LuaValue& val)> cb) |
||||||
|
: value_(val), cb_(cb) {} |
||||||
|
|
||||||
|
auto Property::PushValue(lua_State& s) -> int { |
||||||
|
std::visit( |
||||||
|
[&](auto&& arg) { |
||||||
|
using T = std::decay_t<decltype(arg)>; |
||||||
|
if constexpr (std::is_same_v<T, std::monostate>) { |
||||||
|
lua_pushnil(&s); |
||||||
|
} else if constexpr (std::is_same_v<T, int>) { |
||||||
|
lua_pushinteger(&s, arg); |
||||||
|
} else if constexpr (std::is_same_v<T, float>) { |
||||||
|
lua_pushnumber(&s, arg); |
||||||
|
} else if constexpr (std::is_same_v<T, bool>) { |
||||||
|
lua_pushboolean(&s, arg); |
||||||
|
} else if constexpr (std::is_same_v<T, std::string>) { |
||||||
|
lua_pushstring(&s, arg.c_str()); |
||||||
|
} else { |
||||||
|
static_assert(always_false_v<T>, "PushValue missing type"); |
||||||
|
} |
||||||
|
}, |
||||||
|
value_); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
auto Property::PopValue(lua_State& s) -> bool { |
||||||
|
LuaValue new_val; |
||||||
|
switch (lua_type(&s, 2)) { |
||||||
|
case LUA_TNIL: |
||||||
|
new_val = std::monostate{}; |
||||||
|
break; |
||||||
|
case LUA_TNUMBER: |
||||||
|
if (lua_isinteger(&s, 2)) { |
||||||
|
new_val = lua_tointeger(&s, 2); |
||||||
|
} else { |
||||||
|
new_val = lua_tonumber(&s, 2); |
||||||
|
} |
||||||
|
break; |
||||||
|
case LUA_TBOOLEAN: |
||||||
|
new_val = lua_toboolean(&s, 2); |
||||||
|
break; |
||||||
|
case LUA_TSTRING: |
||||||
|
new_val = lua_tostring(&s, 2); |
||||||
|
break; |
||||||
|
default: |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
if (cb_ && std::invoke(*cb_, new_val)) { |
||||||
|
Update(new_val); |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
auto Property::Update(const LuaValue& v) -> void { |
||||||
|
value_ = v; |
||||||
|
|
||||||
|
for (int i = bindings_.size() - 1; i >= 0; i--) { |
||||||
|
auto& b = bindings_[i]; |
||||||
|
|
||||||
|
lua_pushstring(b.first, kBindingsTable); |
||||||
|
lua_gettable(b.first, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable]
|
||||||
|
int type = lua_rawgeti(b.first, -1, b.second); // push bindings[i]
|
||||||
|
|
||||||
|
// Has closure has been GCed?
|
||||||
|
if (type == LUA_TNIL) { |
||||||
|
// Clean up after ourselves.
|
||||||
|
lua_pop(b.first, 1); |
||||||
|
// Remove the binding.
|
||||||
|
bindings_.erase(bindings_.begin() + i); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
PushValue(*b.first); // push the argument
|
||||||
|
lua_call(b.first, 1, 0); // invoke the closure
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
auto Property::AddLuaBinding(lua_State* state, int ref) -> void { |
||||||
|
bindings_.push_back({state, ref}); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace lua
|