1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "common/util.h"
24 #include "common/system.h"
25 
26 #include "gui/gui-manager.h"
27 #include "gui/widget.h"
28 #include "gui/ThemeEval.h"
29 #include "gui/ThemeLayout.h"
30 
31 #include "graphics/font.h"
32 
33 #ifdef LAYOUT_DEBUG_DIALOG
34 #include "graphics/surface.h"
35 #endif
36 
37 namespace GUI {
38 
importLayout(ThemeLayout * layout)39 void ThemeLayout::importLayout(ThemeLayout *layout) {
40 	assert(layout->getLayoutType() == kLayoutMain);
41 
42 	if (layout->_children.size() == 0)
43 		return;
44 
45 	layout = layout->_children[0];
46 
47 	if (getLayoutType() == layout->getLayoutType()) {
48 		for (uint i = 0; i < layout->_children.size(); ++i)
49 			_children.push_back(layout->_children[i]->makeClone(this));
50 	} else {
51 		ThemeLayout *clone = layout->makeClone(this);
52 
53 		// When importing a layout into a layout of the same type, the children
54 		// of the imported layout are copied over, ignoring the padding of the
55 		// imported layout. Here when importing a layout of a different type
56 		// into a layout we explicitly ignore the padding so the appearance
57 		// is the same in both cases.
58 		clone->setPadding(0, 0, 0, 0);
59 
60 		_children.push_back(clone);
61 	}
62 }
63 
resetLayout()64 void ThemeLayout::resetLayout() {
65 	_x = 0;
66 	_y = 0;
67 	_w = _defaultW;
68 	_h = _defaultH;
69 
70 	for (uint i = 0; i < _children.size(); ++i)
71 		_children[i]->resetLayout();
72 }
73 
getWidgetData(const Common::String & name,int16 & x,int16 & y,int16 & w,int16 & h,bool & useRTL)74 bool ThemeLayout::getWidgetData(const Common::String &name, int16 &x, int16 &y, int16 &w, int16 &h, bool &useRTL) {
75 	if (name.empty()) {
76 		assert(getLayoutType() == kLayoutMain);
77 		x = _x; y = _y;
78 		w = _w; h = _h;
79 		useRTL = _useRTL;
80 
81 		return true;
82 	}
83 
84 	for (uint i = 0; i < _children.size(); ++i) {
85 		if (_children[i]->getWidgetData(name, x, y, w, h, useRTL))
86 			return true;
87 	}
88 
89 	return false;
90 }
91 
getWidgetTextHAlign(const Common::String & name)92 Graphics::TextAlign ThemeLayout::getWidgetTextHAlign(const Common::String &name) {
93 	if (name.empty()) {
94 		assert(getLayoutType() == kLayoutMain);
95 		return _textHAlign;
96 	}
97 
98 	Graphics::TextAlign res;
99 
100 	for (uint i = 0; i < _children.size(); ++i) {
101 		if ((res = _children[i]->getWidgetTextHAlign(name)) != Graphics::kTextAlignInvalid)
102 			return res;
103 	}
104 
105 	return Graphics::kTextAlignInvalid;
106 }
107 
getParentWidth()108 int16 ThemeLayoutStacked::getParentWidth() {
109 	ThemeLayout *p = _parent;
110 	int width = 0;
111 
112 	while (p && p->getLayoutType() != kLayoutMain) {
113 		width += p->_padding.right + p->_padding.left;
114 		if (p->getLayoutType() == kLayoutHorizontal) {
115 			const int spacing = ((ThemeLayoutStacked *)p)->_spacing;
116 			for (uint i = 0; i < p->_children.size(); ++i)
117 				width += p->_children[i]->getWidth() + spacing;
118 		}
119 		// FIXME: Do we really want to assume that any layout type different
120 		// from kLayoutHorizontal corresponds to width 0 ?
121 		p = p->_parent;
122 	}
123 
124 	assert(p && p->getLayoutType() == kLayoutMain);
125 	return p->getWidth() - width;
126 }
127 
getParentHeight()128 int16 ThemeLayoutStacked::getParentHeight() {
129 	ThemeLayout *p = _parent;
130 	int height = 0;
131 
132 	while (p && p->getLayoutType() != kLayoutMain) {
133 		height += p->_padding.bottom + p->_padding.top;
134 		if (p->getLayoutType() == kLayoutVertical) {
135 			const int spacing = ((ThemeLayoutStacked *)p)->_spacing;
136 			for (uint i = 0; i < p->_children.size(); ++i)
137 				height += p->_children[i]->getHeight() + spacing;
138 		}
139 		// FIXME: Do we really want to assume that any layout type different
140 		// from kLayoutVertical corresponds to height 0 ?
141 		p = p->_parent;
142 	}
143 
144 	assert(p && p->getLayoutType() == kLayoutMain);
145 	return p->getHeight() - height;
146 }
147 
148 #ifdef LAYOUT_DEBUG_DIALOG
debugDraw(Graphics::Surface * screen,const Graphics::Font * font)149 void ThemeLayout::debugDraw(Graphics::Surface *screen, const Graphics::Font *font) {
150 	uint32 color = 0xFFFFFFFF;
151 	font->drawString(screen, getName(), _x, _y, _w, color, Graphics::kTextAlignRight, 0, true);
152 	screen->hLine(_x, _y, _x + _w, color);
153 	screen->hLine(_x, _y + _h, _x + _w , color);
154 	screen->vLine(_x, _y, _y + _h, color);
155 	screen->vLine(_x + _w, _y, _y + _h, color);
156 
157 	for (uint i = 0; i < _children.size(); ++i)
158 		_children[i]->debugDraw(screen, font);
159 }
160 #endif
161 
162 
getWidgetData(const Common::String & name,int16 & x,int16 & y,int16 & w,int16 & h,bool & useRTL)163 bool ThemeLayoutWidget::getWidgetData(const Common::String &name, int16 &x, int16 &y, int16 &w, int16 &h, bool &useRTL) {
164 	if (name == _name) {
165 		x = _x; y = _y;
166 		w = _w; h = _h;
167 		useRTL = _useRTL;
168 
169 		return true;
170 	}
171 
172 	return false;
173 }
174 
getWidgetTextHAlign(const Common::String & name)175 Graphics::TextAlign ThemeLayoutWidget::getWidgetTextHAlign(const Common::String &name) {
176 	if (name == _name) {
177 		return _textHAlign;
178 	}
179 
180 	return Graphics::kTextAlignInvalid;
181 }
182 
reflowLayout(Widget * widgetChain)183 void ThemeLayoutWidget::reflowLayout(Widget *widgetChain) {
184 	Widget *guiWidget = getWidget(widgetChain);
185 	if (!guiWidget) {
186 		return;
187 	}
188 
189 	int minWidth  = -1;
190 	int minHeight = -1;
191 	guiWidget->getMinSize(minWidth, minHeight);
192 
193 	if (_w != -1 && minWidth != -1 && minWidth > _w) {
194 		_w = minWidth;
195 	}
196 
197 	if (_h != -1 && minHeight != -1 && minHeight > _h) {
198 		_h = minHeight;
199 	}
200 }
201 
isBound(Widget * widgetChain) const202 bool ThemeLayoutWidget::isBound(Widget *widgetChain) const {
203 	Widget *guiWidget = getWidget(widgetChain);
204 	return guiWidget != nullptr;
205 }
206 
getWidget(Widget * widgetChain) const207 Widget *ThemeLayoutWidget::getWidget(Widget *widgetChain) const {
208 	const ThemeLayout *topLevelLayout = this;
209 	while (topLevelLayout->_parent) {
210 		topLevelLayout = topLevelLayout->_parent;
211 	}
212 
213 	assert(topLevelLayout && topLevelLayout->getLayoutType() == kLayoutMain);
214 	const ThemeLayoutMain *dialogLayout = static_cast<const ThemeLayoutMain *>(topLevelLayout);
215 
216 	Common::String widgetName = Common::String::format("%s.%s", dialogLayout->getName(), _name.c_str());
217 	return Widget::findWidgetInChain(widgetChain, widgetName.c_str());
218 }
219 
reflowLayout(Widget * widgetChain)220 void ThemeLayoutMain::reflowLayout(Widget *widgetChain) {
221 	assert(_children.size() <= 1);
222 
223 	resetLayout();
224 
225 	if (_overlays == "screen") {
226 		_x = 0;
227 		_y = 0;
228 		_w = g_gui.getGUIWidth() * g_gui.getScaleFactor();
229 		_h = g_gui.getGUIHeight() * g_gui.getScaleFactor();
230 	} else if (_overlays == "screen_center") {
231 		_x = -1;
232 		_y = -1;
233 		_w = _defaultW > 0 ? MIN(_defaultW, g_gui.getGUIWidth()) * g_gui.getScaleFactor() : -1;
234 		_h = _defaultH > 0 ? MIN(_defaultH, g_gui.getGUIHeight()) * g_gui.getScaleFactor() : -1;
235 	} else {
236 		if (!g_gui.xmlEval()->getWidgetData(_overlays, _x, _y, _w, _h)) {
237 			warning("Unable to retrieve overlayed dialog position %s", _overlays.c_str());
238 		}
239 
240 		if (_w == -1 || _h == -1) {
241 			warning("The overlayed dialog %s has not been sized, using a default size for %s", _overlays.c_str(), _name.c_str());
242 			_x = g_gui.getGUIWidth()      / 10 * g_gui.getScaleFactor();
243 			_y = g_gui.getGUIHeight()     / 10 * g_gui.getScaleFactor();
244 			_w = g_gui.getGUIWidth()  * 8 / 10 * g_gui.getScaleFactor();
245 			_h = g_gui.getGUIHeight() * 8 / 10 * g_gui.getScaleFactor();
246 		}
247 	}
248 
249 	if (g_gui.useRTL()) {
250 		if (this->_name == "GameOptions" || this->_name == "GlobalOptions" || this->_name == "Browser") {
251 			/** The dialogs named above are the stacked dialogs for which the left+right paddings need to be adjusted for RTL.
252 				Whenever a stacked dialog is opened, the below code sets the left and right paddings and enables widgets to be
253 				shifted by that amount. If any new stacked and padded dialogs are added in the future,
254 				add them here and in Widget::draw() to enable RTL support for that particular dialog
255 			*/
256 			int oldX = _x;
257 			_x = g_gui.getGUIWidth() * g_gui.getScaleFactor() - _w - _x;
258 			g_gui.setDialogPaddings(oldX, _x);
259 		}
260 	}
261 
262 	if (_x >= 0) _x += _inset * g_gui.getScaleFactor();
263 	if (_y >= 0) _y += _inset * g_gui.getScaleFactor();
264 	if (_w >= 0) _w -= 2 * _inset * g_gui.getScaleFactor();
265 	if (_h >= 0) _h -= 2 * _inset * g_gui.getScaleFactor();
266 
267 	if (_children.size()) {
268 		_children[0]->setWidth(_w);
269 		_children[0]->setHeight(_h);
270 		_children[0]->reflowLayout(widgetChain);
271 
272 		if (_w == -1)
273 			_w = _children[0]->getWidth();
274 
275 		if (_h == -1)
276 			_h = _children[0]->getHeight();
277 
278 		if (_y == -1)
279 			_y = (g_system->getOverlayHeight() >> 1) - (_h >> 1);
280 
281 		if (_x == -1)
282 			_x = (g_system->getOverlayWidth() >> 1) - (_w >> 1);
283 	}
284 }
285 
reflowLayoutVertical(Widget * widgetChain)286 void ThemeLayoutStacked::reflowLayoutVertical(Widget *widgetChain) {
287 	int curY;
288 	int resize[8];
289 	int rescount = 0;
290 	bool fixedWidth = _w != -1;
291 
292 	curY = _padding.top;
293 	_h = _padding.top + _padding.bottom;
294 
295 	for (uint i = 0; i < _children.size(); ++i) {
296 		if (!_children[i]->isBound(widgetChain)) continue;
297 
298 		_children[i]->reflowLayout(widgetChain);
299 
300 		if (_children[i]->getWidth() == -1) {
301 			int16 width = (_w == -1 ? getParentWidth() : _w) - _padding.left - _padding.right;
302 			_children[i]->setWidth(MAX<int16>(width, 0));
303 		}
304 
305 		if (_children[i]->getHeight() == -1) {
306 			assert(rescount < ARRAYSIZE(resize));
307 			resize[rescount++] = i;
308 			_children[i]->setHeight(0);
309 		}
310 
311 		_children[i]->offsetY(curY);
312 
313 		// Advance the vertical offset by the height of the newest item, plus
314 		// the item spacing value.
315 		curY += _children[i]->getHeight() + _spacing;
316 
317 		// Update width and height of this stack layout
318 		if (!fixedWidth) {
319 			_w = MAX(_w, (int16)(_children[i]->getWidth() + _padding.left + _padding.right));
320 		}
321 		_h += _children[i]->getHeight() + _spacing;
322 	}
323 
324 	// If there are any children at all, then we added the spacing value once
325 	// too often. Correct that.
326 	if (!_children.empty())
327 		_h -= _spacing;
328 
329 	// If the width is not set at this point, then we have no bound widgets.
330 	if (!fixedWidth && _w == -1) {
331 		_w = 0;
332 	}
333 
334 	for (uint i = 0; i < _children.size(); ++i) {
335 		switch (_itemAlign) {
336 		case kItemAlignStart:
337 		default:
338 			_children[i]->offsetX(_padding.left);
339 			break;
340 		case kItemAlignCenter:
341 			// Center child if it this has been requested *and* the space permits it.
342 			if (_children[i]->getWidth() < (_w - _padding.left - _padding.right)) {
343 				_children[i]->offsetX((_w >> 1) - (_children[i]->getWidth() >> 1));
344 			} else {
345 				_children[i]->offsetX(_padding.left);
346 			}
347 			break;
348 		case kItemAlignEnd:
349 			_children[i]->offsetX(_w - _children[i]->getWidth() - _padding.right);
350 			break;
351 		case kItemAlignStretch:
352 			_children[i]->offsetX(_padding.left);
353 			_children[i]->setWidth(_w - _padding.left - _padding.right);
354 			break;
355 		}
356 	}
357 
358 	// If there were any items with undetermined height, then compute and set
359 	// their height now. We do so by determining how much space is left, and
360 	// then distributing this equally over all items which need auto-resizing.
361 	if (rescount) {
362 		int newh = (getParentHeight() - _h - _padding.bottom) / rescount;
363 		if (newh < 0) newh = 0; // In case there is no room left, avoid giving a negative height to widgets
364 
365 		for (int i = 0; i < rescount; ++i) {
366 			// Set the height of the item.
367 			_children[resize[i]]->setHeight(newh);
368 			// Increase the height of this ThemeLayoutStacked accordingly, and
369 			// then shift all subsequence children.
370 			_h += newh;
371 			for (uint j = resize[i] + 1; j < _children.size(); ++j)
372 				_children[j]->offsetY(newh);
373 		}
374 	}
375 }
376 
reflowLayoutHorizontal(Widget * widgetChain)377 void ThemeLayoutStacked::reflowLayoutHorizontal(Widget *widgetChain) {
378 	int curX;
379 	int resize[8];
380 	int rescount = 0;
381 	bool fixedHeight = _h != -1;
382 
383 	curX = _padding.left;
384 	_w = _padding.left + _padding.right;
385 
386 	for (uint i = 0; i < _children.size(); ++i) {
387 		if (!_children[i]->isBound(widgetChain)) continue;
388 
389 		_children[i]->reflowLayout(widgetChain);
390 
391 		if (_children[i]->getHeight() == -1) {
392 			int16 height = (_h == -1 ? getParentHeight() : _h) - _padding.top - _padding.bottom;
393 			_children[i]->setHeight(MAX<int16>(height, 0));
394 		}
395 
396 		if (_children[i]->getWidth() == -1) {
397 			assert(rescount < ARRAYSIZE(resize));
398 			resize[rescount++] = i;
399 			_children[i]->setWidth(0);
400 		}
401 
402 		_children[i]->offsetX(curX);
403 
404 		// Advance the horizontal offset by the width of the newest item, plus
405 		// the item spacing value.
406 		curX += (_children[i]->getWidth() + _spacing);
407 
408 		// Update width and height of this stack layout
409 		_w += _children[i]->getWidth() + _spacing;
410 		if (!fixedHeight) {
411 			_h = MAX(_h, (int16)(_children[i]->getHeight() + _padding.top + _padding.bottom));
412 		}
413 	}
414 
415 	// If there are any children at all, then we added the spacing value once
416 	// too often. Correct that.
417 	if (!_children.empty())
418 		_w -= _spacing;
419 
420 	// If the height is not set at this point, then we have no bound widgets.
421 	if (!fixedHeight && _h == -1) {
422 		_h = 0;
423 	}
424 
425 	for (uint i = 0; i < _children.size(); ++i) {
426 		switch (_itemAlign) {
427 		case kItemAlignStart:
428 		default:
429 			_children[i]->offsetY(_padding.top);
430 			break;
431 		case kItemAlignCenter:
432 			// Center child if it this has been requested *and* the space permits it.
433 			if (_children[i]->getHeight() < (_h - _padding.top - _padding.bottom)) {
434 				_children[i]->offsetY((_h >> 1) - (_children[i]->getHeight() >> 1));
435 			} else {
436 				_children[i]->offsetY(_padding.top);
437 			}
438 			break;
439 		case kItemAlignEnd:
440 			_children[i]->offsetY(_h - _children[i]->getHeight() - _padding.bottom);
441 			break;
442 		case kItemAlignStretch:
443 			_children[i]->offsetY(_padding.top);
444 			_children[i]->setHeight(_w - _padding.top - _padding.bottom);
445 			break;
446 		}
447 	}
448 
449 	// If there were any items with undetermined width, then compute and set
450 	// their width now. We do so by determining how much space is left, and
451 	// then distributing this equally over all items which need auto-resizing.
452 	if (rescount) {
453 		int neww = (getParentWidth() - _w - _padding.right) / rescount;
454 		if (neww < 0) neww = 0; // In case there is no room left, avoid giving a negative width to widgets
455 
456 		for (int i = 0; i < rescount; ++i) {
457 			// Set the width of the item.
458 			_children[resize[i]]->setWidth(neww);
459 			// Increase the width of this ThemeLayoutStacked accordingly, and
460 			// then shift all subsequence children.
461 			_w += neww;
462 			for (uint j = resize[i] + 1; j < _children.size(); ++j)
463 				_children[j]->offsetX(neww);
464 		}
465 	}
466 }
467 
468 } // End of namespace GUI
469