16 constexpr float k_caret_width_px = 2.F;
28 internal::TextSlice& slice,
29 const Portal::Text::EditableText& edit,
30 const Portal::Text::PressParams& params)
32 const Kinesis::AABB2D caret =
slice.run().caret(edit.cursor, k_caret_width_px);
34 const glm::vec2
a =
map(caret.min);
35 const glm::vec2
b =
map(caret.max);
38 { .min = glm::min(
a,
b), .max = glm::max(
a,
b) }, 0.F, glm::vec3(params.color));
39 slice.set_tail({ quad.begin(), quad.end() });
45 internal::TextSlice& slice,
46 const Portal::Text::EditableText& edit,
47 const Portal::Text::PressParams& params)
49 slice.run().assign(edit.text, params);
53 size_t index_on_line(Portal::Text::TextRun&
run, glm::vec2
px,
float target_top)
55 const size_t nearest =
run.index_at(
px);
56 const auto* atlas =
run.atlas();
57 if (!atlas || atlas->line_height() == 0)
60 const float half_line =
static_cast<float>(atlas->line_height()) * 0.5F;
61 if (std::abs(
run.caret(nearest, 0.F).min.y - target_top) < half_line)
64 const auto& text =
run.text();
65 size_t line_start = 0;
67 if (std::abs(
run.caret(line_start, 0.F).min.y - target_top) < half_line)
69 const size_t newline = text.find(
'\n', line_start);
70 if (newline == std::string::npos)
72 line_start = newline + 1;
86 Portal::Text::EditableText& edit,
87 internal::TextSlice& slice,
91 const auto* atlas =
run.atlas();
95 const auto caret =
run.caret(edit.cursor, 0.F);
96 const float line_height =
static_cast<float>(atlas->line_height());
97 const float target_top = caret.min.y +
static_cast<float>(line_delta) * line_height;
98 const float target_y = target_top +
static_cast<float>(atlas->ascender());
99 edit.cursor = index_on_line(
run, { caret.min.x, target_y }, target_top);
103 glm::vec2 pixel_at(glm::vec2 ndc, Kinesis::AABB2D field_bounds, glm::vec2 extent)
106 (ndc.x - field_bounds.min.x) / field_bounds.width() * extent.x,
107 (field_bounds.max.y - ndc.y) / field_bounds.height() * extent.y
114 Portal::Text::EditableText& edit,
115 internal::TextSlice& slice,
116 Kinesis::AABB2D field_bounds,
120 const glm::vec2
px = pixel_at(ndc, field_bounds,
run.extent());
121 const auto* atlas =
run.atlas();
122 if (!atlas || atlas->line_height() == 0) {
123 edit.cursor =
run.index_at(
px);
127 const float line_height =
static_cast<float>(atlas->line_height());
128 const float target_top = std::floor(
px.y / line_height) * line_height;
129 edit.cursor = index_on_line(
run,
px, target_top);
138 std::shared_ptr<Portal::Text::PressParams> in_params,
139 std::string initial_text)
141 buf = std::move(in_buf);
145 params = std::move(in_params);
147 params = std::make_shared<Portal::Text::PressParams>();
159 state = std::make_shared<MappedState<Portal::Text::EditableText>>();
160 state->value.cursor = initial_text.size();
161 state->value.text = std::move(initial_text);
176 render_text(*sl, edit, *p);
177 render_caret(*sl, edit, *p);
184 render_text(*sl, edit, *p);
185 render_caret(*sl, edit, *p);
192 render_text(*sl, edit, *p);
193 render_caret(*sl, edit, *p);
200 render_text(*sl, edit, *p);
201 render_caret(*sl, edit, *p);
208 render_text(*sl, edit, *p);
209 render_caret(*sl, edit, *p);
216 render_caret(*sl, edit, *p);
223 render_caret(*sl, edit, *p);
228 move_vertical(edit, *sl, -1);
230 render_caret(*sl, edit, *p);
235 move_vertical(edit, *sl, 1);
237 render_caret(*sl, edit, *p);
243 move_to_click(edit, *sl, *fb, ndc);
245 render_caret(*sl, edit, *p);
253 std::shared_ptr<Portal::Text::PressParams> in_params,
254 std::string initial_text,
255 float content_multiplier)
259 .
min = glm::vec2(viewport.
min.x, viewport.
max.y - viewport.
height() * content_multiplier),
260 .max = glm::vec2(viewport.
max.x, viewport.
max.y)
263 place(std::move(in_buf), surface, content_bounds, std::move(in_params), std::move(initial_text));
268 [field](
Kinesis::AABB2D shifted)
mutable { field.reposition(shifted); },
284 slice->set_bounds(new_bounds);
void on_scroll(uint32_t id, ScrollFn fn)
Called on scroll while the cursor is over an element.
Context & press_and_hold(uint32_t id, IO::Keys key, KeyFn fn)
Register a key handler that fires on the initial press and on every repeat, with a deterministic firs...
void on_text(uint32_t id, TextFn fn)
Called with each resolved Unicode codepoint while the element has keyboard focus.
void on_press(uint32_t id, IO::MouseButtons btn, PressFn fn)
Called when a mouse button is pressed over an element.
Event wiring between a Layer and a window surface.
std::vector< double > slice(std::span< const double > data, double start_ratio, double end_ratio)
Extract a contiguous slice by ratio, returning a new vector.
std::array< Kakshya::MeshVertex, 6 > textured_mesh_rect(Kinesis::AABB2D region, float weight, glm::vec3 color)
Generate a UV-mapped TRIANGLE_LIST quad (6 vertices, 2 triangles) from an AABB2D, as Kakshya::MeshVer...
constexpr T map(T x, T in_lo, T in_hi, T out_lo, T out_hi) noexcept
Map x from [in_lo, in_hi] to [out_lo, out_hi], unclamped.
void move(EditableText &s, int n)
Move the cursor by n codepoints.
void erase(EditableText &s, int n)
Erase n codepoints from the cursor.
void insert_codepoint(EditableText &s, uint32_t codepoint)
Insert one codepoint at the cursor, advancing it past the insert.
void insert_literal(EditableText &s, char c)
Insert one raw ASCII byte at the cursor, advancing it past the insert.
float height() const noexcept
Axis-aligned bounding rectangle in a 2D coordinate space.
std::shared_ptr< MappedState< Portal::Text::EditableText > > state
Live editable text and cursor. Valid after place().
uint32_t element_id
Element id of the text field. Valid after place().
MAYAFLUX_API TextField & place(FormaTarget in_buf, Surface &surface, Kinesis::AABB2D bounds, std::shared_ptr< Portal::Text::PressParams > in_params, std::string initial_text={})
Register the text field element and wire text editing onto it.
std::shared_ptr< Portal::Text::PressParams > params
Shared, mutable render params - every re-render reads this fresh, so a caller can change it once and ...
Kinesis::AABB2D bounds() const noexcept
Field region, satisfying Kinesis::HasBounds.
MAYAFLUX_API void reposition(Kinesis::AABB2D new_bounds)
Move the field to a new NDC region, keeping its content.
std::shared_ptr< internal::TextSlice > slice
The text and caret drawn into buf. Valid after place().
FormaTarget buf
Target the text and caret draw into.
std::shared_ptr< Kinesis::AABB2D > m_live_bounds
Same value as field_bounds, behind a shared_ptr so wire()'s already-captured Context closures see a r...
Kinesis::AABB2D field_bounds
NDC region occupied by the field. Valid after place().
MAYAFLUX_API TextField & scrollable(FormaTarget in_buf, Surface &surface, Scrollable &scroller, std::shared_ptr< Portal::Text::PressParams > in_params, std::string initial_text={}, float content_multiplier=3.F)
Fluent creator: place this field inside an already-placed Scrollable, growing itself taller than its ...
An editable text region: keyboard/text-input wiring over an Element the caller already has a buffer f...
Mutable UTF-8 text plus a byte-offset cursor.