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