Implement new grid_listbox widget

This commit is contained in:
Celtic Minstrel 2016-08-28 16:31:13 -04:00
parent 943c1bdd57
commit 82153b1da8
7 changed files with 562 additions and 51 deletions

View file

@ -614,6 +614,12 @@
max="-1"
super="gui/listbox_definition"
[/tag]
[tag]
name="grid_listbox_definition"
min="0"
max="-1"
super="gui/listbox_definition"
[/tag]
[tag]
name="horizontal_scrollbar_definition"
min="0"
@ -1525,6 +1531,61 @@
default=false
[/key]
[/tag]
[tag]
name="grid_listbox"
min="0"
max="-1"
super="generic/widget_instance"
[tag]
name="footer"
min="0"
max="1"
super="gui/window/resolution/grid"
[/tag]
[tag]
name="header"
min="0"
max="1"
super="gui/window/resolution/grid"
[/tag]
[tag]
name="list_data"
min="0"
max="1"
super="generic/listbox_grid"
[/tag]
[tag]
name="list_definition"
min="0"
max="1"
[tag]
name="row"
min="1"
max="1"
super="generic/listbox_grid/row"
[/tag]
[/tag]
[key]
name="horizontal_scrollbar_mode"
type="scrollbar_mode"
default=initial_auto
[/key]
[key]
name="vertical_scrollbar_mode"
type="scrollbar_mode"
default=initial_auto
[/key]
[key]
name="has_minimum"
type="bool"
default=true
[/key]
[key]
name="has_maximum"
type="bool"
default=true
[/key]
[/tag]
[tag]
name="listbox"
min="0"

View file

@ -176,5 +176,19 @@
[/horizontal_listbox_definition]
[grid_listbox_definition]
id = "default"
description = "Default grid listbox."
{_GUI_RESOLUTION
({GUI_NORMAL__RESOLUTION})
({GUI_NORMAL__FONT_SIZE__DEFAULT})
()
({GUI__FONT_COLOR_ENABLED__DEFAULT})
({GUI__FONT_COLOR_DISABLED__DEFAULT})
}
[/grid_listbox_definition]
#undef _GUI_RESOLUTION

View file

@ -34,7 +34,7 @@
namespace \
{ \
\
namespace ns_##type \
namespace ns_##type##id \
{ \
\
struct tregister_helper \

View file

@ -513,6 +513,298 @@ void tvertical_list::handle_key_down_arrow(SDLMod /*modifier*/, bool& handled)
}
}
tmatrix::tmatrix() : placed_(false)//, n_cols_(2)
{
}
void tmatrix::create_item(const unsigned /*index*/)
{
if(!placed_) {
return;
}
/** @todo implement. */
assert(false);
}
tpoint tmatrix::calculate_best_size() const
{
// The best size is the one that minimizes overall width and height
// We try a number of columns from 2 up to sqrt(rows) + 1
size_t n_items = get_item_count();
if(n_items == 0) {
return tpoint();
} else if(n_items == 1) {
return item(0).get_best_size();
}
size_t max_cols = sqrt(n_items) + 1;
std::map<size_t,tpoint> best_sizes;
for(size_t cols = 2, n = 0; cols <= max_cols && n < n_items; cols++) {
tpoint result;
for(size_t i = 0; i < n_items / cols + 1; ++i) {
tpoint row_size;
for(size_t j = 0; j < cols && n < n_items; j++, n++) {
const tgrid& grid = item(n);
if(grid.get_visible() == twidget::tvisible::invisible
|| !get_item_shown(n)) {
j--;
continue;
}
const tpoint best_size = grid.get_best_size();
row_size.x += best_size.x;
if(best_size.y > row_size.y) {
row_size.y = best_size.y;
}
}
if(row_size.x > result.x) {
result.x = row_size.x;
}
result.y += row_size.y;
}
best_sizes[cols] = result;
}
return std::min_element(best_sizes.begin(), best_sizes.end(), [](const std::pair<size_t,tpoint>& p1, const std::pair<size_t,tpoint>& p2) {
return p1.second.x + p1.second.y < p2.second.x + p2.second.y;
})->second;
//n_cols_ = iter->first; // TODO: This needs to be recalculated from the best size somehow?
}
void tmatrix::place(const tpoint& origin, const tpoint& size)
{
/*
* - Set every item to its best size.
* - The origin gets increased with the height of the last item.
* - No item should be wider as the size.
* - In the end the origin should be the sum of the origin and the wanted
* height.
*/
tpoint current_origin = origin;
for(size_t i = 0; i < get_item_count(); ++i) {
tgrid& grid = item_ordered(i);
if(grid.get_visible() == twidget::tvisible::invisible
|| !get_item_shown(get_item_at_ordered(i))) {
continue;
}
tpoint best_size = grid.get_best_size();
// FIXME should we look at grow factors???
// best_size.x = size.x;
grid.place(current_origin, best_size);
if(current_origin.x + best_size.x > size.x) {
current_origin.x = origin.x;
current_origin.y += best_size.y;
} else {
current_origin.x += best_size.x;
}
}
// assert(current_origin.y == origin.y + size.y);
}
void tmatrix::set_origin(const tpoint& origin)
{
tpoint current_origin = origin;
for(size_t i = 0; i < get_item_count(); ++i) {
tgrid& grid = item_ordered(i);
if(grid.get_visible() == twidget::tvisible::invisible
|| !get_item_shown(get_item_at_ordered(i))) {
continue;
}
grid.set_origin(current_origin);
if(current_origin.x + grid.get_width() > get_width()) {
current_origin.x = origin.x;
current_origin.y += grid.get_height();
} else {
current_origin.x += grid.get_width();
}
}
}
void tmatrix::set_visible_rectangle(const SDL_Rect& rectangle)
{
/*
* Note for most implementations this function could work only for the
* tindependent class it probably fails. Evaluate to make a generic
* function in the tgenerator template class and call it from the wanted
* placement functions.
*/
for(size_t i = 0; i < get_item_count(); ++i) {
tgrid& grid = item(i);
grid.set_visible_rectangle(rectangle);
}
}
twidget* tmatrix::find_at(const tpoint& coordinate,
const bool must_be_active)
{
assert(get_window());
for(size_t i = 0; i < get_item_count(); ++i) {
tgrid& grid = item(i);
if(grid.get_visible() == twidget::tvisible::invisible
|| !get_item_shown(i)) {
continue;
}
twidget* widget = grid.find_at(coordinate, must_be_active);
if(widget) {
return widget;
}
}
return nullptr;
}
const twidget* tmatrix::find_at(const tpoint& coordinate,
const bool must_be_active) const
{
assert(get_window());
for(size_t i = 0; i < get_item_count(); ++i) {
const tgrid& grid = item(i);
if(grid.get_visible() == twidget::tvisible::invisible
|| !get_item_shown(i)) {
continue;
}
const twidget* widget = grid.find_at(coordinate, must_be_active);
if(widget) {
return widget;
}
}
return nullptr;
}
void tmatrix::handle_key_up_arrow(SDLMod /*modifier*/, bool& handled)
{
if(get_selected_item_count() == 0) {
return;
}
// NOTE maybe this should only work if we can select only one item...
handled = true;
for(int i = get_ordered_index(get_selected_item()) - 1; i >= 0; --i) {
if(item_ordered(i).get_visible() == twidget::tvisible::invisible
|| !get_item_shown(get_item_at_ordered(i))) {
continue;
}
// NOTE we check the first widget to be active since grids have no
// active flag. This method might not be entirely reliable.
tcontrol* control = dynamic_cast<tcontrol*>(item_ordered(i).widget(0, 0));
if(control && control->get_active()) {
select_item(get_item_at_ordered(i), true);
return;
}
}
}
void tmatrix::handle_key_down_arrow(SDLMod /*modifier*/, bool& handled)
{
if(get_selected_item_count() == 0) {
return;
}
// NOTE maybe this should only work if we can select only one item...
handled = true;
for(size_t i = get_ordered_index(get_selected_item()) + 1; i < get_item_count(); ++i) {
if(item_ordered(i).get_visible() == twidget::tvisible::invisible
|| !get_item_shown(get_item_at_ordered(i))) {
continue;
}
// NOTE we check the first widget to be active since grids have no
// active flag. This method might not be entirely reliable.
tcontrol* control = dynamic_cast<tcontrol*>(item_ordered(i).widget(0, 0));
if(control && control->get_active()) {
select_item(get_item_at_ordered(i), true);
return;
}
}
}
void tmatrix::handle_key_left_arrow(SDLMod /*modifier*/, bool& handled)
{
if(get_selected_item_count() == 0) {
return;
}
// NOTE maybe this should only work if we can select only one item...
handled = true;
for(int i = get_ordered_index(get_selected_item()) - 1; i >= 0; --i) {
if(item(get_item_at_ordered(i)).get_visible() == twidget::tvisible::invisible
|| !get_item_shown(get_item_at_ordered(i))) {
continue;
}
// NOTE we check the first widget to be active since grids have no
// active flag. This method might not be entirely reliable.
tcontrol* control = dynamic_cast<tcontrol*>(item(get_item_at_ordered(i)).widget(0, 0));
if(control && control->get_active()) {
select_item(get_item_at_ordered(i), true);
return;
}
}
}
void tmatrix::handle_key_right_arrow(SDLMod /*modifier*/,
bool& handled)
{
if(get_selected_item_count() == 0) {
return;
}
// NOTE maybe this should only work if we can select only one item...
handled = true;
for(size_t i = get_ordered_index(get_selected_item()) + 1; i < get_item_count(); ++i) {
if(item(get_item_at_ordered(i)).get_visible() == twidget::tvisible::invisible
|| !get_item_shown(get_item_at_ordered(i))) {
continue;
}
// NOTE we check the first widget to be active since grids have no
// active flag. This method might not be entirely reliable.
tcontrol* control = dynamic_cast<tcontrol*>(item(get_item_at_ordered(i)).widget(0, 0));
if(control && control->get_active()) {
select_item(get_item_at_ordered(i), true);
return;
}
}
}
void tindependent::request_reduce_width(const unsigned maximum_width)
{
for(size_t i = 0; i < get_item_count(); ++i) {

View file

@ -349,11 +349,10 @@ private:
*/
struct tmatrix : public virtual tgenerator_
{
tmatrix();
/** See thorizontal_list::create_item(). */
void create_item(const unsigned /*index*/)
{
ERROR_LOG(false);
}
void create_item(const unsigned /*index*/);
/* Also make the overload from the generator_ visible. */
using tgenerator_::create_item;
@ -372,72 +371,48 @@ struct tmatrix : public virtual tgenerator_
}
/** See @ref twidget::calculate_best_size. */
virtual tpoint calculate_best_size() const override
{
ERROR_LOG(false);
}
virtual tpoint calculate_best_size() const override;
/** See @ref twidget::place. */
virtual void place(const tpoint& /*origin*/
,
const tpoint& /*size*/) override
{
ERROR_LOG(false);
}
, const tpoint& /*size*/) override;
/** See @ref twidget::set_origin. */
virtual void set_origin(const tpoint& /*origin*/) override
{
ERROR_LOG(false);
}
virtual void set_origin(const tpoint& /*origin*/) override;
/** See @ref thorizontal_list::set_visible_rectangle(). */
void set_visible_rectangle(const SDL_Rect& /*rectangle*/)
{
ERROR_LOG(false);
}
void set_visible_rectangle(const SDL_Rect& /*rectangle*/);
/** See @ref twidget::find_at. */
virtual twidget* find_at(const tpoint& /*coordinate*/
,
const bool /*must_be_active*/) override
{
ERROR_LOG(false);
}
, const bool /*must_be_active*/) override;
/** See @ref twidget::find_at. */
virtual const twidget* find_at(const tpoint& /*coordinate*/
,
const bool /*must_be_active*/) const override
{
ERROR_LOG(false);
}
, const bool /*must_be_active*/) const override;
/***** ***** ***** ***** keyboard functions ***** ***** ***** *****/
/** Inherited from tgenerator_. */
void handle_key_up_arrow(SDLMod, bool&)
{
ERROR_LOG(false);
}
void handle_key_up_arrow(SDLMod, bool&);
/** Inherited from tgenerator_. */
void handle_key_down_arrow(SDLMod, bool&)
{
ERROR_LOG(false);
}
void handle_key_down_arrow(SDLMod, bool&);
/** Inherited from tgenerator_. */
void handle_key_left_arrow(SDLMod, bool&)
{
ERROR_LOG(false);
}
void handle_key_left_arrow(SDLMod, bool&);
/** Inherited from tgenerator_. */
void handle_key_right_arrow(SDLMod, bool&)
{
ERROR_LOG(false);
}
void handle_key_right_arrow(SDLMod, bool&);
private:
/**
* Has the grid already been placed?
*
* If the grid is placed it's no problem set the location of the new
* item,it hasn't been placed, there's no information about its location
* so do nothing.
*/
bool placed_;
};
/**

View file

@ -44,12 +44,11 @@ namespace gui2
// ------------ WIDGET -----------{
REGISTER_WIDGET(listbox)
REGISTER_WIDGET3(tlistbox_definition, horizontal_listbox, _4)
REGISTER_WIDGET3(tlistbox_definition, grid_listbox, _4)
namespace
{
// in separate namespace to avoid name classes
REGISTER_WIDGET3(tlistbox_definition, horizontal_listbox, _4)
void callback_list_item_clicked(twidget& caller)
{
get_parent<tlistbox>(caller).list_item_clicked(caller);
@ -1100,6 +1099,150 @@ twidget* tbuilder_horizontal_listbox::build() const
#endif
}
/*WIKI_MACRO
* @begin{macro}{grid_listbox_description}
*
* A grid listbox is a control that holds several items of the
* same type. Normally the items in a listbox are ordered in rows,
* this version orders them in a grid instead.
* @end{macro}
*/
/*WIKI
* @page = GUIWidgetInstanceWML
* @order = 2_grid_listbox
* @begin{parent}{name="gui/window/resolution/grid/row/column/"}
* @begin{tag}{name="grid_listbox"}{min="0"}{max="-1"}{super="generic/widget_instance"}
* == Horizontal listbox ==
*
* @macro = grid_listbox_description
*
* List with the grid listbox specific variables:
* @begin{table}{config}
* vertical_scrollbar_mode & scrollbar_mode & initial_auto &
* Determines whether or not to show the
* scrollbar. $
* horizontal_scrollbar_mode & scrollbar_mode & initial_auto &
* Determines whether or not to show the
* scrollbar. $
*
* list_definition & section & & This defines how a listbox item
* looks. It must contain the grid
* definition for 1 column of the list. $
*
* list_data & section & [] & A grid alike section which stores the
* initial data for the listbox. Every row
* must have the same number of columns as
* the 'list_definition'. $
*
* has_minimum & bool & true & If false, less than one cell can be selected. $
*
* has_maximum & bool & true & If false, more than one cell can be selected. $
*
* @end{table}
* @begin{tag}{name="header"}{min=0}{max=1}{super="gui/window/resolution/grid"}
* @end{tag}{name="header"}
* @begin{tag}{name="footer"}{min=0}{max=1}{super="gui/window/resolution/grid"}
* @end{tag}{name="footer"}
* @begin{tag}{name="list_definition"}{min=0}{max=1}
* @begin{tag}{name="row"}{min=1}{max=1}{super="generic/listbox_grid/row"}
* @end{tag}{name="row"}
* @end{tag}{name="list_definition"}
* @begin{tag}{name="list_data"}{min=0}{max=1}{super="generic/listbox_grid"}
* @end{tag}{name="list_data"}
* In order to force widgets to be the same size inside a grid listbox,
* the widgets need to be inside a linked_group.
*
* Inside the list section there are only the following widgets allowed
* * grid (to nest)
* * selectable widgets which are
* ** toggle_button
* ** toggle_panel
* @end{tag}{name="grid_listbox"}
* @end{parent}{name="gui/window/resolution/grid/row/column/"}
*/
tbuilder_grid_listbox::tbuilder_grid_listbox(const config& cfg)
: tbuilder_control(cfg)
, vertical_scrollbar_mode(
get_scrollbar_mode(cfg["vertical_scrollbar_mode"]))
, horizontal_scrollbar_mode(
get_scrollbar_mode(cfg["horizontal_scrollbar_mode"]))
, list_builder(nullptr)
, list_data()
, has_minimum_(cfg["has_minimum"].to_bool(true))
, has_maximum_(cfg["has_maximum"].to_bool(true))
{
const config& l = cfg.child("list_definition");
VALIDATE(l, _("No list defined."));
list_builder = std::make_shared<tbuilder_grid>(l);
assert(list_builder);
VALIDATE(list_builder->rows == 1,
_("A 'list_definition' should contain one row."));
const config& data = cfg.child("list_data");
if(!data)
return;
for(const auto & row : data.child_range("row"))
{
unsigned col = 0;
for(const auto & c : row.child_range("column"))
{
list_data.push_back(string_map());
for(const auto & i : c.attribute_range())
{
list_data.back()[i.first] = i.second;
}
++col;
}
VALIDATE(col == list_builder->cols,
_("'list_data' must have "
"the same number of columns as the 'list_definition'."));
}
}
twidget* tbuilder_grid_listbox::build() const
{
#ifdef GUI2_EXPERIMENTAL_LISTBOX
tlist* widget = new tlist(
true, true, tgenerator_::grid, true, list_builder);
init_control(widget);
if(!list_data.empty()) {
widget->append_rows(list_data);
}
return widget;
#else
tlistbox* widget
= new tlistbox(has_minimum_, has_maximum_, tgenerator_::grid, true);
init_control(widget);
widget->set_list_builder(list_builder); // FIXME in finalize???
widget->set_vertical_scrollbar_mode(vertical_scrollbar_mode);
widget->set_horizontal_scrollbar_mode(horizontal_scrollbar_mode);
DBG_GUI_G << "Window builder: placed listbox '" << id
<< "' with definition '" << definition << "'.\n";
std::shared_ptr<const tlistbox_definition::tresolution>
conf = std::static_pointer_cast<const tlistbox_definition::tresolution>(
widget->config());
assert(conf);
widget->init_grid(conf->grid);
widget->finalize(nullptr, nullptr, list_data);
return widget;
#endif
}
} // namespace implementation
// }------------ END --------------

View file

@ -33,6 +33,7 @@ namespace implementation
{
struct tbuilder_listbox;
struct tbuilder_horizontal_listbox;
struct tbuilder_grid_listbox;
}
/** The listbox class. */
@ -40,6 +41,7 @@ class tlistbox : public tscrollbar_container
{
friend struct implementation::tbuilder_listbox;
friend struct implementation::tbuilder_horizontal_listbox;
friend struct implementation::tbuilder_grid_listbox;
friend class tdebug_layout_graph;
public:
@ -432,6 +434,30 @@ struct tbuilder_horizontal_listbox : public tbuilder_control
bool has_minimum_, has_maximum_;
};
struct tbuilder_grid_listbox : public tbuilder_control
{
explicit tbuilder_grid_listbox(const config& cfg);
using tbuilder_control::build;
twidget* build() const;
tscrollbar_container::tscrollbar_mode vertical_scrollbar_mode;
tscrollbar_container::tscrollbar_mode horizontal_scrollbar_mode;
tbuilder_grid_ptr list_builder;
/**
* Listbox data.
*
* Contains a vector with the data to set in every cell, it's used to
* serialize the data in the config, so the config is no longer required.
*/
std::vector<string_map> list_data;
bool has_minimum_, has_maximum_;
};
} // namespace implementation
// }------------ END --------------