
These will be changed to conditionally include system Lua headers, e.g. "lua.h", instead of submodule Lua headers, e.g. "module/lua/lua.h". If a header named "lua.h" includes "lua.h", the build will fail due to recursion. This can't be solved using angle brackets to include system headers, because macos builds won't find them: In file included from /Users/runner/work/wesnoth/wesnoth/src/ai/registry.cpp:30: In file included from /Users/runner/work/wesnoth/wesnoth/src/ai/composite/aspect.hpp:24: In file included from /Users/runner/work/wesnoth/wesnoth/src/ai/lua/lua_object.hpp:25: /Users/runner/work/wesnoth/wesnoth/src/lua/lua.h:4:14: error: 'lua.h' file not found with <angled> include; use "quotes" instead #include <lua.h> ^~~~~~~ "lua.h" Renamed with (requires GNU sed): $ for f in src/lua/*.h; do > git mv "${f}" "src/lua/wrapper_${f#src/lua/}"; > done $ git grep -El -- '#[ \t]*include[ \t]+"lua/[^"]+[.]h"' src | \ > xargs sed -Ei -- ' > s|(#[ \t]*include[ \t]+"lua/)(lua[.]h")( )?|\1wrapper_\2|; > s|(#[ \t]*include[ \t]+"lua/)(lualib[.]h")( )?|\1wrapper_\2|; > s|(#[ \t]*include[ \t]+"lua/)(lauxlib[.]h")( )?|\1wrapper_\2|; > '
311 lines
9.6 KiB
C++
311 lines
9.6 KiB
C++
/*
|
|
Copyright (C) 2014 - 2024
|
|
by Chris Beck <render787@gmail.com>
|
|
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
|
|
#include "scripting/lua_gui2.hpp"
|
|
|
|
#include "gui/core/gui_definition.hpp"
|
|
#include "gui/core/window_builder.hpp"
|
|
#include "gui/dialogs/drop_down_menu.hpp"
|
|
#include "gui/dialogs/gamestate_inspector.hpp"
|
|
#include "gui/dialogs/lua_interpreter.hpp"
|
|
#include "gui/dialogs/wml_message.hpp"
|
|
#include "gui/dialogs/story_viewer.hpp"
|
|
#include "gui/dialogs/transient_message.hpp"
|
|
#include "gui/dialogs/message.hpp"
|
|
#include "gui/widgets/retval.hpp"
|
|
#include "gui/core/gui_definition.hpp"
|
|
#include "scripting/lua_widget_methods.hpp" //intf_show_dialog
|
|
|
|
#include "config.hpp"
|
|
#include "log.hpp"
|
|
#include "scripting/lua_common.hpp"
|
|
#include "scripting/lua_cpp_function.hpp"
|
|
#include "scripting/lua_kernel_base.hpp"
|
|
#include "scripting/lua_widget_methods.hpp"
|
|
#include "scripting/push_check.hpp"
|
|
#include "serialization/string_utils.hpp"
|
|
#include "help/help.hpp"
|
|
#include "game_config_manager.hpp"
|
|
#include "tstring.hpp"
|
|
#include "game_data.hpp"
|
|
#include "game_state.hpp"
|
|
#include "sdl/input.hpp" // get_mouse_state
|
|
|
|
#include <functional>
|
|
#include <optional>
|
|
|
|
#include <map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "lua/wrapper_lauxlib.h" // for luaL_checkinteger, lua_setfield, etc
|
|
|
|
static lg::log_domain log_scripting_lua("scripting/lua");
|
|
#define ERR_LUA LOG_STREAM(err, log_scripting_lua)
|
|
|
|
namespace lua_gui2 {
|
|
|
|
|
|
/**
|
|
* Displays a message window
|
|
* - Arg 1: Table describing the window
|
|
* - Arg 2: List of options (nil or empty table - no options)
|
|
* - Arg 3: Text input specifications (nil or empty table - no text input)
|
|
* - Ret 1: option chosen (if no options: 0 if there's text input, -2 if escape pressed, else -1)
|
|
* - Ret 2: string entered (empty if none, nil if no text input)
|
|
*/
|
|
int show_message_dialog(lua_State* L)
|
|
{
|
|
config txt_cfg;
|
|
const bool has_input = !lua_isnoneornil(L, 3) && luaW_toconfig(L, 3, txt_cfg) && !txt_cfg.empty();
|
|
|
|
gui2::dialogs::wml_message_input input;
|
|
input.caption = txt_cfg["label"].str();
|
|
input.text = txt_cfg["text"].str();
|
|
input.maximum_length = txt_cfg["max_length"].to_int(256);
|
|
input.text_input_was_specified = has_input;
|
|
|
|
gui2::dialogs::wml_message_options options{};
|
|
if(!lua_isnoneornil(L, 2)) {
|
|
luaL_checktype(L, 2, LUA_TTABLE);
|
|
std::size_t n = lua_rawlen(L, 2);
|
|
for(std::size_t i = 1; i <= n; i++) {
|
|
lua_rawgeti(L, 2, i);
|
|
t_string short_opt;
|
|
config opt;
|
|
if(luaW_totstring(L, -1, short_opt)) {
|
|
opt["label"] = short_opt;
|
|
} else if(!luaW_toconfig(L, -1, opt)) {
|
|
std::ostringstream error;
|
|
error << "expected array of config and/or translatable strings, but index ";
|
|
error << i << " was a " << lua_typename(L, lua_type(L, -1));
|
|
return luaL_argerror(L, 2, error.str().c_str());
|
|
}
|
|
gui2::dialogs::wml_message_option option(opt["label"], opt["description"], opt["image"]);
|
|
if(opt["default"].to_bool(false)) {
|
|
options.chosen_option = i - 1;
|
|
}
|
|
options.option_list.push_back(option);
|
|
lua_pop(L, 1);
|
|
}
|
|
lua_getfield(L, 2, "default");
|
|
if(lua_isnumber(L, -1)) {
|
|
int i = lua_tointeger(L, -1);
|
|
if(i < 1 || std::size_t(i) > n) {
|
|
std::ostringstream error;
|
|
error << "default= key in options list is not a valid option index (1-" << n << ")";
|
|
return luaL_argerror(L, 2, error.str().c_str());
|
|
}
|
|
options.chosen_option = i - 1;
|
|
}
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
const config& def_cfg = luaW_checkconfig(L, 1);
|
|
const std::string& title = def_cfg["title"];
|
|
const std::string& message = def_cfg["message"];
|
|
|
|
using portrait = gui2::dialogs::wml_message_portrait;
|
|
std::unique_ptr<portrait> left;
|
|
std::unique_ptr<portrait> right;
|
|
const bool is_double = def_cfg.has_attribute("second_portrait");
|
|
const bool left_side = def_cfg["left_side"].to_bool(true);
|
|
if(is_double || left_side) {
|
|
left.reset(new portrait {def_cfg["portrait"], def_cfg["mirror"].to_bool(false)});
|
|
} else {
|
|
// This means right side only.
|
|
right.reset(new portrait {def_cfg["portrait"], def_cfg["mirror"].to_bool(false)});
|
|
}
|
|
if(is_double) {
|
|
right.reset(new portrait {def_cfg["second_portrait"], def_cfg["second_mirror"].to_bool(false)});
|
|
}
|
|
|
|
int dlg_result = gui2::dialogs::show_wml_message(title, message, left.get(), right.get(), options, input);
|
|
|
|
if(!has_input && options.option_list.empty()) {
|
|
lua_pushinteger(L, dlg_result);
|
|
} else {
|
|
lua_pushinteger(L, options.chosen_option + 1);
|
|
}
|
|
|
|
if(has_input) {
|
|
lua_pushlstring(L, input.text.c_str(), input.text.length());
|
|
} else {
|
|
lua_pushnil(L);
|
|
}
|
|
|
|
return 2;
|
|
}
|
|
|
|
/**
|
|
* Displays a popup message
|
|
* - Arg 1: Title (allows Pango markup)
|
|
* - Arg 2: Message (allows Pango markup)
|
|
* - Arg 3: Image (optional)
|
|
*/
|
|
int show_popup_dialog(lua_State *L) {
|
|
t_string title = luaW_checktstring(L, 1);
|
|
t_string msg = luaW_checktstring(L, 2);
|
|
std::string image = lua_isnoneornil(L, 3) ? "" : luaL_checkstring(L, 3);
|
|
|
|
gui2::show_transient_message(title, msg, image, true, true);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Displays a story screen
|
|
* - Arg 1: The story config
|
|
* - Arg 2: The default title
|
|
*/
|
|
int show_story(lua_State* L) {
|
|
config story = luaW_checkconfig(L, 1);
|
|
t_string title = luaW_checktstring(L, 2);
|
|
gui2::dialogs::story_viewer::display(title, story);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Displays a popup menu at the current mouse position
|
|
* Best used from a [set_menu_item], to show a submenu
|
|
* - Arg 1: Configs defining each item, with keys icon, image/label, second_label, tooltip
|
|
* - Args 2, 3: Initial selection (integer); whether to parse markup (boolean)
|
|
*/
|
|
int show_menu(lua_State* L) {
|
|
std::vector<config> items = lua_check<std::vector<config>>(L, 1);
|
|
SDL_Rect pos {1,1,1,1};
|
|
sdl::get_mouse_state(&pos.x, &pos.y);
|
|
|
|
int initial = -1;
|
|
bool markup = false;
|
|
if(lua_isnumber(L, 2)) {
|
|
initial = lua_tointeger(L, 2) - 1;
|
|
markup = luaW_toboolean(L, 3);
|
|
} else if(lua_isnumber(L, 3)) {
|
|
initial = lua_tointeger(L, 3) - 1;
|
|
markup = luaW_toboolean(L, 2);
|
|
} else if(lua_isboolean(L, 2)) {
|
|
markup = luaW_toboolean(L, 2);
|
|
}
|
|
|
|
gui2::dialogs::drop_down_menu menu(pos, items, initial, markup, false);
|
|
menu.show();
|
|
lua_pushinteger(L, menu.selected_item() + 1);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Displays a simple message box.
|
|
*/
|
|
int show_message_box(lua_State* L) {
|
|
const t_string title = luaW_checktstring(L, 1), message = luaW_checktstring(L, 2);
|
|
std::string button = luaL_optstring(L, 3, "ok"), btn_style;
|
|
std::transform(button.begin(), button.end(), std::inserter(btn_style, btn_style.begin()), [](char c) { return std::tolower(c); });
|
|
bool markup = lua_isnoneornil(L, 3) ? luaW_toboolean(L, 3) : luaW_toboolean(L, 4);
|
|
using button_style = gui2::dialogs::message::button_style;
|
|
std::optional<button_style> style;
|
|
if(btn_style.empty()) {
|
|
style = button_style::auto_close;
|
|
} else if(btn_style == "ok") {
|
|
style = button_style::ok_button;
|
|
} else if(btn_style == "close") {
|
|
style = button_style::close_button;
|
|
} else if(btn_style == "ok_cancel") {
|
|
style = button_style::ok_cancel_buttons;
|
|
} else if(btn_style == "cancel") {
|
|
style = button_style::cancel_button;
|
|
} else if(btn_style == "yes_no") {
|
|
style = button_style::yes_no_buttons;
|
|
}
|
|
if(style) {
|
|
int result = gui2::show_message(title, message, *style, markup, markup);
|
|
if(style == button_style::ok_cancel_buttons || style == button_style::yes_no_buttons) {
|
|
lua_pushboolean(L, result == gui2::retval::OK);
|
|
return 1;
|
|
}
|
|
} else {
|
|
gui2::show_message(title, message, button, false, markup, markup);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int show_lua_console(lua_State* /*L*/, lua_kernel_base* lk)
|
|
{
|
|
gui2::dialogs::lua_interpreter::display(lk);
|
|
return 0;
|
|
}
|
|
|
|
int show_gamestate_inspector(const vconfig& cfg, const game_data& data, const game_state& state)
|
|
{
|
|
gui2::dialogs::gamestate_inspector::display(data.get_variables(), *state.events_manager_, state.board_, cfg["name"]);
|
|
return 0;
|
|
}
|
|
|
|
static int show_help(lua_State *L)
|
|
{
|
|
help::show_help(luaL_checkstring(L, 1));
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* - Arg 1: string, widget type
|
|
* - Arg 3: string, id
|
|
* - Arg 3: config,
|
|
*/
|
|
|
|
int intf_add_widget_definition(lua_State* L)
|
|
{
|
|
std::string type = luaL_checkstring(L, 1);
|
|
std::string id = luaL_checkstring(L, 2);
|
|
try {
|
|
if(gui2::add_single_widget_definition(type, id, luaW_checkconfig(L, 3))) {
|
|
lua_kernel_base::get_lua_kernel<lua_kernel_base>(L).add_widget_definition(type, id);
|
|
}
|
|
} catch(const std::invalid_argument& e) {
|
|
return luaL_argerror(L, 1, e.what());
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int luaW_open(lua_State* L)
|
|
{
|
|
auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
|
|
lk.add_log("Adding gui module...\n");
|
|
static luaL_Reg const gui_callbacks[] = {
|
|
{ "show_menu", &show_menu },
|
|
{ "show_narration", &show_message_dialog },
|
|
{ "show_popup", &show_popup_dialog },
|
|
{ "show_story", &show_story },
|
|
{ "show_prompt", &show_message_box },
|
|
{ "show_help", &show_help },
|
|
{ "add_widget_definition", &intf_add_widget_definition },
|
|
{ "show_dialog", &intf_show_dialog },
|
|
{ nullptr, nullptr },
|
|
};
|
|
std::vector<lua_cpp::Reg> const cpp_gui_callbacks {
|
|
{"show_lua_console", std::bind(&lua_kernel_base::intf_show_lua_console, &lk, std::placeholders::_1)},
|
|
{nullptr, nullptr}
|
|
};
|
|
lua_newtable(L);
|
|
luaL_setfuncs(L, gui_callbacks, 0);
|
|
lua_cpp::set_functions(L, cpp_gui_callbacks);
|
|
|
|
lua_pushstring(L, "widget");
|
|
lua_widget::luaW_open(L);
|
|
lua_rawset(L, -3);
|
|
|
|
return 1;
|
|
}
|
|
|
|
} // end namespace lua_gui2
|