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