Handling of RTL in the left pane of the help browser

Fixes the display of topic headings, including unit names, in right to left
languages (Arabic and Hebrew).

The GUI1 menu code is only kept to support one UI feature, the help browser.
However, the code supported multiple columns and multiple things in each
column; to do the latter it handled each column as a string with embedded
separators. To support the help GUI, all that's needed is for each row to have
an indent, an icon, and a text field. Traces of the multiple-column support are
still in the code, but the drawing code is simplified.

The logic for working out whether mouse clicks are on the icon or the text has
moved from the subclass to the main menu class, as it's a subset of the logic
for drawing the UI.

In LTR languages, this looks almost identical.

In RTL languages, the book icons now appear in the right place, and the text
placement is reasonable.
This commit is contained in:
Steve Cotton 2024-01-11 13:36:51 +01:00 committed by Steve Cotton
parent 69dae9f0a3
commit 286f14657b
6 changed files with 203 additions and 257 deletions

View file

@ -0,0 +1,2 @@
### User interface
* Fix the left pane of the help browsers layout for right-to-left languages (Arabic and Hebrew) (issue #8205)

View file

@ -29,12 +29,12 @@
namespace help {
help_menu::help_menu(const section& toplevel, int max_height) :
gui::menu(empty_string_vector, true, max_height, -1, &gui::menu::bluebg_style),
gui::menu(true, max_height, -1, &gui::menu::bluebg_style),
visible_items_(),
toplevel_(toplevel),
expanded_(),
chosen_topic_(nullptr),
selected_item_(&toplevel, "", 0)
selected_item_(&toplevel, 0)
{
silent_ = true; //silence the default menu sounds
update_visible_items(toplevel_);
@ -43,7 +43,7 @@ help_menu::help_menu(const section& toplevel, int max_height) :
selected_item_ = visible_items_.front();
}
bool help_menu::expanded(const section &sec)
bool help_menu::expanded(const section &sec) const
{
return expanded_.find(&sec) != expanded_.end();
}
@ -70,8 +70,7 @@ void help_menu::update_visible_items(const section &sec, unsigned level)
}
for (const auto &s : sec.sections) {
if (is_visible_id(s.id)) {
const std::string vis_string = get_string_to_show(s, level + 1);
visible_items_.emplace_back(&s, vis_string, level + 1);
visible_items_.emplace_back(&s, level + 1);
if (expanded(s)) {
update_visible_items(s, level + 1);
}
@ -79,38 +78,11 @@ void help_menu::update_visible_items(const section &sec, unsigned level)
}
for (const auto &t : sec.topics) {
if (is_visible_id(t.id)) {
const std::string vis_string = get_string_to_show(t, level + 1);
visible_items_.emplace_back(&t, vis_string, level + 1);
visible_items_.emplace_back(&t, level + 1);
}
}
}
std::string help_menu::indent_list(const std::string& icon, const unsigned level) {
std::stringstream to_show;
for (unsigned i = 1; i < level; ++i) {
to_show << " "; // Indent 4 spaces
}
to_show << IMG_TEXT_SEPARATOR << IMAGE_PREFIX << icon;
return to_show.str();
}
std::string help_menu::get_string_to_show(const section &sec, const unsigned level)
{
std::stringstream to_show;
to_show << indent_list(expanded(sec) ? open_section_img : closed_section_img, level)
<< IMG_TEXT_SEPARATOR << sec.title;
return to_show.str();
}
std::string help_menu::get_string_to_show(const topic &topic, const unsigned level)
{
std::stringstream to_show;
to_show << indent_list(topic_img, level)
<< IMG_TEXT_SEPARATOR << topic.title;
return to_show.str();
}
bool help_menu::select_topic_internal(const topic &t, const section &sec)
{
topic_list::const_iterator tit =
@ -167,21 +139,13 @@ int help_menu::process()
// * user single-clicks on the icon (or to the left of it): expand or collapse the tree view
// * user double-clicks anywhere: expand or collapse the tree view
// * note: the first click of the double-click has the single-click effect too
int x = mousex - menu::location().x;
const std::string icon_img = expanded(*sec) ? open_section_img : closed_section_img;
// the "thickness" is the width of the left border
int text_start = style_->item_size(indent_list(icon_img, selected_item_.level)).w - style_->get_thickness();
// NOTE: if you want to forbid click to the left of the icon
// also check x >= text_start-image_width(icon_img)
if (menu::double_clicked() || x < text_start) {
if (menu::double_clicked() || hit_on_indent_or_icon(static_cast<std::size_t>(res), mousex)) {
// Open or close a section if we double-click on it
// or do simple click on the icon.
expanded(*sec) ? contract(*sec) : expand(*sec);
update_visible_items(toplevel_);
display_visible_items();
} else if (x >= text_start){
} else {
// click on title open the topic associated to this section
chosen_topic_ = find_topic(default_toplevel, ".."+sec->id );
}
@ -202,22 +166,33 @@ const topic *help_menu::chosen_topic()
void help_menu::display_visible_items()
{
std::vector<std::string> menu_items;
std::vector<gui::indented_menu_item> menu_items;
std::optional<std::size_t> selected;
for(std::vector<visible_item>::const_iterator items_it = visible_items_.begin(),
end = visible_items_.end(); items_it != end; ++items_it) {
std::string to_show = items_it->visible_string;
if (selected_item_ == *items_it)
to_show = std::string("*") + to_show;
menu_items.push_back(to_show);
selected = menu_items.size();
menu_items.push_back(items_it->get_menu_item(*this));
}
set_items(menu_items, false, true);
set_items(menu_items, selected);
}
help_menu::visible_item::visible_item(const section *_sec, const std::string &vis_string, int lev) :
t(nullptr), sec(_sec), visible_string(vis_string), level(lev) {}
help_menu::visible_item::visible_item(const section *_sec, int lev) :
t(nullptr), sec(_sec), level(lev) {}
help_menu::visible_item::visible_item(const topic *_t, const std::string &vis_string, int lev) :
t(_t), sec(nullptr), visible_string(vis_string), level(lev) {}
help_menu::visible_item::visible_item(const topic *_t, int lev) :
t(_t), sec(nullptr), level(lev) {}
gui::indented_menu_item help_menu::visible_item::get_menu_item(const help_menu& parent) const
{
if(sec) {
const auto& img = parent.expanded(*sec) ? open_section_img : closed_section_img;
return {level, img, sec->title};
}
// As sec was a nullptr, this must have a non-null topic
return {level, topic_img, t->title};
}
bool help_menu::visible_item::operator==(const section &_sec) const
{

View file

@ -51,15 +51,16 @@ public:
private:
/** Information about an item that is visible in the menu. */
struct visible_item {
visible_item(const section *_sec, const std::string &visible_string, int level);
visible_item(const topic *_t, const std::string &visible_string, int level);
visible_item(const section *_sec, int level);
visible_item(const topic *_t, int level);
// Invariant, one if these should be nullptr. The constructors
// enforce it.
const topic *t;
const section *sec;
std::string visible_string;
gui::indented_menu_item get_menu_item(const help_menu& parent) const;
/** Indentation level, always one more than the parent section. */
int level;
bool operator==(const visible_item &vis_item) const;
bool operator==(const visible_item &sec) const;
bool operator==(const section &sec) const;
bool operator==(const topic &t) const;
};
@ -68,7 +69,7 @@ private:
void update_visible_items(const section &top_level, unsigned starting_level=0);
/** Return true if the section is expanded. */
bool expanded(const section &sec);
bool expanded(const section &sec) const;
/** Mark a section as expanded. Do not update the visible items or anything. */
void expand(const section &sec);
@ -84,10 +85,10 @@ private:
* menu-string at the specified level.
*/
std::string indent_list(const std::string &icon, const unsigned level);
/** Return the string to use as the menu-string for sections at the specified level. */
std::string get_string_to_show(const section &sec, const unsigned level);
/** Return the string to use as the menu-string for topics at the specified level. */
std::string get_string_to_show(const topic &topic, const unsigned level);
/** Return the data to use with the superclass's set_items() for sections at the specified level. */
gui::indented_menu_item get_item_to_show(const section &sec, const unsigned level);
/** Return the data to use with the superclass's set_items() for topics at the specified level. */
gui::indented_menu_item get_item_to_show(const topic &topic, const unsigned level);
/** Draw the currently visible items. */
void display_visible_items();

View file

@ -33,11 +33,18 @@
#include <numeric>
namespace {
/**
* For converting indented_menu_item.indent_level into a width in pixels.
* The text size might change, so instead of caching a value pango_line_size
* is called repeatedly with this as an argument.
*/
const std::string indent_string{" "};
};
namespace gui {
menu::menu(const std::vector<std::string>& items,
bool click_selects, int max_height, int max_width,
style *menu_style, const bool auto_join)
menu::menu(bool click_selects, int max_height, int max_width, style *menu_style, const bool auto_join)
: scrollarea(auto_join), silent_(false),
max_height_(max_height), max_width_(max_width),
max_items_(-1), item_height_(-1),
@ -50,35 +57,19 @@ menu::menu(const std::vector<std::string>& items,
{
style_ = (menu_style) ? menu_style : &default_style;
style_->init();
fill_items(items, true);
fill_items({});
}
menu::~menu()
{
}
void menu::fill_items(const std::vector<std::string>& items, bool strip_spaces)
void menu::fill_items(const std::vector<indented_menu_item>& items)
{
for(std::vector<std::string>::const_iterator itor = items.begin();
itor != items.end(); ++itor) {
for(const auto& itor : items) {
const std::size_t id = items_.size();
item_pos_.push_back(id);
const item new_item(utils::quoted_split(*itor, COLUMN_SEPARATOR, !strip_spaces),id);
items_.push_back(new_item);
//make sure there is always at least one item
if(items_.back().fields.empty()) {
items_.back().fields.push_back(" ");
}
//if the first character in an item is an asterisk,
//it means this item should be selected by default
std::string& first_item = items_.back().fields.front();
if(first_item.empty() == false && first_item[0] == DEFAULT_ITEM) {
selected_ = id;
first_item.erase(first_item.begin());
}
items_.emplace_back(itor, id);
}
update_size();
@ -103,8 +94,7 @@ void menu::update_size()
}
use_ellipsis_ = false;
const std::vector<int>& widths = column_widths();
int w = std::accumulate(widths.begin(), widths.end(), 0);
int w = widest_row_width();
if (items_.size() > max_items_onscreen())
w += scrollbar_width();
w = std::max(w, width());
@ -132,34 +122,32 @@ void menu::set_inner_location(const SDL_Rect& /*rect*/)
update_scrollbar_grip_height();
}
void menu::set_items(const std::vector<std::string>& items, bool strip_spaces, bool keep_viewport)
void menu::set_items(const std::vector<indented_menu_item>& items, std::optional<std::size_t> selected)
{
const bool scrolled_to_max = (has_scrollbar() && get_position() == get_max_position());
items_.clear();
item_pos_.clear();
itemRects_.clear();
column_widths_.clear();
widest_row_width_.reset();
//undrawn_items_.clear();
max_items_ = -1; // Force recalculation of the max items.
item_height_ = -1; // Force recalculation of the item height.
if (!keep_viewport || selected_ >= items.size()) {
if(selected) {
selected_ = *selected;
} else {
selected_ = 0;
}
fill_items(items, strip_spaces);
if(!keep_viewport) {
set_position(0);
} else if(scrolled_to_max) {
fill_items(items);
if(scrolled_to_max) {
set_position(get_max_position());
}
update_scrollbar_grip_height();
if(!keep_viewport) {
adjust_viewport_to_selection();
}
adjust_viewport_to_selection();
queue_redraw();
}
@ -175,7 +163,7 @@ void menu::set_max_width(const int new_max_width)
{
max_width_ = new_max_width;
itemRects_.clear();
column_widths_.clear();
widest_row_width_.reset();
update_size();
}
@ -194,6 +182,9 @@ std::size_t menu::max_items_onscreen() const
std::vector<int> heights;
std::size_t n;
for(n = 0; n != items_.size(); ++n) {
// The for loop, sort and sum around this are unnecessary, because
// get_item_height has ignored its argument since Wesnoth 0.6.99.1.
// It caches and returns the height of the tallest item.
heights.push_back(get_item_height(n));
}
@ -448,34 +439,30 @@ void menu::scroll(unsigned int)
queue_redraw();
}
SDL_Rect menu::style::item_size(const std::string& item) const {
SDL_Rect menu::style::item_size(const indented_menu_item& imi) const {
SDL_Rect res {0,0,0,0};
std::vector<std::string> img_text_items = utils::split(item, IMG_TEXT_SEPARATOR, utils::REMOVE_EMPTY);
for (std::vector<std::string>::const_iterator it = img_text_items.begin();
it != img_text_items.end(); ++it) {
if (res.w > 0 || res.h > 0) {
// Not the first item, add the spacing.
res.w += 5;
}
const std::string str = *it;
if (!str.empty() && str[0] == IMAGE_PREFIX) {
const std::string image_name(str.begin()+1,str.end());
const point image_size = image::get_size(image_name);
if (image_size.x && image_size.y) {
int w = image_size.x;
int h = image_size.y;
res.w += w;
res.h = std::max<int>(h, res.h);
}
}
else {
const SDL_Rect area {0,0,10000,10000};
const SDL_Rect font_size =
font::pango_draw_text(false, area, get_font_size(),
font::NORMAL_COLOR, str, 0, 0);
res.w += font_size.w;
res.h = std::max<int>(font_size.h, res.h);
}
res.w = imi.indent_level * font::pango_line_size(indent_string, get_font_size()).first;
if (!imi.icon.empty()) {
// Not the first item, add the spacing.
res.w += 5;
const texture img = image::get_texture(imi.icon);
res.w += img.w();
res.h = std::max<int>(img.h(), res.h);
}
if (!imi.text.empty()) {
// Not the first item, add the spacing.
res.w += 5;
const SDL_Rect area {0,0,10000,10000};
const SDL_Rect font_size =
font::pango_draw_text(false, area, get_font_size(),
font::NORMAL_COLOR, imi.text, 0, 0);
res.w += font_size.w;
res.h = std::max<int>(font_size.h, res.h);
}
return res;
}
@ -518,103 +505,77 @@ void menu::style::draw_row(menu& menu_ref, const std::size_t row_index, const SD
menu_ref.draw_row(row_index, minirect, type);
}
void menu::column_widths_item(const std::vector<std::string>& row, std::vector<int>& widths) const
int menu::widest_row_width() const
{
for(std::size_t col = 0; col != row.size(); ++col) {
const SDL_Rect res = style_->item_size(row[col]);
std::size_t text_trailing_space = (item_ends_with_image(row[col])) ? 0 : style_->get_cell_padding();
if(col == widths.size()) {
widths.push_back(res.w + text_trailing_space);
} else if(static_cast<std::size_t>(res.w) > widths[col] - text_trailing_space) {
widths[col] = res.w + text_trailing_space;
}
}
}
bool menu::item_ends_with_image(const std::string& item) const
{
std::string::size_type pos = item.find_last_of(IMG_TEXT_SEPARATOR);
pos = (pos == std::string::npos) ? 0 : pos+1;
return(item.size() > pos && item.at(pos) == IMAGE_PREFIX);
}
const std::vector<int>& menu::column_widths() const
{
if(column_widths_.empty()) {
for(std::size_t row = 0; row != items_.size(); ++row) {
column_widths_item(items_[row].fields,column_widths_);
if(!widest_row_width_) {
int widest = 0;
for(const auto& row : items_) {
const SDL_Rect size = style_->item_size(row.fields);
widest = std::max(widest, size.w);
}
// Assume there's text at the end of the item, and add padding accordingly.
widest_row_width_ = static_cast<int>(widest + style_->get_cell_padding());
}
return column_widths_;
return *widest_row_width_;
}
bool menu::hit_on_indent_or_icon(std::size_t row_index, int x) const
{
if(row_index >= items_.size()) {
return false;
}
// The virtual method item_size() is overloaded by imgsel_style::item_size(),
// which adds borders on both sides. Call it twice and remove one side's padding.
const auto& imi = items_[row_index].fields;
int width_used_so_far = style_->item_size({imi.indent_level, imi.icon, ""}).w;
width_used_so_far -= style_->item_size({0, "", ""}).w / 2;
const SDL_Rect& loc = inner_location();
if (current_language_rtl()) {
// inner_location() already takes account of the scrollbar width
return x > loc.x + loc.w - width_used_so_far;
}
return x < loc.x + width_used_so_far;
}
void menu::draw_row(const std::size_t row_index, const SDL_Rect& loc, ROW_TYPE)
{
//called from style, draws one row's contents in a generic and adaptable way
const std::vector<std::string>& row = items_[row_index].fields;
const auto& imi = items_[row_index].fields;
rect area = video::game_canvas();
rect column = inner_location();
const std::vector<int>& widths = column_widths();
bool lang_rtl = current_language_rtl();
int dir = (lang_rtl) ? -1 : 1;
int xpos = loc.x;
if(lang_rtl) {
xpos += loc.w;
// There's nothing to draw for the indent, just mark the space as used
int width_used_so_far = imi.indent_level * font::pango_line_size(indent_string, style_->get_font_size()).first;
if (!imi.icon.empty()) {
const texture img = image::get_texture(imi.icon);
int img_w = img.w();
int img_h = img.h();
const int remaining_width = max_width_ < 0 ? area.w : std::min<int>(max_width_, loc.w - width_used_so_far);
if(img && img_w <= remaining_width && loc.y + img_h < area.h) {
const std::size_t y = loc.y + (loc.h - img_h)/2;
const std::size_t x = loc.x + (lang_rtl ? loc.w - width_used_so_far - img_w : width_used_so_far);
draw::blit(img, {int(x), int(y), img_w, img_h});
// If there wasn't space for the icon, it doesn't get drawn, nor does the width get used.
// If it is drawn, add 5 pixels of padding.
width_used_so_far += img_w + 5;
}
}
for(std::size_t i = 0; i != row.size(); ++i) {
if(lang_rtl) {
xpos -= widths[i];
}
const int last_x = xpos;
column.w = widths[i];
std::string str = row[i];
std::vector<std::string> img_text_items = utils::split(str, IMG_TEXT_SEPARATOR, utils::REMOVE_EMPTY);
for (std::vector<std::string>::const_iterator it = img_text_items.begin();
it != img_text_items.end(); ++it) {
str = *it;
if (!str.empty() && str[0] == IMAGE_PREFIX) {
const std::string image_name(str.begin()+1,str.end());
const texture img = image::get_texture(image_name);
int img_w = img.w();
int img_h = img.h();
const int remaining_width = max_width_ < 0 ? area.w :
std::min<int>(max_width_, ((lang_rtl)? xpos - loc.x : loc.x + loc.w - xpos));
if(img && img_w <= remaining_width
&& loc.y + img_h < area.h) {
const std::size_t y = loc.y + (loc.h - img_h)/2;
const std::size_t w = img_w + 5;
const std::size_t x = xpos + ((lang_rtl) ? widths[i] - w : 0);
draw::blit(img, {int(x), int(y), img_w, img_h});
if(!lang_rtl)
xpos += w;
column.w -= w;
}
} else {
column.x = xpos;
const auto text_size = font::pango_line_size(str, style_->get_font_size());
const std::size_t y = loc.y + (loc.h - text_size.second)/2;
rect text_loc = column;
text_loc.w = loc.w - (xpos - loc.x) - 2 * style_->get_thickness();
text_loc.h = text_size.second;
font::pango_draw_text(true, text_loc, style_->get_font_size(), font::NORMAL_COLOR, str,
xpos, y);
xpos += dir * (text_size.first + 5);
}
}
if(lang_rtl)
xpos = last_x;
else
xpos = last_x + widths[i];
// Expected to be non-empty, but I guess a unit type could have a blank name
if (!imi.text.empty()) {
const auto text_size = font::pango_line_size(imi.text, style_->get_font_size());
const std::size_t x = loc.x + (lang_rtl ? std::max(0, loc.w - width_used_so_far - text_size.first) : width_used_so_far);
const std::size_t y = loc.y + (loc.h - text_size.second)/2;
rect text_loc = loc;
text_loc.w = loc.w - (width_used_so_far) - 2 * style_->get_thickness();
text_loc.h = text_size.second;
font::pango_draw_text(true, text_loc, style_->get_font_size(), font::NORMAL_COLOR, imi.text,
x, y);
}
}
@ -640,18 +601,6 @@ int menu::hit(int x, int y) const
return -1;
}
int menu::hit_column(int x) const
{
const std::vector<int>& widths = column_widths();
int j = -1, j_end = widths.size();
for(x -= location().x; x >= 0; x -= widths[j]) {
if(++j == j_end) {
return -1;
}
}
return j;
}
SDL_Rect menu::get_item_rect(int item) const
{
return get_item_rect_internal(item_pos_[item]);
@ -701,25 +650,22 @@ SDL_Rect menu::get_item_rect_internal(std::size_t item) const
return res;
}
std::size_t menu::get_item_height_internal(const std::vector<std::string>& item) const
std::size_t menu::get_item_height_internal(const indented_menu_item& imi) const
{
std::size_t res = 0;
for(std::vector<std::string>::const_iterator i = item.begin(); i != item.end(); ++i) {
SDL_Rect rect = style_->item_size(*i);
res = std::max<int>(rect.h,res);
}
return res;
return style_->item_size(imi).h;
}
std::size_t menu::get_item_height(int) const
{
// This could probably return the height of a single line of Pango text, plus
// padding. However, keeping compatibility with the current numbers means
// less unknowns about what the numbers should actually be.
if(item_height_ != -1)
return std::size_t(item_height_);
std::size_t max_height = 0;
for(std::size_t n = 0; n != items_.size(); ++n) {
max_height = std::max<int>(max_height,get_item_height_internal(items_[n].fields));
for(const auto& item : items_) {
max_height = std::max<int>(max_height,get_item_height_internal(item.fields));
}
return item_height_ = max_height;

View file

@ -15,6 +15,7 @@
#pragma once
#include <optional>
#include <map>
#include <set>
@ -28,6 +29,25 @@ namespace image{
namespace gui {
/**
* The only kind of row still supported by the menu class.
*
* If comparing to 1.17.24 or before, these three items were held as a single string, thus a single member
* of the "fields" and a single "column" of the multi-column support.
*/
struct indented_menu_item
{
/** An amount of blank space at the start of the row, measured in tab-stops (so 1 is around 4 en-widths) */
int indent_level;
/** If non-empty, a picture to display before the text */
std::string icon;
std::string text;
};
/**
* Superclass of the help_menu, which displays the left-hand pane of the GUI1 help browser.
* Historically a more generic class, but now only used for that singular purpose.
*/
class menu : public scrollarea
{
public:
@ -41,7 +61,7 @@ public:
virtual ~style();
virtual void init() {}
virtual SDL_Rect item_size(const std::string& item) const;
virtual SDL_Rect item_size(const indented_menu_item& imi) const;
virtual void draw_row_bg(menu& menu_ref, const std::size_t row_index, const SDL_Rect& rect, ROW_TYPE type);
virtual void draw_row(menu& menu_ref, const std::size_t row_index, const SDL_Rect& rect, ROW_TYPE type);
std::size_t get_font_size() const;
@ -66,7 +86,7 @@ public:
double normal_alpha, double selected_alpha);
virtual ~imgsel_style();
virtual SDL_Rect item_size(const std::string& item) const;
virtual SDL_Rect item_size(const indented_menu_item& imi) const;
virtual void draw_row_bg(menu& menu_ref, const std::size_t row_index, const SDL_Rect& rect, ROW_TYPE type);
virtual void draw_row(menu& menu_ref, const std::size_t row_index, const SDL_Rect& rect, ROW_TYPE type);
@ -94,18 +114,15 @@ public:
struct item
{
item() : fields(), id(0)
{}
item(const std::vector<std::string>& fields, std::size_t id)
item(const indented_menu_item& fields, std::size_t id)
: fields(fields), id(id)
{}
std::vector<std::string> fields;
indented_menu_item fields;
std::size_t id;
};
menu(const std::vector<std::string>& items,
menu(
bool click_selects=false, int max_height=-1, int max_width=-1,
style *menu_style=nullptr, const bool auto_join=true);
@ -119,13 +136,11 @@ public:
void reset_selection();
/**
* Set new items to show and redraw/recalculate everything. If
* strip_spaces is false, spaces will remain at the item edges. If
* keep_viewport is true, the menu tries to keep the selection at
* the same position as it were before the items were set.
* Set new items to show and redraw/recalculate everything. The menu tries
* to keep the selection at the same position as it were before the items
* were set.
*/
virtual void set_items(const std::vector<std::string>& items, bool strip_spaces=true,
bool keep_viewport=false);
virtual void set_items(const std::vector<indented_menu_item>& items, std::optional<std::size_t> selected);
/**
* Set a new max height for this menu. Note that this does not take
@ -151,12 +166,11 @@ public:
void scroll(unsigned int pos) override;
protected:
bool item_ends_with_image(const std::string& item) const;
virtual void handle_event(const SDL_Event& event) override;
void set_inner_location(const SDL_Rect& rect) override;
bool requires_event_focus(const SDL_Event *event=nullptr) const override;
const std::vector<int>& column_widths() const;
int widest_row_width() const;
virtual void draw_row(const std::size_t row_index, const SDL_Rect& rect, ROW_TYPE type);
style *style_;
@ -164,7 +178,16 @@ protected:
int hit(int x, int y) const;
int hit_column(int x) const;
/**
* Returns true if a mouse-click with the given x-coordinate, and an
* appropriate y-coordinate would lie within the indent or icon part of
* the given row.
*
* The unusual combination of arguments fit with this being called when
* handling a mouse event, where we already know which row was selected,
* and are just inquiring a bit more about the details of that row.
*/
bool hit_on_indent_or_icon(std::size_t row_index, int x) const;
void invalidate_row(std::size_t id);
void invalidate_row_pos(std::size_t pos);
@ -181,7 +204,10 @@ private:
std::vector<item> items_;
std::vector<std::size_t> item_pos_;
mutable std::vector<int> column_widths_;
/**
* Cached return value of widest_row_width(), calculated on demand when calling that function.
*/
mutable std::optional<int> widest_row_width_;
std::size_t selected_;
bool click_selects_;
@ -193,16 +219,13 @@ private:
bool double_clicked_;
void column_widths_item(const std::vector<std::string>& row, std::vector<int>& widths) const;
void clear_item(int item);
void draw_contents() override;
mutable std::map<int,SDL_Rect> itemRects_;
SDL_Rect get_item_rect(int item) const;
SDL_Rect get_item_rect_internal(std::size_t pos) const;
std::size_t get_item_height_internal(const std::vector<std::string>& item) const;
std::size_t get_item_height_internal(const indented_menu_item& imi) const;
std::size_t get_item_height(int item) const;
int items_start() const;
@ -226,10 +249,9 @@ private:
bool use_ellipsis_;
/**
* Set new items to show. If strip_spaces is false, spaces will
* remain at the item edges.
* Set new items to show.
*/
void fill_items(const std::vector<std::string>& items, bool strip_spaces);
void fill_items(const std::vector<indented_menu_item>& imi);
void update_size();
enum SELECTION_MOVE_VIEWPORT { MOVE_VIEWPORT, NO_MOVE_VIEWPORT };

View file

@ -212,9 +212,9 @@ void menu::imgsel_style::draw_row(menu& menu_ref, const std::size_t row_index, c
}
}
SDL_Rect menu::imgsel_style::item_size(const std::string& item) const
SDL_Rect menu::imgsel_style::item_size(const indented_menu_item& imi) const
{
SDL_Rect bounds = style::item_size(item);
SDL_Rect bounds = style::item_size(imi);
bounds.w += 2 * thickness_;
bounds.h += 2 * thickness_ + 4;