1 /** @file textcanvas.cpp Text-based drawing surface.
2 *
3 * @authors Copyright © 2013-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 *
5 * @par License
6 * LGPL: http://www.gnu.org/licenses/lgpl.html
7 *
8 * <small>This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or (at your
11 * option) any later version. This program is distributed in the hope that it
12 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
14 * General Public License for more details. You should have received a copy of
15 * the GNU Lesser General Public License along with this program; if not, see:
16 * http://www.gnu.org/licenses</small>
17 */
18
19 #include "de/shell/TextCanvas"
20 #include <QList>
21 #include <QDebug>
22
23 namespace de { namespace shell {
24
DENG2_PIMPL_NOREF(TextCanvas)25 DENG2_PIMPL_NOREF(TextCanvas)
26 {
27 Size size;
28 QList<Char *> lines;
29
30 struct RichFormat {
31 Char::Attribs attrib;
32 Rangei range;
33 };
34 QList<RichFormat> richFormats;
35
36 Impl(Size const &initialSize) : size(initialSize)
37 {
38 // Allocate lines based on supplied initial size.
39 for (duint row = 0; row < size.y; ++row)
40 {
41 lines.append(makeLine());
42 }
43 }
44
45 ~Impl()
46 {
47 for (int i = 0; i < lines.size(); ++i)
48 {
49 delete [] lines[i];
50 }
51 }
52
53 dsize lineCount() const
54 {
55 return lines.size();
56 }
57
58 Char *makeLine()
59 {
60 return new Char[size.x];
61 }
62
63 void resize(Size const &newSize)
64 {
65 if (newSize == size) return;
66
67 // Allocate or free lines.
68 while (newSize.y < lineCount())
69 {
70 lines.removeLast();
71 }
72 while (newSize.y > lineCount())
73 {
74 lines.append(makeLine());
75 }
76
77 Q_ASSERT(lineCount() == newSize.y);
78 size.y = newSize.y;
79
80 // Make sure all lines are the correct width.
81 for (int row = 0; row < lines.size(); ++row)
82 {
83 Char *newLine = new Char[newSize.x];
84 memcpy(newLine, lines[row], sizeof(Char) * de::min(size.x, newSize.x));
85 delete [] lines[row];
86 lines[row] = newLine;
87 }
88
89 size.x = newSize.x;
90 }
91
92 void markAllAsDirty(bool markDirty)
93 {
94 for (int row = 0; row < lines.size(); ++row)
95 {
96 Char *line = lines[row];
97 for (duint col = 0; col < size.x; ++col)
98 {
99 Char &c = line[col];
100 if (markDirty)
101 c.attribs |= Char::Dirty;
102 else
103 c.attribs &= ~Char::Dirty;
104 }
105 }
106 }
107
108 Char::Attribs richAttribsForTextIndex(int pos, int offset = 0) const
109 {
110 Char::Attribs attr;
111 foreach (RichFormat const &rf, richFormats)
112 {
113 if (rf.range.contains(offset + pos))
114 {
115 attr |= rf.attrib;
116 }
117 }
118 return attr;
119 }
120 };
121
TextCanvas(Size const & size)122 TextCanvas::TextCanvas(Size const &size) : d(new Impl(size))
123 {
124 d->size = size;
125 }
126
~TextCanvas()127 TextCanvas::~TextCanvas()
128 {}
129
size() const130 TextCanvas::Size TextCanvas::size() const
131 {
132 return d->size;
133 }
134
width() const135 int TextCanvas::width() const
136 {
137 return d->size.x;
138 }
139
height() const140 int TextCanvas::height() const
141 {
142 return d->size.y;
143 }
144
rect() const145 Rectanglei TextCanvas::rect() const
146 {
147 return Rectanglei(0, 0, size().x, size().y);
148 }
149
resize(Size const & newSize)150 void TextCanvas::resize(Size const &newSize)
151 {
152 d->resize(newSize);
153 }
154
at(Coord const & pos)155 TextCanvas::Char &TextCanvas::at(Coord const &pos)
156 {
157 Q_ASSERT(isValid(pos));
158 return d->lines[pos.y][pos.x];
159 }
160
at(Coord const & pos) const161 TextCanvas::Char const &TextCanvas::at(Coord const &pos) const
162 {
163 Q_ASSERT(isValid(pos));
164 return d->lines[pos.y][pos.x];
165 }
166
isValid(Coord const & pos) const167 bool TextCanvas::isValid(Coord const &pos) const
168 {
169 return (pos.x >= 0 && pos.y >= 0 && pos.x < int(d->size.x) && pos.y < int(d->size.y));
170 }
171
markDirty()172 void TextCanvas::markDirty()
173 {
174 d->markAllAsDirty(true);
175 }
176
clear(Char const & ch)177 void TextCanvas::clear(Char const &ch)
178 {
179 fill(Rectanglei(0, 0, d->size.x, d->size.y), ch);
180 }
181
fill(Rectanglei const & rect,Char const & ch)182 void TextCanvas::fill(Rectanglei const &rect, Char const &ch)
183 {
184 for (int y = rect.top(); y < rect.bottom(); ++y)
185 {
186 for (int x = rect.left(); x < rect.right(); ++x)
187 {
188 Coord const xy(x, y);
189 if (isValid(xy)) at(xy) = ch;
190 }
191 }
192 }
193
put(Vector2i const & pos,Char const & ch)194 void TextCanvas::put(Vector2i const &pos, Char const &ch)
195 {
196 if (isValid(pos))
197 {
198 at(pos) = ch;
199 }
200 }
201
clearRichFormat()202 void TextCanvas::clearRichFormat()
203 {
204 d->richFormats.clear();
205 }
206
setRichFormatRange(Char::Attribs const & attribs,Rangei const & range)207 void TextCanvas::setRichFormatRange(Char::Attribs const &attribs, Rangei const &range)
208 {
209 Impl::RichFormat rf;
210 rf.attrib = attribs;
211 rf.range = range;
212 d->richFormats.append(rf);
213 }
214
drawText(Vector2i const & pos,String const & text,Char::Attribs const & attribs,int richOffset)215 void TextCanvas::drawText(Vector2i const &pos, String const &text,
216 Char::Attribs const &attribs, int richOffset)
217 {
218 Vector2i p = pos;
219 for (int i = 0; i < text.size(); ++i)
220 {
221 if (isValid(p))
222 {
223 at(p) = Char(text[i], attribs | d->richAttribsForTextIndex(i, richOffset));
224 }
225 p.x++;
226 }
227 }
228
drawWrappedText(Vector2i const & pos,String const & text,ILineWrapping const & wraps,Char::Attribs const & attribs,Alignment lineAlignment)229 void TextCanvas::drawWrappedText(Vector2i const &pos, String const &text,
230 ILineWrapping const &wraps, Char::Attribs const &attribs,
231 Alignment lineAlignment)
232 {
233 int const width = wraps.width();
234
235 for (int y = 0; y < wraps.height(); ++y)
236 {
237 WrappedLine const &span = wraps.line(y);
238 String part = text.substr(span.range);
239 int x = 0;
240 if (lineAlignment.testFlag(AlignRight))
241 {
242 x = width - part.size();
243 }
244 else if (!lineAlignment.testFlag(AlignLeft))
245 {
246 x = width/2 - part.size()/2;
247 }
248 drawText(pos + Vector2i(x, y), part, attribs, span.range.start);
249 }
250 }
251
drawLineRect(Rectanglei const & rect,Char::Attribs const & attribs)252 void TextCanvas::drawLineRect(Rectanglei const &rect, Char::Attribs const &attribs)
253 {
254 Char const corner('+', attribs);
255 Char const hEdge ('-', attribs);
256 Char const vEdge ('|', attribs);
257
258 // Horizontal edges.
259 for (duint x = 1; x < rect.width() - 1; ++x)
260 {
261 put(rect.topLeft + Vector2i(x, 0), hEdge);
262 put(rect.bottomLeft() + Vector2i(x, -1), hEdge);
263 }
264
265 // Vertical edges.
266 for (duint y = 1; y < rect.height() - 1; ++y)
267 {
268 put(rect.topLeft + Vector2i(0, y), vEdge);
269 put(rect.topRight() + Vector2i(-1, y), vEdge);
270 }
271
272 put(rect.topLeft, corner);
273 put(rect.topRight() - Vector2i(1, 0), corner);
274 put(rect.bottomRight - Vector2i(1, 1), corner);
275 put(rect.bottomLeft() - Vector2i(0, 1), corner);
276 }
277
draw(TextCanvas const & canvas,Coord const & topLeft)278 void TextCanvas::draw(TextCanvas const &canvas, Coord const &topLeft)
279 {
280 for (duint y = 0; y < canvas.d->size.y; ++y)
281 {
282 for (duint x = 0; x < canvas.d->size.x; ++x)
283 {
284 Coord const xy(x, y);
285 Coord const p = topLeft + xy;
286 if (isValid(p))
287 {
288 at(p) = canvas.at(xy);
289 }
290 }
291 }
292 }
293
show()294 void TextCanvas::show()
295 {
296 d->markAllAsDirty(false);
297 }
298
setCursorPosition(Vector2i const &)299 void TextCanvas::setCursorPosition(Vector2i const &) {}
300
301 }} // namespace de::shell
302