HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
draw_context_impl.hpp
1// Copyright Take Vos 2021-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "draw_context_intf.hpp"
8#include "gfx_pipeline_box_vulkan_impl.hpp"
9#include "gfx_pipeline_image_vulkan_impl.hpp"
10#include "gfx_pipeline_SDF_vulkan_impl.hpp"
11#include "gfx_pipeline_override_vulkan_impl.hpp"
12#include "gfx_device_vulkan_intf.hpp"
13#include "../text/text.hpp"
14#include "../macros.hpp"
15
16hi_export_module(hikogui.GFX : draw_context_impl);
17
18hi_export namespace hi { inline namespace v1 {
19
20inline draw_context::draw_context(
21 gfx_device& device,
22 vector_span<gfx_pipeline_box::vertex>& box_vertices,
23 vector_span<gfx_pipeline_image::vertex>& image_vertices,
24 vector_span<gfx_pipeline_SDF::vertex>& sdf_vertices,
25 vector_span<gfx_pipeline_override::vertex>& override_vertices) noexcept :
26 device(std::addressof(device)),
27 frame_buffer_index(std::numeric_limits<size_t>::max()),
28 scissor_rectangle(),
29 _box_vertices(&box_vertices),
30 _image_vertices(&image_vertices),
31 _sdf_vertices(&sdf_vertices),
32 _override_vertices(&override_vertices)
33{
34 _box_vertices->clear();
35 _image_vertices->clear();
36 _sdf_vertices->clear();
37 _override_vertices->clear();
38}
39
40inline void
41draw_context::_draw_override(aarectangle const& clipping_rectangle, quad box, draw_attributes const& attributes) const noexcept
42{
43 if (_override_vertices->full()) {
44 // Too many boxes where added, just don't draw them anymore.
45 ++global_counter<"override::overflow">;
46 return;
47 }
48
49 gfx_pipeline_override::device_shared::place_vertices(*_override_vertices, clipping_rectangle, box, attributes.fill_color, attributes.line_color);
50}
51
52inline void
53draw_context::_draw_box(aarectangle const& clipping_rectangle, quad box, draw_attributes const& attributes) const noexcept
54{
55 // clang-format off
56 auto const border_radius = attributes.line_width * 0.5f;
57 auto const box_ =
58 attributes.border_side == hi::border_side::inside ? box - border_radius :
59 attributes.border_side == hi::border_side::outside ? box + border_radius :
60 box;
61
62 auto const corner_radius =
63 attributes.border_side == hi::border_side::inside ? attributes.corner_radius - border_radius :
64 attributes.border_side == hi::border_side::outside ? attributes.corner_radius + border_radius :
65 attributes.corner_radius;
66 // clang-format on
67
68 if (_box_vertices->full()) {
69 // Too many boxes where added, just don't draw them anymore.
70 ++global_counter<"draw_box::overflow">;
71 return;
72 }
73
74 gfx_pipeline_box::device_shared::place_vertices(
75 *_box_vertices,
76 clipping_rectangle,
77 box_,
78 attributes.fill_color,
79 attributes.line_color,
80 attributes.line_width,
81 corner_radius);
82}
83
84[[nodiscard]] inline bool draw_context::_draw_image(
85 aarectangle const& clipping_rectangle,
86 quad const& box,
87 gfx_pipeline_image::paged_image const& image) const noexcept
88{
89 hi_assert_not_null(_image_vertices);
90
91 if (image.state != gfx_pipeline_image::paged_image::state_type::uploaded) {
92 return false;
93 }
94
95 device->image_pipeline->place_vertices(*_image_vertices, clipping_rectangle, box, image);
96 return true;
97}
98
99inline void draw_context::_draw_glyph(
100 aarectangle const& clipping_rectangle,
101 quad const& box,
102 font const& font,
103 glyph_id glyph,
104 draw_attributes const& attributes) const noexcept
105{
106 hi_assert_not_null(_sdf_vertices);
107
108 if (_sdf_vertices->full()) {
109 auto box_attributes = attributes;
110 box_attributes.fill_color = hi::color{1.0f, 0.0f, 1.0f}; // Magenta.
111 _draw_box(clipping_rectangle, box, box_attributes);
112 ++global_counter<"draw_glyph::overflow">;
113 return;
114 }
115
116 auto const atlas_was_updated =
117 device->SDF_pipeline->place_vertices(*_sdf_vertices, clipping_rectangle, box, font, glyph, attributes.fill_color);
118
119 if (atlas_was_updated) {
120 device->SDF_pipeline->prepare_atlas_for_rendering();
121 }
122}
123
124inline void draw_context::_draw_text(
125 aarectangle const& clipping_rectangle,
126 matrix3 const& transform,
127 text_shaper const& text,
128 draw_attributes const& attributes) const noexcept
129{
130 hi_assert_not_null(_sdf_vertices);
131
132 auto atlas_was_updated = false;
133 for (auto const& c : text) {
134 auto const box = translate2{c.position} * c.metrics.bounding_rectangle;
135 auto const color = attributes.num_colors > 0 ? attributes.fill_color : quad_color{c.style->color};
136
137 if (not is_visible(c.general_category)) {
138 continue;
139
140 } else if (_sdf_vertices->full()) {
141 auto box_attributes = attributes;
142 box_attributes.fill_color = hi::color{1.0f, 0.0f, 1.0f}; // Magenta.
143 _draw_box(clipping_rectangle, box, box_attributes);
144 ++global_counter<"draw_glyph::overflow">;
145 break;
146 }
147
148 atlas_was_updated |= device->SDF_pipeline->place_vertices(
149 *_sdf_vertices, clipping_rectangle, transform * box, *c.glyphs.font, c.glyphs.ids.front(), color);
150 }
151
152 if (atlas_was_updated) {
153 device->SDF_pipeline->prepare_atlas_for_rendering();
154 }
155}
156
157inline void draw_context::_draw_text_selection(
158 aarectangle const& clipping_rectangle,
159 matrix3 const& transform,
160 text_shaper const& text,
161 text_selection const& selection,
162 draw_attributes const& attributes) const noexcept
163{
164 auto const[first, last] = selection.selection_indices();
165 auto const first_ = text.begin() + first;
166 auto const last_ = text.begin() + last;
167 hi_axiom(first_ <= text.end());
168 hi_axiom(last_ <= text.end());
169 hi_axiom(first_ <= last_);
170
171 for (auto it = first_; it != last_; ++it) {
172 _draw_box(clipping_rectangle, transform * it->rectangle, attributes);
173 }
174}
175
176inline void draw_context::_draw_text_insertion_cursor_empty(
177 aarectangle const& clipping_rectangle,
178 matrix3 const& transform,
179 text_shaper const& text,
180 draw_attributes const& attributes) const noexcept
181{
182 auto const maximum_left = std::round(text.rectangle().left() - 0.5f);
183 auto const maximum_right = std::round(text.rectangle().right() - 0.5f);
184 auto const& only_line = text.lines()[0];
185
186 auto const bottom = std::floor(only_line.rectangle.bottom());
187 auto const top = std::ceil(only_line.rectangle.top());
188 auto const left = only_line.paragraph_direction == unicode_bidi_class::L ? maximum_left : maximum_right;
189
190 auto const shape_I = aarectangle{point2{left, bottom}, point2{left + 1.0f, top}};
191 _draw_box(clipping_rectangle, transform * shape_I, attributes);
192}
193
194inline void draw_context::_draw_text_insertion_cursor(
195 aarectangle const& clipping_rectangle,
196 matrix3 const& transform,
197 text_shaper const& text,
198 text_cursor cursor,
199 bool show_flag,
200 draw_attributes const& attributes) const noexcept
201{
202 auto const maximum_left = std::round(text.rectangle().left() - 0.5f);
203 auto const maximum_right = std::round(text.rectangle().right() - 0.5f);
204
205 auto const it = text.get_it(cursor);
206 auto const& line = text.lines()[it->line_nr];
207 auto const ltr = it->direction == unicode_bidi_class::L;
208 auto const on_right = ltr == cursor.after();
209
210 // The initial position of the cursor.
211 auto bottom = std::floor(line.rectangle.bottom());
212 auto top = std::ceil(line.rectangle.top());
213 auto left = std::round((on_right ? it->rectangle.right() : it->rectangle.left()) - 0.5f);
214
215 auto const next_line_nr = it->line_nr + 1;
216 auto const line_ltr = line.paragraph_direction == unicode_bidi_class::L;
217 auto const end_of_line = line_ltr ? it->column_nr == line.columns.size() - 1 : it->column_nr == 0;
218 if (cursor.after() and end_of_line and next_line_nr < text.lines().size()) {
219 // The cursor is after the last character on the line,
220 // the cursor should appear at the start of the next line.
221 auto const& next_line = text.lines()[next_line_nr];
222
223 bottom = std::floor(next_line.rectangle.bottom());
224 top = std::ceil(next_line.rectangle.top());
225 left = it->direction == unicode_bidi_class::L ? maximum_left : maximum_right;
226 }
227
228 // Clamp the cursor position between the left and right side of the layed out text.
229 left = std::clamp(left, maximum_left - 1.0f, maximum_right + 1.0f);
230
231 // Draw the vertical line cursor.
232 auto const shape_I = aarectangle{point2{left, bottom}, point2{left + 1.0f, top}};
233 _draw_box(clipping_rectangle, transform * shape_I, attributes);
234
235 if (show_flag) {
236 // Draw the LTR/RTL flag at the top of the line cursor.
237 auto const shape_flag = ltr ? aarectangle{point2{left + 1.0f, top - 1.0f}, point2{left + 3.0f, top}} :
238 aarectangle{point2{left - 2.0f, top - 1.0f}, point2{left, top}};
239
240 _draw_box(clipping_rectangle, transform * shape_flag, attributes);
241 }
242}
243
244inline void draw_context::_draw_text_overwrite_cursor(
245 aarectangle const& clipping_rectangle,
246 matrix3 const& transform,
247 text_shaper::char_const_iterator it,
248 draw_attributes const& attributes) const noexcept
249{
250 auto const box = ceil(it->rectangle) + 0.5f;
251 _draw_box(clipping_rectangle, transform * box, attributes);
252}
253
254inline void draw_context::_draw_text_cursors(
255 aarectangle const& clipping_rectangle,
256 matrix3 const& transform,
257 text_shaper const& text,
258 text_cursor primary_cursor,
259 bool overwrite_mode,
260 bool dead_character_mode,
261 draw_attributes const& attributes) const noexcept
262{
263 hi_axiom(attributes.line_width == 0.0f);
264
265 if (text.empty()) {
266 // When text is empty, draw a cursor directly.
267 return _draw_text_insertion_cursor_empty(clipping_rectangle, transform, text, attributes);
268 }
269
270 auto draw_flags = false;
271
272 hi_assert_bounds(primary_cursor.index(), text);
273
274 if (dead_character_mode) {
275 hi_assert(primary_cursor.before());
276 auto cursor_attributes = attributes;
277 cursor_attributes.fill_color = attributes.line_color;
278 cursor_attributes.line_color = {};
279 return _draw_text_overwrite_cursor(
280 clipping_rectangle, transform, text.begin() + primary_cursor.index(), cursor_attributes);
281 }
282
283 if (overwrite_mode and primary_cursor.before()) {
284 auto cursor_attributes = attributes;
285 cursor_attributes.fill_color = {};
286 cursor_attributes.line_color = attributes.fill_color;
287 cursor_attributes.line_width = 1.0f;
288 return _draw_text_overwrite_cursor(
289 clipping_rectangle, transform, text.begin() + primary_cursor.index(), cursor_attributes);
290 }
291
292 // calculate the position of the primary cursor.
293 auto const primary_it = text.begin() + primary_cursor.index();
294 auto const primary_ltr = primary_it->direction == unicode_bidi_class::L;
295 auto const primary_is_on_right = primary_ltr == primary_cursor.after();
296 auto const primary_is_on_left = not primary_is_on_right;
297
298 do {
299 if (primary_cursor.start_of_text() or primary_cursor.end_of_text(text.size())) {
300 // Don't draw secondary cursor which would be on the other edge of the text-field.
301 break;
302 }
303
304 auto const secondary_cursor = primary_cursor.neighbor(text.size());
305 auto const secondary_it = text.begin() + secondary_cursor.index();
306 auto const secondary_ltr = secondary_it->direction == unicode_bidi_class::L;
307 auto const secondary_is_on_right = secondary_ltr == secondary_cursor.after();
308 auto const secondary_is_on_left = not secondary_is_on_right;
309
310 if (primary_is_on_right and secondary_is_on_left and text.move_right_char(primary_it) == secondary_it) {
311 // The secondary character is right of primary character, and the cursors are touching.
312 break;
313 } else if (primary_is_on_left and secondary_is_on_right and text.move_left_char(primary_it) == secondary_it) {
314 // The secondary character is left of primary character, and the cursors are touching.
315 break;
316 }
317
318 draw_flags = true;
319 auto cursor_attributes = attributes;
320 cursor_attributes.fill_color = attributes.line_color;
321 cursor_attributes.line_color = {};
322 _draw_text_insertion_cursor(clipping_rectangle, transform, text, secondary_cursor, draw_flags, cursor_attributes);
323 } while (false);
324
325 _draw_text_insertion_cursor(clipping_rectangle, transform, text, primary_cursor, draw_flags, attributes);
326}
327
328}} // namespace hi::v1
@ bottom
Align to the bottom.
@ top
Align to the top.
@ left
Align the text to the left side.
@ rectangle
The gui_event has rectangle data.
The HikoGUI namespace.
Definition array_generic.hpp:20
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
This is a RGBA floating point color.
Definition color_intf.hpp:49
T addressof(T... args)
T ceil(T... args)
T floor(T... args)
T round(T... args)