88 hi_assert_not_null(delegate);
89 delegate->deinit(*
this);
95 hi_assert_not_null(this->delegate);
96 _delegate_cbt = this->delegate->subscribe([&] {
97 ++global_counter<
"text_field_widget:delegate:layout">;
100 this->delegate->init(*
this);
102 _scroll_widget = std::make_unique<scroll_widget<axis::none>>(
this);
106 _error_label_widget =
107 std::make_unique<label_widget>(
this, _error_label, alignment::top_left(), semantic_text_style::error);
110 ++global_counter<
"text_field_widget:continues:constrain">;
114 ++global_counter<
"text_field_widget:text_style:constrain">;
117 _text_cbt = _text.subscribe([&](
auto...) {
118 ++global_counter<
"text_field_widget:text:constrain">;
122 ++global_counter<
"text_field_widget:error_label:constrain">;
127 template<text_field_widget_attribute...
Attributes>
134 set_attributes(std::forward<Attributes>(attributes)...);
143 template<incompatible_with<std::shared_ptr<delegate_type>> Value, text_field_w
idget_attribute... Attributes>
147 Attributes&&...attributes)
noexcept requires requires
155 co_yield *_scroll_widget;
160 hi_assert_not_null(delegate);
161 hi_assert_not_null(_error_label_widget);
162 hi_assert_not_null(_scroll_widget);
164 if (_text_widget->focus()) {
166 _error_label = delegate->validate(*
this, *_text);
174 _scroll_constraints = _scroll_widget->update_constraints();
178 _scroll_constraints.margins.left() +
scroll_width + _scroll_constraints.margins.right(),
179 _scroll_constraints.margins.top() + _scroll_constraints.preferred.
height() + _scroll_constraints.margins.bottom()};
182 auto margins = theme().margin();
183 if (_error_label->empty()) {
185 _error_label_constraints = _error_label_widget->update_constraints();
189 _error_label_constraints = _error_label_widget->update_constraints();
190 inplace_max(size.width(), _error_label_constraints.preferred.
width());
191 size.height() += _error_label_constraints.margins.top() + _error_label_constraints.preferred.
height();
192 inplace_max(margins.left(), _error_label_constraints.margins.left());
193 inplace_max(margins.right(), _error_label_constraints.margins.right());
194 inplace_max(margins.bottom(), _error_label_constraints.margins.bottom());
198 auto const resolved_alignment = resolve_mirror(*
alignment, os_settings::left_to_right());
200 return {size, size, size, resolved_alignment, margins};
202 void set_layout(widget_layout
const&
context)
noexcept override
207 _scroll_constraints.margins.top() + _scroll_constraints.preferred.
height() +
208 _scroll_constraints.margins.bottom()};
211 _scroll_shape = box_shape{_scroll_constraints,
scroll_rectangle, theme().baseline_adjustment()};
215 aarectangle{0, 0,
context.rectangle().width(), _error_label_constraints.preferred.
height()};
216 _error_label_shape = box_shape{_error_label_constraints,
error_label_rectangle, theme().baseline_adjustment()};
221 _error_label_widget->set_layout(
context.transform(_error_label_shape));
223 _scroll_widget->set_layout(
context.transform(_scroll_shape));
225 void draw(draw_context
const&
context)
noexcept override
231 _error_label_widget->draw(
context);
234 bool handle_event(gui_event
const&
event)
noexcept override
236 switch (
event.type()) {
237 case gui_event_type::gui_cancel:
244 case gui_event_type::gui_activate:
256 hitbox hitbox_test(point2 position)
const noexcept override
260 r = _scroll_widget->hitbox_test_from_parent(position, r);
261 r = _error_label_widget->hitbox_test_from_parent(position, r);
267 [[
nodiscard]]
bool accepts_keyboard_focus(keyboard_focus_group group)
const noexcept override
270 return _scroll_widget->accepts_keyboard_focus(group);
278 if (
not _error_label->empty()) {
279 return theme().text_style(semantic_text_style::error)->color;
280 }
else if (_text_widget->focus()) {
281 return theme().color(semantic_color::accent);
282 }
else if (phase() == widget_phase::hover) {
283 return theme().color(semantic_color::border, _layout.layer + 1);
285 return theme().color(semantic_color::border, _layout.layer);
289 return theme().color(semantic_color::border, _layout.layer - 1);
297 box_constraints _scroll_constraints;
298 box_shape _scroll_shape;
302 text_widget *_text_widget =
nullptr;
306 observer<gstring> _text;
310 observer<label> _error_label;
312 box_constraints _error_label_constraints;
313 box_shape _error_label_shape;
315 callback<
void()> _delegate_cbt;
316 callback<
void(
bool)> _continues_cbt;
317 callback<
void(semantic_text_style)> _text_style_cbt;
318 callback<
void(gstring)> _text_cbt;
319 callback<
void(label)> _error_label_cbt;
323 template<text_field_widget_attribute
First, text_field_widget_attribute...
Rest>
331 hi_static_no_default();
334 set_attributes(std::forward<Rest>(
rest)...);
337 void revert(
bool force)
noexcept
339 hi_assert_not_null(delegate);
340 _text = delegate->text(*
this);
341 _error_label = label{};
343 void commit(
bool force)
noexcept
345 hi_axiom(loop::main().on_thread());
346 hi_assert_not_null(delegate);
351 if (delegate->validate(*
this, text).empty()) {
353 delegate->set_text(*
this, text);
357 _text = delegate->text(*
this);
358 _error_label = label{};
361 void draw_background_box(draw_context
const&
context)
const noexcept
363 auto const outline = _scroll_shape.rectangle;
369 context.draw_line(
layout(), translate3{0.0f, 0.5f, 0.1f} * line, theme().border_width(), focus_color());