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