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
33namespace ui {
34
35namespace {
36
37int current_ui_scale = 1; // Global UI Screen Scaling factor
38int old_ui_scale = 1; // Add this field in InitThemeEvent
39Theme* current_theme = nullptr; // Global active theme
40
41int compare_layer_flags(int a, int b)
42{
43 // TODO improve this
44 return a - b;
45}
46
47void 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
76void 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
86std::function<void(int srcx, int srcy, int dstx, int dsty, int w, int h)>
87getDrawSurfaceFunction(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
101PaintWidgetPartInfo::PaintWidgetPartInfo()
102{
103 bgColor = gfx::ColorNone;
104 styleFlags = 0;
105 text = nullptr;
106 mnemonic = 0;
107}
108
109PaintWidgetPartInfo::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
120int 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
129Theme::Theme()
130{
131}
132
133Theme::~Theme()
134{
135 if (current_theme == this)
136 set_theme(nullptr, guiscale());
137}
138
139void Theme::regenerateTheme()
140{
141 set_mouse_cursor(kNoCursor);
142 onRegenerateTheme();
143}
144
145void 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
184void 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
208void 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
219void 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
236void 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
297void 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
316void 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
507gfx::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
519void 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
535void 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
593gfx::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
605void 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, &center, &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
633gfx::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
653void 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
712void 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
735Theme* get_theme()
736{
737 return current_theme;
738}
739
740int guiscale()
741{
742 return current_ui_scale;
743}
744
745int details::old_guiscale()
746{
747 return old_ui_scale;
748}
749
750// static
751void 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
764void 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