The Battle for Wesnoth  1.19.8+dev
lua_gui2.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2024
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) {
284 
285  idx++;
286  const config& cfg = luaW_checkconfig(L, idx);
287  if (!cfg.empty()) {
288  if (!cfg["title"].empty()) {
289  dlg->set_title(cfg["title"]);
290  }
291 
292  if (!cfg["ok_label"].empty()) {
293  dlg->set_ok_label(cfg["ok_label"]);
294  }
295 
296  if (!cfg["cancel_label"].empty()) {
297  dlg->set_cancel_label(cfg["cancel_label"]);
298  }
299 
300  if (!cfg["help_topic"].empty()) {
301  dlg->set_help_topic(cfg["help_topic"]);
302  }
303  }
304 
305  if(dlg->show() && dlg->is_selected()) {
306  luaW_pushunittype(L, *types[dlg->get_selected_index()]);
307  return 1;
308  }
309  } else {
310  ERR_LUA << "Unable to show recruit dialog";
311  }
312 
313  return 0;
314 }
315 
316 
317 int intf_show_recall_dialog(lua_State* L)
318 {
319  int idx = 1;
320  const size_t len = lua_rawlen(L, idx);
321  if (!lua_istable(L, idx)) {
322  return luaL_error(L, "List of units not specified!");
323  }
324 
325  std::vector<unit_const_ptr> units;
326  units.reserve(len);
327  for (size_t i = 1; i <= len; i++) {
328  lua_rawgeti(L, idx, i);
330  if (u) {
331  units.push_back(u);
332  }
333  lua_pop(L, idx);
334  }
335 
336  const display* disp = display::get_singleton();
337  if (!units.empty() && disp != nullptr) {
339 
340  idx++;
341  const config& cfg = luaW_checkconfig(L, idx);
342  if (!cfg.empty()) {
343  if (!cfg["title"].empty()) {
344  dlg->set_title(cfg["title"]);
345  }
346 
347  if (!cfg["ok_label"].empty()) {
348  dlg->set_ok_label(cfg["ok_label"]);
349  }
350 
351  if (!cfg["cancel_label"].empty()) {
352  dlg->set_cancel_label(cfg["cancel_label"]);
353  }
354 
355  if (!cfg["help_topic"].empty()) {
356  dlg->set_help_topic(cfg["help_topic"]);
357  }
358  }
359 
360  if(dlg->show() && dlg->is_selected()) {
361  luaW_pushunit(L, units[dlg->get_selected_index()]->underlying_id());
362  return 1;
363  }
364  } else {
365  ERR_LUA << "Unable to show recall dialog";
366  }
367 
368  return 0;
369 }
370 
371 static int show_help(lua_State *L)
372 {
373  help::show_help(luaL_checkstring(L, 1));
374  return 0;
375 }
376 
377 /**
378  * - Arg 1: string, widget type
379  * - Arg 3: string, id
380  * - Arg 3: config,
381  */
382 
383 int intf_add_widget_definition(lua_State* L)
384 {
385  std::string type = luaL_checkstring(L, 1);
386  std::string id = luaL_checkstring(L, 2);
387  try {
389  lua_kernel_base::get_lua_kernel<lua_kernel_base>(L).add_widget_definition(type, id);
390  }
391  } catch(const std::invalid_argument& e) {
392  return luaL_argerror(L, 1, e.what());
393  }
394  return 0;
395 }
396 
397 int luaW_open(lua_State* L)
398 {
399  auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
400  lk.add_log("Adding gui module...\n");
401  static luaL_Reg const gui_callbacks[] = {
402  { "show_menu", &show_menu },
403  { "show_narration", &show_message_dialog },
404  { "show_popup", &show_popup_dialog },
405  { "show_story", &show_story },
406  { "show_prompt", &show_message_box },
407  { "show_help", &show_help },
408  { "switch_theme", &switch_theme },
409  { "add_widget_definition", &intf_add_widget_definition },
410  { "show_dialog", &intf_show_dialog },
411  { nullptr, nullptr },
412  };
413  std::vector<lua_cpp::Reg> const cpp_gui_callbacks {
414  {"show_lua_console", std::bind(&lua_kernel_base::intf_show_lua_console, &lk, std::placeholders::_1)},
415  {nullptr, nullptr}
416  };
417  lua_newtable(L);
418  luaL_setfuncs(L, gui_callbacks, 0);
419  lua_cpp::set_functions(L, cpp_gui_callbacks);
420 
421  lua_pushstring(L, "widget");
423  lua_rawset(L, -3);
424 
425  return 1;
426 }
427 
428 } // end namespace lua_gui2
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:849
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:338
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, 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:317
int intf_show_recruit_dialog(lua_State *L)
Definition: lua_gui2.cpp:262
static int show_help(lua_State *L)
Definition: lua_gui2.cpp:371
int luaW_open(lua_State *L)
Definition: lua_gui2.cpp:397
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:383
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