72 set_attributes(std::forward<Attributes>(attributes)...);
87 hi_static_no_default();
90 set_attributes(std::forward<Rest>(
rest)...);
94 attributes_type attributes;
98 template<
typename...
Args>
107 delegate->deinit(*
this);
118 _current_label_widget = std::make_unique<label_widget>(
this, this->attributes.alignment,
this->attributes.text_style);
120 _off_label_widget = std::make_unique<label_widget>(
this, this->attributes.off_label,
this->attributes.alignment, semantic_text_style::placeholder);
122 _overlay_widget = std::make_unique<overlay_widget>(
this);
127 _off_label_cbt = this->attributes.off_label.subscribe([&](
auto...) {
128 ++global_counter<
"selection_widget:off_label:constrain">;
132 _delegate_options_cbt = this->delegate->subscribe_on_options([&] {
134 }, callback_flags::main);
135 _delegate_options_cbt();
137 _delegate_value_cbt = this->delegate->subscribe_on_value([&] {
139 }, callback_flags::main);
140 _delegate_value_cbt();
142 this->delegate->init(*
this);
165 Attributes&&...attributes)
noexcept requires requires
167 make_default_delegate(std::forward<Value>(value), std::forward<OptionList>(
option_list));
172 attributes_type{std::forward<Attributes>(attributes)...},
173 make_default_delegate(std::forward<Value>(value), std::forward<OptionList>(
option_list)))
180 co_yield *_overlay_widget;
181 co_yield *_current_label_widget;
182 co_yield *_off_label_widget;
187 hi_assert_not_null(_off_label_widget);
188 hi_assert_not_null(_current_label_widget);
189 hi_assert_not_null(_overlay_widget);
192 _off_label_constraints = _off_label_widget->update_constraints();
193 _current_label_constraints = _current_label_widget->update_constraints();
194 _overlay_constraints = _overlay_widget->update_constraints();
196 auto const extra_size = extent2{theme().size() + theme().margin<
float>() * 2.0f, theme().margin<
float>() * 2.0f};
201 _scroll_widget->
minimum->height() = theme().size();
204 r.preferred.width() =
std::max(r.preferred.width(), _overlay_constraints.preferred.
width() +
extra_size.width());
206 r.margins = theme().margin();
207 r.alignment = resolve(*attributes.alignment, os_settings::left_to_right());
208 hi_axiom(r.holds_invariant());
212 void set_layout(widget_layout
const&
context)
noexcept override
215 if (os_settings::left_to_right()) {
216 _left_box_rectangle = aarectangle{0.0f, 0.0f, theme().size(),
context.height()};
220 _left_box_rectangle.right() + theme().margin<
float>(),
222 context.width() - _left_box_rectangle.width() - theme().margin<
float>() * 2.0f,
224 _off_label_shape = box_shape{_off_label_constraints,
option_rectangle, theme().baseline_adjustment()};
225 _current_label_shape = box_shape{_off_label_constraints,
option_rectangle, theme().baseline_adjustment()};
228 _left_box_rectangle = aarectangle{
context.width() - theme().size(), 0.0f, theme().size(),
context.height()};
232 theme().margin<
float>(),
234 context.width() - _left_box_rectangle.width() - theme().margin<
float>() * 2.0f,
236 _off_label_shape = box_shape{_off_label_constraints,
option_rectangle, theme().baseline_adjustment()};
237 _current_label_shape = box_shape{_off_label_constraints,
option_rectangle, theme().baseline_adjustment()};
240 _chevrons_glyph =
find_glyph(elusive_icon::ChevronUp);
241 auto const chevrons_glyph_bbox = _chevrons_glyph.get_metrics().bounding_rectangle * theme().icon_size();
250 context.width() - theme().size(), _overlay_constraints.minimum.
width(), _overlay_constraints.maximum.
width());
256 _overlay_shape = box_shape{_overlay_constraints,
overlay_rectangle, theme().baseline_adjustment()};
259 _off_label_widget->set_layout(
context.transform(_off_label_shape));
260 _current_label_widget->set_layout(
context.transform(_current_label_shape));
263 void draw(draw_context
const&
context)
noexcept override
265 animate_overlay(
context.display_time_point);
273 _off_label_widget->draw(
context);
274 _current_label_widget->draw(
context);
278 _overlay_widget->draw(
context);
282 bool handle_event(gui_event
const&
event)
noexcept override
284 switch (
event.type()) {
285 case gui_event_type::mouse_up:
287 return handle_event(gui_event_type::gui_activate);
291 case gui_event_type::gui_activate_next:
294 case gui_event_type::gui_activate:
300 ++global_counter<
"selection_widget:gui_activate:relayout">;
304 case gui_event_type::gui_cancel:
314 [[
nodiscard]] hitbox hitbox_test(point2 position)
const noexcept override
316 hi_axiom(loop::main().on_thread());
319 auto r = _overlay_widget->hitbox_test_from_parent(position);
321 if (
layout().contains(position)) {
322 r =
std::max(r, hitbox{
id, _layout.elevation,
not delegate->empty(*
this) ? hitbox_type::button : hitbox_type::_default});
331 [[
nodiscard]]
bool accepts_keyboard_focus(keyboard_focus_group group)
const noexcept override
333 hi_axiom(loop::main().on_thread());
339 hi_axiom(loop::main().on_thread());
342 return theme().color(semantic_color::accent);
344 return super::focus_color();
350 enum class overlay_state_type {
358 overlay_state_type _overlay_state = overlay_state_type::closed;
359 utc_nanoseconds _overlay_close_start = {};
361 bool _notification_from_delegate =
true;
364 box_constraints _current_label_constraints;
365 box_shape _current_label_shape;
368 box_constraints _off_label_constraints;
369 box_shape _off_label_shape;
371 aarectangle _left_box_rectangle;
373 font_book::font_glyph_type _chevrons_glyph;
374 aarectangle _chevrons_rectangle;
377 box_constraints _overlay_constraints;
378 box_shape _overlay_shape;
381 grid_widget *_grid_widget =
nullptr;
383 callback<
void()> _delegate_options_cbt;
384 callback<
void()> _delegate_value_cbt;
385 callback<
void(label)> _off_label_cbt;
389 return _overlay_state == overlay_state_type::closed;
394 hi_axiom(loop::main().on_thread());
396 if (
auto focus_id = delegate->keyboard_focus_id(*
this)) {
397 _overlay_state = overlay_state_type::open;
406 hi_axiom(loop::main().on_thread());
408 if (_overlay_state == overlay_state_type::open) {
409 _overlay_state = overlay_state_type::closing;
410 _overlay_close_start = std::chrono::utc_clock::now();
417 if (_overlay_state != overlay_state_type::closed) {
418 _overlay_state = overlay_state_type::closed;
424 void animate_overlay(utc_nanoseconds display_time_point)
noexcept
426 hi_axiom(loop::main().on_thread());
428 switch (_overlay_state) {
429 case overlay_state_type::open:
431 case overlay_state_type::closing:
432 if (display_time_point >= _overlay_close_start + _overlay_close_delay) {
433 force_close_overlay();
438 case overlay_state_type::closed:
447 _grid_widget->clear();
448 for (
auto i = 0
_uz; i != delegate->size(*
this); ++i) {
449 _grid_widget->push_bottom(delegate->make_option_widget(*
this, *_grid_widget, i));
452 ++global_counter<
"selection_widget:update_options:constrain">;
458 if (
auto selected_label = delegate->selected_label(*
this)) {
460 _current_label_widget->label = *selected_label;
471 void draw_outline(draw_context
const&
context)
noexcept
478 theme().border_width(),
480 theme().rounding_radius());
483 void draw_left_box(draw_context
const&
context)
noexcept
485 auto const corner_radii = os_settings::left_to_right() ?
487 hi::corner_radii(0.0f, theme().rounding_radius<
float>(), 0.0f, theme().rounding_radius<
float>());
488 context.draw_box(
layout(), translate_z(0.1f) * _left_box_rectangle, focus_color(), corner_radii);
491 void draw_chevrons(draw_context
const&
context)
noexcept
493 context.draw_glyph(
layout(), translate_z(0.2f) * _chevrons_rectangle, _chevrons_glyph, label_color());