82 virtual ~text_field_widget()
84 hi_assert_not_null(delegate);
85 delegate->deinit(*
this);
89 super(), delegate(
std::move(delegate)), _text()
91 hi_assert_not_null(this->delegate);
92 _delegate_cbt = this->delegate->subscribe([&] {
93 ++global_counter<
"text_field_widget:delegate:layout">;
96 this->delegate->init(*
this);
98 _scroll_widget = std::make_unique<scroll_widget<axis::none>>();
99 _scroll_widget->set_parent(
this);
101 _text_widget = &_scroll_widget->emplace<text_widget>(_text,
alignment);
104 _error_label_widget = std::make_unique<label_widget>(_error_label, alignment::top_left());
105 _error_label_widget->set_parent(
this);
108 ++global_counter<
"text_field_widget:continues:constrain">;
111 _text_cbt = _text.subscribe([&](
auto...) {
112 ++global_counter<
"text_field_widget:text:constrain">;
115 _error_label_cbt = _error_label.subscribe([&](
auto const& new_value) {
116 ++global_counter<
"text_field_widget:error_label:constrain">;
121 template<text_field_widget_attribute... Attributes>
123 std::shared_ptr<delegate_type> delegate,
124 Attributes&&...attributes) noexcept :
136 template<incompatible_with<std::shared_ptr<delegate_type>> Value, text_field_w
idget_attribute... Attributes>
139 Attributes&&...attributes)
noexcept requires requires
145 [[nodiscard]] generator<widget_intf&> children(
bool include_invisible)
noexcept override
147 if (_scroll_widget) {
148 co_yield *_scroll_widget;
150 if (_error_label_widget) {
151 co_yield *_error_label_widget;
157 hi_assert_not_null(delegate);
158 hi_assert_not_null(_error_label_widget);
159 hi_assert_not_null(_scroll_widget);
161 if (_text_widget->focus()) {
163 _error_label = delegate->validate(*
this, *_text);
171 _scroll_constraints = _scroll_widget->update_constraints();
173 auto const scroll_width = 100;
174 auto const box_size = extent2{
175 _scroll_constraints.margins.left() + scroll_width + _scroll_constraints.margins.right(),
176 _scroll_constraints.margins.top() + _scroll_constraints.preferred.height() + _scroll_constraints.margins.bottom()};
178 auto size = box_size;
179 auto margins = theme().margin();
180 if (_error_label->empty()) {
182 _error_label_constraints = _error_label_widget->update_constraints();
186 _error_label_constraints = _error_label_widget->update_constraints();
187 inplace_max(size.width(), _error_label_constraints.preferred.width());
188 size.height() += _error_label_constraints.margins.top() + _error_label_constraints.preferred.height();
189 inplace_max(margins.left(), _error_label_constraints.margins.left());
190 inplace_max(margins.right(), _error_label_constraints.margins.right());
191 inplace_max(margins.bottom(), _error_label_constraints.margins.bottom());
195 auto const resolved_alignment = resolve_mirror(*
alignment, os_settings::left_to_right());
197 return {size, size, size, resolved_alignment, margins};
199 void set_layout(widget_layout
const& context)
noexcept override
202 auto const scroll_size = extent2{
204 _scroll_constraints.margins.top() + _scroll_constraints.preferred.height() +
205 _scroll_constraints.margins.bottom()};
207 auto const scroll_rectangle = aarectangle{point2{0, context.height() - scroll_size.height()}, scroll_size};
208 _scroll_shape = box_shape{_scroll_constraints, scroll_rectangle, theme().baseline_adjustment()};
211 auto const error_label_rectangle =
212 aarectangle{0, 0, context.rectangle().width(), _error_label_constraints.preferred.height()};
213 _error_label_shape = box_shape{_error_label_constraints, error_label_rectangle, theme().baseline_adjustment()};
218 _error_label_widget->set_layout(context.transform(_error_label_shape));
220 _scroll_widget->set_layout(context.transform(_scroll_shape));
222 void draw(draw_context
const& context)
noexcept override
225 draw_background_box(context);
227 _scroll_widget->draw(context);
228 _error_label_widget->draw(context);
231 bool handle_event(gui_event
const& event)
noexcept override
233 switch (event.type()) {
234 case gui_event_type::gui_cancel:
241 case gui_event_type::gui_activate:
253 hitbox hitbox_test(point2 position)
const noexcept override
257 r = _scroll_widget->hitbox_test_from_parent(position, r);
258 r = _error_label_widget->hitbox_test_from_parent(position, r);
264 [[nodiscard]]
bool accepts_keyboard_focus(keyboard_focus_group group)
const noexcept override
267 return _scroll_widget->accepts_keyboard_focus(group);
272 [[nodiscard]]
color focus_color() const noexcept
override
275 if (not _error_label->empty()) {
276 auto const error_style = theme().text_style_set()[{phrasing::error}];
277 return error_style.color();
278 }
else if (_text_widget->focus()) {
279 return theme().accent_color();
280 }
else if (phase() == widget_phase::hover) {
281 return theme().border_color(_layout.layer + 1);
283 return theme().border_color(_layout.layer);
287 return theme().border_color(_layout.layer - 1);
294 std::unique_ptr<scroll_widget<axis::none>> _scroll_widget;
295 box_constraints _scroll_constraints;
296 box_shape _scroll_shape;
300 text_widget *_text_widget =
nullptr;
304 observer<gstring> _text;
308 observer<label> _error_label;
309 std::unique_ptr<label_widget> _error_label_widget;
310 box_constraints _error_label_constraints;
311 box_shape _error_label_shape;
313 callback<void()> _delegate_cbt;
314 callback<void(
bool)> _continues_cbt;
315 callback<void(gstring)> _text_cbt;
316 callback<void(label)> _error_label_cbt;
318 void set_attributes() noexcept {}
320 template<text_field_widget_attribute First, text_field_widget_attribute... Rest>
321 void set_attributes(First&& first, Rest&&...rest)
noexcept
323 if constexpr (forward_of<First, observer<hi::alignment>>) {
326 hi_static_no_default();
332 void revert(
bool force)
noexcept
334 hi_assert_not_null(delegate);
335 _text = delegate->text(*
this);
336 _error_label = label{};
338 void commit(
bool force)
noexcept
340 hi_axiom(loop::main().on_thread());
341 hi_assert_not_null(delegate);
346 if (delegate->validate(*
this, text).empty()) {
348 delegate->set_text(*
this, text);
352 _text = delegate->text(*
this);
353 _error_label = label{};
356 void draw_background_box(draw_context
const& context)
const noexcept
358 auto const outline = _scroll_shape.rectangle;
360 auto const corner_radii = hi::corner_radii(0.0f, 0.0f, theme().rounding_radius<float>(), theme().rounding_radius<float>());
361 context.draw_box(
layout(), outline, background_color(), corner_radii);
363 auto const line = line_segment(get<0>(outline), get<1>(outline));
364 context.draw_line(
layout(), translate3{0.0f, 0.5f, 0.1f} * line, theme().border_width(), focus_color());