1/*
2 * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#include "uiwidget.h"
24#include "uihorizontallayout.h"
25#include "uiverticallayout.h"
26#include "uigridlayout.h"
27#include "uianchorlayout.h"
28#include "uitranslator.h"
29
30#include <framework/graphics/painter.h>
31#include <framework/graphics/texture.h>
32#include <framework/graphics/texturemanager.h>
33
34void UIWidget::initBaseStyle()
35{
36 m_backgroundColor = Color::alpha;
37 m_borderColor.set(Color::black);
38 m_iconColor = Color::white;
39 m_color = Color::white;
40 m_opacity = 1.0f;
41 m_rotation = 0.0f;
42 m_iconAlign = Fw::AlignNone;
43
44 // generate an unique id, this is need because anchored layouts find widgets by id
45 static unsigned long id = 1;
46 m_id = std::string("widget") + std::to_string(id++);
47}
48
49void UIWidget::parseBaseStyle(const OTMLNodePtr& styleNode)
50{
51 // parse lua variables and callbacks first
52 for(const OTMLNodePtr& node : styleNode->children()) {
53 // lua functions
54 if(stdext::starts_with(node->tag(), "@")) {
55 // load once
56 if(m_firstOnStyle) {
57 std::string funcName = node->tag().substr(1);
58 std::string funcOrigin = std::string("@") + node->source() + ": [" + node->tag() + "]";
59 g_lua.loadFunction(node->value(), funcOrigin);
60 luaSetField(funcName);
61 }
62 // lua fields value
63 } else if(stdext::starts_with(node->tag(), "&")) {
64 std::string fieldName = node->tag().substr(1);
65 std::string fieldOrigin = std::string("@") + node->source() + ": [" + node->tag() + "]";
66
67 g_lua.evaluateExpression(node->value(), fieldOrigin);
68 luaSetField(fieldName);
69 }
70 }
71 // load styles used by all widgets
72 for(const OTMLNodePtr& node : styleNode->children()) {
73 if(node->tag() == "color")
74 setColor(node->value<Color>());
75 else if(node->tag() == "x")
76 setX(node->value<int>());
77 else if(node->tag() == "y")
78 setY(node->value<int>());
79 else if(node->tag() == "pos")
80 setPosition(node->value<Point>());
81 else if(node->tag() == "width")
82 setWidth(node->value<int>());
83 else if(node->tag() == "height")
84 setHeight(node->value<int>());
85 else if(node->tag() == "rect")
86 setRect(node->value<Rect>());
87 else if(node->tag() == "background")
88 setBackgroundColor(node->value<Color>());
89 else if(node->tag() == "background-color")
90 setBackgroundColor(node->value<Color>());
91 else if(node->tag() == "background-offset-x")
92 setBackgroundOffsetX(node->value<int>());
93 else if(node->tag() == "background-offset-y")
94 setBackgroundOffsetY(node->value<int>());
95 else if(node->tag() == "background-offset")
96 setBackgroundOffset(node->value<Point>());
97 else if(node->tag() == "background-width")
98 setBackgroundWidth(node->value<int>());
99 else if(node->tag() == "background-height")
100 setBackgroundHeight(node->value<int>());
101 else if(node->tag() == "background-size")
102 setBackgroundSize(node->value<Size>());
103 else if(node->tag() == "background-rect")
104 setBackgroundRect(node->value<Rect>());
105 else if(node->tag() == "icon")
106 setIcon(stdext::resolve_path(node->value(), node->source()));
107 else if(node->tag() == "icon-source")
108 setIcon(stdext::resolve_path(node->value(), node->source()));
109 else if(node->tag() == "icon-color")
110 setIconColor(node->value<Color>());
111 else if(node->tag() == "icon-offset-x")
112 setIconOffsetX(node->value<int>());
113 else if(node->tag() == "icon-offset-y")
114 setIconOffsetY(node->value<int>());
115 else if(node->tag() == "icon-offset")
116 setIconOffset(node->value<Point>());
117 else if(node->tag() == "icon-width")
118 setIconWidth(node->value<int>());
119 else if(node->tag() == "icon-height")
120 setIconHeight(node->value<int>());
121 else if(node->tag() == "icon-size")
122 setIconSize(node->value<Size>());
123 else if(node->tag() == "icon-rect")
124 setIconRect(node->value<Rect>());
125 else if(node->tag() == "icon-clip")
126 setIconClip(node->value<Rect>());
127 else if(node->tag() == "icon-align")
128 setIconAlign(Fw::translateAlignment(node->value()));
129 else if(node->tag() == "opacity")
130 setOpacity(node->value<float>());
131 else if (node->tag() == "rotation")
132 setRotation(node->value<float>());
133 else if(node->tag() == "enabled")
134 setEnabled(node->value<bool>());
135 else if(node->tag() == "visible")
136 setVisible(node->value<bool>());
137 else if(node->tag() == "checked")
138 setChecked(node->value<bool>());
139 else if(node->tag() == "draggable")
140 setDraggable(node->value<bool>());
141 else if(node->tag() == "on")
142 setOn(node->value<bool>());
143 else if(node->tag() == "focusable")
144 setFocusable(node->value<bool>());
145 else if (node->tag() == "auto-draw")
146 setAutoDraw(node->value<bool>());
147 else if(node->tag() == "auto-focus")
148 setAutoFocusPolicy(Fw::translateAutoFocusPolicy(node->value()));
149 else if(node->tag() == "phantom")
150 setPhantom(node->value<bool>());
151 else if(node->tag() == "size")
152 setSize(node->value<Size>());
153 else if(node->tag() == "fixed-size")
154 setFixedSize(node->value<bool>());
155 else if(node->tag() == "clipping")
156 setClipping(node->value<bool>());
157 else if(node->tag() == "border") {
158 auto split = stdext::split(node->value(), " ");
159 if(split.size() == 2) {
160 setBorderWidth(stdext::safe_cast<int>(split[0]));
161 setBorderColor(stdext::safe_cast<Color>(split[1]));
162 } else
163 throw OTMLException(node, "border param must have its width followed by its color");
164 }
165 else if(node->tag() == "border-width")
166 setBorderWidth(node->value<int>());
167 else if(node->tag() == "border-width-top")
168 setBorderWidthTop(node->value<int>());
169 else if(node->tag() == "border-width-right")
170 setBorderWidthRight(node->value<int>());
171 else if(node->tag() == "border-width-bottom")
172 setBorderWidthBottom(node->value<int>());
173 else if(node->tag() == "border-width-left")
174 setBorderWidthLeft(node->value<int>());
175 else if(node->tag() == "border-color")
176 setBorderColor(node->value<Color>());
177 else if(node->tag() == "border-color-top")
178 setBorderColorTop(node->value<Color>());
179 else if(node->tag() == "border-color-right")
180 setBorderColorRight(node->value<Color>());
181 else if(node->tag() == "border-color-bottom")
182 setBorderColorBottom(node->value<Color>());
183 else if(node->tag() == "border-color-left")
184 setBorderColorLeft(node->value<Color>());
185 else if(node->tag() == "margin-top")
186 setMarginTop(node->value<int>());
187 else if(node->tag() == "margin-right")
188 setMarginRight(node->value<int>());
189 else if(node->tag() == "margin-bottom")
190 setMarginBottom(node->value<int>());
191 else if(node->tag() == "margin-left")
192 setMarginLeft(node->value<int>());
193 else if(node->tag() == "margin") {
194 std::string marginDesc = node->value();
195 std::vector<std::string> split = stdext::split(marginDesc, " ");
196 if(split.size() == 4) {
197 setMarginTop(stdext::safe_cast<int>(split[0]));
198 setMarginRight(stdext::safe_cast<int>(split[1]));
199 setMarginBottom(stdext::safe_cast<int>(split[2]));
200 setMarginLeft(stdext::safe_cast<int>(split[3]));
201 } else if(split.size() == 3) {
202 int marginTop = stdext::safe_cast<int>(split[0]);
203 int marginHorizontal = stdext::safe_cast<int>(split[1]);
204 int marginBottom = stdext::safe_cast<int>(split[2]);
205 setMarginTop(marginTop);
206 setMarginRight(marginHorizontal);
207 setMarginBottom(marginBottom);
208 setMarginLeft(marginHorizontal);
209 } else if(split.size() == 2) {
210 int marginVertical = stdext::safe_cast<int>(split[0]);
211 int marginHorizontal = stdext::safe_cast<int>(split[1]);
212 setMarginTop(marginVertical);
213 setMarginRight(marginHorizontal);
214 setMarginBottom(marginVertical);
215 setMarginLeft(marginHorizontal);
216 } else if(split.size() == 1) {
217 int margin = stdext::safe_cast<int>(split[0]);
218 setMarginTop(margin);
219 setMarginRight(margin);
220 setMarginBottom(margin);
221 setMarginLeft(margin);
222 }
223 }
224 else if(node->tag() == "padding-top")
225 setPaddingTop(node->value<int>());
226 else if(node->tag() == "padding-right")
227 setPaddingRight(node->value<int>());
228 else if(node->tag() == "padding-bottom")
229 setPaddingBottom(node->value<int>());
230 else if(node->tag() == "padding-left")
231 setPaddingLeft(node->value<int>());
232 else if(node->tag() == "padding") {
233 std::string paddingDesc = node->value();
234 std::vector<std::string> split = stdext::split(paddingDesc, " ");
235 if(split.size() == 4) {
236 setPaddingTop(stdext::safe_cast<int>(split[0]));
237 setPaddingRight(stdext::safe_cast<int>(split[1]));
238 setPaddingBottom(stdext::safe_cast<int>(split[2]));
239 setPaddingLeft(stdext::safe_cast<int>(split[3]));
240 } else if(split.size() == 3) {
241 int paddingTop = stdext::safe_cast<int>(split[0]);
242 int paddingHorizontal = stdext::safe_cast<int>(split[1]);
243 int paddingBottom = stdext::safe_cast<int>(split[2]);
244 setPaddingTop(paddingTop);
245 setPaddingRight(paddingHorizontal);
246 setPaddingBottom(paddingBottom);
247 setPaddingLeft(paddingHorizontal);
248 } else if(split.size() == 2) {
249 int paddingVertical = stdext::safe_cast<int>(split[0]);
250 int paddingHorizontal = stdext::safe_cast<int>(split[1]);
251 setPaddingTop(paddingVertical);
252 setPaddingRight(paddingHorizontal);
253 setPaddingBottom(paddingVertical);
254 setPaddingLeft(paddingHorizontal);
255 } else if(split.size() == 1) {
256 int padding = stdext::safe_cast<int>(split[0]);
257 setPaddingTop(padding);
258 setPaddingRight(padding);
259 setPaddingBottom(padding);
260 setPaddingLeft(padding);
261 }
262 }
263 // layouts
264 else if(node->tag() == "layout") {
265 std::string layoutType;
266 if(node->hasValue())
267 layoutType = node->value();
268 else
269 layoutType = node->valueAt<std::string>("type", "");
270
271 if(!layoutType.empty()) {
272 UILayoutPtr layout;
273 if(layoutType == "horizontalBox")
274 layout = UIHorizontalLayoutPtr(new UIHorizontalLayout(static_self_cast<UIWidget>()));
275 else if(layoutType == "verticalBox")
276 layout = UIVerticalLayoutPtr(new UIVerticalLayout(static_self_cast<UIWidget>()));
277 else if(layoutType == "grid")
278 layout = UIGridLayoutPtr(new UIGridLayout(static_self_cast<UIWidget>()));
279 else if(layoutType == "anchor")
280 layout = UIAnchorLayoutPtr(new UIAnchorLayout(static_self_cast<UIWidget>()));
281 else
282 throw OTMLException(node, "cannot determine layout type");
283 setLayout(layout);
284 }
285
286 if(node->hasChildren())
287 m_layout->applyStyle(node);
288 }
289 // anchors
290 else if(stdext::starts_with(node->tag(), "anchors.")) {
291 UIWidgetPtr parent = getParent();
292 if(!parent) {
293 if(m_firstOnStyle)
294 throw OTMLException(node, "cannot create anchor, there is no parent widget!");
295 else
296 continue;
297 }
298
299 UILayoutPtr layout = parent->getLayout();
300 UIAnchorLayoutPtr anchorLayout;
301 if(layout->isUIAnchorLayout())
302 anchorLayout = layout->static_self_cast<UIAnchorLayout>();
303
304 if(!anchorLayout)
305 throw OTMLException(node, "cannot create anchor, the parent widget doesn't use anchor layout!");
306
307 std::string what = node->tag().substr(8);
308 if(what == "fill") {
309 fill(node->value());
310 } else if(what == "centerIn") {
311 centerIn(node->value());
312 } else {
313 Fw::AnchorEdge anchoredEdge = Fw::translateAnchorEdge(what);
314
315 if(node->value() == "none") {
316 removeAnchor(anchoredEdge);
317 } else {
318 std::vector<std::string> split = stdext::split(node->value(), ".");
319 if(split.size() != 2)
320 throw OTMLException(node, "invalid anchor description");
321
322 std::string hookedWidgetId = split[0];
323 Fw::AnchorEdge hookedEdge = Fw::translateAnchorEdge(split[1]);
324
325 if(anchoredEdge == Fw::AnchorNone)
326 throw OTMLException(node, "invalid anchor edge");
327
328 if(hookedEdge == Fw::AnchorNone)
329 throw OTMLException(node, "invalid anchor target edge");
330
331 addAnchor(anchoredEdge, hookedWidgetId, hookedEdge);
332 }
333 }
334 }
335 else if (node->tag() == "cursor")
336 setCursor(node->value());
337 else if (node->tag() == "change-cursor-image")
338 setChangeCursorImage(node->value<bool>());
339 }
340}
341
342void UIWidget::drawBackground(const Rect& screenCoords)
343{
344 if(m_backgroundColor.aF() > 0.0f) {
345 Rect drawRect = screenCoords;
346 drawRect.translate(m_backgroundRect.topLeft());
347 if(m_backgroundRect.isValid())
348 drawRect.resize(m_backgroundRect.size());
349 g_drawQueue->addFilledRect(drawRect, m_backgroundColor);
350 }
351}
352
353void UIWidget::drawBorder(const Rect& screenCoords)
354{
355 // top
356 if(m_borderWidth.top > 0) {
357 Rect borderRect(screenCoords.topLeft(), screenCoords.width(), m_borderWidth.top);
358 g_drawQueue->addFilledRect(borderRect, m_borderColor.top);
359 }
360
361 // right
362 if(m_borderWidth.right > 0) {
363 Rect borderRect(screenCoords.topRight() - Point(m_borderWidth.right - 1, 0), m_borderWidth.right, screenCoords.height());
364 g_drawQueue->addFilledRect(borderRect, m_borderColor.right);
365 }
366
367 // bottom
368 if(m_borderWidth.bottom > 0) {
369 Rect borderRect(screenCoords.bottomLeft() - Point(0, m_borderWidth.bottom - 1), screenCoords.width(), m_borderWidth.bottom);
370 g_drawQueue->addFilledRect(borderRect, m_borderColor.bottom);
371 }
372
373 // left
374 if(m_borderWidth.left > 0) {
375 Rect borderRect(screenCoords.topLeft(), m_borderWidth.left, screenCoords.height());
376 g_drawQueue->addFilledRect(borderRect, m_borderColor.left);
377 }
378}
379
380void UIWidget::drawIcon(const Rect& screenCoords)
381{
382 if(m_icon) {
383 Rect drawRect;
384 if(m_iconRect.isValid()) {
385 drawRect = screenCoords;
386 drawRect.translate(m_iconRect.topLeft());
387 drawRect.resize(m_iconRect.size());
388 } else {
389 drawRect.resize(m_iconClipRect.size());
390
391 if(m_iconAlign == Fw::AlignNone)
392 drawRect.moveCenter(screenCoords.center());
393 else
394 drawRect.alignIn(screenCoords, m_iconAlign);
395 }
396 drawRect.translate(m_iconOffset);
397 g_drawQueue->addTexturedRect(drawRect, m_icon, m_iconClipRect, m_iconColor);
398 }
399}
400
401void UIWidget::setIcon(const std::string& iconFile)
402{
403 if (iconFile.empty()) {
404 m_icon = nullptr;
405 m_iconPath = "";
406 }
407 else {
408 m_icon = g_textures.getTexture(iconFile);
409 m_iconPath = iconFile;
410 }
411 if(m_icon && !m_iconClipRect.isValid())
412 m_iconClipRect = Rect(0, 0, m_icon->getSize());
413}
414