Initial flame_lua_runtime package

This commit is contained in:
gem
2026-06-07 22:53:58 +08:00
commit 733b2fb798
262 changed files with 28439 additions and 0 deletions

17
.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
# Dart / Flutter generated files
.dart_tool/
.packages
.flutter-plugins
.flutter-plugins-dependencies
pubspec.lock
build/
coverage/
# IDE / OS
*.iml
.idea/
.vscode/
.DS_Store
# Logs
*.log

4
.pubignore Normal file
View File

@@ -0,0 +1,4 @@
build/
.dart_tool/
.packages
pubspec.lock

9
CHANGELOG.md Normal file
View File

@@ -0,0 +1,9 @@
# Changelog
## 0.1.0
- Initial extracted package skeleton for `flame_lua_runtime`.
- Added public API barrel with `LuaGameWidget`, `FlameLuaGame`, `RuntimeOptions`, package repositories, and script engine interfaces.
- Added shared Runtime Lua helper assets.
- Added manifest-driven package/resource/module loading support.
- Added Runtime commands, rendering nodes, package activation, and Lua bridge infrastructure.

29
LICENSE Normal file
View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2026, Flame Lua Runtime contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

103
README.md Normal file
View File

@@ -0,0 +1,103 @@
# flame_lua_runtime
A Flutter + Flame + Lua runtime kit for manifest-driven 2D game packages.
The package provides a reusable runtime boundary:
```text
RuntimeEvent -> Lua -> GameDiff / RuntimeCommand -> Flame
```
It is designed for Flutter apps that want to host Lua-authored 2D games or interactive showcases while keeping the Flutter/Flame side generic.
## Features
- `LuaGameWidget` for embedding a Lua game package in a Flutter app.
- Manifest-driven Lua module, resource, audio, and package loading.
- Controlled Lua modularization through `runtime.import(moduleName)`.
- Generic Runtime nodes for panels, text, sprites, buttons, particles, Spine, and list views.
- Runtime commands for movement, fading, scaling, rotation, sequencing, audio, resources, toast, clipboard, and Spine animation.
- Shared Lua helper modules under `assets/runtime/lua/`.
- Configurable Runtime Lua asset root via `RuntimeOptions.runtimeLuaRoot`.
## Example
This package includes a runnable Flutter showcase app under `example/`:
```bash
cd example
flutter run --dart-define=LUA_GAME_ID=showcase
```
Other bundled example packages can be selected with:
```bash
flutter run --dart-define=LUA_GAME_ID=template
flutter run --dart-define=LUA_GAME_ID=ludo
flutter run --dart-define=LUA_GAME_ID=flight
```
## Quick start
Add the package to your app:
```yaml
dependencies:
flame_lua_runtime: ^0.1.0
```
Embed a game package:
```dart
import 'package:flame_lua_runtime/flame_lua_runtime.dart';
LuaGameWidget(
gameId: 'template',
runtimeOptions: const RuntimeOptions(
runtimeLuaRoot: 'packages/flame_lua_runtime/assets/runtime/lua',
),
)
```
Your app should provide game package assets such as:
```text
assets/games/template/manifest.json
assets/games/template/scripts/main.lua
assets/games/template/scripts/state.lua
assets/games/template/scripts/ui.lua
```
The game manifest declares package-local scripts and shared Runtime Lua modules:
```json
{
"modules": {
"main": "scripts/main.lua",
"runtime_ui": "runtime:runtime_ui.lua",
"runtime_widgets": "runtime:runtime_widgets.lua",
"runtime_commands": "runtime:runtime_commands.lua",
"layout": "runtime:layout.lua"
}
}
```
## Runtime asset path
When used as a published package, configure:
```dart
const RuntimeOptions(
runtimeLuaRoot: 'packages/flame_lua_runtime/assets/runtime/lua',
)
```
For source-tree development, the default remains:
```dart
RuntimeOptions.defaultRuntimeLuaRoot // assets/runtime/lua
```
## Status
This package is in early extraction stage. Public API is intentionally small and centered on `LuaGameWidget`, `FlameLuaGame`, `RuntimeOptions`, package repositories, and script engine interfaces.

28
analysis_options.yaml Normal file
View File

@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@@ -0,0 +1,292 @@
---@alias RuntimeLayoutAlign 'start'|'center'|'end'
---@class (exact) RuntimeLayoutItem
---@field node RuntimeNode
---@field marginLeft? number
---@field marginRight? number
---@field marginTop? number
---@field marginBottom? number
---@class RuntimeLayoutItemOpts
---@field margin? number
---@field mx? number
---@field my? number
---@field ml? number
---@field mr? number
---@field mt? number
---@field mb? number
---@field marginLeft? number
---@field marginRight? number
---@field marginTop? number
---@field marginBottom? number
---@class RuntimeLinearLayoutOpts
---@field x? number
---@field y? number
---@field width? number
---@field height? number
---@field gap? number
---@field align? RuntimeLayoutAlign
---@field padding? number
---@field paddingX? number
---@field paddingY? number
---@field px? number
---@field py? number
---@field paddingLeft? number
---@field paddingTop? number
---@class RuntimeBoxLayoutOpts: RuntimeLinearLayoutOpts
---@field rows? integer
---@field columns? integer
---@field cols? integer
---@field cellWidth? number
---@field cellHeight? number
---@field cellW? number
---@field cellH? number
---@field gapX? number
---@field gapY? number
---@field valign? RuntimeLayoutAlign
---@class RuntimeLayout
local layout = {}
---@param value any
---@param fallback number
---@return number
local function read_number(value, fallback)
if type(value) == "number" then
return value
end
return fallback
end
---@param opts? table
---@param key string
---@return number
local function spacing(opts, key)
opts = opts or {}
if key == "left" then
return read_number(opts.paddingLeft, read_number(opts.paddingX, read_number(opts.px, read_number(opts.padding, 0))))
end
if key == "top" then
return read_number(opts.paddingTop, read_number(opts.paddingY, read_number(opts.py, read_number(opts.padding, 0))))
end
return 0
end
---@param node RuntimeNode
---@param parent? string
local function apply_parent(node, parent)
if parent ~= nil and parent ~= "" then
node.parent = parent
elseif parent == "" then
node.parent = nil
end
end
---@param axis_size number
---@param item_size number
---@param align RuntimeLayoutAlign
---@return number
local function align_cross(axis_size, item_size, align)
if align == "center" then
return (axis_size - item_size) / 2
end
if align == "end" then
return axis_size - item_size
end
return 0
end
---@param value any
---@return RuntimeNode
local function node_of(value)
return value.node or value
end
---@param value any
---@param key string
---@return number
local function margin_of(value, key)
local all = read_number(value.margin, 0)
if key == "marginLeft" then
return read_number(value.marginLeft, read_number(value.ml, read_number(value.mx, all)))
end
if key == "marginRight" then
return read_number(value.marginRight, read_number(value.mr, read_number(value.mx, all)))
end
if key == "marginTop" then
return read_number(value.marginTop, read_number(value.mt, read_number(value.my, all)))
end
if key == "marginBottom" then
return read_number(value.marginBottom, read_number(value.mb, read_number(value.my, all)))
end
return 0
end
---@param node RuntimeNode
---@param opts? RuntimeLayoutItemOpts
---@return RuntimeLayoutItem
function layout.item(node, opts)
opts = opts or {}
return {
node = node,
marginLeft = margin_of(opts, "marginLeft"),
marginRight = margin_of(opts, "marginRight"),
marginTop = margin_of(opts, "marginTop"),
marginBottom = margin_of(opts, "marginBottom")
}
end
---@param origin RuntimePoint
---@param position RuntimePoint
---@return RuntimePoint
function layout.local_position(origin, position)
return {
x = position.x - origin.x,
y = position.y - origin.y
}
end
---@param parent? string
---@param items (RuntimeNode|RuntimeLayoutItem)[]
---@param opts? RuntimeLinearLayoutOpts
---@return RuntimeNode[]
function layout.row(parent, items, opts)
opts = opts or {}
local cursor = read_number(opts.x, 0) + spacing(opts, "left")
local base_y = read_number(opts.y, 0) + spacing(opts, "top")
local gap = read_number(opts.gap, 0)
local height = read_number(opts.height, 0)
local align = opts.align or "start"
local nodes = {}
for index, item in ipairs(items) do
local node = node_of(item)
cursor = cursor + margin_of(item, "marginLeft")
node.x = cursor
local item_height = read_number(node.height, 0)
node.y = base_y + align_cross(height, item_height, align)
apply_parent(node, parent)
cursor = cursor + read_number(node.width, 0) + margin_of(item, "marginRight") + gap
nodes[index] = node
end
return nodes
end
---@param parent? string
---@param items (RuntimeNode|RuntimeLayoutItem)[]
---@param opts? RuntimeLinearLayoutOpts
---@return RuntimeNode[]
function layout.column(parent, items, opts)
opts = opts or {}
local cursor = read_number(opts.y, 0) + spacing(opts, "top")
local base_x = read_number(opts.x, 0) + spacing(opts, "left")
local gap = read_number(opts.gap, 0)
local width = read_number(opts.width, 0)
local align = opts.align or "start"
local nodes = {}
for index, item in ipairs(items) do
local node = node_of(item)
cursor = cursor + margin_of(item, "marginTop")
node.y = cursor
local item_width = read_number(node.width, 0)
node.x = base_x + align_cross(width, item_width, align)
apply_parent(node, parent)
cursor = cursor + read_number(node.height, 0) + margin_of(item, "marginBottom") + gap
nodes[index] = node
end
return nodes
end
---@param parent? string
---@param items (RuntimeNode|RuntimeLayoutItem)[]
---@param opts? RuntimeLinearLayoutOpts
---@return RuntimeNode[]
function layout.stack(parent, items, opts)
opts = opts or {}
local offset_x = read_number(opts.x, 0) + spacing(opts, "left")
local offset_y = read_number(opts.y, 0) + spacing(opts, "top")
local nodes = {}
for index, item in ipairs(items) do
local node = node_of(item)
node.x = read_number(node.x, 0) + offset_x
node.y = read_number(node.y, 0) + offset_y
apply_parent(node, parent)
nodes[index] = node
end
return nodes
end
---@param parent? string
---@param items (RuntimeNode|RuntimeLayoutItem)[]
---@param opts? RuntimeBoxLayoutOpts
---@return RuntimeNode[]
function layout.box(parent, items, opts)
opts = opts or {}
local count = #items
local rows = math.floor(read_number(opts.rows, 0))
local columns = math.floor(read_number(opts.columns, read_number(opts.cols, 0)))
if columns <= 0 and rows > 0 then
columns = math.ceil(count / rows)
end
if columns <= 0 then
columns = count > 0 and count or 1
end
if rows <= 0 then
rows = math.ceil(count / columns)
end
local gap_x = read_number(opts.gapX, read_number(opts.gap, 0))
local gap_y = read_number(opts.gapY, read_number(opts.gap, 0))
local origin_x = read_number(opts.x, 0) + spacing(opts, "left")
local origin_y = read_number(opts.y, 0) + spacing(opts, "top")
local cell_w = read_number(opts.cellWidth, read_number(opts.cellW, 0))
local cell_h = read_number(opts.cellHeight, read_number(opts.cellH, 0))
local width = read_number(opts.width, 0)
local height = read_number(opts.height, 0)
if cell_w <= 0 and width > 0 then
cell_w = (width - gap_x * (columns - 1)) / columns
end
if cell_h <= 0 and height > 0 then
cell_h = (height - gap_y * (rows - 1)) / rows
end
local align = opts.align or "start"
local valign = opts.valign or "start"
local nodes = {}
for index, item in ipairs(items) do
local node = node_of(item)
local item_width = read_number(node.width, cell_w)
local item_height = read_number(node.height, cell_h)
local effective_cell_w = cell_w > 0 and cell_w or item_width
local effective_cell_h = cell_h > 0 and cell_h or item_height
local col = (index - 1) % columns
local row = math.floor((index - 1) / columns)
local margin_left = margin_of(item, "marginLeft")
local margin_right = margin_of(item, "marginRight")
local margin_top = margin_of(item, "marginTop")
local margin_bottom = margin_of(item, "marginBottom")
local inner_w = effective_cell_w - margin_left - margin_right
local inner_h = effective_cell_h - margin_top - margin_bottom
node.x = origin_x + col * (effective_cell_w + gap_x) + margin_left + align_cross(inner_w, item_width, align)
node.y = origin_y + row * (effective_cell_h + gap_y) + margin_top + align_cross(inner_h, item_height, valign)
apply_parent(node, parent)
nodes[index] = node
end
return nodes
end
return layout

View File

@@ -0,0 +1,261 @@
---@class (exact) RuntimeCommandOpts
---@field id? string
---@field group? string
---@field commandGroup? string
---@field scope? string
---@field onComplete? string
---@field duration? number
---@class (exact) RuntimeAudioCommandOpts: RuntimeCommandOpts
---@field volume? number
---@class (exact) RuntimeBgmCommandOpts: RuntimeAudioCommandOpts
---@field channel? string
---@field loop? boolean
---@class (exact) RuntimeSpineCommandOpts: RuntimeCommandOpts
---@field track? integer
---@field loop? boolean
---@field queue? boolean
---@field delay? number
---@class (exact) RuntimeResourceCommandOpts: RuntimeCommandOpts
---@field failOnError? boolean
---@class RuntimeCommands
local commands = {}
---@param opts? table
---@return table
local function copy_opts(opts)
local result = {}
if opts ~= nil then
for key, value in pairs(opts) do
result[key] = value
end
end
return result
end
---@param command_type RuntimeCommandType
---@param opts? table
---@return RuntimeCommand
local function with_type(command_type, opts)
local command = copy_opts(opts)
command.type = command_type
return command
end
---@param text string
---@param opts? RuntimeCommandOpts
---@return RuntimeCommand
function commands.toast(text, opts)
local command = with_type("toast", opts)
command.text = text
return command
end
---@param text string
---@param opts? RuntimeCommandOpts
---@return RuntimeCommand
function commands.copy_text(text, opts)
local command = with_type("copy_text", opts)
command.text = text
return command
end
---@param duration number
---@param opts? RuntimeCommandOpts
---@return RuntimeCommand
function commands.delay(duration, opts)
local command = with_type("delay", opts)
command.duration = duration
return command
end
---@param items RuntimeCommand[]
---@param opts? RuntimeCommandOpts
---@return RuntimeCommand
function commands.sequence(items, opts)
local command = with_type("sequence", opts)
command.commands = items or {}
return command
end
---@param items RuntimeCommand[]
---@param opts? RuntimeCommandOpts
---@return RuntimeCommand
function commands.parallel(items, opts)
local command = with_type("parallel", opts)
command.commands = items or {}
return command
end
---@param target string
---@param path RuntimePoint[]
---@param opts? RuntimeCommandOpts
---@return RuntimeCommand
function commands.move_path(target, path, opts)
local command = with_type("move_path", opts)
command.target = target
command.path = path or {}
return command
end
---@param target string
---@param x number
---@param y number
---@param opts? RuntimeCommandOpts
---@return RuntimeCommand
function commands.move_to(target, x, y, opts)
local command = with_type("move_to", opts)
command.target = target
command.x = x
command.y = y
return command
end
---@param target string
---@param alpha number
---@param opts? RuntimeCommandOpts
---@return RuntimeCommand
function commands.fade_to(target, alpha, opts)
local command = with_type("fade_to", opts)
command.target = target
command.alpha = alpha
return command
end
---@param target string
---@param scale number
---@param opts? RuntimeCommandOpts
---@return RuntimeCommand
function commands.scale_to(target, scale, opts)
local command = with_type("scale_to", opts)
command.target = target
command.scale = scale
return command
end
---@param target string
---@param angle number
---@param opts? RuntimeCommandOpts
---@return RuntimeCommand
function commands.rotate_to(target, angle, opts)
local command = with_type("rotate_to", opts)
command.target = target
command.angle = angle
return command
end
---@param target string
---@param opts? RuntimeCommandOpts
---@return RuntimeCommand
function commands.remove_node(target, opts)
local command = with_type("remove_node", opts)
command.target = target
return command
end
---@param target string
---@param animation string
---@param opts? RuntimeSpineCommandOpts
---@return RuntimeCommand
function commands.play_spine_animation(target, animation, opts)
local command = with_type("play_spine_animation", opts)
command.target = target
command.animation = animation
if command.loop == nil then
command.loop = true
end
return command
end
---@param asset string
---@param opts? RuntimeAudioCommandOpts
---@return RuntimeCommand
function commands.play_sound(asset, opts)
local command = with_type("play_sound", opts)
command.asset = asset
return command
end
---@param asset string
---@param opts? RuntimeBgmCommandOpts
---@return RuntimeCommand
function commands.play_bgm(asset, opts)
local command = with_type("play_bgm", opts)
command.asset = asset
if command.channel == nil then
command.channel = "bgm"
end
if command.loop == nil then
command.loop = true
end
return command
end
---@param channel? string
---@param opts? RuntimeCommandOpts
---@return RuntimeCommand
function commands.pause_bgm(channel, opts)
local command = with_type("pause_bgm", opts)
command.channel = channel or "bgm"
return command
end
---@param channel? string
---@param opts? RuntimeCommandOpts
---@return RuntimeCommand
function commands.resume_bgm(channel, opts)
local command = with_type("resume_bgm", opts)
command.channel = channel or "bgm"
return command
end
---@param channel? string
---@param opts? RuntimeCommandOpts
---@return RuntimeCommand
function commands.stop_bgm(channel, opts)
local command = with_type("stop_bgm", opts)
command.channel = channel or "bgm"
return command
end
---@param group string
---@param opts? RuntimeResourceCommandOpts
---@return RuntimeCommand
function commands.preload_group(group, opts)
local command = with_type("preload_resources", opts)
command.group = group
return command
end
---@param group string
---@param opts? RuntimeCommandOpts
---@return RuntimeCommand
function commands.evict_group(group, opts)
local command = with_type("evict_resources", opts)
command.group = group
return command
end
---@param id string
---@return RuntimeCommand
function commands.cancel_id(id)
return { type = "cancel_commands", id = id }
end
---@param group string
---@return RuntimeCommand
function commands.cancel_group(group)
return { type = "cancel_commands", group = group }
end
---@param scope string
---@return RuntimeCommand
function commands.cancel_scope(scope)
return { type = "cancel_commands", scope = scope }
end
return commands

View File

@@ -0,0 +1,427 @@
---@class RuntimeUi
local runtime_ui = {}
---@generic T: table
---@param base? T
---@param opts? table
---@return T
local function merge(base, opts)
local result = {}
for key, value in pairs(base or {}) do
result[key] = value
end
if opts ~= nil then
for key, value in pairs(opts) do
result[key] = value
end
end
return result
end
---@param value any
---@return boolean
local function is_table(value)
return type(value) == "table"
end
---@param opts? table
---@return table
local function normalize_opts(opts)
local source = opts or {}
local result = {}
for key, value in pairs(source) do
if key ~= "w" and key ~= "h" and key ~= "size" and key ~= "handler" and key ~= "onClick" then
result[key] = value
end
end
if result.width == nil then
result.width = source.w or source.size
end
if result.height == nil then
result.height = source.h or source.size
end
if result.onTap == nil then
result.onTap = source.handler or source.onClick
end
return result
end
---@param base table
---@param opts? table
---@return table
local function node_opts(base, opts)
return merge(base, normalize_opts(opts))
end
---@param base? RuntimeNodeProps
---@param opts? RuntimeNodeProps
---@return RuntimeNodeProps
function runtime_ui.style(base, opts)
return merge(base or {}, opts)
end
---@param parent string
---@param opts? RuntimeNodeProps
---@return RuntimeNodeProps
function runtime_ui.with_parent(parent, opts)
return merge(opts or {}, { parent = parent })
end
---@param node_type RuntimeNodeType
---@param id string
---@param opts? RuntimeNodeProps
---@return RuntimeNode
function runtime_ui.node(node_type, id, opts)
return node_opts({ id = id, type = node_type }, opts)
end
---@param id string
---@param x number|RuntimeNodeInit
---@param y? number
---@param width? number
---@param height? number
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function runtime_ui.panel(id, x, y, width, height, opts)
if is_table(x) then
return runtime_ui.node("panel", id, node_opts({ x = 0, y = 0, width = 0, height = 0 }, x))
end
return runtime_ui.node("panel", id, node_opts({
x = x,
y = y,
width = width,
height = height
}, opts))
end
---@param id string
---@param x number|RuntimeNodeInit
---@param y? number
---@param width? number
---@param height? number
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function runtime_ui.rect(id, x, y, width, height, opts)
if is_table(x) then
return runtime_ui.node("rect", id, node_opts({ x = 0, y = 0, width = 0, height = 0 }, x))
end
return runtime_ui.node("rect", id, node_opts({
x = x,
y = y,
width = width,
height = height
}, opts))
end
---@param id string
---@param x number|RuntimeNodeInit
---@param y? number
---@param size? number
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function runtime_ui.circle(id, x, y, size, opts)
if is_table(x) then
return runtime_ui.node("circle", id, node_opts({ x = 0, y = 0, width = 0, height = 0 }, x))
end
return runtime_ui.node("circle", id, node_opts({
x = x,
y = y,
width = size,
height = size
}, opts))
end
---@param id string
---@param x number|RuntimeNodeInit
---@param y? number
---@param width? number
---@param height? number
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function runtime_ui.line(id, x, y, width, height, opts)
if is_table(x) then
return runtime_ui.node("line", id, node_opts({ x = 0, y = 0, width = 0, height = 0 }, x))
end
return runtime_ui.node("line", id, node_opts({
x = x,
y = y,
width = width,
height = height
}, opts))
end
---@param id string
---@param x number|RuntimeNodeInit
---@param y? number
---@param width? number
---@param height? number
---@param value? number
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function runtime_ui.progress(id, x, y, width, height, value, opts)
if is_table(x) then
return runtime_ui.node("progress", id, node_opts({ x = 0, y = 0, width = 0, height = 0, value = 0 }, x))
end
return runtime_ui.node("progress", id, node_opts({
x = x,
y = y,
width = width,
height = height,
value = value
}, opts))
end
---@param id string
---@param x number|RuntimeNodeInit
---@param y? number
---@param width? number
---@param height? number
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function runtime_ui.particle(id, x, y, width, height, opts)
if is_table(x) then
return runtime_ui.node("particle", id, node_opts({ x = 0, y = 0, width = 0, height = 0 }, x))
end
return runtime_ui.node("particle", id, node_opts({
x = x,
y = y,
width = width,
height = height
}, opts))
end
---@param id string
---@param text string|RuntimeNodeInit
---@param x? number
---@param y? number
---@param width? number
---@param height? number
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function runtime_ui.text(id, text, x, y, width, height, opts)
if is_table(text) then
return runtime_ui.node("text", id, node_opts({ text = "", x = 0, y = 0, width = 0, height = 0 }, text))
end
return runtime_ui.node("text", id, node_opts({
text = text,
x = x,
y = y,
width = width,
height = height
}, opts))
end
---@param id string
---@param text string|RuntimeNodeInit
---@param x? number
---@param y? number
---@param width? number
---@param height? number
---@param handler? string
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function runtime_ui.button(id, text, x, y, width, height, handler, opts)
if is_table(text) then
return runtime_ui.node("button", id, node_opts({
text = "",
x = 0,
y = 0,
width = 0,
height = 0,
interactive = true
}, text))
end
return runtime_ui.node("button", id, node_opts({
text = text,
x = x,
y = y,
width = width,
height = height,
interactive = true,
onTap = handler
}, opts))
end
---@param id string
---@param x number|RuntimeNodeInit
---@param y? number
---@param width? number
---@param height? number
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function runtime_ui.list_view(id, x, y, width, height, opts)
if is_table(x) then
return runtime_ui.node("listView", id, node_opts({ x = 0, y = 0, width = 0, height = 0 }, x))
end
return runtime_ui.node("listView", id, node_opts({
x = x,
y = y,
width = width,
height = height
}, opts))
end
---@param id string
---@param asset string|RuntimeNodeInit
---@param x? number
---@param y? number
---@param width? number
---@param height? number
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function runtime_ui.image(id, asset, x, y, width, height, opts)
if is_table(asset) then
return runtime_ui.node("image", id, node_opts({ asset = "", x = 0, y = 0, width = 0, height = 0 }, asset))
end
return runtime_ui.node("image", id, node_opts({
asset = asset,
x = x,
y = y,
width = width,
height = height
}, opts))
end
---@param id string
---@param asset string|RuntimeNodeInit
---@param x? number
---@param y? number
---@param width? number
---@param height? number
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function runtime_ui.sprite(id, asset, x, y, width, height, opts)
if is_table(asset) then
return runtime_ui.node("sprite", id, node_opts({ asset = "", x = 0, y = 0, width = 0, height = 0 }, asset))
end
return runtime_ui.node("sprite", id, node_opts({
asset = asset,
x = x,
y = y,
width = width,
height = height
}, opts))
end
---@param id string
---@param asset string|RuntimeNodeInit
---@param x? number
---@param y? number
---@param width? number
---@param height? number
---@param animation? string
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function runtime_ui.spine(id, asset, x, y, width, height, animation, opts)
if is_table(asset) then
return runtime_ui.node("spine", id, node_opts({ asset = "", x = 0, y = 0, width = 0, height = 0, loop = true }, asset))
end
return runtime_ui.node("spine", id, node_opts({
asset = asset,
x = x,
y = y,
width = width,
height = height,
animation = animation,
loop = true
}, opts))
end
---@param id string
---@param props RuntimeNodeInit
---@return RuntimeNodeUpdate
function runtime_ui.update(id, props)
return { id = id, props = normalize_opts(props or {}) }
end
---@param id string
---@param text string
---@return RuntimeNodeUpdate
function runtime_ui.text_update(id, text)
return runtime_ui.update(id, { text = text })
end
---@param id string
---@param visible boolean
---@return RuntimeNodeUpdate
function runtime_ui.visible_update(id, visible)
return runtime_ui.update(id, { visible = visible })
end
---@param id string
---@param alpha number
---@return RuntimeNodeUpdate
function runtime_ui.alpha_update(id, alpha)
return runtime_ui.update(id, { alpha = alpha })
end
---@param id string
---@param scale number
---@return RuntimeNodeUpdate
function runtime_ui.scale_update(id, scale)
return runtime_ui.update(id, { scale = scale })
end
---@param id string
---@param x number
---@param y number
---@return RuntimeNodeUpdate
function runtime_ui.position_update(id, x, y)
return runtime_ui.update(id, { x = x, y = y })
end
---@param id string
---@param width number
---@param height number
---@return RuntimeNodeUpdate
function runtime_ui.size_update(id, width, height)
return runtime_ui.update(id, { width = width, height = height })
end
---@param id string
---@param x number
---@param y number
---@param scale number
---@param rotation number
---@return RuntimeNodeUpdate
function runtime_ui.transform_update(id, x, y, scale, rotation)
return runtime_ui.update(id, {
x = x,
y = y,
scale = scale,
rotation = rotation
})
end
---@param ids string[]
---@param props RuntimeNodeInit
---@return RuntimeNodeUpdate[]
function runtime_ui.batch_update(ids, props)
local updates = {}
for _, id in ipairs(ids) do
table.insert(updates, runtime_ui.update(id, props))
end
return updates
end
---@param nodes RuntimeNode[]
---@param node RuntimeNode
---@return RuntimeNode[]
function runtime_ui.append(nodes, node)
table.insert(nodes, node)
return nodes
end
---@param nodes RuntimeNode[]
---@param extra_nodes RuntimeNode[]
---@return RuntimeNode[]
function runtime_ui.append_all(nodes, extra_nodes)
for _, node in ipairs(extra_nodes) do
table.insert(nodes, node)
end
return nodes
end
return runtime_ui

View File

@@ -0,0 +1,773 @@
local runtime_ui = runtime.import("runtime_ui")
---@class (exact) RuntimeDialogButton
---@field id? string
---@field text string
---@field handler string
---@field color? string
---@class (exact) RuntimeDialogOpts
---@field screenWidth? number
---@field screenHeight? number
---@field overlay? boolean
---@field overlayColor? string
---@field blockInput? boolean
---@field layer? integer
---@field color? string
---@field radius? number
---@field panelStyle? RuntimeNodeProps
---@field titleColor? string
---@field titleSize? number
---@field titleStyle? RuntimeNodeProps
---@field messageColor? string
---@field messageSize? number
---@field messageStyle? RuntimeNodeProps
---@field buttons? RuntimeDialogButton[]
---@field buttonGap? number
---@field buttonStyle? RuntimeNodeProps
---@class (exact) RuntimeLabeledProgressOpts: RuntimeNodeProps
---@field labelHeight? number
---@field labelStyle? RuntimeNodeProps
---@class RuntimePillOpts: RuntimeNodeInit
---@field panelStyle? RuntimeNodeProps
---@field textStyle? RuntimeNodeProps
---@class RuntimeTextButtonOpts: RuntimeNodeInit
---@field variant? 'primary'|'secondary'|'ghost'
---@class RuntimeListItemOpts: RuntimeTextButtonOpts
---@field selected? boolean
---@field activeColor? string
---@field inactiveColor? string
---@class RuntimeTabItem
---@field id? string
---@field key? string
---@field text string
---@field handler? string
---@field selected? boolean
---@class RuntimeTabsOpts: RuntimeNodeInit
---@field tabs RuntimeTabItem[]
---@field selected? string
---@field gap? number
---@field itemWidth? number
---@field itemHeight? number
---@field activeColor? string
---@field inactiveColor? string
---@field buttonStyle? RuntimeNodeProps
---@class RuntimeActionItem
---@field id? string
---@field text string
---@field handler? string
---@field visible? boolean
---@field color? string
---@field style? RuntimeNodeProps
---@class RuntimeActionRowOpts: RuntimeNodeInit
---@field actions RuntimeActionItem[]
---@field gap? number
---@field itemWidth? number
---@field itemHeight? number
---@field buttonStyle? RuntimeNodeProps
---@class RuntimePanelHeaderOpts: RuntimeNodeInit
---@field eyebrow? string
---@field title string
---@field summary? string
---@field gap? number
---@field eyebrowId? string
---@field titleId? string
---@field summaryId? string
---@field eyebrowHeight? number
---@field titleHeight? number
---@field summaryHeight? number
---@field eyebrowStyle? RuntimeNodeProps
---@field titleStyle? RuntimeNodeProps
---@field summaryStyle? RuntimeNodeProps
---@class RuntimeWidgetTheme
---@field primary? string
---@field secondary? string
---@field success? string
---@field overlay? string
---@field surface? string
---@field surfaceAlt? string
---@field card? string
---@field text? string
---@field muted? string
---@field progress? string
---@field transparent? string
---@class RuntimeWidgets
local widgets = {}
-- -----------------------------------------------------------------------------
-- Theme/config
-- -----------------------------------------------------------------------------
---@type RuntimeWidgetTheme
local widget_theme = {
primary = "#ff2563eb",
secondary = "#ff475569",
success = "#ff22c55e",
overlay = "#99000000",
surface = "#ff1e293b",
surfaceAlt = "#ff334155",
card = "#ee111827",
text = "#ffffffff",
muted = "#ffe5e7eb",
progress = "#ff22c55e",
transparent = "#00000000"
}
-- -----------------------------------------------------------------------------
-- Internal helpers
-- -----------------------------------------------------------------------------
---@generic T: table
---@param base? T
---@param opts? table
---@return T
local function merge(base, opts)
local result = {}
for key, value in pairs(base or {}) do
result[key] = value
end
for key, value in pairs(opts or {}) do
result[key] = value
end
return result
end
---@param value any
---@return boolean
local function is_table(value)
return type(value) == "table"
end
---@param source? table
---@param keys string[]
---@return table
local function without_keys(source, keys)
local result = merge(source or {}, nil)
for _, key in ipairs(keys) do
result[key] = nil
end
return result
end
---@param opts? RuntimeNodeInit
---@return RuntimeNodeInit
local function normalize_box(opts)
local result = merge(opts or {}, nil)
if result.width == nil then
result.width = result.w or result.size
end
if result.height == nil then
result.height = result.h or result.size
end
result.w = nil
result.h = nil
result.size = nil
return result
end
---@param opts? RuntimeNodeInit
---@param fallback? table
---@return RuntimeNodeInit
local function with_box(opts, fallback)
return merge(fallback or {}, normalize_box(opts))
end
---@param target RuntimeNode[]
---@param source? RuntimeNode[]
---@return RuntimeNode[]
local function append_all(target, source)
for _, node in ipairs(source or {}) do
table.insert(target, node)
end
return target
end
---@param parent string
---@param opts? RuntimeNodeProps
---@return RuntimeNodeProps
local function child_opts(parent, opts)
return merge(opts or {}, { parent = parent })
end
---@param value table
---@param opts? table
---@param key string
---@return table, table
local function collection_args(value, opts, key)
local options = opts or {}
local items = value
if is_table(value) and value[key] ~= nil then
options = value
items = value[key]
end
return items or {}, options
end
---@param tokens? RuntimeWidgetTheme
---@return RuntimeWidgets
function widgets.configure(tokens)
widget_theme = merge(widget_theme, tokens or {})
return widgets
end
---@param name string
---@return string
local function theme_color(name)
return widget_theme[name] or "#ffffffff"
end
-- -----------------------------------------------------------------------------
-- Text helpers
-- -----------------------------------------------------------------------------
---@param id string
---@param text string|RuntimeNodeInit
---@param x? number
---@param y? number
---@param width? number
---@param height? number
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function widgets.label(id, text, x, y, width, height, opts)
local fallback = { text = "", x = 0, y = 0, width = 0, height = 0 }
local options
if is_table(text) then
options = with_box(text, fallback)
else
options = with_box(merge({ text = text, x = x, y = y, width = width, height = height }, opts), fallback)
end
return runtime_ui.text(id, merge({
color = theme_color("muted"),
fontSize = 14,
textAlign = "left"
}, options))
end
---@param id string
---@param text string|RuntimeNodeInit
---@param x? number
---@param y? number
---@param width? number
---@param height? number
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function widgets.section_title(id, text, x, y, width, height, opts)
local options
if is_table(text) then
options = text
else
options = merge({ text = text, x = x, y = y, width = width, height = height }, opts)
end
return widgets.label(id, merge({
color = theme_color("text"),
fontSize = 18,
textAlign = "left"
}, options))
end
-- -----------------------------------------------------------------------------
-- Badges and buttons
-- -----------------------------------------------------------------------------
---@param id string
---@param text string|RuntimePillOpts
---@param x? number
---@param y? number
---@param width? number
---@param height? number
---@param opts? RuntimePillOpts
---@return RuntimeNode[]
function widgets.pill(id, text, x, y, width, height, opts)
local options
if is_table(text) then
options = with_box(text, { text = "", x = 0, y = 0, width = 0, height = 0 })
else
options = with_box(merge({ text = text, x = x, y = y, width = width, height = height }, opts), {
text = "",
x = 0,
y = 0,
width = 0,
height = 0
})
end
local panel_opts = merge({
color = theme_color("surfaceAlt"),
radius = options.height / 2
}, options.panelStyle)
panel_opts = merge(panel_opts, without_keys(options, { "text", "panelStyle", "textStyle" }))
local text_opts = child_opts(id, merge({
color = theme_color("text"),
fontSize = 12,
textAlign = "center"
}, options.textStyle))
return {
runtime_ui.panel(id, panel_opts),
runtime_ui.text(id .. "_text", merge({
text = options.text or "",
x = 0,
y = 0,
width = options.width,
height = options.height
}, text_opts))
}
end
---@param id string
---@param text string|RuntimeTextButtonOpts
---@param x? number
---@param y? number
---@param width? number
---@param height? number
---@param handler? string
---@param opts? RuntimeTextButtonOpts
---@return RuntimeNode
function widgets.text_button(id, text, x, y, width, height, handler, opts)
local options
if is_table(text) then
options = with_box(text, { text = "", x = 0, y = 0, width = 0, height = 0 })
else
options = with_box(merge({ text = text, x = x, y = y, width = width, height = height, handler = handler }, opts), {
text = "",
x = 0,
y = 0,
width = 0,
height = 0
})
end
local variant = options.variant or "primary"
local color = theme_color("primary")
if variant == "secondary" then
color = theme_color("secondary")
elseif variant == "ghost" then
color = theme_color("transparent")
end
return runtime_ui.button(id, merge({
color = color,
radius = 10,
fontSize = 13,
textAlign = "center"
}, without_keys(options, { "variant" })))
end
---@param id string
---@param text string|RuntimeListItemOpts
---@param x? number
---@param y? number
---@param width? number
---@param height? number
---@param handler? string
---@param opts? RuntimeListItemOpts
---@return RuntimeNode
function widgets.list_item(id, text, x, y, width, height, handler, opts)
local options
if is_table(text) then
options = with_box(text, { text = "", x = 0, y = 0, width = 0, height = 0 })
else
options = with_box(merge({ text = text, x = x, y = y, width = width, height = height, handler = handler }, opts), {
text = "",
x = 0,
y = 0,
width = 0,
height = 0
})
end
local selected = options.selected == true
local color = selected and (options.activeColor or theme_color("primary")) or (options.inactiveColor or theme_color("surface"))
return widgets.text_button(id, merge({
color = color,
radius = 10,
fontSize = 12
}, without_keys(options, { "selected", "activeColor", "inactiveColor" })))
end
---@param id string
---@param tabs RuntimeTabItem[]|RuntimeTabsOpts
---@param opts? RuntimeTabsOpts
---@return RuntimeNode[]
function widgets.tabs(id, tabs, opts)
local items, options = collection_args(tabs, opts, "tabs")
local origin_x = options.x or 0
local origin_y = options.y or 0
local gap = options.gap or 6
local item_w = options.itemWidth or options.width or options.w or 72
local item_h = options.itemHeight or options.height or options.h or 24
local nodes = {}
for index, tab in ipairs(items) do
local key = tab.key or tostring(index)
local selected = tab.selected == true or key == options.selected
local node_id = tab.id or (id .. "_" .. key)
local style = merge({
x = origin_x + (index - 1) * (item_w + gap),
y = origin_y,
w = item_w,
h = item_h,
text = tab.text or key,
handler = tab.handler or key,
selected = selected,
activeColor = options.activeColor,
inactiveColor = options.inactiveColor
}, options.buttonStyle)
style = merge(style, without_keys(options, {
"tabs",
"selected",
"gap",
"itemWidth",
"itemHeight",
"activeColor",
"inactiveColor",
"buttonStyle",
"x",
"y",
"width",
"height",
"w",
"h"
}))
nodes[index] = widgets.list_item(node_id, style)
end
return nodes
end
---@param id string
---@param actions RuntimeActionItem[]|RuntimeActionRowOpts
---@param opts? RuntimeActionRowOpts
---@return RuntimeNode[]
function widgets.action_row(id, actions, opts)
local items, options = collection_args(actions, opts, "actions")
local count = #items
local gap = options.gap or 8
local origin_x = options.x or 0
local origin_y = options.y or 0
local item_h = options.itemHeight or options.height or options.h or 32
local item_w = options.itemWidth
if item_w == nil and (options.width ~= nil or options.w ~= nil) and count > 0 then
item_w = ((options.width or options.w) - gap * (count - 1)) / count
end
item_w = item_w or 88
local nodes = {}
for index, action in ipairs(items) do
local visible = action.visible ~= false
local style = merge({
x = origin_x + (index - 1) * (item_w + gap),
y = origin_y,
w = item_w,
h = item_h,
text = visible and (action.text or "") or "",
handler = visible and (action.handler or "noop") or "noop",
color = action.color or (index == 1 and theme_color("primary") or theme_color("surfaceAlt")),
visible = visible
}, options.buttonStyle)
style = merge(style, action.style)
style = merge(style, without_keys(options, {
"actions",
"gap",
"itemWidth",
"itemHeight",
"buttonStyle",
"x",
"y",
"width",
"height",
"w",
"h"
}))
nodes[index] = widgets.text_button(action.id or (id .. "_" .. tostring(index)), style)
end
return nodes
end
-- -----------------------------------------------------------------------------
-- Panels and headers
-- -----------------------------------------------------------------------------
---@param id string
---@param opts RuntimePanelHeaderOpts
---@return RuntimeNode[]
function widgets.panel_header(id, opts)
local options = with_box(opts or {}, { x = 0, y = 0, width = 0, height = 0, title = "" })
local nodes = {}
local cursor = options.y
local gap = options.gap or 4
local layer = options.layer
local parent = options.parent
if options.eyebrow ~= nil and options.eyebrow ~= "" then
local h = options.eyebrowHeight or 20
table.insert(nodes, widgets.label(options.eyebrowId or (id .. "_eyebrow"), merge({
text = options.eyebrow,
x = options.x,
y = cursor,
width = options.width,
height = h,
parent = parent,
layer = layer,
color = theme_color("primary"),
fontSize = 13
}, options.eyebrowStyle)))
cursor = cursor + h + gap
end
local title_h = options.titleHeight or 28
table.insert(nodes, widgets.section_title(options.titleId or (id .. "_title"), merge({
text = options.title or "",
x = options.x,
y = cursor,
width = options.width,
height = title_h,
parent = parent,
layer = layer,
fontSize = 20
}, options.titleStyle)))
cursor = cursor + title_h + gap
if options.summary ~= nil and options.summary ~= "" then
local h = options.summaryHeight or 22
table.insert(nodes, widgets.label(options.summaryId or (id .. "_summary"), merge({
text = options.summary,
x = options.x,
y = cursor,
width = options.width,
height = h,
parent = parent,
layer = layer,
color = theme_color("muted"),
fontSize = 12
}, options.summaryStyle)))
end
return nodes
end
---@param id string
---@param width number|RuntimeNodeInit
---@param height? number
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function widgets.overlay(id, width, height, opts)
if is_table(width) then
return runtime_ui.rect(id, merge({
x = 0,
y = 0,
color = theme_color("overlay"),
layer = 900
}, width))
end
return runtime_ui.rect(id, 0, 0, width, height, merge({
color = theme_color("overlay"),
layer = 900
}, opts))
end
---@param id string
---@param x number|RuntimeNodeInit
---@param y? number
---@param width? number
---@param height? number
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function widgets.card(id, x, y, width, height, opts)
if is_table(x) then
return runtime_ui.panel(id, merge({
x = 0,
y = 0,
width = 0,
height = 0,
color = theme_color("card"),
radius = 14,
layer = 910
}, x))
end
return runtime_ui.panel(id, x, y, width, height, merge({
color = theme_color("card"),
radius = 14,
layer = 910
}, opts))
end
-- -----------------------------------------------------------------------------
-- Progress
-- -----------------------------------------------------------------------------
---@param id string
---@param x number|RuntimeNodeInit
---@param y? number
---@param width? number
---@param height? number
---@param value? number
---@param opts? RuntimeNodeInit
---@return RuntimeNode
function widgets.progress_bar(id, x, y, width, height, value, opts)
if is_table(x) then
return runtime_ui.progress(id, merge({
x = 0,
y = 0,
width = 0,
height = 0,
value = 0,
color = theme_color("progress"),
radius = 6
}, x))
end
return runtime_ui.progress(id, x, y, width, height, value, merge({
color = theme_color("progress"),
radius = 6
}, opts))
end
---@param id string
---@param label string
---@param x number
---@param y number
---@param width number
---@param height number
---@param value number
---@param opts? RuntimeLabeledProgressOpts
---@return RuntimeNode[]
function widgets.labeled_progress(id, label, x, y, width, height, value, opts)
local label_height = opts ~= nil and opts.labelHeight or 24
local progress_opts = merge(opts or {}, nil)
progress_opts.labelHeight = nil
progress_opts.labelStyle = nil
return {
runtime_ui.text(id .. "_label", label, x, y, width, label_height, merge({
color = theme_color("text"),
fontSize = 16
}, opts ~= nil and opts.labelStyle or nil)),
widgets.progress_bar(id, x, y + label_height + 6, width, height, value, progress_opts)
}
end
-- -----------------------------------------------------------------------------
-- Dialogs
-- -----------------------------------------------------------------------------
---@param parent string
---@param id string
---@param buttons RuntimeDialogButton[]
---@param x number
---@param y number
---@param width number
---@param height number
---@param gap? number
---@param opts? RuntimeNodeProps
---@return RuntimeNode[]
function widgets.button_row(parent, id, buttons, x, y, width, height, gap, opts)
local nodes = {}
local count = #buttons
if count == 0 then
return nodes
end
local actual_gap = gap or 8
local button_width = (width - actual_gap * (count - 1)) / count
for index, button in ipairs(buttons) do
local button_id = button.id or (id .. "_button_" .. index)
table.insert(nodes, runtime_ui.button(
button_id,
button.text or "OK",
x + (index - 1) * (button_width + actual_gap),
y,
button_width,
height,
button.handler,
child_opts(parent, merge({
layer = 920,
color = button.color or theme_color("primary"),
radius = 10
}, opts))
))
end
return nodes
end
---@param id string
---@param title string
---@param message string
---@param x number
---@param y number
---@param width number
---@param height number
---@param opts? RuntimeDialogOpts
---@return RuntimeNode[]
function widgets.dialog(id, title, message, x, y, width, height, opts)
local options = opts or {}
local nodes = {}
local layer = options.layer or 900
local screen_width = options.screenWidth or 720
local screen_height = options.screenHeight or 720
if options.overlay ~= false then
table.insert(nodes, widgets.overlay(id .. "_overlay", screen_width, screen_height, {
layer = layer,
color = options.overlayColor or theme_color("overlay"),
interactive = options.blockInput == true
}))
end
table.insert(nodes, widgets.card(id, x, y, width, height, merge({
layer = layer + 1,
color = options.color or theme_color("card"),
radius = options.radius or 16
}, options.panelStyle)))
table.insert(nodes, runtime_ui.text(
id .. "_title",
title or "",
20,
18,
width - 40,
32,
child_opts(id, merge({
layer = layer + 2,
color = options.titleColor or theme_color("text"),
fontSize = options.titleSize or 22
}, options.titleStyle))
))
table.insert(nodes, runtime_ui.text(
id .. "_message",
message or "",
20,
60,
width - 40,
height - 116,
child_opts(id, merge({
layer = layer + 2,
color = options.messageColor or theme_color("muted"),
fontSize = options.messageSize or 16
}, options.messageStyle))
))
append_all(nodes, widgets.button_row(
id,
id,
options.buttons or {},
20,
height - 48,
width - 40,
34,
options.buttonGap or 8,
options.buttonStyle
))
return nodes
end
return widgets

45
example/.gitignore vendored Normal file
View File

@@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

17
example/.luarc.json Normal file
View File

@@ -0,0 +1,17 @@
{
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"runtime.version": "Lua 5.4",
"workspace.checkThirdParty": false,
"diagnostics.globals": ["runtime"],
"diagnostics.neededFileStatus": {
"undefined-field": "Any",
"inject-field": "Any",
"assign-type-mismatch": "Any",
"param-type-mismatch": "Any",
"return-type-mismatch": "Any",
"missing-fields": "Any",
"undefined-global": "Any"
},
"type.weakUnionCheck": true,
"type.weakNilCheck": true
}

45
example/.metadata Normal file
View File

@@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
- platform: android
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
- platform: ios
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
- platform: linux
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
- platform: macos
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
- platform: web
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
- platform: windows
create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

16
example/README.md Normal file
View File

@@ -0,0 +1,16 @@
# flame_lua_ludo
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

14
example/android/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View File

@@ -0,0 +1,44 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "dev.flame_lua.runtime_showcase"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "dev.flame_lua.runtime_showcase"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="flame_lua_runtime_showcase"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,5 @@
package dev.flame_lua.runtime_showcase
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,24 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip

View File

@@ -0,0 +1,26 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.9.1" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}
include(":app")

View File

@@ -0,0 +1,22 @@
{
"gameId": "flight",
"name": "Lua 飞行棋基础版",
"version": "0.1.0",
"runtimeApiVersion": 1,
"entry": "scripts/main.lua",
"assetsBase": "assets",
"modules": {
"runtime_ui": "runtime:runtime_ui.lua",
"runtime_widgets": "runtime:runtime_widgets.lua",
"runtime_commands": "runtime:runtime_commands.lua",
"layout": "runtime:layout.lua",
"theme": "scripts/theme.lua",
"styles": "scripts/styles.lua",
"state": "scripts/state.lua",
"board": "scripts/board.lua",
"rules": "scripts/rules.lua",
"ui": "scripts/ui.lua",
"animation": "scripts/animation.lua"
},
"resources": {}
}

View File

@@ -0,0 +1,17 @@
---@type RuntimeCommands
local commands = runtime.import("runtime_commands")
local animation = {}
function animation.move_piece(piece_id, path)
return commands.move_path(piece_id, path, {
duration = 0.55,
onComplete = "piece_move_done"
})
end
function animation.toast(text)
return commands.toast(text)
end
return animation

View File

@@ -0,0 +1,91 @@
local board = {
route_length = 40,
finish_progress = 40,
start = {
red = 1,
yellow = 11,
blue = 21,
green = 31
},
homes = {
red = {
{ x = 92, y = 132 },
{ x = 142, y = 132 }
},
yellow = {
{ x = 518, y = 132 },
{ x = 568, y = 132 }
},
blue = {
{ x = 518, y = 548 },
{ x = 568, y = 548 }
},
green = {
{ x = 92, y = 548 },
{ x = 142, y = 548 }
}
},
finish = {
red = {
{ x = 290, y = 330 },
{ x = 250, y = 330 }
},
yellow = {
{ x = 350, y = 330 },
{ x = 390, y = 330 }
},
blue = {
{ x = 350, y = 390 },
{ x = 390, y = 390 }
},
green = {
{ x = 290, y = 390 },
{ x = 250, y = 390 }
}
},
route = {}
}
local function add(x, y)
table.insert(board.route, { x = x, y = y })
end
for i = 0, 9 do add(200 + i * 32, 104) end
for i = 0, 9 do add(520, 136 + i * 32) end
for i = 0, 9 do add(488 - i * 32, 456) end
for i = 0, 9 do add(168, 424 - i * 32) end
function board.home_position(piece)
local index = piece.id:sub(-1) == "2" and 2 or 1
return board.homes[piece.owner][index]
end
function board.finish_position(piece)
local index = piece.id:sub(-1) == "2" and 2 or 1
return board.finish[piece.owner][index]
end
function board.route_index(owner, progress)
local start = board.start[owner]
local index = start + progress - 1
while index > board.route_length do
index = index - board.route_length
end
return index
end
function board.route_position(owner, progress)
return board.route[board.route_index(owner, progress)]
end
function board.position(piece)
if piece.status == "home" then
return board.home_position(piece)
end
if piece.status == "finished" then
return board.finish_position(piece)
end
return board.route_position(piece.owner, piece.progress)
end
return board

View File

@@ -0,0 +1,154 @@
local state = runtime.import("state")
local rules = runtime.import("rules")
local ui = runtime.import("ui")
local animation = runtime.import("animation")
---@type RuntimeWidgets
local widgets = runtime.import("runtime_widgets")
local theme = runtime.import("theme")
widgets.configure({
primary = theme.colors.dice_button,
secondary = theme.colors.board_inner,
success = theme.colors.green,
overlay = "#99000000",
surface = theme.colors.top_bar,
surfaceAlt = theme.colors.board_inner,
card = theme.colors.top_bar,
text = theme.colors.text,
muted = theme.colors.muted_text,
progress = theme.colors.green,
transparent = "#00000000"
})
function smoke_test(ctx)
return ctx ~= nil
and ctx.runtimeApiVersion ~= nil
and state.current_player ~= nil
and rules.plan_move ~= nil
and ui.create_board_nodes ~= nil
and animation.move_piece ~= nil
and widgets.dialog ~= nil
end
function init(ctx)
return {
render = { creates = ui.create_board_nodes() },
ui = { creates = ui.create_ui_nodes() },
commands = {}
}
end
local function handle_roll_dice()
if state.phase == "game_over" then
return { commands = { animation.toast("游戏已结束") } }
end
if state.phase ~= "waiting_roll" then
return { commands = { animation.toast("请先完成当前移动") } }
end
state.dice = rules.next_dice()
local movable = rules.movable_pieces()
if #movable == 0 then
local message = state.current_player .. " 无可移动飞机"
if state.dice ~= 6 then
state.current_player = rules.next_player()
end
state.phase = "waiting_roll"
return {
ui = { updates = ui.dice_and_status_updates() },
render = { updates = ui.highlight_updates(rules.all_piece_ids(), false) },
commands = { animation.toast(message) }
}
end
state.phase = "waiting_piece"
return {
ui = { updates = ui.dice_and_status_updates() },
render = { updates = ui.highlight_updates(movable, true) }
}
end
local function handle_piece_tap(piece_id)
if state.phase ~= "waiting_piece" then
return {}
end
local piece = state.pieces[piece_id]
if piece == nil or piece.owner ~= state.current_player or not rules.can_move(piece, state.dice) then
return { commands = { animation.toast("该飞机不能移动") } }
end
local plan = rules.plan_move(piece, state.dice)
state.phase = "animating"
state.selected_piece = piece_id
state.pending_move = plan
return {
ui = { updates = ui.dice_and_status_updates() },
render = { updates = ui.highlight_updates(rules.all_piece_ids(), false) },
commands = { animation.move_piece(piece_id, plan.path) }
}
end
local function handle_move_done()
local plan = state.pending_move
if plan == nil then
state.phase = "waiting_roll"
return { ui = { updates = ui.dice_and_status_updates() } }
end
rules.commit_move(plan)
local moved_piece = state.pieces[plan.piece_id]
local updates = ui.piece_position_updates(plan.captures)
table.insert(updates, ui.piece_position_update(plan.piece_id))
local messages = {}
if #plan.captures > 0 then
table.insert(messages, "撞回 " .. tostring(#plan.captures) .. " 架飞机")
end
if rules.player_finished(moved_piece.owner) then
state.winner = moved_piece.owner
state.phase = "game_over"
table.insert(messages, moved_piece.owner .. " 获胜")
else
if state.dice ~= 6 then
state.current_player = rules.next_player()
else
table.insert(messages, "掷出 6额外回合")
end
state.phase = "waiting_roll"
end
state.selected_piece = nil
state.pending_move = nil
local commands = {}
if #messages > 0 then
commands = { animation.toast(table.concat(messages, "")) }
end
return {
ui = { updates = ui.dice_and_status_updates() },
render = { updates = updates },
commands = commands
}
end
function on_event(event)
if event.handler == "roll_dice" then
return handle_roll_dice()
end
if event.handler == "piece_tap" then
return handle_piece_tap(event.target)
end
if event.handler == "piece_move_done" then
return handle_move_done()
end
return {}
end

View File

@@ -0,0 +1,153 @@
local state = runtime.import("state")
local board = runtime.import("board")
local rules = {}
local function piece_index(piece_id)
return piece_id:sub(-1) == "2" and 2 or 1
end
function rules.next_player()
for index, player in ipairs(state.players) do
if player == state.current_player then
local next_index = index + 1
if next_index > #state.players then
next_index = 1
end
return state.players[next_index]
end
end
return state.players[1]
end
function rules.next_dice()
state.dice_index = state.dice_index + 1
if state.dice_index > #state.dice_values then
state.dice_index = 1
end
return state.dice_values[state.dice_index]
end
function rules.can_move(piece, dice)
if piece == nil or piece.status == "finished" then
return false
end
if piece.status == "home" then
return dice == 6
end
return true
end
function rules.movable_pieces()
local result = {}
for id, piece in pairs(state.pieces) do
if piece.owner == state.current_player and rules.can_move(piece, state.dice) then
table.insert(result, id)
end
end
return result
end
function rules.all_piece_ids()
local result = {}
for id, _ in pairs(state.pieces) do
table.insert(result, id)
end
return result
end
function rules.piece_position(piece)
return board.position(piece)
end
function rules.plan_move(piece, dice)
local path = {}
local final_status = "route"
local final_progress = piece.progress
if piece.status == "home" then
final_progress = 1
table.insert(path, board.route_position(piece.owner, final_progress))
else
for step = 1, dice do
local progress = piece.progress + step
if progress >= board.finish_progress then
final_status = "finished"
final_progress = board.finish_progress
table.insert(path, board.finish_position(piece))
break
end
table.insert(path, board.route_position(piece.owner, progress))
final_progress = progress
end
end
local captures = {}
if final_status == "route" then
local landing_index = board.route_index(piece.owner, final_progress)
for id, other in pairs(state.pieces) do
if id ~= piece.id and other.owner ~= piece.owner and other.status == "route" then
if board.route_index(other.owner, other.progress) == landing_index then
table.insert(captures, id)
end
end
end
end
return {
piece_id = piece.id,
final_status = final_status,
final_progress = final_progress,
path = path,
captures = captures
}
end
function rules.commit_move(plan)
local piece = state.pieces[plan.piece_id]
piece.status = plan.final_status
piece.progress = plan.final_progress
for _, captured_id in ipairs(plan.captures) do
local captured = state.pieces[captured_id]
captured.status = "home"
captured.progress = 0
end
end
function rules.player_finished(owner)
for _, piece in pairs(state.pieces) do
if piece.owner == owner and piece.status ~= "finished" then
return false
end
end
return true
end
function rules.status_text()
if state.winner ~= nil then
return "胜利者: " .. state.winner
end
if state.phase == "waiting_roll" then
return "" .. state.current_player .. " 掷骰子"
end
if state.phase == "waiting_piece" then
return "请选择 " .. state.current_player .. " 的飞机"
end
if state.phase == "animating" then
return "飞机移动中..."
end
return "准备中"
end
function rules.piece_label(piece)
if piece.status == "home" then
return "待飞"
end
if piece.status == "finished" then
return "到达"
end
return tostring(piece.progress)
end
return rules

View File

@@ -0,0 +1,589 @@
---@meta
--- COMMON RUNTIME TYPES SECTION.
--- Source of truth: tool/lua_runtime_defs_common.lua
--- After editing this common section, run:
--- dart run tool/generate_lua_runtime_defs.dart
---@alias RuntimeNodeType
---| 'panel'
---| 'button'
---| 'text'
---| 'circle'
---| 'rect'
---| 'line'
---| 'progress'
---| 'listView'
---| 'sprite'
---| 'image'
---| 'spine'
---| 'particle'
---@alias RuntimeAnchor
---| 'center'
---| 'topLeft'
---| 'topRight'
---| 'bottomLeft'
---| 'bottomRight'
---@alias RuntimeTextAlign
---| 'left'
---| 'center'
---| 'right'
---@alias RuntimeParticlePreset
---| 'burst'
---| 'trail'
---| 'snow'
---| 'confetti'
---@alias RuntimeCommandType
---| 'move_path'
---| 'move_to'
---| 'fade_to'
---| 'scale_to'
---| 'rotate_to'
---| 'remove_node'
---| 'sequence'
---| 'parallel'
---| 'delay'
---| 'toast'
---| 'play_sound'
---| 'play_bgm'
---| 'pause_bgm'
---| 'resume_bgm'
---| 'stop_bgm'
---| 'preload_resources'
---| 'evict_resources'
---| 'cancel_commands'
---| 'play_spine_animation'
---| 'copy_text'
---@alias RuntimeEventType
---| 'tap'
---| 'animation_done'
---| 'resize'
---| 'scroll'
---@alias RuntimeScaleMode
---| 'fit'
---| 'fill'
---| 'stretch'
---| 'none'
---@alias RuntimeLayoutAlign
---| 'start'
---| 'center'
---| 'end'
---@alias RuntimeButtonVariant
---| 'primary'
---| 'secondary'
---| 'ghost'
---@class (exact) RuntimeNode
---@field id string
---@field type RuntimeNodeType
---@field parent? string
---@field asset? string Normal image/sprite/spine asset key. For button nodes this is the normal-state image.
---@field pressedAsset? string Button pressed-state image asset key.
---@field disabledAsset? string Button disabled-state image asset key.
---@field animation? string
---@field skin? string
---@field loop? boolean
---@field text? string
---@field x? number
---@field y? number
---@field width? number
---@field height? number
---@field paddingLeft? number
---@field paddingTop? number
---@field paddingRight? number
---@field paddingBottom? number
---@field anchor? RuntimeAnchor
---@field layer? integer
---@field visible? boolean
---@field alpha? number
---@field scale? number
---@field rotation? number
---@field color? string
---@field fontSize? number
---@field textAlign? RuntimeTextAlign
---@field radius? number
---@field strokeWidth? number
---@field value? number
---@field scrollX? number
---@field scrollY? number
---@field contentWidth? number
---@field contentHeight? number
---@field virtualized? boolean
---@field cacheExtent? number
---@field inertia? boolean
---@field scrollbarThumbColor? string
---@field scrollbarTrackColor? string
---@field scrollbarThickness? number
---@field scrollbarVisible? boolean
---@field interactive? boolean
---@field onTap? string
---@field onScroll? string
---@field preset? RuntimeParticlePreset
---@field count? integer
---@field duration? number
---@field speedMin? number
---@field speedMax? number
---@field gravityX? number
---@field gravityY? number
---@field spread? number
---@field colorTo? string
---@field radiusTo? number
---@field autoRemove? boolean
---@field fadeOut? boolean
---@class (exact) RuntimeNodeProps
---@field type? RuntimeNodeType
---@field parent? string
---@field asset? string Normal image/sprite/spine asset key. For button nodes this is the normal-state image.
---@field pressedAsset? string Button pressed-state image asset key.
---@field disabledAsset? string Button disabled-state image asset key.
---@field animation? string
---@field skin? string
---@field loop? boolean
---@field text? string
---@field x? number
---@field y? number
---@field width? number
---@field height? number
---@field paddingLeft? number
---@field paddingTop? number
---@field paddingRight? number
---@field paddingBottom? number
---@field anchor? RuntimeAnchor
---@field layer? integer
---@field visible? boolean
---@field alpha? number
---@field scale? number
---@field rotation? number
---@field color? string
---@field fontSize? number
---@field textAlign? RuntimeTextAlign
---@field radius? number
---@field strokeWidth? number
---@field value? number
---@field scrollX? number
---@field scrollY? number
---@field contentWidth? number
---@field contentHeight? number
---@field virtualized? boolean
---@field cacheExtent? number
---@field inertia? boolean
---@field scrollbarThumbColor? string
---@field scrollbarTrackColor? string
---@field scrollbarThickness? number
---@field scrollbarVisible? boolean
---@field interactive? boolean
---@field onTap? string
---@field onScroll? string
---@field preset? RuntimeParticlePreset
---@field count? integer
---@field duration? number
---@field speedMin? number
---@field speedMax? number
---@field gravityX? number
---@field gravityY? number
---@field spread? number
---@field colorTo? string
---@field radiusTo? number
---@field autoRemove? boolean
---@field fadeOut? boolean
---Helper-only fields accepted by runtime_ui/runtime_widgets. They are normalized
---before the node/update crosses the Dart Runtime protocol boundary.
---@class RuntimeNodeInit: RuntimeNodeProps
---@field w? number Alias for width.
---@field h? number Alias for height.
---@field size? number Alias for both width and height.
---@field handler? string Alias for onTap.
---@field onClick? string Alias for onTap.
---@class (exact) RuntimeNodeUpdate
---@field id string
---@field props RuntimeNodeProps
---@class (exact) RuntimeNodeRemove
---@field id string
---@class (exact) RuntimeDiffSection
---@field creates? RuntimeNode[]
---@field updates? RuntimeNodeUpdate[]
---@field removes? (string|RuntimeNodeRemove)[]
---@class (exact) RuntimeDiff
---@field render? RuntimeDiffSection
---@field ui? RuntimeDiffSection
---@field commands? RuntimeCommand[]
---@class (exact) RuntimeEvent
---@field type RuntimeEventType|string
---@field target? string
---@field handler? string
---@field x? number
---@field y? number
---@field data? table
---@class (exact) RuntimeCommand
---@field type RuntimeCommandType
---@field target? string
---@field scope? string
---@field id? string
---@field group? string
---@field commandGroup? string
---@field onComplete? string
---@field duration? number
---@field commands? RuntimeCommand[]
---@field path? RuntimePoint[]
---@field x? number
---@field y? number
---@field alpha? number
---@field scale? number
---@field angle? number
---@field text? string
---@field message? string
---@field asset? string
---@field name? string
---@field volume? number
---@field channel? string
---@field loop? boolean
---@field failOnError? boolean
---@field animation? string
---@field track? integer
---@field queue? boolean
---@field delay? number
---@class (exact) RuntimeCommandOpts
---@field id? string
---@field group? string
---@field commandGroup? string
---@field scope? string
---@field onComplete? string
---@field duration? number
---@class (exact) RuntimeAudioCommandOpts: RuntimeCommandOpts
---@field volume? number
---@field name? string
---@class (exact) RuntimeBgmCommandOpts: RuntimeAudioCommandOpts
---@field channel? string
---@field loop? boolean
---@class (exact) RuntimeSpineCommandOpts: RuntimeCommandOpts
---@field track? integer
---@field loop? boolean
---@field queue? boolean
---@field delay? number
---@class (exact) RuntimeResourceCommandOpts: RuntimeCommandOpts
---@field failOnError? boolean
---@class (exact) RuntimePoint
---@field x number
---@field y number
---@class (exact) RuntimeLocaleContext
---@field requested string
---@field resolved string
---@field default string
---@field supported string[]
---@field languageCode string
---@field scriptCode? string
---@field countryCode? string
---@class (exact) RuntimeScreenContext
---@field width number
---@field height number
---@class (exact) RuntimeDesignContext
---@field width number
---@field height number
---@class (exact) RuntimeViewportContext
---@field x number
---@field y number
---@field width number
---@field height number
---@field scaleX number
---@field scaleY number
---@field scaleMode RuntimeScaleMode|string
---@class (exact) RuntimeContext
---@field screen RuntimeScreenContext
---@field design RuntimeDesignContext
---@field viewport RuntimeViewportContext
---@field seed integer
---@field runtimeApiVersion integer
---@field gameId string
---@field gameVersion string
---@field locale? RuntimeLocaleContext
---@class RuntimeUi
---@field style fun(base?: RuntimeNodeProps, opts?: RuntimeNodeProps): RuntimeNodeProps
---@field with_parent fun(parent: string, opts?: RuntimeNodeProps): RuntimeNodeProps
---@field node fun(node_type: RuntimeNodeType, id: string, opts?: RuntimeNodeInit): RuntimeNode
---@field panel fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field rect fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field circle fun(id: string, x: number|RuntimeNodeInit, y?: number, size?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field line fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field progress fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, value?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field particle fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field text fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field button fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeNodeInit): RuntimeNode
---@field list_view fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field image fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field sprite fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field spine fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, animation?: string, opts?: RuntimeNodeInit): RuntimeNode
---@field update fun(id: string, props: RuntimeNodeInit): RuntimeNodeUpdate
---@field text_update fun(id: string, text: string): RuntimeNodeUpdate
---@field visible_update fun(id: string, visible: boolean): RuntimeNodeUpdate
---@field alpha_update fun(id: string, alpha: number): RuntimeNodeUpdate
---@field scale_update fun(id: string, scale: number): RuntimeNodeUpdate
---@field position_update fun(id: string, x: number, y: number): RuntimeNodeUpdate
---@field size_update fun(id: string, width: number, height: number): RuntimeNodeUpdate
---@field transform_update fun(id: string, x: number, y: number, scale: number, rotation: number): RuntimeNodeUpdate
---@field batch_update fun(ids: string[], props: RuntimeNodeInit): RuntimeNodeUpdate[]
---@field append fun(nodes: RuntimeNode[], node: RuntimeNode): RuntimeNode[]
---@field append_all fun(nodes: RuntimeNode[], extra_nodes: RuntimeNode[]): RuntimeNode[]
---@class (exact) RuntimeDialogButton
---@field id? string
---@field text string
---@field handler string
---@field color? string
---@class (exact) RuntimeDialogOpts
---@field screenWidth? number
---@field screenHeight? number
---@field overlay? boolean
---@field overlayColor? string
---@field blockInput? boolean
---@field layer? integer
---@field color? string
---@field radius? number
---@field panelStyle? RuntimeNodeProps
---@field titleColor? string
---@field titleSize? number
---@field titleStyle? RuntimeNodeProps
---@field messageColor? string
---@field messageSize? number
---@field messageStyle? RuntimeNodeProps
---@field buttons? RuntimeDialogButton[]
---@field buttonGap? number
---@field buttonStyle? RuntimeNodeProps
---@class RuntimeLabeledProgressOpts: RuntimeNodeInit
---@field labelHeight? number
---@field labelStyle? RuntimeNodeProps
---@class RuntimePillOpts: RuntimeNodeInit
---@field panelStyle? RuntimeNodeProps
---@field textStyle? RuntimeNodeProps
---@class RuntimeTextButtonOpts: RuntimeNodeInit
---@field variant? RuntimeButtonVariant
---@class RuntimeListItemOpts: RuntimeTextButtonOpts
---@field selected? boolean
---@field activeColor? string
---@field inactiveColor? string
---@class RuntimeTabItem
---@field id? string
---@field key? string
---@field text string
---@field handler? string
---@field selected? boolean
---@class RuntimeTabsOpts: RuntimeNodeInit
---@field tabs? RuntimeTabItem[]
---@field selected? string
---@field gap? number
---@field itemWidth? number
---@field itemHeight? number
---@field activeColor? string
---@field inactiveColor? string
---@field buttonStyle? RuntimeNodeProps
---@class RuntimeActionItem
---@field id? string
---@field text string
---@field handler? string
---@field visible? boolean
---@field color? string
---@field style? RuntimeNodeProps
---@class RuntimeActionRowOpts: RuntimeNodeInit
---@field actions? RuntimeActionItem[]
---@field gap? number
---@field itemWidth? number
---@field itemHeight? number
---@field buttonStyle? RuntimeNodeProps
---@class RuntimePanelHeaderOpts: RuntimeNodeInit
---@field eyebrow? string
---@field title string
---@field summary? string
---@field gap? number
---@field eyebrowId? string
---@field titleId? string
---@field summaryId? string
---@field eyebrowHeight? number
---@field titleHeight? number
---@field summaryHeight? number
---@field eyebrowStyle? RuntimeNodeProps
---@field titleStyle? RuntimeNodeProps
---@field summaryStyle? RuntimeNodeProps
---@class RuntimeWidgetTheme
---@field primary? string
---@field secondary? string
---@field success? string
---@field overlay? string
---@field surface? string
---@field surfaceAlt? string
---@field card? string
---@field text? string
---@field muted? string
---@field progress? string
---@field transparent? string
---@class RuntimeWidgets
---@field configure fun(tokens?: RuntimeWidgetTheme): RuntimeWidgets
---@field label fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field section_title fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field pill fun(id: string, text: string|RuntimePillOpts, x?: number, y?: number, width?: number, height?: number, opts?: RuntimePillOpts): RuntimeNode[]
---@field text_button fun(id: string, text: string|RuntimeTextButtonOpts, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeTextButtonOpts): RuntimeNode
---@field list_item fun(id: string, text: string|RuntimeListItemOpts, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeListItemOpts): RuntimeNode
---@field tabs fun(id: string, tabs: RuntimeTabItem[]|RuntimeTabsOpts, opts?: RuntimeTabsOpts): RuntimeNode[]
---@field action_row fun(id: string, actions: RuntimeActionItem[]|RuntimeActionRowOpts, opts?: RuntimeActionRowOpts): RuntimeNode[]
---@field panel_header fun(id: string, opts: RuntimePanelHeaderOpts): RuntimeNode[]
---@field overlay fun(id: string, width: number|RuntimeNodeInit, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field card fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field progress_bar fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, value?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field labeled_progress fun(id: string, label: string, x: number, y: number, width: number, height: number, value: number, opts?: RuntimeLabeledProgressOpts): RuntimeNode[]
---@field button_row fun(parent: string, id: string, buttons: RuntimeDialogButton[], x: number, y: number, width: number, height: number, gap?: number, opts?: RuntimeNodeProps): RuntimeNode[]
---@field dialog fun(id: string, title: string, message: string, x: number, y: number, width: number, height: number, opts?: RuntimeDialogOpts): RuntimeNode[]
---@class (exact) RuntimeLayoutItem
---@field node RuntimeNode
---@field marginLeft? number
---@field marginRight? number
---@field marginTop? number
---@field marginBottom? number
---@class RuntimeLayoutItemOpts
---@field margin? number
---@field mx? number
---@field my? number
---@field ml? number
---@field mr? number
---@field mt? number
---@field mb? number
---@field marginLeft? number
---@field marginRight? number
---@field marginTop? number
---@field marginBottom? number
---@class RuntimeLinearLayoutOpts
---@field x? number
---@field y? number
---@field width? number
---@field height? number
---@field gap? number
---@field align? RuntimeLayoutAlign
---@field padding? number
---@field paddingX? number
---@field paddingY? number
---@field px? number
---@field py? number
---@field paddingLeft? number
---@field paddingTop? number
---@class RuntimeBoxLayoutOpts: RuntimeLinearLayoutOpts
---@field rows? integer
---@field columns? integer
---@field cols? integer
---@field cellWidth? number
---@field cellHeight? number
---@field cellW? number
---@field cellH? number
---@field gapX? number
---@field gapY? number
---@field valign? RuntimeLayoutAlign
---@class RuntimeLayout
---@field item fun(node: RuntimeNode, opts?: RuntimeLayoutItemOpts): RuntimeLayoutItem
---@field local_position fun(origin: RuntimePoint, position: RuntimePoint): RuntimePoint
---@field row fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
---@field column fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
---@field stack fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
---@field box fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeBoxLayoutOpts): RuntimeNode[]
---@class RuntimeCommands
---@field toast fun(text: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field copy_text fun(text: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field delay fun(duration: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field sequence fun(items: RuntimeCommand[], opts?: RuntimeCommandOpts): RuntimeCommand
---@field parallel fun(items: RuntimeCommand[], opts?: RuntimeCommandOpts): RuntimeCommand
---@field move_path fun(target: string, path: RuntimePoint[], opts?: RuntimeCommandOpts): RuntimeCommand
---@field move_to fun(target: string, x: number, y: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field fade_to fun(target: string, alpha: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field scale_to fun(target: string, scale: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field rotate_to fun(target: string, angle: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field remove_node fun(target: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field play_spine_animation fun(target: string, animation: string, opts?: RuntimeSpineCommandOpts): RuntimeCommand
---@field play_sound fun(asset: string, opts?: RuntimeAudioCommandOpts): RuntimeCommand
---@field play_bgm fun(asset: string, opts?: RuntimeBgmCommandOpts): RuntimeCommand
---@field pause_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field resume_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field stop_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field preload_group fun(group: string, opts?: RuntimeResourceCommandOpts): RuntimeCommand
---@field evict_group fun(group: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field cancel_id fun(id: string): RuntimeCommand
---@field cancel_group fun(group: string): RuntimeCommand
---@field cancel_scope fun(scope: string): RuntimeCommand
---@class RuntimeImportApi
---@field import fun(moduleName: string): table
---@type RuntimeImportApi
runtime = runtime
---@alias PlayerColor 'red'|'yellow'|'blue'|'green'
---@alias GamePhase 'waiting_roll'|'waiting_piece'|'animating'|'game_over'
---@alias PieceStatus 'home'|'route'|'finished'
---@class (exact) PieceState
---@field id string
---@field owner PlayerColor
---@field progress integer
---@field status PieceStatus
---@class (exact) LudoState
---@field current_player PlayerColor
---@field phase GamePhase
---@field dice_index integer
---@field dice_values integer[]
---@field dice? integer
---@field selected_piece? string
---@field pending_move? table
---@field winner? PlayerColor
---@field players PlayerColor[]
---@field pieces table<string, PieceState>
---@class (exact) BoardPoint
---@field x number
---@field y number
---@class (exact) BoardData
---@field red_home BoardPoint[]
---@field blue_home BoardPoint[]
---@field path BoardPoint[]
---@field start table<PlayerColor, integer>

View File

@@ -0,0 +1,22 @@
---@type table
return {
players = { "red", "yellow", "blue", "green" },
current_player = "red",
phase = "waiting_roll",
dice_index = 0,
dice_values = { 6, 3, 4, 6, 2, 5, 1, 6, 4, 2, 6, 5 },
dice = nil,
selected_piece = nil,
pending_move = nil,
winner = nil,
pieces = {
red_1 = { id = "red_1", owner = "red", status = "home", progress = 0 },
red_2 = { id = "red_2", owner = "red", status = "home", progress = 0 },
yellow_1 = { id = "yellow_1", owner = "yellow", status = "home", progress = 0 },
yellow_2 = { id = "yellow_2", owner = "yellow", status = "home", progress = 0 },
blue_1 = { id = "blue_1", owner = "blue", status = "home", progress = 0 },
blue_2 = { id = "blue_2", owner = "blue", status = "home", progress = 0 },
green_1 = { id = "green_1", owner = "green", status = "home", progress = 0 },
green_2 = { id = "green_2", owner = "green", status = "home", progress = 0 }
}
}

View File

@@ -0,0 +1,81 @@
local theme = runtime.import("theme")
local colors = theme.colors
local styles = {}
styles.layers = {
board = 1,
cell = 2,
home = 3,
plane = 10,
hud = 100,
hud_content = 101
}
styles.board = {
color = colors.board,
layer = styles.layers.board
}
styles.center = {
color = colors.board_inner,
layer = styles.layers.board
}
styles.route_cell = {
anchor = "center",
color = colors.route_cell,
layer = styles.layers.cell
}
styles.home_panel = function(owner)
return {
color = colors[owner .. "_home"],
layer = styles.layers.home
}
end
styles.hud = {
color = colors.top_bar,
layer = styles.layers.hud
}
styles.hud_text = {
fontSize = 20,
color = colors.text,
layer = styles.layers.hud_content
}
styles.small_text = {
fontSize = 14,
color = colors.muted_text,
layer = styles.layers.hud_content
}
styles.dice_button = {
color = colors.dice_button,
fontSize = 20,
layer = styles.layers.hud_content
}
styles.plane = function(owner)
return {
anchor = "center",
color = colors[owner],
layer = styles.layers.plane,
interactive = true,
onTap = "piece_tap",
alpha = 0.95
}
end
styles.plane_highlight = {
scale = 1.25,
alpha = 1
}
styles.plane_normal = {
scale = 1,
alpha = 0.95
}
return styles

View File

@@ -0,0 +1,22 @@
return {
title = "Lua 飞行棋",
colors = {
background = "#0f172a",
board = "#1e293b",
board_inner = "#334155",
route_cell = "#f8fafc",
route_text = "#0f172a",
top_bar = "#020617",
dice_button = "#2563eb",
text = "#ffffff",
muted_text = "#cbd5e1",
red = "#ef4444",
yellow = "#eab308",
blue = "#3b82f6",
green = "#22c55e",
red_home = "#7f1d1d",
yellow_home = "#713f12",
blue_home = "#1e3a8a",
green_home = "#14532d"
}
}

View File

@@ -0,0 +1,130 @@
---@type RuntimeUi
local runtime_ui = runtime.import("runtime_ui")
---@type RuntimeLayout
local layout = runtime.import("layout")
local theme = runtime.import("theme")
local styles = runtime.import("styles")
local state = runtime.import("state")
local board = runtime.import("board")
local rules = runtime.import("rules")
local ui = {}
local function color_name(owner)
if owner == "red" then return "" end
if owner == "yellow" then return "" end
if owner == "blue" then return "" end
if owner == "green" then return "绿" end
return owner
end
local function piece_size(piece)
if piece.status == "finished" then
return 30
end
return 34
end
function ui.highlight_updates(ids, enabled)
return runtime_ui.batch_update(ids, enabled and styles.plane_highlight or styles.plane_normal)
end
function ui.piece_position_update(piece_id)
local piece = state.pieces[piece_id]
local pos = rules.piece_position(piece)
return runtime_ui.update(piece_id, {
x = pos.x,
y = pos.y,
width = piece_size(piece),
height = piece_size(piece),
scale = 1,
alpha = 0.95
})
end
function ui.piece_position_updates(ids)
local updates = {}
for _, id in ipairs(ids) do
table.insert(updates, ui.piece_position_update(id))
end
return updates
end
function ui.all_piece_position_updates()
return ui.piece_position_updates(rules.all_piece_ids())
end
function ui.create_board_nodes()
local nodes = {
runtime_ui.panel("board_panel", 28, 82, 664, 610, styles.board),
runtime_ui.panel("center_airport", 252, 292, 168, 136, styles.center),
runtime_ui.text("board_title", theme.title, 268, 318, 140, 32, styles.hud_text),
runtime_ui.text("board_tip", "掷 6 起飞 · 撞子回家 · 全部到达获胜", 206, 360, 320, 28, styles.small_text),
runtime_ui.panel("home_red", 62, 98, 128, 92, styles.home_panel("red")),
runtime_ui.panel("home_yellow", 498, 98, 128, 92, styles.home_panel("yellow")),
runtime_ui.panel("home_blue", 498, 516, 128, 92, styles.home_panel("blue")),
runtime_ui.panel("home_green", 62, 516, 128, 92, styles.home_panel("green")),
runtime_ui.text("label_red", "红方", 96, 102, 60, 24, styles.small_text),
runtime_ui.text("label_yellow", "黄方", 532, 102, 60, 24, styles.small_text),
runtime_ui.text("label_blue", "蓝方", 532, 520, 60, 24, styles.small_text),
runtime_ui.text("label_green", "绿方", 96, 520, 60, 24, styles.small_text)
}
for index, cell in ipairs(board.route) do
runtime_ui.append(nodes, runtime_ui.circle("route_" .. tostring(index), cell.x, cell.y, 24, styles.route_cell))
end
for _, owner in ipairs(state.players) do
local start = board.route[board.start[owner]]
runtime_ui.append(nodes, runtime_ui.text("start_" .. owner, color_name(owner), start.x - 14, start.y - 14, 36, 24, {
fontSize = 14,
color = theme.colors[owner],
layer = styles.layers.cell + 1
}))
end
for id, piece in pairs(state.pieces) do
local pos = rules.piece_position(piece)
runtime_ui.append(nodes, runtime_ui.circle(id, pos.x, pos.y, piece_size(piece), styles.plane(piece.owner)))
end
return nodes
end
function ui.create_ui_nodes()
local hud_items = layout.row("top_bar", {
runtime_ui.text("turn_text", "当前: 红方", 0, 0, 120, 32, styles.hud_text),
runtime_ui.text("dice_text", "骰子: -", 0, 0, 100, 32, styles.hud_text),
runtime_ui.text("status_text", rules.status_text(), 0, 0, 260, 32, styles.hud_text),
layout.item(
runtime_ui.button("dice_button", "掷骰子", 0, 0, 120, 48, "roll_dice", styles.dice_button),
{ marginLeft = 18 }
)
}, {
x = 24,
height = 76,
gap = 16,
align = "center"
})
local nodes = { runtime_ui.panel("top_bar", 0, 0, 720, 76, styles.hud) }
return runtime_ui.append_all(nodes, hud_items)
end
function ui.turn_name(owner)
return "当前: " .. color_name(owner) .. ""
end
function ui.dice_and_status_updates()
return {
runtime_ui.text_update("dice_text", "骰子: " .. tostring(state.dice or "-")),
runtime_ui.text_update("turn_text", ui.turn_name(state.current_player)),
runtime_ui.text_update("status_text", rules.status_text())
}
end
function ui.status_update()
return { runtime_ui.text_update("status_text", rules.status_text()) }
end
return ui

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@@ -0,0 +1,55 @@
{
"gameId": "ludo",
"name": "Lua Ludo 示例",
"version": "0.1.0",
"runtimeApiVersion": 1,
"entry": "scripts/main.lua",
"assetsBase": "assets",
"defaultLocale": "zh-Hans",
"supportedLocales": [
"zh-Hans",
"en"
],
"display": {
"designWidth": 720,
"designHeight": 720,
"scaleMode": "fit"
},
"modules": {
"runtime_ui": "runtime:runtime_ui.lua",
"runtime_widgets": "runtime:runtime_widgets.lua",
"runtime_commands": "runtime:runtime_commands.lua",
"layout": "runtime:layout.lua",
"i18n": "scripts/i18n.lua",
"theme": "scripts/theme.lua",
"styles": "scripts/styles.lua",
"state": "scripts/state.lua",
"board": "scripts/board.lua",
"rules": "scripts/rules.lua",
"ui": "scripts/ui.lua",
"animation": "scripts/animation.lua"
},
"resources": {
"board": {
"type": "image",
"path": "assets/board.png",
"group": "board"
},
"piece_red": {
"type": "image",
"path": "assets/piece_red.png",
"group": "pieces"
},
"piece_blue": {
"type": "image",
"path": "assets/piece_blue.png",
"group": "pieces"
},
"dice": {
"type": "audio",
"path": "assets/dice.wav",
"preload": "lazy",
"group": "sfx"
}
}
}

View File

@@ -0,0 +1,37 @@
---@type RuntimeCommands
local commands = runtime.import("runtime_commands")
local animation = {}
function animation.move_piece(piece_id, path)
return commands.move_path(piece_id, path, {
duration = 0.7,
onComplete = "piece_move_done"
})
end
function animation.toast(text)
return commands.toast(text)
end
function animation.play_sound(name)
return commands.play_sound(name)
end
function animation.play_bgm(name, channel)
return commands.play_bgm(name, { channel = channel or "bgm" })
end
function animation.pause_bgm(channel)
return commands.pause_bgm(channel)
end
function animation.resume_bgm(channel)
return commands.resume_bgm(channel)
end
function animation.stop_bgm(channel)
return commands.stop_bgm(channel)
end
return animation

View File

@@ -0,0 +1,20 @@
---@type BoardData
return {
red_home = {
{x = 80, y = 520},
{x = 140, y = 520}
},
blue_home = {
{x = 520, y = 120},
{x = 580, y = 120}
},
path = {
{x = 120, y = 420}, {x = 180, y = 420}, {x = 240, y = 420},
{x = 300, y = 420}, {x = 360, y = 420}, {x = 420, y = 420},
{x = 480, y = 420}, {x = 480, y = 360}, {x = 480, y = 300},
{x = 480, y = 240}, {x = 420, y = 240}, {x = 360, y = 240},
{x = 300, y = 240}, {x = 240, y = 240}, {x = 180, y = 240},
{x = 120, y = 240}, {x = 120, y = 300}, {x = 120, y = 360}
},
start = { red = 1, blue = 10 }
}

View File

@@ -0,0 +1,103 @@
---@class RuntimeI18n
local i18n = {}
local default_locale = "zh-Hans"
local current_locale = default_locale
local messages = {
["zh-Hans"] = {
title = "Lua 飞行棋",
roll_button = "掷骰子",
dice_empty = "骰子: -",
dice_value = "骰子: {value}",
current_player = "当前玩家: {player}",
["player.red"] = "红方",
["player.blue"] = "蓝方",
["toast.move_first"] = "请先移动棋子",
["toast.no_movable_piece"] = "无可移动棋子",
["toast.invalid_piece"] = "该棋子不能移动"
},
en = {
title = "Lua Ludo",
roll_button = "Roll",
dice_empty = "Dice: -",
dice_value = "Dice: {value}",
current_player = "Current player: {player}",
["player.red"] = "Red",
["player.blue"] = "Blue",
["toast.move_first"] = "Move a piece first",
["toast.no_movable_piece"] = "No movable pieces",
["toast.invalid_piece"] = "This piece cannot move"
}
}
---@param locale? string
---@return string
local function normalize_locale(locale)
if type(locale) ~= "string" or locale == "" then
return default_locale
end
locale = string.gsub(locale, "_", "-")
if messages[locale] ~= nil then
return locale
end
local language = string.match(locale, "^([A-Za-z]+)")
if language ~= nil and messages[language] ~= nil then
return language
end
return default_locale
end
---@param value string
---@return string
local function escape_pattern(value)
return string.gsub(value, "([%(%)%.%%%+%-%*%?%[%]%^%$])", "%%%1")
end
---@param template string
---@param vars? table<string, string|number>
---@return string
local function interpolate(template, vars)
if vars == nil then
return template
end
local result = template
for key, value in pairs(vars) do
result = string.gsub(result, "{" .. escape_pattern(key) .. "}", tostring(value))
end
return result
end
---@param ctx? RuntimeContext
function i18n.configure(ctx)
local locale = nil
if ctx ~= nil and ctx.locale ~= nil then
locale = ctx.locale.resolved or ctx.locale.requested or ctx.locale.default
end
current_locale = normalize_locale(locale)
end
---@return string
function i18n.locale()
return current_locale
end
---@param key string
---@param vars? table<string, string|number>
---@return string
function i18n.t(key, vars)
local bundle = messages[current_locale] or messages[default_locale]
local fallback = messages[default_locale]
local value = bundle[key] or fallback[key] or key
return interpolate(value, vars)
end
---@param color PlayerColor
---@return string
function i18n.player(color)
return i18n.t("player." .. color)
end
return i18n

View File

@@ -0,0 +1,117 @@
local state = runtime.import("state")
local rules = runtime.import("rules")
local ui = runtime.import("ui")
local animation = runtime.import("animation")
local i18n = runtime.import("i18n")
---@type RuntimeWidgets
local widgets = runtime.import("runtime_widgets")
local theme = runtime.import("theme")
widgets.configure({
primary = theme.colors.dice_button,
secondary = theme.colors.board,
success = theme.colors.blue,
overlay = "#99000000",
surface = theme.colors.top_bar,
surfaceAlt = theme.colors.board,
card = theme.colors.top_bar,
text = theme.colors.text,
muted = "#ffcbd5e1",
progress = theme.colors.blue,
transparent = "#00000000"
})
function smoke_test(ctx)
i18n.configure(ctx)
return ctx ~= nil
and ctx.runtimeApiVersion ~= nil
and state.current_player ~= nil
and rules.next_player ~= nil
and ui.create_board_nodes ~= nil
and animation.move_piece ~= nil
and widgets.dialog ~= nil
end
function init(ctx)
i18n.configure(ctx)
return {
render = { creates = ui.create_board_nodes() },
ui = { creates = ui.create_ui_nodes() },
commands = {}
}
end
local function handle_roll_dice()
if state.phase ~= "waiting_roll" then
return { commands = { animation.toast(i18n.t("toast.move_first")) } }
end
state.dice = rules.next_dice()
local movable = rules.movable_pieces()
if #movable == 0 then
state.current_player = rules.next_player()
state.phase = "waiting_roll"
return {
ui = { updates = ui.dice_and_turn_updates(state.dice, state.current_player) },
render = { updates = ui.highlight_updates(rules.all_piece_ids(), false) },
commands = { animation.toast(i18n.t("toast.no_movable_piece")) }
}
end
state.phase = "waiting_piece"
return {
ui = { updates = ui.dice_update(state.dice) },
render = { updates = ui.highlight_updates(movable, true) },
commands = { animation.play_sound("dice") }
}
end
local function handle_piece_tap(piece_id)
if state.phase ~= "waiting_piece" then
return {}
end
local piece = state.pieces[piece_id]
if piece == nil or piece.owner ~= state.current_player or not rules.can_move(piece, state.dice) then
return { commands = { animation.toast(i18n.t("toast.invalid_piece")) } }
end
local path = rules.calculate_path(piece, state.dice)
rules.apply_move(piece, state.dice)
state.phase = "animating"
state.selected_piece = piece_id
return {
render = { updates = ui.highlight_updates(rules.all_piece_ids(), false) },
commands = { animation.move_piece(piece_id, path) }
}
end
local function handle_move_done()
if state.dice ~= 6 then
state.current_player = rules.next_player()
end
state.phase = "waiting_roll"
state.selected_piece = nil
return {
ui = { updates = ui.turn_update(state.current_player) }
}
end
function on_event(event)
if event.handler == "roll_dice" then
return handle_roll_dice()
end
if event.handler == "piece_tap" then
return handle_piece_tap(event.target)
end
if event.handler == "piece_move_done" then
return handle_move_done()
end
return {}
end

View File

@@ -0,0 +1,88 @@
local state = runtime.import("state")
local board = runtime.import("board")
local rules = {}
function rules.next_player()
if state.current_player == "red" then
return "blue"
end
return "red"
end
function rules.next_dice()
state.dice_index = state.dice_index + 1
if state.dice_index > #state.dice_values then
state.dice_index = 1
end
return state.dice_values[state.dice_index]
end
function rules.piece_home_position(piece)
if piece.owner == "red" then
if piece.id == "red_1" then return board.red_home[1] end
return board.red_home[2]
end
if piece.id == "blue_1" then return board.blue_home[1] end
return board.blue_home[2]
end
function rules.can_move(piece, dice)
if piece.status == "home" then
return dice == 6
end
return true
end
function rules.movable_pieces()
local result = {}
for id, piece in pairs(state.pieces) do
if piece.owner == state.current_player and rules.can_move(piece, state.dice) then
table.insert(result, id)
end
end
return result
end
function rules.all_piece_ids()
local result = {}
for id, _ in pairs(state.pieces) do
table.insert(result, id)
end
return result
end
function rules.calculate_path(piece, dice)
local path = {}
if piece.status == "home" then
local start_index = board.start[piece.owner]
local pos = board.path[start_index]
table.insert(path, {x = pos.x, y = pos.y})
return path
end
for i = 1, dice do
local index = piece.path_index + i
while index > #board.path do
index = index - #board.path
end
local pos = board.path[index]
table.insert(path, {x = pos.x, y = pos.y})
end
return path
end
function rules.apply_move(piece, dice)
if piece.status == "home" then
piece.status = "path"
piece.path_index = board.start[piece.owner]
return
end
piece.path_index = piece.path_index + dice
while piece.path_index > #board.path do
piece.path_index = piece.path_index - #board.path
end
end
return rules

View File

@@ -0,0 +1,587 @@
---@meta
--- COMMON RUNTIME TYPES SECTION.
--- Source of truth: tool/lua_runtime_defs_common.lua
--- After editing this common section, run:
--- dart run tool/generate_lua_runtime_defs.dart
---@alias RuntimeNodeType
---| 'panel'
---| 'button'
---| 'text'
---| 'circle'
---| 'rect'
---| 'line'
---| 'progress'
---| 'listView'
---| 'sprite'
---| 'image'
---| 'spine'
---| 'particle'
---@alias RuntimeAnchor
---| 'center'
---| 'topLeft'
---| 'topRight'
---| 'bottomLeft'
---| 'bottomRight'
---@alias RuntimeTextAlign
---| 'left'
---| 'center'
---| 'right'
---@alias RuntimeParticlePreset
---| 'burst'
---| 'trail'
---| 'snow'
---| 'confetti'
---@alias RuntimeCommandType
---| 'move_path'
---| 'move_to'
---| 'fade_to'
---| 'scale_to'
---| 'rotate_to'
---| 'remove_node'
---| 'sequence'
---| 'parallel'
---| 'delay'
---| 'toast'
---| 'play_sound'
---| 'play_bgm'
---| 'pause_bgm'
---| 'resume_bgm'
---| 'stop_bgm'
---| 'preload_resources'
---| 'evict_resources'
---| 'cancel_commands'
---| 'play_spine_animation'
---| 'copy_text'
---@alias RuntimeEventType
---| 'tap'
---| 'animation_done'
---| 'resize'
---| 'scroll'
---@alias RuntimeScaleMode
---| 'fit'
---| 'fill'
---| 'stretch'
---| 'none'
---@alias RuntimeLayoutAlign
---| 'start'
---| 'center'
---| 'end'
---@alias RuntimeButtonVariant
---| 'primary'
---| 'secondary'
---| 'ghost'
---@class (exact) RuntimeNode
---@field id string
---@field type RuntimeNodeType
---@field parent? string
---@field asset? string Normal image/sprite/spine asset key. For button nodes this is the normal-state image.
---@field pressedAsset? string Button pressed-state image asset key.
---@field disabledAsset? string Button disabled-state image asset key.
---@field animation? string
---@field skin? string
---@field loop? boolean
---@field text? string
---@field x? number
---@field y? number
---@field width? number
---@field height? number
---@field paddingLeft? number
---@field paddingTop? number
---@field paddingRight? number
---@field paddingBottom? number
---@field anchor? RuntimeAnchor
---@field layer? integer
---@field visible? boolean
---@field alpha? number
---@field scale? number
---@field rotation? number
---@field color? string
---@field fontSize? number
---@field textAlign? RuntimeTextAlign
---@field radius? number
---@field strokeWidth? number
---@field value? number
---@field scrollX? number
---@field scrollY? number
---@field contentWidth? number
---@field contentHeight? number
---@field virtualized? boolean
---@field cacheExtent? number
---@field inertia? boolean
---@field scrollbarThumbColor? string
---@field scrollbarTrackColor? string
---@field scrollbarThickness? number
---@field scrollbarVisible? boolean
---@field interactive? boolean
---@field onTap? string
---@field onScroll? string
---@field preset? RuntimeParticlePreset
---@field count? integer
---@field duration? number
---@field speedMin? number
---@field speedMax? number
---@field gravityX? number
---@field gravityY? number
---@field spread? number
---@field colorTo? string
---@field radiusTo? number
---@field autoRemove? boolean
---@field fadeOut? boolean
---@class (exact) RuntimeNodeProps
---@field type? RuntimeNodeType
---@field parent? string
---@field asset? string Normal image/sprite/spine asset key. For button nodes this is the normal-state image.
---@field pressedAsset? string Button pressed-state image asset key.
---@field disabledAsset? string Button disabled-state image asset key.
---@field animation? string
---@field skin? string
---@field loop? boolean
---@field text? string
---@field x? number
---@field y? number
---@field width? number
---@field height? number
---@field paddingLeft? number
---@field paddingTop? number
---@field paddingRight? number
---@field paddingBottom? number
---@field anchor? RuntimeAnchor
---@field layer? integer
---@field visible? boolean
---@field alpha? number
---@field scale? number
---@field rotation? number
---@field color? string
---@field fontSize? number
---@field textAlign? RuntimeTextAlign
---@field radius? number
---@field strokeWidth? number
---@field value? number
---@field scrollX? number
---@field scrollY? number
---@field contentWidth? number
---@field contentHeight? number
---@field virtualized? boolean
---@field cacheExtent? number
---@field inertia? boolean
---@field scrollbarThumbColor? string
---@field scrollbarTrackColor? string
---@field scrollbarThickness? number
---@field scrollbarVisible? boolean
---@field interactive? boolean
---@field onTap? string
---@field onScroll? string
---@field preset? RuntimeParticlePreset
---@field count? integer
---@field duration? number
---@field speedMin? number
---@field speedMax? number
---@field gravityX? number
---@field gravityY? number
---@field spread? number
---@field colorTo? string
---@field radiusTo? number
---@field autoRemove? boolean
---@field fadeOut? boolean
---Helper-only fields accepted by runtime_ui/runtime_widgets. They are normalized
---before the node/update crosses the Dart Runtime protocol boundary.
---@class RuntimeNodeInit: RuntimeNodeProps
---@field w? number Alias for width.
---@field h? number Alias for height.
---@field size? number Alias for both width and height.
---@field handler? string Alias for onTap.
---@field onClick? string Alias for onTap.
---@class (exact) RuntimeNodeUpdate
---@field id string
---@field props RuntimeNodeProps
---@class (exact) RuntimeNodeRemove
---@field id string
---@class (exact) RuntimeDiffSection
---@field creates? RuntimeNode[]
---@field updates? RuntimeNodeUpdate[]
---@field removes? (string|RuntimeNodeRemove)[]
---@class (exact) RuntimeDiff
---@field render? RuntimeDiffSection
---@field ui? RuntimeDiffSection
---@field commands? RuntimeCommand[]
---@class (exact) RuntimeEvent
---@field type RuntimeEventType|string
---@field target? string
---@field handler? string
---@field x? number
---@field y? number
---@field data? table
---@class (exact) RuntimeCommand
---@field type RuntimeCommandType
---@field target? string
---@field scope? string
---@field id? string
---@field group? string
---@field commandGroup? string
---@field onComplete? string
---@field duration? number
---@field commands? RuntimeCommand[]
---@field path? RuntimePoint[]
---@field x? number
---@field y? number
---@field alpha? number
---@field scale? number
---@field angle? number
---@field text? string
---@field message? string
---@field asset? string
---@field name? string
---@field volume? number
---@field channel? string
---@field loop? boolean
---@field failOnError? boolean
---@field animation? string
---@field track? integer
---@field queue? boolean
---@field delay? number
---@class (exact) RuntimeCommandOpts
---@field id? string
---@field group? string
---@field commandGroup? string
---@field scope? string
---@field onComplete? string
---@field duration? number
---@class (exact) RuntimeAudioCommandOpts: RuntimeCommandOpts
---@field volume? number
---@field name? string
---@class (exact) RuntimeBgmCommandOpts: RuntimeAudioCommandOpts
---@field channel? string
---@field loop? boolean
---@class (exact) RuntimeSpineCommandOpts: RuntimeCommandOpts
---@field track? integer
---@field loop? boolean
---@field queue? boolean
---@field delay? number
---@class (exact) RuntimeResourceCommandOpts: RuntimeCommandOpts
---@field failOnError? boolean
---@class (exact) RuntimePoint
---@field x number
---@field y number
---@class (exact) RuntimeLocaleContext
---@field requested string
---@field resolved string
---@field default string
---@field supported string[]
---@field languageCode string
---@field scriptCode? string
---@field countryCode? string
---@class (exact) RuntimeScreenContext
---@field width number
---@field height number
---@class (exact) RuntimeDesignContext
---@field width number
---@field height number
---@class (exact) RuntimeViewportContext
---@field x number
---@field y number
---@field width number
---@field height number
---@field scaleX number
---@field scaleY number
---@field scaleMode RuntimeScaleMode|string
---@class (exact) RuntimeContext
---@field screen RuntimeScreenContext
---@field design RuntimeDesignContext
---@field viewport RuntimeViewportContext
---@field seed integer
---@field runtimeApiVersion integer
---@field gameId string
---@field gameVersion string
---@field locale? RuntimeLocaleContext
---@class RuntimeUi
---@field style fun(base?: RuntimeNodeProps, opts?: RuntimeNodeProps): RuntimeNodeProps
---@field with_parent fun(parent: string, opts?: RuntimeNodeProps): RuntimeNodeProps
---@field node fun(node_type: RuntimeNodeType, id: string, opts?: RuntimeNodeInit): RuntimeNode
---@field panel fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field rect fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field circle fun(id: string, x: number|RuntimeNodeInit, y?: number, size?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field line fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field progress fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, value?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field particle fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field text fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field button fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeNodeInit): RuntimeNode
---@field list_view fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field image fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field sprite fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field spine fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, animation?: string, opts?: RuntimeNodeInit): RuntimeNode
---@field update fun(id: string, props: RuntimeNodeInit): RuntimeNodeUpdate
---@field text_update fun(id: string, text: string): RuntimeNodeUpdate
---@field visible_update fun(id: string, visible: boolean): RuntimeNodeUpdate
---@field alpha_update fun(id: string, alpha: number): RuntimeNodeUpdate
---@field scale_update fun(id: string, scale: number): RuntimeNodeUpdate
---@field position_update fun(id: string, x: number, y: number): RuntimeNodeUpdate
---@field size_update fun(id: string, width: number, height: number): RuntimeNodeUpdate
---@field transform_update fun(id: string, x: number, y: number, scale: number, rotation: number): RuntimeNodeUpdate
---@field batch_update fun(ids: string[], props: RuntimeNodeInit): RuntimeNodeUpdate[]
---@field append fun(nodes: RuntimeNode[], node: RuntimeNode): RuntimeNode[]
---@field append_all fun(nodes: RuntimeNode[], extra_nodes: RuntimeNode[]): RuntimeNode[]
---@class (exact) RuntimeDialogButton
---@field id? string
---@field text string
---@field handler string
---@field color? string
---@class (exact) RuntimeDialogOpts
---@field screenWidth? number
---@field screenHeight? number
---@field overlay? boolean
---@field overlayColor? string
---@field blockInput? boolean
---@field layer? integer
---@field color? string
---@field radius? number
---@field panelStyle? RuntimeNodeProps
---@field titleColor? string
---@field titleSize? number
---@field titleStyle? RuntimeNodeProps
---@field messageColor? string
---@field messageSize? number
---@field messageStyle? RuntimeNodeProps
---@field buttons? RuntimeDialogButton[]
---@field buttonGap? number
---@field buttonStyle? RuntimeNodeProps
---@class RuntimeLabeledProgressOpts: RuntimeNodeInit
---@field labelHeight? number
---@field labelStyle? RuntimeNodeProps
---@class RuntimePillOpts: RuntimeNodeInit
---@field panelStyle? RuntimeNodeProps
---@field textStyle? RuntimeNodeProps
---@class RuntimeTextButtonOpts: RuntimeNodeInit
---@field variant? RuntimeButtonVariant
---@class RuntimeListItemOpts: RuntimeTextButtonOpts
---@field selected? boolean
---@field activeColor? string
---@field inactiveColor? string
---@class RuntimeTabItem
---@field id? string
---@field key? string
---@field text string
---@field handler? string
---@field selected? boolean
---@class RuntimeTabsOpts: RuntimeNodeInit
---@field tabs? RuntimeTabItem[]
---@field selected? string
---@field gap? number
---@field itemWidth? number
---@field itemHeight? number
---@field activeColor? string
---@field inactiveColor? string
---@field buttonStyle? RuntimeNodeProps
---@class RuntimeActionItem
---@field id? string
---@field text string
---@field handler? string
---@field visible? boolean
---@field color? string
---@field style? RuntimeNodeProps
---@class RuntimeActionRowOpts: RuntimeNodeInit
---@field actions? RuntimeActionItem[]
---@field gap? number
---@field itemWidth? number
---@field itemHeight? number
---@field buttonStyle? RuntimeNodeProps
---@class RuntimePanelHeaderOpts: RuntimeNodeInit
---@field eyebrow? string
---@field title string
---@field summary? string
---@field gap? number
---@field eyebrowId? string
---@field titleId? string
---@field summaryId? string
---@field eyebrowHeight? number
---@field titleHeight? number
---@field summaryHeight? number
---@field eyebrowStyle? RuntimeNodeProps
---@field titleStyle? RuntimeNodeProps
---@field summaryStyle? RuntimeNodeProps
---@class RuntimeWidgetTheme
---@field primary? string
---@field secondary? string
---@field success? string
---@field overlay? string
---@field surface? string
---@field surfaceAlt? string
---@field card? string
---@field text? string
---@field muted? string
---@field progress? string
---@field transparent? string
---@class RuntimeWidgets
---@field configure fun(tokens?: RuntimeWidgetTheme): RuntimeWidgets
---@field label fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field section_title fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field pill fun(id: string, text: string|RuntimePillOpts, x?: number, y?: number, width?: number, height?: number, opts?: RuntimePillOpts): RuntimeNode[]
---@field text_button fun(id: string, text: string|RuntimeTextButtonOpts, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeTextButtonOpts): RuntimeNode
---@field list_item fun(id: string, text: string|RuntimeListItemOpts, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeListItemOpts): RuntimeNode
---@field tabs fun(id: string, tabs: RuntimeTabItem[]|RuntimeTabsOpts, opts?: RuntimeTabsOpts): RuntimeNode[]
---@field action_row fun(id: string, actions: RuntimeActionItem[]|RuntimeActionRowOpts, opts?: RuntimeActionRowOpts): RuntimeNode[]
---@field panel_header fun(id: string, opts: RuntimePanelHeaderOpts): RuntimeNode[]
---@field overlay fun(id: string, width: number|RuntimeNodeInit, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field card fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field progress_bar fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, value?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field labeled_progress fun(id: string, label: string, x: number, y: number, width: number, height: number, value: number, opts?: RuntimeLabeledProgressOpts): RuntimeNode[]
---@field button_row fun(parent: string, id: string, buttons: RuntimeDialogButton[], x: number, y: number, width: number, height: number, gap?: number, opts?: RuntimeNodeProps): RuntimeNode[]
---@field dialog fun(id: string, title: string, message: string, x: number, y: number, width: number, height: number, opts?: RuntimeDialogOpts): RuntimeNode[]
---@class (exact) RuntimeLayoutItem
---@field node RuntimeNode
---@field marginLeft? number
---@field marginRight? number
---@field marginTop? number
---@field marginBottom? number
---@class RuntimeLayoutItemOpts
---@field margin? number
---@field mx? number
---@field my? number
---@field ml? number
---@field mr? number
---@field mt? number
---@field mb? number
---@field marginLeft? number
---@field marginRight? number
---@field marginTop? number
---@field marginBottom? number
---@class RuntimeLinearLayoutOpts
---@field x? number
---@field y? number
---@field width? number
---@field height? number
---@field gap? number
---@field align? RuntimeLayoutAlign
---@field padding? number
---@field paddingX? number
---@field paddingY? number
---@field px? number
---@field py? number
---@field paddingLeft? number
---@field paddingTop? number
---@class RuntimeBoxLayoutOpts: RuntimeLinearLayoutOpts
---@field rows? integer
---@field columns? integer
---@field cols? integer
---@field cellWidth? number
---@field cellHeight? number
---@field cellW? number
---@field cellH? number
---@field gapX? number
---@field gapY? number
---@field valign? RuntimeLayoutAlign
---@class RuntimeLayout
---@field item fun(node: RuntimeNode, opts?: RuntimeLayoutItemOpts): RuntimeLayoutItem
---@field local_position fun(origin: RuntimePoint, position: RuntimePoint): RuntimePoint
---@field row fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
---@field column fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
---@field stack fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
---@field box fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeBoxLayoutOpts): RuntimeNode[]
---@class RuntimeCommands
---@field toast fun(text: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field copy_text fun(text: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field delay fun(duration: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field sequence fun(items: RuntimeCommand[], opts?: RuntimeCommandOpts): RuntimeCommand
---@field parallel fun(items: RuntimeCommand[], opts?: RuntimeCommandOpts): RuntimeCommand
---@field move_path fun(target: string, path: RuntimePoint[], opts?: RuntimeCommandOpts): RuntimeCommand
---@field move_to fun(target: string, x: number, y: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field fade_to fun(target: string, alpha: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field scale_to fun(target: string, scale: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field rotate_to fun(target: string, angle: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field remove_node fun(target: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field play_spine_animation fun(target: string, animation: string, opts?: RuntimeSpineCommandOpts): RuntimeCommand
---@field play_sound fun(asset: string, opts?: RuntimeAudioCommandOpts): RuntimeCommand
---@field play_bgm fun(asset: string, opts?: RuntimeBgmCommandOpts): RuntimeCommand
---@field pause_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field resume_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field stop_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field preload_group fun(group: string, opts?: RuntimeResourceCommandOpts): RuntimeCommand
---@field evict_group fun(group: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field cancel_id fun(id: string): RuntimeCommand
---@field cancel_group fun(group: string): RuntimeCommand
---@field cancel_scope fun(scope: string): RuntimeCommand
---@class RuntimeImportApi
---@field import fun(moduleName: string): table
---@type RuntimeImportApi
runtime = runtime
---@alias PlayerColor 'red'|'blue'
---@alias GamePhase 'waiting_roll'|'waiting_piece'|'animating'
---@alias PieceStatus 'home'|'path'|'finished'
---@class (exact) PieceState
---@field id string
---@field owner PlayerColor
---@field path_index integer
---@field status PieceStatus
---@class (exact) LudoState
---@field current_player PlayerColor
---@field phase GamePhase
---@field dice_index integer
---@field dice_values integer[]
---@field dice? integer
---@field selected_piece? string
---@field players PlayerColor[]
---@field pieces table<string, PieceState>
---@class (exact) BoardPoint
---@field x number
---@field y number
---@class (exact) BoardData
---@field red_home BoardPoint[]
---@field blue_home BoardPoint[]
---@field path BoardPoint[]
---@field start table<PlayerColor, integer>

View File

@@ -0,0 +1,15 @@
---@type LudoState
return {
current_player = "red",
phase = "waiting_roll",
dice_index = 0,
dice_values = {6, 3, 5, 2, 6, 4},
selected_piece = nil,
players = {"red", "blue"},
pieces = {
red_1 = { id = "red_1", owner = "red", path_index = 0, status = "home" },
red_2 = { id = "red_2", owner = "red", path_index = 0, status = "home" },
blue_1 = { id = "blue_1", owner = "blue", path_index = 0, status = "home" },
blue_2 = { id = "blue_2", owner = "blue", path_index = 0, status = "home" }
}
}

View File

@@ -0,0 +1,80 @@
local theme = runtime.import("theme")
local colors = theme.colors
local styles = {}
styles.layers = {
background = 0,
board = 1,
board_content = 2,
piece = 10,
hud = 100,
hud_content = 101
}
styles.board_panel = {
color = colors.board,
layer = styles.layers.background
}
styles.board_title = {
fontSize = 24,
color = colors.text,
layer = styles.layers.board_content
}
styles.red_home = {
color = colors.red_home,
layer = styles.layers.board
}
styles.blue_home = {
color = colors.blue_home,
layer = styles.layers.board
}
styles.path_cell = {
anchor = "center",
color = colors.path_cell,
layer = styles.layers.board_content
}
styles.top_bar = {
color = colors.top_bar,
layer = styles.layers.hud
}
styles.hud_text = {
fontSize = 22,
color = colors.text,
layer = styles.layers.hud_content
}
styles.dice_button = {
color = colors.dice_button,
fontSize = 20,
layer = styles.layers.hud_content
}
styles.piece_highlight = {
scale = 1.25,
alpha = 1
}
styles.piece_normal = {
scale = 1,
alpha = 0.95
}
function styles.piece(owner)
return {
anchor = "center",
asset = "piece_" .. owner,
color = colors[owner],
layer = styles.layers.piece,
interactive = true,
onTap = "piece_tap"
}
end
return styles

View File

@@ -0,0 +1,18 @@
local i18n = runtime.import("i18n")
return {
title = function()
return i18n.t("title")
end,
colors = {
red = "#ef4444",
blue = "#3b82f6",
top_bar = "#020617",
dice_button = "#2563eb",
board = "#1e293b",
red_home = "#7f1d1d",
blue_home = "#1e3a8a",
path_cell = "#f8fafc",
text = "#ffffff"
}
}

View File

@@ -0,0 +1,74 @@
---@type RuntimeUi
local runtime_ui = runtime.import("runtime_ui")
---@type RuntimeLayout
local layout = runtime.import("layout")
local theme = runtime.import("theme")
local i18n = runtime.import("i18n")
local styles = runtime.import("styles")
local state = runtime.import("state")
local board = runtime.import("board")
local rules = runtime.import("rules")
local ui = {}
local board_origin = { x = 40, y = 90 }
function ui.highlight_updates(ids, enabled)
return runtime_ui.batch_update(ids, enabled and styles.piece_highlight or styles.piece_normal)
end
function ui.create_board_nodes()
local nodes = {
runtime_ui.sprite("board_panel", "board", 40, 90, 640, 520, styles.board_panel),
runtime_ui.text("board_title", theme.title(), 240, 15, 180, 40, runtime_ui.with_parent("board_panel", styles.board_title)),
runtime_ui.panel("red_home", 20, 390, 130, 90, runtime_ui.with_parent("board_panel", styles.red_home)),
runtime_ui.panel("blue_home", 460, -10, 130, 90, runtime_ui.with_parent("board_panel", styles.blue_home))
}
for index, cell in ipairs(board.path) do
local pos = layout.local_position(board_origin, cell)
runtime_ui.append(nodes, runtime_ui.circle("cell_" .. tostring(index), pos.x, pos.y, 28, runtime_ui.with_parent("board_panel", styles.path_cell)))
end
for id, piece in pairs(state.pieces) do
local pos = rules.piece_home_position(piece)
runtime_ui.append(nodes, runtime_ui.circle(id, pos.x, pos.y, 44, styles.piece(piece.owner)))
end
return nodes
end
function ui.create_ui_nodes()
local hud_items = layout.row("top_bar", {
runtime_ui.text("turn_text", i18n.t("current_player", { player = i18n.player(state.current_player) }), 0, 0, 230, 32, styles.hud_text),
runtime_ui.text("dice_text", i18n.t("dice_empty"), 0, 0, 120, 32, styles.hud_text),
layout.item(
runtime_ui.button("dice_button", i18n.t("roll_button"), 0, 0, 130, 48, "roll_dice", styles.dice_button),
{ marginLeft = 134 }
)
}, {
x = 24,
height = 76,
gap = 16,
align = "center"
})
local nodes = { runtime_ui.panel("top_bar", 0, 0, 720, 76, styles.top_bar) }
return runtime_ui.append_all(nodes, hud_items)
end
function ui.dice_and_turn_updates(dice, current_player)
return {
runtime_ui.text_update("dice_text", i18n.t("dice_value", { value = dice })),
runtime_ui.text_update("turn_text", i18n.t("current_player", { player = i18n.player(current_player) }))
}
end
function ui.dice_update(dice)
return { runtime_ui.text_update("dice_text", i18n.t("dice_value", { value = dice })) }
end
function ui.turn_update(current_player)
return { runtime_ui.text_update("turn_text", i18n.t("current_player", { player = i18n.player(current_player) })) }
end
return ui

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

View File

@@ -0,0 +1,62 @@
{
"gameId": "showcase",
"name": "Lua Runtime Showcase",
"version": "0.1.0",
"runtimeApiVersion": 1,
"entry": "scripts/main.lua",
"assetsBase": "assets",
"defaultLocale": "zh-Hans",
"supportedLocales": [
"zh-Hans",
"en"
],
"display": {
"designWidth": 720,
"designHeight": 720,
"scaleMode": "fit"
},
"modules": {
"runtime_ui": "runtime:runtime_ui.lua",
"runtime_widgets": "runtime:runtime_widgets.lua",
"runtime_commands": "runtime:runtime_commands.lua",
"layout": "runtime:layout.lua",
"theme": "scripts/theme.lua",
"styles": "scripts/styles.lua",
"state": "scripts/state.lua",
"i18n": "scripts/i18n.lua",
"examples": "scripts/examples.lua",
"ui": "scripts/ui.lua"
},
"resources": {
"sample_image": {
"type": "image",
"path": "assets/sample.png",
"preload": "optional",
"group": "media"
},
"click": {
"type": "audio",
"path": "assets/click.wav",
"preload": "lazy",
"group": "media"
},
"button_normal": {
"type": "image",
"path": "assets/button_normal.png",
"preload": "optional",
"group": "media"
},
"button_pressed": {
"type": "image",
"path": "assets/button_pressed.png",
"preload": "optional",
"group": "media"
},
"button_disabled": {
"type": "image",
"path": "assets/button_disabled.png",
"preload": "optional",
"group": "media"
}
}
}

View File

@@ -0,0 +1,819 @@
---@class (exact) ShowcaseAction
---@field text string
---@field text_en? string
---@field handler string
---@class (exact) ShowcaseExample
---@field id string
---@field group string
---@field group_en? string
---@field category string
---@field category_en? string
---@field menu string
---@field menu_en? string
---@field title string
---@field title_en? string
---@field summary string
---@field summary_en? string
---@field code string
---@field params string
---@field params_en? string
---@field actions ShowcaseAction[]
---@class ShowcaseExamples
local examples = {}
---@type ShowcaseExample[]
examples.items = {
{
id = "nodes",
group = "基础功能",
group_en = "Basics",
category = "节点",
category_en = "Nodes",
menu = "基础节点",
menu_en = "Nodes",
title = "RuntimeNode 基础组件",
title_en = "RuntimeNode primitives",
summary = "基础节点、交互和属性更新。",
summary_en = "Primitive nodes, interaction and property updates.",
code = [[local runtime_ui = runtime.import("runtime_ui")
runtime_ui.circle("hero", {
x = 80,
y = 64,
size = 48,
color = "#ff22c55e",
interactive = true,
handler = "demo_anim"
})]],
params = [[参数说明type/circle/rect/line 定义节点类型;
x/y/width/height 定位尺寸;
w/h 是 width/height 的便利别名;
size 会同时设置 width 和 height
handler/onClick 是 onTap 的便利别名;
color/alpha/scale/rotation 控制表现;
interactive/onTap 打开点击事件。
填写样例:
runtime_ui.circle("hero", {
x = 80,
y = 64,
size = 48,
color = "#ff22c55e",
alpha = 0.9,
scale = 1.0,
interactive = true,
handler = "demo_anim"
})]],
params_en = [[Params: type/circle/rect/line choose node kind;
x/y/width/height place it;
color/alpha/scale/rotation style it;
interactive/onTap enable taps.]],
actions = {
{ text = "更新进度", text_en = "Progress", handler = "demo_progress" },
{ text = "显隐/透明", text_en = "Visibility", handler = "demo_visibility" }
}
},
{
id = "text_demo",
group = "基础功能",
group_en = "Basics",
category = "文本",
category_en = "Text",
menu = "文本",
menu_en = "Text",
title = "Text 文本组件",
title_en = "Text component",
summary = "当前支持纯文本、颜色和字号;暂不支持富文本。",
summary_en = "Plain text, color and font size; rich text is not supported yet.",
code = [[runtime_ui.text("label", "Hello Lua", 20, 40, 220, 28, {
color = "#ffe2e8f0",
fontSize = 18
})
-- 当前协议没有 richText/spans 字段]],
params = [[参数说明text 是纯文本;
color/fontSize/alpha 控制整体样式;
textAlign 可填 "left" / "center" / "right"
当前没有 richText/spans不能做局部文字样式。
填写样例:
runtime_ui.text("label", "Hello Lua", 20, 40, 220, 28, {
color = "#ffe2e8f0",
fontSize = 18,
textAlign = "left"
})]],
params_en = [[Params: text is plain copy;
color/fontSize/alpha style the whole node;
richText/spans are not supported yet.]],
actions = {
{ text = "改文案", text_en = "Change", handler = "demo_text_change" },
{ text = "样式切换", text_en = "Style", handler = "demo_text_style" }
}
},
{
id = "buttons",
group = "基础功能",
group_en = "Basics",
category = "按钮",
category_en = "Buttons",
menu = "按钮交互",
menu_en = "Buttons",
title = "基础按钮和点击事件",
title_en = "Buttons and tap events",
summary = "button 节点、onTap handler、状态更新和禁用态。",
summary_en = "Button nodes, onTap handlers, state updates and disabled style.",
code = [[runtime_ui.button("ok", {
text = "确认",
x = 20,
y = 40,
w = 120,
h = 34,
handler = "submit",
color = theme.primary,
radius = 10,
fontSize = 13,
asset = "button_normal",
pressedAsset = "button_pressed",
disabledAsset = "button_disabled"
})
-- asset/pressedAsset/disabledAsset 都是 manifest image 资源 key。
-- event.handler == "submit" 时返回 Diff/Command]],
params = [[参数说明button = 背景 + 文本 + onTap
w/h 是 width/height 的便利别名;
handler/onClick 是 onTap 的便利别名;
color/radius/fontSize 控制样式;
asset/pressedAsset/disabledAsset 可配置 normal/pressed/disabled 三态图片;
interactive=false 可禁用命中并使用 disabledAsset。
填写样例:
runtime_ui.button("ok", {
text = "确认",
x = 20,
y = 40,
w = 120,
h = 34,
handler = "submit",
color = theme.primary,
radius = 10,
fontSize = 13,
asset = "button_normal",
pressedAsset = "button_pressed",
disabledAsset = "button_disabled",
interactive = true
})]],
params_en = [[Params: button combines background, label and onTap;
color/radius/fontSize style it;
interactive=false disables hit testing.]],
actions = {
{ text = "点击按钮", text_en = "Tap", handler = "demo_button_primary" },
{ text = "切换状态", text_en = "Toggle", handler = "demo_button_toggle" }
}
},
{
id = "button_images",
group = "基础功能",
group_en = "Basics",
category = "按钮",
category_en = "Buttons",
menu = "按钮三态图",
menu_en = "Button skins",
title = "Button 三态图片",
title_en = "Button state images",
summary = "button 使用 normal / pressed / disabled 三态图片资源。",
summary_en = "Buttons can render normal, pressed and disabled image assets.",
code = [[runtime_ui.button("start", {
text = "开始",
x = 20,
y = 40,
w = 132,
h = 40,
handler = "start",
asset = "button_normal",
pressedAsset = "button_pressed",
disabledAsset = "button_disabled"
})
runtime_ui.button("locked", {
text = "禁用",
x = 170,
y = 40,
w = 132,
h = 40,
handler = "noop",
asset = "button_normal",
pressedAsset = "button_pressed",
disabledAsset = "button_disabled",
interactive = false
})]],
params = [[参数说明:
asset = normal 状态图片资源 key
pressedAsset = 按下状态图片资源 key
disabledAsset = 禁用状态图片资源 key
interactive=false 时 Runtime 自动选择 disabledAsset
按住按钮时 Runtime 自动切换 pressedAsset
未配置对应状态图片时回退到 asset再回退到 color/radius 背景。
manifest 示例:
"button_normal": { "type": "image", "path": "assets/button_normal.png" },
"button_pressed": { "type": "image", "path": "assets/button_pressed.png" },
"button_disabled": { "type": "image", "path": "assets/button_disabled.png" }]],
params_en = [[Params:
asset is the normal-state image resource key;
pressedAsset is used while the button is pressed;
disabledAsset is used when interactive=false;
missing state images fall back to asset, then to color/radius background.]],
actions = {
{ text = "点击图片按钮", text_en = "Tap", handler = "demo_button_image_tap" },
{ text = "切换禁用", text_en = "Toggle", handler = "demo_button_image_toggle" }
}
},
{
id = "sprites",
group = "基础功能",
group_en = "Basics",
category = "精灵",
category_en = "Sprites",
menu = "图片精灵",
menu_en = "Sprites",
title = "图片和 Sprite 节点",
title_en = "Image and sprite nodes",
summary = "image/sprite 使用 manifest 资源 key不暴露真实路径。",
summary_en = "Image/sprite nodes use manifest resource keys, not raw paths.",
code = [[runtime_ui.image("avatar", "sample_image", 24, 48, 56, 56)
runtime_ui.sprite("icon", "sample_image", 104, 48, 56, 56, {
layer = 20
})]],
params = [[参数说明asset 必须是 manifest 资源 key
image/sprite 不暴露真实路径;
layer 控制绘制顺序。
填写样例:
-- manifest.resources.sample_image.type = "image"
runtime_ui.image("avatar", "sample_image", 24, 48, 56, 56, {
layer = 20,
alpha = 1
})]],
params_en = [[Params: asset must be a manifest resource key;
image/sprite never expose raw paths;
layer controls draw order.]],
actions = {
{ text = "精灵动画", text_en = "Animate", handler = "demo_sprite_anim" },
{ text = "切换样式", text_en = "Style", handler = "demo_sprite_style" }
}
},
{
id = "radio_group",
group = "基础功能",
group_en = "Basics",
category = "选择",
category_en = "Selection",
menu = "RadioGroup",
menu_en = "Radio",
title = "RadioGroup 组合模式",
title_en = "RadioGroup composition",
summary = "Lua 用 circle/text/button 组合单选项。",
summary_en = "Lua composes radio options from circle/text/button nodes.",
code = [[-- RadioGroup 不是 Dart 原生控件
-- Lua 输出普通 RuntimeNodecircle + text + button
runtime_ui.circle("radio_dot", 24, 52, 14, { color = theme.primary })
runtime_ui.text("radio_label", "Audio", 46, 49, 120, 20)]],
params = [[参数说明RadioGroup 是 Lua 组合,不是原生控件;
用 circle/text/button 组合,并由 Lua state 保存选中值。
填写样例:
local option = { key = "audio", label = "Audio", y = 72 }
runtime_ui.circle("radio_" .. option.key .. "_dot", 18, option.y, 14, {
color = selected and theme.primary or "#ff475569"
})
runtime_ui.button("radio_" .. option.key .. "_hit", "", 8, option.y - 4, 180, 24, "demo_radio_audio", {
color = "#00000000",
interactive = true
})]],
params_en = [[Params: RadioGroup is Lua composition, not a native control;
compose circle/text/button and keep selection in Lua state.]],
actions = {
{ text = "选 Audio", text_en = "Audio", handler = "demo_radio_audio" },
{ text = "选 Spine", text_en = "Spine", handler = "demo_radio_spine" },
{ text = "选 Lua", text_en = "Lua", handler = "demo_radio_lua" }
}
},
{
id = "list_view",
group = "基础功能",
group_en = "Basics",
category = "列表",
category_en = "List",
menu = "ListView",
menu_en = "List",
title = "原生 ListView 容器",
title_en = "Native ListView container",
summary = "双轴滚动、惯性、回调、滚动条样式和虚拟化。",
summary_en = "Two-axis scroll, inertia, callbacks, styled scrollbar and culling.",
code = [[runtime_ui.list_view("list", 16, 42, 260, 72, {
contentWidth = 420,
contentHeight = 150,
scrollX = state.list_scroll_x,
scrollY = state.list_scroll_y,
virtualized = true,
cacheExtent = 24,
inertia = true,
onScroll = "demo_list_scrolled"
})
runtime_ui.button("row_1", "Lua", 8, 8, 220, 24, "select", {
parent = "list"
})]],
params = [[参数说明scrollX/scrollY 是双轴偏移;
contentWidth/contentHeight 是内容尺寸;
virtualized/cacheExtent 控制直接子节点裁剪;
inertia 开启拖动惯性;
onScroll 接收滚动回调;
scrollbarVisible=false 可隐藏滚动条;
scrollbarThumbColor/scrollbarTrackColor/scrollbarThickness 控制样式。
填写样例:
local opts = {
contentWidth = 420,
contentHeight = 150,
scrollX = 0,
scrollY = 0,
virtualized = true,
cacheExtent = 24,
inertia = true,
onScroll = "demo_list_scrolled",
scrollbarVisible = false
}
runtime_ui.list_view("list", 16, 42, 260, 72, opts)
-- 子节点必须把 parent 指向 listView
runtime_ui.button("row_1", "Lua", 8, 8, 220, 24, "select", {
parent = "list"
})]],
params_en = [[Params: scrollX/scrollY are two-axis offsets;
contentWidth/contentHeight define content size;
virtualized/cacheExtent cull children;
inertia enables momentum;
onScroll emits callbacks;
scrollbar* styles bars.]],
actions = {
{ text = "横/竖排列", text_en = "Axis", handler = "demo_list_horizontal" },
{ text = "下一项", text_en = "Next", handler = "demo_list_next" },
{ text = "重置滚动", text_en = "Reset", handler = "demo_list_reset" }
}
},
{
id = "particles",
group = "基础功能",
group_en = "Basics",
category = "特效",
category_en = "Effects",
menu = "粒子特效",
menu_en = "Particles",
title = "Particle 粒子特效",
title_en = "Particle effects",
summary = "Lua 描述粒子参数Dart Runtime 创建 Flame 粒子组件。",
summary_en = "Lua describes particle params; Dart Runtime creates Flame particles.",
code = [[runtime_ui.particle("hit_fx", 220, 140, 160, 160, {
preset = "burst",
count = 40,
duration = 0.6,
color = "#ffffcc33",
colorTo = "#00ffcc33",
radius = 2.4,
radiusTo = 0,
speedMin = 60,
speedMax = 180,
gravityY = 120,
spread = 360,
autoRemove = true,
fadeOut = true,
layer = 80
})]],
params = [[参数说明particle 是 Runtime 原生节点Lua 不持有 Flame 粒子对象;
preset 可填 "burst" / "trail" / "snow" / "confetti"
count 是粒子数量;
duration 是生命周期秒数;
color/colorTo 控制颜色渐变;
radius/radiusTo 控制尺寸变化;
speedMin/speedMax 控制初速度范围;
gravityX/gravityY 控制加速度;
spread 控制发散角度;
autoRemove=true 表示生命周期结束后自动移除;
fadeOut=true 表示随进度淡出。
填写样例:
local opts = {
preset = "confetti",
count = 72,
duration = 1.2,
color = "#ffff4d6d",
colorTo = "#fffacc15",
radius = 2.6,
radiusTo = 0,
speedMin = 120,
speedMax = 260,
gravityY = 240,
spread = 90,
autoRemove = true,
fadeOut = true
}
runtime_ui.particle("win_fx", 280, 72, 220, 160, opts)]],
params_en = [[Params: particle is a native Runtime node; Lua never owns Flame particle objects;
preset: burst/trail/snow/confetti;
count controls particle amount;
duration is lifetime in seconds;
color/colorTo define gradient;
radius/radiusTo define size change;
speedMin/speedMax define initial speed;
gravityX/gravityY define acceleration;
spread defines emission angle;
autoRemove removes after lifetime;
fadeOut fades by progress.]],
actions = {
{ text = "爆发", text_en = "Burst", handler = "demo_particle_burst" },
{ text = "彩纸", text_en = "Confetti", handler = "demo_particle_confetti" },
{ text = "雪花", text_en = "Snow", handler = "demo_particle_snow" }
}
},
{
id = "layout_demo",
group = "基础功能",
group_en = "Basics",
category = "布局",
category_en = "Layout",
menu = "布局 Helper",
menu_en = "Layout",
title = "Lua layout helper",
title_en = "Lua layout helper",
summary = "layout.row/column/box 消费临时 margin不污染协议。",
summary_en = "layout.row/column/box consume temporary margin without polluting protocol tables.",
code = [[local layout = runtime.import("layout")
layout.box("panel", {
runtime_ui.rect("a", 0, 0, 42, 28),
runtime_ui.rect("b", 0, 0, 42, 28),
runtime_ui.rect("c", 0, 0, 42, 28),
runtime_ui.rect("d", 0, 0, 42, 28)
}, {
x = 16,
y = 48,
rows = 2,
columns = 2,
cellWidth = 58,
cellHeight = 34,
gapX = 8,
gapY = 8,
align = "center",
valign = "center"
})]],
params = [[参数说明layout.item 的 margin 只用于 Lua 布局计算,不会进入 RuntimeNode props
row/column 处理单轴排列;
box 按 rows/columns 把节点排成几排几列;
cellWidth/cellHeight 是单元格尺寸;
gapX/gapY 是列间距和行间距;
align/valign 控制节点在单元格内的水平/垂直对齐。
填写样例:
layout.box("panel", {
layout.item(runtime_ui.rect("a", 0, 0, 42, 28), { marginRight = 4 }),
runtime_ui.rect("b", 0, 0, 42, 28),
runtime_ui.rect("c", 0, 0, 42, 28),
runtime_ui.rect("d", 0, 0, 42, 28)
}, {
x = 16,
y = 48,
rows = 2,
columns = 2,
cellWidth = 58,
cellHeight = 34,
gapX = 8,
gapY = 8,
align = "center",
valign = "center"
})]],
params_en = [[Params: layout.item margins are Lua-only layout metadata and never enter RuntimeNode props;
box uses rows/columns to place nodes in a grid;
cellWidth/cellHeight define cells;
gapX/gapY define column and row gaps;
align/valign align each node inside its cell.]],
actions = {
{ text = "横向布局", text_en = "Row", handler = "demo_layout_row" },
{ text = "纵向布局", text_en = "Column", handler = "demo_layout_column" },
{ text = "Box 网格", text_en = "Box", handler = "demo_layout_box" }
}
},
{
id = "diff",
group = "运行协议",
group_en = "Runtime Protocol",
category = "Diff",
category_en = "Diff",
menu = "Diff 更新",
menu_en = "Diff",
title = "GameDiff 创建、更新、移除",
title_en = "GameDiff create/update/remove",
summary = "Diff 创建、更新、移除节点。",
summary_en = "Create, update and remove nodes with GameDiff.",
code = [[return {
render = {
creates = { node },
updates = { { id = "node", props = { x = 120 } } },
removes = { { id = "node" } }
}
}]],
params = [[参数说明creates 创建节点;
updates 只更新 props
removes 只需要 id。
Runtime 会白名单校验未知字段。
填写样例:
return {
ui = {
creates = {
runtime_ui.text("tip", "Hello", 20, 20, 160, 24, { textAlign = "left" })
},
updates = {
{ id = "tip", props = { text = "Updated", color = "#ffffa000" } }
},
removes = {
{ id = "tip" }
}
}
}]],
params_en = [[Params: creates add nodes;
updates patch props;
removes only need ids.
Runtime rejects unknown fields by whitelist.]],
actions = {
{ text = "创建/删除", text_en = "Create/remove", handler = "demo_temp" },
{ text = "Toast", text_en = "Toast", handler = "demo_toast" }
}
},
{
id = "commands",
group = "运行协议",
group_en = "Runtime Protocol",
category = "命令",
category_en = "Commands",
menu = "命令动画",
menu_en = "Commands",
title = "RuntimeCommand 动作和组合",
title_en = "RuntimeCommand actions",
summary = "动作、组合、取消和完成回调。",
summary_en = "Actions, composition, cancellation and callbacks.",
code = [[commands.sequence({
commands.move_to("actor", 220, 104, { duration = 0.55 }),
commands.parallel({
commands.scale_to("actor", 1.45, { duration = 0.35 }),
commands.fade_to("actor", 0.45, { duration = 0.35 })
})
}, { group = "demo_anim", onComplete = "done" })]],
params = [[参数说明target 指向节点;
duration 控制时长;
sequence 串行;
parallel 并行;
group/id/scope 可用于取消。
填写样例:
local move_opts = {
duration = 0.55,
id = "intro_move",
group = "intro",
scope = "dialog_1"
}
commands.move_to("actor", 220, 104, move_opts)
commands.cancel_group("intro")]],
params_en = [[Params: target points to a node;
duration controls timing;
sequence runs serially;
parallel runs together;
group/id/scope support cancellation.]],
actions = {
{ text = "动画序列", text_en = "Animate", handler = "demo_anim" },
{ text = "取消动画", text_en = "Cancel", handler = "demo_cancel" }
}
},
{
id = "widgets",
group = "Lua 表现层",
group_en = "Lua Layer",
category = "Widget",
category_en = "Widget",
menu = "组合组件",
menu_en = "Widgets",
title = "Lua Widget 组合组件",
title_en = "Lua widget composition",
summary = "Lua 组合组件仍输出普通 RuntimeNode。",
summary_en = "Lua widgets still output plain RuntimeNode tables.",
code = [[local widgets = runtime.import("runtime_widgets")
widgets.dialog("dialog", "标题", "内容", 150, 190, 420, 230, {
buttons = {
{ text = "确定", handler = "close_dialog" }
}
})]],
params = [[参数说明widgets.dialog 是 Lua helper输出普通 RuntimeNode
buttons 数组定义文案和 handler。
填写样例:
local dialog_opts = {
buttons = {
{ text = "确定", handler = "close_dialog" },
{ text = "取消", handler = "cancel_dialog" }
},
modal = true
}
widgets.dialog("dialog", "标题", "内容", 150, 190, 420, 230, dialog_opts)]],
params_en = [[Params: widgets.dialog is a Lua helper that emits plain RuntimeNodes;
buttons define labels and handlers.]],
actions = {
{ text = "打开 Dialog", text_en = "Open Dialog", handler = "demo_dialog" },
{ text = "关闭 Dialog", text_en = "Close Dialog", handler = "close_dialog" }
}
},
{
id = "audio",
group = "资源能力",
group_en = "Assets",
category = "资源",
category_en = "Assets",
menu = "资源音频",
menu_en = "Audio",
title = "资源、音效和 BGM",
title_en = "Assets, SFX and BGM",
summary = "资源 key、图片、音效、BGM 和资源组。",
summary_en = "Resource keys, images, SFX, BGM and groups.",
code = [[commands.play_sound("click", { volume = 0.8 })
commands.play_bgm("click", { channel = "demo", loop = true })
commands.preload_group("media", { failOnError = true })]],
params = [[参数说明asset/name 使用 manifest audio key
volume 范围 0~1
channel 管理 BGM
group 管理资源预载/释放。
填写样例:
commands.play_sound("click", {
volume = 0.8,
id = "click_sfx"
})
commands.play_bgm("click", {
channel = "demo",
loop = true,
volume = 0.5
})
commands.preload_group("media", { failOnError = true })]],
params_en = [[Params: asset/name use manifest audio keys;
volume is 0..1;
channel manages BGM;
group manages resource preload/evict.]],
actions = {
{ text = "音效", text_en = "SFX", handler = "demo_sound" },
{ text = "BGM 循环", text_en = "BGM loop", handler = "demo_bgm" },
{ text = "预载/释放", text_en = "Preload/evict", handler = "demo_resource" }
}
},
{
id = "spine",
group = "资源能力",
group_en = "Assets",
category = "骨骼",
category_en = "Spine",
menu = "Spine 模板",
menu_en = "Spine",
title = "Spine 接入模板",
title_en = "Spine integration template",
summary = "Spine 资源、节点和动画命令模板。",
summary_en = "Template for Spine resources, nodes and animation commands.",
code = [[-- manifest.resources
"hero_spine": {
"type": "spine",
"atlas": "assets/hero.atlas",
"skeleton": "assets/hero.skel"
}
runtime_ui.spine("hero", "hero_spine", 100, 120, 160, 220, "idle")
commands.play_spine_animation("hero", "attack", { loop = false })]],
params = [[参数说明spine 节点使用 manifest spine key
animation/skin/loop 描述播放状态;
track/queue/delay 控制动画命令。
填写样例:
runtime_ui.spine("hero", "hero_spine", 100, 120, 160, 220, "idle", {
skin = "default",
loop = true
})
commands.play_spine_animation("hero", "attack", {
track = 0,
loop = false,
queue = false,
delay = 0
})]],
params_en = [[Params: spine nodes use manifest spine keys;
animation/skin/loop describe playback;
track/queue/delay control animation commands.]],
actions = {
{ text = "播放音效", text_en = "SFX", handler = "demo_sound" },
{ text = "Toast", text_en = "Toast", handler = "demo_toast" }
}
},
{
id = "i18n",
group = "平台能力",
group_en = "Platform",
category = "本地化",
category_en = "I18N",
menu = "多语言",
menu_en = "I18N",
title = "Lua 多语言 Showcase",
title_en = "Lua-owned localization",
summary = "Lua 管理文案表、回退和语言切换。",
summary_en = "Lua owns copy tables, fallback and locale switching.",
code = [[local i18n = runtime.import("i18n")
i18n.apply_context(ctx)
runtime_ui.text("title", i18n.t("app_title"), 24, 18, 360, 34)
-- 点击按钮i18n.toggle_locale() 后更新所有文案]],
params = [[参数说明Runtime 只传 ctx.locale
Lua 自己管理 messages、fallback、刷新 Diff。
填写样例:
local messages = {
["zh-Hans"] = { app_title = "Lua Showcase" },
en = { app_title = "Lua Showcase" }
}
local locale = ctx.locale or "zh-Hans"
local text = (messages[locale] or messages["zh-Hans"]).app_title]],
params_en = [[Params: Runtime only passes ctx.locale;
Lua owns messages, fallback and refresh diffs.]],
actions = {
{ text = "切换语言", text_en = "Toggle locale", handler = "demo_i18n_toggle" },
{ text = "刷新文案", text_en = "Refresh copy", handler = "demo_i18n_refresh" }
}
},
{
id = "responsive",
group = "平台能力",
group_en = "Platform",
category = "适配",
category_en = "Layout",
menu = "分辨率适配",
menu_en = "Responsive",
title = "分辨率适配 Showcase",
title_en = "Responsive layout showcase",
summary = "设计分辨率、viewport 和布局模拟。",
summary_en = "Design size, viewport and layout simulation.",
code = [[-- manifest.display
{
"designWidth": 720,
"designHeight": 720,
"scaleMode": "fit"
}
-- ctx.screen / ctx.design / ctx.viewport
-- Lua 根据设计宽度选择 compact / desktop 布局]],
params = [[参数说明manifest.display 声明设计分辨率;
ctx.screen/design/viewport 给 Lua 做布局决策。
填写样例:
-- manifest.display
{
designWidth = 720,
designHeight = 720,
scaleMode = "fit"
}
local compact = ctx.screen.width < 640
local menu_w = compact and 220 or 320]],
params_en = [[Params: manifest.display declares design size;
ctx.screen/design/viewport let Lua make layout decisions.]],
actions = {
{ text = "手机宽度", text_en = "Phone", handler = "demo_responsive_phone" },
{ text = "平板宽度", text_en = "Tablet", handler = "demo_responsive_tablet" },
{ text = "桌面宽度", text_en = "Desktop", handler = "demo_responsive_desktop" }
}
}
}
---@return ShowcaseExample[]
function examples.all()
return examples.items
end
---@param id string
---@return ShowcaseExample
function examples.find(id)
for _, example in ipairs(examples.items) do
if example.id == id then
return example
end
end
return examples.items[1]
end
return examples

View File

@@ -0,0 +1,151 @@
local state = runtime.import("state")
---@class ShowcaseI18n
local i18n = {}
local fallback_locale = "zh-Hans"
local messages = {
["zh-Hans"] = {
app_title = "Lua Runtime Showcase",
app_subtitle = "仿 Cocos Showcase左侧管理所有示例右侧查看说明并直接运行。",
examples_title = "Examples",
preview_title = "Preview / Runtime Stage",
tab_code = "代码",
tab_params = "参数",
copy_code = "复制代码",
copy_params = "复制参数",
selected_prefix = "已选择示例:",
status_ready = "点击按钮查看 RuntimeEvent -> Lua -> Diff / Command 示例",
i18n_current = "当前语言",
i18n_zh = "中文文案:运行时只传 locale翻译由 Lua 包管理。",
i18n_en = "English copy: Lua owns text resources and fallback policy.",
i18n_switched = "已切换语言:",
responsive_title = "分辨率适配",
responsive_design = "设计分辨率",
responsive_screen = "屏幕",
responsive_viewport = "视口",
responsive_phone = "模拟手机宽度",
responsive_tablet = "模拟平板宽度",
responsive_desktop = "模拟桌面宽度"
},
en = {
app_title = "Lua Runtime Showcase",
app_subtitle = "Cocos-style showcase: select examples on the left, inspect and run them on the right.",
examples_title = "Examples",
preview_title = "Preview / Runtime Stage",
tab_code = "Code",
tab_params = "Params",
copy_code = "Copy code",
copy_params = "Copy params",
selected_prefix = "Selected example: ",
status_ready = "Tap actions to inspect RuntimeEvent -> Lua -> Diff / Command.",
i18n_current = "Current locale",
i18n_zh = "中文文案:运行时只传 locale翻译由 Lua 包管理。",
i18n_en = "English copy: Lua owns text resources and fallback policy.",
i18n_switched = "Locale switched: ",
responsive_title = "Responsive Layout",
responsive_design = "Design",
responsive_screen = "Screen",
responsive_viewport = "Viewport",
responsive_phone = "Simulate phone width",
responsive_tablet = "Simulate tablet width",
responsive_desktop = "Simulate desktop width"
}
}
---@param locale? string
---@return string
local function normalize(locale)
if locale == nil or locale == "" then
return fallback_locale
end
if messages[locale] ~= nil then
return locale
end
local language = string.match(locale, "^([A-Za-z]+)")
if language ~= nil and messages[language] ~= nil then
return language
end
return fallback_locale
end
---@param ctx? RuntimeContext
function i18n.apply_context(ctx)
local locale = fallback_locale
if ctx ~= nil and ctx.locale ~= nil and ctx.locale.resolved ~= nil then
locale = ctx.locale.resolved
end
state.locale = normalize(locale)
end
---@param locale string
function i18n.set_locale(locale)
state.locale = normalize(locale)
end
---@return string
function i18n.toggle_locale()
if state.locale == "en" then
state.locale = "zh-Hans"
else
state.locale = "en"
end
return state.locale
end
---@return string
function i18n.current_locale()
return normalize(state.locale)
end
---@param key string
---@return string
function i18n.t(key)
local locale = normalize(state.locale)
local bundle = messages[locale] or messages[fallback_locale]
return bundle[key] or messages[fallback_locale][key] or key
end
---@param example ShowcaseExample
---@param field string
---@return string
function i18n.example_field(example, field)
if i18n.current_locale() == "en" then
local en_value = example[field .. "_en"]
if en_value ~= nil and en_value ~= "" then
return en_value
end
end
return example[field]
end
---@param example ShowcaseExample
---@return string
function i18n.example_label(example)
return i18n.example_field(example, "menu")
end
---@param example ShowcaseExample
---@return string
function i18n.example_params(example)
return i18n.example_field(example, "params")
end
---@param example ShowcaseExample
---@return string
function i18n.example_group(example)
return i18n.example_field(example, "group")
end
---@param action ShowcaseAction
---@return string
function i18n.action_text(action)
if i18n.current_locale() == "en" and action.text_en ~= nil and action.text_en ~= "" then
return action.text_en
end
return action.text
end
return i18n

View File

@@ -0,0 +1,698 @@
local state = runtime.import("state")
local ui = runtime.import("ui")
---@type RuntimeUi
local runtime_ui = runtime.import("runtime_ui")
local examples = runtime.import("examples")
local i18n = runtime.import("i18n")
---@type RuntimeCommands
local commands = runtime.import("runtime_commands")
---@type RuntimeWidgets
local widgets = runtime.import("runtime_widgets")
local theme = runtime.import("theme")
widgets.configure({
primary = theme.primary,
secondary = "#ff475569",
success = theme.success,
overlay = "#99000000",
surface = "#ff1e293b",
surfaceAlt = theme.border,
card = "#ee111827",
text = theme.text,
muted = theme.muted,
progress = theme.success,
transparent = "#00000000"
})
function smoke_test(ctx)
return ctx ~= nil
and ctx.runtimeApiVersion ~= nil
and examples.all ~= nil
and i18n.t ~= nil
and ui.create_nodes ~= nil
and widgets.dialog ~= nil
and commands.sequence ~= nil
end
local function apply_context(ctx)
if ctx == nil then
return
end
i18n.apply_context(ctx)
if ctx.screen ~= nil then
state.screen_width = ctx.screen.width or state.screen_width
state.screen_height = ctx.screen.height or state.screen_height
end
if ctx.viewport ~= nil then
state.viewport_width = ctx.viewport.width or state.viewport_width
state.viewport_height = ctx.viewport.height or state.viewport_height
state.viewport_scale = ctx.viewport.scaleX or state.viewport_scale
end
end
function init(ctx)
apply_context(ctx)
state.status = i18n.t("status_ready")
return {
render = { creates = ui.create_nodes() },
ui = {},
commands = {}
}
end
---@param text string
---@return RuntimeDiff
local function status_only(text)
return {
ui = { updates = ui.status_updates(text) }
}
end
---@return RuntimeDiff
local function handle_anim()
return {
ui = { updates = ui.status_updates("执行 sequence + parallel移动、缩放、旋转、淡入淡出。") },
commands = {
commands.sequence({
commands.move_path("sample_circle", {
{ x = 104, y = 42 },
{ x = 180, y = 44 },
{ x = 248, y = 78 }
}, { duration = 0.75, group = "demo_anim" }),
commands.parallel({
commands.scale_to("sample_circle", 1.45, { duration = 0.35, group = "demo_anim" }),
commands.rotate_to("sample_circle", 6.28, { duration = 0.35, group = "demo_anim" }),
commands.fade_to("sample_circle", 0.45, { duration = 0.35, group = "demo_anim" })
}, { group = "demo_anim" }),
commands.parallel({
commands.move_to("sample_circle", 104, 42, { duration = 0.55, group = "demo_anim" }),
commands.scale_to("sample_circle", 1, { duration = 0.55, group = "demo_anim" }),
commands.fade_to("sample_circle", 1, { duration = 0.55, group = "demo_anim" })
}, { group = "demo_anim" })
}, { id = "demo_anim_sequence", group = "demo_anim", onComplete = "demo_anim_done" })
}
}
end
---@return RuntimeDiff
local function handle_cancel()
return {
ui = { updates = ui.status_updates("已发送 cancel_commandsgroup = demo_anim。") },
commands = { commands.cancel_group("demo_anim") }
}
end
---@return RuntimeDiff
local function handle_progress()
state.progress = state.progress + 0.15
if state.progress > 1 then
state.progress = 0.05
end
local updates = ui.progress_updates()
local status_updates = ui.status_updates("通过 NodeDiff.update 更新 progress.value = " .. tostring(state.progress))
for _, update in ipairs(status_updates) do
table.insert(updates, update)
end
return { render = { updates = updates } }
end
---@return RuntimeDiff
local function handle_visibility()
state.visible = not state.visible
local updates = ui.visibility_updates()
local status_updates = ui.status_updates("通过 visible / alpha 更新节点显示状态。")
for _, update in ipairs(status_updates) do
table.insert(updates, update)
end
return { render = { updates = updates } }
end
---@return RuntimeDiff
local function handle_dialog()
if state.dialog_open then
return status_only("Dialog 已经打开;点击关闭按钮移除它。")
end
state.dialog_open = true
return {
ui = {
creates = ui.dialog_nodes(),
updates = ui.status_updates("创建 widgets.dialogLua 组合普通 RuntimeNode。")
}
}
end
---@return RuntimeDiff
local function handle_close_dialog()
state.dialog_open = false
return {
ui = {
removes = ui.dialog_removes(),
updates = ui.status_updates("通过 GameDiff.removes 移除 Dialog 节点。")
}
}
end
---@return RuntimeDiff
local function handle_temp()
if state.temp_node_visible then
state.temp_node_visible = false
return {
render = { removes = { { id = "temp_node" }, { id = "temp_node_text" } } },
ui = { updates = ui.status_updates("通过 GameDiff.removes 移除临时节点。") }
}
end
state.temp_node_visible = true
return {
render = { creates = ui.temp_nodes() },
ui = { updates = ui.status_updates("创建临时节点,并用 delay + remove_node 自动删除。") },
commands = {
commands.sequence({
commands.delay(1.2, { id = "temp_delay", group = "temp" }),
commands.remove_node("temp_node", { group = "temp" }),
commands.remove_node("temp_node_text", { group = "temp", onComplete = "temp_removed" })
}, { group = "temp" })
}
}
end
---@return RuntimeDiff
local function handle_sound()
return {
ui = { updates = ui.status_updates("播放 manifest 资源 clickcommands.play_sound('click')。") },
commands = { commands.play_sound("click", { volume = 0.8, onComplete = "sound_done" }) }
}
end
---@return RuntimeDiff
local function handle_bgm()
if state.bgm_state == "stopped" then
state.bgm_state = "playing"
return {
ui = { updates = ui.status_updates("启动 BGM channel=demoplay_bgm可继续点击切换暂停/恢复/停止。") },
commands = { commands.play_bgm("click", { channel = "demo", loop = true, volume = 0.25 }) }
}
end
if state.bgm_state == "playing" then
state.bgm_state = "paused"
return {
ui = { updates = ui.status_updates("暂停 BGMpause_bgm('demo')。") },
commands = { commands.pause_bgm("demo") }
}
end
if state.bgm_state == "paused" then
state.bgm_state = "resumed"
return {
ui = { updates = ui.status_updates("恢复 BGMresume_bgm('demo')。") },
commands = { commands.resume_bgm("demo") }
}
end
state.bgm_state = "stopped"
return {
ui = { updates = ui.status_updates("停止 BGMstop_bgm('demo')。") },
commands = { commands.stop_bgm("demo") }
}
end
---@return RuntimeDiff
local function handle_text_change()
state.text_variant = state.text_variant == "plain" and "styled" or "plain"
local updates = ui.text_updates()
local status_updates = ui.status_updates("Text 当前是纯文本协议;富文本 richText/spans 尚未支持。")
for _, update in ipairs(status_updates) do
table.insert(updates, update)
end
return { ui = { updates = updates } }
end
---@return RuntimeDiff
local function handle_text_style()
state.text_variant = state.text_variant == "plain" and "styled" or "plain"
local updates = ui.text_updates()
local status_updates = ui.status_updates("已切换 text 的 color/fontSize不是富文本。")
for _, update in ipairs(status_updates) do
table.insert(updates, update)
end
return { ui = { updates = updates } }
end
---@return RuntimeDiff
local function handle_button_primary()
return {
ui = { updates = ui.status_updates("按钮点击RuntimeEvent.tap -> Lua handler。") },
commands = { commands.play_sound("click", { volume = 0.55 }) }
}
end
---@return RuntimeDiff
local function handle_button_toggle()
state.button_active = not state.button_active
local updates = ui.button_updates()
local status_updates = ui.status_updates(state.button_active and "按钮恢复可点击状态。" or "按钮切换为置灰状态。")
for _, update in ipairs(status_updates) do
table.insert(updates, update)
end
return { ui = { updates = updates } }
end
---@return RuntimeDiff
local function handle_button_image_tap()
return {
ui = { updates = ui.status_updates("图片按钮点击:按下时显示 pressedAsset松开回到 asset。") },
commands = { commands.play_sound("click", { volume = 0.45 }) }
}
end
---@return RuntimeDiff
local function handle_button_image_toggle()
state.button_image_enabled = not state.button_image_enabled
local updates = ui.button_image_updates()
local status_updates = ui.status_updates(state.button_image_enabled and "图片按钮恢复 interactive=true。" or "图片按钮设为 interactive=false显示 disabledAsset。")
for _, update in ipairs(status_updates) do
table.insert(updates, update)
end
return { ui = { updates = updates } }
end
---@return RuntimeDiff
local function handle_sprite_anim()
return {
ui = { updates = ui.status_updates("精灵节点执行 move_to + rotate_to。") },
commands = {
commands.parallel({
commands.move_to("sprite_sprite_demo", 154, 54, { duration = 0.35, group = "sprite_demo" }),
commands.rotate_to("sprite_sprite_demo", 6.28, { duration = 0.35, group = "sprite_demo" })
}, { group = "sprite_demo", onComplete = "sprite_anim_done" })
}
}
end
---@return RuntimeDiff
local function handle_sprite_style()
state.sprite_variant = state.sprite_variant == "image" and "sprite" or "image"
local updates = ui.sprite_updates()
local status_updates = ui.status_updates("切换 sprite 示例样式:" .. state.sprite_variant)
for _, update in ipairs(status_updates) do
table.insert(updates, update)
end
return { ui = { updates = updates } }
end
---@param value string
---@return RuntimeDiff
local function handle_radio(value)
state.radio_selected = value
local updates = ui.radio_updates()
local status_updates = ui.status_updates("RadioGroup 选择:" .. value)
for _, update in ipairs(status_updates) do
table.insert(updates, update)
end
return { ui = { updates = updates } }
end
---@param index integer
---@return RuntimeDiff
local function handle_list_pick(index)
local items = { "Lua", "Runtime", "Flame", "Diff", "Command" }
if index < 1 then
index = #items
elseif index > #items then
index = 1
end
state.list_selected = items[index]
if state.list_axis == "horizontal" then
state.list_scroll_x = math.min(math.max((index - 2) * 126, 0), 330)
state.list_scroll_y = 0
else
state.list_scroll_x = 0
state.list_scroll_y = math.min(math.max((index - 2) * 28, 0), 78)
end
local updates = ui.list_updates()
local status_updates = ui.status_updates("ListView 选中:" .. state.list_selected .. ",双轴滚动已更新。")
for _, update in ipairs(status_updates) do
table.insert(updates, update)
end
return { ui = { updates = updates } }
end
---@return RuntimeDiff
local function handle_list_horizontal()
state.list_axis = state.list_axis == "horizontal" and "vertical" or "horizontal"
state.list_scroll_x = 0
state.list_scroll_y = 0
local updates = ui.list_updates()
local status_updates = ui.status_updates("ListView 排列方向:" .. state.list_axis)
for _, update in ipairs(status_updates) do
table.insert(updates, update)
end
return { ui = { updates = updates } }
end
---@return RuntimeDiff
local function handle_list_reset()
state.list_scroll_x = 0
state.list_scroll_y = 0
local updates = ui.list_updates()
local status_updates = ui.status_updates("ListView 滚动已重置;滚轮/拖拽会触发 onScroll。")
for _, update in ipairs(status_updates) do
table.insert(updates, update)
end
return { ui = { updates = updates } }
end
---@param event RuntimeEvent
---@return RuntimeDiff
local function handle_list_scrolled(event)
if event.data ~= nil then
state.list_scroll_x = event.data.scrollX or state.list_scroll_x
state.list_scroll_y = event.data.scrollY or state.list_scroll_y
end
return {
ui = { updates = ui.status_updates("onScroll 回调:" .. tostring(state.list_scroll_x) .. ", " .. tostring(state.list_scroll_y)) }
}
end
---@param step integer
---@return RuntimeDiff
---@param preset string
---@return RuntimeDiff
local function handle_particle(preset)
state.particle_seed = (state.particle_seed or 0) + 1
local count = 42 + (state.particle_seed % 5)
local updates = {
runtime_ui.update("particle_burst", {
preset = preset == "confetti" and "confetti" or "burst",
count = count,
duration = preset == "confetti" and 1.2 or 0.85,
color = preset == "confetti" and "#ffff4d6d" or "#ffffcc33",
colorTo = preset == "confetti" and "#fffacc15" or "#00ffcc33",
gravityY = preset == "confetti" and 240 or 90,
spread = preset == "confetti" and 90 or 360,
visible = preset ~= "snow"
}),
runtime_ui.update("particle_trail", {
visible = preset == "burst"
}),
runtime_ui.update("particle_snow", {
count = 56 + (state.particle_seed % 7),
visible = preset == "snow"
})
}
local status_updates = ui.status_updates("Particle 预设:" .. preset)
for _, update in ipairs(status_updates) do
table.insert(updates, update)
end
return { ui = { updates = updates } }
end
local function handle_list(step)
local items = { "Lua", "Runtime", "Flame", "Diff", "Command" }
local index = 1
for i, value in ipairs(items) do
if value == state.list_selected then
index = i
break
end
end
return handle_list_pick(index + step)
end
---@param mode string
---@return RuntimeDiff
local function handle_layout(mode)
state.layout_mode = mode
local updates = ui.layout_updates(mode)
local status_updates = ui.status_updates("布局示例:" .. mode)
for _, update in ipairs(status_updates) do
table.insert(updates, update)
end
return { ui = { updates = updates } }
end
---@return RuntimeDiff
local function handle_resource()
if state.resource_state == "ready" then
state.resource_state = "evicted"
return {
ui = { updates = ui.status_updates("释放资源组 mediaevict_resources。再次点击会 preload。") },
commands = { commands.evict_group("media") }
}
end
state.resource_state = "ready"
return {
ui = { updates = ui.status_updates("预载资源组 mediapreload_resources。") },
commands = { commands.preload_group("media", { failOnError = true }) }
}
end
---@return RuntimeDiff
---@return RuntimeDiff
local function handle_i18n_toggle()
local locale = i18n.toggle_locale()
return {
ui = { updates = ui.locale_updates() },
commands = { commands.toast(i18n.t("i18n_switched") .. locale) }
}
end
---@return RuntimeDiff
local function handle_i18n_refresh()
return {
ui = { updates = ui.locale_updates() }
}
end
---@param mode string
---@return RuntimeDiff
local function handle_responsive(mode)
state.responsive_mode = mode
local label = i18n.t("responsive_desktop")
if mode == "phone" then
label = i18n.t("responsive_phone")
elseif mode == "tablet" then
label = i18n.t("responsive_tablet")
end
local updates = ui.responsive_updates()
local status_updates = ui.status_updates(label)
for _, update in ipairs(status_updates) do
table.insert(updates, update)
end
return { ui = { updates = updates } }
end
---@param event RuntimeEvent
---@return RuntimeDiff
local function handle_resize(event)
if event.data ~= nil then
apply_context(event.data)
end
return { ui = { updates = ui.responsive_updates() } }
end
---@return RuntimeDiff
local function handle_toast()
return {
ui = { updates = ui.status_updates("发送 toast 命令Runtime 会显示临时 overlay 并自动移除。") },
commands = { commands.toast("Hello from Lua showcase") }
}
end
---@param target? string
---@return RuntimeDiff
local function handle_select_example(target)
local id = string.sub(target or "example_nodes", 9)
local selected = examples.find(id)
state.selected_example = selected.id
state.detail_tab = "code"
return {
ui = { updates = ui.example_updates(selected) }
}
end
---@param tab string
---@return RuntimeDiff
local function handle_detail_tab(tab)
state.detail_tab = tab
return { ui = { updates = ui.example_updates(examples.find(state.selected_example)) } }
end
---@return RuntimeDiff
local function handle_copy_detail()
local selected = examples.find(state.selected_example)
local text = selected.code
local label = "代码"
if state.detail_tab == "params" then
text = i18n.example_params(selected)
label = "参数说明"
end
return {
ui = { updates = ui.status_updates("已复制" .. label .. "到剪贴板。") },
commands = { commands.copy_text(text) }
}
end
function on_event(event)
if event.handler == "select_example" then
return handle_select_example(event.target)
end
if event.handler == "detail_tab_code" then
return handle_detail_tab("code")
end
if event.handler == "detail_tab_params" then
return handle_detail_tab("params")
end
if event.handler == "copy_detail" then
return handle_copy_detail()
end
if event.handler == "demo_anim" then
return handle_anim()
end
if event.handler == "demo_cancel" then
return handle_cancel()
end
if event.handler == "demo_progress" then
return handle_progress()
end
if event.handler == "demo_visibility" then
return handle_visibility()
end
if event.handler == "demo_dialog" then
return handle_dialog()
end
if event.handler == "close_dialog" then
return handle_close_dialog()
end
if event.handler == "demo_temp" then
return handle_temp()
end
if event.handler == "demo_text_change" then
return handle_text_change()
end
if event.handler == "demo_text_style" then
return handle_text_style()
end
if event.handler == "demo_button_primary" then
return handle_button_primary()
end
if event.handler == "demo_button_toggle" then
return handle_button_toggle()
end
if event.handler == "demo_button_image_tap" then
return handle_button_image_tap()
end
if event.handler == "demo_button_image_toggle" then
return handle_button_image_toggle()
end
if event.handler == "demo_sprite_anim" then
return handle_sprite_anim()
end
if event.handler == "demo_sprite_style" then
return handle_sprite_style()
end
if event.handler == "demo_radio_audio" then
return handle_radio("audio")
end
if event.handler == "demo_radio_spine" then
return handle_radio("spine")
end
if event.handler == "demo_radio_lua" then
return handle_radio("Lua")
end
if event.handler == "demo_list_prev" then
return handle_list(-1)
end
if event.handler == "demo_list_next" then
return handle_list(1)
end
if event.handler == "demo_list_horizontal" then
return handle_list_horizontal()
end
if event.handler == "demo_list_reset" then
return handle_list_reset()
end
if event.handler == "demo_list_scrolled" then
return handle_list_scrolled(event)
end
if event.handler == "demo_list_pick_1" then
return handle_list_pick(1)
end
if event.handler == "demo_list_pick_2" then
return handle_list_pick(2)
end
if event.handler == "demo_list_pick_3" then
return handle_list_pick(3)
end
if event.handler == "demo_list_pick_4" then
return handle_list_pick(4)
end
if event.handler == "demo_list_pick_5" then
return handle_list_pick(5)
end
if event.handler == "demo_particle_burst" then
return handle_particle("burst")
end
if event.handler == "demo_particle_confetti" then
return handle_particle("confetti")
end
if event.handler == "demo_particle_snow" then
return handle_particle("snow")
end
if event.handler == "demo_layout_row" then
return handle_layout("row")
end
if event.handler == "demo_layout_column" then
return handle_layout("column")
end
if event.handler == "demo_layout_box" then
return handle_layout("box")
end
if event.handler == "demo_sound" then
return handle_sound()
end
if event.handler == "demo_bgm" then
return handle_bgm()
end
if event.handler == "demo_resource" then
return handle_resource()
end
if event.handler == "demo_i18n_toggle" then
return handle_i18n_toggle()
end
if event.handler == "demo_i18n_refresh" then
return handle_i18n_refresh()
end
if event.handler == "demo_responsive_phone" then
return handle_responsive("phone")
end
if event.handler == "demo_responsive_tablet" then
return handle_responsive("tablet")
end
if event.handler == "demo_responsive_desktop" then
return handle_responsive("desktop")
end
if event.handler == "demo_toast" then
return handle_toast()
end
if event.type == "resize" then
return handle_resize(event)
end
if event.handler == "demo_anim_done" then
return status_only("动画完成事件RuntimeCommand.onComplete -> RuntimeEvent -> Lua。")
end
if event.handler == "temp_removed" then
state.temp_node_visible = false
return status_only("临时节点已由 remove_node 命令删除。")
end
if event.handler == "sound_done" then
return status_only("音效播放完成事件已回到 Lua。")
end
if event.handler == "sprite_anim_done" then
return status_only("精灵动画完成。")
end
return {}
end

View File

@@ -0,0 +1,606 @@
---@meta
--- COMMON RUNTIME TYPES SECTION.
--- Source of truth: tool/lua_runtime_defs_common.lua
--- After editing this common section, run:
--- dart run tool/generate_lua_runtime_defs.dart
---@alias RuntimeNodeType
---| 'panel'
---| 'button'
---| 'text'
---| 'circle'
---| 'rect'
---| 'line'
---| 'progress'
---| 'listView'
---| 'sprite'
---| 'image'
---| 'spine'
---| 'particle'
---@alias RuntimeAnchor
---| 'center'
---| 'topLeft'
---| 'topRight'
---| 'bottomLeft'
---| 'bottomRight'
---@alias RuntimeTextAlign
---| 'left'
---| 'center'
---| 'right'
---@alias RuntimeParticlePreset
---| 'burst'
---| 'trail'
---| 'snow'
---| 'confetti'
---@alias RuntimeCommandType
---| 'move_path'
---| 'move_to'
---| 'fade_to'
---| 'scale_to'
---| 'rotate_to'
---| 'remove_node'
---| 'sequence'
---| 'parallel'
---| 'delay'
---| 'toast'
---| 'play_sound'
---| 'play_bgm'
---| 'pause_bgm'
---| 'resume_bgm'
---| 'stop_bgm'
---| 'preload_resources'
---| 'evict_resources'
---| 'cancel_commands'
---| 'play_spine_animation'
---| 'copy_text'
---@alias RuntimeEventType
---| 'tap'
---| 'animation_done'
---| 'resize'
---| 'scroll'
---@alias RuntimeScaleMode
---| 'fit'
---| 'fill'
---| 'stretch'
---| 'none'
---@alias RuntimeLayoutAlign
---| 'start'
---| 'center'
---| 'end'
---@alias RuntimeButtonVariant
---| 'primary'
---| 'secondary'
---| 'ghost'
---@class (exact) RuntimeNode
---@field id string
---@field type RuntimeNodeType
---@field parent? string
---@field asset? string Normal image/sprite/spine asset key. For button nodes this is the normal-state image.
---@field pressedAsset? string Button pressed-state image asset key.
---@field disabledAsset? string Button disabled-state image asset key.
---@field animation? string
---@field skin? string
---@field loop? boolean
---@field text? string
---@field x? number
---@field y? number
---@field width? number
---@field height? number
---@field paddingLeft? number
---@field paddingTop? number
---@field paddingRight? number
---@field paddingBottom? number
---@field anchor? RuntimeAnchor
---@field layer? integer
---@field visible? boolean
---@field alpha? number
---@field scale? number
---@field rotation? number
---@field color? string
---@field fontSize? number
---@field textAlign? RuntimeTextAlign
---@field radius? number
---@field strokeWidth? number
---@field value? number
---@field scrollX? number
---@field scrollY? number
---@field contentWidth? number
---@field contentHeight? number
---@field virtualized? boolean
---@field cacheExtent? number
---@field inertia? boolean
---@field scrollbarThumbColor? string
---@field scrollbarTrackColor? string
---@field scrollbarThickness? number
---@field scrollbarVisible? boolean
---@field interactive? boolean
---@field onTap? string
---@field onScroll? string
---@field preset? RuntimeParticlePreset
---@field count? integer
---@field duration? number
---@field speedMin? number
---@field speedMax? number
---@field gravityX? number
---@field gravityY? number
---@field spread? number
---@field colorTo? string
---@field radiusTo? number
---@field autoRemove? boolean
---@field fadeOut? boolean
---@class (exact) RuntimeNodeProps
---@field type? RuntimeNodeType
---@field parent? string
---@field asset? string Normal image/sprite/spine asset key. For button nodes this is the normal-state image.
---@field pressedAsset? string Button pressed-state image asset key.
---@field disabledAsset? string Button disabled-state image asset key.
---@field animation? string
---@field skin? string
---@field loop? boolean
---@field text? string
---@field x? number
---@field y? number
---@field width? number
---@field height? number
---@field paddingLeft? number
---@field paddingTop? number
---@field paddingRight? number
---@field paddingBottom? number
---@field anchor? RuntimeAnchor
---@field layer? integer
---@field visible? boolean
---@field alpha? number
---@field scale? number
---@field rotation? number
---@field color? string
---@field fontSize? number
---@field textAlign? RuntimeTextAlign
---@field radius? number
---@field strokeWidth? number
---@field value? number
---@field scrollX? number
---@field scrollY? number
---@field contentWidth? number
---@field contentHeight? number
---@field virtualized? boolean
---@field cacheExtent? number
---@field inertia? boolean
---@field scrollbarThumbColor? string
---@field scrollbarTrackColor? string
---@field scrollbarThickness? number
---@field scrollbarVisible? boolean
---@field interactive? boolean
---@field onTap? string
---@field onScroll? string
---@field preset? RuntimeParticlePreset
---@field count? integer
---@field duration? number
---@field speedMin? number
---@field speedMax? number
---@field gravityX? number
---@field gravityY? number
---@field spread? number
---@field colorTo? string
---@field radiusTo? number
---@field autoRemove? boolean
---@field fadeOut? boolean
---Helper-only fields accepted by runtime_ui/runtime_widgets. They are normalized
---before the node/update crosses the Dart Runtime protocol boundary.
---@class RuntimeNodeInit: RuntimeNodeProps
---@field w? number Alias for width.
---@field h? number Alias for height.
---@field size? number Alias for both width and height.
---@field handler? string Alias for onTap.
---@field onClick? string Alias for onTap.
---@class (exact) RuntimeNodeUpdate
---@field id string
---@field props RuntimeNodeProps
---@class (exact) RuntimeNodeRemove
---@field id string
---@class (exact) RuntimeDiffSection
---@field creates? RuntimeNode[]
---@field updates? RuntimeNodeUpdate[]
---@field removes? (string|RuntimeNodeRemove)[]
---@class (exact) RuntimeDiff
---@field render? RuntimeDiffSection
---@field ui? RuntimeDiffSection
---@field commands? RuntimeCommand[]
---@class (exact) RuntimeEvent
---@field type RuntimeEventType|string
---@field target? string
---@field handler? string
---@field x? number
---@field y? number
---@field data? table
---@class (exact) RuntimeCommand
---@field type RuntimeCommandType
---@field target? string
---@field scope? string
---@field id? string
---@field group? string
---@field commandGroup? string
---@field onComplete? string
---@field duration? number
---@field commands? RuntimeCommand[]
---@field path? RuntimePoint[]
---@field x? number
---@field y? number
---@field alpha? number
---@field scale? number
---@field angle? number
---@field text? string
---@field message? string
---@field asset? string
---@field name? string
---@field volume? number
---@field channel? string
---@field loop? boolean
---@field failOnError? boolean
---@field animation? string
---@field track? integer
---@field queue? boolean
---@field delay? number
---@class (exact) RuntimeCommandOpts
---@field id? string
---@field group? string
---@field commandGroup? string
---@field scope? string
---@field onComplete? string
---@field duration? number
---@class (exact) RuntimeAudioCommandOpts: RuntimeCommandOpts
---@field volume? number
---@field name? string
---@class (exact) RuntimeBgmCommandOpts: RuntimeAudioCommandOpts
---@field channel? string
---@field loop? boolean
---@class (exact) RuntimeSpineCommandOpts: RuntimeCommandOpts
---@field track? integer
---@field loop? boolean
---@field queue? boolean
---@field delay? number
---@class (exact) RuntimeResourceCommandOpts: RuntimeCommandOpts
---@field failOnError? boolean
---@class (exact) RuntimePoint
---@field x number
---@field y number
---@class (exact) RuntimeLocaleContext
---@field requested string
---@field resolved string
---@field default string
---@field supported string[]
---@field languageCode string
---@field scriptCode? string
---@field countryCode? string
---@class (exact) RuntimeScreenContext
---@field width number
---@field height number
---@class (exact) RuntimeDesignContext
---@field width number
---@field height number
---@class (exact) RuntimeViewportContext
---@field x number
---@field y number
---@field width number
---@field height number
---@field scaleX number
---@field scaleY number
---@field scaleMode RuntimeScaleMode|string
---@class (exact) RuntimeContext
---@field screen RuntimeScreenContext
---@field design RuntimeDesignContext
---@field viewport RuntimeViewportContext
---@field seed integer
---@field runtimeApiVersion integer
---@field gameId string
---@field gameVersion string
---@field locale? RuntimeLocaleContext
---@class RuntimeUi
---@field style fun(base?: RuntimeNodeProps, opts?: RuntimeNodeProps): RuntimeNodeProps
---@field with_parent fun(parent: string, opts?: RuntimeNodeProps): RuntimeNodeProps
---@field node fun(node_type: RuntimeNodeType, id: string, opts?: RuntimeNodeInit): RuntimeNode
---@field panel fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field rect fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field circle fun(id: string, x: number|RuntimeNodeInit, y?: number, size?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field line fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field progress fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, value?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field particle fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field text fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field button fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeNodeInit): RuntimeNode
---@field list_view fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field image fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field sprite fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field spine fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, animation?: string, opts?: RuntimeNodeInit): RuntimeNode
---@field update fun(id: string, props: RuntimeNodeInit): RuntimeNodeUpdate
---@field text_update fun(id: string, text: string): RuntimeNodeUpdate
---@field visible_update fun(id: string, visible: boolean): RuntimeNodeUpdate
---@field alpha_update fun(id: string, alpha: number): RuntimeNodeUpdate
---@field scale_update fun(id: string, scale: number): RuntimeNodeUpdate
---@field position_update fun(id: string, x: number, y: number): RuntimeNodeUpdate
---@field size_update fun(id: string, width: number, height: number): RuntimeNodeUpdate
---@field transform_update fun(id: string, x: number, y: number, scale: number, rotation: number): RuntimeNodeUpdate
---@field batch_update fun(ids: string[], props: RuntimeNodeInit): RuntimeNodeUpdate[]
---@field append fun(nodes: RuntimeNode[], node: RuntimeNode): RuntimeNode[]
---@field append_all fun(nodes: RuntimeNode[], extra_nodes: RuntimeNode[]): RuntimeNode[]
---@class (exact) RuntimeDialogButton
---@field id? string
---@field text string
---@field handler string
---@field color? string
---@class (exact) RuntimeDialogOpts
---@field screenWidth? number
---@field screenHeight? number
---@field overlay? boolean
---@field overlayColor? string
---@field blockInput? boolean
---@field layer? integer
---@field color? string
---@field radius? number
---@field panelStyle? RuntimeNodeProps
---@field titleColor? string
---@field titleSize? number
---@field titleStyle? RuntimeNodeProps
---@field messageColor? string
---@field messageSize? number
---@field messageStyle? RuntimeNodeProps
---@field buttons? RuntimeDialogButton[]
---@field buttonGap? number
---@field buttonStyle? RuntimeNodeProps
---@class RuntimeLabeledProgressOpts: RuntimeNodeInit
---@field labelHeight? number
---@field labelStyle? RuntimeNodeProps
---@class RuntimePillOpts: RuntimeNodeInit
---@field panelStyle? RuntimeNodeProps
---@field textStyle? RuntimeNodeProps
---@class RuntimeTextButtonOpts: RuntimeNodeInit
---@field variant? RuntimeButtonVariant
---@class RuntimeListItemOpts: RuntimeTextButtonOpts
---@field selected? boolean
---@field activeColor? string
---@field inactiveColor? string
---@class RuntimeTabItem
---@field id? string
---@field key? string
---@field text string
---@field handler? string
---@field selected? boolean
---@class RuntimeTabsOpts: RuntimeNodeInit
---@field tabs? RuntimeTabItem[]
---@field selected? string
---@field gap? number
---@field itemWidth? number
---@field itemHeight? number
---@field activeColor? string
---@field inactiveColor? string
---@field buttonStyle? RuntimeNodeProps
---@class RuntimeActionItem
---@field id? string
---@field text string
---@field handler? string
---@field visible? boolean
---@field color? string
---@field style? RuntimeNodeProps
---@class RuntimeActionRowOpts: RuntimeNodeInit
---@field actions? RuntimeActionItem[]
---@field gap? number
---@field itemWidth? number
---@field itemHeight? number
---@field buttonStyle? RuntimeNodeProps
---@class RuntimePanelHeaderOpts: RuntimeNodeInit
---@field eyebrow? string
---@field title string
---@field summary? string
---@field gap? number
---@field eyebrowId? string
---@field titleId? string
---@field summaryId? string
---@field eyebrowHeight? number
---@field titleHeight? number
---@field summaryHeight? number
---@field eyebrowStyle? RuntimeNodeProps
---@field titleStyle? RuntimeNodeProps
---@field summaryStyle? RuntimeNodeProps
---@class RuntimeWidgetTheme
---@field primary? string
---@field secondary? string
---@field success? string
---@field overlay? string
---@field surface? string
---@field surfaceAlt? string
---@field card? string
---@field text? string
---@field muted? string
---@field progress? string
---@field transparent? string
---@class RuntimeWidgets
---@field configure fun(tokens?: RuntimeWidgetTheme): RuntimeWidgets
---@field label fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field section_title fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field pill fun(id: string, text: string|RuntimePillOpts, x?: number, y?: number, width?: number, height?: number, opts?: RuntimePillOpts): RuntimeNode[]
---@field text_button fun(id: string, text: string|RuntimeTextButtonOpts, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeTextButtonOpts): RuntimeNode
---@field list_item fun(id: string, text: string|RuntimeListItemOpts, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeListItemOpts): RuntimeNode
---@field tabs fun(id: string, tabs: RuntimeTabItem[]|RuntimeTabsOpts, opts?: RuntimeTabsOpts): RuntimeNode[]
---@field action_row fun(id: string, actions: RuntimeActionItem[]|RuntimeActionRowOpts, opts?: RuntimeActionRowOpts): RuntimeNode[]
---@field panel_header fun(id: string, opts: RuntimePanelHeaderOpts): RuntimeNode[]
---@field overlay fun(id: string, width: number|RuntimeNodeInit, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field card fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field progress_bar fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, value?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field labeled_progress fun(id: string, label: string, x: number, y: number, width: number, height: number, value: number, opts?: RuntimeLabeledProgressOpts): RuntimeNode[]
---@field button_row fun(parent: string, id: string, buttons: RuntimeDialogButton[], x: number, y: number, width: number, height: number, gap?: number, opts?: RuntimeNodeProps): RuntimeNode[]
---@field dialog fun(id: string, title: string, message: string, x: number, y: number, width: number, height: number, opts?: RuntimeDialogOpts): RuntimeNode[]
---@class (exact) RuntimeLayoutItem
---@field node RuntimeNode
---@field marginLeft? number
---@field marginRight? number
---@field marginTop? number
---@field marginBottom? number
---@class RuntimeLayoutItemOpts
---@field margin? number
---@field mx? number
---@field my? number
---@field ml? number
---@field mr? number
---@field mt? number
---@field mb? number
---@field marginLeft? number
---@field marginRight? number
---@field marginTop? number
---@field marginBottom? number
---@class RuntimeLinearLayoutOpts
---@field x? number
---@field y? number
---@field width? number
---@field height? number
---@field gap? number
---@field align? RuntimeLayoutAlign
---@field padding? number
---@field paddingX? number
---@field paddingY? number
---@field px? number
---@field py? number
---@field paddingLeft? number
---@field paddingTop? number
---@class RuntimeBoxLayoutOpts: RuntimeLinearLayoutOpts
---@field rows? integer
---@field columns? integer
---@field cols? integer
---@field cellWidth? number
---@field cellHeight? number
---@field cellW? number
---@field cellH? number
---@field gapX? number
---@field gapY? number
---@field valign? RuntimeLayoutAlign
---@class RuntimeLayout
---@field item fun(node: RuntimeNode, opts?: RuntimeLayoutItemOpts): RuntimeLayoutItem
---@field local_position fun(origin: RuntimePoint, position: RuntimePoint): RuntimePoint
---@field row fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
---@field column fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
---@field stack fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
---@field box fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeBoxLayoutOpts): RuntimeNode[]
---@class RuntimeCommands
---@field toast fun(text: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field copy_text fun(text: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field delay fun(duration: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field sequence fun(items: RuntimeCommand[], opts?: RuntimeCommandOpts): RuntimeCommand
---@field parallel fun(items: RuntimeCommand[], opts?: RuntimeCommandOpts): RuntimeCommand
---@field move_path fun(target: string, path: RuntimePoint[], opts?: RuntimeCommandOpts): RuntimeCommand
---@field move_to fun(target: string, x: number, y: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field fade_to fun(target: string, alpha: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field scale_to fun(target: string, scale: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field rotate_to fun(target: string, angle: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field remove_node fun(target: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field play_spine_animation fun(target: string, animation: string, opts?: RuntimeSpineCommandOpts): RuntimeCommand
---@field play_sound fun(asset: string, opts?: RuntimeAudioCommandOpts): RuntimeCommand
---@field play_bgm fun(asset: string, opts?: RuntimeBgmCommandOpts): RuntimeCommand
---@field pause_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field resume_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field stop_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field preload_group fun(group: string, opts?: RuntimeResourceCommandOpts): RuntimeCommand
---@field evict_group fun(group: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field cancel_id fun(id: string): RuntimeCommand
---@field cancel_group fun(group: string): RuntimeCommand
---@field cancel_scope fun(scope: string): RuntimeCommand
---@class RuntimeImportApi
---@field import fun(moduleName: string): table
---@type RuntimeImportApi
runtime = runtime
---@class (exact) ShowcaseAction
---@field text string
---@field text_en? string
---@field handler string
---@class (exact) ShowcaseExample
---@field id string
---@field group string
---@field group_en? string
---@field category string
---@field category_en? string
---@field menu string
---@field menu_en? string
---@field title string
---@field title_en? string
---@field summary string
---@field summary_en? string
---@field code string
---@field params string
---@field params_en? string
---@field actions ShowcaseAction[]
---@class (exact) ShowcaseState
---@field selected_example string
---@field detail_tab string
---@field progress number
---@field visible boolean
---@field dialog_open boolean
---@field temp_node_visible boolean
---@field bgm_state string
---@field resource_state string
---@field text_variant string
---@field button_active boolean
---@field sprite_variant string
---@field layout_mode string
---@field radio_selected string
---@field list_selected string
---@field list_axis string
---@field list_scroll_x number
---@field list_scroll_y number
---@field locale string
---@field screen_width number
---@field screen_height number
---@field viewport_width number
---@field viewport_height number
---@field viewport_scale number
---@field responsive_mode string
---@field status string

View File

@@ -0,0 +1,32 @@
---@class ShowcaseState
local state = {
selected_example = "nodes",
detail_tab = "code",
progress = 0.35,
visible = true,
dialog_open = false,
temp_node_visible = false,
bgm_state = "stopped",
resource_state = "ready",
text_variant = "plain",
button_active = true,
button_image_enabled = true,
sprite_variant = "image",
layout_mode = "row",
radio_selected = "audio",
list_selected = "Lua",
list_axis = "vertical",
list_scroll_x = 0,
list_scroll_y = 0,
locale = "zh-Hans",
screen_width = 720,
screen_height = 720,
viewport_width = 720,
viewport_height = 720,
viewport_scale = 1,
responsive_mode = "desktop",
particle_seed = 0,
status = "点击按钮查看 RuntimeEvent -> Lua -> Diff / Command 示例"
}
return state

View File

@@ -0,0 +1,44 @@
local theme = runtime.import("theme")
---@class ShowcaseStyles
local styles = {}
styles.title = {
color = theme.text,
fontSize = 24,
layer = 20
}
styles.label = {
color = theme.muted,
fontSize = 14,
layer = 20
}
styles.value = {
color = theme.text,
fontSize = 16,
layer = 20
}
styles.button = {
color = "#ff2563eb",
radius = 10,
fontSize = 14,
layer = 30
}
styles.small_button = {
color = "#ff334155",
radius = 8,
fontSize = 12,
layer = 30
}
styles.card = {
color = theme.card,
radius = 14,
layer = 10
}
return styles

View File

@@ -0,0 +1,18 @@
---@class ShowcaseTheme
local theme = {
screen_width = 720,
screen_height = 720,
background = "#ff0f172a",
panel = "#ff111827",
card = "#ff1f2937",
border = "#ff334155",
text = "#fff8fafc",
muted = "#ff94a3b8",
primary = "#ff38bdf8",
success = "#ff22c55e",
warning = "#fff59e0b",
danger = "#ffef4444",
purple = "#ffa855f7"
}
return theme

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
{
"gameId": "template",
"name": "Lua Runtime Template",
"version": "0.1.0",
"runtimeApiVersion": 1,
"entry": "scripts/main.lua",
"assetsBase": "assets",
"defaultLocale": "zh-Hans",
"supportedLocales": [
"zh-Hans",
"en"
],
"display": {
"designWidth": 720,
"designHeight": 720,
"scaleMode": "fit"
},
"modules": {
"runtime_ui": "runtime:runtime_ui.lua",
"runtime_commands": "runtime:runtime_commands.lua",
"runtime_widgets": "runtime:runtime_widgets.lua",
"layout": "runtime:layout.lua",
"state": "scripts/state.lua",
"ui": "scripts/ui.lua"
},
"resources": {}
}

View File

@@ -0,0 +1,35 @@
local state = runtime.import("state")
local ui = runtime.import("ui")
---@type RuntimeCommands
local commands = runtime.import("runtime_commands")
function smoke_test(ctx)
return ctx ~= nil
and ctx.runtimeApiVersion ~= nil
and ui.create_nodes ~= nil
and commands.toast ~= nil
end
function init(ctx)
state.status = "Template ready: " .. tostring(ctx.gameId or "template")
return {
render = { creates = ui.create_nodes() },
ui = {},
commands = {}
}
end
function on_event(event)
if event.handler == "template_start" then
state.started = true
state.clicks = state.clicks + 1
state.status = "收到 tap 事件Lua 已返回 Diff 和 toast command。"
return {
ui = { updates = ui.state_updates() },
commands = {
commands.toast("Template event handled in Lua", { duration = 1.2 })
}
}
end
return {}
end

View File

@@ -0,0 +1,557 @@
---@meta
--- COMMON RUNTIME TYPES SECTION.
--- Source of truth: tool/lua_runtime_defs_common.lua
--- After editing this common section, run:
--- dart run tool/generate_lua_runtime_defs.dart
---@alias RuntimeNodeType
---| 'panel'
---| 'button'
---| 'text'
---| 'circle'
---| 'rect'
---| 'line'
---| 'progress'
---| 'listView'
---| 'sprite'
---| 'image'
---| 'spine'
---| 'particle'
---@alias RuntimeAnchor
---| 'center'
---| 'topLeft'
---| 'topRight'
---| 'bottomLeft'
---| 'bottomRight'
---@alias RuntimeTextAlign
---| 'left'
---| 'center'
---| 'right'
---@alias RuntimeParticlePreset
---| 'burst'
---| 'trail'
---| 'snow'
---| 'confetti'
---@alias RuntimeCommandType
---| 'move_path'
---| 'move_to'
---| 'fade_to'
---| 'scale_to'
---| 'rotate_to'
---| 'remove_node'
---| 'sequence'
---| 'parallel'
---| 'delay'
---| 'toast'
---| 'play_sound'
---| 'play_bgm'
---| 'pause_bgm'
---| 'resume_bgm'
---| 'stop_bgm'
---| 'preload_resources'
---| 'evict_resources'
---| 'cancel_commands'
---| 'play_spine_animation'
---| 'copy_text'
---@alias RuntimeEventType
---| 'tap'
---| 'animation_done'
---| 'resize'
---| 'scroll'
---@alias RuntimeScaleMode
---| 'fit'
---| 'fill'
---| 'stretch'
---| 'none'
---@alias RuntimeLayoutAlign
---| 'start'
---| 'center'
---| 'end'
---@alias RuntimeButtonVariant
---| 'primary'
---| 'secondary'
---| 'ghost'
---@class (exact) RuntimeNode
---@field id string
---@field type RuntimeNodeType
---@field parent? string
---@field asset? string Normal image/sprite/spine asset key. For button nodes this is the normal-state image.
---@field pressedAsset? string Button pressed-state image asset key.
---@field disabledAsset? string Button disabled-state image asset key.
---@field animation? string
---@field skin? string
---@field loop? boolean
---@field text? string
---@field x? number
---@field y? number
---@field width? number
---@field height? number
---@field paddingLeft? number
---@field paddingTop? number
---@field paddingRight? number
---@field paddingBottom? number
---@field anchor? RuntimeAnchor
---@field layer? integer
---@field visible? boolean
---@field alpha? number
---@field scale? number
---@field rotation? number
---@field color? string
---@field fontSize? number
---@field textAlign? RuntimeTextAlign
---@field radius? number
---@field strokeWidth? number
---@field value? number
---@field scrollX? number
---@field scrollY? number
---@field contentWidth? number
---@field contentHeight? number
---@field virtualized? boolean
---@field cacheExtent? number
---@field inertia? boolean
---@field scrollbarThumbColor? string
---@field scrollbarTrackColor? string
---@field scrollbarThickness? number
---@field scrollbarVisible? boolean
---@field interactive? boolean
---@field onTap? string
---@field onScroll? string
---@field preset? RuntimeParticlePreset
---@field count? integer
---@field duration? number
---@field speedMin? number
---@field speedMax? number
---@field gravityX? number
---@field gravityY? number
---@field spread? number
---@field colorTo? string
---@field radiusTo? number
---@field autoRemove? boolean
---@field fadeOut? boolean
---@class (exact) RuntimeNodeProps
---@field type? RuntimeNodeType
---@field parent? string
---@field asset? string Normal image/sprite/spine asset key. For button nodes this is the normal-state image.
---@field pressedAsset? string Button pressed-state image asset key.
---@field disabledAsset? string Button disabled-state image asset key.
---@field animation? string
---@field skin? string
---@field loop? boolean
---@field text? string
---@field x? number
---@field y? number
---@field width? number
---@field height? number
---@field paddingLeft? number
---@field paddingTop? number
---@field paddingRight? number
---@field paddingBottom? number
---@field anchor? RuntimeAnchor
---@field layer? integer
---@field visible? boolean
---@field alpha? number
---@field scale? number
---@field rotation? number
---@field color? string
---@field fontSize? number
---@field textAlign? RuntimeTextAlign
---@field radius? number
---@field strokeWidth? number
---@field value? number
---@field scrollX? number
---@field scrollY? number
---@field contentWidth? number
---@field contentHeight? number
---@field virtualized? boolean
---@field cacheExtent? number
---@field inertia? boolean
---@field scrollbarThumbColor? string
---@field scrollbarTrackColor? string
---@field scrollbarThickness? number
---@field scrollbarVisible? boolean
---@field interactive? boolean
---@field onTap? string
---@field onScroll? string
---@field preset? RuntimeParticlePreset
---@field count? integer
---@field duration? number
---@field speedMin? number
---@field speedMax? number
---@field gravityX? number
---@field gravityY? number
---@field spread? number
---@field colorTo? string
---@field radiusTo? number
---@field autoRemove? boolean
---@field fadeOut? boolean
---Helper-only fields accepted by runtime_ui/runtime_widgets. They are normalized
---before the node/update crosses the Dart Runtime protocol boundary.
---@class RuntimeNodeInit: RuntimeNodeProps
---@field w? number Alias for width.
---@field h? number Alias for height.
---@field size? number Alias for both width and height.
---@field handler? string Alias for onTap.
---@field onClick? string Alias for onTap.
---@class (exact) RuntimeNodeUpdate
---@field id string
---@field props RuntimeNodeProps
---@class (exact) RuntimeNodeRemove
---@field id string
---@class (exact) RuntimeDiffSection
---@field creates? RuntimeNode[]
---@field updates? RuntimeNodeUpdate[]
---@field removes? (string|RuntimeNodeRemove)[]
---@class (exact) RuntimeDiff
---@field render? RuntimeDiffSection
---@field ui? RuntimeDiffSection
---@field commands? RuntimeCommand[]
---@class (exact) RuntimeEvent
---@field type RuntimeEventType|string
---@field target? string
---@field handler? string
---@field x? number
---@field y? number
---@field data? table
---@class (exact) RuntimeCommand
---@field type RuntimeCommandType
---@field target? string
---@field scope? string
---@field id? string
---@field group? string
---@field commandGroup? string
---@field onComplete? string
---@field duration? number
---@field commands? RuntimeCommand[]
---@field path? RuntimePoint[]
---@field x? number
---@field y? number
---@field alpha? number
---@field scale? number
---@field angle? number
---@field text? string
---@field message? string
---@field asset? string
---@field name? string
---@field volume? number
---@field channel? string
---@field loop? boolean
---@field failOnError? boolean
---@field animation? string
---@field track? integer
---@field queue? boolean
---@field delay? number
---@class (exact) RuntimeCommandOpts
---@field id? string
---@field group? string
---@field commandGroup? string
---@field scope? string
---@field onComplete? string
---@field duration? number
---@class (exact) RuntimeAudioCommandOpts: RuntimeCommandOpts
---@field volume? number
---@field name? string
---@class (exact) RuntimeBgmCommandOpts: RuntimeAudioCommandOpts
---@field channel? string
---@field loop? boolean
---@class (exact) RuntimeSpineCommandOpts: RuntimeCommandOpts
---@field track? integer
---@field loop? boolean
---@field queue? boolean
---@field delay? number
---@class (exact) RuntimeResourceCommandOpts: RuntimeCommandOpts
---@field failOnError? boolean
---@class (exact) RuntimePoint
---@field x number
---@field y number
---@class (exact) RuntimeLocaleContext
---@field requested string
---@field resolved string
---@field default string
---@field supported string[]
---@field languageCode string
---@field scriptCode? string
---@field countryCode? string
---@class (exact) RuntimeScreenContext
---@field width number
---@field height number
---@class (exact) RuntimeDesignContext
---@field width number
---@field height number
---@class (exact) RuntimeViewportContext
---@field x number
---@field y number
---@field width number
---@field height number
---@field scaleX number
---@field scaleY number
---@field scaleMode RuntimeScaleMode|string
---@class (exact) RuntimeContext
---@field screen RuntimeScreenContext
---@field design RuntimeDesignContext
---@field viewport RuntimeViewportContext
---@field seed integer
---@field runtimeApiVersion integer
---@field gameId string
---@field gameVersion string
---@field locale? RuntimeLocaleContext
---@class RuntimeUi
---@field style fun(base?: RuntimeNodeProps, opts?: RuntimeNodeProps): RuntimeNodeProps
---@field with_parent fun(parent: string, opts?: RuntimeNodeProps): RuntimeNodeProps
---@field node fun(node_type: RuntimeNodeType, id: string, opts?: RuntimeNodeInit): RuntimeNode
---@field panel fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field rect fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field circle fun(id: string, x: number|RuntimeNodeInit, y?: number, size?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field line fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field progress fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, value?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field particle fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field text fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field button fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeNodeInit): RuntimeNode
---@field list_view fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field image fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field sprite fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field spine fun(id: string, asset: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, animation?: string, opts?: RuntimeNodeInit): RuntimeNode
---@field update fun(id: string, props: RuntimeNodeInit): RuntimeNodeUpdate
---@field text_update fun(id: string, text: string): RuntimeNodeUpdate
---@field visible_update fun(id: string, visible: boolean): RuntimeNodeUpdate
---@field alpha_update fun(id: string, alpha: number): RuntimeNodeUpdate
---@field scale_update fun(id: string, scale: number): RuntimeNodeUpdate
---@field position_update fun(id: string, x: number, y: number): RuntimeNodeUpdate
---@field size_update fun(id: string, width: number, height: number): RuntimeNodeUpdate
---@field transform_update fun(id: string, x: number, y: number, scale: number, rotation: number): RuntimeNodeUpdate
---@field batch_update fun(ids: string[], props: RuntimeNodeInit): RuntimeNodeUpdate[]
---@field append fun(nodes: RuntimeNode[], node: RuntimeNode): RuntimeNode[]
---@field append_all fun(nodes: RuntimeNode[], extra_nodes: RuntimeNode[]): RuntimeNode[]
---@class (exact) RuntimeDialogButton
---@field id? string
---@field text string
---@field handler string
---@field color? string
---@class (exact) RuntimeDialogOpts
---@field screenWidth? number
---@field screenHeight? number
---@field overlay? boolean
---@field overlayColor? string
---@field blockInput? boolean
---@field layer? integer
---@field color? string
---@field radius? number
---@field panelStyle? RuntimeNodeProps
---@field titleColor? string
---@field titleSize? number
---@field titleStyle? RuntimeNodeProps
---@field messageColor? string
---@field messageSize? number
---@field messageStyle? RuntimeNodeProps
---@field buttons? RuntimeDialogButton[]
---@field buttonGap? number
---@field buttonStyle? RuntimeNodeProps
---@class RuntimeLabeledProgressOpts: RuntimeNodeInit
---@field labelHeight? number
---@field labelStyle? RuntimeNodeProps
---@class RuntimePillOpts: RuntimeNodeInit
---@field panelStyle? RuntimeNodeProps
---@field textStyle? RuntimeNodeProps
---@class RuntimeTextButtonOpts: RuntimeNodeInit
---@field variant? RuntimeButtonVariant
---@class RuntimeListItemOpts: RuntimeTextButtonOpts
---@field selected? boolean
---@field activeColor? string
---@field inactiveColor? string
---@class RuntimeTabItem
---@field id? string
---@field key? string
---@field text string
---@field handler? string
---@field selected? boolean
---@class RuntimeTabsOpts: RuntimeNodeInit
---@field tabs? RuntimeTabItem[]
---@field selected? string
---@field gap? number
---@field itemWidth? number
---@field itemHeight? number
---@field activeColor? string
---@field inactiveColor? string
---@field buttonStyle? RuntimeNodeProps
---@class RuntimeActionItem
---@field id? string
---@field text string
---@field handler? string
---@field visible? boolean
---@field color? string
---@field style? RuntimeNodeProps
---@class RuntimeActionRowOpts: RuntimeNodeInit
---@field actions? RuntimeActionItem[]
---@field gap? number
---@field itemWidth? number
---@field itemHeight? number
---@field buttonStyle? RuntimeNodeProps
---@class RuntimePanelHeaderOpts: RuntimeNodeInit
---@field eyebrow? string
---@field title string
---@field summary? string
---@field gap? number
---@field eyebrowId? string
---@field titleId? string
---@field summaryId? string
---@field eyebrowHeight? number
---@field titleHeight? number
---@field summaryHeight? number
---@field eyebrowStyle? RuntimeNodeProps
---@field titleStyle? RuntimeNodeProps
---@field summaryStyle? RuntimeNodeProps
---@class RuntimeWidgetTheme
---@field primary? string
---@field secondary? string
---@field success? string
---@field overlay? string
---@field surface? string
---@field surfaceAlt? string
---@field card? string
---@field text? string
---@field muted? string
---@field progress? string
---@field transparent? string
---@class RuntimeWidgets
---@field configure fun(tokens?: RuntimeWidgetTheme): RuntimeWidgets
---@field label fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field section_title fun(id: string, text: string|RuntimeNodeInit, x?: number, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field pill fun(id: string, text: string|RuntimePillOpts, x?: number, y?: number, width?: number, height?: number, opts?: RuntimePillOpts): RuntimeNode[]
---@field text_button fun(id: string, text: string|RuntimeTextButtonOpts, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeTextButtonOpts): RuntimeNode
---@field list_item fun(id: string, text: string|RuntimeListItemOpts, x?: number, y?: number, width?: number, height?: number, handler?: string, opts?: RuntimeListItemOpts): RuntimeNode
---@field tabs fun(id: string, tabs: RuntimeTabItem[]|RuntimeTabsOpts, opts?: RuntimeTabsOpts): RuntimeNode[]
---@field action_row fun(id: string, actions: RuntimeActionItem[]|RuntimeActionRowOpts, opts?: RuntimeActionRowOpts): RuntimeNode[]
---@field panel_header fun(id: string, opts: RuntimePanelHeaderOpts): RuntimeNode[]
---@field overlay fun(id: string, width: number|RuntimeNodeInit, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field card fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field progress_bar fun(id: string, x: number|RuntimeNodeInit, y?: number, width?: number, height?: number, value?: number, opts?: RuntimeNodeInit): RuntimeNode
---@field labeled_progress fun(id: string, label: string, x: number, y: number, width: number, height: number, value: number, opts?: RuntimeLabeledProgressOpts): RuntimeNode[]
---@field button_row fun(parent: string, id: string, buttons: RuntimeDialogButton[], x: number, y: number, width: number, height: number, gap?: number, opts?: RuntimeNodeProps): RuntimeNode[]
---@field dialog fun(id: string, title: string, message: string, x: number, y: number, width: number, height: number, opts?: RuntimeDialogOpts): RuntimeNode[]
---@class (exact) RuntimeLayoutItem
---@field node RuntimeNode
---@field marginLeft? number
---@field marginRight? number
---@field marginTop? number
---@field marginBottom? number
---@class RuntimeLayoutItemOpts
---@field margin? number
---@field mx? number
---@field my? number
---@field ml? number
---@field mr? number
---@field mt? number
---@field mb? number
---@field marginLeft? number
---@field marginRight? number
---@field marginTop? number
---@field marginBottom? number
---@class RuntimeLinearLayoutOpts
---@field x? number
---@field y? number
---@field width? number
---@field height? number
---@field gap? number
---@field align? RuntimeLayoutAlign
---@field padding? number
---@field paddingX? number
---@field paddingY? number
---@field px? number
---@field py? number
---@field paddingLeft? number
---@field paddingTop? number
---@class RuntimeBoxLayoutOpts: RuntimeLinearLayoutOpts
---@field rows? integer
---@field columns? integer
---@field cols? integer
---@field cellWidth? number
---@field cellHeight? number
---@field cellW? number
---@field cellH? number
---@field gapX? number
---@field gapY? number
---@field valign? RuntimeLayoutAlign
---@class RuntimeLayout
---@field item fun(node: RuntimeNode, opts?: RuntimeLayoutItemOpts): RuntimeLayoutItem
---@field local_position fun(origin: RuntimePoint, position: RuntimePoint): RuntimePoint
---@field row fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
---@field column fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
---@field stack fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeLinearLayoutOpts): RuntimeNode[]
---@field box fun(parent?: string, items: (RuntimeNode|RuntimeLayoutItem)[], opts?: RuntimeBoxLayoutOpts): RuntimeNode[]
---@class RuntimeCommands
---@field toast fun(text: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field copy_text fun(text: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field delay fun(duration: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field sequence fun(items: RuntimeCommand[], opts?: RuntimeCommandOpts): RuntimeCommand
---@field parallel fun(items: RuntimeCommand[], opts?: RuntimeCommandOpts): RuntimeCommand
---@field move_path fun(target: string, path: RuntimePoint[], opts?: RuntimeCommandOpts): RuntimeCommand
---@field move_to fun(target: string, x: number, y: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field fade_to fun(target: string, alpha: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field scale_to fun(target: string, scale: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field rotate_to fun(target: string, angle: number, opts?: RuntimeCommandOpts): RuntimeCommand
---@field remove_node fun(target: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field play_spine_animation fun(target: string, animation: string, opts?: RuntimeSpineCommandOpts): RuntimeCommand
---@field play_sound fun(asset: string, opts?: RuntimeAudioCommandOpts): RuntimeCommand
---@field play_bgm fun(asset: string, opts?: RuntimeBgmCommandOpts): RuntimeCommand
---@field pause_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field resume_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field stop_bgm fun(channel?: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field preload_group fun(group: string, opts?: RuntimeResourceCommandOpts): RuntimeCommand
---@field evict_group fun(group: string, opts?: RuntimeCommandOpts): RuntimeCommand
---@field cancel_id fun(id: string): RuntimeCommand
---@field cancel_group fun(group: string): RuntimeCommand
---@field cancel_scope fun(scope: string): RuntimeCommand
---@class RuntimeImportApi
---@field import fun(moduleName: string): table
---@type RuntimeImportApi
runtime = runtime

View File

@@ -0,0 +1,8 @@
---@class TemplateState
local state = {
started = false,
clicks = 0,
status = "点击 Start 验证 RuntimeEvent -> Lua -> Diff / Command"
}
return state

View File

@@ -0,0 +1,100 @@
---@type RuntimeUi
local runtime_ui = runtime.import("runtime_ui")
---@type RuntimeWidgets
local widgets = runtime.import("runtime_widgets")
local state = runtime.import("state")
widgets.configure({
primary = "#ff2563eb",
secondary = "#ff475569",
success = "#ff22c55e",
overlay = "#99000000",
surface = "#ff1e293b",
surfaceAlt = "#ff334155",
card = "#ee111827",
text = "#ffe2e8f0",
muted = "#ff94a3b8",
progress = "#ff22c55e",
transparent = "#00000000"
})
---@class TemplateUi
local ui = {}
---@return RuntimeNode[]
function ui.create_nodes()
return {
runtime_ui.rect("template_bg", {
x = 0,
y = 0,
w = 720,
h = 720,
color = "#ff0f172a"
}),
runtime_ui.panel("template_card", {
x = 72,
y = 96,
w = 576,
h = 360,
color = "#ee111827",
radius = 18,
layer = 5
}),
widgets.section_title("template_title", {
text = "Lua Runtime Template",
x = 104,
y = 132,
w = 420,
h = 34,
color = "#ffe2e8f0",
fontSize = 24,
layer = 10
}),
widgets.label("template_desc", {
text = "最小接入示例:一个 manifest、一个 main.lua、一组 RuntimeNode。",
x = 104,
y = 178,
w = 500,
h = 24,
color = "#ff94a3b8",
fontSize = 14,
layer = 10
}),
runtime_ui.button("template_start", {
text = state.started and "Started" or "Start",
x = 104,
y = 236,
w = 144,
h = 44,
handler = "template_start",
color = state.started and "#ff16a34a" or "#ff2563eb",
radius = 12,
fontSize = 15,
layer = 10
}),
runtime_ui.text("template_counter", "Clicks: " .. tostring(state.clicks), 272, 246, 180, 24, {
color = "#ffe2e8f0",
fontSize = 15,
layer = 10
}),
runtime_ui.text("template_status", state.status, 104, 320, 500, 24, {
color = "#fffacc15",
fontSize = 13,
layer = 10
})
}
end
---@return RuntimeNodeUpdate[]
function ui.state_updates()
return {
runtime_ui.update("template_start", {
text = state.started and "Started" or "Start",
color = state.started and "#ff16a34a" or "#ff2563eb"
}),
runtime_ui.text_update("template_counter", "Clicks: " .. tostring(state.clicks)),
runtime_ui.text_update("template_status", state.status)
}
end
return ui

34
example/ios/.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1,616 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.flameLuaLudo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flameLuaLudo.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flameLuaLudo.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flameLuaLudo.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.flameLuaLudo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.flameLuaLudo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,13 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Some files were not shown because too many files have changed in this diff Show More