67 constexpr static auto prefix = Name /
"text-field";
78 observer<alignment>
alignment = alignment::middle_flush();
83 delegate->deinit(*
this);
90 _delegate_cbt = this->delegate->subscribe([&] {
91 ++global_counter<
"text_field_widget:delegate:layout">;
94 this->delegate->init(*
this);
96 _scroll_widget = std::make_unique<scroll_widget<axis::none, prefix>>(
this);
97 _text_widget = &_scroll_widget->make_widget<text_widget<prefix>>(_text,
alignment);
101 _error_label_widget = std::make_unique<label_widget<prefix /
"error">>(
this, _error_label, alignment::top_left());
103 _continues_cbt =
continues.subscribe([&](
auto...) {
104 ++global_counter<
"text_field_widget:continues:constrain">;
107 _text_cbt = _text.subscribe([&](
auto...) {
108 ++global_counter<
"text_field_widget:text:constrain">;
111 _error_label_cbt = _error_label.subscribe([&](
auto const& new_value) {
112 ++global_counter<
"text_field_widget:error_label:constrain">;
120 text_field_widget_attribute
auto&&...attributes) noexcept :
142 [[nodiscard]] generator<widget const&> children(
bool include_invisible)
const noexcept override
144 co_yield *_scroll_widget;
147 [[nodiscard]] box_constraints update_constraints() noexcept
override
153 if (*_text_widget->focus) {
156 if (
auto error_label = delegate->validate(*
this, *_text)) {
157 _error_label = *error_label;
161 _error_label = label{};
169 _scroll_constraints = _scroll_widget->update_constraints();
171 hilet scroll_width = 100;
173 _scroll_constraints.margins.left() + scroll_width + _scroll_constraints.margins.right(),
174 _scroll_constraints.margins.top() + _scroll_constraints.preferred.
height() + _scroll_constraints.margins.bottom()};
176 auto size = box_size;
177 auto margins = theme<prefix>.margin(
this);
178 if (_error_label->empty()) {
180 _error_label_constraints = _error_label_widget->update_constraints();
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());
193 hilet resolved_alignment = resolve_mirror(*
alignment, os_settings::left_to_right());
195 return {size, size, size, resolved_alignment, margins};
198 void set_layout(widget_layout
const& context)
noexcept override
203 _scroll_constraints.margins.top() + _scroll_constraints.preferred.
height() +
204 _scroll_constraints.margins.bottom()};
206 hilet scroll_rectangle = aarectanglei{point2i{0, context.height() - scroll_size.height()}, scroll_size};
207 _scroll_shape = box_shape{_scroll_constraints, scroll_rectangle, theme<prefix>.cap_height(
this)};
210 hilet error_label_rectangle =
211 aarectanglei{0, 0, context.rectangle().width(), _error_label_constraints.preferred.
height()};
212 _error_label_shape = box_shape{_error_label_constraints, error_label_rectangle, theme<prefix>.cap_height(
this)};
217 _error_label_widget->set_layout(context.transform(_error_label_shape));
219 _scroll_widget->set_layout(context.transform(_scroll_shape));
222 void draw(widget_draw_context
const& context)
noexcept override
225 draw_background_box(context);
227 _scroll_widget->draw(context);
228 _error_label_widget->draw(context);
232 bool handle_event(gui_event
const& event)
noexcept override
234 switch (event.type()) {
235 case gui_event_type::gui_cancel:
242 case gui_event_type::gui_activate:
255 hitbox hitbox_test(point2i position)
const noexcept override
259 r = _scroll_widget->hitbox_test_from_parent(position, r);
260 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);
277 notifier<>::callback_token _delegate_cbt;
282 box_constraints _scroll_constraints;
283 box_shape _scroll_shape;
287 text_widget<prefix> *_text_widget =
nullptr;
291 observer<hi::text> _text;
295 observer<label> _error_label;
297 box_constraints _error_label_constraints;
298 box_shape _error_label_shape;
300 typename decltype(
continues)::callback_token _continues_cbt;
301 typename decltype(_text)::callback_token _text_cbt;
302 typename decltype(_error_label)::callback_token _error_label_cbt;
304 void set_attributes() noexcept {}
305 void set_attributes(text_field_widget_attribute
auto&& first, text_field_widget_attribute
auto&&...rest)
noexcept
307 if constexpr (forward_of<
decltype(first), observer<hi::alignment>>) {
316 void revert(
bool force)
noexcept
319 _text = delegate->text(*
this);
320 _error_label = label{};
323 void commit(
bool force)
noexcept
329 if (not delegate->validate(*
this, *_text)) {
331 delegate->set_text(*
this, *_text);
335 _text = delegate->text(*
this);
336 _error_label = label{};
340 void draw_background_box(widget_draw_context
const& context)
const noexcept
342 hilet outline = narrow_cast<aarectangle>(_scroll_shape.rectangle);
344 context.draw_box(layout, outline, theme<prefix>.background_color(
this), theme<prefix>.corner_radius(
this));
347 hilet line = line_segment(get<0>(outline), get<1>(outline));
349 hilet outline_color = _error_label->empty() ? theme<prefix>.border_color(
this) : color::red();
350 context.draw_line(layout, translate3{0.0f, 0.5f, 0.1f} * line, theme<prefix>.border_width(
this), outline_color);