reflex-terminal 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.doc/ext/reflex-terminal/renderer.cpp +33 -1
- data/.doc/ext/reflex-terminal/terminal.cpp +26 -10
- data/.github/workflows/release-gem.yml +4 -49
- data/.github/workflows/tag.yml +5 -28
- data/.github/workflows/test.yml +3 -37
- data/ChangeLog.md +23 -0
- data/Gemfile.lock +26 -0
- data/README.md +2 -2
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/examples/terminal.rb +24 -13
- data/ext/reflex-terminal/renderer.cpp +37 -1
- data/ext/reflex-terminal/terminal.cpp +27 -10
- data/include/reflex/terminal.h +30 -5
- data/include/reflex-terminal/renderer.h +8 -0
- data/lib/reflex/terminal.rb +3 -2
- data/lib/reflex/terminal_view.rb +76 -10
- data/reflex-terminal.gemspec +5 -5
- data/src/pty.cpp +33 -4
- data/src/renderer.cpp +350 -80
- data/src/terminal.cpp +237 -105
- data/src/win32/pty.cpp +2 -2
- data/test/test_renderer.rb +122 -4
- data/test/test_terminal.rb +163 -8
- data/test/test_terminal_view.rb +1 -1
- metadata +12 -12
- data/.github/workflows/utils.rb +0 -127
data/src/renderer.cpp
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
#include <assert.h>
|
|
5
5
|
#include <math.h>
|
|
6
|
+
#include <algorithm>
|
|
6
7
|
#include <vector>
|
|
7
8
|
#include <map>
|
|
9
|
+
#include <xot/util.h>
|
|
8
10
|
#include <rays/image.h>
|
|
9
11
|
#include <reflex/exception.h>
|
|
10
12
|
#include "terminal.h"
|
|
@@ -17,38 +19,67 @@ namespace ReflexTerminal
|
|
|
17
19
|
using namespace Reflex;
|
|
18
20
|
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
struct Glyph
|
|
21
23
|
{
|
|
24
|
+
// a glyph's place in the atlas image. width is its own, so that a
|
|
25
|
+
// wide character does not run over the slot after it
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
coord x, y, width;
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
bool is_valid () const {return width > 0;}
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
};// Glyph
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
typedef std::map<String, Glyph> GlyphMap;
|
|
30
34
|
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
enum
|
|
33
37
|
{
|
|
34
|
-
// a glyph's place in the atlas image. width is its own, so that a
|
|
35
|
-
// wide character does not run over the slot after it
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
FONT_BOLD = Xot::bit(0),
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
FONT_ITALIC = Xot::bit(1),
|
|
40
42
|
|
|
41
|
-
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
static constexpr int NFONTS = 4;
|
|
47
|
+
|
|
48
|
+
static constexpr int ATLAS_WIDTH = 1024;
|
|
49
|
+
|
|
50
|
+
static constexpr int ATLAS_INITIAL_ROWS = 8;
|
|
51
|
+
|
|
52
|
+
static constexpr int ATLAS_MAX_HEIGHT = 4096;
|
|
53
|
+
|
|
54
|
+
static constexpr float FAINT_OPACITY = 0.5f;
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
static int
|
|
58
|
+
to_font_index (uint attribs)
|
|
59
|
+
{
|
|
60
|
+
int index = 0;
|
|
61
|
+
if (attribs & Terminal::Span::BOLD) index |= FONT_BOLD;
|
|
62
|
+
if (attribs & Terminal::Span::ITALIC) index |= FONT_ITALIC;
|
|
63
|
+
return index;
|
|
64
|
+
}
|
|
42
65
|
|
|
43
66
|
|
|
44
67
|
struct Renderer::Data
|
|
45
68
|
{
|
|
46
69
|
|
|
47
|
-
Font
|
|
70
|
+
Font fonts[NFONTS];
|
|
48
71
|
|
|
49
72
|
coord cell_width = 0, cell_height = 0;
|
|
50
73
|
|
|
51
|
-
//
|
|
74
|
+
// whether blinking spans are in their lit phase. the renderer only
|
|
75
|
+
// draws one frame, so the timer toggling this lives with a view
|
|
76
|
+
bool blink_visible = true;
|
|
77
|
+
|
|
78
|
+
float background_alpha = 1;
|
|
79
|
+
|
|
80
|
+
// where the next glyph goes. the faces share the one atlas, so
|
|
81
|
+
// that a row mixing them still draws from a single texture and
|
|
82
|
+
// the painter's batch holds
|
|
52
83
|
coord x = 0, y = 0;
|
|
53
84
|
|
|
54
85
|
// Painter::text() flushes the batch and uploads a texture on every
|
|
@@ -57,11 +88,11 @@ namespace ReflexTerminal
|
|
|
57
88
|
|
|
58
89
|
// the empty Glyph of a character too wide for the atlas is kept
|
|
59
90
|
// as well, so that it is not measured again on every frame
|
|
60
|
-
|
|
91
|
+
GlyphMap glyphs[NFONTS];
|
|
61
92
|
|
|
62
93
|
// reused between frames so that a screenful of new characters
|
|
63
94
|
// does not allocate
|
|
64
|
-
std::vector<String> missing;
|
|
95
|
+
std::vector<String> missing[NFONTS];
|
|
65
96
|
|
|
66
97
|
};// Renderer::Data
|
|
67
98
|
|
|
@@ -82,7 +113,7 @@ namespace ReflexTerminal
|
|
|
82
113
|
static void
|
|
83
114
|
reset_atlas (Renderer::Data* self)
|
|
84
115
|
{
|
|
85
|
-
self->glyphs.clear();
|
|
116
|
+
for (auto& glyphs : self->glyphs) glyphs.clear();
|
|
86
117
|
self->x = self->y = 0;
|
|
87
118
|
self->atlas = Image(ATLAS_WIDTH, self->cell_height * ATLAS_INITIAL_ROWS);
|
|
88
119
|
}
|
|
@@ -93,8 +124,18 @@ namespace ReflexTerminal
|
|
|
93
124
|
if (!font)
|
|
94
125
|
argument_error(__FILE__, __LINE__);
|
|
95
126
|
|
|
96
|
-
|
|
97
|
-
|
|
127
|
+
Font bold = font.dup(); bold .set_weight(Font::WEIGHT_BOLD);
|
|
128
|
+
Font italic = font.dup(); italic.set_italic(true);
|
|
129
|
+
Font bold_italic = bold.dup(); bold_italic.set_italic(true);
|
|
130
|
+
|
|
131
|
+
self->fonts[0] = font;
|
|
132
|
+
self->fonts[FONT_BOLD] = bold;
|
|
133
|
+
self->fonts[ FONT_ITALIC] = italic;
|
|
134
|
+
self->fonts[FONT_BOLD | FONT_ITALIC] = bold_italic;
|
|
135
|
+
|
|
136
|
+
// the cell is sized to the regular face; a family whose variants
|
|
137
|
+
// measure differently has them clipped to it
|
|
138
|
+
self->cell_width = ceil(font.get_width("M"));
|
|
98
139
|
self->cell_height = ceil(font.get_height());
|
|
99
140
|
reset_atlas(self.get());
|
|
100
141
|
}
|
|
@@ -102,7 +143,7 @@ namespace ReflexTerminal
|
|
|
102
143
|
const Font&
|
|
103
144
|
Renderer::font () const
|
|
104
145
|
{
|
|
105
|
-
return self->
|
|
146
|
+
return self->fonts[0];
|
|
106
147
|
}
|
|
107
148
|
|
|
108
149
|
coord
|
|
@@ -117,6 +158,33 @@ namespace ReflexTerminal
|
|
|
117
158
|
return self->cell_height;
|
|
118
159
|
}
|
|
119
160
|
|
|
161
|
+
void
|
|
162
|
+
Renderer::set_blink_visible (bool visible)
|
|
163
|
+
{
|
|
164
|
+
self->blink_visible = visible;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
bool
|
|
168
|
+
Renderer::blink_visible () const
|
|
169
|
+
{
|
|
170
|
+
return self->blink_visible;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
void
|
|
174
|
+
Renderer::set_background_alpha (float alpha)
|
|
175
|
+
{
|
|
176
|
+
if (!(0 <= alpha && alpha <= 1))
|
|
177
|
+
argument_error(__FILE__, __LINE__, "invalid background alpha: %g", alpha);
|
|
178
|
+
|
|
179
|
+
self->background_alpha = alpha;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
float
|
|
183
|
+
Renderer::background_alpha () const
|
|
184
|
+
{
|
|
185
|
+
return self->background_alpha;
|
|
186
|
+
}
|
|
187
|
+
|
|
120
188
|
static bool
|
|
121
189
|
grow_atlas (Renderer::Data* self)
|
|
122
190
|
{
|
|
@@ -131,6 +199,7 @@ namespace ReflexTerminal
|
|
|
131
199
|
Image grown(ATLAS_WIDTH, height);
|
|
132
200
|
Painter painter = grown.painter();
|
|
133
201
|
painter.begin();
|
|
202
|
+
painter.set_blend_mode(Rays::BLEND_REPLACE);
|
|
134
203
|
painter.image(self->atlas);
|
|
135
204
|
painter.end();
|
|
136
205
|
|
|
@@ -139,9 +208,9 @@ namespace ReflexTerminal
|
|
|
139
208
|
}
|
|
140
209
|
|
|
141
210
|
static Glyph
|
|
142
|
-
allocate_glyph (Renderer::Data* self, const String& text)
|
|
211
|
+
allocate_glyph (Renderer::Data* self, int font_index, const String& text)
|
|
143
212
|
{
|
|
144
|
-
coord width = ceil(self->
|
|
213
|
+
coord width = ceil(self->fonts[font_index].get_width(text.c_str()));
|
|
145
214
|
if (width <= 0 || width > ATLAS_WIDTH) return {0, 0, 0};
|
|
146
215
|
|
|
147
216
|
if (self->x + width > ATLAS_WIDTH)
|
|
@@ -157,49 +226,63 @@ namespace ReflexTerminal
|
|
|
157
226
|
}
|
|
158
227
|
|
|
159
228
|
static void
|
|
160
|
-
add_glyphs (Renderer::Data* self
|
|
229
|
+
add_glyphs (Renderer::Data* self)
|
|
161
230
|
{
|
|
162
231
|
// each paint switches the offscreen rendering context, which costs
|
|
163
|
-
// far more than the drawing itself, so the unknown glyphs
|
|
164
|
-
// collected first and drawn
|
|
232
|
+
// far more than the drawing itself, so the unknown glyphs of every
|
|
233
|
+
// face are collected first and drawn in the one session
|
|
165
234
|
|
|
166
|
-
|
|
167
|
-
|
|
235
|
+
GlyphMap added[NFONTS];
|
|
236
|
+
size_t nadded = 0;
|
|
237
|
+
for (int i = 0; i < NFONTS; ++i)
|
|
168
238
|
{
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
239
|
+
for (const String& text : self->missing[i])
|
|
240
|
+
{
|
|
241
|
+
if (self->glyphs[i].find(text) != self->glyphs[i].end()) continue;
|
|
242
|
+
if (added[i].find(text) != added[i].end()) continue;
|
|
243
|
+
|
|
244
|
+
Glyph glyph = allocate_glyph(self, i, text);
|
|
245
|
+
if (glyph.is_valid())
|
|
246
|
+
{
|
|
247
|
+
added[i][text] = glyph;
|
|
248
|
+
++nadded;
|
|
249
|
+
}
|
|
250
|
+
else
|
|
251
|
+
self->glyphs[i][text] = glyph;// no room: do not try again
|
|
252
|
+
}
|
|
177
253
|
}
|
|
178
|
-
if (
|
|
254
|
+
if (nadded == 0) return;
|
|
179
255
|
|
|
180
256
|
Painter painter = self->atlas.painter();
|
|
181
257
|
painter.begin();
|
|
182
|
-
painter.
|
|
258
|
+
painter.set_blend_mode(Rays::BLEND_REPLACE);
|
|
183
259
|
painter.set_fill(1, 1, 1);
|
|
184
|
-
for (
|
|
185
|
-
|
|
186
|
-
|
|
260
|
+
for (int i = 0; i < NFONTS; ++i)
|
|
261
|
+
{
|
|
262
|
+
if (added[i].empty()) continue;
|
|
187
263
|
|
|
188
|
-
|
|
264
|
+
painter.set_font(self->fonts[i]);
|
|
265
|
+
for (const auto& it : added[i])
|
|
266
|
+
painter.text(it.first.c_str(), it.second.x, it.second.y);
|
|
267
|
+
|
|
268
|
+
self->glyphs[i].insert(added[i].begin(), added[i].end());
|
|
269
|
+
}
|
|
270
|
+
painter.end();
|
|
189
271
|
}
|
|
190
272
|
|
|
191
273
|
template <typename FUN>
|
|
192
274
|
static void
|
|
193
275
|
add_missing_glyphs (Renderer::Data* self, FUN fun)
|
|
194
276
|
{
|
|
195
|
-
assert(
|
|
277
|
+
assert(std::all_of(
|
|
278
|
+
self->missing, self->missing + NFONTS,
|
|
279
|
+
[](const auto& missing) {return missing.empty();}));
|
|
196
280
|
|
|
197
281
|
fun();
|
|
198
|
-
if (self->missing.empty())
|
|
199
|
-
return;
|
|
200
282
|
|
|
201
|
-
add_glyphs(self
|
|
202
|
-
self->missing
|
|
283
|
+
add_glyphs(self);
|
|
284
|
+
for (auto& missing : self->missing)
|
|
285
|
+
missing.clear();
|
|
203
286
|
}
|
|
204
287
|
|
|
205
288
|
void
|
|
@@ -209,21 +292,23 @@ namespace ReflexTerminal
|
|
|
209
292
|
// rendering context and replaces the image whenever it grows, so a
|
|
210
293
|
// caller must not run bake_glyphs inside a frame being drawn
|
|
211
294
|
|
|
212
|
-
if (!self->
|
|
295
|
+
if (!self->fonts[0])
|
|
213
296
|
invalid_state_error(__FILE__, __LINE__);
|
|
214
297
|
|
|
215
298
|
// an empty atlas is seeded with printable ascii, so that a screen
|
|
216
299
|
// of it is never handed back to be rasterized a character at a
|
|
217
300
|
// time. it also leaves the atlas holding something no matter what
|
|
218
|
-
// the screen showed, which is how a caller tells it has been baked
|
|
219
|
-
|
|
301
|
+
// the screen showed, which is how a caller tells it has been baked.
|
|
302
|
+
// only the regular face is seeded; the other faces are rare enough
|
|
303
|
+
// to earn their place as they appear
|
|
304
|
+
if (self->glyphs[0].empty())
|
|
220
305
|
{
|
|
221
306
|
// baked on its own, so that the walk that follows collects
|
|
222
307
|
// only what the seed does not already cover
|
|
223
308
|
add_missing_glyphs(self.get(), [&]()
|
|
224
309
|
{
|
|
225
310
|
for (char c = 0x20; c <= 0x7e; ++c)
|
|
226
|
-
self->missing.emplace_back(&c, 1);
|
|
311
|
+
self->missing[0].emplace_back(&c, 1);
|
|
227
312
|
});
|
|
228
313
|
}
|
|
229
314
|
|
|
@@ -234,6 +319,9 @@ namespace ReflexTerminal
|
|
|
234
319
|
{
|
|
235
320
|
for (const Terminal::Span& span : row)
|
|
236
321
|
{
|
|
322
|
+
if (span.attribs & Terminal::Span::INVISIBLE) continue;
|
|
323
|
+
|
|
324
|
+
int font_index = to_font_index(span.attribs);
|
|
237
325
|
const uint* offsets = cell_offsets + span.cell_offset;
|
|
238
326
|
for (uint i = 0; i < span.cell_size; ++i)
|
|
239
327
|
{
|
|
@@ -241,8 +329,8 @@ namespace ReflexTerminal
|
|
|
241
329
|
uint end = (i + 1) < span.cell_size ? offsets[i + 1] : (uint) span.text.size();
|
|
242
330
|
|
|
243
331
|
String text(span.text.data() + begin, end - begin);
|
|
244
|
-
if (self->glyphs.find(text) == self->glyphs.end())
|
|
245
|
-
self->missing.emplace_back(text);
|
|
332
|
+
if (self->glyphs[font_index].find(text) == self->glyphs[font_index].end())
|
|
333
|
+
self->missing[font_index].emplace_back(text);
|
|
246
334
|
}
|
|
247
335
|
}
|
|
248
336
|
}
|
|
@@ -256,7 +344,10 @@ namespace ReflexTerminal
|
|
|
256
344
|
// had the room for them: one too wide to fit is kept as an empty Glyph
|
|
257
345
|
// and still counted
|
|
258
346
|
|
|
259
|
-
|
|
347
|
+
size_t count = 0;
|
|
348
|
+
for (const auto& glyphs : self->glyphs)
|
|
349
|
+
count += glyphs.size();
|
|
350
|
+
return count;
|
|
260
351
|
}
|
|
261
352
|
|
|
262
353
|
static Color
|
|
@@ -279,57 +370,162 @@ namespace ReflexTerminal
|
|
|
279
370
|
((attribs & Terminal::Span::SELECTED) != 0);
|
|
280
371
|
}
|
|
281
372
|
|
|
282
|
-
|
|
283
|
-
|
|
373
|
+
static Color
|
|
374
|
+
to_text_color (uint attribs, Color color)
|
|
284
375
|
{
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
if (!self->font)
|
|
288
|
-
invalid_state_error(__FILE__, __LINE__);
|
|
376
|
+
if (attribs & Terminal::Span::FAINT)
|
|
377
|
+
color.alpha *= FAINT_OPACITY;
|
|
289
378
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
// or the application has named a color of its own
|
|
293
|
-
Color theme_fg(1, 1, 1), theme_bg(0, 0, 0);
|
|
294
|
-
terminal.get_color(Terminal::COLOR_FOREGROUND, &theme_fg);
|
|
295
|
-
terminal.get_color(Terminal::COLOR_BACKGROUND, &theme_bg);
|
|
379
|
+
return color;
|
|
380
|
+
}
|
|
296
381
|
|
|
297
|
-
|
|
298
|
-
|
|
382
|
+
static int
|
|
383
|
+
underline_style (uint attribs)
|
|
384
|
+
{
|
|
385
|
+
return (int) ((attribs & Terminal::Span::UNDERLINE_MASK) >> Terminal::Span::UNDERLINE_SHIFT);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
static bool
|
|
389
|
+
has_decorations (uint attribs)
|
|
390
|
+
{
|
|
391
|
+
return
|
|
392
|
+
(attribs & (Terminal::Span::STRIKETHROUGH | Terminal::Span::OVERLINE)) ||
|
|
393
|
+
underline_style(attribs) != Terminal::Span::UNDERLINE_NONE;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
static bool
|
|
397
|
+
is_blink_hidden (const Renderer::Data* self, uint attribs)
|
|
398
|
+
{
|
|
399
|
+
return (attribs & Terminal::Span::BLINK) && !self->blink_visible;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
static void
|
|
403
|
+
draw_curly_underline (
|
|
404
|
+
Painter* painter, const Color& color,
|
|
405
|
+
coord left, coord top, coord width, coord cw, coord ch,
|
|
406
|
+
coord thickness, coord amplitude)
|
|
407
|
+
{
|
|
408
|
+
static constexpr int NSEGMENTS = 8;
|
|
409
|
+
|
|
410
|
+
coord center_y = top + ch - amplitude - thickness * 0.5f;
|
|
411
|
+
size_t npoints = (size_t) ceil(width / cw * NSEGMENTS) + 1;
|
|
412
|
+
|
|
413
|
+
std::vector<Point> points;
|
|
414
|
+
points.reserve(npoints);
|
|
415
|
+
for (size_t i = 0; i < npoints; ++i)
|
|
416
|
+
{
|
|
417
|
+
coord x = std::min((coord) (cw / NSEGMENTS * i), width);
|
|
418
|
+
points.emplace_back(left + x, center_y + amplitude * sin(x / cw * 2 * M_PI));
|
|
419
|
+
}
|
|
299
420
|
|
|
300
421
|
painter->push_state();
|
|
301
|
-
painter->
|
|
422
|
+
painter->no_fill();
|
|
423
|
+
painter->set_stroke(color);
|
|
424
|
+
painter->set_stroke_width(thickness);
|
|
425
|
+
painter->line(points.data(), points.size());
|
|
426
|
+
painter->pop_state();
|
|
427
|
+
}
|
|
302
428
|
|
|
303
|
-
|
|
304
|
-
|
|
429
|
+
static void
|
|
430
|
+
draw_decoration (
|
|
431
|
+
Painter* painter, uint attribs, const Color& color,
|
|
432
|
+
coord left, coord top, coord width, coord cw, coord ch)
|
|
433
|
+
{
|
|
434
|
+
coord amplitude = std::max((coord) 1, (coord) round(ch / 16));
|
|
435
|
+
coord t = amplitude;
|
|
436
|
+
if (attribs & Terminal::Span::BOLD) t = ceil(t * 1.5f);
|
|
437
|
+
|
|
438
|
+
painter->set_fill(color);
|
|
439
|
+
if (attribs & Terminal::Span::STRIKETHROUGH)
|
|
440
|
+
painter->rect(left, top + ch * 0.5f - t, width, t);
|
|
441
|
+
if (attribs & Terminal::Span::OVERLINE)
|
|
442
|
+
painter->rect(left, top, width, t);
|
|
443
|
+
|
|
444
|
+
coord bottom = top + ch - t;
|
|
445
|
+
switch (underline_style(attribs))
|
|
446
|
+
{
|
|
447
|
+
case Terminal::Span::UNDERLINE_SINGLE:
|
|
448
|
+
painter->rect(left, bottom, width, t);
|
|
449
|
+
break;
|
|
450
|
+
|
|
451
|
+
case Terminal::Span::UNDERLINE_DOUBLE:
|
|
452
|
+
painter->rect(left, bottom, width, t);
|
|
453
|
+
painter->rect(left, bottom - t * 2, width, t);
|
|
454
|
+
break;
|
|
455
|
+
|
|
456
|
+
case Terminal::Span::UNDERLINE_DOTTED:
|
|
457
|
+
for (coord x = 0; x < width; x += t * 2)
|
|
458
|
+
painter->rect(left + x, bottom, std::min(t, width - x), t);
|
|
459
|
+
break;
|
|
460
|
+
|
|
461
|
+
case Terminal::Span::UNDERLINE_DASHED:
|
|
462
|
+
for (coord x = 0, dash = cw / 3; x < width; x += dash * 2)
|
|
463
|
+
painter->rect(left + x, bottom, std::min(dash, width - x), t);
|
|
464
|
+
break;
|
|
465
|
+
|
|
466
|
+
case Terminal::Span::UNDERLINE_CURLY:
|
|
467
|
+
draw_curly_underline(painter, color, left, top, width, cw, ch, t, amplitude);
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
static void
|
|
473
|
+
draw_backgrounds (
|
|
474
|
+
Renderer::Data* self, Painter* painter, const Terminal& terminal,
|
|
475
|
+
const Color& theme_fg, const Color& theme_bg)
|
|
476
|
+
{
|
|
477
|
+
coord cw = self->cell_width;
|
|
478
|
+
coord ch = self->cell_height;
|
|
305
479
|
|
|
306
|
-
// every background first, then every glyph: alternating shapes
|
|
307
|
-
// and images breaks the painter's batch and costs several times
|
|
308
|
-
// more than the two passes do
|
|
309
480
|
const Terminal::RowList& rows = terminal.spans();
|
|
310
481
|
for (size_t y = 0; y < rows.size(); ++y)
|
|
311
482
|
{
|
|
312
483
|
for (const Terminal::Span& span : rows[y])
|
|
313
484
|
{
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
if (
|
|
485
|
+
if (colors_inverted(span.attribs))
|
|
486
|
+
painter->set_fill(to_color(span.fg, theme_fg));
|
|
487
|
+
else if (span.bg != Terminal::Span::COLOR_NONE)
|
|
488
|
+
painter->set_fill(to_color(span.bg, theme_bg));
|
|
489
|
+
else
|
|
490
|
+
continue;
|
|
317
491
|
|
|
318
|
-
painter->set_fill(fill);
|
|
319
492
|
painter->rect(span.x * cw, y * ch, span.width * cw, ch);
|
|
320
493
|
}
|
|
321
494
|
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
static void
|
|
498
|
+
draw_glyphs (
|
|
499
|
+
Renderer::Data* self, Painter* painter, const Terminal& terminal,
|
|
500
|
+
const Color& theme_fg, const Color& theme_bg)
|
|
501
|
+
{
|
|
502
|
+
coord cw = self->cell_width;
|
|
503
|
+
coord ch = self->cell_height;
|
|
322
504
|
|
|
323
505
|
const Image& atlas = self->atlas;
|
|
324
506
|
const uint* cell_offsets = Terminal_get_cell_offsets(terminal).data();
|
|
507
|
+
|
|
508
|
+
const Terminal::RowList& rows = terminal.spans();
|
|
325
509
|
for (size_t y = 0; y < rows.size(); ++y)
|
|
326
510
|
{
|
|
327
511
|
for (const Terminal::Span& span : rows[y])
|
|
328
512
|
{
|
|
329
|
-
if (span.cell_size == 0)
|
|
513
|
+
if (span.cell_size == 0)
|
|
514
|
+
continue;
|
|
515
|
+
|
|
516
|
+
if (span.attribs & Terminal::Span::INVISIBLE)
|
|
517
|
+
continue;
|
|
518
|
+
|
|
519
|
+
if (is_blink_hidden(self, span.attribs))
|
|
520
|
+
continue;
|
|
330
521
|
|
|
331
522
|
bool inverted = colors_inverted(span.attribs);
|
|
332
|
-
painter->set_fill(
|
|
523
|
+
painter->set_fill(to_text_color(
|
|
524
|
+
span.attribs,
|
|
525
|
+
inverted ? to_color(span.bg, theme_bg) : to_color(span.fg, theme_fg)));
|
|
526
|
+
|
|
527
|
+
int font_index = to_font_index(span.attribs);
|
|
528
|
+
const GlyphMap& glyphs = self->glyphs[font_index];
|
|
333
529
|
|
|
334
530
|
// a span breaks where narrow meets wide, so every cell in
|
|
335
531
|
// one steps the same distance
|
|
@@ -344,10 +540,11 @@ namespace ReflexTerminal
|
|
|
344
540
|
uint end = i + 1 < span.cell_size ? offsets[i + 1] : (uint) span.text.size();
|
|
345
541
|
String text(span.text.data() + begin, end - begin);
|
|
346
542
|
|
|
347
|
-
auto it =
|
|
348
|
-
if (it ==
|
|
543
|
+
auto it = glyphs.find(text);
|
|
544
|
+
if (it == glyphs.end() || !it->second.is_valid())
|
|
349
545
|
{
|
|
350
546
|
// the atlas is full: draw it the slow way
|
|
547
|
+
painter->set_font(self->fonts[font_index]);
|
|
351
548
|
painter->text(text.c_str(), left, top);
|
|
352
549
|
continue;
|
|
353
550
|
}
|
|
@@ -357,6 +554,79 @@ namespace ReflexTerminal
|
|
|
357
554
|
}
|
|
358
555
|
}
|
|
359
556
|
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
static Color
|
|
560
|
+
to_decoration_color (
|
|
561
|
+
const Terminal::Span& span, const Color& theme_fg, const Color& theme_bg)
|
|
562
|
+
{
|
|
563
|
+
if (span.ul != Terminal::Span::COLOR_NONE)
|
|
564
|
+
return to_color(span.ul, theme_fg);
|
|
565
|
+
else if (colors_inverted(span.attribs))
|
|
566
|
+
return to_color(span.bg, theme_bg);
|
|
567
|
+
else
|
|
568
|
+
return to_color(span.fg, theme_fg);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
static void
|
|
572
|
+
draw_decorations (
|
|
573
|
+
Renderer::Data* self, Painter* painter, const Terminal& terminal,
|
|
574
|
+
const Color& theme_fg, const Color& theme_bg)
|
|
575
|
+
{
|
|
576
|
+
coord cw = self->cell_width;
|
|
577
|
+
coord ch = self->cell_height;
|
|
578
|
+
|
|
579
|
+
const Terminal::RowList& rows = terminal.spans();
|
|
580
|
+
for (size_t y = 0; y < rows.size(); ++y)
|
|
581
|
+
{
|
|
582
|
+
for (const Terminal::Span& span : rows[y])
|
|
583
|
+
{
|
|
584
|
+
if (!has_decorations(span.attribs))
|
|
585
|
+
continue;
|
|
586
|
+
|
|
587
|
+
if (is_blink_hidden(self, span.attribs))
|
|
588
|
+
continue;
|
|
589
|
+
|
|
590
|
+
draw_decoration(
|
|
591
|
+
painter, span.attribs,
|
|
592
|
+
to_text_color(span.attribs, to_decoration_color(span, theme_fg, theme_bg)),
|
|
593
|
+
span.x * cw, y * ch, span.width * cw, cw, ch);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
void
|
|
599
|
+
Renderer::draw (Painter* painter, const Terminal& terminal, const Bounds& bounds)
|
|
600
|
+
{
|
|
601
|
+
if (!painter)
|
|
602
|
+
argument_error(__FILE__, __LINE__);
|
|
603
|
+
if (!self->fonts[0])
|
|
604
|
+
invalid_state_error(__FILE__, __LINE__);
|
|
605
|
+
|
|
606
|
+
// what the terminal has not been told is left to the renderer, so the
|
|
607
|
+
// theme starts here and get_color only writes over it when the child
|
|
608
|
+
// or the application has named a color of its own
|
|
609
|
+
Color theme_fg(1, 1, 1), theme_bg(0, 0, 0);
|
|
610
|
+
terminal.get_color(Terminal::COLOR_FOREGROUND, &theme_fg);
|
|
611
|
+
terminal.get_color(Terminal::COLOR_BACKGROUND, &theme_bg);
|
|
612
|
+
|
|
613
|
+
painter->push_state();
|
|
614
|
+
painter->set_font(self->fonts[0]);
|
|
615
|
+
|
|
616
|
+
if (self->background_alpha > 0)
|
|
617
|
+
{
|
|
618
|
+
Color background = theme_bg;
|
|
619
|
+
background.alpha = self->background_alpha;
|
|
620
|
+
painter->set_fill(background);
|
|
621
|
+
painter->rect(bounds);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// a pass per kind rather than span by span: alternating shapes and
|
|
625
|
+
// images would break the painter's batch and cost several times
|
|
626
|
+
// more. decorations go last, over the glyphs they cross
|
|
627
|
+
draw_backgrounds(self.get(), painter, terminal, theme_fg, theme_bg);
|
|
628
|
+
draw_glyphs( self.get(), painter, terminal, theme_fg, theme_bg);
|
|
629
|
+
draw_decorations(self.get(), painter, terminal, theme_fg, theme_bg);
|
|
360
630
|
|
|
361
631
|
painter->pop_state();
|
|
362
632
|
}
|