1 /*
2 Copyright (C) 2009 - 2018 by Mark de Wever <koraq@xs4all.nl>
3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY.
11
12 See the COPYING file for more details.
13 */
14
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16
17 #include "gui/widgets/stacked_widget.hpp"
18
19 #include "gui/auxiliary/find_widget.hpp"
20 #include "gui/core/register_widget.hpp"
21 #include "gui/widgets/settings.hpp"
22 #include "gui/widgets/widget_helpers.hpp"
23 #include "gui/widgets/generator.hpp"
24 #include "gettext.hpp"
25 #include "utils/const_clone.hpp"
26 #include "utils/general.hpp"
27
28 #include "utils/functional.hpp"
29
30 namespace gui2
31 {
32
33 // ------------ WIDGET -----------{
34
35 REGISTER_WIDGET(stacked_widget)
36
37 struct stacked_widget_implementation
38 {
39 template<typename W>
findgui2::stacked_widget_implementation40 static W* find(utils::const_clone_ref<stacked_widget, W> stack,
41 const std::string& id,
42 const bool must_be_active)
43 {
44 // Use base method if find-in-all-layer isn't set.
45 if(!stack.find_in_all_layers_) {
46 return stack.container_base::find(id, must_be_active);
47 }
48
49 for(unsigned i = 0; i < stack.get_layer_count(); ++i) {
50 if(W* res = stack.get_layer_grid(i)->find(id, must_be_active)) {
51 return res;
52 }
53 }
54
55 return stack.container_base::find(id, must_be_active);
56 }
57 };
58
stacked_widget(const implementation::builder_stacked_widget & builder)59 stacked_widget::stacked_widget(const implementation::builder_stacked_widget& builder)
60 : container_base(builder, type())
61 , generator_(generator_base::build(false, false, generator_base::independent, false))
62 , selected_layer_(-1)
63 , find_in_all_layers_(false)
64 {
65 }
66
get_active() const67 bool stacked_widget::get_active() const
68 {
69 return true;
70 }
71
get_state() const72 unsigned stacked_widget::get_state() const
73 {
74 return 0;
75 }
76
layout_children()77 void stacked_widget::layout_children()
78 {
79 assert(generator_);
80 for(unsigned i = 0; i < generator_->get_item_count(); ++i) {
81 generator_->item(i).layout_children();
82 }
83 }
84
85 void
finalize(std::vector<builder_grid_const_ptr> widget_builder)86 stacked_widget::finalize(std::vector<builder_grid_const_ptr> widget_builder)
87 {
88 assert(generator_);
89 string_map empty_data;
90 for(const auto & builder : widget_builder)
91 {
92 generator_->create_item(-1, builder, empty_data, nullptr);
93 }
94 swap_grid(nullptr, &get_grid(), generator_, "_content_grid");
95
96 select_layer(-1);
97 }
98
set_self_active(const bool)99 void stacked_widget::set_self_active(const bool /*active*/)
100 {
101 /* DO NOTHING */
102 }
103
select_layer_impl(std::function<bool (unsigned int i)> display_condition)104 void stacked_widget::select_layer_impl(std::function<bool(unsigned int i)> display_condition)
105 {
106 const unsigned int num_layers = get_layer_count();
107
108 // Deselect all layers except the chosen ones.
109 for(unsigned int i = 0; i < num_layers; ++i) {
110 const bool selected = display_condition(i);
111
112 /* Selecting a previously selected item will deselect it, regardless of the what is passed to
113 * select_item. This causes issues if this function is called when all layers are visible (for
114 * example, initialization). For layers other than the chosen one, this is the desired behavior.
115 * However the chosen layer could *also* be deselected undesirably due to the conditions outlined
116 * above, and as this widget's generator does not stipulate a minimum selection, it's possible to
117 * end up with no layers visible at all.
118 *
119 * This works around that by performing no selection unless necessary to change states.
120 */
121 if(generator_->is_selected(i) != selected) {
122 generator_->select_item(i, selected);
123 }
124 }
125
126 // If we already have our chosen layers, exit.
127 if(selected_layer_ >= 0) {
128 return;
129 }
130
131 // Else, re-show all layers.
132 for(unsigned int i = 0; i < num_layers; ++i) {
133 /* By design, only the last selected item will receive events even if multiple items are visible
134 * and said item is not at the top of the stack. If this point is reached, all layers have already
135 * been hidden by the loop above, so the last layer selected will be the top-most one, as desired.
136 */
137 generator_->select_item(i, true);
138 }
139 }
140
update_selected_layer_index(const int i)141 void stacked_widget::update_selected_layer_index(const int i)
142 {
143 selected_layer_ = utils::clamp<int>(i, -1, get_layer_count() - 1);
144 }
145
layer_selected(const unsigned layer)146 bool stacked_widget::layer_selected(const unsigned layer)
147 {
148 assert(layer < get_layer_count());
149 return generator_->is_selected(layer);
150 }
151
select_layer(const int layer)152 void stacked_widget::select_layer(const int layer)
153 {
154 update_selected_layer_index(layer);
155
156 select_layer_impl([this](unsigned int i)
157 {
158 return i == static_cast<unsigned int>(selected_layer_);
159 });
160 }
161
select_layers(const boost::dynamic_bitset<> & mask)162 void stacked_widget::select_layers(const boost::dynamic_bitset<>& mask)
163 {
164 assert(mask.size() == get_layer_count());
165
166 select_layer_impl([&](unsigned int i)
167 {
168 if(mask[i]) {
169 update_selected_layer_index(i);
170 }
171
172 return mask[i];
173 });
174 }
175
get_layer_count() const176 unsigned int stacked_widget::get_layer_count() const
177 {
178 return generator_->get_item_count();
179 }
180
get_layer_grid(unsigned int i)181 grid* stacked_widget::get_layer_grid(unsigned int i)
182 {
183 assert(generator_);
184 return &generator_->item(i);
185 }
186
get_layer_grid(unsigned int i) const187 const grid* stacked_widget::get_layer_grid(unsigned int i) const
188 {
189 assert(generator_);
190 return &generator_->item(i);
191 }
192
find(const std::string & id,const bool must_be_active)193 widget* stacked_widget::find(const std::string& id, const bool must_be_active)
194 {
195 return stacked_widget_implementation::find<widget>(*this, id, must_be_active);
196 }
197
find(const std::string & id,const bool must_be_active) const198 const widget* stacked_widget::find(const std::string& id, const bool must_be_active) const
199 {
200 return stacked_widget_implementation::find<const widget>(*this, id, must_be_active);
201 }
202
203 // }---------- DEFINITION ---------{
204
stacked_widget_definition(const config & cfg)205 stacked_widget_definition::stacked_widget_definition(const config& cfg)
206 : styled_widget_definition(cfg)
207 {
208 DBG_GUI_P << "Parsing stacked widget " << id << '\n';
209
210 load_resolutions<resolution>(cfg);
211 }
212
213 /*WIKI
214 * @page = GUIWidgetDefinitionWML
215 * @order = 1_stacked_widget
216 *
217 * == Stacked widget ==
218 *
219 * A stacked widget holds several widgets on top of each other. This can be used
220 * for various effects; add an optional overlay to an image, stack it with a
221 * spacer to force a minimum size of a widget. The latter is handy to avoid
222 * making a separate definition for a single instance with a fixed size.
223 *
224 * A stacked widget has no states.
225 * @begin{parent}{name="gui/"}
226 * @begin{tag}{name="stacked_widget_definition"}{min=0}{max=-1}{super="generic/widget_definition"}
227 * @begin{tag}{name="resolution"}{min=0}{max=-1}{super="generic/widget_definition/resolution"}
228 * @allow{link}{name="gui/window/resolution/grid"}
229 * @end{tag}{name="resolution"}
230 * @end{tag}{name="stacked_widget_definition"}
231 * @end{parent}{name="gui/"}
232 */
resolution(const config & cfg)233 stacked_widget_definition::resolution::resolution(const config& cfg)
234 : resolution_definition(cfg), grid(nullptr)
235 {
236 // Add a dummy state since every widget needs a state.
237 static config dummy("draw");
238 state.emplace_back(dummy);
239
240 const config& child = cfg.child("grid");
241 VALIDATE(child, _("No grid defined."));
242
243 grid = std::make_shared<builder_grid>(child);
244 }
245
246 // }---------- BUILDER -----------{
247
248 /*WIKI
249 * @page = GUIToolkitWML
250 * @order = 2_stacked_widget
251 *
252 * == Stacked widget ==
253 *
254 * A stacked widget is a set of widget stacked on top of each other. The
255 * widgets are drawn in the layers, in the order defined in the the instance
256 * config. By default the last drawn item is also the 'active' layer for the
257 * event handling.
258 * @begin{parent}{name="gui/window/resolution/grid/row/column/"}
259 * @begin{tag}{name="stacked_widget"}{min="0"}{max="-1"}{super="generic/widget_instance"}
260 * @begin{table}{config}
261 * @end{table}
262 * @begin{tag}{name="layer"}{min=0}{max=-1}{super="gui/window/resolution/grid"}
263 * @end{tag}{name="layer"}
264 * @end{tag}{name="stacked_widget"}
265 * @end{parent}{name="gui/window/resolution/grid/row/column/"}
266 */
267
268 namespace implementation
269 {
270
builder_stacked_widget(const config & real_cfg)271 builder_stacked_widget::builder_stacked_widget(const config& real_cfg)
272 : builder_styled_widget(real_cfg), stack()
273 {
274 const config& cfg = real_cfg.has_child("stack") ? real_cfg.child("stack") : real_cfg;
275 if(&cfg != &real_cfg) {
276 lg::wml_error() << "Stacked widgets no longer require a [stack] tag. Instead, place [layer] tags directly in the widget definition.\n";
277 }
278 VALIDATE(cfg.has_child("layer"), _("No stack layers defined."));
279 for(const auto & layer : cfg.child_range("layer"))
280 {
281 stack.emplace_back(std::make_shared<builder_grid>(layer));
282 }
283 }
284
build() const285 widget* builder_stacked_widget::build() const
286 {
287 stacked_widget* widget = new stacked_widget(*this);
288
289 DBG_GUI_G << "Window builder: placed stacked widget '" << id
290 << "' with definition '" << definition << "'.\n";
291
292 const auto conf = widget->cast_config_to<stacked_widget_definition>();
293 assert(conf);
294
295 widget->init_grid(conf->grid);
296
297 widget->finalize(stack);
298
299 return widget;
300 }
301
302 } // namespace implementation
303
304 // }------------ END --------------
305
306 } // namespace gui2
307