widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #09898
[Merge] lp:~widelands-dev/widelands/bug-536489-pictorial-dropdown into lp:widelands
GunChleoc has proposed merging lp:~widelands-dev/widelands/bug-536489-pictorial-dropdown into lp:widelands.
Commit message:
Implemented a pictorial dropdown. Used the tribe/shared-in button in the multiplayer setup as a test case. Added enum class ButtonDisableStyle to UI::Button for the player color in disabled shared-in dropdowns.
Requested reviews:
Widelands Developers (widelands-dev)
Related bugs:
Bug #536489 in widelands: "Dropdown button"
https://bugs.launchpad.net/widelands/+bug/536489
For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/bug-536489-pictorial-dropdown/+merge/319023
Pictorial dropdown. I only replaced one button so far, because the diff is already big enough.
--
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/bug-536489-pictorial-dropdown into lp:widelands.
=== modified file 'src/network/network_player_settings_backend.cc'
--- src/network/network_player_settings_backend.cc 2017-01-25 18:55:59 +0000
+++ src/network/network_player_settings_backend.cc 2017-03-06 08:30:52 +0000
@@ -34,62 +34,59 @@
s->next_player_state(id);
}
-/// Toggle through the tribes + handle shared in players
-void NetworkPlayerSettingsBackend::toggle_tribe(uint8_t id) {
+void NetworkPlayerSettingsBackend::set_tribe(uint8_t id, const std::string& tribename) {
const GameSettings& settings = s->settings();
- if (id >= settings.players.size())
+ if (id >= settings.players.size() || tribename.empty())
return;
if (settings.players.at(id).state != PlayerSettings::stateShared) {
- const PlayerSettings& player = settings.players.at(id);
- const std::string& currenttribe = player.tribe;
- std::string nexttribe = settings.tribes.at(0).name;
- uint32_t num_tribes = settings.tribes.size();
- bool random_tribe = false;
-
- if (player.random_tribe) {
- nexttribe = settings.tribes.at(0).name;
- } else if (player.tribe == settings.tribes.at(num_tribes - 1).name) {
- nexttribe = "Random";
- random_tribe = true;
- } else {
- for (uint32_t i = 0; i < num_tribes - 1; ++i) {
- if (settings.tribes[i].name == currenttribe) {
- nexttribe = settings.tribes.at(i + 1).name;
- break;
- }
- }
- }
-
- s->set_player_tribe(id, nexttribe, random_tribe);
+ s->set_player_tribe(id, tribename, tribename == "random");
+ }
+}
+
+/// Set the shared in player for the given id
+void NetworkPlayerSettingsBackend::set_shared_in(uint8_t id, uint8_t shared_in) {
+ const GameSettings& settings = s->settings();
+ if (id > settings.players.size() || shared_in > settings.players.size())
+ return;
+ if (settings.players.at(id).state == PlayerSettings::stateShared) {
+ s->set_player_shared(id, shared_in);
+ }
+}
+
+/// Toggle through shared in players
+void NetworkPlayerSettingsBackend::toggle_shared_in(uint8_t id) {
+ const GameSettings& settings = s->settings();
+
+ if (id >= settings.players.size() ||
+ settings.players.at(id).state != PlayerSettings::stateShared)
+ return;
+
+ uint8_t sharedplr = settings.players.at(id).shared_in;
+ for (; sharedplr < settings.players.size(); ++sharedplr) {
+ if (settings.players.at(sharedplr).state != PlayerSettings::stateClosed &&
+ settings.players.at(sharedplr).state != PlayerSettings::stateShared)
+ break;
+ }
+ if (sharedplr < settings.players.size()) {
+ // We have already found the next player
+ set_shared_in(id, sharedplr + 1);
+ return;
+ }
+ sharedplr = 0;
+ for (; sharedplr < settings.players.at(id).shared_in; ++sharedplr) {
+ if (settings.players.at(sharedplr).state != PlayerSettings::stateClosed &&
+ settings.players.at(sharedplr).state != PlayerSettings::stateShared)
+ break;
+ }
+ if (sharedplr < settings.players.at(id).shared_in) {
+ // We have found the next player
+ set_shared_in(id, sharedplr + 1);
+ return;
} else {
- // This button is temporarily used to select the player that uses this starting position
- uint8_t sharedplr = settings.players.at(id).shared_in;
- for (; sharedplr < settings.players.size(); ++sharedplr) {
- if (settings.players.at(sharedplr).state != PlayerSettings::stateClosed &&
- settings.players.at(sharedplr).state != PlayerSettings::stateShared)
- break;
- }
- if (sharedplr < settings.players.size()) {
- // We have already found the next player
- s->set_player_shared(id, sharedplr + 1);
- return;
- }
- sharedplr = 0;
- for (; sharedplr < settings.players.at(id).shared_in; ++sharedplr) {
- if (settings.players.at(sharedplr).state != PlayerSettings::stateClosed &&
- settings.players.at(sharedplr).state != PlayerSettings::stateShared)
- break;
- }
- if (sharedplr < settings.players.at(id).shared_in) {
- // We have found the next player
- s->set_player_shared(id, sharedplr + 1);
- return;
- } else {
- // No fitting player found
- return toggle_type(id);
- }
+ // No fitting player found
+ return toggle_type(id);
}
}
@@ -141,10 +138,10 @@
if (player.state == PlayerSettings::stateShared) {
// ensure that the shared_in player is able to use this starting position
if (player.shared_in > settings.players.size())
- toggle_tribe(id);
+ toggle_shared_in(id);
if (settings.players.at(player.shared_in - 1).state == PlayerSettings::stateClosed ||
settings.players.at(player.shared_in - 1).state == PlayerSettings::stateShared)
- toggle_tribe(id);
+ toggle_shared_in(id);
if (shared_in_tribe[id] != settings.players.at(player.shared_in - 1).tribe) {
s->set_player_tribe(id, settings.players.at(player.shared_in - 1).tribe,
=== modified file 'src/network/network_player_settings_backend.h'
--- src/network/network_player_settings_backend.h 2017-01-25 18:55:59 +0000
+++ src/network/network_player_settings_backend.h 2017-03-06 08:30:52 +0000
@@ -31,13 +31,22 @@
}
void toggle_type(uint8_t id);
- void toggle_tribe(uint8_t id);
+ void set_shared_in(uint8_t id, uint8_t shared_in);
+ void set_tribe(uint8_t id, const std::string& tribename);
+ void set_block_tribe_selection(bool blocked) {
+ tribe_selection_blocked = blocked;
+ }
+
void toggle_init(uint8_t id);
void toggle_team(uint8_t id);
void refresh(uint8_t id);
GameSettingsProvider* const s;
std::string shared_in_tribe[kMaxPlayers];
+ bool tribe_selection_blocked;
+
+private:
+ void toggle_shared_in(uint8_t id);
};
#endif // end of include guard: WL_NETWORK_NETWORK_PLAYER_SETTINGS_BACKEND_H
=== modified file 'src/ui_basic/button.cc'
--- src/ui_basic/button.cc 2017-02-12 09:10:57 +0000
+++ src/ui_basic/button.cc 2017-03-06 08:30:52 +0000
@@ -49,6 +49,7 @@
pressed_(false),
enabled_(true),
style_(init_style),
+ disable_style_(ButtonDisableStyle::kMonochrome),
repeating_(false),
image_mode_(UI::Button::ImageMode::kShrink),
time_nextact_(0),
@@ -85,6 +86,7 @@
pressed_(false),
enabled_(true),
style_(init_style),
+ disable_style_(ButtonDisableStyle::kMonochrome),
repeating_(false),
image_mode_(mode),
time_nextact_(0),
@@ -149,6 +151,14 @@
* Redraw the button
*/
void Button::draw(RenderTarget& dst) {
+ const bool is_flat = (enabled_ && style_ == Style::kFlat) ||
+ (!enabled_ && static_cast<int>(disable_style_ & ButtonDisableStyle::kFlat));
+ const bool is_permpressed =
+ (enabled_ && style_ == Style::kPermpressed) ||
+ (!enabled_ && static_cast<int>(disable_style_ & ButtonDisableStyle::kPermpressed));
+ const bool is_monochrome =
+ !enabled_ && static_cast<int>(disable_style_ & ButtonDisableStyle::kMonochrome);
+
// Draw the background
if (pic_background_) {
dst.fill_rect(Rectf(0.f, 0.f, get_w(), get_h()), RGBAColor(0, 0, 0, 255));
@@ -156,13 +166,13 @@
Recti(Vector2i(0, 0), get_w(), get_h()), pic_background_, Vector2i(get_x(), get_y()));
}
- if (enabled_ && highlighted_ && style_ != Style::kFlat)
+ if (is_flat && highlighted_)
dst.brighten_rect(Rectf(0.f, 0.f, get_w(), get_h()), MOUSE_OVER_BRIGHT_FACTOR);
// If we've got a picture, draw it centered
if (pic_custom_) {
if (image_mode_ == UI::Button::ImageMode::kUnscaled) {
- if (enabled_) {
+ if (!is_monochrome) {
dst.blit(Vector2f((get_w() - static_cast<int32_t>(pic_custom_->width())) / 2.f,
(get_h() - static_cast<int32_t>(pic_custom_->height())) / 2.f),
pic_custom_);
@@ -181,7 +191,7 @@
int blit_width = image_scale * pic_custom_->width();
int blit_height = image_scale * pic_custom_->height();
- if (enabled_) {
+ if (!is_monochrome) {
dst.blitrect_scale(Rectf((get_w() - blit_width) / 2.f, (get_h() - blit_height) / 2.f,
blit_width, blit_height),
pic_custom_,
@@ -200,7 +210,7 @@
// Otherwise draw title string centered
const Image* entry_text_im =
autofit_ui_text(title_, get_inner_w() - 2 * kButtonImageMargin,
- enabled_ ? UI_FONT_CLR_FG : UI_FONT_CLR_DISABLED);
+ is_monochrome ? UI_FONT_CLR_DISABLED : UI_FONT_CLR_FG);
// Blit on pixel boundary (not float), so that the text is blitted pixel perfect.
dst.blit(
Vector2f((get_w() - entry_text_im->width()) / 2, (get_h() - entry_text_im->height()) / 2),
@@ -213,11 +223,11 @@
// stays pressed when it is pressed once
RGBAColor black(0, 0, 0, 255);
- if (style_ != Style::kFlat) {
+ if (!is_flat) {
assert(2 <= get_w());
assert(2 <= get_h());
// Button is a normal one, not flat. We invert the behaviour for kPermpressed.
- if ((style_ == Style::kPermpressed) == (pressed_ && highlighted_)) {
+ if (is_permpressed == (pressed_ && highlighted_)) {
// top edge
dst.brighten_rect(Rectf(0.f, 0.f, get_w(), 2.f), BUTTON_EDGE_BRIGHT_FACTOR);
// left edge
@@ -339,6 +349,10 @@
style_ = input_style;
}
+void Button::set_disable_style(UI::ButtonDisableStyle input_style) {
+ disable_style_ = input_style;
+}
+
void Button::set_perm_pressed(bool pressed) {
set_style(pressed ? UI::Button::Style::kPermpressed : UI::Button::Style::kRaised);
}
=== modified file 'src/ui_basic/button.h'
--- src/ui_basic/button.h 2017-01-25 18:55:59 +0000
+++ src/ui_basic/button.h 2017-03-06 08:30:52 +0000
@@ -32,6 +32,18 @@
struct Font;
+enum class ButtonDisableStyle {
+ kMonochrome = 2, // Greyed out. Can be combined with the other 2 styles.
+ kPermpressed = 4, // Button will appear pressed.
+ kFlat = 8, // Button will appear flat.
+};
+inline ButtonDisableStyle operator&(ButtonDisableStyle a, ButtonDisableStyle b) {
+ return static_cast<ButtonDisableStyle>(static_cast<int>(a) & static_cast<int>(b));
+}
+inline ButtonDisableStyle operator|(ButtonDisableStyle a, ButtonDisableStyle b) {
+ return static_cast<ButtonDisableStyle>(static_cast<int>(a) | static_cast<int>(b));
+}
+
/// This is simply a button. Override void clicked() to react to the click.
/// This is all that is needed in most cases, but if there is a need to give a
/// callback function to the button, there are some templates for that below.
@@ -105,6 +117,9 @@
return style_;
}
+ /// Sets the visual style of the disabled button
+ void set_disable_style(UI::ButtonDisableStyle input_style);
+
/// Convenience function. If 'pressed', sets the style to kPermpressed, otherwise to kRaised.
void set_perm_pressed(bool pressed);
@@ -123,6 +138,7 @@
bool pressed_; // mouse is clicked over the button
bool enabled_;
UI::Button::Style style_;
+ UI::ButtonDisableStyle disable_style_;
bool repeating_;
const UI::Button::ImageMode image_mode_;
=== modified file 'src/ui_basic/dropdown.cc'
--- src/ui_basic/dropdown.cc 2017-02-25 11:17:28 +0000
+++ src/ui_basic/dropdown.cc 2017-03-06 08:30:52 +0000
@@ -24,16 +24,18 @@
#include <boost/format.hpp>
#include "base/i18n.h"
+#include "base/macros.h"
#include "graphic/align.h"
#include "graphic/font_handler1.h"
#include "graphic/rendertarget.h"
#include "ui_basic/mouse_constants.h"
+#include "ui_basic/tabpanel.h"
namespace {
-int base_height() {
+int base_height(int button_dimension) {
return std::max(
- 24,
+ button_dimension,
UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height() + 2);
}
@@ -46,63 +48,104 @@
int32_t y,
uint32_t w,
uint32_t h,
+ int button_dimension,
const std::string& label,
+ const DropdownType type,
const Image* background,
const Image* button_background)
: UI::Panel(parent,
x,
y,
- w,
- base_height()), // Height only to fit the button, so we can use this in Box layout.
+ type == DropdownType::kTextual ? w : button_dimension,
+ // Height only to fit the button, so we can use this in Box layout.
+ base_height(button_dimension)),
max_list_height_(h - 2 * get_h()),
+ list_width_(w),
+ button_dimension_(button_dimension),
mouse_tolerance_(50),
button_box_(this, 0, 0, UI::Box::Horizontal, w, h),
- push_button_(&button_box_,
- "dropdown_select",
- 0,
- 0,
- 24,
- get_h(),
- button_background,
- g_gr->images().get("images/ui_basic/scrollbar_down.png"),
- pgettext("dropdown", "Select Item")),
- display_button_(&button_box_, "dropdown_label", 0, 0, w - 24, get_h(), background, label),
- // Hook into parent so we can drop down outside the panel
- list_(parent, x, y + get_h(), w, 0, button_background, ListselectLayout::kDropdown),
- label_(label) {
- list_.set_visible(false);
- list_.set_background(background);
- display_button_.set_perm_pressed(true);
+ push_button_(type == DropdownType::kTextual ?
+ new UI::Button(&button_box_,
+ "dropdown_select",
+ 0,
+ 0,
+ button_dimension,
+ get_h(),
+ button_background,
+ g_gr->images().get("images/ui_basic/scrollbar_down.png"),
+ pgettext("dropdown", "Select Item")) :
+ nullptr),
+ display_button_(&button_box_,
+ "dropdown_label",
+ 0,
+ 0,
+ type == DropdownType::kTextual ? w - button_dimension : button_dimension,
+ get_h(),
+ background,
+ label),
+ label_(label),
+ type_(type),
+ is_enabled_(true) {
+ assert(max_list_height_ > 0);
+ // Hook into highest parent that we can get so that we can drop down outside the panel.
+ // Positioning breaks down with TabPanels, so we exclude them.
+ while (parent->get_parent() && !is_a(UI::TabPanel, parent->get_parent())) {
+ parent = parent->get_parent();
+ }
+ list_ = new UI::Listselect<uintptr_t>(
+ parent, 0, 0, w, 0, button_background, ListselectLayout::kDropdown);
+
+ list_->set_visible(false);
+ list_->set_background(background);
+
button_box_.add(&display_button_);
- button_box_.add(&push_button_);
+ display_button_.sigclicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
+ if (push_button_ != nullptr) {
+ display_button_.set_perm_pressed(true);
+ button_box_.add(push_button_);
+ push_button_->sigclicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
+ }
button_box_.set_size(w, get_h());
-
- display_button_.sigclicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
- push_button_.sigclicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
- list_.clicked.connect(boost::bind(&BaseDropdown::set_value, this));
- list_.clicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
+ list_->clicked.connect(boost::bind(&BaseDropdown::set_value, this));
+ list_->clicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
set_can_focus(true);
+ set_value();
layout();
}
BaseDropdown::~BaseDropdown() {
- clear();
+ // The list will clear itself
}
void BaseDropdown::set_height(int height) {
- max_list_height_ = height - base_height();
+ max_list_height_ = height - base_height(button_dimension_);
layout();
}
void BaseDropdown::layout() {
- const int base_h = base_height();
- const int w = get_w();
+ const int base_h = base_height(button_dimension_);
+ const int w = type_ == DropdownType::kTextual ? get_w() : button_dimension_;
button_box_.set_size(w, base_h);
- display_button_.set_desired_size(w - 24, base_h);
+ display_button_.set_desired_size(
+ type_ == DropdownType::kTextual ? w - button_dimension_ : w, base_h);
int new_list_height =
- std::min(static_cast<int>(list_.size()) * list_.get_lineheight(), max_list_height_);
- list_.set_size(w, new_list_height);
+ std::min(static_cast<int>(list_->size()) * list_->get_lineheight(), max_list_height_);
+ list_->set_size(type_ == DropdownType::kTextual ? w : list_width_, new_list_height);
set_desired_size(w, base_h);
+
+ // Update list position.
+ // The list is hooked into the highest parent that we can get so that we can drop down outside
+ // the panel.
+ // Positioning breaks down with TabPanels, so we exclude them.
+ UI::Panel* parent = get_parent();
+ int new_list_y = get_y() + get_h() + parent->get_y();
+ int new_list_x = get_x() + parent->get_x();
+ while (parent->get_parent() && !is_a(UI::TabPanel, parent->get_parent())) {
+ parent = parent->get_parent();
+ new_list_y += parent->get_y();
+ new_list_x += parent->get_x();
+ }
+ list_->set_pos(Vector2i(new_list_x, new_list_y));
}
void BaseDropdown::add(const std::string& name,
@@ -110,7 +153,8 @@
const Image* pic,
const bool select_this,
const std::string& tooltip_text) {
- list_.add(name, value, pic, select_this, tooltip_text);
+ assert(pic != nullptr || type_ != DropdownType::kPictorial);
+ list_->add(name, value, pic, select_this, tooltip_text);
if (select_this) {
set_value();
}
@@ -118,16 +162,25 @@
}
bool BaseDropdown::has_selection() const {
- return list_.has_selection();
+ return list_->has_selection();
}
uint32_t BaseDropdown::get_selected() const {
- return list_.get_selected();
+ return list_->get_selected();
+}
+
+void BaseDropdown::select(uint32_t entry) {
+ assert(entry < list_->size());
+ list_->select(entry);
+ current_selection_ = list_->selection_index();
+ update();
}
void BaseDropdown::set_label(const std::string& text) {
label_ = text;
- display_button_.set_title(label_);
+ if (type_ == DropdownType::kTextual) {
+ display_button_.set_title(label_);
+ }
}
void BaseDropdown::set_tooltip(const std::string& text) {
@@ -136,68 +189,98 @@
}
void BaseDropdown::set_enabled(bool on) {
+ is_enabled_ = on;
set_can_focus(on);
- push_button_.set_enabled(on);
- push_button_.set_tooltip(on ? pgettext("dropdown", "Select Item") : "");
+ if (push_button_ != nullptr) {
+ push_button_->set_enabled(on);
+ push_button_->set_tooltip(on ? pgettext("dropdown", "Select Item") : "");
+ }
display_button_.set_enabled(on);
- list_.set_visible(false);
+ list_->set_visible(false);
+}
+
+void BaseDropdown::set_disable_style(UI::ButtonDisableStyle disable_style) {
+ display_button_.set_disable_style(disable_style);
+}
+
+bool BaseDropdown::is_expanded() const {
+ return list_->is_visible();
}
void BaseDropdown::set_pos(Vector2i point) {
UI::Panel::set_pos(point);
- list_.set_pos(Vector2i(point.x, point.y + get_h()));
+ list_->set_pos(Vector2i(point.x, point.y + get_h()));
}
void BaseDropdown::clear() {
- list_.clear();
- list_.set_size(list_.get_w(), 0);
+ list_->clear();
+ current_selection_ = list_->selection_index();
+ list_->set_size(list_->get_w(), 0);
+ list_->set_visible(false);
set_layout_toplevel(false);
}
void BaseDropdown::think() {
- if (list_.is_visible()) {
+ if (list_->is_visible()) {
// Autocollapse with a bit of tolerance for the mouse movement to make it less fiddly.
- if (!(has_focus() || list_.has_focus()) || is_mouse_away()) {
+ if (!(has_focus() || list_->has_focus()) || is_mouse_away()) {
toggle_list();
}
}
}
uint32_t BaseDropdown::size() const {
- return list_.size();
+ return list_->size();
}
-void BaseDropdown::set_value() {
- const std::string name = list_.has_selection() ? list_.get_selected_name() :
- /** TRANSLATORS: Selection in Dropdown menus. */
+void BaseDropdown::update() {
+ const std::string name = list_->has_selection() ?
+ list_->get_selected_name() :
+ /** TRANSLATORS: Selection in Dropdown menus. */
pgettext("dropdown", "Not Selected");
- if (label_.empty()) {
- display_button_.set_title(name);
+ if (type_ == DropdownType::kTextual) {
+ if (label_.empty()) {
+ display_button_.set_title(name);
+ } else {
+ /** TRANSLATORS: Label: Value. */
+ display_button_.set_title((boost::format(_("%1%: %2%")) % label_ % (name)).str());
+ }
+ display_button_.set_tooltip(list_->has_selection() ? list_->get_selected_tooltip() :
+ tooltip_);
} else {
- /** TRANSLATORS: Label: Value. */
- display_button_.set_title((boost::format(_("%1%: %2%")) % label_ % (name)).str());
+ display_button_.set_pic(list_->has_selection() ?
+ list_->get_selected_image() :
+ g_gr->images().get("images/ui_basic/different.png"));
+ display_button_.set_tooltip((boost::format(_("%1%: %2%")) % label_ % name).str());
}
- display_button_.set_tooltip(list_.has_selection() ? list_.get_selected_tooltip() : tooltip_);
+}
+
+void BaseDropdown::set_value() {
+ update();
selected();
- current_selection_ = list_.selection_index();
+ current_selection_ = list_->selection_index();
}
void BaseDropdown::toggle_list() {
- list_.set_visible(!list_.is_visible());
- if (list_.is_visible()) {
- list_.move_to_top();
+ if (!is_enabled_) {
+ list_->set_visible(false);
+ return;
+ }
+ list_->set_visible(!list_->is_visible());
+ if (list_->is_visible()) {
+ list_->move_to_top();
focus();
}
// Make sure that the list covers and deactivates the elements below it
- set_layout_toplevel(list_.is_visible());
+ set_layout_toplevel(list_->is_visible());
}
bool BaseDropdown::is_mouse_away() const {
return (get_mouse_position().x + mouse_tolerance_) < 0 ||
- get_mouse_position().x > (get_w() + mouse_tolerance_) ||
+ get_mouse_position().x > (list_->get_w() + mouse_tolerance_) ||
(get_mouse_position().y + mouse_tolerance_ / 2) < 0 ||
- get_mouse_position().y > (get_h() + list_.get_h() + mouse_tolerance_);
+ get_mouse_position().y > (get_h() + list_->get_h() + mouse_tolerance_);
}
bool BaseDropdown::handle_key(bool down, SDL_Keysym code) {
@@ -205,18 +288,18 @@
switch (code.sym) {
case SDLK_KP_ENTER:
case SDLK_RETURN:
- if (list_.is_visible()) {
+ if (list_->is_visible()) {
set_value();
}
case SDLK_ESCAPE:
- if (list_.is_visible()) {
- list_.select(current_selection_);
+ if (list_->is_visible()) {
+ list_->select(current_selection_);
toggle_list();
return true;
}
break;
case SDLK_DOWN:
- if (!list_.is_visible() && !is_mouse_away()) {
+ if (!list_->is_visible() && !is_mouse_away()) {
toggle_list();
return true;
}
@@ -225,8 +308,8 @@
break; // not handled
}
}
- if (list_.is_visible()) {
- return list_.handle_key(down, code);
+ if (list_->is_visible()) {
+ return list_->handle_key(down, code);
}
return false;
}
=== modified file 'src/ui_basic/dropdown.h'
--- src/ui_basic/dropdown.h 2017-01-25 18:55:59 +0000
+++ src/ui_basic/dropdown.h 2017-03-06 08:30:52 +0000
@@ -34,23 +34,30 @@
namespace UI {
+enum class DropdownType { kTextual, kPictorial };
+
/// Implementation for a dropdown menu that lets the user select a value.
class BaseDropdown : public Panel {
protected:
/// \param parent the parent panel
/// \param x the x-position within 'parent'
/// \param y the y-position within 'parent'
- /// \param w the dropdown's width
- /// \param h the maximum height for the dropdown list
+ /// \param list_w the dropdown's width
+ /// \param list_h the maximum height for the dropdown list
+ /// \param button_dimension the width of the push button in textual dropdowns. For pictorial
+ /// dropdowns, this both the width and the height of the button.
/// \param label a label to prefix to the selected entry on the display button.
+ /// \param type whether this is a textual or pictorial dropdown
/// \param background the background image for this dropdown
/// \param button_background the background image all buttons in this dropdown
BaseDropdown(Panel* parent,
int32_t x,
int32_t y,
- uint32_t w,
- uint32_t h,
+ uint32_t list_w,
+ uint32_t list_h,
+ int button_dimension,
const std::string& label,
+ const DropdownType type,
const Image* background,
const Image* button_background);
~BaseDropdown();
@@ -71,6 +78,22 @@
/// Enables/disables the dropdown selection.
void set_enabled(bool on);
+ /// Whether the dropdown selection is enabled.
+ bool is_enabled() const {
+ return is_enabled_;
+ }
+
+ /// Which visual style to use for disabled pictorial dropdowns.
+ void set_disable_style(UI::ButtonDisableStyle disable_style);
+
+ /// Whether the dropdown has no elements to select.
+ bool empty() {
+ return size() == 0;
+ }
+
+ /// Whether the dropdown has been opened by the user.
+ bool is_expanded() const;
+
/// Move the dropdown. The dropdown's position is relative to the parent in
/// pixels.
void set_pos(Vector2i point) override;
@@ -87,7 +110,7 @@
/// Add an element to the list
/// \param name the display name of the entry
/// \param value the index of the entry
- /// \param pic an image to illustrate the entry
+ /// \param pic an image to illustrate the entry. Can be nullptr for textual dropdowns.
/// \param select_this whether this element should be selected
/// \param tooltip_text a tooltip for this entry
void add(const std::string& name,
@@ -99,6 +122,9 @@
/// \return the index of the selected element
uint32_t get_selected() const;
+ /// Select the entry. Assumes that it exists. Does not trigger the 'selected' signal.
+ void select(uint32_t entry);
+
/// Removes all elements from the list.
void clear();
@@ -109,6 +135,9 @@
private:
void layout() override;
+ /// Updates the buttons
+ void update();
+
/// Updates the title and tooltip of the display button and triggers a 'selected' signal.
void set_value();
/// Toggles the dropdown list on and off.
@@ -118,14 +147,19 @@
bool is_mouse_away() const;
int max_list_height_;
+ int list_width_;
+ int button_dimension_;
const int mouse_tolerance_; // Allow mouse outside the panel a bit before autocollapse
UI::Box button_box_;
- UI::Button push_button_;
+ UI::Button* push_button_; // Only used in textual dropdowns
UI::Button display_button_;
- UI::Listselect<uintptr_t> list_;
+ // The list needs to be a pointer for destruction, because we hook into the highest parent that we can get.
+ UI::Listselect<uintptr_t>* list_;
std::string label_;
std::string tooltip_;
uint32_t current_selection_;
+ DropdownType type_;
+ bool is_enabled_;
};
/// A dropdown menu that lets the user select a value of the datatype 'Entry'.
@@ -134,29 +168,44 @@
/// \param parent the parent panel
/// \param x the x-position within 'parent'
/// \param y the y-position within 'parent'
- /// \param w the dropdown's width
- /// \param h the maximum height for the dropdown list
+ /// \param list_w the dropdown's width
+ /// \param list_h the maximum height for the dropdown list
+ /// \param button_dimension the width of the push button in textual dropdowns. For pictorial
+ /// dropdowns, this both the width and the height of the button.
/// \param label a label to prefix to the selected entry on the display button.
+ /// \param type whether this is a textual or pictorial dropdown
/// \param background the background image for this dropdown
/// \param button_background the background image all buttons in this dropdown
Dropdown(Panel* parent,
int32_t x,
int32_t y,
- uint32_t w,
- uint32_t h,
+ uint32_t list_w,
+ uint32_t list_h,
+ int button_dimension,
const std::string& label,
+ const DropdownType type = DropdownType::kTextual,
const Image* background = g_gr->images().get("images/ui_basic/but1.png"),
const Image* button_background = g_gr->images().get("images/ui_basic/but3.png"))
- : BaseDropdown(parent, x, y, w, h, label, background, button_background) {
+ : BaseDropdown(parent,
+ x,
+ y,
+ list_w,
+ list_h,
+ button_dimension,
+ label,
+ type,
+ background,
+ button_background) {
}
~Dropdown() {
- clear();
+ entry_cache_.clear();
}
/// Add an element to the list
/// \param name the display name of the entry
/// \param value the value for the entry
- /// \param pic an image to illustrate the entry
+ /// \param pic an image to illustrate the entry. Can be nullptr in textual dropdowns
+ /// only.
/// \param select_this whether this element should be selected
/// \param tooltip_text a tooltip for this entry
void add(const std::string& name,
@@ -173,9 +222,19 @@
return *entry_cache_[BaseDropdown::get_selected()];
}
+ /// Select the entry if it exists. Does not trigger the 'selected' signal.
+ void select(const Entry& entry) {
+ for (uint32_t i = 0; i < entry_cache_.size(); ++i) {
+ if (entry == *entry_cache_[i]) {
+ BaseDropdown::select(i);
+ }
+ }
+ }
+
/// Removes all elements from the list.
void clear() {
BaseDropdown::clear();
+ entry_cache_.clear();
}
private:
=== modified file 'src/ui_basic/listselect.cc'
--- src/ui_basic/listselect.cc 2017-02-27 13:48:29 +0000
+++ src/ui_basic/listselect.cc 2017-03-06 08:30:52 +0000
@@ -305,6 +305,14 @@
return entry_records_[selection_]->tooltip;
}
+/**
+ * \return The image for the currently selected entry. Requires an entry to have been selected.
+ */
+const Image* BaseListselect::get_selected_image() const {
+ assert(selection_ < entry_records_.size());
+ return entry_records_[selection_]->pic;
+}
+
int BaseListselect::get_lineheight() const {
return lineheight_ + kMargin;
}
@@ -322,6 +330,19 @@
if (scrollbar_.is_enabled() && selection_mode_ == ListselectLayout::kDropdown) {
scrollbar_.set_steps(steps + kMargin);
}
+ // For dropdowns, autoincrease width
+ if (selection_mode_ == ListselectLayout::kDropdown) {
+ for (size_t i = 0; i < entry_records_.size(); ++i) {
+ const EntryRecord& er = *entry_records_[i];
+ const Image* entry_text_im = UI::g_fh1->render(as_uifont(
+ richtext_escape(er.name), UI_FONT_SIZE_SMALL, er.use_clr ? er.clr : UI_FONT_CLR_FG));
+ int picw = max_pic_width_ ? max_pic_width_ + 10 : 0;
+ int difference = entry_text_im->width() + picw + 8 - get_eff_w();
+ if (difference > 0) {
+ set_size(get_w() + difference, get_h());
+ }
+ }
+ }
}
/**
@@ -340,6 +361,8 @@
if (selection_mode_ == ListselectLayout::kDropdown) {
RGBAColor black(0, 0, 0, 255);
+ // top edge
+ dst.brighten_rect(Rectf(0.f, 0.f, get_w(), 2.f), BUTTON_EDGE_BRIGHT_FACTOR / 4);
// left edge
dst.brighten_rect(Rectf(0.f, 0.f, 2.f, get_h()), BUTTON_EDGE_BRIGHT_FACTOR);
// bottom edge
=== modified file 'src/ui_basic/listselect.h'
--- src/ui_basic/listselect.h 2017-02-12 09:10:57 +0000
+++ src/ui_basic/listselect.h 2017-03-06 08:30:52 +0000
@@ -105,6 +105,7 @@
const std::string& get_selected_name() const;
const std::string& get_selected_tooltip() const;
+ const Image* get_selected_image() const;
void set_background(const Image* background) {
background_ = background;
=== modified file 'src/ui_fsmenu/launch_spg.cc'
--- src/ui_fsmenu/launch_spg.cc 2017-02-26 12:16:09 +0000
+++ src/ui_fsmenu/launch_spg.cc 2017-03-06 08:30:52 +0000
@@ -65,6 +65,7 @@
get_h() * 4 / 10 + buth_,
butw_,
get_h() - get_h() * 4 / 10 - buth_,
+ buth_,
""),
back_(this,
"back",
=== modified file 'src/ui_fsmenu/options.cc'
--- src/ui_fsmenu/options.cc 2017-02-23 19:38:51 +0000
+++ src/ui_fsmenu/options.cc 2017-03-06 08:30:52 +0000
@@ -138,12 +138,14 @@
0,
100, // 100 is arbitrary, will be resized in layout().
100, // 100 is arbitrary, will be resized in layout().
+ 24,
_("Language")),
resolution_dropdown_(&box_interface_,
0,
0,
100, // 100 is arbitrary, will be resized in layout().
100, // 100 is arbitrary, will be resized in layout().
+ 24,
_("In-game resolution")),
fullscreen_(&box_interface_, Vector2i(0, 0), _("Fullscreen"), "", 0),
=== modified file 'src/wui/building_statistics_menu.cc'
--- src/wui/building_statistics_menu.cc 2017-02-26 12:16:09 +0000
+++ src/wui/building_statistics_menu.cc 2017-03-06 08:30:52 +0000
@@ -331,6 +331,7 @@
kBuildGridCellHeight, g_gr->images().get("images/ui_basic/but1.png"),
descr.representative_image(&iplayer().get_player()->get_playercolor()), "",
UI::Button::Style::kFlat);
+ building_buttons_[id]->set_disable_style(UI::ButtonDisableStyle::kMonochrome | UI::ButtonDisableStyle::kFlat);
button_box->add(building_buttons_[id]);
owned_labels_[id] =
=== modified file 'src/wui/multiplayersetupgroup.cc'
--- src/wui/multiplayersetupgroup.cc 2017-02-26 11:00:07 +0000
+++ src/wui/multiplayersetupgroup.cc 2017-03-06 08:30:52 +0000
@@ -22,6 +22,7 @@
#include <string>
#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
#include "ai/computer_player.h"
#include "base/i18n.h"
@@ -33,9 +34,11 @@
#include "logic/game.h"
#include "logic/game_settings.h"
#include "logic/map_objects/tribes/tribe_descr.h"
+#include "logic/map_objects/tribes/tribes.h"
#include "logic/player.h"
#include "ui_basic/button.h"
#include "ui_basic/checkbox.h"
+#include "ui_basic/dropdown.h"
#include "ui_basic/icon.h"
#include "ui_basic/scrollbar.h"
#include "ui_basic/textarea.h"
@@ -145,20 +148,23 @@
int32_t const w,
int32_t const h,
GameSettingsProvider* const settings,
- NetworkPlayerSettingsBackend* const npsb,
- std::map<std::string, const Image*>& tp,
- std::map<std::string, std::string>& tn)
+ NetworkPlayerSettingsBackend* const npsb)
: UI::Box(parent, 0, 0, UI::Box::Horizontal, w, h),
player(nullptr),
type(nullptr),
- tribe(nullptr),
init(nullptr),
s(settings),
n(npsb),
id_(id),
- tribepics_(tp),
- tribenames_(tn) {
+ tribes_dropdown_(this, 0, 0, 50, 200, h, _("Tribe"), UI::DropdownType::kPictorial),
+ last_state_(PlayerSettings::stateClosed),
+ last_player_amount_(0) {
set_size(w, h);
+ tribes_dropdown_.set_visible(false);
+ tribes_dropdown_.set_enabled(false);
+ tribes_dropdown_.selected.connect(
+ boost::bind(&MultiPlayerPlayerGroup::set_tribe_or_shared_in, boost::ref(*this)));
+
const Image* player_image =
playercolor_image(id, g_gr->images().get("images/players/player_position_menu.png"),
g_gr->images().get("images/players/player_position_menu_pc.png"));
@@ -170,11 +176,7 @@
type->sigclicked.connect(
boost::bind(&MultiPlayerPlayerGroup::toggle_type, boost::ref(*this)));
add(type);
- tribe = new UI::Button(
- this, "player_tribe", 0, 0, h, h, g_gr->images().get("images/ui_basic/but1.png"), "");
- tribe->sigclicked.connect(
- boost::bind(&MultiPlayerPlayerGroup::toggle_tribe, boost::ref(*this)));
- add(tribe);
+ add(&tribes_dropdown_);
init = new UI::Button(this, "player_init", 0, 0, w - 4 * h, h,
g_gr->images().get("images/ui_basic/but1.png"), "");
init->sigclicked.connect(
@@ -192,9 +194,23 @@
n->toggle_type(id_);
}
- /// Toggle through the tribes + handle shared in players
- void toggle_tribe() {
- n->toggle_tribe(id_);
+ /// This will update the game settings for the tribe or shared_in with the value
+ /// currently selected in the tribes dropdown.
+ void set_tribe_or_shared_in() {
+ n->set_block_tribe_selection(true);
+ tribes_dropdown_.set_disable_style(s->settings().players[id_].state ==
+ PlayerSettings::stateShared ?
+ UI::ButtonDisableStyle::kPermpressed :
+ UI::ButtonDisableStyle::kMonochrome);
+ if (tribes_dropdown_.has_selection()) {
+ if (s->settings().players[id_].state == PlayerSettings::stateShared) {
+ n->set_shared_in(
+ id_, boost::lexical_cast<unsigned int>(tribes_dropdown_.get_selected()));
+ } else {
+ n->set_tribe(id_, tribes_dropdown_.get_selected());
+ }
+ }
+ n->set_block_tribe_selection(false);
}
/// Toggle through the initializations
@@ -207,6 +223,107 @@
n->toggle_team(id_);
}
+ /// Helper function to cast shared_in for use in the dropdown.
+ const std::string shared_in_as_string(uint8_t shared_in) {
+ return boost::lexical_cast<std::string>(static_cast<unsigned int>(shared_in));
+ }
+
+ /// Update the tribes dropdown from the server settings if the server setting changed.
+ /// This will keep the host and client UIs in sync.
+ void update_tribes_dropdown(const PlayerSettings& player_setting) {
+ if (player_setting.state == PlayerSettings::stateClosed ||
+ player_setting.state == PlayerSettings::stateOpen) {
+ return;
+ }
+ if (!tribes_dropdown_.is_visible()) {
+ tribes_dropdown_.set_visible(true);
+ }
+ if (!tribes_dropdown_.is_expanded() && !n->tribe_selection_blocked &&
+ tribes_dropdown_.has_selection()) {
+ const std::string selected_tribe = tribes_dropdown_.get_selected();
+ if (player_setting.state == PlayerSettings::stateShared) {
+ const std::string shared_in = shared_in_as_string(player_setting.shared_in);
+ if (shared_in != selected_tribe) {
+ tribes_dropdown_.select(shared_in);
+ }
+ } else {
+ if (player_setting.random_tribe) {
+ if (selected_tribe != "random") {
+ tribes_dropdown_.select("random");
+ }
+ } else if (selected_tribe != player_setting.tribe) {
+ tribes_dropdown_.select(player_setting.tribe);
+ }
+ }
+ }
+ }
+
+ /// If the map was changed or the selection mode changed between shared_in and tribe, rebuild the
+ /// dropdown.
+ void rebuild_tribes_dropdown(const GameSettings& settings) {
+ const PlayerSettings& player_setting = settings.players[id_];
+
+ if (player_setting.state == PlayerSettings::stateClosed ||
+ player_setting.state == PlayerSettings::stateOpen) {
+ return;
+ }
+
+ if (tribes_dropdown_.empty() || last_player_amount_ != settings.players.size() ||
+ ((player_setting.state == PlayerSettings::stateShared ||
+ last_state_ == PlayerSettings::stateShared) &&
+ player_setting.state != last_state_)) {
+ tribes_dropdown_.clear();
+
+ // We need to see the playercolor if setting shared_in is disabled
+ tribes_dropdown_.set_disable_style(player_setting.state == PlayerSettings::stateShared ?
+ UI::ButtonDisableStyle::kPermpressed :
+ UI::ButtonDisableStyle::kMonochrome);
+
+ if (player_setting.state == PlayerSettings::stateShared) {
+ for (size_t i = 0; i < settings.players.size(); ++i) {
+ if (i != id_) {
+ // TODO(GunChleoc): Do not add players that are also shared_in.
+ const Image* player_image = playercolor_image(
+ i, g_gr->images().get("images/players/player_position_menu.png"),
+ g_gr->images().get("images/players/player_position_menu_pc.png"));
+ assert(player_image);
+ const std::string player_name =
+ /** TRANSLATORS: This is an option in multiplayer setup for sharing
+ another player's starting position. */
+ (boost::format(_("Shared in Player %u")) % static_cast<unsigned int>(i + 1))
+ .str();
+ tribes_dropdown_.add(
+ player_name, shared_in_as_string(i + 1), player_image, false, player_name);
+ }
+ }
+ int shared_in = 0;
+ while (shared_in == id_) {
+ ++shared_in;
+ }
+ tribes_dropdown_.select(shared_in_as_string(shared_in + 1));
+ tribes_dropdown_.set_enabled(tribes_dropdown_.size() > 1);
+ } else {
+ {
+ i18n::Textdomain td("tribes");
+ for (const TribeBasicInfo& tribeinfo : Widelands::get_all_tribeinfos()) {
+ tribes_dropdown_.add(_(tribeinfo.descname), tribeinfo.name,
+ g_gr->images().get(tribeinfo.icon), false,
+ tribeinfo.tooltip);
+ }
+ }
+ tribes_dropdown_.add(pgettext("tribe", "Random"), "random",
+ g_gr->images().get("images/ui_fsmenu/random.png"), false,
+ _("The tribe will be selected at random"));
+ if (player_setting.random_tribe) {
+ tribes_dropdown_.select("random");
+ } else {
+ tribes_dropdown_.select(player_setting.tribe);
+ }
+ }
+ }
+ last_player_amount_ = settings.players.size();
+ }
+
/// Refresh all user interfaces
void refresh() {
const GameSettings& settings = s->settings();
@@ -225,16 +342,17 @@
bool tribeaccess = s->can_change_player_tribe(id_);
bool const initaccess = s->can_change_player_init(id_);
bool teamaccess = s->can_change_player_team(id_);
-
type->set_enabled(typeaccess);
+
+ rebuild_tribes_dropdown(settings);
+
if (player_setting.state == PlayerSettings::stateClosed) {
type->set_tooltip(_("Closed"));
type->set_pic(g_gr->images().get("images/ui_basic/stop.png"));
team->set_visible(false);
team->set_enabled(false);
- tribe->set_visible(false);
- tribe->set_enabled(false);
- tribe->set_style(UI::Button::Style::kRaised);
+ tribes_dropdown_.set_visible(false);
+ tribes_dropdown_.set_enabled(false);
init->set_visible(false);
init->set_enabled(false);
return;
@@ -243,30 +361,25 @@
type->set_pic(g_gr->images().get("images/ui_basic/continue.png"));
team->set_visible(false);
team->set_enabled(false);
- tribe->set_visible(false);
- tribe->set_enabled(false);
- tribe->set_style(UI::Button::Style::kRaised);
+ tribes_dropdown_.set_visible(false);
+ tribes_dropdown_.set_enabled(false);
init->set_visible(false);
init->set_enabled(false);
return;
} else if (player_setting.state == PlayerSettings::stateShared) {
type->set_tooltip(_("Shared in"));
type->set_pic(g_gr->images().get("images/ui_fsmenu/shared_in.png"));
- const Image* player_image =
- playercolor_image(player_setting.shared_in - 1,
- g_gr->images().get("images/players/player_position_menu.png"),
- g_gr->images().get("images/players/player_position_menu_pc.png"));
- assert(player_image);
- tribe->set_pic(player_image);
- tribe->set_tooltip(
- (boost::format(_("Player %u")) % static_cast<unsigned int>(player_setting.shared_in))
- .str());
+
+ update_tribes_dropdown(player_setting);
+
+ if (tribes_dropdown_.is_enabled() != initaccess) {
+ tribes_dropdown_.set_enabled(initaccess && !n->tribe_selection_blocked &&
+ tribes_dropdown_.size() > 1);
+ }
team->set_visible(false);
team->set_enabled(false);
- // Flat ~= icon
- tribe->set_style(initaccess ? UI::Button::Style::kRaised : UI::Button::Style::kFlat);
- tribe->set_enabled(true);
+
} else {
std::string title;
std::string pic = "images/";
@@ -292,25 +405,12 @@
}
type->set_tooltip(title.c_str());
type->set_pic(g_gr->images().get(pic));
- if (player_setting.random_tribe) {
- std::string random = pgettext("tribe", "Random");
- if (!tribenames_["random"].size())
- tribepics_[random] = g_gr->images().get("images/ui_fsmenu/random.png");
- tribe->set_tooltip(random.c_str());
- tribe->set_pic(tribepics_[random]);
- } else {
- if (!tribenames_[player_setting.tribe].size()) {
- // get tribes name and picture
- i18n::Textdomain td("tribes");
- for (const TribeBasicInfo& tribeinfo : settings.tribes) {
- tribenames_[tribeinfo.name] = _(tribeinfo.descname);
- tribepics_[tribeinfo.name] = g_gr->images().get(tribeinfo.icon);
- }
- }
- tribe->set_tooltip(tribenames_[player_setting.tribe].c_str());
- tribe->set_pic(tribepics_[player_setting.tribe]);
+
+ update_tribes_dropdown(player_setting);
+
+ if (tribes_dropdown_.is_enabled() != tribeaccess) {
+ tribes_dropdown_.set_enabled(tribeaccess && !n->tribe_selection_blocked);
}
- tribe->set_style(UI::Button::Style::kRaised);
if (player_setting.team) {
team->set_title(std::to_string(static_cast<unsigned int>(player_setting.team)));
@@ -319,10 +419,8 @@
}
team->set_visible(true);
team->set_enabled(teamaccess);
- tribe->set_enabled(tribeaccess);
}
init->set_enabled(initaccess);
- tribe->set_visible(true);
init->set_visible(true);
if (settings.scenario)
@@ -342,18 +440,19 @@
}
}
}
+ last_state_ = player_setting.state;
}
UI::Icon* player;
UI::Button* type;
- UI::Button* tribe;
UI::Button* init;
UI::Button* team;
GameSettingsProvider* const s;
NetworkPlayerSettingsBackend* const n;
uint8_t const id_;
- std::map<std::string, const Image*>& tribepics_;
- std::map<std::string, std::string>& tribenames_;
+ UI::Dropdown<std::string> tribes_dropdown_; /// Select the tribe or shared_in player.
+ PlayerSettings::State last_state_; /// The dropdown needs updating if this changes
+ size_t last_player_amount_; /// The dropdown needs rebuilding if this changes
};
MultiPlayerSetupGroup::MultiPlayerSetupGroup(UI::Panel* const parent,
@@ -411,10 +510,9 @@
playerbox.set_size(w * 9 / 15, h - buth);
multi_player_player_groups.resize(kMaxPlayers);
for (uint8_t i = 0; i < multi_player_player_groups.size(); ++i) {
- multi_player_player_groups.at(i) = new MultiPlayerPlayerGroup(
- &playerbox, i, 0, 0, playerbox.get_w(), buth, s, npsb.get(), tribepics_, tribenames_);
- playerbox.add(
- multi_player_player_groups.at(i), UI::Box::Resizing::kAlign, UI::Align::kCenter);
+ multi_player_player_groups.at(i) =
+ new MultiPlayerPlayerGroup(&playerbox, i, 0, 0, playerbox.get_w(), buth, s, npsb.get());
+ playerbox.add(multi_player_player_groups.at(i));
}
refresh();
}
Follow ups