53 struct attributes_type {
64 attributes_type(attributes_type
const&)
noexcept =
default;
65 attributes_type(attributes_type&&) noexcept = default;
66 attributes_type& operator=(attributes_type const&) noexcept = default;
67 attributes_type& operator=(attributes_type&&) noexcept = default;
70 explicit attributes_type(Attributes&&...attributes) noexcept
75 void set_attributes() noexcept {}
78 void set_attributes(First&& first, Rest&&...rest)
noexcept
82 }
else if constexpr (forward_of<First, observer<hi::alignment>>) {
84 }
else if constexpr (forward_of<First, observer<hi::semantic_text_style>>) {
87 hi_static_no_default();
96 not_null<std::shared_ptr<delegate_type>> delegate;
98 template<
typename... Args>
99 [[nodiscard]]
static not_null<std::shared_ptr<delegate_type>> make_default_delegate(Args &&...args)
100 requires requires { make_shared_ctad_not_null<default_selection_delegate>(
std::forward<Args>(args)...); }
102 return make_shared_ctad_not_null<default_selection_delegate>(
std::forward<Args>(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);
125 _grid_widget = &_scroll_widget->emplace<
grid_widget>();
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);
164 OptionList&& option_list,
165 Attributes&&...attributes)
noexcept requires requires
178 [[nodiscard]] generator<widget_intf&> children(
bool include_invisible)
noexcept override
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};
198 auto r =
max(_off_label_constraints + extra_size, _current_label_constraints + extra_size);
201 _scroll_widget->minimum->height() = theme().size();
203 r.minimum.width() =
std::max(r.minimum.width(), _overlay_constraints.minimum.width() + extra_size.width());
204 r.preferred.width() =
std::max(r.preferred.width(), _overlay_constraints.preferred.width() + extra_size.width());
205 r.maximum.width() =
std::max(r.maximum.width(), _overlay_constraints.maximum.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()};
219 auto const option_rectangle = aarectangle{
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()};
231 auto const option_rectangle = aarectangle{
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();
242 _chevrons_rectangle =
align(_left_box_rectangle, chevrons_glyph_bbox, alignment::middle_center());
249 auto const overlay_width = std::clamp(
250 context.width() - theme().size(), _overlay_constraints.minimum.width(), _overlay_constraints.maximum.width());
251 auto const overlay_height = _overlay_constraints.preferred.height();
252 auto const overlay_x = os_settings::left_to_right() ? theme().size() : context.width() - theme().size() - overlay_width;
253 auto const overlay_y = (context.height() - overlay_height) / 2;
254 auto const overlay_rectangle_request = aarectangle{overlay_x, overlay_y, overlay_width, overlay_height};
255 auto const overlay_rectangle = make_overlay_rectangle(overlay_rectangle_request);
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);
268 if (overlaps(context,
layout())) {
269 draw_outline(context);
270 draw_left_box(context);
271 draw_chevrons(context);
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());
334 return mode() >=
widget_mode::partial and to_bool(group & hi::keyboard_focus_group::normal) and not delegate->empty(*
this);
337 [[nodiscard]] color focus_color() const noexcept
override
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 {
356 constexpr static std::chrono::nanoseconds _overlay_close_delay = std::chrono::milliseconds(200);
358 overlay_state_type _overlay_state = overlay_state_type::closed;
359 utc_nanoseconds _overlay_close_start = {};
361 bool _notification_from_delegate =
true;
363 std::unique_ptr<label_widget> _current_label_widget;
364 box_constraints _current_label_constraints;
365 box_shape _current_label_shape;
367 std::unique_ptr<label_widget> _off_label_widget;
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;
376 std::unique_ptr<overlay_widget> _overlay_widget;
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;
387 [[nodiscard]]
bool overlay_closed() const noexcept
389 return _overlay_state == overlay_state_type::closed;
392 void open_overlay() noexcept
394 hi_axiom(loop::main().on_thread());
396 if (
auto focus_id = delegate->keyboard_focus_id(*
this)) {
397 _overlay_state = overlay_state_type::open;
399 process_event(gui_event::window_set_keyboard_target(*focus_id, keyboard_focus_group::menu));
404 void close_overlay() noexcept
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();
415 void force_close_overlay() noexcept
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:
445 void update_options() noexcept
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">;
456 void update_value() noexcept
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() ?
486 hi::corner_radii(theme().rounding_radius<float>(), 0.0f, theme().rounding_radius<float>(), 0.0f) :
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());