Implement the ability to use toggle buttons in drop down menus

This commit is contained in:
Charles Dang 2017-02-13 23:24:30 +11:00
parent cfb2792932
commit 83f159be8d
5 changed files with 107 additions and 16 deletions

View file

@ -22,6 +22,7 @@
#include "gui/widgets/integer_selector.hpp"
#include "gui/widgets/scrollbar.hpp"
#include "gui/widgets/settings.hpp"
#include "gui/widgets/toggle_button.hpp"
#include "gui/widgets/toggle_panel.hpp"
#include "gui/widgets/window.hpp"
@ -38,6 +39,28 @@ REGISTER_DIALOG(drop_down_menu)
namespace {
void click_callback(window& window, bool&, bool&, point coordinate)
{
listbox& list = find_widget<listbox>(&window, "list", true);
/* Disregard clicks on scrollbars and toggle buttons so the dropdown menu can be scrolled or have an embedded
* toggle button selected without the menu closing.
*
* This works since this click_callback function is called before widgets' left-button-up handlers.
* Additionally, this is done before row deselection so selecting/deselecting a toggle button doesn't also leave
* the list with no row visually selected. Oddly, the visial deselection doesn't seem to cause any crashes, and
* the previously selected row is reselected when the menu is opened again. Still, it's odd to see your selection
* vanish.
*/
if(list.vertical_scrollbar()->get_state() == scrollbar_base::PRESSED) {
return;
}
if(toggle_button* checkbox = dynamic_cast<toggle_button*>(window.find_at(coordinate, true))) {
if(checkbox->get_state() == toggle_button::FOCUSED) {
return;
}
}
/* FIXME: This dialog uses a listbox with 'has_minimum = false'. This allows a listbox to have 0 or more selections,
* and selecting the same entry toggles that entry's state (ie, if it was selected, it will be deselected). Because
* of this, selecting the same entry in the dropdown list essentially sets the list's selected row to -1, causing problems.
@ -45,18 +68,11 @@ namespace {
* In order to work around this, we first manually deselect the selected entry here. This handler is called *before*
* the listbox's click handler, and as such the selected item will remain toggled on when the click handler fires.
*/
listbox& list = find_widget<listbox>(&window, "list", true);
const int sel = list.get_selected_row();
if(sel >= 0) {
list.select_row(sel, false);
}
// Disregard clicks if they're on the scrollbar
// This check works since this function is called before scrollbar_base's left-button-up handler
if(list.vertical_scrollbar()->get_state() == scrollbar_base::PRESSED) {
return;
}
SDL_Rect rect = window.get_rectangle();
if(!sdl::point_in_rect(coordinate, rect)) {
window.set_retval(window::CANCEL);
@ -117,6 +133,18 @@ void drop_down_menu::pre_show(window& window)
delete mi_grid->swap_child("label", img, false);
}
}
if(entry.has_attribute("checkbox")) {
toggle_button* checkbox = new toggle_button;
checkbox->set_definition("default");
checkbox->set_id("checkbox");
checkbox->set_value_bool(entry["checkbox"].to_bool(false));
grid* mi_grid = dynamic_cast<grid*>(new_row.find("menu_item", false));
if(mi_grid) {
delete mi_grid->swap_child("icon", checkbox, false);
}
}
}
if(selected_item_ >= 0 && unsigned(selected_item_) < list.get_item_count()) {
@ -134,7 +162,19 @@ void drop_down_menu::pre_show(window& window)
void drop_down_menu::post_show(window& window)
{
selected_item_ = find_widget<listbox>(&window, "list", true).get_selected_row();
listbox& list = find_widget<listbox>(&window, "list", true);
selected_item_ = list.get_selected_row();
for(unsigned i = 0; i < list.get_item_count(); i++) {
grid* row_grid = list.get_row_grid(i);
if(toggle_button* checkbox = find_widget<toggle_button>(row_grid, "checkbox", false, false)) {
toggle_states_.push_back(checkbox->get_value_bool());
} else {
toggle_states_.push_back(false);
}
}
}
} // namespace dialogs

View file

@ -17,6 +17,8 @@
#include "gui/dialogs/modal_dialog.hpp"
#include "sdl/rect.hpp"
#include <boost/dynamic_bitset.hpp>
class config;
namespace gui2
@ -28,22 +30,43 @@ class drop_down_menu : public modal_dialog
{
public:
drop_down_menu(SDL_Rect button_pos, const std::vector<config>& items, int selected_item, bool use_markup)
: button_pos_(button_pos)
, items_(items)
: items_(items)
, toggle_states_()
, button_pos_(button_pos)
, selected_item_(selected_item)
, use_markup_(use_markup)
{
set_restore(true);
}
int selected_item() const { return selected_item_; }
int selected_item() const
{
return selected_item_;
}
boost::dynamic_bitset<> get_toggle_states() const
{
return toggle_states_;
}
private:
/// The screen location of the menu_button button that triggred this droplist.
/// Note: we don't adjust the location of this dialog to when resizing the window.
/// Instead this dialog automatically closes itself on resizing.
SDL_Rect button_pos_;
/** Configuration of rach row. */
std::vector<config> items_;
/** If a toggle button widget is present, returns the toggled state of each row's button. */
boost::dynamic_bitset<> toggle_states_;
/**
* The screen location of the menu_button button that triggered this droplist.
* Note: we don't adjust the location of this dialog to when resizing the window.
* Instead this dialog automatically closes itself on resizing.
*/
SDL_Rect button_pos_;
int selected_item_;
bool use_markup_;
/** Inherited from modal_dialog, implemented by REGISTER_DIALOG. */
virtual const std::string& window_id() const;

View file

@ -46,6 +46,7 @@ menu_button::menu_button()
, retval_(0)
, values_()
, selected_()
, toggle_states_()
{
values_.push_back(config_of("label", this->get_label()));
@ -172,6 +173,25 @@ void menu_button::signal_handler_left_button_click(const event::ui_event event,
}
}
// Toggle states are recorded regardless of dismissal type
toggle_states_ = droplist.get_toggle_states();
/* In order to allow toggle button states to be specified by verious dialogs in the values config, we write the state
* bools to the values_ config here, but only if a checkbox= key was already provided. The value of the checkbox= key
* is handled by the drop_down_menu widget.
*
* Passing the dynamic_bitset directly to the drop_down_menu ctor would mean bool values would need to be passed to this
* class independently of the values config by dialogs that use this widget. However, the bool states are also saved
* in a dynamic_bitset class member which can be fetched for other uses if necessary.
*/
for(unsigned i = 0; i < values_.size(); i++) {
::config& c = values_[i];
if(c.has_attribute("checkbox")) {
c["checkbox"] = toggle_states_[i];
}
}
handled = true;
}
@ -184,6 +204,7 @@ void menu_button::set_values(const std::vector<::config>& values, int selected)
}
values_ = values;
selected_ = selected;
toggle_states_.resize(values_.size(), false);
set_label(values_[selected_]["label"]);
}
void menu_button::set_selected(int selected)

View file

@ -20,6 +20,8 @@
#include "gui/widgets/styled_widget.hpp"
#include "gui/widgets/selectable_item.hpp"
#include <boost/dynamic_bitset.hpp>
class config;
namespace gui2
@ -88,6 +90,8 @@ public:
/** Returns the value of the selected row */
std::string get_value_string() const { return values_[selected_]["label"]; }
boost::dynamic_bitset<> get_toggle_states() const { return toggle_states_; }
private:
/**
* Possible states of the widget.
@ -125,6 +129,8 @@ private:
*/
int selected_;
boost::dynamic_bitset<> toggle_states_;
/** See @ref styled_widget::get_control_type. */
virtual const std::string& get_control_type() const override;

View file

@ -81,7 +81,6 @@ public:
return icon_name_;
}
private:
/**
* Possible states of the widget.
*
@ -97,6 +96,8 @@ private:
COUNT
};
private:
void set_state(const state_t state);
/**