1 module fluid.text;
2 
3 import std.algorithm;
4 
5 import fluid.node;
6 import fluid.style;
7 import fluid.backend;
8 import fluid.typeface;
9 
10 
11 @safe:
12 
13 
14 /// Draws text: handles updates, formatting and styling.
15 struct Text(T : Node) {
16 
17     /// Node owning this text struct.
18     T node;
19 
20     /// Texture generated by the struct.
21     /// Note: Lifetime of the texture is managed by the struct. The texture will be destroyed every time the text
22     /// changes.
23     Texture texture;
24 
25     /// Underlying text.
26     string value;
27 
28     alias value this;
29 
30     this(T node, string text) {
31 
32         this.node = node;
33         opAssign(text);
34 
35     }
36 
37     /// Copy the text, clear ownership and texture.
38     this(ref const Text text) {
39 
40         this.node = null;
41         this.texture = texture.init;
42         this.value = text.value;
43 
44     }
45 
46     ~this() @trusted {
47 
48         texture.destroy();
49 
50     }
51 
52     inout(FluidBackend) backend() inout
53 
54         => node.tree.backend;
55 
56     string opAssign(string text) {
57 
58         // Ignore if there's no change to be made
59         if (text == value) return text;
60 
61         // Request update otherwise
62         node.updateSize;
63         return value = text;
64 
65     }
66 
67     string opOpAssign(string operator)(string text) {
68 
69         node.updateSize;
70         return mixin("value ", operator, "= text");
71 
72     }
73 
74     /// Get the size of the text.
75     Vector2 size() const {
76 
77         return texture.viewportSize;
78 
79     }
80 
81     alias minSize = size;
82 
83     /// Set new bounding box for the text and redraw it.
84     void resize() {
85 
86         auto style = node.pickStyle;
87         auto dpi = backend.dpi;
88 
89         style.setDPI(dpi);
90 
91         const size = style.typeface.measure(value);
92 
93         resizeImpl(style, size, dpi, false);
94 
95     }
96 
97     /// Set new bounding box for the text; wrap the text if it doesn't fit in boundaries. Redraw it.
98     void resize(alias splitter = Typeface.defaultWordChunks)(Vector2 space, bool wrap = true) {
99 
100         auto style = node.pickStyle;
101         auto dpi = backend.dpi;
102         auto scale = backend.hidpiScale;
103 
104         // Apply DPI
105         style.setDPI(dpi);
106         space.x *= scale.x;
107         space.y *= scale.y;
108 
109         const size = style.typeface.measure!splitter(space, value, wrap);
110 
111         resizeImpl(style, size, dpi, wrap);
112 
113     }
114 
115     private void resizeImpl(const Style style, Vector2 size, Vector2 dpi, bool wrap) @trusted {
116 
117         // Empty, nothing to do
118         if (size.x < 1 || size.y < 1) return;
119 
120         auto image = generateColorImage(
121             cast(int) size.x,
122             cast(int) size.y,
123             color!"0000"
124         );
125 
126         style.typeface.draw(image, Rectangle(0, 0, size.tupleof), value, color!"fff", wrap);
127 
128         auto oldtexture = texture.id;
129 
130         // Destroy old texture if needed
131         if (texture !is texture.init) {
132 
133             backend.unloadTexture(texture);
134 
135         }
136 
137         // Load texture
138         texture = backend.loadTexture(image);
139         texture.dpiX = cast(int) dpi.x;
140         texture.dpiY = cast(int) dpi.y;
141 
142     }
143 
144     /// Draw the text.
145     void draw(const Style style, Vector2 position) @trusted {
146 
147         import std.math;
148 
149         if (texture !is texture.init) {
150 
151             auto rectangle = Rectangle(position.tupleof, texture.viewportSize.tupleof);
152 
153             backend.drawTextureAlign(texture, rectangle, style.textColor, value);
154 
155         }
156 
157     }
158 
159     void draw(const Style style, Rectangle rectangle) {
160 
161         draw(style, Vector2(rectangle.x, rectangle.y));
162 
163     }
164 
165     string toString() const {
166 
167         return value;
168 
169     }
170 
171 }