1 module rpui.widgets.multiline_label.renderer;
2 
3 import std.container.array;
4 import std..string;
5 import std.array;
6 import std.math;
7 
8 import rpui.math;
9 import rpui.primitives;
10 import rpui.theme;
11 import rpui.events;
12 import rpui.widget;
13 import rpui.widgets.multiline_label.widget;
14 import rpui.render.components;
15 import rpui.render.components_factory;
16 import rpui.render.renderer;
17 import rpui.render.transforms;
18 
19 final class MultilineLabelRenderer : Renderer {
20     private Array!UiText lines;
21     private Array!UiTextTransforms textTransforms;
22     private MultilineLabel widget;
23     private Theme theme;
24     private utf32string lastCaption;
25 
26     override void onCreate(Widget widget, in string style) {
27         this.theme = widget.view.theme;
28         this.widget = cast(MultilineLabel) widget;
29     }
30 
31     override void onRender() {
32         for (size_t i = 0; i < lines.length; ++i) {
33             const transforms = textTransforms[i];
34             const text = lines[i];
35             renderUiText(theme, text.render, text.attrs, transforms);
36         }
37     }
38 
39     override void onProgress(in ProgressEvent event) {
40         if (lastCaption != widget.caption) {
41             updateLines();
42             lastCaption = widget.caption;
43         }
44 
45         const textLineHeight = widget.measure.lineHeight * widget.lineHeightFactor;
46         float textCurrentPosY = getStartTextPosY(textLineHeight);
47 
48         widget.measure.maxLineWidth = 0;
49         widget.measure.lineHeight = 0;
50 
51         for (size_t i = 0; i < lines.length; ++i) {
52             const textPos = vec2(widget.innerOffsetStart.x + widget.absolutePosition.x, textCurrentPosY);
53             const textSizeY = textLineHeight;
54             const textSize = vec2(widget.size.x - widget.innerOffsetSize.x, textSizeY);
55             textCurrentPosY += textSizeY;
56 
57             with (lines[i].attrs) {
58                 textAlign = widget.textAlign;
59                 textVerticalAlign = widget.textVerticalAlign;
60             }
61 
62             textTransforms[i] = updateUiTextTransforms(
63                 &lines[i].render,
64                 &theme.regularFont,
65                 textTransforms[i],
66                 lines[i].attrs,
67                 widget.view.cameraView,
68                 textPos,
69                 textSize
70             );
71 
72             if (widget.measure.maxLineWidth < textTransforms[i].size.x) {
73                 widget.measure.maxLineWidth = textTransforms[i].size.x;
74             }
75 
76             if (widget.measure.lineHeight < textTransforms[i].size.y) {
77                 widget.measure.lineHeight = textTransforms[i].size.y;
78             }
79         }
80 
81         widget.measure.linesCount = lines.length;
82     }
83 
84     private float getStartTextPosY(in float textLineHeight) {
85         const boundaryHeight = textLineHeight * lines.length;
86         float textPosY = widget.absolutePosition.y;
87 
88         switch (widget.textVerticalAlign) {
89             case VerticalAlign.bottom:
90                 textPosY += widget.size.y - boundaryHeight - widget.innerOffsetEnd.y;
91                 break;
92 
93             case VerticalAlign.middle:
94                 textPosY += round((widget.size.y - boundaryHeight) * 0.5);
95                 break;
96 
97             default:
98                 textPosY += widget.innerOffsetStart.y;
99                 break;
100         }
101 
102         return textPosY;
103     }
104 
105     private void updateLines() {
106         textTransforms.clear();
107         const strings = lineSplitter(widget.caption).array;
108 
109         lines.length = strings.length;
110         textTransforms.length = strings.length;
111 
112         for (size_t i = 0; i < lines.length; ++i) {
113             lines[i] = createUiTextFromRdpl(theme, widget.style, "Regular");
114             lines[i].attrs.caption = strings[i];
115         }
116     }
117 }