| #ifdef WITH_RIVE_SCRIPTING | |
| #include "lua.h" | |
| #include "rive/renderer.hpp" | |
| #include "rive/rive_types.hpp" | |
| #include "rive/shapes/paint/blend_mode.hpp" | |
| #include "rive/shapes/paint/fill.hpp" | |
| #include "rive/shapes/paint/stroke.hpp" | |
| #include "rive/shapes/paint/solid_color.hpp" | |
| #include "rive/shapes/paint/feather.hpp" | |
| #include "lualib.h" | |
| #include "rive/lua/rive_lua_libs.hpp" | |
| #include "rive/factory.hpp" | |
| using namespace rive; | |
| static void readStyle(lua_State* L, ScriptedPaint* paint, int index) | |
| { | |
| int atom; | |
| const char* styleName = lua_tostringatom(L, index, &atom); | |
| if (!styleName) | |
| { | |
| luaL_typeerrorL(L, index, lua_typename(L, LUA_TSTRING)); | |
| } | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::stroke: | |
| paint->style(RenderPaintStyle::stroke); | |
| break; | |
| case (int)LuaAtoms::fill: | |
| paint->style(RenderPaintStyle::fill); | |
| break; | |
| default: | |
| luaL_error(L, "'%s' is not a valid PaintStyle", styleName); | |
| break; | |
| } | |
| } | |
| static void readJoin(lua_State* L, ScriptedPaint* paint, int index) | |
| { | |
| int atom; | |
| const char* joinName = lua_tostringatom(L, index, &atom); | |
| if (!joinName) | |
| { | |
| luaL_typeerrorL(L, index, lua_typename(L, LUA_TSTRING)); | |
| } | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::miter: | |
| paint->join(StrokeJoin::miter); | |
| break; | |
| case (int)LuaAtoms::round: | |
| paint->join(StrokeJoin::round); | |
| break; | |
| case (int)LuaAtoms::bevel: | |
| paint->join(StrokeJoin::bevel); | |
| break; | |
| default: | |
| luaL_error(L, "'%s' is not a valid StrokeJoin", joinName); | |
| break; | |
| } | |
| } | |
| static void readCap(lua_State* L, ScriptedPaint* paint, int index) | |
| { | |
| int atom; | |
| const char* capName = lua_tostringatom(L, index, &atom); | |
| if (!capName) | |
| { | |
| luaL_typeerrorL(L, index, lua_typename(L, LUA_TSTRING)); | |
| } | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::butt: | |
| paint->cap(StrokeCap::butt); | |
| break; | |
| case (int)LuaAtoms::round: | |
| paint->cap(StrokeCap::round); | |
| break; | |
| case (int)LuaAtoms::square: | |
| paint->cap(StrokeCap::square); | |
| break; | |
| default: | |
| luaL_error(L, "'%s' is not a valid StrokeCap", capName); | |
| break; | |
| } | |
| } | |
| BlendMode rive::lua_toblendmode(lua_State* L, int idx) | |
| { | |
| int atom; | |
| const char* blendName = lua_tostringatom(L, idx, &atom); | |
| if (!blendName) | |
| { | |
| luaL_typeerrorL(L, idx, lua_typename(L, LUA_TSTRING)); | |
| } | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::srcOver: | |
| return BlendMode::srcOver; | |
| case (int)LuaAtoms::screen: | |
| return BlendMode::screen; | |
| case (int)LuaAtoms::overlay: | |
| return BlendMode::overlay; | |
| case (int)LuaAtoms::darken: | |
| return BlendMode::darken; | |
| case (int)LuaAtoms::lighten: | |
| return BlendMode::lighten; | |
| case (int)LuaAtoms::colorDodge: | |
| return BlendMode::colorDodge; | |
| case (int)LuaAtoms::colorBurn: | |
| return BlendMode::colorBurn; | |
| case (int)LuaAtoms::hardLight: | |
| return BlendMode::hardLight; | |
| case (int)LuaAtoms::softLight: | |
| return BlendMode::softLight; | |
| case (int)LuaAtoms::difference: | |
| return BlendMode::difference; | |
| case (int)LuaAtoms::exclusion: | |
| return BlendMode::exclusion; | |
| case (int)LuaAtoms::multiply: | |
| return BlendMode::multiply; | |
| case (int)LuaAtoms::hue: | |
| return BlendMode::hue; | |
| case (int)LuaAtoms::saturation: | |
| return BlendMode::saturation; | |
| case (int)LuaAtoms::color: | |
| return BlendMode::color; | |
| case (int)LuaAtoms::luminosity: | |
| return BlendMode::luminosity; | |
| default: | |
| luaL_error(L, "'%s' is not a valid BlendMode", blendName); | |
| return BlendMode::srcOver; | |
| } | |
| } | |
| static void readBlendMode(lua_State* L, ScriptedPaint* paint, int index) | |
| { | |
| auto blendMode = lua_toblendmode(L, index); | |
| paint->blendMode(blendMode); | |
| } | |
| ScriptedPaintData::ScriptedPaintData() {} | |
| ScriptedPaintData::ScriptedPaintData(const ShapePaint* shapePaint) | |
| { | |
| if (shapePaint->is<Fill>()) | |
| { | |
| style(RenderPaintStyle::fill); | |
| } | |
| else if (shapePaint->is<Stroke>()) | |
| { | |
| auto stroke = shapePaint->as<Stroke>(); | |
| style(RenderPaintStyle::stroke); | |
| thickness(stroke->thickness()); | |
| cap((StrokeCap)stroke->cap()); | |
| join((StrokeJoin)stroke->join()); | |
| } | |
| for (auto& child : shapePaint->children()) | |
| { | |
| if (child->is<SolidColor>()) | |
| { | |
| color(child->as<SolidColor>()->colorValue()); | |
| break; | |
| } | |
| } | |
| if (shapePaint->feather()) | |
| { | |
| feather(shapePaint->feather()->strength()); | |
| } | |
| blendMode((BlendMode)shapePaint->blendModeValue()); | |
| } | |
| ScriptedPaint::ScriptedPaint(Factory* factory) : | |
| renderPaint(factory->makeRenderPaint()) | |
| {} | |
| ScriptedPaint::ScriptedPaint(Factory* factory, const ScriptedPaint& source) : | |
| ScriptedPaint(factory) | |
| { | |
| style(source.m_style); | |
| color(source.m_color); | |
| thickness(source.m_thickness); | |
| join(source.m_join); | |
| cap(source.m_cap); | |
| feather(source.m_feather); | |
| blendMode(source.m_blendMode); | |
| gradient(source.m_gradient); | |
| } | |
| static bool paint_set_value(lua_State* L, | |
| ScriptedPaint* renderPaint, | |
| int keyAtom, | |
| int valueIndex) | |
| { | |
| switch (keyAtom) | |
| { | |
| case (int)LuaAtoms::style: | |
| readStyle(L, renderPaint, valueIndex); | |
| return true; | |
| case (int)LuaAtoms::join: | |
| readJoin(L, renderPaint, valueIndex); | |
| return true; | |
| case (int)LuaAtoms::cap: | |
| readCap(L, renderPaint, valueIndex); | |
| return true; | |
| case (int)LuaAtoms::thickness: | |
| renderPaint->thickness(float(luaL_checknumber(L, valueIndex))); | |
| return true; | |
| case (int)LuaAtoms::blendMode: | |
| readBlendMode(L, renderPaint, valueIndex); | |
| return true; | |
| case (int)LuaAtoms::feather: | |
| renderPaint->feather(float(luaL_checknumber(L, valueIndex))); | |
| return true; | |
| case (int)LuaAtoms::gradient: | |
| { | |
| ScriptedGradient* gradient = | |
| lua_torive<ScriptedGradient>(L, valueIndex, true); | |
| if (gradient != nullptr) | |
| { | |
| renderPaint->gradient(gradient->shader); | |
| } | |
| else | |
| { | |
| renderPaint->gradient(nullptr); | |
| } | |
| return true; | |
| } | |
| case (int)LuaAtoms::color: | |
| renderPaint->color(luaL_checkunsigned(L, valueIndex)); | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| static void setPropertiesFromDefinitionTable(lua_State* L, | |
| ScriptedPaint* scriptedPaint, | |
| int tableIndex) | |
| { | |
| luaL_checktype(L, tableIndex, LUA_TTABLE); | |
| // iterate table | |
| // Push another reference to the table on top of the stack (so we know | |
| // where it is, and this function can work for negative, positive and | |
| // pseudo indices | |
| lua_pushvalue(L, tableIndex); | |
| // stack now contains: -1 => table | |
| lua_pushnil(L); | |
| // stack now contains: -1 => nil; -2 => table | |
| while (lua_next(L, -2)) | |
| { | |
| // stack now contains: -1 => value; -2 => key; -3 => table | |
| // copy the key so that lua_tostring does not modify the original | |
| // lua_pushvalue(L, -2); | |
| // stack now contains: -1 => key; -2 => value; -3 => key; -4 => | |
| // table | |
| int atom; | |
| const char* key = lua_tostringatom(L, -2, &atom); | |
| if (key) | |
| { | |
| paint_set_value(L, scriptedPaint, atom, -1); | |
| } | |
| lua_pop(L, 1); | |
| // stack now contains: -1 => key; -2 => table | |
| } | |
| // stack now contains: -1 => table (when lua_next returns 0 it pops the | |
| // key but does not push anything.) Pop table | |
| lua_pop(L, 1); | |
| } | |
| static int paint_new(lua_State* L) | |
| { | |
| ScriptingContext* context = | |
| static_cast<ScriptingContext*>(lua_getthreaddata(L)); | |
| lua_newrive<ScriptedPaint>(L, context->factory()); | |
| return 1; | |
| } | |
| static int paint_with(lua_State* L) | |
| { | |
| ScriptingContext* context = | |
| static_cast<ScriptingContext*>(lua_getthreaddata(L)); | |
| auto scriptedPaint = lua_newrive<ScriptedPaint>(L, context->factory()); | |
| setPropertiesFromDefinitionTable(L, scriptedPaint, 1); | |
| return 1; | |
| } | |
| static int paint_newindex(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto scriptedPaint = lua_torive<ScriptedPaint>(L, 1); | |
| paint_set_value(L, scriptedPaint, atom, 3); | |
| return 0; | |
| } | |
| void ScriptedPaintData::pushStyle(lua_State* L) | |
| { | |
| switch (m_style) | |
| { | |
| case RenderPaintStyle::fill: | |
| lua_pushstring(L, "fill"); | |
| break; | |
| case RenderPaintStyle::stroke: | |
| lua_pushstring(L, "stroke"); | |
| break; | |
| default: | |
| RIVE_UNREACHABLE(); | |
| } | |
| } | |
| void ScriptedPaintData::pushJoin(lua_State* L) | |
| { | |
| switch (m_join) | |
| { | |
| case StrokeJoin::miter: | |
| lua_pushstring(L, "miter"); | |
| break; | |
| case StrokeJoin::bevel: | |
| lua_pushstring(L, "bevel"); | |
| break; | |
| case StrokeJoin::round: | |
| lua_pushstring(L, "round"); | |
| break; | |
| default: | |
| RIVE_UNREACHABLE(); | |
| } | |
| } | |
| void ScriptedPaintData::pushCap(lua_State* L) | |
| { | |
| switch (m_cap) | |
| { | |
| case StrokeCap::butt: | |
| lua_pushstring(L, "butt"); | |
| break; | |
| case StrokeCap::square: | |
| lua_pushstring(L, "square"); | |
| break; | |
| case StrokeCap::round: | |
| lua_pushstring(L, "round"); | |
| break; | |
| default: | |
| RIVE_UNREACHABLE(); | |
| } | |
| } | |
| void ScriptedPaintData::pushThickness(lua_State* L) | |
| { | |
| lua_pushnumber(L, m_thickness); | |
| } | |
| void ScriptedPaintData::pushBlendMode(lua_State* L) | |
| { | |
| switch (m_blendMode) | |
| { | |
| case BlendMode::srcOver: | |
| lua_pushstring(L, "srcOver"); | |
| break; | |
| case BlendMode::screen: | |
| lua_pushstring(L, "screen"); | |
| break; | |
| case BlendMode::overlay: | |
| lua_pushstring(L, "overlay"); | |
| break; | |
| case BlendMode::darken: | |
| lua_pushstring(L, "darken"); | |
| break; | |
| case BlendMode::lighten: | |
| lua_pushstring(L, "lighten"); | |
| break; | |
| case BlendMode::colorDodge: | |
| lua_pushstring(L, "colorDodge"); | |
| break; | |
| case BlendMode::colorBurn: | |
| lua_pushstring(L, "colorBurn"); | |
| break; | |
| case BlendMode::hardLight: | |
| lua_pushstring(L, "hardLight"); | |
| break; | |
| case BlendMode::softLight: | |
| lua_pushstring(L, "softLight"); | |
| break; | |
| case BlendMode::difference: | |
| lua_pushstring(L, "difference"); | |
| break; | |
| case BlendMode::exclusion: | |
| lua_pushstring(L, "exclusion"); | |
| break; | |
| case BlendMode::multiply: | |
| lua_pushstring(L, "multiply"); | |
| break; | |
| case BlendMode::hue: | |
| lua_pushstring(L, "hue"); | |
| break; | |
| case BlendMode::saturation: | |
| lua_pushstring(L, "saturation"); | |
| break; | |
| case BlendMode::color: | |
| lua_pushstring(L, "color"); | |
| break; | |
| case BlendMode::luminosity: | |
| lua_pushstring(L, "luminosity"); | |
| break; | |
| default: | |
| RIVE_UNREACHABLE(); | |
| } | |
| } | |
| void ScriptedPaintData::pushFeather(lua_State* L) | |
| { | |
| lua_pushnumber(L, m_feather); | |
| } | |
| void ScriptedPaintData::pushGradient(lua_State* L) | |
| { | |
| if (m_gradient) | |
| { | |
| ScriptedGradient* gradient = lua_newrive<ScriptedGradient>(L); | |
| gradient->shader = m_gradient; | |
| } | |
| else | |
| { | |
| lua_pushnil(L); | |
| } | |
| } | |
| void ScriptedPaintData::pushColor(lua_State* L) | |
| { | |
| lua_pushunsigned(L, m_color); | |
| } | |
| static int paint_index(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto scriptedPaint = (ScriptedPaintData*)lua_touserdata(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::style: | |
| scriptedPaint->pushStyle(L); | |
| return 1; | |
| case (int)LuaAtoms::join: | |
| scriptedPaint->pushJoin(L); | |
| return 1; | |
| case (int)LuaAtoms::cap: | |
| scriptedPaint->pushCap(L); | |
| return 1; | |
| case (int)LuaAtoms::thickness: | |
| scriptedPaint->pushThickness(L); | |
| return 1; | |
| case (int)LuaAtoms::blendMode: | |
| scriptedPaint->pushBlendMode(L); | |
| return 1; | |
| case (int)LuaAtoms::feather: | |
| scriptedPaint->pushFeather(L); | |
| return 1; | |
| case (int)LuaAtoms::gradient: | |
| scriptedPaint->pushGradient(L); | |
| return 1; | |
| case (int)LuaAtoms::color: | |
| scriptedPaint->pushColor(L); | |
| return 1; | |
| default: | |
| return 0; | |
| } | |
| } | |
| // Direct field getters bypass the __index Lua frame. They are dispatched by | |
| // LOP_GETTABLEKS via global_State::udatadirectfields[tag] when the field-name | |
| // string matches a registered handler. Slow path (paint_index) still handles | |
| // every other field, so anything that returns a string, userdata, or requires | |
| // allocation must NOT be registered here. | |
| static void paint_direct_thickness(void* udata, void* result) | |
| { | |
| lua_userdatadirectfield_setnumber(result, | |
| ((ScriptedPaintData*)udata)->thickness()); | |
| } | |
| static void paint_direct_feather(void* udata, void* result) | |
| { | |
| lua_userdatadirectfield_setnumber(result, | |
| ((ScriptedPaintData*)udata)->feather()); | |
| } | |
| static void paint_direct_color(void* udata, void* result) | |
| { | |
| // Matches the slow path: lua_pushunsigned stores ColorInt as lua_Number. | |
| lua_userdatadirectfield_setnumber( | |
| result, | |
| (double)((ScriptedPaintData*)udata)->color()); | |
| } | |
| static int paint_copy(lua_State* L) | |
| { | |
| int argCount = lua_gettop(L); | |
| auto scriptedPaint = lua_torive<ScriptedPaint>(L, 1); | |
| ScriptingContext* context = | |
| static_cast<ScriptingContext*>(lua_getthreaddata(L)); | |
| auto scriptedPaintCopy = | |
| lua_newrive<ScriptedPaint>(L, context->factory(), *scriptedPaint); | |
| if (argCount == 2) | |
| { | |
| setPropertiesFromDefinitionTable(L, scriptedPaintCopy, 2); | |
| } | |
| return 1; | |
| } | |
| static int paint_namecall(lua_State* L) | |
| { | |
| int atom; | |
| const char* str = lua_namecallatom(L, &atom); | |
| if (str != nullptr) | |
| { | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::copy: | |
| return paint_copy(L); | |
| } | |
| } | |
| luaL_error(L, | |
| "%s is not a valid method of %s", | |
| str, | |
| ScriptedMat2D::luaName); | |
| return 0; | |
| } | |
| static const luaL_Reg paintStaticMethods[] = { | |
| {"new", paint_new}, | |
| {"with", paint_with}, | |
| {NULL, NULL}, | |
| }; | |
| static void registerPaintDirectFieldGetters(lua_State* L, int tag) | |
| { | |
| lua_registeruserdatadirectfieldget(L, | |
| tag, | |
| "thickness", | |
| paint_direct_thickness); | |
| lua_registeruserdatadirectfieldget(L, tag, "feather", paint_direct_feather); | |
| lua_registeruserdatadirectfieldget(L, tag, "color", paint_direct_color); | |
| } | |
| int luaopen_rive_paint(lua_State* L) | |
| { | |
| lua_register_rive<ScriptedPaintData>(L); | |
| lua_pushcfunction(L, paint_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| registerPaintDirectFieldGetters(L, ScriptedPaintData::luaTag); | |
| luaL_register(L, ScriptedPaint::luaName, paintStaticMethods); | |
| lua_register_rive<ScriptedPaint>(L); | |
| lua_pushcfunction(L, paint_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, paint_newindex, nullptr); | |
| lua_setfield(L, -2, "__newindex"); | |
| lua_pushcfunction(L, paint_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| registerPaintDirectFieldGetters(L, ScriptedPaint::luaTag); | |
| return 1; | |
| } | |
| #endif |