The Battle for Wesnoth  1.19.5+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 "gui/gui.hpp"
26 #include "gui/dialogs/message.hpp"
27 #include "gui/widgets/retval.hpp"
28 #include "scripting/lua_widget_methods.hpp" //intf_show_dialog
29 
30 #include "config.hpp"
31 #include "log.hpp"
32 #include "scripting/lua_common.hpp"
35 #include "scripting/push_check.hpp"
36 #include "help/help.hpp"
37 #include "tstring.hpp"
38 #include "game_data.hpp"
39 #include "game_state.hpp"
40 #include "sdl/input.hpp" // get_mouse_state
41 
42 #include <functional>
43 #include "utils/optional_fwd.hpp"
44 
45 #include <vector>
46 
47 
48 static lg::log_domain log_scripting_lua("scripting/lua");
49 #define ERR_LUA LOG_STREAM(err, log_scripting_lua)
50 
51 namespace lua_gui2 {
52 
53 
54 /**
55  * Displays a message window
56  * - Arg 1: Table describing the window
57  * - Arg 2: List of options (nil or empty table - no options)
58  * - Arg 3: Text input specifications (nil or empty table - no text input)
59  * - Ret 1: option chosen (if no options: 0 if there's text input, -2 if escape pressed, else -1)
60  * - Ret 2: string entered (empty if none, nil if no text input)
61  */
62 int show_message_dialog(lua_State* L)
63 {
64  config txt_cfg;
65  const bool has_input = !lua_isnoneornil(L, 3) && luaW_toconfig(L, 3, txt_cfg) && !txt_cfg.empty();
66 
68  input.caption = txt_cfg["label"].str();
69  input.text = txt_cfg["text"].str();
70  input.maximum_length = txt_cfg["max_length"].to_int(256);
71  input.text_input_was_specified = has_input;
72 
74  if(!lua_isnoneornil(L, 2)) {
75  luaL_checktype(L, 2, LUA_TTABLE);
76  std::size_t n = lua_rawlen(L, 2);
77  for(std::size_t i = 1; i <= n; i++) {
78  lua_rawgeti(L, 2, i);
79  t_string short_opt;
80  config opt;
81  if(luaW_totstring(L, -1, short_opt)) {
82  opt["label"] = short_opt;
83  } else if(!luaW_toconfig(L, -1, opt)) {
84  std::ostringstream error;
85  error << "expected array of config and/or translatable strings, but index ";
86  error << i << " was a " << lua_typename(L, lua_type(L, -1));
87  return luaL_argerror(L, 2, error.str().c_str());
88  }
89  gui2::dialogs::wml_message_option option(opt["label"], opt["description"], opt["image"]);
90  if(opt["default"].to_bool(false)) {
91  options.chosen_option = i - 1;
92  }
93  options.option_list.push_back(option);
94  lua_pop(L, 1);
95  }
96  lua_getfield(L, 2, "default");
97  if(lua_isnumber(L, -1)) {
98  int i = lua_tointeger(L, -1);
99  if(i < 1 || std::size_t(i) > n) {
100  std::ostringstream error;
101  error << "default= key in options list is not a valid option index (1-" << n << ")";
102  return luaL_argerror(L, 2, error.str().c_str());
103  }
104  options.chosen_option = i - 1;
105  }
106  lua_pop(L, 1);
107  }
108 
109  const config& def_cfg = luaW_checkconfig(L, 1);
110  const std::string& title = def_cfg["title"];
111  const std::string& message = def_cfg["message"];
112 
113  using portrait = gui2::dialogs::wml_message_portrait;
114  std::unique_ptr<portrait> left;
115  std::unique_ptr<portrait> right;
116  const bool is_double = def_cfg.has_attribute("second_portrait");
117  const bool left_side = def_cfg["left_side"].to_bool(true);
118  if(is_double || left_side) {
119  left.reset(new portrait {def_cfg["portrait"], def_cfg["mirror"].to_bool(false)});
120  } else {
121  // This means right side only.
122  right.reset(new portrait {def_cfg["portrait"], def_cfg["mirror"].to_bool(false)});
123  }
124  if(is_double) {
125  right.reset(new portrait {def_cfg["second_portrait"], def_cfg["second_mirror"].to_bool(false)});
126  }
127 
128  int dlg_result = gui2::dialogs::show_wml_message(title, message, left.get(), right.get(), options, input);
129 
130  if(!has_input && options.option_list.empty()) {
131  lua_pushinteger(L, dlg_result);
132  } else {
133  lua_pushinteger(L, options.chosen_option + 1);
134  }
135 
136  if(has_input) {
137  lua_pushlstring(L, input.text.c_str(), input.text.length());
138  } else {
139  lua_pushnil(L);
140  }
141 
142  return 2;
143 }
144 
145 /**
146  * Displays a popup message
147  * - Arg 1: Title (allows Pango markup)
148  * - Arg 2: Message (allows Pango markup)
149  * - Arg 3: Image (optional)
150  */
151 int show_popup_dialog(lua_State *L) {
152  t_string title = luaW_checktstring(L, 1);
154  std::string image = lua_isnoneornil(L, 3) ? "" : luaL_checkstring(L, 3);
155 
156  gui2::show_transient_message(title, msg, image, true, true);
157  return 0;
158 }
159 
160 /**
161  * Displays a story screen
162  * - Arg 1: The story config
163  * - Arg 2: The default title
164  */
165 int show_story(lua_State* L) {
166  config story = luaW_checkconfig(L, 1);
167  t_string title = luaW_checktstring(L, 2);
169  return 0;
170 }
171 
172 /**
173  * Changes the current ui(gui2) theme
174  * - Arg 1: The id of the theme to switch to
175  */
176 int switch_theme(lua_State* L) {
177  std::string theme_id = luaL_checkstring(L, 1);
178  gui2::switch_theme(theme_id);
179  return 0;
180 }
181 
182 /**
183  * Displays a popup menu at the current mouse position
184  * Best used from a [set_menu_item], to show a submenu
185  * - Arg 1: Configs defining each item, with keys icon, image/label, second_label, tooltip
186  * - Args 2, 3: Initial selection (integer); whether to parse markup (boolean)
187  */
188 int show_menu(lua_State* L) {
189  std::vector<config> items = lua_check<std::vector<config>>(L, 1);
190  rect pos{ sdl::get_mouse_location(), {1, 1} };
191 
192  int initial = -1;
193  bool markup = false;
194  if(lua_isnumber(L, 2)) {
195  initial = lua_tointeger(L, 2) - 1;
196  markup = luaW_toboolean(L, 3);
197  } else if(lua_isnumber(L, 3)) {
198  initial = lua_tointeger(L, 3) - 1;
199  markup = luaW_toboolean(L, 2);
200  } else if(lua_isboolean(L, 2)) {
201  markup = luaW_toboolean(L, 2);
202  }
203 
204  gui2::dialogs::drop_down_menu menu(pos, items, initial, markup, false);
205  menu.show();
206  lua_pushinteger(L, menu.selected_item() + 1);
207  return 1;
208 }
209 
210 /**
211  * Displays a simple message box.
212  */
213 int show_message_box(lua_State* L) {
214  const t_string title = luaW_checktstring(L, 1), message = luaW_checktstring(L, 2);
215  std::string button = luaL_optstring(L, 3, "ok"), btn_style;
216  std::transform(button.begin(), button.end(), std::inserter(btn_style, btn_style.begin()), [](char c) { return std::tolower(c); });
217  bool markup = lua_isnoneornil(L, 3) ? luaW_toboolean(L, 3) : luaW_toboolean(L, 4);
218  using button_style = gui2::dialogs::message::button_style;
219  utils::optional<button_style> style;
220  if(btn_style.empty()) {
221  style = button_style::auto_close;
222  } else if(btn_style == "ok") {
223  style = button_style::ok_button;
224  } else if(btn_style == "close") {
225  style = button_style::close_button;
226  } else if(btn_style == "ok_cancel") {
227  style = button_style::ok_cancel_buttons;
228  } else if(btn_style == "cancel") {
229  style = button_style::cancel_button;
230  } else if(btn_style == "yes_no") {
231  style = button_style::yes_no_buttons;
232  }
233  if(style) {
234  int result = gui2::show_message(title, message, *style, markup, markup);
235  if(style == button_style::ok_cancel_buttons || style == button_style::yes_no_buttons) {
236  lua_pushboolean(L, result == gui2::retval::OK);
237  return 1;
238  }
239  } else {
240  gui2::show_message(title, message, button, false, markup, markup);
241  }
242  return 0;
243 }
244 
245 int show_lua_console(lua_State* /*L*/, lua_kernel_base* lk)
246 {
248  return 0;
249 }
250 
251 int show_gamestate_inspector(const std::string& name, const game_data& data, const game_state& state)
252 {
253  gui2::dialogs::gamestate_inspector::display(data.get_variables(), *state.events_manager_, state.board_, name);
254  return 0;
255 }
256 
257 static int show_help(lua_State *L)
258 {
259  help::show_help(luaL_checkstring(L, 1));
260  return 0;
261 }
262 
263 /**
264  * - Arg 1: string, widget type
265  * - Arg 3: string, id
266  * - Arg 3: config,
267  */
268 
269 int intf_add_widget_definition(lua_State* L)
270 {
271  std::string type = luaL_checkstring(L, 1);
272  std::string id = luaL_checkstring(L, 2);
273  try {
275  lua_kernel_base::get_lua_kernel<lua_kernel_base>(L).add_widget_definition(type, id);
276  }
277  } catch(const std::invalid_argument& e) {
278  return luaL_argerror(L, 1, e.what());
279  }
280  return 0;
281 }
282 
283 int luaW_open(lua_State* L)
284 {
285  auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
286  lk.add_log("Adding gui module...\n");
287  static luaL_Reg const gui_callbacks[] = {
288  { "show_menu", &show_menu },
289  { "show_narration", &show_message_dialog },
290  { "show_popup", &show_popup_dialog },
291  { "show_story", &show_story },
292  { "show_prompt", &show_message_box },
293  { "show_help", &show_help },
294  { "switch_theme", &switch_theme },
295  { "add_widget_definition", &intf_add_widget_definition },
296  { "show_dialog", &intf_show_dialog },
297  { nullptr, nullptr },
298  };
299  std::vector<lua_cpp::Reg> const cpp_gui_callbacks {
300  {"show_lua_console", std::bind(&lua_kernel_base::intf_show_lua_console, &lk, std::placeholders::_1)},
301  {nullptr, nullptr}
302  };
303  lua_newtable(L);
304  luaL_setfuncs(L, gui_callbacks, 0);
305  lua_cpp::set_functions(L, cpp_gui_callbacks);
306 
307  lua_pushstring(L, "widget");
309  lua_rawset(L, -3);
310 
311  return 1;
312 }
313 
314 } // end namespace lua_gui2
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
bool has_attribute(config_key_type key) const
Definition: config.cpp:157
bool empty() const
Definition: config.cpp:849
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)
Helper class for message options.
Definition: wml_message.hpp:26
int intf_show_lua_console(lua_State *L)
Definitions for the interface to Wesnoth Markup Language (WML).
std::size_t i
Definition: function.cpp:1023
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.
Definition: lua_common.cpp:927
bool luaW_toboolean(lua_State *L, int n)
Definition: lua_common.cpp:998
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:849
t_string luaW_checktstring(lua_State *L, int index)
Converts a scalar to a translatable string.
Definition: lua_common.cpp:635
static lg::log_domain log_scripting_lua("scripting/lua")
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:151
static int show_help(lua_State *L)
Definition: lua_gui2.cpp:257
int luaW_open(lua_State *L)
Definition: lua_gui2.cpp:283
int show_lua_console(lua_State *, lua_kernel_base *lk)
Definition: lua_gui2.cpp:245
int show_story(lua_State *L)
Displays a story screen.
Definition: lua_gui2.cpp:165
int switch_theme(lua_State *L)
Changes the current ui(gui2) theme.
Definition: lua_gui2.cpp:176
int show_message_box(lua_State *L)
Displays a simple message box.
Definition: lua_gui2.cpp:213
int show_gamestate_inspector(const std::string &name, const game_data &data, const game_state &state)
Definition: lua_gui2.cpp:251
int intf_add_widget_definition(lua_State *L)
Definition: lua_gui2.cpp:269
int show_message_dialog(lua_State *L)
Displays a message window.
Definition: lua_gui2.cpp:62
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:188
int luaW_open(lua_State *L)
point get_mouse_location()
Returns the current mouse location in draw space.
Definition: input.cpp:54
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
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:47
mock_char c
static map_location::direction n
#define e