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