1 | // Aseprite UI Library |
2 | // Copyright (C) 2019-2022 Igara Studio S.A. |
3 | // Copyright (C) 2001-2018 David Capello |
4 | // |
5 | // This file is released under the terms of the MIT license. |
6 | // Read LICENSE.txt for more information. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "ui/theme.h" |
13 | |
14 | #include "gfx/point.h" |
15 | #include "gfx/size.h" |
16 | #include "os/font.h" |
17 | #include "os/surface.h" |
18 | #include "os/system.h" |
19 | #include "ui/intern.h" |
20 | #include "ui/manager.h" |
21 | #include "ui/paint_event.h" |
22 | #include "ui/scale.h" |
23 | #include "ui/size_hint_event.h" |
24 | #include "ui/style.h" |
25 | #include "ui/system.h" |
26 | #include "ui/view.h" |
27 | #include "ui/widget.h" |
28 | #include "ui/window.h" |
29 | |
30 | #include <algorithm> |
31 | #include <cstring> |
32 | |
33 | namespace ui { |
34 | |
35 | namespace { |
36 | |
37 | int current_ui_scale = 1; // Global UI Screen Scaling factor |
38 | int old_ui_scale = 1; // Add this field in InitThemeEvent |
39 | Theme* current_theme = nullptr; // Global active theme |
40 | |
41 | int compare_layer_flags(int a, int b) |
42 | { |
43 | // TODO improve this |
44 | return a - b; |
45 | } |
46 | |
47 | void for_each_layer(const int flags, |
48 | const Style* style, |
49 | std::function<void(const Style::Layer&)> callback) |
50 | { |
51 | ASSERT(style); |
52 | if (!style) |
53 | return; |
54 | |
55 | const Style::Layer* bestLayer = nullptr; |
56 | |
57 | for (const auto& layer : style->layers()) { |
58 | if (bestLayer && |
59 | bestLayer->type() != layer.type()) { |
60 | callback(*bestLayer); |
61 | bestLayer = nullptr; |
62 | } |
63 | |
64 | if ((!layer.flags() |
65 | || (layer.flags() & flags) == layer.flags()) |
66 | && (!bestLayer |
67 | || (bestLayer && compare_layer_flags(bestLayer->flags(), layer.flags()) <= 0))) { |
68 | bestLayer = &layer; |
69 | } |
70 | } |
71 | |
72 | if (bestLayer) |
73 | callback(*bestLayer); |
74 | } |
75 | |
76 | void for_each_layer(const Widget* widget, |
77 | const Style* style, |
78 | std::function<void(const Style::Layer&)> callback) |
79 | { |
80 | for_each_layer( |
81 | PaintWidgetPartInfo::getStyleFlagsForWidget(widget), |
82 | style, |
83 | callback); |
84 | } |
85 | |
86 | std::function<void(int srcx, int srcy, int dstx, int dsty, int w, int h)> |
87 | getDrawSurfaceFunction(Graphics* g, os::Surface* sheet, gfx::Color color) |
88 | { |
89 | if (color != gfx::ColorNone) |
90 | return [g, sheet, color](int srcx, int srcy, int dstx, int dsty, int w, int h) { |
91 | g->drawColoredRgbaSurface(sheet, color, srcx, srcy, dstx, dsty, w, h); |
92 | }; |
93 | else |
94 | return [g, sheet](int srcx, int srcy, int dstx, int dsty, int w, int h) { |
95 | g->drawRgbaSurface(sheet, srcx, srcy, dstx, dsty, w, h); |
96 | }; |
97 | } |
98 | |
99 | } // anonymous namespace |
100 | |
101 | PaintWidgetPartInfo::PaintWidgetPartInfo() |
102 | { |
103 | bgColor = gfx::ColorNone; |
104 | styleFlags = 0; |
105 | text = nullptr; |
106 | mnemonic = 0; |
107 | } |
108 | |
109 | PaintWidgetPartInfo::PaintWidgetPartInfo(const Widget* widget) |
110 | { |
111 | bgColor = (!widget->isTransparent() ? |
112 | widget->bgColor(): |
113 | gfx::ColorNone); |
114 | styleFlags = PaintWidgetPartInfo::getStyleFlagsForWidget(widget); |
115 | text = &widget->text(); |
116 | mnemonic = widget->mnemonic(); |
117 | } |
118 | |
119 | // static |
120 | int PaintWidgetPartInfo::getStyleFlagsForWidget(const Widget* widget) |
121 | { |
122 | return |
123 | (widget->isEnabled() ? 0: Style::Layer::kDisabled) | |
124 | (widget->isSelected() ? Style::Layer::kSelected: 0) | |
125 | (widget->hasMouse() ? Style::Layer::kMouse: 0) | |
126 | (widget->hasFocus() ? Style::Layer::kFocus: 0); |
127 | } |
128 | |
129 | Theme::Theme() |
130 | { |
131 | } |
132 | |
133 | Theme::~Theme() |
134 | { |
135 | if (current_theme == this) |
136 | set_theme(nullptr, guiscale()); |
137 | } |
138 | |
139 | void Theme::regenerateTheme() |
140 | { |
141 | set_mouse_cursor(kNoCursor); |
142 | onRegenerateTheme(); |
143 | } |
144 | |
145 | void Theme::setDecorativeWidgetBounds(Widget* widget) |
146 | { |
147 | switch (widget->type()) { |
148 | |
149 | case kWindowTitleLabelWidget: { |
150 | Window* window = widget->window(); |
151 | gfx::Rect labelBounds(widget->sizeHint()); |
152 | gfx::Rect windowBounds(window->bounds()); |
153 | gfx::Border margin; |
154 | if (widget->style()) |
155 | margin = widget->style()->margin(); |
156 | |
157 | labelBounds.offset( |
158 | windowBounds.x + margin.left(), |
159 | windowBounds.y + margin.top()); |
160 | |
161 | widget->setBounds(labelBounds); |
162 | break; |
163 | } |
164 | |
165 | case kWindowCloseButtonWidget: { |
166 | Window* window = widget->window(); |
167 | gfx::Rect buttonBounds(widget->sizeHint()); |
168 | gfx::Rect windowBounds(window->bounds()); |
169 | gfx::Border margin(0, 0, 0, 0); |
170 | if (widget->style()) |
171 | margin = widget->style()->margin(); |
172 | |
173 | buttonBounds.offset( |
174 | windowBounds.x2() - margin.right() - buttonBounds.w, |
175 | windowBounds.y + margin.top()); |
176 | |
177 | widget->setBounds(buttonBounds); |
178 | break; |
179 | } |
180 | |
181 | } |
182 | } |
183 | |
184 | void Theme::paintWidgetPart(Graphics* g, |
185 | const Style* style, |
186 | const gfx::Rect& bounds, |
187 | const PaintWidgetPartInfo& info) |
188 | { |
189 | ASSERT(g); |
190 | ASSERT(style); |
191 | |
192 | // External background |
193 | if (!gfx::is_transparent(info.bgColor)) |
194 | g->fillRect(info.bgColor, bounds); |
195 | |
196 | gfx::Rect rc = bounds; |
197 | gfx::Color outBgColor = gfx::ColorNone; |
198 | for_each_layer( |
199 | info.styleFlags, style, |
200 | [this, g, style, &info, &rc, &outBgColor] |
201 | (const Style::Layer& layer) { |
202 | paintLayer(g, style, layer, |
203 | (info.text ? *info.text: std::string()), |
204 | info.mnemonic, rc, outBgColor); |
205 | }); |
206 | } |
207 | |
208 | void Theme::paintWidget(Graphics* g, |
209 | const Widget* widget, |
210 | const Style* style, |
211 | const gfx::Rect& bounds) |
212 | { |
213 | ASSERT(widget); |
214 | |
215 | PaintWidgetPartInfo info(widget); |
216 | paintWidgetPart(g, style, bounds, info); |
217 | } |
218 | |
219 | void Theme::paintScrollBar(Graphics* g, |
220 | const Widget* widget, |
221 | const Style* style, |
222 | const Style* thumbStyle, |
223 | const gfx::Rect& bounds, |
224 | const gfx::Rect& thumbBounds) |
225 | { |
226 | PaintWidgetPartInfo info(widget); |
227 | paintWidgetPart(g, style, bounds, info); |
228 | |
229 | // TODO flags for the thumb could have "mouse" only |
230 | // when the mouse is inside the thumb |
231 | |
232 | info.bgColor = gfx::ColorNone; |
233 | paintWidgetPart(g, thumbStyle, thumbBounds, info); |
234 | } |
235 | |
236 | void Theme::paintTooltip(Graphics* g, |
237 | const Widget* widget, |
238 | const Style* style, |
239 | const Style* arrowStyle, |
240 | const gfx::Rect& bounds, |
241 | const int arrowAlign, |
242 | const gfx::Rect& target) |
243 | { |
244 | if (style) |
245 | paintWidget(g, widget, style, bounds); |
246 | |
247 | // Draw arrow |
248 | if (arrowStyle && arrowAlign) { |
249 | gfx::Size topLeft; |
250 | gfx::Size center; |
251 | gfx::Size bottomRight; |
252 | calcSlices(widget, arrowStyle, |
253 | topLeft, center, bottomRight); |
254 | |
255 | gfx::Rect clip, rc(0, 0, |
256 | topLeft.w+center.w+bottomRight.w, |
257 | topLeft.h+center.h+bottomRight.h); |
258 | |
259 | if (arrowAlign & LEFT) { |
260 | clip.w = topLeft.w; |
261 | clip.x = bounds.x; |
262 | rc.x = bounds.x; |
263 | } |
264 | else if (arrowAlign & RIGHT) { |
265 | clip.w = bottomRight.w; |
266 | clip.x = bounds.x+bounds.w-clip.w; |
267 | rc.x = bounds.x2()-rc.w; |
268 | } |
269 | else { |
270 | clip.w = center.w; |
271 | clip.x = target.x+target.w/2-clip.w/2; |
272 | rc.x = clip.x - topLeft.w; |
273 | } |
274 | |
275 | if (arrowAlign & TOP) { |
276 | clip.h = topLeft.h; |
277 | clip.y = bounds.y; |
278 | rc.y = bounds.y; |
279 | } |
280 | else if (arrowAlign & BOTTOM) { |
281 | clip.h = bottomRight.h; |
282 | clip.y = bounds.y+bounds.h-clip.h; |
283 | rc.y = bounds.y2()-rc.h; |
284 | } |
285 | else { |
286 | clip.h = center.h; |
287 | clip.y = target.y+target.h/2-clip.h/2; |
288 | rc.y = clip.y - topLeft.h; |
289 | } |
290 | |
291 | IntersectClip intClip(g, clip); |
292 | if (intClip) |
293 | paintWidget(g, widget, arrowStyle, rc); |
294 | } |
295 | } |
296 | |
297 | void Theme::paintTextBoxWithStyle(Graphics* g, |
298 | const Widget* widget) |
299 | { |
300 | gfx::Color bg = gfx::ColorNone, fg = gfx::ColorNone; |
301 | |
302 | for_each_layer( |
303 | PaintWidgetPartInfo::getStyleFlagsForWidget(widget), |
304 | widget->style(), |
305 | [&fg, &bg](const Style::Layer& layer) { |
306 | switch (layer.type()) { |
307 | case Style::Layer::Type::kBackground: bg = layer.color(); break; |
308 | case Style::Layer::Type::kText: fg = layer.color(); break; |
309 | } |
310 | }); |
311 | |
312 | if (fg != gfx::ColorNone) |
313 | Theme::drawTextBox(g, widget, nullptr, nullptr, bg, fg); |
314 | } |
315 | |
316 | void Theme::paintLayer(Graphics* g, |
317 | const Style* style, |
318 | const Style::Layer& layer, |
319 | const std::string& text, |
320 | const int mnemonic, |
321 | gfx::Rect& rc, |
322 | gfx::Color& bgColor) |
323 | { |
324 | ASSERT(style); |
325 | if (!style) |
326 | return; |
327 | |
328 | switch (layer.type()) { |
329 | |
330 | case Style::Layer::Type::kBackground: |
331 | case Style::Layer::Type::kBackgroundBorder: |
332 | if (layer.spriteSheet() && |
333 | !layer.spriteBounds().isEmpty()) { |
334 | if (!layer.slicesBounds().isEmpty()) { |
335 | Theme::drawSlices(g, layer.spriteSheet(), rc, |
336 | layer.spriteBounds(), |
337 | layer.slicesBounds(), |
338 | layer.color(), true); |
339 | |
340 | if (layer.type() == Style::Layer::Type::kBackgroundBorder) { |
341 | rc.x += layer.slicesBounds().x; |
342 | rc.y += layer.slicesBounds().y; |
343 | rc.w -= layer.spriteBounds().w - layer.slicesBounds().w; |
344 | rc.h -= layer.spriteBounds().h - layer.slicesBounds().h; |
345 | } |
346 | } |
347 | // Draw background using different methods |
348 | else { |
349 | IntersectClip clip(g, rc); |
350 | if (clip) { |
351 | auto draw = getDrawSurfaceFunction( |
352 | g, layer.spriteSheet(), layer.color()); |
353 | |
354 | switch (layer.align()) { |
355 | |
356 | // Horizontal line |
357 | case MIDDLE: |
358 | for (int x=rc.x; x<rc.x2(); x+=layer.spriteBounds().w) { |
359 | draw(layer.spriteBounds().x, |
360 | layer.spriteBounds().y, |
361 | x, rc.y+rc.h/2-layer.spriteBounds().h/2, |
362 | layer.spriteBounds().w, |
363 | layer.spriteBounds().h); |
364 | } |
365 | break; |
366 | |
367 | // Vertical line |
368 | case CENTER: |
369 | for (int y=rc.y; y<rc.y2(); y+=layer.spriteBounds().h) { |
370 | draw(layer.spriteBounds().x, |
371 | layer.spriteBounds().y, |
372 | rc.x+rc.w/2-layer.spriteBounds().w/2, y, |
373 | layer.spriteBounds().w, |
374 | layer.spriteBounds().h); |
375 | } |
376 | break; |
377 | |
378 | // One instance |
379 | case CENTER | MIDDLE: |
380 | draw(layer.spriteBounds().x, |
381 | layer.spriteBounds().y, |
382 | rc.x+rc.w/2-layer.spriteBounds().w/2, |
383 | rc.y+rc.h/2-layer.spriteBounds().h/2, |
384 | layer.spriteBounds().w, |
385 | layer.spriteBounds().h); |
386 | break; |
387 | |
388 | // Pattern |
389 | case 0: |
390 | for (int y=rc.y; y<rc.y2(); y+=layer.spriteBounds().h) { |
391 | for (int x=rc.x; x<rc.x2(); x+=layer.spriteBounds().w) |
392 | draw(layer.spriteBounds().x, |
393 | layer.spriteBounds().y, |
394 | x, y, |
395 | layer.spriteBounds().w, |
396 | layer.spriteBounds().h); |
397 | } |
398 | break; |
399 | } |
400 | } |
401 | } |
402 | } |
403 | else if (layer.color() != gfx::ColorNone) { |
404 | bgColor = layer.color(); |
405 | g->fillRect(layer.color(), rc); |
406 | } |
407 | break; |
408 | |
409 | case Style::Layer::Type::kBorder: |
410 | if (layer.spriteSheet() && |
411 | !layer.spriteBounds().isEmpty() && |
412 | !layer.slicesBounds().isEmpty()) { |
413 | Theme::drawSlices(g, layer.spriteSheet(), rc, |
414 | layer.spriteBounds(), |
415 | layer.slicesBounds(), |
416 | layer.color(), false); |
417 | |
418 | rc.x += layer.slicesBounds().x; |
419 | rc.y += layer.slicesBounds().y; |
420 | rc.w -= layer.spriteBounds().w - layer.slicesBounds().w; |
421 | rc.h -= layer.spriteBounds().h - layer.slicesBounds().h; |
422 | } |
423 | else if (layer.color() != gfx::ColorNone) { |
424 | g->drawRect(layer.color(), rc); |
425 | } |
426 | break; |
427 | |
428 | case Style::Layer::Type::kText: |
429 | if (layer.color() != gfx::ColorNone) { |
430 | os::FontRef oldFont = AddRef(g->font()); |
431 | if (style->font()) |
432 | g->setFont(AddRef(style->font())); |
433 | |
434 | if (layer.align() & WORDWRAP) { |
435 | gfx::Rect textBounds = rc; |
436 | textBounds.offset(layer.offset()); |
437 | |
438 | g->drawAlignedUIText(text, |
439 | layer.color(), |
440 | bgColor, |
441 | textBounds, layer.align()); |
442 | } |
443 | else { |
444 | gfx::Size textSize = g->measureUIText(text); |
445 | gfx::Point pt; |
446 | |
447 | if (layer.align() & LEFT) |
448 | pt.x = rc.x; |
449 | else if (layer.align() & RIGHT) |
450 | pt.x = rc.x+rc.w-textSize.w; |
451 | else |
452 | pt.x = rc.x+rc.w/2-textSize.w/2; |
453 | |
454 | if (layer.align() & TOP) |
455 | pt.y = rc.y; |
456 | else if (layer.align() & BOTTOM) |
457 | pt.y = rc.y+rc.h-textSize.h; |
458 | else |
459 | pt.y = rc.y+rc.h/2-textSize.h/2; |
460 | |
461 | pt += layer.offset(); |
462 | |
463 | g->drawUIText(text, |
464 | layer.color(), |
465 | bgColor, |
466 | pt, mnemonic); |
467 | } |
468 | |
469 | if (style->font()) |
470 | g->setFont(oldFont); |
471 | } |
472 | break; |
473 | |
474 | case Style::Layer::Type::kIcon: { |
475 | os::Surface* icon = layer.icon(); |
476 | if (icon) { |
477 | gfx::Size iconSize(icon->width(), icon->height()); |
478 | gfx::Point pt; |
479 | |
480 | if (layer.align() & LEFT) |
481 | pt.x = rc.x; |
482 | else if (layer.align() & RIGHT) |
483 | pt.x = rc.x+rc.w-iconSize.w; |
484 | else |
485 | pt.x = rc.x+rc.w/2-iconSize.w/2; |
486 | |
487 | if (layer.align() & TOP) |
488 | pt.y = rc.y; |
489 | else if (layer.align() & BOTTOM) |
490 | pt.y = rc.y+rc.h-iconSize.h; |
491 | else |
492 | pt.y = rc.y+rc.h/2-iconSize.h/2; |
493 | |
494 | pt += layer.offset(); |
495 | |
496 | if (layer.color() != gfx::ColorNone) |
497 | g->drawColoredRgbaSurface(icon, layer.color(), pt.x, pt.y); |
498 | else |
499 | g->drawRgbaSurface(icon, pt.x, pt.y); |
500 | } |
501 | break; |
502 | } |
503 | |
504 | } |
505 | } |
506 | |
507 | gfx::Size Theme::calcSizeHint(const Widget* widget, |
508 | const Style* style) |
509 | { |
510 | gfx::Size sizeHint; |
511 | gfx::Border borderHint; |
512 | gfx::Rect textHint; |
513 | int textAlign; |
514 | calcWidgetMetrics(widget, style, sizeHint, borderHint, |
515 | textHint, textAlign); |
516 | return sizeHint; |
517 | } |
518 | |
519 | void Theme::calcTextInfo(const Widget* widget, |
520 | const Style* style, |
521 | const gfx::Rect& bounds, |
522 | gfx::Rect& textBounds, int& textAlign) |
523 | { |
524 | gfx::Size sizeHint; |
525 | gfx::Border borderHint; |
526 | gfx::Rect textHint; |
527 | calcWidgetMetrics(widget, style, sizeHint, borderHint, |
528 | textHint, textAlign); |
529 | |
530 | textBounds = bounds; |
531 | textBounds.shrink(borderHint); |
532 | textBounds.offset(textHint.origin()); |
533 | } |
534 | |
535 | void Theme::measureLayer(const Widget* widget, |
536 | const Style* style, |
537 | const Style::Layer& layer, |
538 | gfx::Border& borderHint, |
539 | gfx::Rect& textHint, int& textAlign, |
540 | gfx::Size& iconHint, int& iconAlign) |
541 | { |
542 | ASSERT(style); |
543 | if (!style) |
544 | return; |
545 | |
546 | switch (layer.type()) { |
547 | |
548 | case Style::Layer::Type::kBackground: |
549 | case Style::Layer::Type::kBackgroundBorder: |
550 | case Style::Layer::Type::kBorder: |
551 | if (layer.spriteSheet() && |
552 | !layer.spriteBounds().isEmpty()) { |
553 | if (!layer.slicesBounds().isEmpty()) { |
554 | borderHint.left(std::max(borderHint.left(), layer.slicesBounds().x)); |
555 | borderHint.top(std::max(borderHint.top(), layer.slicesBounds().y)); |
556 | borderHint.right(std::max(borderHint.right(), layer.spriteBounds().w - layer.slicesBounds().x2())); |
557 | borderHint.bottom(std::max(borderHint.bottom(), layer.spriteBounds().h - layer.slicesBounds().y2())); |
558 | } |
559 | else { |
560 | iconHint.w = std::max(iconHint.w, layer.spriteBounds().w); |
561 | iconHint.h = std::max(iconHint.h, layer.spriteBounds().h); |
562 | } |
563 | } |
564 | break; |
565 | |
566 | case Style::Layer::Type::kText: |
567 | if (layer.color() != gfx::ColorNone) { |
568 | os::Font* font = (style->font() ? style->font(): |
569 | widget->font()); |
570 | gfx::Size textSize(Graphics::measureUITextLength(widget->text(), font), |
571 | font->height()); |
572 | |
573 | textHint.offset(layer.offset()); |
574 | textHint.w = std::max(textHint.w, textSize.w+ABS(layer.offset().x)); |
575 | textHint.h = std::max(textHint.h, textSize.h+ABS(layer.offset().y)); |
576 | textAlign = layer.align(); |
577 | } |
578 | break; |
579 | |
580 | case Style::Layer::Type::kIcon: { |
581 | os::Surface* icon = layer.icon(); |
582 | if (icon) { |
583 | iconHint.w = std::max(iconHint.w, icon->width()+ABS(layer.offset().x)); |
584 | iconHint.h = std::max(iconHint.h, icon->height()+ABS(layer.offset().y)); |
585 | iconAlign = layer.align(); |
586 | } |
587 | break; |
588 | } |
589 | |
590 | } |
591 | } |
592 | |
593 | gfx::Border Theme::calcBorder(const Widget* widget, |
594 | const Style* style) |
595 | { |
596 | gfx::Size sizeHint; |
597 | gfx::Border borderHint; |
598 | gfx::Rect textHint; |
599 | int textAlign; |
600 | calcWidgetMetrics(widget, style, sizeHint, borderHint, |
601 | textHint, textAlign); |
602 | return borderHint; |
603 | } |
604 | |
605 | void Theme::calcSlices(const Widget* widget, |
606 | const Style* style, |
607 | gfx::Size& topLeft, |
608 | gfx::Size& center, |
609 | gfx::Size& bottomRight) |
610 | { |
611 | ASSERT(widget); |
612 | ASSERT(style); |
613 | |
614 | for_each_layer( |
615 | widget, style, |
616 | [&topLeft, ¢er, &bottomRight] |
617 | (const Style::Layer& layer) { |
618 | if (layer.spriteSheet() && |
619 | !layer.spriteBounds().isEmpty() && |
620 | !layer.slicesBounds().isEmpty()) { |
621 | gfx::Rect sprite = layer.spriteBounds(); |
622 | gfx::Rect slices = layer.slicesBounds(); |
623 | topLeft.w = std::max(topLeft.w, slices.x); |
624 | topLeft.h = std::max(topLeft.h, slices.y); |
625 | center.w = std::max(center.w, slices.w); |
626 | center.h = std::max(center.h, slices.h); |
627 | bottomRight.w = std::max(bottomRight.w, sprite.w - slices.x2()); |
628 | bottomRight.h = std::max(bottomRight.h, sprite.h - slices.y2()); |
629 | } |
630 | }); |
631 | } |
632 | |
633 | gfx::Color Theme::calcBgColor(const Widget* widget, |
634 | const Style* style) |
635 | { |
636 | ASSERT(widget); |
637 | ASSERT(style); |
638 | |
639 | gfx::Color bgColor = gfx::ColorNone; |
640 | |
641 | for_each_layer( |
642 | widget, style, |
643 | [&bgColor] |
644 | (const Style::Layer& layer) { |
645 | if (layer.type() == Style::Layer::Type::kBackground || |
646 | layer.type() == Style::Layer::Type::kBackgroundBorder) |
647 | bgColor = layer.color(); |
648 | }); |
649 | |
650 | return bgColor; |
651 | } |
652 | |
653 | void Theme::calcWidgetMetrics(const Widget* widget, |
654 | const Style* style, |
655 | gfx::Size& sizeHint, |
656 | gfx::Border& borderHint, |
657 | gfx::Rect& textHint, int& textAlign) |
658 | { |
659 | ASSERT(widget); |
660 | ASSERT(style); |
661 | if (!style) |
662 | return; |
663 | |
664 | borderHint = gfx::Border(0, 0, 0, 0); |
665 | gfx::Border paddingHint(0, 0, 0, 0); |
666 | |
667 | textHint = gfx::Rect(0, 0, 0, 0); |
668 | textAlign = CENTER | MIDDLE; |
669 | |
670 | gfx::Size iconHint(0, 0); |
671 | int iconAlign = CENTER | MIDDLE; |
672 | |
673 | for_each_layer( |
674 | widget, style, |
675 | [this, widget, style, &borderHint, |
676 | &textHint, &textAlign, &iconHint, &iconAlign] |
677 | (const Style::Layer& layer) { |
678 | measureLayer(widget, style, layer, |
679 | borderHint, |
680 | textHint, textAlign, |
681 | iconHint, iconAlign); |
682 | }); |
683 | |
684 | gfx::Border undef = Style::UndefinedBorder(); |
685 | |
686 | if (style->border().left() != undef.left()) borderHint.left(style->border().left()); |
687 | if (style->border().top() != undef.top()) borderHint.top(style->border().top()); |
688 | if (style->border().right() != undef.right()) borderHint.right(style->border().right()); |
689 | if (style->border().bottom() != undef.bottom()) borderHint.bottom(style->border().bottom()); |
690 | |
691 | if (style->padding().left() != undef.left()) paddingHint.left(style->padding().left()); |
692 | if (style->padding().top() != undef.top()) paddingHint.top(style->padding().top()); |
693 | if (style->padding().right() != undef.right()) paddingHint.right(style->padding().right()); |
694 | if (style->padding().bottom() != undef.bottom()) paddingHint.bottom(style->padding().bottom()); |
695 | |
696 | sizeHint = gfx::Size(borderHint.width() + paddingHint.width(), |
697 | borderHint.height() + paddingHint.height()); |
698 | |
699 | if ((textAlign & (LEFT | CENTER | RIGHT)) == (iconAlign & (LEFT | CENTER | RIGHT))) |
700 | sizeHint.w += std::max(textHint.w, iconHint.w); |
701 | else |
702 | sizeHint.w += textHint.w + iconHint.w; |
703 | |
704 | if ((textAlign & (TOP | MIDDLE | BOTTOM)) == (iconAlign & (TOP | MIDDLE | BOTTOM))) |
705 | sizeHint.h += std::max(textHint.h, iconHint.h); |
706 | else |
707 | sizeHint.h += textHint.h + iconHint.h; |
708 | } |
709 | |
710 | ////////////////////////////////////////////////////////////////////// |
711 | |
712 | void set_theme(Theme* theme, const int uiscale) |
713 | { |
714 | old_ui_scale = current_ui_scale; |
715 | current_ui_scale = uiscale; |
716 | |
717 | if (theme) { |
718 | theme->regenerateTheme(); |
719 | |
720 | current_theme = theme; |
721 | |
722 | // Set the theme for all widgets |
723 | details::reinitThemeForAllWidgets(); |
724 | |
725 | // Reinitialize all widget using the new theme/uiscale |
726 | if (Manager* manager = Manager::getDefault()) { |
727 | manager->initTheme(); |
728 | manager->invalidate(); |
729 | } |
730 | } |
731 | |
732 | old_ui_scale = current_ui_scale; |
733 | } |
734 | |
735 | Theme* get_theme() |
736 | { |
737 | return current_theme; |
738 | } |
739 | |
740 | int guiscale() |
741 | { |
742 | return current_ui_scale; |
743 | } |
744 | |
745 | int details::old_guiscale() |
746 | { |
747 | return old_ui_scale; |
748 | } |
749 | |
750 | // static |
751 | void Theme::drawSlices(Graphics* g, os::Surface* sheet, |
752 | const gfx::Rect& rc, |
753 | const gfx::Rect& sprite, |
754 | const gfx::Rect& slices, |
755 | const gfx::Color color, |
756 | const bool drawCenter) |
757 | { |
758 | Paint paint; |
759 | paint.color(color); |
760 | g->drawSurfaceNine(sheet, sprite, slices, rc, drawCenter, &paint); |
761 | } |
762 | |
763 | // static |
764 | void Theme::drawTextBox(Graphics* g, const Widget* widget, |
765 | int* w, int* h, gfx::Color bg, gfx::Color fg) |
766 | { |
767 | View* view = (g ? View::getView(widget): nullptr); |
768 | char* text = const_cast<char*>(widget->text().c_str()); |
769 | char* beg, *end; |
770 | int x1, y1; |
771 | int x, y, chr, len; |
772 | gfx::Point scroll; |
773 | int textheight = widget->textHeight(); |
774 | os::Font* font = widget->font(); |
775 | char *beg_end, *old_end; |
776 | int width; |
777 | gfx::Rect vp; |
778 | |
779 | if (view) { |
780 | vp = view->viewportBounds().offset(-widget->bounds().origin()); |
781 | scroll = view->viewScroll(); |
782 | } |
783 | else { |
784 | vp = widget->clientBounds(); |
785 | scroll.x = scroll.y = 0; |
786 | } |
787 | x1 = widget->clientBounds().x + widget->border().left(); |
788 | y1 = widget->clientBounds().y + widget->border().top(); |
789 | |
790 | // Fill background |
791 | if (g) |
792 | g->fillRect(bg, vp); |
793 | |
794 | chr = 0; |
795 | |
796 | // Without word-wrap |
797 | if (!(widget->align() & WORDWRAP)) { |
798 | width = widget->clientChildrenBounds().w; |
799 | } |
800 | // With word-wrap |
801 | else { |
802 | if (w) { |
803 | width = *w; |
804 | *w = 0; |
805 | } |
806 | else { |
807 | // TODO modificable option? I don't think so, this is very internal stuff |
808 | #if 0 |
809 | // Shows more information in x-scroll 0 |
810 | width = vp.w; |
811 | #else |
812 | // Make good use of the complete text-box |
813 | if (view) { |
814 | gfx::Size maxSize = view->getScrollableSize(); |
815 | width = std::max(vp.w, maxSize.w); |
816 | } |
817 | else { |
818 | width = vp.w; |
819 | } |
820 | width -= widget->border().width(); |
821 | #endif |
822 | } |
823 | } |
824 | |
825 | // Draw line-by-line |
826 | y = y1; |
827 | for (beg=end=text; end; ) { |
828 | x = x1; |
829 | |
830 | // Without word-wrap |
831 | if (!(widget->align() & WORDWRAP)) { |
832 | end = std::strchr(beg, '\n'); |
833 | if (end) { |
834 | chr = *end; |
835 | *end = 0; |
836 | } |
837 | } |
838 | // With word-wrap |
839 | else { |
840 | old_end = nullptr; |
841 | for (beg_end=beg;;) { |
842 | end = std::strpbrk(beg_end, " \n" ); |
843 | if (end) { |
844 | chr = *end; |
845 | *end = 0; |
846 | } |
847 | |
848 | // To here we can print |
849 | if ((old_end) && (x+font->textLength(beg) > x1+width-scroll.x)) { |
850 | if (end) |
851 | *end = chr; |
852 | |
853 | end = old_end; |
854 | chr = *end; |
855 | *end = 0; |
856 | break; |
857 | } |
858 | // We can print one word more |
859 | else if (end) { |
860 | // Force break |
861 | if (chr == '\n') |
862 | break; |
863 | |
864 | *end = chr; |
865 | beg_end = end+1; |
866 | } |
867 | // We are in the end of text |
868 | else |
869 | break; |
870 | |
871 | old_end = end; |
872 | } |
873 | } |
874 | |
875 | len = font->textLength(beg); |
876 | |
877 | // Render the text |
878 | if (g) { |
879 | int xout; |
880 | |
881 | if (widget->align() & CENTER) |
882 | xout = x + width/2 - len/2; |
883 | else if (widget->align() & RIGHT) |
884 | xout = x + width - len; |
885 | else // Left align |
886 | xout = x; |
887 | |
888 | g->drawText(beg, fg, gfx::ColorNone, gfx::Point(xout, y)); |
889 | } |
890 | |
891 | if (w) |
892 | *w = std::max(*w, len); |
893 | |
894 | y += textheight; |
895 | |
896 | if (end) { |
897 | *end = chr; |
898 | beg = end+1; |
899 | } |
900 | } |
901 | |
902 | if (h) |
903 | *h = (y - y1 + scroll.y); |
904 | |
905 | if (w) *w += widget->border().width(); |
906 | if (h) *h += widget->border().height(); |
907 | } |
908 | |
909 | } // namespace ui |
910 | |