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/graphics.h" |
13 | |
14 | #include "base/string.h" |
15 | #include "gfx/clip.h" |
16 | #include "gfx/matrix.h" |
17 | #include "gfx/path.h" |
18 | #include "gfx/point.h" |
19 | #include "gfx/rect.h" |
20 | #include "gfx/region.h" |
21 | #include "gfx/size.h" |
22 | #include "os/draw_text.h" |
23 | #include "os/font.h" |
24 | #include "os/sampling.h" |
25 | #include "os/surface.h" |
26 | #include "os/system.h" |
27 | #include "os/window.h" |
28 | #include "ui/display.h" |
29 | #include "ui/scale.h" |
30 | #include "ui/theme.h" |
31 | |
32 | #include <algorithm> |
33 | #include <cctype> |
34 | #include <string> |
35 | |
36 | namespace ui { |
37 | |
38 | Graphics::Graphics(Display* display, const os::SurfaceRef& surface, int dx, int dy) |
39 | : m_display(display) |
40 | , m_surface(surface) |
41 | , m_dx(dx) |
42 | , m_dy(dy) |
43 | { |
44 | } |
45 | |
46 | Graphics::~Graphics() |
47 | { |
48 | // If we were drawing in the screen surface, we mark these regions |
49 | // as dirty for the final flip. |
50 | if (m_display) |
51 | m_display->dirtyRect(m_dirtyBounds); |
52 | } |
53 | |
54 | int Graphics::width() const |
55 | { |
56 | return m_surface->width(); |
57 | } |
58 | |
59 | int Graphics::height() const |
60 | { |
61 | return m_surface->height(); |
62 | } |
63 | |
64 | int Graphics::getSaveCount() const |
65 | { |
66 | return m_surface->getSaveCount(); |
67 | } |
68 | |
69 | gfx::Rect Graphics::getClipBounds() const |
70 | { |
71 | return m_surface->getClipBounds().offset(-m_dx, -m_dy); |
72 | } |
73 | |
74 | void Graphics::saveClip() |
75 | { |
76 | m_surface->saveClip(); |
77 | } |
78 | |
79 | void Graphics::restoreClip() |
80 | { |
81 | m_surface->restoreClip(); |
82 | } |
83 | |
84 | bool Graphics::clipRect(const gfx::Rect& rc) |
85 | { |
86 | return m_surface->clipRect(gfx::Rect(rc).offset(m_dx, m_dy)); |
87 | } |
88 | |
89 | void Graphics::save() |
90 | { |
91 | m_surface->save(); |
92 | } |
93 | |
94 | void Graphics::concat(const gfx::Matrix& matrix) |
95 | { |
96 | m_surface->concat(matrix); |
97 | } |
98 | |
99 | void Graphics::setMatrix(const gfx::Matrix& matrix) |
100 | { |
101 | m_surface->setMatrix(matrix); |
102 | } |
103 | |
104 | void Graphics::resetMatrix() |
105 | { |
106 | m_surface->resetMatrix(); |
107 | } |
108 | |
109 | void Graphics::restore() |
110 | { |
111 | m_surface->restore(); |
112 | } |
113 | |
114 | gfx::Matrix Graphics::matrix() const |
115 | { |
116 | return m_surface->matrix(); |
117 | } |
118 | |
119 | gfx::Color Graphics::getPixel(int x, int y) |
120 | { |
121 | os::SurfaceLock lock(m_surface.get()); |
122 | return m_surface->getPixel(m_dx+x, m_dy+y); |
123 | } |
124 | |
125 | void Graphics::putPixel(gfx::Color color, int x, int y) |
126 | { |
127 | dirty(gfx::Rect(m_dx+x, m_dy+y, 1, 1)); |
128 | |
129 | os::SurfaceLock lock(m_surface.get()); |
130 | m_surface->putPixel(color, m_dx+x, m_dy+y); |
131 | } |
132 | |
133 | void Graphics::drawHLine(int x, int y, int w, const Paint& paint) |
134 | { |
135 | dirty(gfx::Rect(m_dx+x, m_dy+y, w, 1)); |
136 | |
137 | os::SurfaceLock lock(m_surface.get()); |
138 | m_surface->drawRect(gfx::Rect(m_dx+x, m_dy+y, w, 1), paint); |
139 | } |
140 | |
141 | void Graphics::drawHLine(gfx::Color color, int x, int y, int w) |
142 | { |
143 | dirty(gfx::Rect(m_dx+x, m_dy+y, w, 1)); |
144 | |
145 | os::SurfaceLock lock(m_surface.get()); |
146 | os::Paint paint; |
147 | paint.color(color); |
148 | m_surface->drawRect(gfx::Rect(m_dx+x, m_dy+y, w, 1), paint); |
149 | } |
150 | |
151 | void Graphics::drawVLine(int x, int y, int h, const Paint& paint) |
152 | { |
153 | dirty(gfx::Rect(m_dx+x, m_dy+y, 1, h)); |
154 | |
155 | os::SurfaceLock lock(m_surface.get()); |
156 | m_surface->drawRect(gfx::Rect(m_dx+x, m_dy+y, 1, h), paint); |
157 | } |
158 | |
159 | void Graphics::drawVLine(gfx::Color color, int x, int y, int h) |
160 | { |
161 | dirty(gfx::Rect(m_dx+x, m_dy+y, 1, h)); |
162 | |
163 | os::SurfaceLock lock(m_surface.get()); |
164 | os::Paint paint; |
165 | paint.color(color); |
166 | m_surface->drawRect(gfx::Rect(m_dx+x, m_dy+y, 1, h), paint); |
167 | } |
168 | |
169 | void Graphics::drawLine(gfx::Color color, const gfx::Point& _a, const gfx::Point& _b) |
170 | { |
171 | gfx::Point a(m_dx+_a.x, m_dy+_a.y); |
172 | gfx::Point b(m_dx+_b.x, m_dy+_b.y); |
173 | dirty(gfx::Rect(a, b)); |
174 | |
175 | os::SurfaceLock lock(m_surface.get()); |
176 | os::Paint paint; |
177 | paint.color(color); |
178 | m_surface->drawLine(a, b, paint); |
179 | } |
180 | |
181 | void Graphics::drawPath(gfx::Path& path, const Paint& paint) |
182 | { |
183 | os::SurfaceLock lock(m_surface.get()); |
184 | |
185 | auto m = matrix(); |
186 | save(); |
187 | setMatrix(gfx::Matrix::MakeTrans(m_dx, m_dy)); |
188 | concat(m); |
189 | |
190 | m_surface->drawPath(path, paint); |
191 | |
192 | dirty(matrix().mapRect(path.bounds()).inflate(1, 1)); |
193 | restore(); |
194 | } |
195 | |
196 | void Graphics::drawRect(const gfx::Rect& rcOrig, const Paint& paint) |
197 | { |
198 | gfx::Rect rc(rcOrig); |
199 | rc.offset(m_dx, m_dy); |
200 | dirty(rc); |
201 | |
202 | os::SurfaceLock lock(m_surface.get()); |
203 | m_surface->drawRect(rc, paint); |
204 | } |
205 | |
206 | void Graphics::drawRect(gfx::Color color, const gfx::Rect& rcOrig) |
207 | { |
208 | gfx::Rect rc(rcOrig); |
209 | rc.offset(m_dx, m_dy); |
210 | dirty(rc); |
211 | |
212 | os::SurfaceLock lock(m_surface.get()); |
213 | os::Paint paint; |
214 | paint.color(color); |
215 | paint.style(os::Paint::Stroke); |
216 | m_surface->drawRect(rc, paint); |
217 | } |
218 | |
219 | void Graphics::fillRect(gfx::Color color, const gfx::Rect& rcOrig) |
220 | { |
221 | gfx::Rect rc(rcOrig); |
222 | rc.offset(m_dx, m_dy); |
223 | dirty(rc); |
224 | |
225 | os::SurfaceLock lock(m_surface.get()); |
226 | os::Paint paint; |
227 | paint.color(color); |
228 | paint.style(os::Paint::Fill); |
229 | m_surface->drawRect(rc, paint); |
230 | } |
231 | |
232 | void Graphics::fillRegion(gfx::Color color, const gfx::Region& rgn) |
233 | { |
234 | for (gfx::Region::iterator it=rgn.begin(), end=rgn.end(); it!=end; ++it) |
235 | fillRect(color, *it); |
236 | } |
237 | |
238 | void Graphics::fillAreaBetweenRects(gfx::Color color, |
239 | const gfx::Rect& outer, const gfx::Rect& inner) |
240 | { |
241 | if (!outer.intersects(inner)) |
242 | fillRect(color, outer); |
243 | else { |
244 | gfx::Region rgn(outer); |
245 | rgn.createSubtraction(rgn, gfx::Region(inner)); |
246 | fillRegion(color, rgn); |
247 | } |
248 | } |
249 | |
250 | void Graphics::drawSurface(os::Surface* surface, int x, int y) |
251 | { |
252 | dirty(gfx::Rect(m_dx+x, m_dy+y, surface->width(), surface->height())); |
253 | |
254 | os::SurfaceLock lockSrc(surface); |
255 | os::SurfaceLock lockDst(m_surface.get()); |
256 | m_surface->drawSurface(surface, m_dx+x, m_dy+y); |
257 | } |
258 | |
259 | void Graphics::drawSurface(os::Surface* surface, |
260 | const gfx::Rect& srcRect, |
261 | const gfx::Rect& dstRect, |
262 | const os::Sampling& sampling, |
263 | const ui::Paint* paint) |
264 | { |
265 | dirty(gfx::Rect(m_dx+dstRect.x, m_dy+dstRect.y, |
266 | dstRect.w, dstRect.h)); |
267 | |
268 | os::SurfaceLock lockSrc(surface); |
269 | os::SurfaceLock lockDst(m_surface.get()); |
270 | m_surface->drawSurface( |
271 | surface, |
272 | srcRect, |
273 | gfx::Rect(dstRect).offset(m_dx, m_dy), |
274 | sampling, |
275 | paint); |
276 | } |
277 | |
278 | void Graphics::drawRgbaSurface(os::Surface* surface, int x, int y) |
279 | { |
280 | dirty(gfx::Rect(m_dx+x, m_dy+y, surface->width(), surface->height())); |
281 | |
282 | os::SurfaceLock lockSrc(surface); |
283 | os::SurfaceLock lockDst(m_surface.get()); |
284 | m_surface->drawRgbaSurface(surface, m_dx+x, m_dy+y); |
285 | } |
286 | |
287 | void Graphics::drawRgbaSurface(os::Surface* surface, int srcx, int srcy, int dstx, int dsty, int w, int h) |
288 | { |
289 | dirty(gfx::Rect(m_dx+dstx, m_dy+dsty, w, h)); |
290 | |
291 | os::SurfaceLock lockSrc(surface); |
292 | os::SurfaceLock lockDst(m_surface.get()); |
293 | m_surface->drawRgbaSurface(surface, srcx, srcy, m_dx+dstx, m_dy+dsty, w, h); |
294 | } |
295 | |
296 | void Graphics::drawColoredRgbaSurface(os::Surface* surface, gfx::Color color, int x, int y) |
297 | { |
298 | dirty(gfx::Rect(m_dx+x, m_dy+y, surface->width(), surface->height())); |
299 | |
300 | os::SurfaceLock lockSrc(surface); |
301 | os::SurfaceLock lockDst(m_surface.get()); |
302 | m_surface->drawColoredRgbaSurface(surface, color, gfx::ColorNone, |
303 | gfx::Clip(m_dx+x, m_dy+y, 0, 0, surface->width(), surface->height())); |
304 | } |
305 | |
306 | void Graphics::drawColoredRgbaSurface(os::Surface* surface, gfx::Color color, |
307 | int srcx, int srcy, int dstx, int dsty, int w, int h) |
308 | { |
309 | dirty(gfx::Rect(m_dx+dstx, m_dy+dsty, w, h)); |
310 | |
311 | os::SurfaceLock lockSrc(surface); |
312 | os::SurfaceLock lockDst(m_surface.get()); |
313 | m_surface->drawColoredRgbaSurface(surface, color, gfx::ColorNone, |
314 | gfx::Clip(m_dx+dstx, m_dy+dsty, srcx, srcy, w, h)); |
315 | } |
316 | |
317 | void Graphics::drawSurfaceNine(os::Surface* surface, |
318 | const gfx::Rect& src, |
319 | const gfx::Rect& center, |
320 | const gfx::Rect& dst, |
321 | const bool drawCenter, |
322 | const ui::Paint* paint) |
323 | { |
324 | gfx::Rect displacedDst(m_dx+dst.x, m_dy+dst.y, dst.w, dst.h); |
325 | dirty(displacedDst); |
326 | |
327 | os::SurfaceLock lockSrc(surface); |
328 | os::SurfaceLock lockDst(m_surface.get()); |
329 | m_surface->drawSurfaceNine(surface, src, center, displacedDst, drawCenter, paint); |
330 | } |
331 | |
332 | void Graphics::blit(os::Surface* srcSurface, int srcx, int srcy, int dstx, int dsty, int w, int h) |
333 | { |
334 | dirty(gfx::Rect(m_dx+dstx, m_dy+dsty, w, h)); |
335 | |
336 | os::SurfaceLock lockSrc(srcSurface); |
337 | os::SurfaceLock lockDst(m_surface.get()); |
338 | srcSurface->blitTo(m_surface.get(), srcx, srcy, m_dx+dstx, m_dy+dsty, w, h); |
339 | } |
340 | |
341 | void Graphics::setFont(const os::FontRef& font) |
342 | { |
343 | m_font = font; |
344 | } |
345 | |
346 | void Graphics::drawText(const std::string& str, |
347 | gfx::Color fg, gfx::Color bg, |
348 | const gfx::Point& origPt, |
349 | os::DrawTextDelegate* delegate) |
350 | { |
351 | gfx::Point pt(m_dx+origPt.x, m_dy+origPt.y); |
352 | |
353 | os::SurfaceLock lock(m_surface.get()); |
354 | gfx::Rect textBounds = |
355 | os::draw_text(m_surface.get(), m_font.get(), str, fg, bg, pt.x, pt.y, delegate); |
356 | |
357 | dirty(gfx::Rect(pt.x, pt.y, textBounds.w, textBounds.h)); |
358 | } |
359 | |
360 | namespace { |
361 | |
362 | class DrawUITextDelegate : public os::DrawTextDelegate { |
363 | public: |
364 | DrawUITextDelegate(os::Surface* surface, |
365 | os::Font* font, const int mnemonic) |
366 | : m_surface(surface) |
367 | , m_font(font) |
368 | , m_mnemonic(std::tolower(mnemonic)) |
369 | , m_underscoreColor(gfx::ColorNone) { |
370 | } |
371 | |
372 | gfx::Rect bounds() const { return m_bounds; } |
373 | |
374 | void preProcessChar(const int index, |
375 | const int codepoint, |
376 | gfx::Color& fg, |
377 | gfx::Color& bg, |
378 | const gfx::Rect& charBounds) override { |
379 | if (m_surface) { |
380 | if (m_mnemonic && |
381 | // TODO use ICU library to lower unicode chars |
382 | std::tolower(codepoint) == m_mnemonic) { |
383 | m_underscoreColor = fg; |
384 | m_mnemonic = 0; // Just one time |
385 | } |
386 | else { |
387 | m_underscoreColor = gfx::ColorNone; |
388 | } |
389 | } |
390 | } |
391 | |
392 | bool preDrawChar(const gfx::Rect& charBounds) override { |
393 | m_bounds |= charBounds; |
394 | return true; |
395 | } |
396 | |
397 | void postDrawChar(const gfx::Rect& charBounds) override { |
398 | if (!gfx::is_transparent(m_underscoreColor)) { |
399 | // TODO underscore height = guiscale() should be configurable from ui::Theme |
400 | int dy = 0; |
401 | if (m_font->type() == os::FontType::FreeType) // TODO use other method to locate the underline |
402 | dy += guiscale(); |
403 | gfx::Rect underscoreBounds(charBounds.x, charBounds.y+charBounds.h+dy, |
404 | charBounds.w, guiscale()); |
405 | |
406 | os::Paint paint; |
407 | paint.color(m_underscoreColor); |
408 | paint.style(os::Paint::Fill); |
409 | m_surface->drawRect(underscoreBounds, paint); |
410 | |
411 | m_bounds |= underscoreBounds; |
412 | } |
413 | } |
414 | |
415 | private: |
416 | os::Surface* m_surface; |
417 | os::Font* m_font; |
418 | int m_mnemonic; |
419 | gfx::Color m_underscoreColor; |
420 | gfx::Rect m_bounds; |
421 | }; |
422 | |
423 | } |
424 | |
425 | void Graphics::drawUIText(const std::string& str, gfx::Color fg, gfx::Color bg, |
426 | const gfx::Point& pt, const int mnemonic) |
427 | { |
428 | os::SurfaceLock lock(m_surface.get()); |
429 | int x = m_dx+pt.x; |
430 | int y = m_dy+pt.y; |
431 | |
432 | DrawUITextDelegate delegate(m_surface.get(), m_font.get(), mnemonic); |
433 | os::draw_text(m_surface.get(), m_font.get(), str, |
434 | fg, bg, x, y, &delegate); |
435 | |
436 | dirty(delegate.bounds()); |
437 | } |
438 | |
439 | void Graphics::drawAlignedUIText(const std::string& str, gfx::Color fg, gfx::Color bg, |
440 | const gfx::Rect& rc, const int align) |
441 | { |
442 | doUIStringAlgorithm(str, fg, bg, rc, align, true); |
443 | } |
444 | |
445 | gfx::Size Graphics::measureUIText(const std::string& str) |
446 | { |
447 | return gfx::Size( |
448 | Graphics::measureUITextLength(str, m_font.get()), |
449 | m_font->height()); |
450 | } |
451 | |
452 | // static |
453 | int Graphics::measureUITextLength(const std::string& str, os::Font* font) |
454 | { |
455 | DrawUITextDelegate delegate(nullptr, font, 0); |
456 | os::draw_text(nullptr, font, str, |
457 | gfx::ColorNone, gfx::ColorNone, 0, 0, |
458 | &delegate); |
459 | return delegate.bounds().w; |
460 | } |
461 | |
462 | gfx::Size Graphics::fitString(const std::string& str, int maxWidth, int align) |
463 | { |
464 | return doUIStringAlgorithm(str, gfx::ColorNone, gfx::ColorNone, |
465 | gfx::Rect(0, 0, maxWidth, 0), align, false); |
466 | } |
467 | |
468 | gfx::Size Graphics::doUIStringAlgorithm(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Rect& rc, int align, bool draw) |
469 | { |
470 | gfx::Point pt(0, rc.y); |
471 | |
472 | if ((align & (MIDDLE | BOTTOM)) != 0) { |
473 | gfx::Size preSize = doUIStringAlgorithm(str, gfx::ColorNone, gfx::ColorNone, rc, 0, false); |
474 | if (align & MIDDLE) |
475 | pt.y = rc.y + rc.h/2 - preSize.h/2; |
476 | else if (align & BOTTOM) |
477 | pt.y = rc.y + rc.h - preSize.h; |
478 | } |
479 | |
480 | gfx::Size calculatedSize(0, 0); |
481 | std::size_t beg, end, newBeg; |
482 | std::string line; |
483 | int lineSeparation = 2*guiscale(); |
484 | |
485 | // Draw line-by-line |
486 | for (beg=end=0; end != std::string::npos && beg<str.size(); ) { |
487 | pt.x = rc.x; |
488 | |
489 | // Without word-wrap |
490 | if ((align & (WORDWRAP | CHARWRAP)) == 0) { |
491 | end = str.find('\n', beg); |
492 | if (end != std::string::npos) |
493 | newBeg = end+1; |
494 | else |
495 | newBeg = std::string::npos; |
496 | } |
497 | // With char-wrap |
498 | else if ((align & CHARWRAP) == CHARWRAP) { |
499 | for (end=beg+1; end<str.size(); ++end) { |
500 | // If we are out of the available width (rc.w) using the new "end" |
501 | if ((rc.w > 0) && |
502 | (m_font->textLength(str.substr(beg, end-beg).c_str()) > rc.w)) { |
503 | if (end > beg+1) |
504 | --end; |
505 | break; |
506 | } |
507 | } |
508 | newBeg = end; |
509 | } |
510 | // With word-wrap |
511 | else { |
512 | std::size_t old_end = std::string::npos; |
513 | for (std::size_t new_word_beg=beg;;) { |
514 | end = str.find_first_of(" \n" , new_word_beg); |
515 | |
516 | // If we have already a word to print (old_end != npos), and |
517 | // we are out of the available width (rc.w) using the new "end" |
518 | if ((old_end != std::string::npos) && |
519 | (rc.w > 0) && |
520 | (pt.x+m_font->textLength(str.substr(beg, end-beg).c_str()) > rc.w)) { |
521 | // We go back to the "old_end" and paint from "beg" to "end" |
522 | end = old_end; |
523 | break; |
524 | } |
525 | // If we have more words to print... |
526 | else if (end != std::string::npos) { |
527 | // Force line break, now we have to paint from "beg" to "end" |
528 | if (str[end] == '\n') |
529 | break; |
530 | |
531 | // White-space, this is a beginning of a new word. |
532 | new_word_beg = end+1; |
533 | } |
534 | // We are in the end of text |
535 | else |
536 | break; |
537 | |
538 | old_end = end; |
539 | } |
540 | newBeg = end+1; |
541 | } |
542 | |
543 | // Get the entire line to be painted |
544 | if (end != std::string::npos) |
545 | line = str.substr(beg, end-beg); |
546 | else |
547 | line = str.substr(beg); |
548 | |
549 | gfx::Size lineSize( |
550 | m_font->textLength(line.c_str()), |
551 | m_font->height()+lineSeparation); |
552 | calculatedSize.w = std::max(calculatedSize.w, lineSize.w); |
553 | |
554 | // Render the text |
555 | if (draw) { |
556 | int xout; |
557 | if ((align & CENTER) == CENTER) |
558 | xout = pt.x + rc.w/2 - lineSize.w/2; |
559 | else if ((align & RIGHT) == RIGHT) |
560 | xout = pt.x + rc.w - lineSize.w; |
561 | else |
562 | xout = pt.x; |
563 | |
564 | drawText(line, fg, bg, gfx::Point(xout, pt.y)); |
565 | |
566 | if (!gfx::is_transparent(bg)) |
567 | fillAreaBetweenRects(bg, |
568 | gfx::Rect(rc.x, pt.y, rc.w, lineSize.h), |
569 | gfx::Rect(xout, pt.y, lineSize.w, lineSize.h)); |
570 | } |
571 | |
572 | pt.y += lineSize.h; |
573 | calculatedSize.h += lineSize.h; |
574 | beg = newBeg; |
575 | |
576 | if (pt.y+lineSize.h >= rc.y2()) |
577 | break; |
578 | } |
579 | |
580 | if (calculatedSize.h > 0) |
581 | calculatedSize.h -= lineSeparation; |
582 | |
583 | // Fill bottom area |
584 | if (draw && !gfx::is_transparent(bg)) { |
585 | if (pt.y < rc.y+rc.h) |
586 | fillRect(bg, gfx::Rect(rc.x, pt.y, rc.w, rc.y+rc.h-pt.y)); |
587 | } |
588 | |
589 | return calculatedSize; |
590 | } |
591 | |
592 | void Graphics::dirty(const gfx::Rect& bounds) |
593 | { |
594 | gfx::Rect rc = m_surface->getClipBounds(); |
595 | rc = rc.createIntersection(bounds); |
596 | if (!rc.isEmpty()) |
597 | m_dirtyBounds |= rc; |
598 | } |
599 | |
600 | ////////////////////////////////////////////////////////////////////// |
601 | // ScreenGraphics |
602 | |
603 | ScreenGraphics::ScreenGraphics(Display* display) |
604 | : Graphics(display, AddRef(display->surface()), 0, 0) |
605 | { |
606 | setFont(AddRef(get_theme()->getDefaultFont())); |
607 | } |
608 | |
609 | ScreenGraphics::~ScreenGraphics() |
610 | { |
611 | } |
612 | |
613 | } // namespace ui |
614 | |