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