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