The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
lua_gui2.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2014 - 2025
3 by Chris Beck <render787@gmail.com>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
17
18#include "game_display.hpp"
19#include "gui/gui.hpp"
32#include "scripting/lua_widget_methods.hpp" //intf_show_dialog
33
34#include "config.hpp"
35#include "game_data.hpp"
36#include "game_state.hpp"
37#include "log.hpp"
42#include "help/help.hpp"
43#include "tstring.hpp"
44#include "sdl/input.hpp" // get_mouse_state
45#include "units/ptr.hpp"
46#include "units/unit.hpp"
48
49#include <functional>
50#include <vector>
51
52
53static lg::log_domain log_scripting_lua("scripting/lua");
54#define ERR_LUA LOG_STREAM(err, log_scripting_lua)
55
56namespace lua_gui2 {
57
58
59/**
60 * Displays a message window
61 * - Arg 1: Table describing the window
62 * - Arg 2: List of options (nil or empty table - no options)
63 * - Arg 3: Text input specifications (nil or empty table - no text input)
64 * - Ret 1: option chosen (if no options: 0 if there's text input, -2 if escape pressed, else -1)
65 * - Ret 2: string entered (empty if none, nil if no text input)
66 */
67int show_message_dialog(lua_State* L)
68{
69 config txt_cfg;
70 const bool has_input = !lua_isnoneornil(L, 3) && luaW_toconfig(L, 3, txt_cfg) && !txt_cfg.empty();
71
73 input.caption = txt_cfg["label"].str();
74 input.text = txt_cfg["text"].str();
75 input.maximum_length = txt_cfg["max_length"].to_int(256);
76 input.text_input_was_specified = has_input;
77
79 if(!lua_isnoneornil(L, 2)) {
80 luaL_checktype(L, 2, LUA_TTABLE);
81 std::size_t n = lua_rawlen(L, 2);
82 for(std::size_t i = 1; i <= n; i++) {
83 lua_rawgeti(L, 2, i);
84 t_string short_opt;
85 config opt;
86 if(luaW_totstring(L, -1, short_opt)) {
87 opt["label"] = short_opt;
88 } else if(!luaW_toconfig(L, -1, opt)) {
89 std::ostringstream error;
90 error << "expected array of config and/or translatable strings, but index ";
91 error << i << " was a " << lua_typename(L, lua_type(L, -1));
92 return luaL_argerror(L, 2, error.str().c_str());
93 }
94 gui2::dialogs::wml_message_option option(opt["label"], opt["description"], opt["image"]);
95 if(opt["default"].to_bool(false)) {
96 options.chosen_option = i - 1;
97 }
98 options.option_list.push_back(option);
99 lua_pop(L, 1);
100 }
101 lua_getfield(L, 2, "default");
102 if(lua_isnumber(L, -1)) {
103 int i = lua_tointeger(L, -1);
104 if(i < 1 || std::size_t(i) > n) {
105 std::ostringstream error;
106 error << "default= key in options list is not a valid option index (1-" << n << ")";
107 return luaL_argerror(L, 2, error.str().c_str());
108 }
109 options.chosen_option = i - 1;
110 }
111 lua_pop(L, 1);
112 }
113
114 const config& def_cfg = luaW_checkconfig(L, 1);
115 const std::string& title = def_cfg["title"];
116 const std::string& message = def_cfg["message"];
117
119 std::unique_ptr<portrait> left;
120 std::unique_ptr<portrait> right;
121 const bool is_double = def_cfg.has_attribute("second_portrait");
122 const bool left_side = def_cfg["left_side"].to_bool(true);
123 if(is_double || left_side) {
124 left.reset(new portrait {def_cfg["portrait"], def_cfg["mirror"].to_bool(false)});
125 } else {
126 // This means right side only.
127 right.reset(new portrait {def_cfg["portrait"], def_cfg["mirror"].to_bool(false)});
128 }
129 if(is_double) {
130 right.reset(new portrait {def_cfg["second_portrait"], def_cfg["second_mirror"].to_bool(false)});
131 }
132
133 int dlg_result = gui2::dialogs::show_wml_message(title, message, left.get(), right.get(), options, input);
134
135 if(!has_input && options.option_list.empty()) {
136 lua_pushinteger(L, dlg_result);
137 } else {
138 lua_pushinteger(L, options.chosen_option + 1);
139 }
140
141 if(has_input) {
142 lua_pushlstring(L, input.text.c_str(), input.text.length());
143 } else {
144 lua_pushnil(L);
145 }
146
147 return 2;
148}
149
150/**
151 * Displays a popup message
152 * - Arg 1: Title (allows Pango markup)
153 * - Arg 2: Message (allows Pango markup)
154 * - Arg 3: Image (optional)
155 */
156int show_popup_dialog(lua_State *L) {
157 t_string title = luaW_checktstring(L, 1);
158 t_string msg = luaW_checktstring(L, 2);
159 std::string image = lua_isnoneornil(L, 3) ? "" : luaL_checkstring(L, 3);
160
161 gui2::show_transient_message(title, msg, image, true, true);
162 return 0;
163}
164
165/**
166 * Displays a story screen
167 * - Arg 1: The story config
168 * - Arg 2: The default title
169 */
170int show_story(lua_State* L) {
171 config story = luaW_checkconfig(L, 1);
172 t_string title = luaW_checktstring(L, 2);
174 return 0;
175}
176
177/**
178 * Changes the current ui(gui2) theme
179 * - Arg 1: The id of the theme to switch to
180 */
181int switch_theme(lua_State* L) {
182 std::string theme_id = luaL_checkstring(L, 1);
183 gui2::switch_theme(theme_id);
184 return 0;
185}
186
187/**
188 * Displays a popup menu at the current mouse position
189 * Best used from a [set_menu_item], to show a submenu
190 * - Arg 1: Configs defining each item, with keys icon, image/label, second_label, tooltip
191 * - Args 2, 3: Initial selection (integer); whether to parse markup (boolean)
192 */
193int show_menu(lua_State* L) {
194 std::vector<config> items = lua_check<std::vector<config>>(L, 1);
195 rect pos{ sdl::get_mouse_location(), {1, 1} };
196
197 int initial = -1;
198 bool markup = false;
199 if(lua_isnumber(L, 2)) {
200 initial = lua_tointeger(L, 2) - 1;
201 markup = luaW_toboolean(L, 3);
202 } else if(lua_isnumber(L, 3)) {
203 initial = lua_tointeger(L, 3) - 1;
204 markup = luaW_toboolean(L, 2);
205 } else if(lua_isboolean(L, 2)) {
206 markup = luaW_toboolean(L, 2);
207 }
208
209 gui2::dialogs::drop_down_menu menu(pos, items, initial, markup, false);
210 menu.show();
211 lua_pushinteger(L, menu.selected_item() + 1);
212 return 1;
213}
214
215/**
216 * Displays a simple message box.
217 */
218int show_message_box(lua_State* L) {
219 const t_string title = luaW_checktstring(L, 1), message = luaW_checktstring(L, 2);
220 std::string button = luaL_optstring(L, 3, "ok"), btn_style;
221 std::transform(button.begin(), button.end(), std::inserter(btn_style, btn_style.begin()), [](char c) { return std::tolower(c); });
222 bool markup = lua_isnoneornil(L, 3) ? luaW_toboolean(L, 3) : luaW_toboolean(L, 4);
223 using button_style = gui2::dialogs::message::button_style;
224 utils::optional<button_style> style;
225 if(btn_style.empty()) {
226 style = button_style::auto_close;
227 } else if(btn_style == "ok") {
228 style = button_style::ok_button;
229 } else if(btn_style == "close") {
230 style = button_style::close_button;
231 } else if(btn_style == "ok_cancel") {
232 style = button_style::ok_cancel_buttons;
233 } else if(btn_style == "cancel") {
234 style = button_style::cancel_button;
235 } else if(btn_style == "yes_no") {
236 style = button_style::yes_no_buttons;
237 }
238 if(style) {
239 int result = gui2::show_message(title, message, *style, markup, markup);
240 if(style == button_style::ok_cancel_buttons || style == button_style::yes_no_buttons) {
241 lua_pushboolean(L, result == gui2::retval::OK);
242 return 1;
243 }
244 } else {
245 gui2::show_message(title, message, button, false, markup, markup);
246 }
247 return 0;
248}
249
250int show_lua_console(lua_State* /*L*/, lua_kernel_base* lk)
251{
253 return 0;
254}
255
256int show_gamestate_inspector(const std::string& name, const game_data& data, const game_state& state)
257{
258 gui2::dialogs::gamestate_inspector::display(data.get_variables(), *state.events_manager_, state.board_, name);
259 return 0;
260}
261
263{
264 int idx = 1;
265 const std::size_t len = lua_rawlen(L, idx);
266 if (!lua_istable(L, idx)) {
267 return luaL_error(L, "List of unit types not specified!");
268 }
269
270 std::vector<const unit_type*> types;
271 types.reserve(len);
272 for (std::size_t i = 1; i <= len; i++) {
273 lua_rawgeti(L, idx, i);
274 const unit_type* ut = luaW_tounittype(L, -1);
275 if (ut) {
276 types.push_back(ut);
277 }
278 lua_pop(L, idx);
279 }
280
281 const display* disp = display::get_singleton();
282 if (!types.empty() && disp != nullptr) {
283 std::map<const unit_type*, t_string> dummy; // TODO implement recruitability
285
286 idx++;
287 const config& cfg = luaW_checkconfig(L, idx);
288 if (!cfg.empty()) {
289 if (!cfg["title"].empty()) {
290 dlg->set_title(cfg["title"]);
291 }
292
293 if (!cfg["ok_label"].empty()) {
294 dlg->set_ok_label(cfg["ok_label"]);
295 }
296
297 if (!cfg["cancel_label"].empty()) {
298 dlg->set_cancel_label(cfg["cancel_label"]);
299 }
300
301 if (!cfg["help_topic"].empty()) {
302 dlg->set_help_topic(cfg["help_topic"]);
303 }
304 }
305
306 if(dlg->show() && dlg->is_selected()) {
307 luaW_pushunittype(L, *types[dlg->get_selected_index()]);
308 return 1;
309 }
310 } else {
311 ERR_LUA << "Unable to show recruit dialog";
312 }
313
314 return 0;
315}
316
317
318int intf_show_recall_dialog(lua_State* L)
319{
320 int idx = 1;
321 const std::size_t len = lua_rawlen(L, idx);
322 if (!lua_istable(L, idx)) {
323 return luaL_error(L, "List of units not specified!");
324 }
325
326 std::vector<unit_const_ptr> units;
327 units.reserve(len);
328 for (std::size_t i = 1; i <= len; i++) {
329 lua_rawgeti(L, idx, i);
331 if (u) {
332 units.push_back(u);
333 }
334 lua_pop(L, idx);
335 }
336
337 const display* disp = display::get_singleton();
338 if (!units.empty() && disp != nullptr) {
340
341 idx++;
342 const config& cfg = luaW_checkconfig(L, idx);
343 if (!cfg.empty()) {
344 if (!cfg["title"].empty()) {
345 dlg->set_title(cfg["title"]);
346 }
347
348 if (!cfg["ok_label"].empty()) {
349 dlg->set_ok_label(cfg["ok_label"]);
350 }
351
352 if (!cfg["cancel_label"].empty()) {
353 dlg->set_cancel_label(cfg["cancel_label"]);
354 }
355
356 if (!cfg["help_topic"].empty()) {
357 dlg->set_help_topic(cfg["help_topic"]);
358 }
359 }
360
361 if(dlg->show() && dlg->is_selected()) {
362 luaW_pushunit(L, units[dlg->get_selected_index()]->underlying_id());
363 return 1;
364 }
365 } else {
366 ERR_LUA << "Unable to show recall dialog";
367 }
368
369 return 0;
370}
371
372static int show_help(lua_State *L)
373{
374 help::show_help(luaL_checkstring(L, 1));
375 return 0;
376}
377
378/**
379 * - Arg 1: string, widget type
380 * - Arg 3: string, id
381 * - Arg 3: config,
382 */
383
385{
386 std::string type = luaL_checkstring(L, 1);
387 std::string id = luaL_checkstring(L, 2);
388 try {
390 lua_kernel_base::get_lua_kernel<lua_kernel_base>(L).add_widget_definition(type, id);
391 }
392 } catch(const std::invalid_argument& e) {
393 return luaL_argerror(L, 1, e.what());
394 }
395 return 0;
396}
397
398int luaW_open(lua_State* L)
399{
400 auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
401 lk.add_log("Adding gui module...\n");
402 static luaL_Reg const gui_callbacks[] = {
403 { "show_menu", &show_menu },
404 { "show_narration", &show_message_dialog },
405 { "show_popup", &show_popup_dialog },
406 { "show_story", &show_story },
407 { "show_prompt", &show_message_box },
408 { "show_help", &show_help },
409 { "switch_theme", &switch_theme },
410 { "add_widget_definition", &intf_add_widget_definition },
411 { "show_dialog", &intf_show_dialog },
412 { nullptr, nullptr },
413 };
414 std::vector<lua_cpp::Reg> const cpp_gui_callbacks {
415 {"show_lua_console", std::bind(&lua_kernel_base::intf_show_lua_console, &lk, std::placeholders::_1)},
416 {nullptr, nullptr}
417 };
418 lua_newtable(L);
419 luaL_setfuncs(L, gui_callbacks, 0);
420 lua_cpp::set_functions(L, cpp_gui_callbacks);
421
422 lua_pushstring(L, "widget");
424 lua_rawset(L, -3);
425
426 return 1;
427}
428
429} // end namespace lua_gui2
static auto & dummy
#define ERR_LUA
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
bool has_attribute(std::string_view key) const
Definition config.cpp:157
bool empty() const
Definition config.cpp:823
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition display.hpp:88
const team & playing_team() const
Definition display.cpp:332
static display * get_singleton()
Returns the display object if a display object exists.
Definition display.hpp:102
game_board board_
const std::unique_ptr< game_events::manager > events_manager_
Used by the menu_button widget.
static void display(lua_kernel_base *lk)
Display a new console, using given video and lua kernel.
button_style
Selects the style of the buttons to be shown.
Definition message.hpp:70
bool show(const unsigned auto_close_time=0)
Shows the window.
static void display(const std::string &scenario_name, const config &story)
static std::unique_ptr< units_dialog > build_recruit_dialog(const std::vector< const unit_type * > &recruit_list, recruit_msgs_map &err_msgs_map, const team &team)
static std::unique_ptr< units_dialog > build_recall_dialog(std::vector< unit_const_ptr > &recall_list, const team &team)
Helper class for message options.
int intf_show_lua_console(lua_State *L)
A single unit type that the player may recruit.
Definition types.hpp:43
Definitions for the interface to Wesnoth Markup Language (WML).
const config * cfg
Contains functions for cleanly handling SDL input.
Standard logging facilities (interface).
config luaW_checkconfig(lua_State *L, int index)
Converts an optional table or vconfig to a config object.
bool luaW_toboolean(lua_State *L, int n)
bool luaW_totstring(lua_State *L, int index, t_string &str)
Converts a scalar to a translatable string.
bool luaW_toconfig(lua_State *L, int index, config &cfg)
Converts an optional table or vconfig to a config object.
t_string luaW_checktstring(lua_State *L, int index)
Converts a scalar to a translatable string.
static lg::log_domain log_scripting_lua("scripting/lua")
unit_ptr luaW_tounit_ptr(lua_State *L, int index, bool only_on_map)
Similar to luaW_tounit but returns a unit_ptr; use this instead of luaW_tounit when using an api that...
Definition lua_unit.cpp:152
lua_unit * luaW_pushunit(lua_State *L, Args... args)
Definition lua_unit.hpp:116
const unit_type * luaW_tounittype(lua_State *L, int idx)
Test if a stack element is a unit type, and return it if so.
void luaW_pushunittype(lua_State *L, const unit_type &ut)
Create a lua object containing a reference to a unittype, and a metatable to access the properties.
int intf_show_dialog(lua_State *L)
Displays a window.
int show_wml_message(const std::string &title, const std::string &message, const wml_message_portrait *left, const wml_message_portrait *right, const wml_message_options &options, const wml_message_input &input)
Helper function to show a portrait.
bool add_single_widget_definition(const std::string &widget_type, const std::string &definition_id, const config &cfg)
Adds a widget definition to the default GUI.
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
void switch_theme(const std::string &theme_id)
Set and activate the given gui2 theme.
Definition gui.cpp:149
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition message.cpp:148
@ OK
Dialog was closed with the OK button.
Definition retval.hpp:35
void show_help(const std::string &show_topic)
Open the help browser.
Definition help.cpp:84
Functions to load and save images from/to disk.
void set_functions(lua_State *L, const std::vector< lua_cpp::Reg > &functions)
Analogous to lua_setfuncs, it registers a collection of function wrapper objects into a table,...
int show_popup_dialog(lua_State *L)
Displays a popup message.
Definition lua_gui2.cpp:156
int intf_show_recall_dialog(lua_State *L)
Definition lua_gui2.cpp:318
int intf_show_recruit_dialog(lua_State *L)
Definition lua_gui2.cpp:262
static int show_help(lua_State *L)
Definition lua_gui2.cpp:372
int luaW_open(lua_State *L)
Definition lua_gui2.cpp:398
int show_lua_console(lua_State *, lua_kernel_base *lk)
Definition lua_gui2.cpp:250
int show_story(lua_State *L)
Displays a story screen.
Definition lua_gui2.cpp:170
int switch_theme(lua_State *L)
Changes the current ui(gui2) theme.
Definition lua_gui2.cpp:181
int show_message_box(lua_State *L)
Displays a simple message box.
Definition lua_gui2.cpp:218
int show_gamestate_inspector(const std::string &name, const game_data &data, const game_state &state)
Definition lua_gui2.cpp:256
int intf_add_widget_definition(lua_State *L)
Definition lua_gui2.cpp:384
int show_message_dialog(lua_State *L)
Displays a message window.
Definition lua_gui2.cpp:67
int show_menu(lua_State *L)
Displays a popup menu at the current mouse position Best used from a [set_menu_item],...
Definition lua_gui2.cpp:193
int luaW_open(lua_State *L)
point get_mouse_location()
Returns the current mouse location in draw space.
Definition input.cpp:49
std::string_view data
Definition picture.cpp:188
std::shared_ptr< const unit > unit_const_ptr
Definition ptr.hpp:27
Parameter pack for message text input options.
unsigned maximum_length
The maximum length of the text.
std::string text
The initial text value.
bool text_input_was_specified
True when [text_input] appeared in [message].
std::string caption
The caption for the optional input text box.
Parameter pack for message list input options.
Parameter pack for message portrait.
An abstract description of a rectangle with integer coordinates.
Definition rect.hpp:49
static map_location::direction n
#define i
#define e
#define c