53 struct attributes_type {
60 attributes_type(attributes_type
const&)
noexcept =
default;
61 attributes_type(attributes_type&&) noexcept = default;
62 attributes_type& operator=(attributes_type const&) noexcept = default;
63 attributes_type& operator=(attributes_type&&) noexcept = default;
66 explicit attributes_type(Attributes&&...attributes) noexcept
71 void set_attributes() noexcept {}
74 void set_attributes(First&& first, Rest&&...rest)
noexcept
78 }
else if constexpr (forward_of<First, observer<hi::alignment>>) {
81 hi_static_no_default();
90 std::shared_ptr<delegate_type> delegate;
92 template<
typename... Args>
93 [[nodiscard]]
static std::shared_ptr<delegate_type> make_default_delegate(Args &&...args)
94 requires requires { make_shared_ctad<default_selection_delegate>(
std::forward<Args>(args)...); }
101 delegate->deinit(*
this);
112 _current_label_widget = std::make_unique<label_widget>(this->attributes.alignment);
113 _current_label_widget->set_parent(
this);
116 _off_label_widget = std::make_unique<label_widget>(this->attributes.off_label, this->attributes.alignment);
117 _off_label_widget->set_parent(
this);
119 _overlay_widget = std::make_unique<overlay_widget>();
120 _overlay_widget->set_parent(
this);
124 _grid_widget = &_scroll_widget->emplace<
grid_widget>();
126 _off_label_cbt = this->attributes.off_label.
subscribe([&](
auto...) {
127 ++global_counter<
"selection_widget:off_label:constrain">;
131 _delegate_options_cbt = this->delegate->subscribe_on_options([&] {
133 }, callback_flags::main);
134 _delegate_options_cbt();
136 _delegate_value_cbt = this->delegate->subscribe_on_value([&] {
138 }, callback_flags::main);
139 _delegate_value_cbt();
141 hi_axiom_not_null(this->delegate);
142 this->delegate->init(*
this);
163 OptionList&& option_list,
164 Attributes&&...attributes)
noexcept requires requires
176 [[nodiscard]] generator<widget_intf&> children(
bool include_invisible)
noexcept override
178 co_yield *_overlay_widget;
179 co_yield *_current_label_widget;
180 co_yield *_off_label_widget;
185 hi_assert_not_null(_off_label_widget);
186 hi_assert_not_null(_current_label_widget);
187 hi_assert_not_null(_overlay_widget);
190 _off_label_constraints = _off_label_widget->update_constraints();
191 _current_label_constraints = _current_label_widget->update_constraints();
192 _overlay_constraints = _overlay_widget->update_constraints();
194 auto const extra_size = extent2{theme().size() + theme().margin<
float>() * 2.0f, theme().margin<
float>() * 2.0f};
196 auto r =
max(_off_label_constraints + extra_size, _current_label_constraints + extra_size);
199 _scroll_widget->minimum->height() = theme().size();
201 r.minimum.width() =
std::max(r.minimum.width(), _overlay_constraints.minimum.width() + extra_size.width());
202 r.preferred.width() =
std::max(r.preferred.width(), _overlay_constraints.preferred.width() + extra_size.width());
203 r.maximum.width() =
std::max(r.maximum.width(), _overlay_constraints.maximum.width() + extra_size.width());
204 r.margins = theme().margin();
205 r.alignment = resolve(*attributes.alignment, os_settings::left_to_right());
206 hi_axiom(r.holds_invariant());
210 void set_layout(widget_layout
const& context)
noexcept override
213 if (os_settings::left_to_right()) {
214 _left_box_rectangle = aarectangle{0.0f, 0.0f, theme().size(), context.height()};
217 auto const option_rectangle = aarectangle{
218 _left_box_rectangle.right() + theme().margin<
float>(),
220 context.width() - _left_box_rectangle.width() - theme().margin<
float>() * 2.0f,
222 _off_label_shape = box_shape{_off_label_constraints, option_rectangle, theme().baseline_adjustment()};
223 _current_label_shape = box_shape{_off_label_constraints, option_rectangle, theme().baseline_adjustment()};
226 _left_box_rectangle = aarectangle{context.width() - theme().size(), 0.0f, theme().size(), context.height()};
229 auto const option_rectangle = aarectangle{
230 theme().margin<
float>(),
232 context.width() - _left_box_rectangle.width() - theme().margin<
float>() * 2.0f,
234 _off_label_shape = box_shape{_off_label_constraints, option_rectangle, theme().baseline_adjustment()};
235 _current_label_shape = box_shape{_off_label_constraints, option_rectangle, theme().baseline_adjustment()};
238 _chevrons_glyph =
find_glyph(elusive_icon::ChevronUp);
239 auto const chevrons_glyph_bbox = _chevrons_glyph.front_glyph_metrics().bounding_rectangle * theme().icon_size();
240 _chevrons_rectangle =
align(_left_box_rectangle, chevrons_glyph_bbox, alignment::middle_center());
247 auto const overlay_width = std::clamp(
248 context.width() - theme().size(), _overlay_constraints.minimum.width(), _overlay_constraints.maximum.width());
249 auto const overlay_height = _overlay_constraints.preferred.height();
250 auto const overlay_x = os_settings::left_to_right() ? theme().size() : context.width() - theme().size() - overlay_width;
251 auto const overlay_y = (context.height() - overlay_height) / 2;
252 auto const overlay_rectangle_request = aarectangle{overlay_x, overlay_y, overlay_width, overlay_height};
253 auto const overlay_rectangle = make_overlay_rectangle(overlay_rectangle_request);
254 _overlay_shape = box_shape{_overlay_constraints, overlay_rectangle, theme().baseline_adjustment()};
257 _off_label_widget->set_layout(context.transform(_off_label_shape));
258 _current_label_widget->set_layout(context.transform(_current_label_shape));
261 void draw(draw_context
const& context)
noexcept override
263 animate_overlay(context.display_time_point);
266 if (overlaps(context,
layout())) {
267 draw_outline(context);
268 draw_left_box(context);
269 draw_chevrons(context);
271 _off_label_widget->draw(context);
272 _current_label_widget->draw(context);
276 _overlay_widget->draw(context);
280 bool handle_event(gui_event
const& event)
noexcept override
282 switch (event.type()) {
283 case gui_event_type::mouse_up:
285 return handle_event(gui_event_type::gui_activate);
289 case gui_event_type::gui_activate_next:
292 case gui_event_type::gui_activate:
298 ++global_counter<
"selection_widget:gui_activate:relayout">;
302 case gui_event_type::gui_cancel:
312 [[nodiscard]] hitbox hitbox_test(point2 position)
const noexcept override
314 hi_axiom(loop::main().on_thread());
317 auto r = _overlay_widget->hitbox_test_from_parent(position);
319 if (
layout().contains(position)) {
320 r =
std::max(r, hitbox{
id, _layout.elevation, not delegate->empty(*
this) ? hitbox_type::button : hitbox_type::_default});
329 [[nodiscard]]
bool accepts_keyboard_focus(keyboard_focus_group group)
const noexcept override
331 hi_axiom(loop::main().on_thread());
332 return mode() >=
widget_mode::partial and to_bool(group & hi::keyboard_focus_group::normal) and not delegate->empty(*
this);
335 [[nodiscard]]
color focus_color() const noexcept
override
337 hi_axiom(loop::main().on_thread());
340 return theme().accent_color();
342 return super::focus_color();
348 enum class overlay_state_type {
354 constexpr static std::chrono::nanoseconds _overlay_close_delay = std::chrono::milliseconds(200);
356 overlay_state_type _overlay_state = overlay_state_type::closed;
357 utc_nanoseconds _overlay_close_start = {};
359 bool _notification_from_delegate =
true;
361 std::unique_ptr<label_widget> _current_label_widget;
362 box_constraints _current_label_constraints;
363 box_shape _current_label_shape;
365 std::unique_ptr<label_widget> _off_label_widget;
366 box_constraints _off_label_constraints;
367 box_shape _off_label_shape;
369 aarectangle _left_box_rectangle;
371 font_glyph_ids _chevrons_glyph;
372 aarectangle _chevrons_rectangle;
374 std::unique_ptr<overlay_widget> _overlay_widget;
375 box_constraints _overlay_constraints;
376 box_shape _overlay_shape;
379 grid_widget *_grid_widget =
nullptr;
381 callback<void()> _delegate_options_cbt;
382 callback<void()> _delegate_value_cbt;
383 callback<void(label)> _off_label_cbt;
385 [[nodiscard]]
bool overlay_closed() const noexcept
387 return _overlay_state == overlay_state_type::closed;
390 void open_overlay() noexcept
392 hi_axiom(loop::main().on_thread());
394 if (
auto focus_id = delegate->keyboard_focus_id(*
this)) {
395 _overlay_state = overlay_state_type::open;
397 process_event(gui_event::window_set_keyboard_target(*focus_id, keyboard_focus_group::menu));
402 void close_overlay() noexcept
404 hi_axiom(loop::main().on_thread());
406 if (_overlay_state == overlay_state_type::open) {
407 _overlay_state = overlay_state_type::closing;
408 _overlay_close_start = std::chrono::utc_clock::now();
413 void force_close_overlay() noexcept
415 if (_overlay_state != overlay_state_type::closed) {
416 _overlay_state = overlay_state_type::closed;
422 void animate_overlay(utc_nanoseconds display_time_point)
noexcept
424 hi_axiom(loop::main().on_thread());
426 switch (_overlay_state) {
427 case overlay_state_type::open:
429 case overlay_state_type::closing:
430 if (display_time_point >= _overlay_close_start + _overlay_close_delay) {
431 force_close_overlay();
436 case overlay_state_type::closed:
443 void update_options() noexcept
445 _grid_widget->clear();
446 for (
auto i = 0_uz; i != delegate->size(*
this); ++i) {
447 _grid_widget->push_bottom(delegate->make_option_widget(*_grid_widget, i));
450 ++global_counter<
"selection_widget:update_options:constrain">;
454 void update_value() noexcept
456 if (
auto selected_label = delegate->selected_label(*
this)) {
458 _current_label_widget->label = *selected_label;
469 void draw_outline(draw_context
const& context)
noexcept
476 theme().border_width(),
478 theme().rounding_radius());
481 void draw_left_box(draw_context
const& context)
noexcept
483 auto const corner_radii = os_settings::left_to_right() ?
484 hi::corner_radii(theme().rounding_radius<float>(), 0.0f, theme().rounding_radius<float>(), 0.0f) :
485 hi::corner_radii(0.0f, theme().rounding_radius<float>(), 0.0f, theme().rounding_radius<float>());
486 context.draw_box(
layout(), translate_z(0.1f) * _left_box_rectangle, focus_color(), corner_radii);
489 void draw_chevrons(draw_context
const& context)
noexcept
491 context.draw_glyph(
layout(), translate_z(0.2f) * _chevrons_rectangle, _chevrons_glyph, background_color());