72 set_attributes(std::forward<Attributes>(attributes)...);
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();
90 set_attributes(std::forward<Rest>(rest)...);
94 attributes_type attributes;
98 template<
typename... Args>
100 requires requires { make_shared_ctad<default_selection_delegate>(std::forward<Args>(args)...); }
102 return make_shared_ctad<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);
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 hi_axiom_not_null(this->delegate);
143 this->delegate->init(*
this);
165 OptionList&& option_list,
166 Attributes&&...attributes)
noexcept requires requires
168 make_default_delegate(std::forward<Value>(value), std::forward<OptionList>(option_list));
173 attributes_type{std::forward<Attributes>(attributes)...},
174 make_default_delegate(std::forward<Value>(value), std::forward<OptionList>(option_list)))
179 [[nodiscard]] generator<widget_intf&> children(
bool include_invisible)
noexcept override
181 co_yield *_overlay_widget;
182 co_yield *_current_label_widget;
183 co_yield *_off_label_widget;
186 [[nodiscard]] box_constraints update_constraints() noexcept
override
188 hi_assert_not_null(_off_label_widget);
189 hi_assert_not_null(_current_label_widget);
190 hi_assert_not_null(_overlay_widget);
193 _off_label_constraints = _off_label_widget->update_constraints();
194 _current_label_constraints = _current_label_widget->update_constraints();
195 _overlay_constraints = _overlay_widget->update_constraints();
197 auto const extra_size = extent2{theme().size() + theme().margin<
float>() * 2.0f, theme().margin<
float>() * 2.0f};
199 auto r = max(_off_label_constraints + extra_size, _current_label_constraints + extra_size);
202 _scroll_widget->
minimum->height() = theme().size();
204 r.minimum.width() =
std::max(r.minimum.width(), _overlay_constraints.minimum.
width() + extra_size.width());
205 r.preferred.width() =
std::max(r.preferred.width(), _overlay_constraints.preferred.
width() + extra_size.width());
206 r.maximum.width() =
std::max(r.maximum.width(), _overlay_constraints.maximum.
width() + extra_size.width());
207 r.margins = theme().margin();
208 r.alignment = resolve(*attributes.alignment, os_settings::left_to_right());
209 hi_axiom(r.holds_invariant());
213 void set_layout(widget_layout
const& context)
noexcept override
216 if (os_settings::left_to_right()) {
217 _left_box_rectangle = aarectangle{0.0f, 0.0f, theme().size(), context.height()};
220 auto const option_rectangle = aarectangle{
221 _left_box_rectangle.right() + theme().margin<
float>(),
223 context.width() - _left_box_rectangle.width() - theme().margin<
float>() * 2.0f,
225 _off_label_shape = box_shape{_off_label_constraints, option_rectangle, theme().baseline_adjustment()};
226 _current_label_shape = box_shape{_off_label_constraints, option_rectangle, theme().baseline_adjustment()};
229 _left_box_rectangle = aarectangle{context.width() - theme().size(), 0.0f, theme().size(), context.height()};
232 auto const option_rectangle = aarectangle{
233 theme().margin<
float>(),
235 context.width() - _left_box_rectangle.width() - theme().margin<
float>() * 2.0f,
237 _off_label_shape = box_shape{_off_label_constraints, option_rectangle, theme().baseline_adjustment()};
238 _current_label_shape = box_shape{_off_label_constraints, option_rectangle, theme().baseline_adjustment()};
241 _chevrons_glyph =
find_glyph(elusive_icon::ChevronUp);
242 auto const chevrons_glyph_bbox = _chevrons_glyph.get_metrics().bounding_rectangle * theme().icon_size();
243 _chevrons_rectangle =
align(_left_box_rectangle, chevrons_glyph_bbox, alignment::middle_center());
250 auto const overlay_width = std::clamp(
251 context.width() - theme().size(), _overlay_constraints.minimum.
width(), _overlay_constraints.maximum.
width());
252 auto const overlay_height = _overlay_constraints.preferred.
height();
253 auto const overlay_x = os_settings::left_to_right() ? theme().size() : context.width() - theme().size() - overlay_width;
254 auto const overlay_y = (context.height() - overlay_height) / 2;
255 auto const overlay_rectangle_request = aarectangle{overlay_x, overlay_y, overlay_width, overlay_height};
256 auto const overlay_rectangle = make_overlay_rectangle(overlay_rectangle_request);
257 _overlay_shape = box_shape{_overlay_constraints, overlay_rectangle, theme().baseline_adjustment()};
260 _off_label_widget->set_layout(context.transform(_off_label_shape));
261 _current_label_widget->set_layout(context.transform(_current_label_shape));
264 void draw(draw_context
const& context)
noexcept override
266 animate_overlay(context.display_time_point);
269 if (overlaps(context,
layout())) {
270 draw_outline(context);
271 draw_left_box(context);
272 draw_chevrons(context);
274 _off_label_widget->draw(context);
275 _current_label_widget->draw(context);
279 _overlay_widget->draw(context);
283 bool handle_event(gui_event
const& event)
noexcept override
285 switch (event.type()) {
286 case gui_event_type::mouse_up:
288 return handle_event(gui_event_type::gui_activate);
292 case gui_event_type::gui_activate_next:
295 case gui_event_type::gui_activate:
301 ++global_counter<
"selection_widget:gui_activate:relayout">;
305 case gui_event_type::gui_cancel:
315 [[nodiscard]] hitbox hitbox_test(point2 position)
const noexcept override
317 hi_axiom(loop::main().on_thread());
320 auto r = _overlay_widget->hitbox_test_from_parent(position);
322 if (
layout().contains(position)) {
323 r =
std::max(r, hitbox{
id, _layout.elevation, not delegate->empty(*
this) ? hitbox_type::button : hitbox_type::_default});
332 [[nodiscard]]
bool accepts_keyboard_focus(keyboard_focus_group group)
const noexcept override
334 hi_axiom(loop::main().on_thread());
335 return mode() >=
widget_mode::partial and to_bool(group & hi::keyboard_focus_group::normal) and not delegate->empty(*
this);
338 [[nodiscard]] color focus_color() const noexcept
override
340 hi_axiom(loop::main().on_thread());
343 return theme().color(semantic_color::accent);
345 return super::focus_color();
351 enum class overlay_state_type {
359 overlay_state_type _overlay_state = overlay_state_type::closed;
360 utc_nanoseconds _overlay_close_start = {};
362 bool _notification_from_delegate =
true;
365 box_constraints _current_label_constraints;
366 box_shape _current_label_shape;
369 box_constraints _off_label_constraints;
370 box_shape _off_label_shape;
372 aarectangle _left_box_rectangle;
374 font_book::font_glyph_type _chevrons_glyph;
375 aarectangle _chevrons_rectangle;
378 box_constraints _overlay_constraints;
379 box_shape _overlay_shape;
382 grid_widget *_grid_widget =
nullptr;
384 callback<void()> _delegate_options_cbt;
385 callback<void()> _delegate_value_cbt;
386 callback<void(label)> _off_label_cbt;
388 [[nodiscard]]
bool overlay_closed() const noexcept
390 return _overlay_state == overlay_state_type::closed;
393 void open_overlay() noexcept
395 hi_axiom(loop::main().on_thread());
397 if (
auto focus_id = delegate->keyboard_focus_id(*
this)) {
398 _overlay_state = overlay_state_type::open;
400 process_event(gui_event::window_set_keyboard_target(*focus_id, keyboard_focus_group::menu));
405 void close_overlay() noexcept
407 hi_axiom(loop::main().on_thread());
409 if (_overlay_state == overlay_state_type::open) {
410 _overlay_state = overlay_state_type::closing;
411 _overlay_close_start = std::chrono::utc_clock::now();
416 void force_close_overlay() noexcept
418 if (_overlay_state != overlay_state_type::closed) {
419 _overlay_state = overlay_state_type::closed;
425 void animate_overlay(utc_nanoseconds display_time_point)
noexcept
427 hi_axiom(loop::main().on_thread());
429 switch (_overlay_state) {
430 case overlay_state_type::open:
432 case overlay_state_type::closing:
433 if (display_time_point >= _overlay_close_start + _overlay_close_delay) {
434 force_close_overlay();
439 case overlay_state_type::closed:
446 void update_options() noexcept
448 _grid_widget->clear();
449 for (
auto i = 0_uz; i != delegate->size(*
this); ++i) {
450 _grid_widget->push_bottom(delegate->make_option_widget(*
this, *_grid_widget, i));
453 ++global_counter<
"selection_widget:update_options:constrain">;
457 void update_value() noexcept
459 if (
auto selected_label = delegate->selected_label(*
this)) {
461 _current_label_widget->label = *selected_label;
472 void draw_outline(draw_context
const& context)
noexcept
479 theme().border_width(),
481 theme().rounding_radius());
484 void draw_left_box(draw_context
const& context)
noexcept
486 auto const corner_radii = os_settings::left_to_right() ?
487 hi::corner_radii(theme().rounding_radius<float>(), 0.0f, theme().rounding_radius<float>(), 0.0f) :
488 hi::corner_radii(0.0f, theme().rounding_radius<float>(), 0.0f, theme().rounding_radius<float>());
489 context.draw_box(
layout(), translate_z(0.1f) * _left_box_rectangle, focus_color(), corner_radii);
492 void draw_chevrons(draw_context
const& context)
noexcept
494 context.draw_glyph(
layout(), translate_z(0.2f) * _chevrons_rectangle, _chevrons_glyph, label_color());