The Battle for Wesnoth  1.19.9+dev
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 
16 #include "scripting/lua_gui2.hpp"
17 
18 #include "game_display.hpp"
19 #include "gui/gui.hpp"
24 #include "gui/dialogs/message.hpp"
29 #include "gui/widgets/retval.hpp"
30 #include "scripting/lua_unit.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"
38 #include "scripting/lua_common.hpp"
41 #include "scripting/push_check.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"
47 #include "utils/optional_fwd.hpp"
48 
49 #include <functional>
50 #include <vector>
51 
52 
53 static lg::log_domain log_scripting_lua("scripting/lua");
54 #define ERR_LUA LOG_STREAM(err, log_scripting_lua)
55 
56 namespace 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  */
67 int 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 
118  using portrait = gui2::dialogs::wml_message_portrait;
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  */
156 int show_popup_dialog(lua_State *L) {
157  t_string title = luaW_checktstring(L, 1);
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  */
170 int 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  */
181 int 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  */
193 int 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  */
218 int 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 
250 int show_lua_console(lua_State* /*L*/, lua_kernel_base* lk)
251 {
253  return 0;
254 }
255 
256 int 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 
262 int intf_show_recruit_dialog(lua_State* L)
263 {
264  int idx = 1;
265  const 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 (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 
318 int intf_show_recall_dialog(lua_State* L)
319 {
320  int idx = 1;
321  const 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 (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 
372 static 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 
384 int intf_add_widget_definition(lua_State* L)
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 
398 int 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
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
bool has_attribute(config_key_type key) const
Definition: config.cpp:157
bool empty() const
Definition: config.cpp:845
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:97
const team & playing_team() const
Definition: display.cpp:339
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:111
game_board board_
Definition: game_state.hpp:44
const std::unique_ptr< game_events::manager > events_manager_
Definition: game_state.hpp:50
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.
Definition: wml_message.hpp:26
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).
std::size_t i
Definition: function.cpp:1029
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.
Definition: lua_common.cpp:610
bool luaW_toconfig(lua_State *L, int index, config &cfg)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:934
t_string luaW_checktstring(lua_State *L, int index)
Converts a scalar to a translatable string.
Definition: lua_common.cpp:635
#define ERR_LUA
Definition: lua_gui2.cpp:54
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 switch_theme(const std::string &current_theme)
Set and activate the given gui2 theme.
Definition: gui.cpp:135
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 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, show topic with id show_topic.
Definition: help.cpp:140
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:54
constexpr auto transform
Definition: ranges.hpp:41
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
std::string_view data
Definition: picture.cpp:178
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
mock_char c
static map_location::direction n
#define e