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 }