The Battle for Wesnoth  1.19.15+dev
window.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2007 - 2025
3  by Mark de Wever <koraq@xs4all.nl>
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 /**
17  * @file
18  * Implementation of window.hpp.
19  */
20 
21 #define GETTEXT_DOMAIN "wesnoth-lib"
22 
24 
25 #include "config.hpp"
26 #include "draw.hpp"
27 #include "events.hpp"
28 #include "formula/callable.hpp"
29 #include "formula/string_utils.hpp"
30 #include "gettext.hpp"
31 #include "log.hpp"
32 #include "wml_exception.hpp"
33 
38 #include "gui/core/log.hpp"
40 #include "sdl/point.hpp"
42 #include "gui/dialogs/tooltip.hpp"
43 #include "gui/widgets/button.hpp"
47 #include "gui/widgets/grid.hpp"
48 #include "gui/widgets/helper.hpp"
49 #include "gui/widgets/panel.hpp"
50 #include "gui/widgets/settings.hpp"
51 #include "gui/widgets/widget.hpp"
52 #include "gui/widgets/window.hpp"
53 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
54 #include "gui/widgets/debug.hpp"
55 #endif
56 #include "sdl/rect.hpp"
57 #include "sdl/texture.hpp"
58 #include "formula/variant.hpp"
59 #include "video.hpp" // only for toggle_fullscreen
60 #include "sdl/userevent.hpp"
61 #include "sdl/input.hpp" // get_mouse_button_mask
62 
63 #include <SDL2/SDL_timer.h>
64 
65 #include <algorithm>
66 #include <functional>
67 
68 
69 static lg::log_domain log_gui("gui/layout");
70 #define ERR_GUI LOG_STREAM(err, log_gui)
71 
72 #define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
73 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
74 
75 #define LOG_IMPL_SCOPE_HEADER \
76  window.get_control_type() + " [" + window.id() + "] " + __func__
77 #define LOG_IMPL_HEADER LOG_IMPL_SCOPE_HEADER + ':'
78 
79 static lg::log_domain log_display("display");
80 #define DBG_DP LOG_STREAM(debug, log_display)
81 #define LOG_DP LOG_STREAM(info, log_display)
82 #define WRN_DP LOG_STREAM(warn, log_display)
83 
84 namespace gui2
85 {
86 
87 // ------------ WIDGET -----------{
88 
89 namespace implementation
90 {
91 /** @todo See whether this hack can be removed. */
92 // Needed to fix a compiler error in REGISTER_WIDGET.
94 {
95 public:
97  {
98  }
99 
101 
102  virtual std::unique_ptr<widget> build() const override
103  {
104  return nullptr;
105  }
106 };
107 
108 } // namespace implementation
109 REGISTER_WIDGET(window)
110 
111 namespace
112 {
113 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
114 const unsigned SHOW = debug_layout_graph::SHOW;
115 const unsigned LAYOUT = debug_layout_graph::LAYOUT;
116 #else
117 // values are irrelavant when DEBUG_WINDOW_LAYOUT_GRAPHS is not defined.
118 const unsigned SHOW = 0;
119 const unsigned LAYOUT = 0;
120 #endif
121 
122 /**
123  * SDL_AddTimer() callback for delay_event.
124  *
125  * @param event The event to push in the event queue.
126  *
127  * @return The new timer interval (always 0).
128  */
129 static uint32_t delay_event_callback(const uint32_t, void* event)
130 {
131  SDL_PushEvent(static_cast<SDL_Event*>(event));
132  delete static_cast<SDL_Event*>(event);
133  return 0;
134 }
135 
136 /**
137  * Allows an event to be delayed a certain amount of time.
138  *
139  * @note the delay is the minimum time, after the time has passed the event
140  * will be pushed in the SDL event queue, so it might delay more.
141  *
142  * @param event The event to delay.
143  * @param delay The number of ms to delay the event.
144  */
145 static void delay_event(const SDL_Event& event, const uint32_t delay)
146 {
147  SDL_AddTimer(delay, delay_event_callback, new SDL_Event(event));
148 }
149 
150 /**
151  * Adds a SHOW_HELPTIP event to the SDL event queue.
152  *
153  * The event is used to show the helptip for the currently focused widget.
154  */
155 static bool helptip()
156 {
157  DBG_GUI_E << "Pushing SHOW_HELPTIP_EVENT event in queue.";
158 
159  SDL_Event event;
161 
162  event.type = SHOW_HELPTIP_EVENT;
163  event.user = data;
164 
165  SDL_PushEvent(&event);
166  return true;
167 }
168 
169 /**
170  * Small helper class to get an unique id for every window instance.
171  *
172  * This is used to send event to the proper window, this allows windows to post
173  * messages to themselves and let them delay for a certain amount of time.
174  */
175 class manager
176 {
177  manager();
178 
179 public:
180  static manager& instance();
181 
182  void add(window& window);
183 
184  void remove(window& window);
185 
186  unsigned get_id(window& window);
187 
188  window* get_window(const unsigned id);
189 
190 private:
191  // The number of active window should be rather small
192  // so keep it simple and don't add a reverse lookup map.
193  std::map<unsigned, window*> windows_;
194 };
195 
196 manager::manager() : windows_()
197 {
198 }
199 
200 manager& manager::instance()
201 {
202  static manager window_manager;
203  return window_manager;
204 }
205 
206 void manager::add(window& win)
207 {
208  static unsigned id;
209  ++id;
210  windows_[id] = &win;
211 }
212 
213 void manager::remove(window& win)
214 {
215  for(auto itor = windows_.begin(); itor != windows_.end(); ++itor) {
216  if(itor->second == &win) {
217  windows_.erase(itor);
218  return;
219  }
220  }
221 
222  assert(false);
223 }
224 
225 unsigned manager::get_id(window& win)
226 {
227  for(const auto& [id, window_ptr] : windows_) {
228  if(window_ptr == &win) {
229  return id;
230  }
231  }
232 
233  assert(false);
234  return 0;
235 }
236 
237 window* manager::get_window(const unsigned id)
238 {
239  if(auto itor = windows_.find(id); itor != windows_.end()) {
240  return itor->second;
241  } else {
242  return nullptr;
243  }
244 }
245 
246 } // namespace
247 
248 window::window(const builder_window::window_resolution& definition)
249  : panel(implementation::builder_window(::config {"definition", definition.definition}), type())
250  , status_(status::NEW)
251  , show_mode_(show_mode::none)
252  , retval_(retval::NONE)
253  , need_layout_(true)
254  , variables_()
255  , invalidate_layout_blocked_(false)
256  , hidden_(true)
257  , automatic_placement_(definition.automatic_placement)
258  , horizontal_placement_(definition.horizontal_placement)
259  , vertical_placement_(definition.vertical_placement)
260  , maximum_width_(definition.maximum_width)
261  , maximum_height_(definition.maximum_height)
262  , x_(definition.x)
263  , y_(definition.y)
264  , w_(definition.width)
265  , h_(definition.height)
266  , reevaluate_best_size_(definition.reevaluate_best_size)
267  , functions_(definition.functions)
268  , tooltip_(definition.tooltip)
269  , helptip_(definition.helptip)
270  , click_dismiss_(definition.click_dismiss)
271  , enter_disabled_(false)
272  , escape_disabled_(false)
273  , linked_size_()
274  , mouse_button_state_(0) /**< Needs to be initialized in @ref show. */
275 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
276  , debug_layout_(new debug_layout_graph(this))
277 #endif
278  , event_distributor_(new event::distributor(*this, event::dispatcher::front_child))
279  , exit_hook_([] { return true; })
280 {
281  manager::instance().add(*this);
282 
283  connect();
284 
285  for(const auto& [id, fixed_width, fixed_height] : definition.linked_groups) {
286  if(!init_linked_size_group(id, fixed_width, fixed_height)) {
287  FAIL(VGETTEXT("Linked ‘$id’ group has multiple definitions.", {{"id", id}}));
288  }
289  }
290 
291  const auto conf = cast_config_to<window_definition>();
292  assert(conf);
293 
294  if(conf->grid) {
295  init_grid(*conf->grid);
296  finalize(*definition.grid);
297  } else {
298  init_grid(*definition.grid);
299  }
300 
301  add_to_keyboard_chain(this);
302 
303  connect_signal<event::SDL_VIDEO_RESIZE>(std::bind(
304  &window::signal_handler_sdl_video_resize, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5));
305 
306  connect_signal<event::SDL_ACTIVATE>(std::bind(
307  &event::distributor::initialize_state, event_distributor_.get()));
308 
309  connect_signal<event::SDL_LEFT_BUTTON_UP>(
311  this,
312  std::placeholders::_2,
313  std::placeholders::_3,
314  std::placeholders::_4,
315  SDL_BUTTON_LMASK),
317  connect_signal<event::SDL_MIDDLE_BUTTON_UP>(
319  this,
320  std::placeholders::_2,
321  std::placeholders::_3,
322  std::placeholders::_4,
323  SDL_BUTTON_MMASK),
325  connect_signal<event::SDL_RIGHT_BUTTON_UP>(
327  this,
328  std::placeholders::_2,
329  std::placeholders::_3,
330  std::placeholders::_4,
331  SDL_BUTTON_RMASK),
333 
334  // FIXME investigate why this handler is being called twice and if this is actually needed
335  connect_signal<event::SDL_KEY_DOWN>(
336  std::bind(
337  &window::signal_handler_sdl_key_down, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5, std::placeholders::_6, false),
339  connect_signal<event::SDL_KEY_DOWN>(std::bind(
340  &window::signal_handler_sdl_key_down, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5, std::placeholders::_6, true));
341 
342  connect_signal<event::MESSAGE_SHOW_TOOLTIP>(
344  this,
345  std::placeholders::_2,
346  std::placeholders::_3,
347  std::placeholders::_5),
349 
350  connect_signal<event::MESSAGE_SHOW_HELPTIP>(
352  this,
353  std::placeholders::_2,
354  std::placeholders::_3,
355  std::placeholders::_5),
357 
358  connect_signal<event::REQUEST_PLACEMENT>(
359  std::bind(
360  &window::signal_handler_request_placement, this, std::placeholders::_2, std::placeholders::_3),
362 
363  connect_signal<event::CLOSE_WINDOW>(std::bind(&window::signal_handler_close_window, this));
364 
365  register_hotkey(hotkey::GLOBAL__HELPTIP,
366  [](auto&&...) { return helptip(); });
367 
368  /** @todo: should eventually become part of global hotkey handling. */
369  register_hotkey(hotkey::HOTKEY_FULLSCREEN,
370  [](auto&&...) { video::toggle_fullscreen(); return true; });
371 }
372 
374 {
375  /*
376  * We need to delete our children here instead of waiting for the grid to
377  * automatically do it. The reason is when the grid deletes its children
378  * they will try to unregister them self from the linked widget list. At
379  * this point the member of window are destroyed and we enter UB. (For
380  * some reason the bug didn't trigger on g++ but it does on MSVC.
381  */
382  for(unsigned row = 0; row < get_grid().get_rows(); ++row) {
383  for(unsigned col = 0; col < get_grid().get_cols(); ++col) {
384  get_grid().remove_child(row, col);
385  }
386  }
387 
388  /*
389  * The tip needs to be closed if the window closes and the window is
390  * not a tip. If we don't do that the tip will unrender in the next
391  * window and cause drawing glitches.
392  * Another issue is that on smallgui and an MP game the tooltip not
393  * unrendered properly can capture the mouse and make playing impossible.
394  */
397  }
398 
399  manager::instance().remove(*this);
400 
401  // If we are currently shown, then queue an undraw.
402  if(!hidden_) {
403  queue_redraw();
404  }
405 }
406 
408 {
409  return manager::instance().get_window(handle);
410 }
411 
412 retval window::get_retval_by_id(const std::string& id)
413 {
414  // Note it might change to a map later depending on the number
415  // of items.
416  if(id == "ok") {
417  return retval::OK;
418  } else if(id == "cancel" || id == "quit") {
419  return retval::CANCEL;
420  } else {
421  return retval::NONE;
422  }
423 }
424 
425 void window::show_tooltip(/*const unsigned auto_close_timeout*/)
426 {
427  // Unhide in any case.
428  hidden_ = false;
429 
430  // Connect to the event handler, if not yet connected.
431  if(!is_connected()) {
432  LOG_DP << "connecting " << id() << " on show_tooltip";
433  connect();
434  }
435 
436  log_scope2(log_gui_draw, "Window: show as tooltip.");
437 
438  generate_dot_file("show", SHOW);
439 
440  assert(status_ == status::NEW);
441 
444 
446 
447  /*
448  * Before show has been called, some functions might have done some testing
449  * on the window and called layout, which can give glitches. So
450  * reinvalidate the window to avoid those glitches.
451  */
453  queue_redraw();
454  DBG_DP << "show tooltip queued to " << get_rectangle();
455 }
456 
457 void window::show_non_modal(/*const unsigned auto_close_timeout*/)
458 {
459  // Unhide in any case.
460  hidden_ = false;
461 
462  // Connect to the event handler, if not yet connected.
463  if(!is_connected()) {
464  LOG_DP << "connecting " << id() << " on show_non_modal";
465  connect();
466  }
467 
468  log_scope2(log_gui_draw, "Window: show non modal.");
469 
470  generate_dot_file("show", SHOW);
471 
472  assert(status_ == status::NEW);
473 
475 
477 
478  /*
479  * Before show has been called, some functions might have done some testing
480  * on the window and called layout, which can give glitches. So
481  * reinvalidate the window to avoid those glitches.
482  */
484  queue_redraw();
485 
486  DBG_DP << "show non-modal queued to " << get_rectangle();
487 
489 }
490 
491 int window::show(const unsigned auto_close_timeout)
492 {
493  // Unhide in any case.
494  hidden_ = false;
495 
496  // Connect to the event handler, if not yet connected.
497  if(!is_connected()) {
498  LOG_DP << "connecting " << id() << " on show";
499  connect();
500  }
501 
502  /*
503  * Removes the old tip if one shown. The show_tip doesn't remove
504  * the tip, since it's the tip.
505  */
507 
509 
511 
512  generate_dot_file("show", SHOW);
513 
514  //assert(status_ == status::NEW);
515 
516  /*
517  * Before show has been called, some functions might have done some testing
518  * on the window and called layout, which can give glitches. So
519  * reinvalidate the window to avoid those glitches.
520  */
522  queue_redraw();
523 
524  // Make sure we display at least once in all cases.
525  events::draw();
526 
527  if(auto_close_timeout) {
528  SDL_Event event;
529  sdl::UserEvent data(CLOSE_WINDOW_EVENT, manager::instance().get_id(*this));
530 
531  event.type = CLOSE_WINDOW_EVENT;
532  event.user = data;
533 
534  delay_event(event, auto_close_timeout);
535  }
536 
537  try
538  {
539  // According to the comment in the next loop, we need to pump() once
540  // before we know which mouse buttons are down. Assume they're all
541  // down, otherwise there's a race condition when the MOUSE_UP gets
542  // processed in the first pump(), which immediately closes the window.
543  bool mouse_button_state_initialized = false;
544  mouse_button_state_ = std::numeric_limits<uint32_t>::max();
545 
546  // Start our loop, drawing will happen here as well.
548  // Process and handle all pending events.
549  events::pump();
550 
551  if(!mouse_button_state_initialized) {
552  /*
553  * The state must be initialize when showing the dialog.
554  * However when initialized before this point there were random
555  * errors. This only happened when the 'click' was done fast; a
556  * slower click worked properly.
557  *
558  * So it seems the events need to be processed before SDL can
559  * return the proper button state. When initializing here all
560  * works fine.
561  */
563  mouse_button_state_initialized = true;
564  }
565 
566  // See if we should close.
569  }
570 
571  // Update the display. This will rate limit to vsync.
572  events::draw();
573  }
574  }
575  catch(...)
576  {
577  // TODO: is this even necessary? What are we catching?
578  DBG_DP << "Caught general exception in show(): " << utils::get_unknown_exception_type();
579  hide();
580  throw;
581  }
582 
583  if(text_box_base* tb = dynamic_cast<text_box_base*>(event_distributor_->keyboard_focus())) {
584  tb->interrupt_composition();
585  }
586 
587  // The window may be kept around to be re-shown later. Hide it for now.
588  hide();
589 
590  return retval_;
591 }
592 
594 {
595  if(hidden_) {
596  return;
597  }
598 
599  // Draw background.
600  if(!this->draw_background()) {
601  // We may need to blur the background behind the window,
602  // but at this point it hasn't been rendered yet.
603  // We thus defer rendering to next frame so we can snapshot what
604  // is underneath the window without drawing over it.
606  return;
607  }
608 
609  // Draw children.
610  this->draw_children();
611 
612  // Draw foreground.
613  if(!this->draw_foreground()) {
615  }
616 
617  return;
618 }
619 
621 {
622  // Queue a redraw of the region if we were shown.
623  if(!hidden_) {
624  queue_redraw();
625  }
626 
627  // Disconnect from the event handler so we stop receiving events.
628  if(is_connected()) {
629  LOG_DP << "disconnecting " << id() << " on hide";
630  disconnect();
631  }
632 
633  hidden_ = true;
634 }
635 
637 {
638  point draw = get_size();
640 
641  // Check that the render buffer size is correct.
642  point buf_raw = render_buffer_.get_raw_size();
643  point buf_draw = render_buffer_.draw_size();
644  bool raw_size_changed = buf_raw.x != render.x || buf_raw.y != render.y;
645  bool draw_size_changed = buf_draw.x != draw.x || buf_draw.y != draw.y;
646  if (!raw_size_changed && !draw_size_changed) {
647  // buffers are fine
648  return;
649  }
650 
651  if(raw_size_changed) {
652  LOG_DP << "regenerating window render buffer as " << render;
653  render_buffer_ = texture(render.x, render.y, SDL_TEXTUREACCESS_TARGET);
654  }
655  if(raw_size_changed || draw_size_changed) {
656  LOG_DP << "updating window render buffer draw size to " << draw;
658  }
659 
660  // Clear the entire texture.
661  {
663  draw::clear();
664  }
665 
666  // Rerender everything.
667  queue_rerender();
668 }
669 
671 {
673 }
674 
675 void window::queue_rerender(const rect& screen_region)
676 {
677  // More than one region updating per-frame should be rare.
678  // Just rerender the minimal area that covers everything.
679  rect local_region = screen_region.intersect(get_rectangle());
680  local_region.shift(-get_origin());
681  awaiting_rerender_.expand_to_cover(local_region);
682 }
683 
684 void window::defer_region(const rect& screen_region)
685 {
686  LOG_DP << "deferring region " << screen_region;
687  deferred_regions_.push_back(screen_region);
688 }
689 
691 {
692  // Ensure our render texture is correctly sized.
694 
695  // Mark regions that were previously deferred for rerender and repaint.
696  for(auto& region : deferred_regions_) {
697  queue_redraw(region);
698  }
699  deferred_regions_.clear();
700 
701  // Render the portion of the window awaiting rerender (if any).
702  if (awaiting_rerender_.empty()) {
703  return;
704  }
705 
706  DBG_DP << "window::render() local " << awaiting_rerender_;
707  auto target_setter = draw::set_render_target(render_buffer_);
708  auto clip_setter = draw::override_clip(awaiting_rerender_);
709 
710  // Clear the to-be-rendered area unconditionally
711  draw::clear();
712 
713  draw();
714  awaiting_rerender_ = {};
715 }
716 
717 bool window::expose(const rect& region)
718 {
719  DBG_DP << "window::expose " << region;
720 
721  // Calculate the destination region we need to draw.
722  rect dst = get_rectangle().intersect(region);
724  if (dst.empty()) {
725  return false;
726  }
727 
728  // Blit from the pre-rendered buffer.
729  rect src = dst;
730  src.shift(-get_origin());
734 
735  return true;
736 }
737 
739 {
740  if(hidden_) {
741  return {0,0,0,0};
742  }
743  return get_rectangle();
744 }
745 
747  : window_(window)
748 {
751 }
752 
754 {
755  assert(window_.invalidate_layout_blocked_);
756  window_.invalidate_layout_blocked_ = false;
757 }
758 
760 {
762  need_layout_ = true;
763  }
764 }
765 widget* window::find_at(const point& coordinate, const bool must_be_active)
766 {
767  return panel::find_at(coordinate, must_be_active);
768 }
769 
771  const bool must_be_active) const
772 {
773  return panel::find_at(coordinate, must_be_active);
774 }
775 
776 widget* window::find(const std::string_view id, const bool must_be_active)
777 {
778  return container_base::find(id, must_be_active);
779 }
780 
781 const widget* window::find(const std::string_view id, const bool must_be_active)
782  const
783 {
784  return container_base::find(id, must_be_active);
785 }
786 
787 bool window::init_linked_size_group(const std::string& id, const bool fixed_width, const bool fixed_height)
788 {
789  assert(fixed_width || fixed_height);
790  auto [iter, success] = linked_size_.try_emplace(id, fixed_width, fixed_height);
791  return success;
792 }
793 
794 bool window::has_linked_size_group(const std::string& id)
795 {
796  return linked_size_.find(id) != linked_size_.end();
797 }
798 
799 void window::add_linked_widget(const std::string& id, widget* wgt)
800 {
801  assert(wgt);
802  if(!has_linked_size_group(id)) {
803  ERR_GUI << "Unknown linked group '" << id << "'; skipping";
804  return;
805  }
806 
807  std::vector<widget*>& widgets = linked_size_[id].widgets;
808  if(!utils::contains(widgets, wgt)) {
809  widgets.push_back(wgt);
810  }
811 }
812 
813 void window::remove_linked_widget(const std::string& id, const widget* wgt)
814 {
815  assert(wgt);
816  if(!has_linked_size_group(id)) {
817  return;
818  }
819 
820  std::vector<widget*>& widgets = linked_size_[id].widgets;
821  auto itor = std::find(widgets.begin(), widgets.end(), wgt);
822 
823  if(itor != widgets.end()) {
824  widgets.erase(itor);
825  assert(!utils::contains(widgets, wgt));
826  }
827 }
828 
830 {
831  if(!need_layout_) {
832  return;
833  }
834  DBG_DP << "window::layout";
835 
836  /***** Initialize. *****/
837 
838  const auto conf = cast_config_to<window_definition>();
839  assert(conf);
840 
842 
844  const point mouse = get_mouse_position();
845 
846  variables_.add("mouse_x", wfl::variant(mouse.x));
847  variables_.add("mouse_y", wfl::variant(mouse.y));
848  variables_.add("window_width", wfl::variant(0));
849  variables_.add("window_height", wfl::variant(0));
850  variables_.add("best_window_width", wfl::variant(size.x));
851  variables_.add("best_window_height", wfl::variant(size.y));
852  variables_.add("size_request_mode", wfl::variant("maximum"));
853 
855 
856  unsigned int maximum_width = maximum_width_(variables_, &functions_);
857  unsigned int maximum_height = maximum_height_(variables_, &functions_);
858 
860  if(maximum_width == 0 || maximum_width > settings::screen_width) {
861  maximum_width = settings::screen_width;
862  }
863 
864  if(maximum_height == 0 || maximum_height > settings::screen_height) {
865  maximum_height = settings::screen_height;
866  }
867  } else {
868  maximum_width = w_(variables_, &functions_);
869  maximum_height = h_(variables_, &functions_);
870  }
871 
872  /***** Handle click dismiss status. *****/
873  button* click_dismiss_button = find_widget<button>("click_dismiss", false, false);
874  if(click_dismiss_button) {
875  click_dismiss_button->set_visible(widget::visibility::invisible);
876  }
877  if(click_dismiss_) {
878  button* btn = find_widget<button>("ok", false, false);
879  if(btn) {
881  click_dismiss_button = btn;
882  }
883  VALIDATE(click_dismiss_button,
884  _("Click dismiss needs a ‘click_dismiss’ or ‘ok’ button."));
885  }
886 
887  /***** Layout. *****/
888  layout_initialize(true);
889  generate_dot_file("layout_initialize", LAYOUT);
890 
892 
893  try
894  {
895  window_implementation::layout(*this, maximum_width, maximum_height);
896  }
897  catch(const layout_exception_resize_failed&)
898  {
899  /** @todo implement the scrollbars on the window. */
900 
901  std::stringstream sstr;
902  sstr << __FILE__ << ":" << __LINE__ << " in function ‘" << __func__
903  << "’ found the following problem: Failed to size window with id ‘" << id()
904  << "’; wanted size " << get_best_size() << ", available size ("
905  << maximum_width << ',' << maximum_height << "), screen size ("
906  << settings::screen_width << ',' << settings::screen_height << ").";
907 
908  throw wml_exception(
909  _("Failed to show a dialog, which doesn’t fit on the screen."),
910  sstr.str(),
912  }
913 
914  /****** Validate click dismiss status. *****/
916  assert(click_dismiss_button);
917  click_dismiss_button->set_visible(widget::visibility::visible);
918 
920  *click_dismiss_button,
921  std::bind(&window::set_retval, this, retval::OK, true));
922 
923  layout_initialize(true);
924  generate_dot_file("layout_initialize", LAYOUT);
925 
927 
928  try
929  {
931  *this, maximum_width, maximum_height);
932  }
933  catch(const layout_exception_resize_failed&)
934  {
935  /** @todo implement the scrollbars on the window. */
936 
937  std::stringstream sstr;
938  sstr << __FILE__ << ":" << __LINE__ << " in function ‘" << __func__
939  << "’ found the following problem: Failed to size window with id ‘" << id()
940  << "’; wanted size " << get_best_size() << ", available size ("
941  << maximum_width << ',' << maximum_height << "), screen size ("
942  << settings::screen_width << ',' << settings::screen_height << ").";
943 
944  throw wml_exception(
945  _("Failed to show a dialog, which doesn’t fit on the screen."),
946  sstr.str(),
948  }
949  }
950 
951  /***** Get the best location for the window *****/
952  size = get_best_size();
953  /* Although 0-size windows might not seem valid/useful, there are
954  a handful of windows that request 0 size just to get a position
955  chosen via the code below, so at least for now allow them:
956  */
957  assert(size.x >= 0 && static_cast<unsigned>(size.x) <= maximum_width
958  && size.y >= 0 && static_cast<unsigned>(size.y) <= maximum_height);
959 
960  point origin(0, 0);
961 
963 
964  switch(horizontal_placement_) {
966  // Do nothing
967  break;
969  origin.x = (settings::screen_width - size.x) / 2;
970  break;
972  origin.x = settings::screen_width - size.x;
973  break;
974  default:
975  assert(false);
976  }
977  switch(vertical_placement_) {
979  // Do nothing
980  break;
982  origin.y = (settings::screen_height - size.y) / 2;
983  break;
985  origin.y = settings::screen_height - size.y;
986  break;
987  default:
988  assert(false);
989  }
990  } else {
991 
992  variables_.add("window_width", wfl::variant(size.x));
993  variables_.add("window_height", wfl::variant(size.y));
994 
996  layout_initialize(true);
997 
1000  h_(variables_, &functions_));
1001 
1002  size = get_best_size();
1003  variables_.add("window_width", wfl::variant(size.x));
1004  variables_.add("window_height", wfl::variant(size.y));
1005  }
1006 
1007  variables_.add("size_request_mode", wfl::variant("size"));
1008 
1009  size.x = w_(variables_, &functions_);
1010  size.y = h_(variables_, &functions_);
1011 
1012  variables_.add("window_width", wfl::variant(size.x));
1013  variables_.add("window_height", wfl::variant(size.y));
1014 
1015  origin.x = x_(variables_, &functions_);
1016  origin.y = y_(variables_, &functions_);
1017  }
1018 
1019  /***** Set the window size *****/
1020  place(origin, size);
1021 
1022  generate_dot_file("layout_finished", LAYOUT);
1023  need_layout_ = false;
1024 
1026 }
1027 
1029 {
1030  // evaluate the group sizes
1031  for(auto & linked_size : linked_size_)
1032  {
1033 
1034  point max_size(0, 0);
1035 
1036  // Determine the maximum size.
1037  for(auto widget : linked_size.second.widgets)
1038  {
1039 
1040  const point size = widget->get_best_size();
1041 
1042  if(size.x > max_size.x) {
1043  max_size.x = size.x;
1044  }
1045  if(size.y > max_size.y) {
1046  max_size.y = size.y;
1047  }
1048  }
1049  if(linked_size.second.width != -1) {
1050  linked_size.second.width = max_size.x;
1051  }
1052  if(linked_size.second.height != -1) {
1053  linked_size.second.height = max_size.y;
1054  }
1055 
1056  // Set the maximum size.
1057  for(auto widget : linked_size.second.widgets)
1058  {
1059 
1061 
1062  if(linked_size.second.width != -1) {
1063  size.x = max_size.x;
1064  }
1065  if(linked_size.second.height != -1) {
1066  size.y = max_size.y;
1067  }
1068 
1070  }
1071  }
1072 }
1073 
1074 bool window::click_dismiss(const int mouse_button_mask)
1075 {
1076  if(does_click_dismiss()) {
1077  if((mouse_button_state_ & mouse_button_mask) == 0) {
1079  } else {
1080  mouse_button_state_ &= ~mouse_button_mask;
1081  }
1082  return true;
1083  }
1084  return false;
1085 }
1086 
1087 void window::finalize(const builder_grid& content_grid)
1088 {
1089  auto widget = content_grid.build();
1090  assert(widget);
1091 
1092  static const std::string id = "_window_content_grid";
1093 
1094  // Make sure the new child has same id.
1095  widget->set_id(id);
1096 
1097  auto* parent_grid = get_grid().find_widget<grid>(id, true, false);
1098  assert(parent_grid);
1099 
1100  if(grid* grandparent_grid = dynamic_cast<grid*>(parent_grid->parent())) {
1101  grandparent_grid->swap_child(id, std::move(widget), false);
1102  } else if(container_base* c = dynamic_cast<container_base*>(parent_grid->parent())) {
1103  c->get_grid().swap_child(id, std::move(widget), true);
1104  } else {
1105  assert(false);
1106  }
1107 }
1108 
1109 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
1110 
1111 void window::generate_dot_file(const std::string& generator,
1112  const unsigned domain)
1113 {
1114  debug_layout_->generate_dot_file(generator, domain);
1115 }
1116 #endif
1117 
1119  const unsigned maximum_width,
1120  const unsigned maximum_height)
1121 {
1123 
1124  /*
1125  * For now we return the status, need to test later whether this can
1126  * entirely be converted to an exception based system as in 'promised' on
1127  * the algorithm page.
1128  */
1129 
1130  try
1131  {
1133 
1134  DBG_GUI_L << LOG_IMPL_HEADER << " best size : " << size
1135  << " maximum size : " << maximum_width << ','
1136  << maximum_height << ".";
1137  if(size.x <= static_cast<int>(maximum_width)
1138  && size.y <= static_cast<int>(maximum_height)) {
1139 
1140  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Fits, nothing to do.";
1141  return;
1142  }
1143 
1144  if(size.x > static_cast<int>(maximum_width)) {
1145  window.reduce_width(maximum_width);
1146 
1148  if(size.x > static_cast<int>(maximum_width)) {
1149  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resize width failed."
1150  << " Wanted width " << maximum_width
1151  << " resulting width " << size.x << ".";
1153  }
1155  << " Status: Resize width succeeded.";
1156  }
1157 
1158  if(size.y > static_cast<int>(maximum_height)) {
1159  window.reduce_height(maximum_height);
1160 
1162  if(size.y > static_cast<int>(maximum_height)) {
1163  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resize height failed."
1164  << " Wanted height " << maximum_height
1165  << " resulting height " << size.y << ".";
1167  }
1169  << " Status: Resize height succeeded.";
1170  }
1171 
1172  assert(size.x <= static_cast<int>(maximum_width)
1173  && size.y <= static_cast<int>(maximum_height));
1174 
1175 
1176  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resizing succeeded.";
1177  return;
1178  }
1179  catch(const layout_exception_width_modified&)
1180  {
1182  << " Status: Width has been modified, rerun.";
1183 
1184  window.layout_initialize(false);
1186  layout(window, maximum_width, maximum_height);
1187  return;
1188  }
1189 }
1190 
1191 void window::mouse_capture(const bool capture)
1192 {
1193  assert(event_distributor_);
1194  event_distributor_->capture_mouse(capture);
1195 }
1196 
1198 {
1199  assert(event_distributor_);
1200 #ifndef __ANDROID__
1201  event_distributor_->keyboard_capture(widget);
1202 #endif
1203 }
1204 
1206 {
1207  assert(event_distributor_);
1208  event_distributor_->keyboard_capture(widget);
1209 }
1210 
1212 {
1213  assert(event_distributor_);
1214  event_distributor_->keyboard_add_to_chain(widget);
1215 }
1216 
1218 {
1219  assert(event_distributor_);
1220  event_distributor_->keyboard_remove_from_chain(widget);
1221 }
1222 
1224 {
1226  return;
1227  }
1228  assert(event_distributor_);
1229  if(tab_order.empty() && !event_distributor_->keyboard_focus()) {
1231  }
1232  if(at < 0 || at >= static_cast<int>(tab_order.size())) {
1233  tab_order.push_back(widget);
1234  } else {
1235  tab_order.insert(tab_order.begin() + at, widget);
1236  }
1237 }
1238 
1240  bool& handled,
1241  const point& new_size)
1242 {
1243  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1244 
1247  settings::screen_width = new_size.x;
1248  settings::screen_height = new_size.y;
1250 
1251  handled = true;
1252 }
1253 
1255  bool& handled,
1256  bool& halt,
1257  const int mouse_button_mask)
1258 {
1259  DBG_GUI_E << LOG_HEADER << ' ' << event << " mouse_button_mask "
1260  << static_cast<unsigned>(mouse_button_mask) << ".";
1261 
1262  handled = halt = click_dismiss(mouse_button_mask);
1263 }
1264 
1265 static bool is_active(const widget* wgt)
1266 {
1267  if(const styled_widget* control = dynamic_cast<const styled_widget*>(wgt)) {
1268  return control->get_active() && control->get_visible() == widget::visibility::visible;
1269  }
1270  return false;
1271 }
1272 
1274  bool& handled,
1275  const SDL_Keycode key,
1276  const SDL_Keymod mod,
1277  bool handle_tab)
1278 {
1279  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1280 
1281  if(text_box_base* tb = dynamic_cast<text_box_base*>(event_distributor_->keyboard_focus())) {
1282  if(tb->is_composing()) {
1283  if(handle_tab && !tab_order.empty() && key == SDLK_TAB) {
1284  tb->interrupt_composition();
1285  } else {
1286  return;
1287  }
1288  }
1289  }
1290  if(key == SDLK_KP_ENTER || key == SDLK_RETURN) {
1291  if (mod & (KMOD_CTRL | KMOD_ALT | KMOD_GUI | KMOD_SHIFT)) {
1292  // Don't handle if modifier is pressed
1293  handled = false;
1294  } else {
1295  // Trigger window OK button only if Enter enabled,
1296  // otherwise pass handling to widget
1297  if (!enter_disabled_) {
1299  handled = true;
1300  }
1301  }
1302  } else if(key == SDLK_ESCAPE && !escape_disabled_) {
1304  handled = true;
1305  } else if(key == SDLK_SPACE) {
1306  handled = click_dismiss(0);
1307  } else if(handle_tab && !tab_order.empty() && key == SDLK_TAB) {
1308  assert(event_distributor_);
1309  widget* focus = event_distributor_->keyboard_focus();
1310  auto iter = std::find(tab_order.begin(), tab_order.end(), focus);
1311  do {
1312  if(mod & KMOD_SHIFT) {
1313  if(iter == tab_order.begin()) {
1314  iter = tab_order.end();
1315  }
1316  iter--;
1317  } else {
1318  if(iter == tab_order.end()) {
1319  iter = tab_order.begin();
1320  } else {
1321  iter++;
1322  if(iter == tab_order.end()) {
1323  iter = tab_order.begin();
1324  }
1325  }
1326  }
1327  } while(!is_active(*iter));
1328  keyboard_capture(*iter);
1329  handled = true;
1330  }
1331 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
1332  if(key == SDLK_F12) {
1333  debug_layout_->generate_dot_file("manual", debug_layout_graph::MANUAL);
1334  handled = true;
1335  }
1336 #endif
1337 }
1338 
1340  bool& handled,
1341  const event::message& message)
1342 {
1343  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1344 
1345  const event::message_show_tooltip& request
1346  = dynamic_cast<const event::message_show_tooltip&>(message);
1347 
1348  dialogs::tip::show(tooltip_.id, request.message, request.location, request.source_rect);
1349 
1350  handled = true;
1351 }
1352 
1354  bool& handled,
1355  const event::message& message)
1356 {
1357  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1358 
1359  const event::message_show_helptip& request
1360  = dynamic_cast<const event::message_show_helptip&>(message);
1361 
1362  dialogs::tip::show(helptip_.id, request.message, request.location, request.source_rect);
1363 
1364  handled = true;
1365 }
1366 
1368  bool& handled)
1369 {
1370  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1371 
1373 
1374  handled = true;
1375 }
1376 
1378 {
1380 }
1381 
1382 // }---------- DEFINITION ---------{
1383 
1386 {
1387  DBG_GUI_P << "Parsing window " << id;
1388 
1389  load_resolutions<resolution>(cfg);
1390 }
1391 
1393  : panel_definition::resolution(cfg), grid(nullptr)
1394 {
1395  auto child = cfg.optional_child("grid");
1396  // VALIDATE(child, _("No grid defined."));
1397 
1398  /** @todo Evaluate whether the grid should become mandatory. */
1399  if(child) {
1400  grid = std::make_shared<builder_grid>(*child);
1401  }
1402 }
1403 
1404 // }------------ END --------------
1405 
1406 } // namespace gui2
1407 
1408 
1409 /**
1410  * @page layout_algorithm Layout algorithm
1411  *
1412  * @section introduction-layout_algorithm Introduction
1413  *
1414  * This page describes how the layout engine for the dialogs works. First
1415  * a global overview of some terms used in this document.
1416  *
1417  * - @ref gui2::widget "Widget"; Any item which can be used in the widget
1418  * toolkit. Not all widgets are visible. In general widgets cannot be
1419  * sized directly, but this is controlled by a window. A widget has an
1420  * internal size cache and if the value in the cache is not equal to 0,0
1421  * that value is its best size. This value gets set when the widget can
1422  * honor a resize request. It will be set with the value which honors
1423  * the request.
1424  *
1425  * - @ref gui2::grid "Grid"; A grid is an invisible container which holds
1426  * one or more widgets. Several widgets have a grid in them to hold
1427  * multiple widgets eg panels and windows.
1428  *
1429  * - @ref gui2::grid::child "Grid cell"; Every widget which is in a grid is
1430  * put in a grid cell. These cells also hold the information about the gaps
1431  * between widgets the behavior on growing etc. All grid cells must have a
1432  * widget inside them.
1433  *
1434  * - @ref gui2::window "Window"; A window is a top level item which has a
1435  * grid with its children. The window handles the sizing of the window and
1436  * makes sure everything fits.
1437  *
1438  * - @ref gui2::window::linked_size "Shared size group"; A shared size
1439  * group is a number of widgets which share width and or height. These
1440  * widgets are handled separately in the layout algorithm. All grid cells
1441  * width such a widget will get the same height and or width and these
1442  * widgets won't be resized when there's not enough size. To be sure that
1443  * these widgets don't cause trouble for the layout algorithm, they must be
1444  * in a container with scrollbars so there will always be a way to properly
1445  * layout them. The engine must enforce this restriction so the shared
1446  * layout property must be set by the engine after validation.
1447  *
1448  * - All visible grid cells; A grid cell is visible when the widget inside
1449  * of it doesn't have the state visibility::invisible. Widgets which have the
1450  * state visibility::hidden are sized properly since when they become
1451  * visibility::visible the layout shouldn't be invalidated. A grid cell
1452  * that's invisible has size 0,0.
1453  *
1454  * - All resizable grid cells; A grid cell is resizable under the following
1455  * conditions:
1456  * - The widget is visibility::visible.
1457  * - The widget is not in a shared size group.
1458  *
1459  * There are two layout algorithms with a different purpose.
1460  *
1461  * - The Window algorithm; this algorithm's goal is it to make sure all grid
1462  * cells fit in the window. Sizing the grid cells depends on the widget
1463  * size as well, but this algorithm only sizes the grid cells and doesn't
1464  * handle the widgets inside them.
1465  *
1466  * - The Grid algorithm; after the Window algorithm made sure that all grid
1467  * cells fit this algorithm makes sure the widgets are put in the optimal
1468  * state in their grid cell.
1469  *
1470  * @section layout_algorithm_window Window
1471  *
1472  * Here is the algorithm used to layout the window:
1473  *
1474  * - Perform a full initialization
1475  * (@ref gui2::widget::layout_initialize (full_initialization = true)):
1476  * - Clear the internal best size cache for all widgets.
1477  * - For widgets with scrollbars hide them unless the
1478  * @ref gui2::scrollbar_container::scrollbar_mode "scrollbar_mode" is
1479  * ALWAYS_VISIBLE or AUTO_VISIBLE.
1480  * - Handle shared sizes:
1481  * - Height and width:
1482  * - Get the best size for all widgets that share height and width.
1483  * - Set the maximum of width and height as best size for all these
1484  * widgets.
1485  * - Width only:
1486  * - Get the best width for all widgets which share their width.
1487  * - Set the maximum width for all widgets, but keep their own height.
1488  * - Height only:
1489  * - Get the best height for all widgets which share their height.
1490  * - Set the maximum height for all widgets, but keep their own width.
1491  * - Start layout loop:
1492  * - Get best size.
1493  * - If width <= maximum_width && height <= maximum_height we're done.
1494  * - If width > maximum_width, optimize the width:
1495  * - For every grid cell in a grid row there will be a resize request
1496  * (@ref gui2::grid::reduce_width):
1497  * - Sort the widgets in the row on the resize priority.
1498  * - Loop through this priority queue until the row fits
1499  * - If priority != 0 try to share the extra width else all
1500  * widgets are tried to reduce the full size.
1501  * - Try to shrink the widgets by either wrapping or using a
1502  * scrollbar (@ref gui2::widget::request_reduce_width).
1503  * - If the row fits in the wanted width this row is done.
1504  * - Else try the next priority.
1505  * - All priorities done and the width still doesn't fit.
1506  * - Loop through this priority queue until the row fits.
1507  * - If priority != 0:
1508  * - try to share the extra width
1509  * -Else:
1510  * - All widgets are tried to reduce the full size.
1511  * - Try to shrink the widgets by sizing them smaller as really
1512  * wanted (@ref gui2::widget::demand_reduce_width).
1513  * For labels, buttons etc. they get ellipsized.
1514  * - If the row fits in the wanted width this row is done.
1515  * - Else try the next priority.
1516  * - All priorities done and the width still doesn't fit.
1517  * - Throw a layout width doesn't fit exception.
1518  * - If height > maximum_height, optimize the height
1519  * (@ref gui2::grid::reduce_height):
1520  * - For every grid cell in a grid column there will be a resize request:
1521  * - Sort the widgets in the column on the resize priority.
1522  * - Loop through this priority queue until the column fits:
1523  * - If priority != 0 try to share the extra height else all
1524  * widgets are tried to reduce the full size.
1525  * - Try to shrink the widgets by using a scrollbar
1526  * (@ref gui2::widget::request_reduce_height).
1527  * - If succeeded for a widget the width is influenced and the
1528  * width might be invalid.
1529  * - Throw a width modified exception.
1530  * - If the column fits in the wanted height this column is done.
1531  * - Else try the next priority.
1532  * - All priorities done and the height still doesn't fit.
1533  * - Loop through this priority queue until the column fits.
1534  * - If priority != 0 try to share the extra height else all
1535  * widgets are tried to reduce the full size.
1536  * - Try to shrink the widgets by sizing them smaller as really
1537  * wanted (@ref gui2::widget::demand_reduce_width).
1538  * For labels, buttons etc. they get ellipsized .
1539  * - If the column fits in the wanted height this column is done.
1540  * - Else try the next priority.
1541  * - All priorities done and the height still doesn't fit.
1542  * - Throw a layout height doesn't fit exception.
1543  * - End layout loop.
1544  *
1545  * - Catch @ref gui2::layout_exception_width_modified "width modified":
1546  * - Goto relayout.
1547  *
1548  * - Catch
1549  * @ref gui2::layout_exception_width_resize_failed "width resize failed":
1550  * - If the window has a horizontal scrollbar which isn't shown but can be
1551  * shown.
1552  * - Show the scrollbar.
1553  * - goto relayout.
1554  * - Else show a layout failure message.
1555  *
1556  * - Catch
1557  * @ref gui2::layout_exception_height_resize_failed "height resize failed":
1558  * - If the window has a vertical scrollbar which isn't shown but can be
1559  * shown:
1560  * - Show the scrollbar.
1561  * - goto relayout.
1562  * - Else:
1563  * - show a layout failure message.
1564  *
1565  * - Relayout:
1566  * - Initialize all widgets
1567  * (@ref gui2::widget::layout_initialize (full_initialization = false))
1568  * - Handle shared sizes, since the reinitialization resets that state.
1569  * - Goto start layout loop.
1570  *
1571  * @section grid Grid
1572  *
1573  * This section will be documented later.
1574  */
std::size_t w_
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:380
Simple push button.
Definition: button.hpp:36
A generic container base class.
bool disable_click_dismiss() const override
See widget::disable_click_dismiss.
void reduce_height(const unsigned maximum_height)
Tries to reduce the height of a container.
const grid & get_grid() const
virtual void layout_initialize(const bool full_initialization) override
See widget::layout_initialize.
void reduce_width(const unsigned maximum_width)
Tries to reduce the width of a container.
widget * find(const std::string_view id, const bool must_be_active) override
See widget::find.
virtual widget * find_at(const point &coordinate, const bool must_be_active) override
See widget::find_at.
virtual void place(const point &origin, const point &size) override
See widget::place.
Main class to show messages to the user.
Definition: message.hpp:36
At the moment two kinds of tips are known:
Definition: tooltip.cpp:41
void connect()
Connects the dispatcher to the event handler.
Definition: dispatcher.cpp:49
void set_mouse_behavior(const mouse_behavior mouse_behavior)
Definition: dispatcher.hpp:412
void disconnect()
Disconnects the dispatcher from the event handler.
Definition: dispatcher.cpp:56
void set_want_keyboard_input(const bool want_keyboard_input)
Definition: dispatcher.hpp:422
bool is_connected() const
Return whether the dispatcher is currently connected.
Definition: dispatcher.hpp:175
void initialize_state()
Initializes the state of the keyboard and mouse.
Basic template class to generate new items.
Base container class.
Definition: grid.hpp:32
static const unsigned HORIZONTAL_ALIGN_RIGHT
Definition: grid.hpp:59
unsigned int get_rows() const
Definition: grid.hpp:302
static const unsigned VERTICAL_ALIGN_BOTTOM
Definition: grid.hpp:52
static const unsigned VERTICAL_ALIGN_CENTER
Definition: grid.hpp:51
static const unsigned HORIZONTAL_ALIGN_CENTER
Definition: grid.hpp:58
void remove_child(const unsigned row, const unsigned col)
Removes and frees a widget in a cell.
Definition: grid.cpp:145
static const unsigned VERTICAL_ALIGN_TOP
Definition: grid.hpp:50
unsigned int get_cols() const
Definition: grid.hpp:308
static const unsigned HORIZONTAL_ALIGN_LEFT
Definition: grid.hpp:57
builder_window(const config &cfg)
Definition: window.cpp:96
virtual std::unique_ptr< widget > build() const override
Definition: window.cpp:102
Abstract base class for text items.
Base class for all widgets.
Definition: widget.hpp:55
void set_layout_size(const point &size)
Definition: widget.cpp:346
bool draw_foreground()
Draws the foreground of the widget.
Definition: widget.cpp:423
point get_best_size() const
Gets the best size for the widget.
Definition: widget.cpp:203
void set_visible(const visibility visible)
Definition: widget.cpp:479
void set_id(const std::string &id)
Definition: widget.cpp:98
void queue_redraw()
Indicates that this widget should be redrawn.
Definition: widget.cpp:464
point get_origin() const
Returns the screen origin of the widget.
Definition: widget.cpp:311
point get_size() const
Returns the size of the widget.
Definition: widget.cpp:316
void draw_children()
Draws the children of a widget.
Definition: widget.cpp:402
bool draw_background()
Draws the background of a widget.
Definition: widget.cpp:381
const std::string & id() const
Definition: widget.cpp:110
window * get_window()
Get the parent window.
Definition: widget.cpp:117
@ visible
The user sets the widget visible, that means:
@ invisible
The user set the widget invisible, that means:
rect get_rectangle() const
Gets the bounding rectangle of the widget on the screen.
Definition: widget.cpp:321
T * find_widget(const std::string_view id, const bool must_be_active, const bool must_exist)
Gets a widget with the wanted id.
Definition: widget.hpp:747
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:59
bool need_layout_
When set the form needs a full layout redraw cycle.
Definition: window.hpp:478
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:387
void capture_and_show_keyboard(widget *widget)
Definition: window.cpp:1205
const unsigned vertical_placement_
Sets the vertical placement.
Definition: window.hpp:506
bool invalidate_layout_blocked_
Is invalidate_layout blocked, see invalidate_layout_blocker.
Definition: window.hpp:484
typed_formula< bool > reevaluate_best_size_
The formula to determine whether the size is good.
Definition: window.hpp:527
typed_formula< unsigned > maximum_width_
The maximum width if automatic_placement_ is true.
Definition: window.hpp:509
void remove_from_keyboard_chain(widget *widget)
Remove the widget from the keyboard chain.
Definition: window.cpp:1217
void keyboard_capture(widget *widget)
Definition: window.cpp:1197
void invalidate_layout()
Updates the size of the window.
Definition: window.cpp:759
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1211
void signal_handler_message_show_tooltip(const event::ui_event event, bool &handled, const event::message &message)
Definition: window.cpp:1339
void mouse_capture(const bool capture=true)
Definition: window.cpp:1191
typed_formula< unsigned > maximum_height_
The maximum height if automatic_placement_ is true.
Definition: window.hpp:512
virtual rect screen_location() override
The current draw location of the window, on the screen.
Definition: window.cpp:738
void update_render_textures()
Ensure render textures are valid and correct.
Definition: window.cpp:636
void show_tooltip()
Shows the window as a tooltip.
Definition: window.cpp:425
int mouse_button_state_
The state of the mouse button.
Definition: window.hpp:628
void hide()
Hides the window.
Definition: window.cpp:620
void show_non_modal()
Shows the window non modal.
Definition: window.cpp:457
std::unique_ptr< event::distributor > event_distributor_
Definition: window.hpp:659
void signal_handler_sdl_video_resize(const event::ui_event event, bool &handled, const point &new_size)
Definition: window.cpp:1239
virtual ~window()
Definition: window.cpp:373
std::map< std::string, linked_size, std::less<> > linked_size_
List of the widgets, whose size are linked together.
Definition: window.hpp:588
static window * window_instance(const unsigned handle)
Returns the instance of a window.
Definition: window.cpp:407
status
The status of the window.
Definition: window.hpp:203
@ CLOSED
The window has been closed.
@ NEW
The window is new and not yet shown.
@ SHOWING
The window is being shown.
@ REQUEST_CLOSE
The window has been requested to be closed but still needs to evaluate the request.
builder_window::window_resolution::tooltip_info tooltip_
The settings for the tooltip.
Definition: window.hpp:533
void signal_handler_sdl_key_down(const event::ui_event event, bool &handled, const SDL_Keycode key, const SDL_Keymod mod, bool handle_tab)
Definition: window.cpp:1273
virtual void layout() override
Lays out the window.
Definition: window.cpp:829
void signal_handler_message_show_helptip(const event::ui_event event, bool &handled, const event::message &message)
Definition: window.cpp:1353
void layout_linked_widgets()
Layouts the linked widgets.
Definition: window.cpp:1028
show_mode show_mode_
The mode in which the window is shown.
Definition: window.hpp:467
std::vector< widget * > tab_order
List of widgets in the tabbing order.
Definition: window.hpp:591
typed_formula< unsigned > h_
The formula to calculate the height of the dialog.
Definition: window.hpp:524
widget * find(const std::string_view id, const bool must_be_active) override
See widget::find.
Definition: window.cpp:776
int show(unsigned auto_close_timeout=0)
Shows the window, running an event loop until it should close.
Definition: window.cpp:491
rect awaiting_rerender_
The part of the window (if any) currently marked for rerender.
Definition: window.hpp:163
void signal_handler_request_placement(const event::ui_event event, bool &handled)
Definition: window.cpp:1367
bool escape_disabled_
Disable the escape key see our setter for more info.
Definition: window.hpp:561
bool does_click_dismiss() const
Does the window close easily?
Definition: window.hpp:300
void signal_handler_close_window()
Definition: window.cpp:1377
bool click_dismiss_
Do we want to have easy close behavior?
Definition: window.hpp:555
void add_linked_widget(const std::string &id, widget *widget)
Adds a widget to a linked size group.
Definition: window.cpp:799
void defer_region(const rect &region)
Defer rendering of a particular region to next frame.
Definition: window.cpp:684
const unsigned horizontal_placement_
Sets the horizontal placement.
Definition: window.hpp:498
void queue_rerender()
Definition: window.cpp:670
static retval get_retval_by_id(const std::string &id)
Gets the retval for the default buttons.
Definition: window.cpp:412
void add_to_tab_order(widget *widget, int at=-1)
Add the widget to the tabbing order.
Definition: window.cpp:1223
const bool automatic_placement_
Do we wish to place the widget automatically?
Definition: window.hpp:490
void finalize(const builder_grid &content_grid)
Finishes the initialization of the grid.
Definition: window.cpp:1087
builder_window::window_resolution::tooltip_info helptip_
The settings for the helptip.
Definition: window.hpp:536
std::function< bool()> exit_hook_
Definition: window.hpp:745
std::vector< rect > deferred_regions_
Parts of the window (if any) with rendering deferred to next frame.
Definition: window.hpp:166
bool hidden_
Avoid drawing the window.
Definition: window.hpp:487
bool click_dismiss(const int mouse_button_mask)
Handles a mouse click event for dismissing the dialog.
Definition: window.cpp:1074
typed_formula< unsigned > x_
The formula to calculate the x value of the dialog.
Definition: window.hpp:515
typed_formula< unsigned > y_
The formula to calculate the y value of the dialog.
Definition: window.hpp:518
void generate_dot_file(const std::string &, const unsigned)
Definition: window.hpp:654
virtual bool expose(const rect &region) override
Called by draw_manager when it believes a redraw is necessary.
Definition: window.cpp:717
wfl::map_formula_callable variables_
The variables of the canvas.
Definition: window.hpp:481
void remove_linked_widget(const std::string &id, const widget *widget)
Removes a widget from a linked size group.
Definition: window.cpp:813
void signal_handler_click_dismiss(const event::ui_event event, bool &handled, bool &halt, const int mouse_button_mask)
The handler for the click dismiss mouse 'event'.
Definition: window.cpp:1254
bool enter_disabled_
Disable the enter key see our setter for more info.
Definition: window.hpp:558
virtual void render() override
Ensure the window's internal render buffer is up-to-date.
Definition: window.cpp:690
bool init_linked_size_group(const std::string &id, const bool fixed_width, const bool fixed_height)
Initializes a linked size group.
Definition: window.cpp:787
void draw()
Draws the window.
Definition: window.cpp:593
typed_formula< unsigned > w_
The formula to calculate the width of the dialog.
Definition: window.hpp:521
wfl::function_symbol_table functions_
The formula definitions available for the calculation formulas.
Definition: window.hpp:530
status status_
The status of the window.
Definition: window.hpp:460
virtual widget * find_at(const point &coordinate, const bool must_be_active) override
See widget::find_at.
Definition: window.cpp:765
texture render_buffer_
The internal render buffer used by render() and expose().
Definition: window.hpp:160
bool has_linked_size_group(const std::string &id)
Is the linked size group defined for this window?
Definition: window.cpp:794
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:33
void set_src(const rect &r)
Set the source region of the texture used for drawing operations.
Definition: texture.cpp:104
point draw_size() const
The size of the texture in draw-space.
Definition: texture.hpp:120
void set_draw_size(int w, int h)
Set the intended size of the texture, in draw-space.
Definition: texture.hpp:129
point get_raw_size() const
The raw internal texture size.
Definition: texture.cpp:99
void clear_src()
Clear the source region.
Definition: texture.hpp:165
map_formula_callable & add(const std::string &key, const variant &value)
Definition: callable.hpp:253
Definitions for the interface to Wesnoth Markup Language (WML).
This file contains the definitions for the gui2::event::message class.
Contains the event distributor.
Drawing functions, for drawing things on the screen.
#define CLOSE_WINDOW_EVENT
Definition: events.hpp:27
#define SHOW_HELPTIP_EVENT
Definition: events.hpp:28
const config * cfg
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
static std::string _(const char *str)
Definition: gettext.hpp:97
Define the common log macros for the gui toolkit.
#define DBG_GUI_L
Definition: log.hpp:55
#define DBG_GUI_P
Definition: log.hpp:66
#define DBG_GUI_E
Definition: log.hpp:35
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:199
#define ERR_GUI
Definition: window.cpp:70
#define LOG_IMPL_HEADER
Definition: window.cpp:77
#define LOG_DP
Definition: window.cpp:81
#define LOG_IMPL_SCOPE_HEADER
Definition: window.cpp:75
#define LOG_HEADER
Definition: window.cpp:73
#define LOG_SCOPE_HEADER
Definition: window.cpp:72
std::map< unsigned, window * > windows_
Definition: window.cpp:193
static lg::log_domain log_display("display")
#define DBG_DP
Definition: window.cpp:80
static lg::log_domain log_gui("gui/layout")
This file contains the window object, this object is a top level container which has the event manage...
Contains functions for cleanly handling SDL input.
Defines the exception classes for the layout algorithm.
Standard logging facilities (interface).
#define log_scope2(domain, description)
Definition: log.hpp:276
Definition: draw.hpp:43
render_target_setter set_render_target(const texture &t)
Set the given texture as the active render target.
Definition: draw.cpp:748
clip_setter override_clip(const ::rect &clip)
Override the clipping area.
Definition: draw.cpp:581
void clear()
Clear the current render target.
Definition: draw.cpp:42
::rect get_clip()
Get the current clipping area, in draw coordinates.
Definition: draw.cpp:606
void blit(const texture &tex, const ::rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition: draw.cpp:394
void draw()
Trigger a draw cycle.
Definition: events.cpp:716
void pump_and_draw()
pump() then immediately draw()
Definition: events.hpp:151
void pump()
Process all events currently in the queue.
Definition: events.cpp:480
void remove()
Removes a tip.
Definition: tooltip.cpp:94
void show(const std::string &window_id, const t_string &message, const point &mouse, const rect &source_rect)
Shows a tip.
Definition: tooltip.cpp:64
ui_event
The event sent to the dispatcher.
Definition: handler.hpp:115
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:163
void init_mouse_location()
Initializes the location of the mouse.
Definition: handler.cpp:889
unsigned screen_width
The screen resolution and pixel pitch should be available for all widgets since their drawing method ...
Definition: settings.cpp:27
unsigned screen_height
Definition: settings.cpp:28
unsigned gamemap_height
Definition: settings.cpp:32
unsigned gamemap_width
The size of the map area, if not available equal to the screen size.
Definition: settings.cpp:31
Generic file dialog.
void get_screen_size_variables(wfl::map_formula_callable &variable)
Gets a formula object with the screen size.
Definition: helper.cpp:151
static bool is_active(const widget *wgt)
Definition: window.cpp:1265
lg::log_domain log_gui_draw("gui/draw")
Definition: log.hpp:28
point get_mouse_position()
Returns the current mouse position.
Definition: helper.cpp:168
retval
Default window/dialog return values.
Definition: retval.hpp:30
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
@ AUTO_CLOSE
The dialog was closed automatically as its timeout had been reached.
Definition: retval.hpp:41
@ CANCEL
Dialog was closed with the CANCEL button.
Definition: retval.hpp:38
@ NONE
Default, unset return value.
Definition: retval.hpp:32
lg::log_domain log_gui_layout("gui/layout")
Definition: log.hpp:54
std::shared_ptr< halo_record > handle
Definition: halo.hpp:31
@ HOTKEY_FULLSCREEN
Contains the implementation details for lexical_cast and shouldn't be used directly.
static std::string at(const std::string &file, int line)
uint32_t get_mouse_button_mask()
Returns the current mouse button mask.
Definition: input.cpp:49
map_location coordinate
Contains an x and y coordinate used for starting positions in maps.
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:87
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:141
SDL_Window * get_window()
Definition: video.cpp:692
int get_pixel_scale()
Get the current active pixel scale multiplier.
Definition: video.cpp:498
void toggle_fullscreen()
Toggle fullscreen mode.
Definition: video.cpp:833
std::string_view data
Definition: picture.cpp:188
Contains the SDL_Rect helper code.
#define REGISTER_WIDGET(id)
Wrapper for REGISTER_WIDGET3.
This file contains the settings handling of the widget library.
rect dst
Location on the final composed sheet.
rect src
Non-transparent portion of the surface to compose.
virtual std::unique_ptr< widget > build() const override
Inherited from builder_widget.
The message for MESSAGE_SHOW_HELPTIP.
Definition: message.hpp:77
const point location
The location where to show the helptip.
Definition: message.hpp:87
const std::string message
The message to show on the helptip.
Definition: message.hpp:84
const rect source_rect
The size of the entity requesting to show a helptip.
Definition: message.hpp:90
The message for MESSAGE_SHOW_TOOLTIP.
Definition: message.hpp:59
const point location
The location where to show the tooltip.
Definition: message.hpp:69
const std::string message
The message to show on the tooltip.
Definition: message.hpp:66
const rect source_rect
The size of the entity requesting to show a tooltip.
Definition: message.hpp:72
The message callbacks hold a reference to a message.
Definition: message.hpp:46
virtual std::unique_ptr< widget > build() const=0
Exception thrown when the height resizing has failed.
Basic exception when the layout doesn't fit.
Exception thrown when the width has been modified during resizing.
Exception thrown when the width resizing has failed.
Helper struct to force widgets the have the same size.
Definition: window.hpp:571
int height
The current height of all widgets in the group, -1 if the height is not linked.
Definition: window.hpp:584
int width
The current width of all widgets in the group, -1 if the width is not linked.
Definition: window.hpp:581
std::vector< widget * > widgets
The widgets linked.
Definition: window.hpp:578
window_definition(const config &cfg)
Definition: window.cpp:1384
static void layout(window &window, const unsigned maximum_width, const unsigned maximum_height)
Layouts the window.
Definition: window.cpp:1118
Holds a 2D point.
Definition: point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:49
void clip(const rect &r)
Clip this rectangle by the given rectangle.
Definition: rect.cpp:101
bool empty() const
False if both w and h are > 0, true otherwise.
Definition: rect.cpp:49
rect intersect(const rect &r) const
Calculates the intersection of this rectangle and another; that is, the maximal rectangle that is con...
Definition: rect.cpp:92
void shift(const point &p)
Shift the rectangle by the given relative position.
Definition: rect.cpp:106
rect & expand_to_cover(const rect &r)
Minimally expand this rect to fully contain another.
Definition: rect.cpp:86
Helper class, don't construct this directly.
mock_char c
Helper for header for the window.
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define FAIL(message)
#define VALIDATE(cond, message)
The macro to use for the validation of WML.