HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
text_field_widget.hpp
Go to the documentation of this file.
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
9#pragma once
10
12#include "widget.hpp"
13#include "label_widget.hpp"
14#include "scroll_widget.hpp"
15#include "../l10n/l10n.hpp"
16#include "../macros.hpp"
17#include <memory>
18#include <string>
19#include <array>
20#include <optional>
21#include <future>
22
23namespace hi { inline namespace v1 {
24
25template<typename Context>
27
64public:
65 using delegate_type = text_field_delegate;
66 using super = widget;
67
69
74
77 observer<semantic_text_style> text_style = semantic_text_style::label;
78
81 observer<alignment> alignment = alignment::middle_flush();
82
83 virtual ~text_field_widget()
84 {
85 hi_assert_not_null(delegate);
86 delegate->deinit(*this);
87 }
88
90 super(parent), delegate(std::move(delegate)), _text()
91 {
92 hi_assert_not_null(this->delegate);
93 _delegate_cbt = this->delegate->subscribe([&] {
94 ++global_counter<"text_field_widget:delegate:layout">;
96 });
97 this->delegate->init(*this);
98
99 _scroll_widget = std::make_unique<scroll_widget<axis::none>>(this);
100 _text_widget = &_scroll_widget->make_widget<text_widget>(_text, alignment, text_style);
101 _text_widget->mode = widget_mode::partial;
102
103 _error_label_widget =
104 std::make_unique<label_widget>(this, _error_label, alignment::top_left(), semantic_text_style::error);
105
106 _continues_cbt = continues.subscribe([&](auto...) {
107 ++global_counter<"text_field_widget:continues:constrain">;
109 });
110 _text_style_cbt = text_style.subscribe([&](auto...) {
111 ++global_counter<"text_field_widget:text_style:constrain">;
113 });
114 _text_cbt = _text.subscribe([&](auto...) {
115 ++global_counter<"text_field_widget:text:constrain">;
117 });
118 _error_label_cbt = _error_label.subscribe([&](auto const& new_value) {
119 ++global_counter<"text_field_widget:error_label:constrain">;
121 });
122 }
123
124 text_field_widget(
125 widget *parent,
127 text_field_widget_attribute auto&&...attributes) noexcept :
128 text_field_widget(parent, std::move(delegate))
129 {
130 set_attributes(hi_forward(attributes)...);
131 }
132
140 widget *parent,
142 text_field_widget_attribute auto&&...attributes) noexcept requires requires
143 {
144 make_default_text_field_delegate(hi_forward(value));
145 } : text_field_widget(parent, make_default_text_field_delegate(hi_forward(value)), hi_forward(attributes)...) {}
146
148 [[nodiscard]] generator<widget_intf&> children(bool include_invisible) noexcept override
149 {
150 co_yield *_scroll_widget;
151 }
152
153 [[nodiscard]] box_constraints update_constraints() noexcept override
154 {
155 hi_assert_not_null(delegate);
156 hi_assert_not_null(_error_label_widget);
157 hi_assert_not_null(_scroll_widget);
158
159 if (*_text_widget->focus) {
160 // Update the optional error value from the string conversion when the text-widget has keyboard focus.
161 _error_label = delegate->validate(*this, *_text);
162
163 } else {
164 // When field is not focused, simply follow the observed_value.
165 revert(false);
166 }
167
168 _layout = {};
169 _scroll_constraints = _scroll_widget->update_constraints();
170
171 hilet scroll_width = 100;
172 hilet box_size = extent2{
173 _scroll_constraints.margins.left() + scroll_width + _scroll_constraints.margins.right(),
174 _scroll_constraints.margins.top() + _scroll_constraints.preferred.height() + _scroll_constraints.margins.bottom()};
175
176 auto size = box_size;
177 auto margins = theme().margin();
178 if (_error_label->empty()) {
179 _error_label_widget->mode = widget_mode::invisible;
180 _error_label_constraints = _error_label_widget->update_constraints();
181
182 } else {
183 _error_label_widget->mode = widget_mode::display;
184 _error_label_constraints = _error_label_widget->update_constraints();
185 inplace_max(size.width(), _error_label_constraints.preferred.width());
186 size.height() += _error_label_constraints.margins.top() + _error_label_constraints.preferred.height();
187 inplace_max(margins.left(), _error_label_constraints.margins.left());
188 inplace_max(margins.right(), _error_label_constraints.margins.right());
189 inplace_max(margins.bottom(), _error_label_constraints.margins.bottom());
190 }
191
192 // The alignment of a text-field is not based on the text-widget due to the intermediate scroll widget.
193 hilet resolved_alignment = resolve_mirror(*alignment, os_settings::left_to_right());
194
195 return {size, size, size, resolved_alignment, margins};
196 }
197 void set_layout(widget_layout const& context) noexcept override
198 {
199 if (compare_store(_layout, context)) {
200 hilet scroll_size = extent2{
201 context.width(),
202 _scroll_constraints.margins.top() + _scroll_constraints.preferred.height() +
203 _scroll_constraints.margins.bottom()};
204
205 hilet scroll_rectangle = aarectangle{point2{0, context.height() - scroll_size.height()}, scroll_size};
206 _scroll_shape = box_shape{_scroll_constraints, scroll_rectangle, theme().baseline_adjustment()};
207
208 if (*_error_label_widget->mode > widget_mode::invisible) {
210 aarectangle{0, 0, context.rectangle().width(), _error_label_constraints.preferred.height()};
211 _error_label_shape = box_shape{_error_label_constraints, error_label_rectangle, theme().baseline_adjustment()};
212 }
213 }
214
215 if (*_error_label_widget->mode > widget_mode::invisible) {
216 _error_label_widget->set_layout(context.transform(_error_label_shape));
217 }
218 _scroll_widget->set_layout(context.transform(_scroll_shape));
219 }
220 void draw(draw_context const& context) noexcept override
221 {
222 if (*mode > widget_mode::invisible and overlaps(context, layout())) {
223 draw_background_box(context);
224
225 _scroll_widget->draw(context);
226 _error_label_widget->draw(context);
227 }
228 }
229 bool handle_event(gui_event const& event) noexcept override
230 {
231 switch (event.type()) {
232 case gui_event_type::gui_cancel:
233 if (*mode >= widget_mode::partial) {
234 revert(true);
235 return true;
236 }
237 break;
238
239 case gui_event_type::gui_activate:
240 if (*mode >= widget_mode::partial) {
241 commit(true);
243 }
244 break;
245
246 default:;
247 }
248
250 }
251 hitbox hitbox_test(point2 position) const noexcept override
252 {
253 if (*mode >= widget_mode::partial) {
254 auto r = hitbox{};
255 r = _scroll_widget->hitbox_test_from_parent(position, r);
256 r = _error_label_widget->hitbox_test_from_parent(position, r);
257 return r;
258 } else {
259 return hitbox{};
260 }
261 }
262 [[nodiscard]] bool accepts_keyboard_focus(keyboard_focus_group group) const noexcept override
263 {
264 if (*mode >= widget_mode::partial) {
265 return _scroll_widget->accepts_keyboard_focus(group);
266 } else {
267 return false;
268 }
269 }
270 [[nodiscard]] color focus_color() const noexcept override
271 {
272 if (*mode >= widget_mode::partial) {
273 if (not _error_label->empty()) {
274 return theme().text_style(semantic_text_style::error)->color;
275 } else if (*_text_widget->focus) {
276 return theme().color(semantic_color::accent);
277 } else if (*hover) {
278 return theme().color(semantic_color::border, semantic_layer + 1);
279 } else {
280 return theme().color(semantic_color::border, semantic_layer);
281 }
282
283 } else {
284 return theme().color(semantic_color::border, semantic_layer - 1);
285 }
286 }
288private:
289 notifier<>::callback_token _delegate_cbt;
290
294 box_constraints _scroll_constraints;
295 box_shape _scroll_shape;
296
299 text_widget *_text_widget = nullptr;
300
303 observer<gstring> _text;
304
307 observer<label> _error_label;
308 std::unique_ptr<label_widget> _error_label_widget;
309 box_constraints _error_label_constraints;
310 box_shape _error_label_shape;
311
312 typename decltype(continues)::callback_token _continues_cbt;
313 typename decltype(text_style)::callback_token _text_style_cbt;
314 typename decltype(_text)::callback_token _text_cbt;
315 typename decltype(_error_label)::callback_token _error_label_cbt;
316
317 void set_attributes() noexcept {}
318 void set_attributes(text_field_widget_attribute auto&& first, text_field_widget_attribute auto&&...rest) noexcept
319 {
320 if constexpr (forward_of<decltype(first), observer<hi::alignment>>) {
321 alignment = hi_forward(first);
322 } else if constexpr (forward_of<decltype(first), observer<hi::semantic_text_style>>) {
323 text_style = hi_forward(first);
324 } else {
325 hi_static_no_default();
326 }
327
328 set_attributes(hi_forward(rest)...);
329 }
330
331 void revert(bool force) noexcept
332 {
333 hi_assert_not_null(delegate);
334 _text = delegate->text(*this);
335 _error_label = label{};
336 }
337 void commit(bool force) noexcept
338 {
339 hi_axiom(loop::main().on_thread());
340 hi_assert_not_null(delegate);
341
342 if (*continues or force) {
343 auto text = *_text;
344
345 if (delegate->validate(*this, text).empty()) {
346 // text is valid.
347 delegate->set_text(*this, text);
348 }
349
350 // After commit get the canonical text to display from the delegate.
351 _text = delegate->text(*this);
352 _error_label = label{};
353 }
354 }
355 void draw_background_box(draw_context const& context) const noexcept
356 {
357 hilet outline = _scroll_shape.rectangle;
358
359 hilet corner_radii = hi::corner_radii(0.0f, 0.0f, theme().rounding_radius<float>(), theme().rounding_radius<float>());
360 context.draw_box(layout(), outline, background_color(), corner_radii);
361
362 hilet line = line_segment(get<0>(outline), get<1>(outline));
363 context.draw_line(layout(), translate3{0.0f, 0.5f, 0.1f} * line, theme().border_width(), focus_color());
364 }
365};
366
367}} // namespace hi::v1
Defines scroll_widget.
Defines delegate_field_delegate and some default text field delegates.
Defines widget.
Defines label_widget.
@ window_relayout
Request that widgets get laid out on the next frame.
@ window_reconstrain
Request that widget get constraint on the next frame.
std::shared_ptr< text_field_delegate > make_default_text_field_delegate(auto &&value) noexcept
Create a shared pointer to a default text delegate.
Definition text_field_delegate.hpp:198
@ partial
A widget is partially enabled.
@ invisible
The widget is invisible.
@ display
The widget is in display-only mode.
DOXYGEN BUG.
Definition algorithm.hpp:16
geometry/margins.hpp
Definition lookahead_iterator.hpp:5
bool compare_store(T &lhs, U &&rhs) noexcept
Compare then store if there was a change.
Definition misc.hpp:56
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
Horizontal/Vertical alignment combination.
Definition alignment.hpp:242
The 4 radii of the corners of a quad or rectangle.
Definition corner_radii.hpp:19
constexpr float & width() noexcept
Access the x-as-width element from the extent.
Definition extent2.hpp:104
constexpr float & height() noexcept
Access the y-as-height element from the extent.
Definition extent2.hpp:115
widget_intf * parent
Pointer to the parent widget.
Definition widget_intf.hpp:28
A delegate that controls the state of a text_field_widget.
Definition text_field_delegate.hpp:26
A single line text field.
Definition text_field_widget.hpp:63
observer< alignment > alignment
The alignment of the text.
Definition text_field_widget.hpp:81
observer< bool > continues
Continues update mode.
Definition text_field_widget.hpp:73
text_field_widget(widget *parent, different_from< std::shared_ptr< delegate_type > > auto &&value, text_field_widget_attribute auto &&...attributes) noexcept
Construct a text field widget.
Definition text_field_widget.hpp:139
observer< semantic_text_style > text_style
The style of the text.
Definition text_field_widget.hpp:77
An interactive graphical object as part of the user-interface.
Definition widget.hpp:37
widget_layout const & layout() const noexcept override
Get the current layout for this widget.
Definition widget.hpp:169
int semantic_layer
The draw layer of the widget.
Definition widget.hpp:66
observer< bool > hover
Mouse cursor is hovering over the widget.
Definition widget.hpp:46
widget(widget *parent) noexcept
Definition widget.hpp:87
bool process_event(gui_event const &event) const noexcept override
Send a event to the window.
Definition widget.hpp:178
observer< widget_mode > mode
The widget mode.
Definition widget.hpp:42
bool handle_event(gui_event const &event) noexcept override
Handle command.
Definition widget.hpp:198
Definition concepts.hpp:54
Definition text_field_widget.hpp:26
Definition text_widget.hpp:32
T move(T... args)