The Battle for Wesnoth  1.19.11+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  , owner_(nullptr)
254  , need_layout_(true)
255  , variables_()
256  , invalidate_layout_blocked_(false)
257  , hidden_(true)
258  , automatic_placement_(definition.automatic_placement)
259  , horizontal_placement_(definition.horizontal_placement)
260  , vertical_placement_(definition.vertical_placement)
261  , maximum_width_(definition.maximum_width)
262  , maximum_height_(definition.maximum_height)
263  , x_(definition.x)
264  , y_(definition.y)
265  , w_(definition.width)
266  , h_(definition.height)
267  , reevaluate_best_size_(definition.reevaluate_best_size)
268  , functions_(definition.functions)
269  , tooltip_(definition.tooltip)
270  , helptip_(definition.helptip)
271  , click_dismiss_(definition.click_dismiss)
272  , enter_disabled_(false)
273  , escape_disabled_(false)
274  , linked_size_()
275  , mouse_button_state_(0) /**< Needs to be initialized in @ref show. */
276 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
277  , debug_layout_(new debug_layout_graph(this))
278 #endif
279  , event_distributor_(new event::distributor(*this, event::dispatcher::front_child))
280  , exit_hook_([] { return true; })
281 {
282  manager::instance().add(*this);
283 
284  connect();
285 
286  for(const auto& [id, fixed_width, fixed_height] : definition.linked_groups) {
287  if(!init_linked_size_group(id, fixed_width, fixed_height)) {
288  FAIL(VGETTEXT("Linked ‘$id’ group has multiple definitions.", {{"id", id}}));
289  }
290  }
291 
292  const auto conf = cast_config_to<window_definition>();
293  assert(conf);
294 
295  if(conf->grid) {
296  init_grid(*conf->grid);
297  finalize(*definition.grid);
298  } else {
299  init_grid(*definition.grid);
300  }
301 
302  add_to_keyboard_chain(this);
303 
304  connect_signal<event::SDL_VIDEO_RESIZE>(std::bind(
305  &window::signal_handler_sdl_video_resize, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5));
306 
307  connect_signal<event::SDL_ACTIVATE>(std::bind(
308  &event::distributor::initialize_state, event_distributor_.get()));
309 
310  connect_signal<event::SDL_LEFT_BUTTON_UP>(
312  this,
313  std::placeholders::_2,
314  std::placeholders::_3,
315  std::placeholders::_4,
316  SDL_BUTTON_LMASK),
318  connect_signal<event::SDL_MIDDLE_BUTTON_UP>(
320  this,
321  std::placeholders::_2,
322  std::placeholders::_3,
323  std::placeholders::_4,
324  SDL_BUTTON_MMASK),
326  connect_signal<event::SDL_RIGHT_BUTTON_UP>(
328  this,
329  std::placeholders::_2,
330  std::placeholders::_3,
331  std::placeholders::_4,
332  SDL_BUTTON_RMASK),
334 
335  // FIXME investigate why this handler is being called twice and if this is actually needed
336  connect_signal<event::SDL_KEY_DOWN>(
337  std::bind(
338  &window::signal_handler_sdl_key_down, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5, std::placeholders::_6, false),
340  connect_signal<event::SDL_KEY_DOWN>(std::bind(
341  &window::signal_handler_sdl_key_down, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5, std::placeholders::_6, true));
342 
343  connect_signal<event::MESSAGE_SHOW_TOOLTIP>(
345  this,
346  std::placeholders::_2,
347  std::placeholders::_3,
348  std::placeholders::_5),
350 
351  connect_signal<event::MESSAGE_SHOW_HELPTIP>(
353  this,
354  std::placeholders::_2,
355  std::placeholders::_3,
356  std::placeholders::_5),
358 
359  connect_signal<event::REQUEST_PLACEMENT>(
360  std::bind(
361  &window::signal_handler_request_placement, this, std::placeholders::_2, std::placeholders::_3),
363 
364  connect_signal<event::CLOSE_WINDOW>(std::bind(&window::signal_handler_close_window, this));
365 
366  register_hotkey(hotkey::GLOBAL__HELPTIP,
367  [](auto&&...) { return helptip(); });
368 
369  /** @todo: should eventually become part of global hotkey handling. */
370  register_hotkey(hotkey::HOTKEY_FULLSCREEN,
371  [](auto&&...) { video::toggle_fullscreen(); return true; });
372 }
373 
375 {
376  /*
377  * We need to delete our children here instead of waiting for the grid to
378  * automatically do it. The reason is when the grid deletes its children
379  * they will try to unregister them self from the linked widget list. At
380  * this point the member of window are destroyed and we enter UB. (For
381  * some reason the bug didn't trigger on g++ but it does on MSVC.
382  */
383  for(unsigned row = 0; row < get_grid().get_rows(); ++row) {
384  for(unsigned col = 0; col < get_grid().get_cols(); ++col) {
385  get_grid().remove_child(row, col);
386  }
387  }
388 
389  /*
390  * The tip needs to be closed if the window closes and the window is
391  * not a tip. If we don't do that the tip will unrender in the next
392  * window and cause drawing glitches.
393  * Another issue is that on smallgui and an MP game the tooltip not
394  * unrendered properly can capture the mouse and make playing impossible.
395  */
398  }
399 
400  manager::instance().remove(*this);
401 
402  // If we are currently shown, then queue an undraw.
403  if(!hidden_) {
404  queue_redraw();
405  }
406 }
407 
409 {
410  return manager::instance().get_window(handle);
411 }
412 
413 retval window::get_retval_by_id(const std::string& id)
414 {
415  // Note it might change to a map later depending on the number
416  // of items.
417  if(id == "ok") {
418  return retval::OK;
419  } else if(id == "cancel" || id == "quit") {
420  return retval::CANCEL;
421  } else {
422  return retval::NONE;
423  }
424 }
425 
426 void window::show_tooltip(/*const unsigned auto_close_timeout*/)
427 {
428  // Unhide in any case.
429  hidden_ = false;
430 
431  // Connect to the event handler, if not yet connected.
432  if(!is_connected()) {
433  LOG_DP << "connecting " << id() << " on show_tooltip";
434  connect();
435  }
436 
437  log_scope2(log_gui_draw, "Window: show as tooltip.");
438 
439  generate_dot_file("show", SHOW);
440 
441  assert(status_ == status::NEW);
442 
445 
447 
448  /*
449  * Before show has been called, some functions might have done some testing
450  * on the window and called layout, which can give glitches. So
451  * reinvalidate the window to avoid those glitches.
452  */
454  queue_redraw();
455  DBG_DP << "show tooltip queued to " << get_rectangle();
456 }
457 
458 void window::show_non_modal(/*const unsigned auto_close_timeout*/)
459 {
460  // Unhide in any case.
461  hidden_ = false;
462 
463  // Connect to the event handler, if not yet connected.
464  if(!is_connected()) {
465  LOG_DP << "connecting " << id() << " on show_non_modal";
466  connect();
467  }
468 
469  log_scope2(log_gui_draw, "Window: show non modal.");
470 
471  generate_dot_file("show", SHOW);
472 
473  assert(status_ == status::NEW);
474 
476 
478 
479  /*
480  * Before show has been called, some functions might have done some testing
481  * on the window and called layout, which can give glitches. So
482  * reinvalidate the window to avoid those glitches.
483  */
485  queue_redraw();
486 
487  DBG_DP << "show non-modal queued to " << get_rectangle();
488 
490 }
491 
492 int window::show(const unsigned auto_close_timeout)
493 {
494  // Unhide in any case.
495  hidden_ = false;
496 
497  // Connect to the event handler, if not yet connected.
498  if(!is_connected()) {
499  LOG_DP << "connecting " << id() << " on show";
500  connect();
501  }
502 
503  /*
504  * Removes the old tip if one shown. The show_tip doesn't remove
505  * the tip, since it's the tip.
506  */
508 
510 
512 
513  generate_dot_file("show", SHOW);
514 
515  //assert(status_ == status::NEW);
516 
517  /*
518  * Before show has been called, some functions might have done some testing
519  * on the window and called layout, which can give glitches. So
520  * reinvalidate the window to avoid those glitches.
521  */
523  queue_redraw();
524 
525  // Make sure we display at least once in all cases.
526  events::draw();
527 
528  if(auto_close_timeout) {
529  SDL_Event event;
530  sdl::UserEvent data(CLOSE_WINDOW_EVENT, manager::instance().get_id(*this));
531 
532  event.type = CLOSE_WINDOW_EVENT;
533  event.user = data;
534 
535  delay_event(event, auto_close_timeout);
536  }
537 
538  try
539  {
540  // According to the comment in the next loop, we need to pump() once
541  // before we know which mouse buttons are down. Assume they're all
542  // down, otherwise there's a race condition when the MOUSE_UP gets
543  // processed in the first pump(), which immediately closes the window.
544  bool mouse_button_state_initialized = false;
545  mouse_button_state_ = std::numeric_limits<uint32_t>::max();
546 
547  // Start our loop, drawing will happen here as well.
549  // Process and handle all pending events.
550  events::pump();
551 
552  if(!mouse_button_state_initialized) {
553  /*
554  * The state must be initialize when showing the dialog.
555  * However when initialized before this point there were random
556  * errors. This only happened when the 'click' was done fast; a
557  * slower click worked properly.
558  *
559  * So it seems the events need to be processed before SDL can
560  * return the proper button state. When initializing here all
561  * works fine.
562  */
564  mouse_button_state_initialized = true;
565  }
566 
567  // See if we should close.
570  }
571 
572  // Update the display. This will rate limit to vsync.
573  events::draw();
574  }
575  }
576  catch(...)
577  {
578  // TODO: is this even necessary? What are we catching?
579  DBG_DP << "Caught general exception in show(): " << utils::get_unknown_exception_type();
580  hide();
581  throw;
582  }
583 
584  if(text_box_base* tb = dynamic_cast<text_box_base*>(event_distributor_->keyboard_focus())) {
585  tb->interrupt_composition();
586  }
587 
588  // The window may be kept around to be re-shown later. Hide it for now.
589  hide();
590 
591  return retval_;
592 }
593 
595 {
596  if(hidden_) {
597  return;
598  }
599 
600  // Draw background.
601  if(!this->draw_background()) {
602  // We may need to blur the background behind the window,
603  // but at this point it hasn't been rendered yet.
604  // We thus defer rendering to next frame so we can snapshot what
605  // is underneath the window without drawing over it.
607  return;
608  }
609 
610  // Draw children.
611  this->draw_children();
612 
613  // Draw foreground.
614  if(!this->draw_foreground()) {
616  }
617 
618  return;
619 }
620 
622 {
623  // Queue a redraw of the region if we were shown.
624  if(!hidden_) {
625  queue_redraw();
626  }
627 
628  // Disconnect from the event handler so we stop receiving events.
629  if(is_connected()) {
630  LOG_DP << "disconnecting " << id() << " on hide";
631  disconnect();
632  }
633 
634  hidden_ = true;
635 }
636 
638 {
639  point draw = get_size();
641 
642  // Check that the render buffer size is correct.
643  point buf_raw = render_buffer_.get_raw_size();
644  point buf_draw = render_buffer_.draw_size();
645  bool raw_size_changed = buf_raw.x != render.x || buf_raw.y != render.y;
646  bool draw_size_changed = buf_draw.x != draw.x || buf_draw.y != draw.y;
647  if (!raw_size_changed && !draw_size_changed) {
648  // buffers are fine
649  return;
650  }
651 
652  if(raw_size_changed) {
653  LOG_DP << "regenerating window render buffer as " << render;
654  render_buffer_ = texture(render.x, render.y, SDL_TEXTUREACCESS_TARGET);
655  }
656  if(raw_size_changed || draw_size_changed) {
657  LOG_DP << "updating window render buffer draw size to " << draw;
659  }
660 
661  // Clear the entire texture.
662  {
664  draw::clear();
665  }
666 
667  // Rerender everything.
668  queue_rerender();
669 }
670 
672 {
674 }
675 
676 void window::queue_rerender(const rect& screen_region)
677 {
678  // More than one region updating per-frame should be rare.
679  // Just rerender the minimal area that covers everything.
680  rect local_region = screen_region.intersect(get_rectangle());
681  local_region.shift(-get_origin());
682  awaiting_rerender_.expand_to_cover(local_region);
683 }
684 
685 void window::defer_region(const rect& screen_region)
686 {
687  LOG_DP << "deferring region " << screen_region;
688  deferred_regions_.push_back(screen_region);
689 }
690 
692 {
693  // Ensure our render texture is correctly sized.
695 
696  // Mark regions that were previously deferred for rerender and repaint.
697  for(auto& region : deferred_regions_) {
698  queue_redraw(region);
699  }
700  deferred_regions_.clear();
701 
702  // Render the portion of the window awaiting rerender (if any).
703  if (awaiting_rerender_.empty()) {
704  return;
705  }
706 
707  DBG_DP << "window::render() local " << awaiting_rerender_;
708  auto target_setter = draw::set_render_target(render_buffer_);
709  auto clip_setter = draw::override_clip(awaiting_rerender_);
710 
711  // Clear the to-be-rendered area unconditionally
712  draw::clear();
713 
714  draw();
716 }
717 
718 bool window::expose(const rect& region)
719 {
720  DBG_DP << "window::expose " << region;
721 
722  // Calculate the destination region we need to draw.
723  rect dst = get_rectangle().intersect(region);
725  if (dst.empty()) {
726  return false;
727  }
728 
729  // Blit from the pre-rendered buffer.
730  rect src = dst;
731  src.shift(-get_origin());
735 
736  return true;
737 }
738 
740 {
741  if(hidden_) {
742  return {0,0,0,0};
743  }
744  return get_rectangle();
745 }
746 
748  : window_(window)
749 {
752 }
753 
755 {
756  assert(window_.invalidate_layout_blocked_);
757  window_.invalidate_layout_blocked_ = false;
758 }
759 
761 {
763  need_layout_ = true;
764  }
765 }
766 widget* window::find_at(const point& coordinate, const bool must_be_active)
767 {
768  return panel::find_at(coordinate, must_be_active);
769 }
770 
772  const bool must_be_active) const
773 {
774  return panel::find_at(coordinate, must_be_active);
775 }
776 
777 widget* window::find(const std::string_view id, const bool must_be_active)
778 {
779  return container_base::find(id, must_be_active);
780 }
781 
782 const widget* window::find(const std::string_view id, const bool must_be_active)
783  const
784 {
785  return container_base::find(id, must_be_active);
786 }
787 
788 bool window::init_linked_size_group(const std::string& id, const bool fixed_width, const bool fixed_height)
789 {
790  assert(fixed_width || fixed_height);
791  auto [iter, success] = linked_size_.try_emplace(id, fixed_width, fixed_height);
792  return success;
793 }
794 
795 bool window::has_linked_size_group(const std::string& id)
796 {
797  return linked_size_.find(id) != linked_size_.end();
798 }
799 
800 void window::add_linked_widget(const std::string& id, widget* wgt)
801 {
802  assert(wgt);
803  if(!has_linked_size_group(id)) {
804  ERR_GUI << "Unknown linked group '" << id << "'; skipping";
805  return;
806  }
807 
808  std::vector<widget*>& widgets = linked_size_[id].widgets;
809  if(std::find(widgets.begin(), widgets.end(), wgt) == widgets.end()) {
810  widgets.push_back(wgt);
811  }
812 }
813 
814 void window::remove_linked_widget(const std::string& id, const widget* wgt)
815 {
816  assert(wgt);
817  if(!has_linked_size_group(id)) {
818  return;
819  }
820 
821  std::vector<widget*>& widgets = linked_size_[id].widgets;
822  auto itor = std::find(widgets.begin(), widgets.end(), wgt);
823 
824  if(itor != widgets.end()) {
825  widgets.erase(itor);
826 
827  assert(std::find(widgets.begin(), widgets.end(), wgt)
828  == widgets.end());
829  }
830 }
831 
833 {
834  if(!need_layout_) {
835  return;
836  }
837  DBG_DP << "window::layout";
838 
839  /***** Initialize. *****/
840 
841  const auto conf = cast_config_to<window_definition>();
842  assert(conf);
843 
845 
847  const point mouse = get_mouse_position();
848 
849  variables_.add("mouse_x", wfl::variant(mouse.x));
850  variables_.add("mouse_y", wfl::variant(mouse.y));
851  variables_.add("window_width", wfl::variant(0));
852  variables_.add("window_height", wfl::variant(0));
853  variables_.add("best_window_width", wfl::variant(size.x));
854  variables_.add("best_window_height", wfl::variant(size.y));
855  variables_.add("size_request_mode", wfl::variant("maximum"));
856 
858 
859  unsigned int maximum_width = maximum_width_(variables_, &functions_);
860  unsigned int maximum_height = maximum_height_(variables_, &functions_);
861 
863  if(maximum_width == 0 || maximum_width > settings::screen_width) {
864  maximum_width = settings::screen_width;
865  }
866 
867  if(maximum_height == 0 || maximum_height > settings::screen_height) {
868  maximum_height = settings::screen_height;
869  }
870  } else {
871  maximum_width = w_(variables_, &functions_);
872  maximum_height = h_(variables_, &functions_);
873  }
874 
875  /***** Handle click dismiss status. *****/
876  button* click_dismiss_button = find_widget<button>("click_dismiss", false, false);
877  if(click_dismiss_button) {
878  click_dismiss_button->set_visible(widget::visibility::invisible);
879  }
880  if(click_dismiss_) {
881  button* btn = find_widget<button>("ok", false, false);
882  if(btn) {
884  click_dismiss_button = btn;
885  }
886  VALIDATE(click_dismiss_button,
887  _("Click dismiss needs a ‘click_dismiss’ or ‘ok’ button."));
888  }
889 
890  /***** Layout. *****/
891  layout_initialize(true);
892  generate_dot_file("layout_initialize", LAYOUT);
893 
895 
896  try
897  {
898  window_implementation::layout(*this, maximum_width, maximum_height);
899  }
900  catch(const layout_exception_resize_failed&)
901  {
902 
903  /** @todo implement the scrollbars on the window. */
904 
905  std::stringstream sstr;
906  sstr << __FILE__ << ":" << __LINE__ << " in function '" << __func__
907  << "' found the following problem: Failed to size window;"
908  << " wanted size " << get_best_size() << " available size "
909  << maximum_width << ',' << maximum_height << " screen size "
911 
912  throw wml_exception(_("Failed to show a dialog, "
913  "which doesn’t fit on the screen."),
914  sstr.str());
915  }
916 
917  /****** Validate click dismiss status. *****/
919  assert(click_dismiss_button);
920  click_dismiss_button->set_visible(widget::visibility::visible);
921 
923  *click_dismiss_button,
924  std::bind(&window::set_retval, this, retval::OK, true));
925 
926  layout_initialize(true);
927  generate_dot_file("layout_initialize", LAYOUT);
928 
930 
931  try
932  {
934  *this, maximum_width, maximum_height);
935  }
936  catch(const layout_exception_resize_failed&)
937  {
938 
939  /** @todo implement the scrollbars on the window. */
940 
941  std::stringstream sstr;
942  sstr << __FILE__ << ":" << __LINE__ << " in function '" << __func__
943  << "' found the following problem: Failed to size window;"
944  << " wanted size " << get_best_size() << " available size "
945  << maximum_width << ',' << maximum_height << " screen size "
947  << '.';
948 
949  throw wml_exception(_("Failed to show a dialog, "
950  "which doesn’t fit on the screen."),
951  sstr.str());
952  }
953  }
954 
955  /***** Get the best location for the window *****/
956  size = get_best_size();
957  /* Although 0-size windows might not seem valid/useful, there are
958  a handful of windows that request 0 size just to get a position
959  chosen via the code below, so at least for now allow them:
960  */
961  assert(size.x >= 0 && static_cast<unsigned>(size.x) <= maximum_width
962  && size.y >= 0 && static_cast<unsigned>(size.y) <= maximum_height);
963 
964  point origin(0, 0);
965 
967 
968  switch(horizontal_placement_) {
970  // Do nothing
971  break;
973  origin.x = (settings::screen_width - size.x) / 2;
974  break;
976  origin.x = settings::screen_width - size.x;
977  break;
978  default:
979  assert(false);
980  }
981  switch(vertical_placement_) {
983  // Do nothing
984  break;
986  origin.y = (settings::screen_height - size.y) / 2;
987  break;
989  origin.y = settings::screen_height - size.y;
990  break;
991  default:
992  assert(false);
993  }
994  } else {
995 
996  variables_.add("window_width", wfl::variant(size.x));
997  variables_.add("window_height", wfl::variant(size.y));
998 
1000  layout_initialize(true);
1001 
1004  h_(variables_, &functions_));
1005 
1006  size = get_best_size();
1007  variables_.add("window_width", wfl::variant(size.x));
1008  variables_.add("window_height", wfl::variant(size.y));
1009  }
1010 
1011  variables_.add("size_request_mode", wfl::variant("size"));
1012 
1013  size.x = w_(variables_, &functions_);
1014  size.y = h_(variables_, &functions_);
1015 
1016  variables_.add("window_width", wfl::variant(size.x));
1017  variables_.add("window_height", wfl::variant(size.y));
1018 
1019  origin.x = x_(variables_, &functions_);
1020  origin.y = y_(variables_, &functions_);
1021  }
1022 
1023  /***** Set the window size *****/
1024  place(origin, size);
1025 
1026  generate_dot_file("layout_finished", LAYOUT);
1027  need_layout_ = false;
1028 
1030 }
1031 
1033 {
1034  // evaluate the group sizes
1035  for(auto & linked_size : linked_size_)
1036  {
1037 
1038  point max_size(0, 0);
1039 
1040  // Determine the maximum size.
1041  for(auto widget : linked_size.second.widgets)
1042  {
1043 
1044  const point size = widget->get_best_size();
1045 
1046  if(size.x > max_size.x) {
1047  max_size.x = size.x;
1048  }
1049  if(size.y > max_size.y) {
1050  max_size.y = size.y;
1051  }
1052  }
1053  if(linked_size.second.width != -1) {
1054  linked_size.second.width = max_size.x;
1055  }
1056  if(linked_size.second.height != -1) {
1057  linked_size.second.height = max_size.y;
1058  }
1059 
1060  // Set the maximum size.
1061  for(auto widget : linked_size.second.widgets)
1062  {
1063 
1065 
1066  if(linked_size.second.width != -1) {
1067  size.x = max_size.x;
1068  }
1069  if(linked_size.second.height != -1) {
1070  size.y = max_size.y;
1071  }
1072 
1074  }
1075  }
1076 }
1077 
1078 bool window::click_dismiss(const int mouse_button_mask)
1079 {
1080  if(does_click_dismiss()) {
1081  if((mouse_button_state_ & mouse_button_mask) == 0) {
1083  } else {
1084  mouse_button_state_ &= ~mouse_button_mask;
1085  }
1086  return true;
1087  }
1088  return false;
1089 }
1090 
1091 void window::finalize(const builder_grid& content_grid)
1092 {
1093  auto widget = content_grid.build();
1094  assert(widget);
1095 
1096  static const std::string id = "_window_content_grid";
1097 
1098  // Make sure the new child has same id.
1099  widget->set_id(id);
1100 
1101  auto* parent_grid = get_grid().find_widget<grid>(id, true, false);
1102  assert(parent_grid);
1103 
1104  if(grid* grandparent_grid = dynamic_cast<grid*>(parent_grid->parent())) {
1105  grandparent_grid->swap_child(id, std::move(widget), false);
1106  } else if(container_base* c = dynamic_cast<container_base*>(parent_grid->parent())) {
1107  c->get_grid().swap_child(id, std::move(widget), true);
1108  } else {
1109  assert(false);
1110  }
1111 }
1112 
1113 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
1114 
1115 void window::generate_dot_file(const std::string& generator,
1116  const unsigned domain)
1117 {
1118  debug_layout_->generate_dot_file(generator, domain);
1119 }
1120 #endif
1121 
1123  const unsigned maximum_width,
1124  const unsigned maximum_height)
1125 {
1127 
1128  /*
1129  * For now we return the status, need to test later whether this can
1130  * entirely be converted to an exception based system as in 'promised' on
1131  * the algorithm page.
1132  */
1133 
1134  try
1135  {
1137 
1138  DBG_GUI_L << LOG_IMPL_HEADER << " best size : " << size
1139  << " maximum size : " << maximum_width << ','
1140  << maximum_height << ".";
1141  if(size.x <= static_cast<int>(maximum_width)
1142  && size.y <= static_cast<int>(maximum_height)) {
1143 
1144  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Fits, nothing to do.";
1145  return;
1146  }
1147 
1148  if(size.x > static_cast<int>(maximum_width)) {
1149  window.reduce_width(maximum_width);
1150 
1152  if(size.x > static_cast<int>(maximum_width)) {
1153  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resize width failed."
1154  << " Wanted width " << maximum_width
1155  << " resulting width " << size.x << ".";
1157  }
1159  << " Status: Resize width succeeded.";
1160  }
1161 
1162  if(size.y > static_cast<int>(maximum_height)) {
1163  window.reduce_height(maximum_height);
1164 
1166  if(size.y > static_cast<int>(maximum_height)) {
1167  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resize height failed."
1168  << " Wanted height " << maximum_height
1169  << " resulting height " << size.y << ".";
1171  }
1173  << " Status: Resize height succeeded.";
1174  }
1175 
1176  assert(size.x <= static_cast<int>(maximum_width)
1177  && size.y <= static_cast<int>(maximum_height));
1178 
1179 
1180  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resizing succeeded.";
1181  return;
1182  }
1183  catch(const layout_exception_width_modified&)
1184  {
1186  << " Status: Width has been modified, rerun.";
1187 
1188  window.layout_initialize(false);
1190  layout(window, maximum_width, maximum_height);
1191  return;
1192  }
1193 }
1194 
1195 void window::mouse_capture(const bool capture)
1196 {
1197  assert(event_distributor_);
1198  event_distributor_->capture_mouse(capture);
1199 }
1200 
1202 {
1203  assert(event_distributor_);
1204  event_distributor_->keyboard_capture(widget);
1205 }
1206 
1208 {
1209  assert(event_distributor_);
1210  event_distributor_->keyboard_add_to_chain(widget);
1211 }
1212 
1214 {
1215  assert(event_distributor_);
1216  event_distributor_->keyboard_remove_from_chain(widget);
1217 }
1218 
1220 {
1221  if(std::find(tab_order.begin(), tab_order.end(), widget) != tab_order.end()) {
1222  return;
1223  }
1224  assert(event_distributor_);
1225  if(tab_order.empty() && !event_distributor_->keyboard_focus()) {
1227  }
1228  if(at < 0 || at >= static_cast<int>(tab_order.size())) {
1229  tab_order.push_back(widget);
1230  } else {
1231  tab_order.insert(tab_order.begin() + at, widget);
1232  }
1233 }
1234 
1236  bool& handled,
1237  const point& new_size)
1238 {
1239  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1240 
1243  settings::screen_width = new_size.x;
1244  settings::screen_height = new_size.y;
1246 
1247  handled = true;
1248 }
1249 
1251  bool& handled,
1252  bool& halt,
1253  const int mouse_button_mask)
1254 {
1255  DBG_GUI_E << LOG_HEADER << ' ' << event << " mouse_button_mask "
1256  << static_cast<unsigned>(mouse_button_mask) << ".";
1257 
1258  handled = halt = click_dismiss(mouse_button_mask);
1259 }
1260 
1261 static bool is_active(const widget* wgt)
1262 {
1263  if(const styled_widget* control = dynamic_cast<const styled_widget*>(wgt)) {
1264  return control->get_active() && control->get_visible() == widget::visibility::visible;
1265  }
1266  return false;
1267 }
1268 
1270  bool& handled,
1271  const SDL_Keycode key,
1272  const SDL_Keymod mod,
1273  bool handle_tab)
1274 {
1275  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1276 
1277  if(text_box_base* tb = dynamic_cast<text_box_base*>(event_distributor_->keyboard_focus())) {
1278  if(tb->is_composing()) {
1279  if(handle_tab && !tab_order.empty() && key == SDLK_TAB) {
1280  tb->interrupt_composition();
1281  } else {
1282  return;
1283  }
1284  }
1285  }
1286  if(key == SDLK_KP_ENTER || key == SDLK_RETURN) {
1287  if (mod & (KMOD_CTRL | KMOD_ALT | KMOD_GUI | KMOD_SHIFT)) {
1288  // Don't handle if modifier is pressed
1289  handled = false;
1290  } else {
1291  // Trigger window OK button only if Enter enabled,
1292  // otherwise pass handling to widget
1293  if (!enter_disabled_) {
1295  handled = true;
1296  }
1297  }
1298  } else if(key == SDLK_ESCAPE && !escape_disabled_) {
1300  handled = true;
1301  } else if(key == SDLK_SPACE) {
1302  handled = click_dismiss(0);
1303  } else if(handle_tab && !tab_order.empty() && key == SDLK_TAB) {
1304  assert(event_distributor_);
1305  widget* focus = event_distributor_->keyboard_focus();
1306  auto iter = std::find(tab_order.begin(), tab_order.end(), focus);
1307  do {
1308  if(mod & KMOD_SHIFT) {
1309  if(iter == tab_order.begin()) {
1310  iter = tab_order.end();
1311  }
1312  iter--;
1313  } else {
1314  if(iter == tab_order.end()) {
1315  iter = tab_order.begin();
1316  } else {
1317  iter++;
1318  if(iter == tab_order.end()) {
1319  iter = tab_order.begin();
1320  }
1321  }
1322  }
1323  } while(!is_active(*iter));
1324  keyboard_capture(*iter);
1325  handled = true;
1326  }
1327 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
1328  if(key == SDLK_F12) {
1329  debug_layout_->generate_dot_file("manual", debug_layout_graph::MANUAL);
1330  handled = true;
1331  }
1332 #endif
1333 }
1334 
1336  bool& handled,
1337  const event::message& message)
1338 {
1339  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1340 
1341  const event::message_show_tooltip& request
1342  = dynamic_cast<const event::message_show_tooltip&>(message);
1343 
1344  dialogs::tip::show(tooltip_.id, request.message, request.location, request.source_rect);
1345 
1346  handled = true;
1347 }
1348 
1350  bool& handled,
1351  const event::message& message)
1352 {
1353  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1354 
1355  const event::message_show_helptip& request
1356  = dynamic_cast<const event::message_show_helptip&>(message);
1357 
1358  dialogs::tip::show(helptip_.id, request.message, request.location, request.source_rect);
1359 
1360  handled = true;
1361 }
1362 
1364  bool& handled)
1365 {
1366  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1367 
1369 
1370  handled = true;
1371 }
1372 
1374 {
1376 }
1377 
1378 // }---------- DEFINITION ---------{
1379 
1382 {
1383  DBG_GUI_P << "Parsing window " << id;
1384 
1385  load_resolutions<resolution>(cfg);
1386 }
1387 
1389  : panel_definition::resolution(cfg), grid(nullptr)
1390 {
1391  auto child = cfg.optional_child("grid");
1392  // VALIDATE(child, _("No grid defined."));
1393 
1394  /** @todo Evaluate whether the grid should become mandatory. */
1395  if(child) {
1396  grid = std::make_shared<builder_grid>(*child);
1397  }
1398 }
1399 
1400 // }------------ END --------------
1401 
1402 } // namespace gui2
1403 
1404 
1405 /**
1406  * @page layout_algorithm Layout algorithm
1407  *
1408  * @section introduction-layout_algorithm Introduction
1409  *
1410  * This page describes how the layout engine for the dialogs works. First
1411  * a global overview of some terms used in this document.
1412  *
1413  * - @ref gui2::widget "Widget"; Any item which can be used in the widget
1414  * toolkit. Not all widgets are visible. In general widgets cannot be
1415  * sized directly, but this is controlled by a window. A widget has an
1416  * internal size cache and if the value in the cache is not equal to 0,0
1417  * that value is its best size. This value gets set when the widget can
1418  * honor a resize request. It will be set with the value which honors
1419  * the request.
1420  *
1421  * - @ref gui2::grid "Grid"; A grid is an invisible container which holds
1422  * one or more widgets. Several widgets have a grid in them to hold
1423  * multiple widgets eg panels and windows.
1424  *
1425  * - @ref gui2::grid::child "Grid cell"; Every widget which is in a grid is
1426  * put in a grid cell. These cells also hold the information about the gaps
1427  * between widgets the behavior on growing etc. All grid cells must have a
1428  * widget inside them.
1429  *
1430  * - @ref gui2::window "Window"; A window is a top level item which has a
1431  * grid with its children. The window handles the sizing of the window and
1432  * makes sure everything fits.
1433  *
1434  * - @ref gui2::window::linked_size "Shared size group"; A shared size
1435  * group is a number of widgets which share width and or height. These
1436  * widgets are handled separately in the layout algorithm. All grid cells
1437  * width such a widget will get the same height and or width and these
1438  * widgets won't be resized when there's not enough size. To be sure that
1439  * these widgets don't cause trouble for the layout algorithm, they must be
1440  * in a container with scrollbars so there will always be a way to properly
1441  * layout them. The engine must enforce this restriction so the shared
1442  * layout property must be set by the engine after validation.
1443  *
1444  * - All visible grid cells; A grid cell is visible when the widget inside
1445  * of it doesn't have the state visibility::invisible. Widgets which have the
1446  * state visibility::hidden are sized properly since when they become
1447  * visibility::visible the layout shouldn't be invalidated. A grid cell
1448  * that's invisible has size 0,0.
1449  *
1450  * - All resizable grid cells; A grid cell is resizable under the following
1451  * conditions:
1452  * - The widget is visibility::visible.
1453  * - The widget is not in a shared size group.
1454  *
1455  * There are two layout algorithms with a different purpose.
1456  *
1457  * - The Window algorithm; this algorithm's goal is it to make sure all grid
1458  * cells fit in the window. Sizing the grid cells depends on the widget
1459  * size as well, but this algorithm only sizes the grid cells and doesn't
1460  * handle the widgets inside them.
1461  *
1462  * - The Grid algorithm; after the Window algorithm made sure that all grid
1463  * cells fit this algorithm makes sure the widgets are put in the optimal
1464  * state in their grid cell.
1465  *
1466  * @section layout_algorithm_window Window
1467  *
1468  * Here is the algorithm used to layout the window:
1469  *
1470  * - Perform a full initialization
1471  * (@ref gui2::widget::layout_initialize (full_initialization = true)):
1472  * - Clear the internal best size cache for all widgets.
1473  * - For widgets with scrollbars hide them unless the
1474  * @ref gui2::scrollbar_container::scrollbar_mode "scrollbar_mode" is
1475  * ALWAYS_VISIBLE or AUTO_VISIBLE.
1476  * - Handle shared sizes:
1477  * - Height and width:
1478  * - Get the best size for all widgets that share height and width.
1479  * - Set the maximum of width and height as best size for all these
1480  * widgets.
1481  * - Width only:
1482  * - Get the best width for all widgets which share their width.
1483  * - Set the maximum width for all widgets, but keep their own height.
1484  * - Height only:
1485  * - Get the best height for all widgets which share their height.
1486  * - Set the maximum height for all widgets, but keep their own width.
1487  * - Start layout loop:
1488  * - Get best size.
1489  * - If width <= maximum_width && height <= maximum_height we're done.
1490  * - If width > maximum_width, optimize the width:
1491  * - For every grid cell in a grid row there will be a resize request
1492  * (@ref gui2::grid::reduce_width):
1493  * - Sort the widgets in the row on the resize priority.
1494  * - Loop through this priority queue until the row fits
1495  * - If priority != 0 try to share the extra width else all
1496  * widgets are tried to reduce the full size.
1497  * - Try to shrink the widgets by either wrapping or using a
1498  * scrollbar (@ref gui2::widget::request_reduce_width).
1499  * - If the row fits in the wanted width this row is done.
1500  * - Else try the next priority.
1501  * - All priorities done and the width still doesn't fit.
1502  * - Loop through this priority queue until the row fits.
1503  * - If priority != 0:
1504  * - try to share the extra width
1505  * -Else:
1506  * - All widgets are tried to reduce the full size.
1507  * - Try to shrink the widgets by sizing them smaller as really
1508  * wanted (@ref gui2::widget::demand_reduce_width).
1509  * For labels, buttons etc. they get ellipsized.
1510  * - If the row fits in the wanted width this row is done.
1511  * - Else try the next priority.
1512  * - All priorities done and the width still doesn't fit.
1513  * - Throw a layout width doesn't fit exception.
1514  * - If height > maximum_height, optimize the height
1515  * (@ref gui2::grid::reduce_height):
1516  * - For every grid cell in a grid column there will be a resize request:
1517  * - Sort the widgets in the column on the resize priority.
1518  * - Loop through this priority queue until the column fits:
1519  * - If priority != 0 try to share the extra height else all
1520  * widgets are tried to reduce the full size.
1521  * - Try to shrink the widgets by using a scrollbar
1522  * (@ref gui2::widget::request_reduce_height).
1523  * - If succeeded for a widget the width is influenced and the
1524  * width might be invalid.
1525  * - Throw a width modified exception.
1526  * - If the column fits in the wanted height this column is done.
1527  * - Else try the next priority.
1528  * - All priorities done and the height still doesn't fit.
1529  * - Loop through this priority queue until the column fits.
1530  * - If priority != 0 try to share the extra height else all
1531  * widgets are tried to reduce the full size.
1532  * - Try to shrink the widgets by sizing them smaller as really
1533  * wanted (@ref gui2::widget::demand_reduce_width).
1534  * For labels, buttons etc. they get ellipsized .
1535  * - If the column fits in the wanted height this column is done.
1536  * - Else try the next priority.
1537  * - All priorities done and the height still doesn't fit.
1538  * - Throw a layout height doesn't fit exception.
1539  * - End layout loop.
1540  *
1541  * - Catch @ref gui2::layout_exception_width_modified "width modified":
1542  * - Goto relayout.
1543  *
1544  * - Catch
1545  * @ref gui2::layout_exception_width_resize_failed "width resize failed":
1546  * - If the window has a horizontal scrollbar which isn't shown but can be
1547  * shown.
1548  * - Show the scrollbar.
1549  * - goto relayout.
1550  * - Else show a layout failure message.
1551  *
1552  * - Catch
1553  * @ref gui2::layout_exception_height_resize_failed "height resize failed":
1554  * - If the window has a vertical scrollbar which isn't shown but can be
1555  * shown:
1556  * - Show the scrollbar.
1557  * - goto relayout.
1558  * - Else:
1559  * - show a layout failure message.
1560  *
1561  * - Relayout:
1562  * - Initialize all widgets
1563  * (@ref gui2::widget::layout_initialize (full_initialization = false))
1564  * - Handle shared sizes, since the reinitialization resets that state.
1565  * - Goto start layout loop.
1566  *
1567  * @section grid Grid
1568  *
1569  * This section will be documented later.
1570  */
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: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:1213
void keyboard_capture(widget *widget)
Definition: window.cpp:1201
void invalidate_layout()
Updates the size of the window.
Definition: window.cpp:760
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1207
void signal_handler_message_show_tooltip(const event::ui_event event, bool &handled, const event::message &message)
Definition: window.cpp:1335
void mouse_capture(const bool capture=true)
Definition: window.cpp:1195
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:739
void update_render_textures()
Ensure render textures are valid and correct.
Definition: window.cpp:637
void show_tooltip()
Shows the window as a tooltip.
Definition: window.cpp:426
int mouse_button_state_
The state of the mouse button.
Definition: window.hpp:644
void hide()
Hides the window.
Definition: window.cpp:621
void show_non_modal()
Shows the window non modal.
Definition: window.cpp:458
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:1235
virtual ~window()
Definition: window.cpp:374
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:408
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:1269
virtual void layout() override
Lays out the window.
Definition: window.cpp:832
void signal_handler_message_show_helptip(const event::ui_event event, bool &handled, const event::message &message)
Definition: window.cpp:1349
void layout_linked_widgets()
Layouts the linked widgets.
Definition: window.cpp:1032
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:777
int show(unsigned auto_close_timeout=0)
Shows the window, running an event loop until it should close.
Definition: window.cpp:492
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:1363
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:1373
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:800
void defer_region(const rect &region)
Defer rendering of a particular region to next frame.
Definition: window.cpp:685
const unsigned horizontal_placement_
Sets the horizontal placement.
Definition: window.hpp:514
void queue_rerender()
Definition: window.cpp:671
static retval get_retval_by_id(const std::string &id)
Gets the retval for the default buttons.
Definition: window.cpp:413
void add_to_tab_order(widget *widget, int at=-1)
Add the widget to the tabbing order.
Definition: window.cpp:1219
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:1091
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:1078
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:718
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:814
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:1250
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:691
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:788
void draw()
Draws the window.
Definition: window.cpp:594
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:766
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:795
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: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:734
clip_setter override_clip(const SDL_Rect &clip)
Override the clipping area.
Definition: draw.cpp:567
void clear()
Clear the current render target.
Definition: draw.cpp:42
void blit(const texture &tex, const SDL_Rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition: draw.cpp:380
::rect get_clip()
Get the current clipping area, in draw coordinates.
Definition: draw.cpp:592
void draw()
Trigger a draw cycle.
Definition: events.cpp:725
void pump_and_draw()
pump() then immediately draw()
Definition: events.hpp:152
void pump()
Process all events currently in the queue.
Definition: events.cpp:483
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:163
void init_mouse_location()
Initializes the location of the mouse.
Definition: handler.cpp:881
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:1261
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
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: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 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:1380
static void layout(window &window, const unsigned maximum_width, const unsigned maximum_height)
Layouts the window.
Definition: window.cpp:1122
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:49
rect & expand_to_cover(const SDL_Rect &r)
Minimally expand this rect to fully contain another.
Definition: rect.cpp:86
void clip(const SDL_Rect &r)
Clip this rectangle by the given rectangle.
Definition: rect.cpp:101
void shift(const point &p)
Shift the rectangle by the given relative position.
Definition: rect.cpp:106
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:92
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.