GUI2/Listbox: improve sort option handling
* Re-added the ability to set a single sorter, this time by ID. * Multi-sorter setting now uses the magic sort_N IDs. * Cleaned up internal handling to remove reliance on header grid columns. Sorters will still be looked for in the header, but rely on the specified ID. This did (I'm pretty sure) work before, but now it's explicit. * The order_pair typedef has been removed * set_active_sorting_option has been removed set_active_sorter and now takes its arguments separately instead of as a pair. * set_active_sorter will use the bound sorter header on-modified handler instead of calling order_by_column directly * set_sorting_options has been renamed to set_sorters * get_active_sorting_option has been renamed get_sorter
This commit is contained in:
parent
10b640c0f2
commit
530cd2b02b
10 changed files with 103 additions and 98 deletions
|
@ -106,7 +106,7 @@ void game_load::pre_show()
|
|||
keyboard_capture(filter);
|
||||
add_to_keyboard_chain(&list);
|
||||
|
||||
list.set_sorting_options(
|
||||
list.set_sorters(
|
||||
[this](const std::size_t i) { return games_[i].name(); },
|
||||
[this](const std::size_t i) { return games_[i].modified(); }
|
||||
);
|
||||
|
|
|
@ -197,7 +197,7 @@ void game_stats::pre_show()
|
|||
}
|
||||
|
||||
// Sorting options for the status list
|
||||
stats_list.set_sorting_options(
|
||||
stats_list.set_sorters(
|
||||
[this](const std::size_t i) {
|
||||
unit_const_ptr leader = get_leader(i + 1);
|
||||
return leader ? leader->name() : t_string();
|
||||
|
@ -211,7 +211,7 @@ void game_stats::pre_show()
|
|||
);
|
||||
|
||||
// Sorting options for the settings list
|
||||
settings_list.set_sorting_options(
|
||||
settings_list.set_sorters(
|
||||
[this](const std::size_t i) {
|
||||
unit_const_ptr leader = get_leader(i + 1);
|
||||
return leader ? leader->name() : t_string();
|
||||
|
|
|
@ -841,7 +841,7 @@ void preferences_dialog::initialize_callbacks()
|
|||
text_box& filter = find_widget<text_box>("filter");
|
||||
filter.set_text_changed_callback(std::bind(&preferences_dialog::hotkey_filter_callback, this));
|
||||
|
||||
hotkey_list.set_sorting_options(
|
||||
hotkey_list.set_sorters(
|
||||
// Action column
|
||||
[this](const std::size_t i) { return visible_hotkeys_[i]->description; },
|
||||
|
||||
|
@ -854,7 +854,7 @@ void preferences_dialog::initialize_callbacks()
|
|||
[this](const std::size_t i) { return !visible_hotkeys_[i]->scope[hotkey::SCOPE_MAIN_MENU]; }
|
||||
);
|
||||
|
||||
hotkey_list.set_active_sorting_option({0, sort_order::type::ascending}, true);
|
||||
hotkey_list.set_active_sorter("sort_0", sort_order::type::ascending, true);
|
||||
|
||||
connect_signal_mouse_left_click(
|
||||
find_widget<button>("btn_add_hotkey"), std::bind(
|
||||
|
@ -1000,7 +1000,7 @@ void preferences_dialog::default_hotkey_callback()
|
|||
|
||||
// Set up the list again and reselect the default sorting option.
|
||||
listbox& hotkey_list = setup_hotkey_list();
|
||||
hotkey_list.set_active_sorting_option({0, sort_order::type::ascending}, true);
|
||||
hotkey_list.set_active_sorter("sort_0", sort_order::type::ascending, true);
|
||||
}
|
||||
|
||||
void preferences_dialog::remove_hotkey_callback(listbox& hotkeys)
|
||||
|
|
|
@ -115,13 +115,13 @@ void unit_create::pre_show()
|
|||
<< std::endl;
|
||||
}
|
||||
|
||||
list.set_sorting_options(
|
||||
list.set_sorters(
|
||||
[this](const std::size_t i) { return units_[i]->race()->plural_name(); },
|
||||
[this](const std::size_t i) { return units_[i]->type_name(); }
|
||||
);
|
||||
|
||||
// Select the first entry on sort if no previous selection was provided.
|
||||
list.set_active_sorting_option({0, sort_order::type::ascending}, choice_.empty());
|
||||
list.set_active_sorter("sort_0", sort_order::type::ascending, choice_.empty());
|
||||
|
||||
list_item_clicked();
|
||||
}
|
||||
|
|
|
@ -144,7 +144,7 @@ void unit_list::pre_show()
|
|||
}
|
||||
}
|
||||
|
||||
list.set_sorting_options(
|
||||
list.set_sorters(
|
||||
[this](const std::size_t i) { return unit_list_[i]->type_name(); },
|
||||
[this](const std::size_t i) { return unit_list_[i]->name(); },
|
||||
[this](const std::size_t i) { return unit_list_[i]->movement_left(); },
|
||||
|
|
|
@ -47,9 +47,8 @@ static lg::log_domain log_display("display");
|
|||
namespace gui2::dialogs
|
||||
{
|
||||
|
||||
// Index 2 is by-level
|
||||
static listbox::order_pair sort_last {-1, sort_order::type::none};
|
||||
static listbox::order_pair sort_default { 2, sort_order::type::descending};
|
||||
static std::pair sort_default{ std::string{"sort_2"}, sort_order::type::descending };
|
||||
static utils::optional<decltype(sort_default)> sort_last;
|
||||
|
||||
REGISTER_DIALOG(unit_recall)
|
||||
|
||||
|
@ -286,7 +285,7 @@ void unit_recall::pre_show()
|
|||
}
|
||||
}
|
||||
|
||||
list.set_sorting_options(
|
||||
list.set_sorters(
|
||||
[this](const std::size_t i) { return recall_list_[i]->type_name(); },
|
||||
[this](const std::size_t i) { return recall_list_[i]->name(); },
|
||||
[this](const std::size_t i) {
|
||||
|
@ -299,7 +298,8 @@ void unit_recall::pre_show()
|
|||
}
|
||||
);
|
||||
|
||||
list.set_active_sorting_option(sort_last.first >= 0 ? sort_last : sort_default, true);
|
||||
const auto [sorter_id, order] = sort_last.value_or(sort_default);
|
||||
list.set_active_sorter(sorter_id, order, true);
|
||||
|
||||
list_item_clicked();
|
||||
}
|
||||
|
@ -427,7 +427,9 @@ void unit_recall::list_item_clicked()
|
|||
void unit_recall::post_show()
|
||||
{
|
||||
listbox& list = find_widget<listbox>("recall_list");
|
||||
sort_last = list.get_active_sorting_option();
|
||||
|
||||
const auto [sorter, order] = list.get_active_sorter();
|
||||
sort_last = std::pair{ sorter->id(), order };
|
||||
|
||||
if(get_retval() == retval::OK) {
|
||||
selected_index_ = list.get_selected_row();
|
||||
|
|
|
@ -375,7 +375,7 @@ void addon_list::finalize_setup()
|
|||
{
|
||||
listbox& list = get_listbox();
|
||||
|
||||
list.set_sorting_options(
|
||||
list.set_sorters(
|
||||
[this](const std::size_t i) { return t_string(addon_vector_[i]->display_title_full()); },
|
||||
[this](const std::size_t i) { return addon_vector_[i]->author; },
|
||||
[this](const std::size_t i) { return addon_vector_[i]->size; },
|
||||
|
@ -383,8 +383,7 @@ void addon_list::finalize_setup()
|
|||
[this](const std::size_t i) { return t_string(addon_vector_[i]->display_type()); }
|
||||
);
|
||||
|
||||
auto order = std::pair(0, sort_order::type::ascending);
|
||||
list.set_active_sorting_option(order);
|
||||
list.set_active_sorter("sort_0", sort_order::type::ascending);
|
||||
}
|
||||
|
||||
void addon_list::set_addon_order(const addon_sort_func& func)
|
||||
|
|
|
@ -87,8 +87,6 @@ listbox::listbox(const implementation::builder_listbox_base& builder)
|
|||
|
||||
// TODO: can we use the replacements system here?
|
||||
swap_grid(nullptr, content_grid(), std::move(generator), "_list_grid");
|
||||
|
||||
initialize_header();
|
||||
}
|
||||
|
||||
grid& listbox::add_row(const widget_item& item, const int index)
|
||||
|
@ -555,57 +553,41 @@ void listbox::handle_key_right_arrow(SDL_Keymod modifier, bool& handled)
|
|||
}
|
||||
}
|
||||
|
||||
void listbox::initialize_header()
|
||||
void listbox::initialize_sorter(const std::string& id, generator_sort_array&& array)
|
||||
{
|
||||
auto header = find_widget<grid>("_header_grid", false, false);
|
||||
if(!header) {
|
||||
return;
|
||||
}
|
||||
if(!header) return;
|
||||
|
||||
for(unsigned i = 0, max = std::max(header->get_cols(), header->get_rows()); i < max; ++i) {
|
||||
//
|
||||
// TODO: I had to change this to case to a toggle_button in order to use a signal handler.
|
||||
// Should probably look into a way to make it more general like it was before (used to be
|
||||
// cast to selectable_item).
|
||||
//
|
||||
// - vultraz, 2017-08-23
|
||||
//
|
||||
if(toggle_button* selectable = header->find_widget<toggle_button>("sort_" + std::to_string(i), false, false)) {
|
||||
// Register callback to sort the list.
|
||||
connect_signal_notify_modified(*selectable, std::bind(&listbox::order_by_column, this, i, std::placeholders::_1));
|
||||
auto toggle = header->find_widget<selectable_item>(id, false, false);
|
||||
if(!toggle) return;
|
||||
|
||||
if(orders_.size() < max) {
|
||||
orders_.resize(max);
|
||||
}
|
||||
const std::size_t i = orders_.size();
|
||||
orders_.emplace_back(toggle, std::move(array));
|
||||
|
||||
orders_[i].first = selectable;
|
||||
}
|
||||
}
|
||||
// TODO: we can bind the pair directly if we remove the on-order callback
|
||||
connect_signal_notify_modified(dynamic_cast<widget&>(*toggle),
|
||||
std::bind(&listbox::order_by_column, this, i, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void listbox::order_by_column(unsigned column, widget& widget)
|
||||
{
|
||||
selectable_item& selectable = dynamic_cast<selectable_item&>(widget);
|
||||
if(column >= orders_.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(auto& pair : orders_) {
|
||||
if(pair.first != nullptr && pair.first != &selectable) {
|
||||
pair.first->set_value(static_cast<unsigned int>(sort_order::type::none));
|
||||
for(auto& [w, _] : orders_) {
|
||||
if(w && w != &selectable) {
|
||||
w->set_value(utils::to_underlying(sort_order::type::none));
|
||||
}
|
||||
}
|
||||
|
||||
sort_order::type order = sort_order::get_enum(selectable.get_value()).value_or(sort_order::type::none);
|
||||
|
||||
if(static_cast<unsigned int>(order) > orders_[column].second.size()) {
|
||||
auto order = sort_order::get_enum(selectable.get_value()).value_or(sort_order::type::none);
|
||||
if(static_cast<unsigned>(order) > orders_[column].second.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(order == sort_order::type::none) {
|
||||
order_by(std::less<unsigned>());
|
||||
} else {
|
||||
order_by(orders_[column].second[static_cast<unsigned int>(order) - 1]);
|
||||
order_by(orders_[column].second[utils::to_underlying(order) - 1]);
|
||||
}
|
||||
|
||||
if(callback_order_change_ != nullptr) {
|
||||
|
@ -630,48 +612,39 @@ bool listbox::sort_helper::more(const t_string& lhs, const t_string& rhs)
|
|||
return translation::icompare(lhs, rhs) > 0;
|
||||
}
|
||||
|
||||
void listbox::set_active_sorting_option(const order_pair& sort_by, const bool select_first)
|
||||
void listbox::set_active_sorter(const std::string& id, sort_order::type order, bool select_first)
|
||||
{
|
||||
// TODO: should this be moved to a public header_grid() getter function?
|
||||
auto header = find_widget<grid>("_header_grid", false, false);
|
||||
if(!header) {
|
||||
return;
|
||||
}
|
||||
for(auto& [w, _] : orders_) {
|
||||
if(!w || dynamic_cast<widget*>(w)->id() != id) continue;
|
||||
|
||||
selectable_item& w = header->find_widget<selectable_item>("sort_" + std::to_string(sort_by.first));
|
||||
// Set the state and fire a modified event to handle updating the list
|
||||
w->set_value(utils::to_underlying(order), true);
|
||||
|
||||
// Set the sorting toggle widgets' value (in this case, its state) to the given sorting
|
||||
// order. This is necessary since the widget's value is used to determine the order in
|
||||
// @ref order_by_column in lieu of a direction being passed directly.
|
||||
w.set_value(static_cast<int>(sort_by.second));
|
||||
|
||||
order_by_column(sort_by.first, dynamic_cast<widget&>(w));
|
||||
|
||||
if(select_first && generator_->get_item_count() > 0) {
|
||||
select_row_at(0);
|
||||
if(select_first && generator_->get_item_count() > 0) {
|
||||
select_row_at(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const listbox::order_pair listbox::get_active_sorting_option()
|
||||
std::pair<widget*, sort_order::type> listbox::get_active_sorter() const
|
||||
{
|
||||
for(unsigned int column = 0; column < orders_.size(); ++column) {
|
||||
selectable_item* w = orders_[column].first;
|
||||
for(const auto& [w, _] : orders_) {
|
||||
if(!w) continue;
|
||||
|
||||
sort_order::type sort = sort_order::get_enum(w->get_value()).value_or(sort_order::type::none);
|
||||
auto sort = sort_order::get_enum(w->get_value()).value_or(sort_order::type::none);
|
||||
if(sort != sort_order::type::none) {
|
||||
return std::pair(column, sort);
|
||||
return { dynamic_cast<widget*>(w), sort };
|
||||
}
|
||||
}
|
||||
|
||||
return std::pair(-1, sort_order::type::none);
|
||||
return { nullptr, sort_order::type::none };
|
||||
}
|
||||
|
||||
void listbox::mark_as_unsorted()
|
||||
{
|
||||
for(auto& pair : orders_) {
|
||||
if(pair.first != nullptr) {
|
||||
pair.first->set_value(static_cast<unsigned int>(sort_order::type::none));
|
||||
for(auto& [w, _] : orders_) {
|
||||
if(w) {
|
||||
w->set_value(utils::to_underlying(sort_order::type::none));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -262,32 +262,62 @@ private:
|
|||
static bool more(const t_string& lhs, const t_string& rhs);
|
||||
};
|
||||
|
||||
public:
|
||||
template<typename... Args>
|
||||
void set_sorting_options(Args&&... functors)
|
||||
/** Implementation detail of @ref set_single_sorter */
|
||||
void initialize_sorter(const std::string& id, generator_sort_array&&);
|
||||
|
||||
/** Implementation detail of @ref set_sorters */
|
||||
template<std::size_t... Is, typename... Args>
|
||||
void set_sorters_impl(std::index_sequence<Is...>, Args&&... fs)
|
||||
{
|
||||
orders_ = {{ nullptr, {
|
||||
[f = functors](int lhs, int rhs) { return sort_helper::less(f(lhs), f(rhs)); },
|
||||
[f = functors](int lhs, int rhs) { return sort_helper::more(f(lhs), f(rhs)); }
|
||||
}}...};
|
||||
(set_single_sorter("sort_" + std::to_string(Is), fs), ...);
|
||||
}
|
||||
|
||||
using order_pair = std::pair<int, sort_order::type>;
|
||||
public:
|
||||
/**
|
||||
* Registers a single sorting control by ID.
|
||||
*
|
||||
* @param id The ID of the selectable_item header widget to bind to.
|
||||
* @param f Any callable whose result is sortable.
|
||||
*/
|
||||
template<typename Func>
|
||||
void set_single_sorter(const std::string& id, const Func& f)
|
||||
{
|
||||
initialize_sorter(id, {
|
||||
[f](int lhs, int rhs) { return sort_helper::less(f(lhs), f(rhs)); },
|
||||
[f](int lhs, int rhs) { return sort_helper::more(f(lhs), f(rhs)); }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the listbox by a pre-set sorting option. The corresponding header widget will also be toggled.
|
||||
* The sorting option should already have been registered by @ref listbox::set_sorting_options().
|
||||
* Registers sorting controls using magic index IDs.
|
||||
*
|
||||
* @param sort_by Pair of column index and sort direction. The column (first argument)
|
||||
* argument will be sorted in the specified direction (second argument)
|
||||
* This function accepts any callable whose result is sortable. Each callable passed
|
||||
* will be bound to a corresponding selectable_item widget in the header, if present,
|
||||
* whose ID is sort_N, where N is the index of the callable in the parameter pack.
|
||||
*
|
||||
* @param select_first If true, the first row post-sort will be selected. If false (default),
|
||||
* the selected row will be maintained post-sort as per standard sorting
|
||||
* functionality.
|
||||
* @param functors Zero or more callables with the signature T(std::size_t).
|
||||
*/
|
||||
void set_active_sorting_option(const order_pair& sort_by, const bool select_first = false);
|
||||
template<typename... Args>
|
||||
void set_sorters(Args&&... functors)
|
||||
{
|
||||
set_sorters_impl(std::index_sequence_for<Args...>{}, std::forward<Args>(functors)...);
|
||||
}
|
||||
|
||||
const order_pair get_active_sorting_option();
|
||||
/**
|
||||
* Sorts the listbox by a pre-set sorting option. The corresponding header widget
|
||||
* will also be toggled. The sorting option should already have been registered by
|
||||
* @ref listbox::set_sorters().
|
||||
*
|
||||
* @param id The id of the sorter widget whose value to set.
|
||||
* @param order The order to sort by (ascending, descending, or none).
|
||||
* @param select_first If true, the first row post-sort will be selected.
|
||||
* If false (default), the selected row will be maintained
|
||||
* post-sort as per standard sorting functionality.
|
||||
*/
|
||||
void set_active_sorter(const std::string& id, sort_order::type order, bool select_first = false);
|
||||
|
||||
/** Returns a widget pointer to the active sorter, along with its corresponding order. */
|
||||
std::pair<widget*, sort_order::type> get_active_sorter() const;
|
||||
|
||||
/** Deactivates all sorting toggle buttons at the top, making the list look like it's not sorted. */
|
||||
void mark_as_unsorted();
|
||||
|
@ -336,11 +366,6 @@ private:
|
|||
* For now it's always fixed width depending on the first row.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Finishes binding callbacks to header sorting toggles.
|
||||
*/
|
||||
void initialize_header();
|
||||
|
||||
/**
|
||||
* Contains a pointer to the generator.
|
||||
*
|
||||
|
|
|
@ -36,6 +36,12 @@ inline constexpr bool decayed_is_same = std::is_same_v<std::decay_t<T1>, std::de
|
|||
template<typename>
|
||||
inline constexpr bool dependent_false_v = false;
|
||||
|
||||
template<typename Enum>
|
||||
constexpr std::underlying_type_t<Enum> to_underlying(Enum e) noexcept
|
||||
{
|
||||
return static_cast<std::underlying_type_t<Enum>>(e);
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue