1 /*
2  * Copyright (C) 2006-2020 by the Widelands Development Team
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  *
18  */
19 
20 #include "scripting/lua_ui.h"
21 
22 #include <SDL_mouse.h>
23 
24 #include "base/macros.h"
25 #include "logic/game_controller.h"
26 #include "logic/player.h"
27 #include "scripting/globals.h"
28 #include "scripting/lua_map.h"
29 #include "scripting/luna.h"
30 #include "wui/interactive_player.h"
31 
32 namespace LuaUi {
33 
34 /* RST
35 :mod:`wl.ui`
36 =============
37 
38 .. module:: wl.ui
39    :synopsis: Provides access on user interface. Mainly for tutorials and
40       debugging.
41 
42 .. moduleauthor:: The Widelands development team
43 
44 .. currentmodule:: wl.ui
45 
46 .. Note::
47 
48    The objects inside this module can not be persisted. That is if the player
49    tries to save the game while any of these objects are assigned to variables,
50    the game will crash. So when using these, make sure that you only create
51    objects for a short amount of time where the user can't take control to do
52    something else.
53 
54 */
55 
56 /*
57  * ========================================================================
58  *                         MODULE CLASSES
59  * ========================================================================
60  */
61 
62 /* RST
63 Module Classes
64 ^^^^^^^^^^^^^^^^
65 
66 */
67 
68 /* RST
69 Panel
70 -----
71 
72 .. class:: Panel
73 
74    The Panel is the most basic ui class. Each ui element is a panel.
75 */
76 const char LuaPanel::className[] = "Panel";
77 const PropertyType<LuaPanel> LuaPanel::Properties[] = {
78    PROP_RO(LuaPanel, buttons), PROP_RO(LuaPanel, dropdowns),  PROP_RO(LuaPanel, tabs),
79    PROP_RO(LuaPanel, windows), PROP_RW(LuaPanel, position_x), PROP_RW(LuaPanel, position_y),
80    PROP_RW(LuaPanel, width),   PROP_RW(LuaPanel, height),     {nullptr, nullptr, nullptr},
81 };
82 const MethodType<LuaPanel> LuaPanel::Methods[] = {
83    METHOD(LuaPanel, get_descendant_position), {nullptr, nullptr},
84 };
85 
86 // Look for all descendant panels of class P and add the corresponding Lua version to the currently
87 // active Lua table. Class P needs to be a NamedPanel.
88 template <class P, class LuaP>
put_all_visible_panels_into_table(lua_State * L,UI::Panel * g)89 static void put_all_visible_panels_into_table(lua_State* L, UI::Panel* g) {
90 	if (g == nullptr) {
91 		return;
92 	}
93 
94 	for (UI::Panel* child = g->get_first_child(); child; child = child->get_next_sibling()) {
95 		put_all_visible_panels_into_table<P, LuaP>(L, child);
96 
97 		if (upcast(P, specific_panel, child)) {
98 			if (specific_panel->is_visible()) {
99 				lua_pushstring(L, specific_panel->get_name());
100 				to_lua<LuaP>(L, new LuaP(specific_panel));
101 				lua_rawset(L, -3);
102 			}
103 		}
104 	}
105 }
106 
107 /*
108  * Properties
109  */
110 
111 /* RST
112    .. attribute:: name
113 
114       (RO) The name of this panel
115 */
116 
117 /* RST
118    .. attribute:: buttons
119 
120       (RO) An :class:`array` of all visible buttons inside this Panel.
121 */
get_buttons(lua_State * L)122 int LuaPanel::get_buttons(lua_State* L) {
123 	assert(panel_);
124 
125 	lua_newtable(L);
126 	put_all_visible_panels_into_table<UI::Button, LuaButton>(L, panel_);
127 
128 	return 1;
129 }
130 
131 /* RST
132    .. attribute:: dropdowns
133 
134       (RO) An :class:`array` of all visible dropdowns inside this Panel.
135 */
get_dropdowns(lua_State * L)136 int LuaPanel::get_dropdowns(lua_State* L) {
137 	assert(panel_);
138 
139 	lua_newtable(L);
140 	put_all_visible_panels_into_table<UI::BaseDropdown, LuaDropdown>(L, panel_);
141 
142 	return 1;
143 }
144 
145 /* RST
146    .. attribute:: tabs
147 
148       (RO) An :class:`array` of all visible tabs inside this Panel.
149 */
put_all_tabs_into_table(lua_State * L,UI::Panel * g)150 static void put_all_tabs_into_table(lua_State* L, UI::Panel* g) {
151 	if (!g) {
152 		return;
153 	}
154 
155 	for (UI::Panel* f = g->get_first_child(); f; f = f->get_next_sibling()) {
156 		put_all_tabs_into_table(L, f);
157 
158 		if (upcast(UI::TabPanel, t, f)) {
159 			for (UI::Tab* tab : t->tabs()) {
160 				lua_pushstring(L, tab->get_name());
161 				to_lua<LuaTab>(L, new LuaTab(tab));
162 				lua_rawset(L, -3);
163 			}
164 		}
165 	}
166 }
get_tabs(lua_State * L)167 int LuaPanel::get_tabs(lua_State* L) {
168 	assert(panel_);
169 
170 	lua_newtable(L);
171 	put_all_tabs_into_table(L, panel_);
172 
173 	return 1;
174 }
175 
176 /* RST
177    .. attribute:: windows
178 
179       (RO) A :class:`array` of all currently open windows that are
180          children of this Panel.
181 */
get_windows(lua_State * L)182 int LuaPanel::get_windows(lua_State* L) {
183 	assert(panel_);
184 
185 	lua_newtable(L);
186 	put_all_visible_panels_into_table<UI::Window, LuaWindow>(L, panel_);
187 
188 	return 1;
189 }
190 
191 /* RST
192    .. attribute:: width, height
193 
194       (RW) The dimensions of this panel in pixels
195 */
get_width(lua_State * L)196 int LuaPanel::get_width(lua_State* L) {
197 	assert(panel_);
198 	lua_pushint32(L, panel_->get_w());
199 	return 1;
200 }
set_width(lua_State * L)201 int LuaPanel::set_width(lua_State* L) {
202 	assert(panel_);
203 	panel_->set_size(luaL_checkint32(L, -1), panel_->get_h());
204 	return 1;
205 }
get_height(lua_State * L)206 int LuaPanel::get_height(lua_State* L) {
207 	assert(panel_);
208 	lua_pushint32(L, panel_->get_h());
209 	return 1;
210 }
set_height(lua_State * L)211 int LuaPanel::set_height(lua_State* L) {
212 	assert(panel_);
213 	panel_->set_size(panel_->get_w(), luaL_checkint32(L, -1));
214 	return 1;
215 }
216 
217 /* RST
218    .. attribute:: position_x, position_y
219 
220       (RO) The top left pixel of the our inner canvas relative to the
221       parent's element inner canvas.
222 */
get_position_x(lua_State * L)223 int LuaPanel::get_position_x(lua_State* L) {
224 	assert(panel_);
225 	Vector2i p = panel_->to_parent(Vector2i::zero());
226 
227 	lua_pushint32(L, p.x);
228 	return 1;
229 }
set_position_x(lua_State * L)230 int LuaPanel::set_position_x(lua_State* L) {
231 	assert(panel_);
232 	Vector2i p(luaL_checkint32(L, -1) - panel_->get_lborder(), panel_->get_y());
233 	panel_->set_pos(p);
234 	return 1;
235 }
get_position_y(lua_State * L)236 int LuaPanel::get_position_y(lua_State* L) {
237 	assert(panel_);
238 	Vector2i p = panel_->to_parent(Vector2i::zero());
239 
240 	lua_pushint32(L, p.y);
241 	return 1;
242 }
set_position_y(lua_State * L)243 int LuaPanel::set_position_y(lua_State* L) {
244 	assert(panel_);
245 	Vector2i p(panel_->get_x(), luaL_checkint32(L, -1) - panel_->get_tborder());
246 	panel_->set_pos(p);
247 	return 1;
248 }
249 
250 /*
251  * Lua Functions
252  */
253 /* RST
254    .. method:: get_descendant_position(child)
255 
256       Get the child position relative to the inner canvas of this Panel in
257       pixels. Throws an error if child is not really a child.
258 
259       :arg child: children to get position for
260       :type child: :class:`Panel`
261 
262       :returns: x, y
263       :rtype: both are :class:`integers`
264 */
get_descendant_position(lua_State * L)265 int LuaPanel::get_descendant_position(lua_State* L) {
266 	assert(panel_);
267 
268 	UI::Panel* cur = (*get_base_user_class<LuaPanel>(L, 2))->panel_;
269 
270 	Vector2i cp = Vector2i::zero();
271 	while (cur && cur != panel_) {
272 		cp += cur->to_parent(Vector2i::zero());
273 		cur = cur->get_parent();
274 	}
275 
276 	if (!cur) {
277 		report_error(L, "Widget is not a descendant!");
278 	}
279 
280 	lua_pushint32(L, cp.x);
281 	lua_pushint32(L, cp.y);
282 	return 2;
283 }
284 
285 /*
286  * C Functions
287  */
288 
289 /* RST
290 Button
291 ------
292 
293 .. class:: Button
294 
295    Child of: :class:`Panel`
296 
297    This represents a simple push button.
298 */
299 const char LuaButton::className[] = "Button";
300 const MethodType<LuaButton> LuaButton::Methods[] = {
301    METHOD(LuaButton, press), METHOD(LuaButton, click), {nullptr, nullptr},
302 };
303 const PropertyType<LuaButton> LuaButton::Properties[] = {
304    PROP_RO(LuaButton, name), {nullptr, nullptr, nullptr},
305 };
306 
307 /*
308  * Properties
309  */
310 
311 // Documented in parent Class
get_name(lua_State * L)312 int LuaButton::get_name(lua_State* L) {
313 	lua_pushstring(L, get()->get_name());
314 	return 1;
315 }
316 
317 /*
318  * Lua Functions
319  */
320 /* RST
321    .. method:: press
322 
323       Press and hold this button. This is mainly to visualize a pressing
324       event in tutorials
325 */
press(lua_State *)326 int LuaButton::press(lua_State* /* L */) {
327 	log("Pressing button '%s'\n", get()->get_name().c_str());
328 	get()->handle_mousein(true);
329 	get()->handle_mousepress(SDL_BUTTON_LEFT, 1, 1);
330 	return 0;
331 }
332 /* RST
333    .. method:: click
334 
335       Click this button just as if the user would have moused over and clicked
336       it.
337 */
click(lua_State *)338 int LuaButton::click(lua_State* /* L */) {
339 	log("Clicking button '%s'\n", get()->get_name().c_str());
340 	get()->handle_mousein(true);
341 	get()->handle_mousepress(SDL_BUTTON_LEFT, 1, 1);
342 	get()->handle_mouserelease(SDL_BUTTON_LEFT, 1, 1);
343 	return 0;
344 }
345 
346 /*
347  * C Functions
348  */
349 
350 /* RST
351 Dropdown
352 --------
353 
354 .. class:: Dropdown
355 
356    Child of: :class:`Panel`
357 
358    This represents a dropdown menu.
359 */
360 const char LuaDropdown::className[] = "Dropdown";
361 const MethodType<LuaDropdown> LuaDropdown::Methods[] = {
362    METHOD(LuaDropdown, open),
363    METHOD(LuaDropdown, highlight_item),
364    METHOD(LuaDropdown, select),
365    {nullptr, nullptr},
366 };
367 const PropertyType<LuaDropdown> LuaDropdown::Properties[] = {
368    PROP_RO(LuaDropdown, name), PROP_RO(LuaDropdown, no_of_items), {nullptr, nullptr, nullptr},
369 };
370 
371 /*
372  * Properties
373  */
374 
375 // Documented in parent Class
get_name(lua_State * L)376 int LuaDropdown::get_name(lua_State* L) {
377 	lua_pushstring(L, get()->get_name());
378 	return 1;
379 }
380 
381 /* RST
382    .. attribute:: no_of_items
383 
384       (RO) The number of items his dropdown has.
385 */
get_no_of_items(lua_State * L)386 int LuaDropdown::get_no_of_items(lua_State* L) {
387 	lua_pushinteger(L, get()->size());
388 	return 1;
389 }
390 
391 /*
392  * Lua Functions
393  */
394 /* RST
395    .. method:: open
396 
397       Open this dropdown menu.
398 */
open(lua_State *)399 int LuaDropdown::open(lua_State* /* L */) {
400 	log("Opening dropdown '%s'\n", get()->get_name().c_str());
401 	get()->set_list_visibility(true);
402 	return 0;
403 }
404 
405 /* RST
406    .. method:: highlight_item(index)
407 
408       :arg index: the index of the item to highlight, starting from ``1``
409       :type index: :class:`integer`
410 
411       Highlights an item in this dropdown without triggering a selection.
412 */
highlight_item(lua_State * L)413 int LuaDropdown::highlight_item(lua_State* L) {
414 	unsigned int desired_item = luaL_checkuint32(L, -1);
415 	if (desired_item < 1 || desired_item > get()->size()) {
416 		report_error(L, "Attempted to highlight item %d on dropdown '%s'. Avaliable range for this "
417 		                "dropdown is 1-%d.",
418 		             desired_item, get()->get_name().c_str(), get()->size());
419 	}
420 	log("Highlighting item %d in dropdown '%s'\n", desired_item, get()->get_name().c_str());
421 	// Open the dropdown
422 	get()->set_list_visibility(true);
423 
424 	SDL_Keysym code;
425 	// Ensure that we're at the top
426 	code.sym = SDLK_UP;
427 	for (size_t i = 1; i < get()->size(); ++i) {
428 		get()->handle_key(true, code);
429 	}
430 	// Press arrow down until the desired item is highlighted
431 	code.sym = SDLK_DOWN;
432 	for (size_t i = 1; i < desired_item; ++i) {
433 		get()->handle_key(true, code);
434 	}
435 	return 0;
436 }
437 
438 /* RST
439    .. method:: select()
440 
441       Selects the currently highlighted item in this dropdown.
442 */
select(lua_State *)443 int LuaDropdown::select(lua_State* /* L */) {
444 	log("Selecting current item in dropdown '%s'\n", get()->get_name().c_str());
445 	SDL_Keysym code;
446 	code.sym = SDLK_RETURN;
447 	get()->handle_key(true, code);
448 	return 0;
449 }
450 
451 /*
452  * C Functions
453  */
454 
455 /* RST
456 Tab
457 ------
458 
459 .. class:: Tab
460 
461    Child of: :class:`Panel`
462 
463    A tab button.
464 */
465 const char LuaTab::className[] = "Tab";
466 const MethodType<LuaTab> LuaTab::Methods[] = {
467    METHOD(LuaTab, click), {nullptr, nullptr},
468 };
469 const PropertyType<LuaTab> LuaTab::Properties[] = {
470    PROP_RO(LuaTab, name), PROP_RO(LuaTab, active), {nullptr, nullptr, nullptr},
471 };
472 
473 /*
474  * Properties
475  */
476 
477 // Documented in parent Class
get_name(lua_State * L)478 int LuaTab::get_name(lua_State* L) {
479 	lua_pushstring(L, get()->get_name());
480 	return 1;
481 }
482 
483 /* RST
484    .. attribute:: active
485 
486       (RO) Is this the currently active tab in this window?
487 */
get_active(lua_State * L)488 int LuaTab::get_active(lua_State* L) {
489 	lua_pushboolean(L, get()->active());
490 	return 1;
491 }
492 
493 /*
494  * Lua Functions
495  */
496 /* RST
497    .. method:: click
498 
499       Click this tab making it the active one.
500 */
click(lua_State *)501 int LuaTab::click(lua_State* /* L */) {
502 	log("Clicking tab '%s'\n", get()->get_name().c_str());
503 	get()->activate();
504 	return 0;
505 }
506 
507 /*
508  * C Functions
509  */
510 
511 /* RST
512 Window
513 ------
514 
515 .. class:: Window
516 
517    Child of: :class:`Panel`
518 
519    This represents a Window.
520 */
521 const char LuaWindow::className[] = "Window";
522 const MethodType<LuaWindow> LuaWindow::Methods[] = {
523    METHOD(LuaWindow, close), {nullptr, nullptr},
524 };
525 const PropertyType<LuaWindow> LuaWindow::Properties[] = {
526    PROP_RO(LuaWindow, name), {nullptr, nullptr, nullptr},
527 };
528 
529 /*
530  * Properties
531  */
532 
533 // Documented in parent Class
get_name(lua_State * L)534 int LuaWindow::get_name(lua_State* L) {
535 	lua_pushstring(L, get()->get_name());
536 	return 1;
537 }
538 
539 /*
540  * Lua Functions
541  */
542 
543 /* RST
544    .. method:: close
545 
546       Closes this window. This invalidates this Object, do
547       not use it any longer.
548 */
close(lua_State *)549 int LuaWindow::close(lua_State* /* L */) {
550 	log("Closing window '%s'\n", get()->get_name().c_str());
551 	delete panel_;
552 	panel_ = nullptr;
553 	return 0;
554 }
555 
556 /*
557  * C Functions
558  */
559 
560 /* RST
561 MapView
562 -------
563 
564 .. class:: MapView
565 
566    Child of :class:`Panel`
567 
568    The map view is the main widget and the root of all panels. It is the big
569    view of the map that is visible at all times while playing.
570 */
571 const char LuaMapView::className[] = "MapView";
572 const MethodType<LuaMapView> LuaMapView::Methods[] = {
573    METHOD(LuaMapView, click),
574    METHOD(LuaMapView, start_road_building),
575    METHOD(LuaMapView, abort_road_building),
576    METHOD(LuaMapView, close),
577    METHOD(LuaMapView, scroll_to_field),
578    METHOD(LuaMapView, scroll_to_map_pixel),
579    METHOD(LuaMapView, is_visible),
580    METHOD(LuaMapView, mouse_to_field),
581    METHOD(LuaMapView, mouse_to_pixel),
582    {nullptr, nullptr},
583 };
584 const PropertyType<LuaMapView> LuaMapView::Properties[] = {
585    PROP_RO(LuaMapView, average_fps),  PROP_RO(LuaMapView, center_map_pixel),
586    PROP_RW(LuaMapView, buildhelp),    PROP_RW(LuaMapView, census),
587    PROP_RW(LuaMapView, statistics),   PROP_RO(LuaMapView, is_building_road),
588    PROP_RO(LuaMapView, is_animating), {nullptr, nullptr, nullptr},
589 };
590 
LuaMapView(lua_State * L)591 LuaMapView::LuaMapView(lua_State* L) : LuaPanel(get_egbase(L).get_ibase()) {
592 }
593 
__unpersist(lua_State * L)594 void LuaMapView::__unpersist(lua_State* L) {
595 	Widelands::Game& game = get_game(L);
596 	panel_ = game.get_ibase();
597 }
598 
599 /*
600  * Properties
601  */
602 /* RST
603    .. attribute:: average_fps
604 
605       (RO) The average frames per second that the user interface is being drawn at.
606 */
get_average_fps(lua_State * L)607 int LuaMapView::get_average_fps(lua_State* L) {
608 	lua_pushdouble(L, get()->average_fps());
609 	return 1;
610 }
611 /* RST
612    .. attribute:: center_map_pixel
613 
614       (RO) The map position (in pixels) that the center pixel of this map view
615       currently sees. This is a table containing 'x', 'y'.
616 */
get_center_map_pixel(lua_State * L)617 int LuaMapView::get_center_map_pixel(lua_State* L) {
618 	const Vector2f center = get()->map_view()->view_area().rect().center();
619 	lua_newtable(L);
620 
621 	lua_pushstring(L, "x");
622 	lua_pushdouble(L, center.x);
623 	lua_rawset(L, -3);
624 
625 	lua_pushstring(L, "y");
626 	lua_pushdouble(L, center.y);
627 	lua_rawset(L, -3);
628 	return 1;
629 }
630 
631 /* RST
632    .. attribute:: buildhelp
633 
634       (RW) True if the buildhelp is show, false otherwise.
635 */
get_buildhelp(lua_State * L)636 int LuaMapView::get_buildhelp(lua_State* L) {
637 	lua_pushboolean(L, get()->buildhelp());
638 	return 1;
639 }
set_buildhelp(lua_State * L)640 int LuaMapView::set_buildhelp(lua_State* L) {
641 	get()->show_buildhelp(luaL_checkboolean(L, -1));
642 	return 0;
643 }
644 
645 /* RST
646    .. attribute:: census
647 
648       (RW) True if the census strings are shown on buildings, false otherwise
649 */
get_census(lua_State * L)650 int LuaMapView::get_census(lua_State* L) {
651 	lua_pushboolean(L, get()->get_display_flag(InteractiveBase::dfShowCensus));
652 	return 1;
653 }
set_census(lua_State * L)654 int LuaMapView::set_census(lua_State* L) {
655 	get()->set_display_flag(InteractiveBase::dfShowCensus, luaL_checkboolean(L, -1));
656 	return 0;
657 }
658 
659 /* RST
660    .. attribute:: statistics
661 
662       (RW) True if the statistics strings are shown on buildings, false
663       otherwise
664 */
get_statistics(lua_State * L)665 int LuaMapView::get_statistics(lua_State* L) {
666 	lua_pushboolean(L, get()->get_display_flag(InteractiveBase::dfShowStatistics));
667 	return 1;
668 }
set_statistics(lua_State * L)669 int LuaMapView::set_statistics(lua_State* L) {
670 	get()->set_display_flag(InteractiveBase::dfShowStatistics, luaL_checkboolean(L, -1));
671 	return 0;
672 }
673 
674 /* RST
675    .. attribute:: is_building_road
676 
677       (RO) Is the player currently in road/waterway building mode?
678 */
get_is_building_road(lua_State * L)679 int LuaMapView::get_is_building_road(lua_State* L) {
680 	lua_pushboolean(L, get()->in_road_building_mode());
681 	return 1;
682 }
683 
684 /* RST
685    .. attribute:: is_animating
686 
687       (RO) True if this MapView is currently panning or zooming.
688 */
get_is_animating(lua_State * L)689 int LuaMapView::get_is_animating(lua_State* L) {
690 	lua_pushboolean(L, get()->map_view()->is_animating());
691 	return 1;
692 }
693 /*
694  * Lua Functions
695  */
696 
697 /* RST
698    .. method:: click(field)
699 
700       Jumps the mouse onto a field and clicks it just like the user would
701       have.
702 
703       :arg field: the field to click on
704       :type field: :class:`wl.map.Field`
705 */
click(lua_State * L)706 int LuaMapView::click(lua_State* L) {
707 	const auto field = *get_user_class<LuaMaps::LuaField>(L, 2);
708 	get()->map_view()->mouse_to_field(field->coords(), MapView::Transition::Jump);
709 
710 	// We fake the triangle here, since we only support clicking on Nodes from
711 	// Lua.
712 	Widelands::NodeAndTriangle<> node_and_triangle{
713 	   field->coords(), Widelands::TCoords<>(field->coords(), Widelands::TriangleIndex::D)};
714 	get()->map_view()->field_clicked(node_and_triangle);
715 	return 0;
716 }
717 
718 /* RST
719    .. method:: start_road_building(flag[, waterway = false])
720 
721       Enters the road building mode as if the player has clicked
722       the flag and chosen build road. It will also warp the mouse
723       to the given starting node. Throws an error if we are already in road
724       building mode.
725 
726       :arg flag: :class:`wl.map.Flag` object to start building from.
727       :arg waterway: if `true`, start building a waterway rather than a road
728 */
729 // UNTESTED
start_road_building(lua_State * L)730 int LuaMapView::start_road_building(lua_State* L) {
731 	InteractiveBase* me = get();
732 	if (me->in_road_building_mode()) {
733 		report_error(L, "Already building road!");
734 	}
735 
736 	Widelands::Coords starting_field =
737 	   (*get_user_class<LuaMaps::LuaFlag>(L, 2))->get(L, get_egbase(L))->get_position();
738 
739 	me->map_view()->mouse_to_field(starting_field, MapView::Transition::Jump);
740 	me->start_build_road(starting_field, me->get_player()->player_number(),
741 	                     lua_gettop(L) > 2 && luaL_checkboolean(L, 3) ? RoadBuildingType::kWaterway :
742 	                                                                    RoadBuildingType::kRoad);
743 
744 	return 0;
745 }
746 
747 /* RST
748    .. method:: abort_road_building
749 
750       If the player is currently in road building mode, this will cancel it.
751       If he wasn't, this will do nothing.
752 */
753 // UNTESTED
abort_road_building(lua_State *)754 int LuaMapView::abort_road_building(lua_State* /* L */) {
755 	InteractiveBase* me = get();
756 	if (me->in_road_building_mode()) {
757 		me->abort_build_road();
758 	}
759 	return 0;
760 }
761 
762 /* RST
763    .. method:: close
764 
765       Closes the MapView. Note that this is the equivalent as clicking on
766       the exit button in the game; that is the game will be exited.
767 
768       This is especially useful for automated testing of features and is for
769       example used in the widelands Lua test suite.
770 */
close(lua_State *)771 int LuaMapView::close(lua_State* /* l */) {
772 	get()->end_modal<UI::Panel::Returncodes>(UI::Panel::Returncodes::kBack);
773 	return 0;
774 }
775 
776 /* RST
777    .. method:: scroll_to_map_pixel(x, y)
778 
779       Starts an animation to center the view on top of the pixel (x, y) in map
780       pixel space. Use `is_animating` to check if the animation is still going
781       on.
782 
783       :arg x: x coordinate of the pixel
784       :type x: number
785       :arg y: y coordinate of the pixel
786       :type y: number
787 */
scroll_to_map_pixel(lua_State * L)788 int LuaMapView::scroll_to_map_pixel(lua_State* L) {
789 	Widelands::Game& game = get_game(L);
790 	// don't move view in replays
791 	if (game.game_controller()->get_game_type() == GameController::GameType::kReplay) {
792 		return 0;
793 	}
794 
795 	const Vector2f center(luaL_checkdouble(L, 2), luaL_checkdouble(L, 3));
796 	get()->map_view()->scroll_to_map_pixel(center, MapView::Transition::Smooth);
797 	return 0;
798 }
799 
800 /* RST
801    .. method:: scroll_to_field(field)
802 
803       Starts an animation to center the view on top of the 'field'. Use
804       `is_animating` to check if the animation is still going on.
805 
806       :arg field: the field to center on
807       :type field: :class:`wl.map.Field`
808 */
scroll_to_field(lua_State * L)809 int LuaMapView::scroll_to_field(lua_State* L) {
810 	get()->map_view()->scroll_to_field(
811 	   (*get_user_class<LuaMaps::LuaField>(L, 2))->coords(), MapView::Transition::Smooth);
812 	return 0;
813 }
814 
815 /* RST
816    .. method:: is_visible(field)
817 
818       Returns `true` if `field` is currently visible in the map view.
819 
820       :arg field: the field
821       :type field: :class:`wl.map.Field`
822 */
is_visible(lua_State * L)823 int LuaMapView::is_visible(lua_State* L) {
824 	lua_pushboolean(L, get()->map_view()->view_area().contains(
825 	                      (*get_user_class<LuaMaps::LuaField>(L, 2))->coords()));
826 	return 1;
827 }
828 
829 /* RST
830    .. method:: mouse_to_pixel(x, y)
831 
832       Starts an animation to move the mouse onto the pixel (x, y) of this panel.
833       Use `is_animating` to check if the animation is still going on.
834 
835       :arg x: x coordinate of the pixel
836       :type x: number
837       :arg y: y coordinate of the pixel
838       :type y: number
839 */
mouse_to_pixel(lua_State * L)840 int LuaMapView::mouse_to_pixel(lua_State* L) {
841 	int x = luaL_checkint32(L, 2);
842 	int y = luaL_checkint32(L, 3);
843 	get()->map_view()->mouse_to_pixel(Vector2i(x, y), MapView::Transition::Smooth);
844 	return 0;
845 }
846 
847 /* RST
848    .. method:: mouse_to_field(field)
849 
850       Starts an animation to move the mouse onto the 'field'. If 'field' is not
851       visible on the screen currently, does nothing. Use `is_animating` to
852       check if the animation is still going on.
853 
854       :arg field: the field
855       :type field: :class:`wl.map.Field`
856 */
mouse_to_field(lua_State * L)857 int LuaMapView::mouse_to_field(lua_State* L) {
858 	get()->map_view()->mouse_to_field(
859 	   (*get_user_class<LuaMaps::LuaField>(L, 2))->coords(), MapView::Transition::Smooth);
860 	return 0;
861 }
862 
863 /*
864  * C Functions
865  */
866 
867 /*
868  * ========================================================================
869  *                            MODULE FUNCTIONS
870  * ========================================================================
871  */
872 
873 /* RST
874 .. function:: set_user_input_allowed(b)
875 
876    Allow or disallow user input. Be warned, setting this will make that
877    mouse movements and keyboard presses are completely ignored. Only
878    scripted stuff will still happen.
879 
880    :arg b: :const:`true` or :const:`false`
881    :type b: :class:`boolean`
882 */
L_set_user_input_allowed(lua_State * L)883 static int L_set_user_input_allowed(lua_State* L) {
884 	UI::Panel::set_allow_user_input(luaL_checkboolean(L, -1));
885 	return 0;
886 }
887 /* RST
888 .. method:: get_user_input_allowed
889 
890    Return the current state of this flag.
891 
892    :returns: :const:`true` or :const:`false`
893    :rtype: :class:`boolean`
894 */
L_get_user_input_allowed(lua_State * L)895 static int L_get_user_input_allowed(lua_State* L) {
896 	lua_pushboolean(L, UI::Panel::allow_user_input());
897 	return 1;
898 }
899 
900 const static struct luaL_Reg wlui[] = {{"set_user_input_allowed", &L_set_user_input_allowed},
901                                        {"get_user_input_allowed", &L_get_user_input_allowed},
902                                        {nullptr, nullptr}};
903 
luaopen_wlui(lua_State * L)904 void luaopen_wlui(lua_State* L) {
905 	lua_getglobal(L, "wl");   // S: wl_table
906 	lua_pushstring(L, "ui");  // S: wl_table "ui"
907 	luaL_newlib(L, wlui);     // S: wl_table "ui" wl.ui_table
908 	lua_settable(L, -3);      // S: wl_table
909 	lua_pop(L, 1);            // S:
910 
911 	register_class<LuaPanel>(L, "ui");
912 
913 	register_class<LuaButton>(L, "ui", true);
914 	add_parent<LuaButton, LuaPanel>(L);
915 	lua_pop(L, 1);  // Pop the meta table
916 
917 	register_class<LuaDropdown>(L, "ui", true);
918 	add_parent<LuaDropdown, LuaPanel>(L);
919 	lua_pop(L, 1);  // Pop the meta table
920 
921 	register_class<LuaTab>(L, "ui", true);
922 	add_parent<LuaTab, LuaPanel>(L);
923 	lua_pop(L, 1);  // Pop the meta table
924 
925 	register_class<LuaWindow>(L, "ui", true);
926 	add_parent<LuaWindow, LuaPanel>(L);
927 	lua_pop(L, 1);  // Pop the meta table
928 
929 	register_class<LuaMapView>(L, "ui", true);
930 	add_parent<LuaMapView, LuaPanel>(L);
931 	lua_pop(L, 1);  // Pop the meta table
932 }
933 }  // namespace LuaUi
934