74 observer<alignment>
alignment = hi::alignment::middle_flush();
78 observer<semantic_text_style>
text_style = semantic_text_style::label;
82 hi_assert_not_null(delegate);
83 delegate->deinit(*
this);
94 hi_assert_not_null(this->delegate);
98 _off_label_widget = std::make_unique<label_widget>(
this,
off_label,
alignment, semantic_text_style::placeholder);
100 _overlay_widget = std::make_unique<overlay_widget>(
this);
103 _column_widget = &_scroll_widget->make_widget<
column_widget>();
105 _off_label_cbt = this->off_label.subscribe([&](
auto...) {
106 ++global_counter<
"selection_widget:off_label:constrain">;
110 _delegate_cbt = this->delegate->subscribe([&] {
111 _notification_from_delegate =
true;
112 ++global_counter<
"selection_widget:delegate:constrain">;
116 this->delegate->init(*
this);
135 set_attributes(hi_forward(first_attribute), hi_forward(attributes)...);
157 OptionList&& option_list,
158 Attributes&&...attributes)
noexcept requires requires
165 hi_forward(attributes)...)
184 different_from<std::shared_ptr<delegate_type>> Value,
185 forward_of<observer<std::vector<std::pair<observer_decay_t<Value>, label>>>> OptionList,
186 forward_of<observer<observer_decay_t<Value>>> OffValue,
187 selection_widget_attribute... Attributes>
191 OptionList&& option_list,
192 OffValue&& off_value,
193 Attributes&&...attributes)
noexcept requires requires
200 hi_forward(attributes)...)
205 [[nodiscard]] generator<widget_intf&> children(
bool include_invisible)
noexcept override
207 co_yield *_overlay_widget;
208 co_yield *_current_label_widget;
209 co_yield *_off_label_widget;
214 hi_assert_not_null(_off_label_widget);
215 hi_assert_not_null(_current_label_widget);
216 hi_assert_not_null(_overlay_widget);
218 if (_notification_from_delegate.
exchange(
false)) {
219 repopulate_options();
223 _off_label_constraints = _off_label_widget->update_constraints();
224 _current_label_constraints = _current_label_widget->update_constraints();
225 _overlay_constraints = _overlay_widget->update_constraints();
227 hilet extra_size = extent2{theme().size() + theme().margin<
float>() * 2.0f, theme().margin<
float>() * 2.0f};
229 auto r =
max(_off_label_constraints + extra_size, _current_label_constraints + extra_size);
232 _scroll_widget->minimum.copy()->height() = theme().size();
234 r.minimum.width() =
std::max(r.minimum.width(), _overlay_constraints.minimum.width() + extra_size.width());
235 r.preferred.width() =
std::max(r.preferred.width(), _overlay_constraints.preferred.width() + extra_size.width());
236 r.maximum.width() =
std::max(r.maximum.width(), _overlay_constraints.maximum.width() + extra_size.width());
237 r.margins = theme().margin();
238 r.padding = theme().margin();
239 r.alignment = resolve(*
alignment, os_settings::left_to_right());
240 hi_axiom(r.holds_invariant());
244 void set_layout(widget_layout
const& context)
noexcept override
247 if (os_settings::left_to_right()) {
248 _left_box_rectangle = aarectangle{0.0f, 0.0f, theme().size(), context.height()};
251 hilet option_rectangle = aarectangle{
252 _left_box_rectangle.right() + theme().margin<
float>(),
254 context.width() - _left_box_rectangle.width() - theme().margin<
float>() * 2.0f,
256 _off_label_shape = box_shape{_off_label_constraints, option_rectangle, theme().baseline_adjustment()};
257 _current_label_shape = box_shape{_off_label_constraints, option_rectangle, theme().baseline_adjustment()};
260 _left_box_rectangle = aarectangle{context.width() - theme().size(), 0.0f, theme().size(), context.height()};
263 hilet option_rectangle = aarectangle{
264 theme().margin<
float>(),
266 context.width() - _left_box_rectangle.width() - theme().margin<
float>() * 2.0f,
268 _off_label_shape = box_shape{_off_label_constraints, option_rectangle, theme().baseline_adjustment()};
269 _current_label_shape = box_shape{_off_label_constraints, option_rectangle, theme().baseline_adjustment()};
272 _chevrons_glyph =
find_glyph(elusive_icon::ChevronUp);
273 hilet chevrons_glyph_bbox = _chevrons_glyph.get_metrics().bounding_rectangle * theme().icon_size();
274 _chevrons_rectangle =
align(_left_box_rectangle, chevrons_glyph_bbox, alignment::middle_center());
281 hilet overlay_width = std::clamp(
282 context.width() - theme().size(), _overlay_constraints.minimum.width(), _overlay_constraints.maximum.width());
283 hilet overlay_height = _overlay_constraints.preferred.height();
284 hilet overlay_x = os_settings::left_to_right() ? theme().size() : context.width() - theme().size() - overlay_width;
285 hilet overlay_y = (context.height() - overlay_height) / 2;
286 hilet overlay_rectangle_request = aarectangle{overlay_x, overlay_y, overlay_width, overlay_height};
287 hilet overlay_rectangle = make_overlay_rectangle(overlay_rectangle_request);
288 _overlay_shape = box_shape{_overlay_constraints, overlay_rectangle, theme().baseline_adjustment()};
289 _overlay_widget->set_layout(context.transform(_overlay_shape, 20.0f));
291 _off_label_widget->set_layout(context.transform(_off_label_shape));
292 _current_label_widget->set_layout(context.transform(_current_label_shape));
295 void draw(draw_context
const& context)
noexcept override
298 if (overlaps(context,
layout())) {
299 draw_outline(context);
300 draw_left_box(context);
301 draw_chevrons(context);
303 _off_label_widget->draw(context);
304 _current_label_widget->draw(context);
308 _overlay_widget->draw(context);
312 bool handle_event(gui_event
const& event)
noexcept override
314 switch (event.type()) {
315 case gui_event_type::mouse_up:
317 return handle_event(gui_event_type::gui_activate);
321 case gui_event_type::gui_activate_next:
324 case gui_event_type::gui_activate:
330 ++global_counter<
"selection_widget:gui_activate:relayout">;
334 case gui_event_type::gui_cancel:
338 ++global_counter<
"selection_widget:gui_cancel:relayout">;
348 [[nodiscard]] hitbox hitbox_test(point2 position)
const noexcept override
350 hi_axiom(loop::main().on_thread());
353 auto r = _overlay_widget->hitbox_test_from_parent(position);
355 if (
layout().contains(position)) {
356 r =
std::max(r, hitbox{
id, _layout.elevation, _has_options ? hitbox_type::button : hitbox_type::_default});
365 [[nodiscard]]
bool accepts_keyboard_focus(keyboard_focus_group group)
const noexcept override
367 hi_axiom(loop::main().on_thread());
371 [[nodiscard]] color focus_color() const noexcept
override
373 hi_axiom(loop::main().on_thread());
376 return theme().color(semantic_color::accent);
378 return super::focus_color();
384 notifier<>::callback_token _delegate_cbt;
385 std::atomic<bool> _notification_from_delegate =
true;
387 std::unique_ptr<label_widget> _current_label_widget;
388 box_constraints _current_label_constraints;
389 box_shape _current_label_shape;
391 std::unique_ptr<label_widget> _off_label_widget;
392 box_constraints _off_label_constraints;
393 box_shape _off_label_shape;
395 aarectangle _left_box_rectangle;
397 font_book::font_glyph_type _chevrons_glyph;
398 aarectangle _chevrons_rectangle;
400 bool _selecting =
false;
401 bool _has_options =
false;
403 std::unique_ptr<overlay_widget> _overlay_widget;
404 box_constraints _overlay_constraints;
405 box_shape _overlay_shape;
410 decltype(
off_label)::callback_token _off_label_cbt;
411 std::vector<menu_button_widget *> _menu_button_widgets;
412 std::vector<notifier<>::callback_token> _menu_button_tokens;
414 void set_attributes() noexcept {}
415 void set_attributes(label_widget_attribute
auto&& first, label_widget_attribute
auto&&...rest)
noexcept
417 if constexpr (forward_of<
decltype(first), observer<hi::label>>) {
419 }
else if constexpr (forward_of<
decltype(first), observer<hi::alignment>>) {
421 }
else if constexpr (forward_of<
decltype(first), observer<hi::semantic_text_style>>) {
424 hi_static_no_default();
427 set_attributes(hi_forward(rest)...);
430 [[nodiscard]] menu_button_widget
const *get_first_menu_button() const noexcept
432 hi_axiom(loop::main().on_thread());
434 if (ssize(_menu_button_widgets) != 0) {
435 return _menu_button_widgets.front();
441 [[nodiscard]] menu_button_widget
const *get_selected_menu_button() const noexcept
443 hi_axiom(loop::main().on_thread());
445 for (hilet& button : _menu_button_widgets) {
453 void start_selecting() noexcept
455 hi_axiom(loop::main().on_thread());
459 if (
auto selected_menu_button = get_selected_menu_button()) {
460 process_event(gui_event::window_set_keyboard_target(selected_menu_button->id, keyboard_focus_group::menu));
462 }
else if (
auto first_menu_button = get_first_menu_button()) {
463 process_event(gui_event::window_set_keyboard_target(first_menu_button->id, keyboard_focus_group::menu));
469 void stop_selecting() noexcept
471 hi_axiom(loop::main().on_thread());
477 void repopulate_options() noexcept
479 hi_axiom(loop::main().on_thread());
480 hi_assert_not_null(delegate);
482 _column_widget->clear();
483 _menu_button_widgets.clear();
484 _menu_button_tokens.clear();
486 auto [options, selected] = delegate->options_and_selected(*
this);
488 _has_options = size(options) > 0;
491 auto show_icon =
false;
492 for (hilet& label : options) {
493 show_icon |= to_bool(label.icon);
496 decltype(selected) index = 0;
497 for (hilet& label : options) {
498 auto menu_button = &_column_widget->make_widget<menu_button_widget>(selected, index, label,
alignment,
text_style);
500 _menu_button_tokens.push_back(menu_button->pressed.subscribe(
502 hi_assert_not_null(delegate);
503 delegate->set_selected(*this, index);
506 callback_flags::main));
508 _menu_button_widgets.push_back(menu_button);
513 if (selected == -1) {
519 _current_label_widget->label = options[selected];
524 void draw_outline(draw_context
const& context)
noexcept
531 theme().border_width(),
533 theme().rounding_radius());
536 void draw_left_box(draw_context
const& context)
noexcept
538 hilet corner_radii = os_settings::left_to_right() ?
539 hi::corner_radii(theme().rounding_radius<float>(), 0.0f, theme().rounding_radius<float>(), 0.0f) :
540 hi::corner_radii(0.0f, theme().rounding_radius<float>(), 0.0f, theme().rounding_radius<float>());
541 context.draw_box(
layout(), translate_z(0.1f) * _left_box_rectangle, focus_color(), corner_radii);
544 void draw_chevrons(draw_context
const& context)
noexcept
546 context.draw_glyph(
layout(), translate_z(0.2f) * _chevrons_rectangle, _chevrons_glyph, label_color());