Merge lp:~widelands-dev/widelands/bug-536489-pictorial-dropdown into lp:widelands

Proposed by GunChleoc
Status: Merged
Merged at revision: 8331
Proposed branch: lp:~widelands-dev/widelands/bug-536489-pictorial-dropdown
Merge into: lp:widelands
Diff against target: 1279 lines (+505/-200)
12 files modified
src/network/network_player_settings_backend.cc (+49/-52)
src/network/network_player_settings_backend.h (+10/-1)
src/ui_basic/button.cc (+20/-6)
src/ui_basic/button.h (+18/-0)
src/ui_basic/dropdown.cc (+149/-67)
src/ui_basic/dropdown.h (+73/-14)
src/ui_basic/listselect.cc (+23/-0)
src/ui_basic/listselect.h (+1/-0)
src/ui_fsmenu/launch_spg.cc (+1/-0)
src/ui_fsmenu/options.cc (+2/-0)
src/wui/building_statistics_menu.cc (+1/-0)
src/wui/multiplayersetupgroup.cc (+158/-60)
To merge this branch: bzr merge lp:~widelands-dev/widelands/bug-536489-pictorial-dropdown
Reviewer Review Type Date Requested Status
kaputtnik (community) testing Approve
Review via email: mp+319023@code.launchpad.net

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.

Description of the change

Pictorial dropdown. I only replaced one button so far, because the diff is already big enough.

To post a comment you must log in.
Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2051. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/208120603.
Appveyor build 1886. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_bug_536489_pictorial_dropdown-1886.

Revision history for this message
kaputtnik (franku) wrote :

Works here :-)

review: Approve (testing)
Revision history for this message
SirVer (sirver) wrote :

2 nits, otherwise code lgtm.

Tested: worked, but when I first tried, the tribe selection was greyed out. I was unable to repro immediately after, but it seems there is some initialized variables somewhere?

Revision history for this message
GunChleoc (gunchleoc) wrote :

Thanks for the review :)

Will need to keep an eye out for the greying out thing when I add the other dropdowns.

Revision history for this message
GunChleoc (gunchleoc) wrote :

@bunnybot merge

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/network/network_player_settings_backend.cc'
2--- src/network/network_player_settings_backend.cc 2017-01-25 18:55:59 +0000
3+++ src/network/network_player_settings_backend.cc 2017-04-03 12:24:35 +0000
4@@ -34,62 +34,59 @@
5 s->next_player_state(id);
6 }
7
8-/// Toggle through the tribes + handle shared in players
9-void NetworkPlayerSettingsBackend::toggle_tribe(uint8_t id) {
10+void NetworkPlayerSettingsBackend::set_tribe(uint8_t id, const std::string& tribename) {
11 const GameSettings& settings = s->settings();
12
13- if (id >= settings.players.size())
14+ if (id >= settings.players.size() || tribename.empty())
15 return;
16
17 if (settings.players.at(id).state != PlayerSettings::stateShared) {
18- const PlayerSettings& player = settings.players.at(id);
19- const std::string& currenttribe = player.tribe;
20- std::string nexttribe = settings.tribes.at(0).name;
21- uint32_t num_tribes = settings.tribes.size();
22- bool random_tribe = false;
23-
24- if (player.random_tribe) {
25- nexttribe = settings.tribes.at(0).name;
26- } else if (player.tribe == settings.tribes.at(num_tribes - 1).name) {
27- nexttribe = "Random";
28- random_tribe = true;
29- } else {
30- for (uint32_t i = 0; i < num_tribes - 1; ++i) {
31- if (settings.tribes[i].name == currenttribe) {
32- nexttribe = settings.tribes.at(i + 1).name;
33- break;
34- }
35- }
36- }
37-
38- s->set_player_tribe(id, nexttribe, random_tribe);
39+ s->set_player_tribe(id, tribename, tribename == "random");
40+ }
41+}
42+
43+/// Set the shared in player for the given id
44+void NetworkPlayerSettingsBackend::set_shared_in(uint8_t id, uint8_t shared_in) {
45+ const GameSettings& settings = s->settings();
46+ if (id > settings.players.size() || shared_in > settings.players.size())
47+ return;
48+ if (settings.players.at(id).state == PlayerSettings::stateShared) {
49+ s->set_player_shared(id, shared_in);
50+ }
51+}
52+
53+/// Toggle through shared in players
54+void NetworkPlayerSettingsBackend::toggle_shared_in(uint8_t id) {
55+ const GameSettings& settings = s->settings();
56+
57+ if (id >= settings.players.size() ||
58+ settings.players.at(id).state != PlayerSettings::stateShared)
59+ return;
60+
61+ uint8_t sharedplr = settings.players.at(id).shared_in;
62+ for (; sharedplr < settings.players.size(); ++sharedplr) {
63+ if (settings.players.at(sharedplr).state != PlayerSettings::stateClosed &&
64+ settings.players.at(sharedplr).state != PlayerSettings::stateShared)
65+ break;
66+ }
67+ if (sharedplr < settings.players.size()) {
68+ // We have already found the next player
69+ set_shared_in(id, sharedplr + 1);
70+ return;
71+ }
72+ sharedplr = 0;
73+ for (; sharedplr < settings.players.at(id).shared_in; ++sharedplr) {
74+ if (settings.players.at(sharedplr).state != PlayerSettings::stateClosed &&
75+ settings.players.at(sharedplr).state != PlayerSettings::stateShared)
76+ break;
77+ }
78+ if (sharedplr < settings.players.at(id).shared_in) {
79+ // We have found the next player
80+ set_shared_in(id, sharedplr + 1);
81+ return;
82 } else {
83- // This button is temporarily used to select the player that uses this starting position
84- uint8_t sharedplr = settings.players.at(id).shared_in;
85- for (; sharedplr < settings.players.size(); ++sharedplr) {
86- if (settings.players.at(sharedplr).state != PlayerSettings::stateClosed &&
87- settings.players.at(sharedplr).state != PlayerSettings::stateShared)
88- break;
89- }
90- if (sharedplr < settings.players.size()) {
91- // We have already found the next player
92- s->set_player_shared(id, sharedplr + 1);
93- return;
94- }
95- sharedplr = 0;
96- for (; sharedplr < settings.players.at(id).shared_in; ++sharedplr) {
97- if (settings.players.at(sharedplr).state != PlayerSettings::stateClosed &&
98- settings.players.at(sharedplr).state != PlayerSettings::stateShared)
99- break;
100- }
101- if (sharedplr < settings.players.at(id).shared_in) {
102- // We have found the next player
103- s->set_player_shared(id, sharedplr + 1);
104- return;
105- } else {
106- // No fitting player found
107- return toggle_type(id);
108- }
109+ // No fitting player found
110+ return toggle_type(id);
111 }
112 }
113
114@@ -141,10 +138,10 @@
115 if (player.state == PlayerSettings::stateShared) {
116 // ensure that the shared_in player is able to use this starting position
117 if (player.shared_in > settings.players.size())
118- toggle_tribe(id);
119+ toggle_shared_in(id);
120 if (settings.players.at(player.shared_in - 1).state == PlayerSettings::stateClosed ||
121 settings.players.at(player.shared_in - 1).state == PlayerSettings::stateShared)
122- toggle_tribe(id);
123+ toggle_shared_in(id);
124
125 if (shared_in_tribe[id] != settings.players.at(player.shared_in - 1).tribe) {
126 s->set_player_tribe(id, settings.players.at(player.shared_in - 1).tribe,
127
128=== modified file 'src/network/network_player_settings_backend.h'
129--- src/network/network_player_settings_backend.h 2017-01-25 18:55:59 +0000
130+++ src/network/network_player_settings_backend.h 2017-04-03 12:24:35 +0000
131@@ -31,13 +31,22 @@
132 }
133
134 void toggle_type(uint8_t id);
135- void toggle_tribe(uint8_t id);
136+ void set_shared_in(uint8_t id, uint8_t shared_in);
137+ void set_tribe(uint8_t id, const std::string& tribename);
138+ void set_block_tribe_selection(bool blocked) {
139+ tribe_selection_blocked = blocked;
140+ }
141+
142 void toggle_init(uint8_t id);
143 void toggle_team(uint8_t id);
144 void refresh(uint8_t id);
145
146 GameSettingsProvider* const s;
147 std::string shared_in_tribe[kMaxPlayers];
148+ bool tribe_selection_blocked;
149+
150+private:
151+ void toggle_shared_in(uint8_t id);
152 };
153
154 #endif // end of include guard: WL_NETWORK_NETWORK_PLAYER_SETTINGS_BACKEND_H
155
156=== modified file 'src/ui_basic/button.cc'
157--- src/ui_basic/button.cc 2017-02-12 09:10:57 +0000
158+++ src/ui_basic/button.cc 2017-04-03 12:24:35 +0000
159@@ -49,6 +49,7 @@
160 pressed_(false),
161 enabled_(true),
162 style_(init_style),
163+ disable_style_(ButtonDisableStyle::kMonochrome),
164 repeating_(false),
165 image_mode_(UI::Button::ImageMode::kShrink),
166 time_nextact_(0),
167@@ -85,6 +86,7 @@
168 pressed_(false),
169 enabled_(true),
170 style_(init_style),
171+ disable_style_(ButtonDisableStyle::kMonochrome),
172 repeating_(false),
173 image_mode_(mode),
174 time_nextact_(0),
175@@ -149,6 +151,14 @@
176 * Redraw the button
177 */
178 void Button::draw(RenderTarget& dst) {
179+ const bool is_flat = (enabled_ && style_ == Style::kFlat) ||
180+ (!enabled_ && static_cast<int>(disable_style_ & ButtonDisableStyle::kFlat));
181+ const bool is_permpressed =
182+ (enabled_ && style_ == Style::kPermpressed) ||
183+ (!enabled_ && static_cast<int>(disable_style_ & ButtonDisableStyle::kPermpressed));
184+ const bool is_monochrome =
185+ !enabled_ && static_cast<int>(disable_style_ & ButtonDisableStyle::kMonochrome);
186+
187 // Draw the background
188 if (pic_background_) {
189 dst.fill_rect(Rectf(0.f, 0.f, get_w(), get_h()), RGBAColor(0, 0, 0, 255));
190@@ -156,13 +166,13 @@
191 Recti(Vector2i(0, 0), get_w(), get_h()), pic_background_, Vector2i(get_x(), get_y()));
192 }
193
194- if (enabled_ && highlighted_ && style_ != Style::kFlat)
195+ if (is_flat && highlighted_)
196 dst.brighten_rect(Rectf(0.f, 0.f, get_w(), get_h()), MOUSE_OVER_BRIGHT_FACTOR);
197
198 // If we've got a picture, draw it centered
199 if (pic_custom_) {
200 if (image_mode_ == UI::Button::ImageMode::kUnscaled) {
201- if (enabled_) {
202+ if (!is_monochrome) {
203 dst.blit(Vector2f((get_w() - static_cast<int32_t>(pic_custom_->width())) / 2.f,
204 (get_h() - static_cast<int32_t>(pic_custom_->height())) / 2.f),
205 pic_custom_);
206@@ -181,7 +191,7 @@
207 int blit_width = image_scale * pic_custom_->width();
208 int blit_height = image_scale * pic_custom_->height();
209
210- if (enabled_) {
211+ if (!is_monochrome) {
212 dst.blitrect_scale(Rectf((get_w() - blit_width) / 2.f, (get_h() - blit_height) / 2.f,
213 blit_width, blit_height),
214 pic_custom_,
215@@ -200,7 +210,7 @@
216 // Otherwise draw title string centered
217 const Image* entry_text_im =
218 autofit_ui_text(title_, get_inner_w() - 2 * kButtonImageMargin,
219- enabled_ ? UI_FONT_CLR_FG : UI_FONT_CLR_DISABLED);
220+ is_monochrome ? UI_FONT_CLR_DISABLED : UI_FONT_CLR_FG);
221 // Blit on pixel boundary (not float), so that the text is blitted pixel perfect.
222 dst.blit(
223 Vector2f((get_w() - entry_text_im->width()) / 2, (get_h() - entry_text_im->height()) / 2),
224@@ -213,11 +223,11 @@
225 // stays pressed when it is pressed once
226 RGBAColor black(0, 0, 0, 255);
227
228- if (style_ != Style::kFlat) {
229+ if (!is_flat) {
230 assert(2 <= get_w());
231 assert(2 <= get_h());
232 // Button is a normal one, not flat. We invert the behaviour for kPermpressed.
233- if ((style_ == Style::kPermpressed) == (pressed_ && highlighted_)) {
234+ if (is_permpressed == (pressed_ && highlighted_)) {
235 // top edge
236 dst.brighten_rect(Rectf(0.f, 0.f, get_w(), 2.f), BUTTON_EDGE_BRIGHT_FACTOR);
237 // left edge
238@@ -339,6 +349,10 @@
239 style_ = input_style;
240 }
241
242+void Button::set_disable_style(UI::ButtonDisableStyle input_style) {
243+ disable_style_ = input_style;
244+}
245+
246 void Button::set_perm_pressed(bool pressed) {
247 set_style(pressed ? UI::Button::Style::kPermpressed : UI::Button::Style::kRaised);
248 }
249
250=== modified file 'src/ui_basic/button.h'
251--- src/ui_basic/button.h 2017-02-28 20:07:07 +0000
252+++ src/ui_basic/button.h 2017-04-03 12:24:35 +0000
253@@ -30,6 +30,20 @@
254
255 namespace UI {
256
257+struct Font;
258+
259+enum class ButtonDisableStyle {
260+ kMonochrome = 2, // Greyed out. Can be combined with the other 2 styles.
261+ kPermpressed = 4, // Button will appear pressed.
262+ kFlat = 8, // Button will appear flat.
263+};
264+inline ButtonDisableStyle operator&(ButtonDisableStyle a, ButtonDisableStyle b) {
265+ return static_cast<ButtonDisableStyle>(static_cast<int>(a) & static_cast<int>(b));
266+}
267+inline ButtonDisableStyle operator|(ButtonDisableStyle a, ButtonDisableStyle b) {
268+ return static_cast<ButtonDisableStyle>(static_cast<int>(a) | static_cast<int>(b));
269+}
270+
271 /// This is simply a button. Override void clicked() to react to the click.
272 /// This is all that is needed in most cases, but if there is a need to give a
273 /// callback function to the button, there are some templates for that below.
274@@ -103,6 +117,9 @@
275 return style_;
276 }
277
278+ /// Sets the visual style of the disabled button
279+ void set_disable_style(UI::ButtonDisableStyle input_style);
280+
281 /// Convenience function. If 'pressed', sets the style to kPermpressed, otherwise to kRaised.
282 void set_perm_pressed(bool pressed);
283
284@@ -121,6 +138,7 @@
285 bool pressed_; // mouse is clicked over the button
286 bool enabled_;
287 UI::Button::Style style_;
288+ UI::ButtonDisableStyle disable_style_;
289 bool repeating_;
290 const UI::Button::ImageMode image_mode_;
291
292
293=== modified file 'src/ui_basic/dropdown.cc'
294--- src/ui_basic/dropdown.cc 2017-02-25 11:17:28 +0000
295+++ src/ui_basic/dropdown.cc 2017-04-03 12:24:35 +0000
296@@ -24,16 +24,18 @@
297 #include <boost/format.hpp>
298
299 #include "base/i18n.h"
300+#include "base/macros.h"
301 #include "graphic/align.h"
302 #include "graphic/font_handler1.h"
303 #include "graphic/rendertarget.h"
304 #include "ui_basic/mouse_constants.h"
305+#include "ui_basic/tabpanel.h"
306
307 namespace {
308
309-int base_height() {
310+int base_height(int button_dimension) {
311 return std::max(
312- 24,
313+ button_dimension,
314 UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height() + 2);
315 }
316
317@@ -46,63 +48,103 @@
318 int32_t y,
319 uint32_t w,
320 uint32_t h,
321+ int button_dimension,
322 const std::string& label,
323+ const DropdownType type,
324 const Image* background,
325 const Image* button_background)
326 : UI::Panel(parent,
327 x,
328 y,
329- w,
330- base_height()), // Height only to fit the button, so we can use this in Box layout.
331+ type == DropdownType::kTextual ? w : button_dimension,
332+ // Height only to fit the button, so we can use this in Box layout.
333+ base_height(button_dimension)),
334 max_list_height_(h - 2 * get_h()),
335+ list_width_(w),
336+ button_dimension_(button_dimension),
337 mouse_tolerance_(50),
338 button_box_(this, 0, 0, UI::Box::Horizontal, w, h),
339- push_button_(&button_box_,
340- "dropdown_select",
341- 0,
342- 0,
343- 24,
344- get_h(),
345- button_background,
346- g_gr->images().get("images/ui_basic/scrollbar_down.png"),
347- pgettext("dropdown", "Select Item")),
348- display_button_(&button_box_, "dropdown_label", 0, 0, w - 24, get_h(), background, label),
349- // Hook into parent so we can drop down outside the panel
350- list_(parent, x, y + get_h(), w, 0, button_background, ListselectLayout::kDropdown),
351- label_(label) {
352- list_.set_visible(false);
353- list_.set_background(background);
354- display_button_.set_perm_pressed(true);
355+ push_button_(type == DropdownType::kTextual ?
356+ new UI::Button(&button_box_,
357+ "dropdown_select",
358+ 0,
359+ 0,
360+ button_dimension,
361+ get_h(),
362+ button_background,
363+ g_gr->images().get("images/ui_basic/scrollbar_down.png"),
364+ pgettext("dropdown", "Select Item")) :
365+ nullptr),
366+ display_button_(&button_box_,
367+ "dropdown_label",
368+ 0,
369+ 0,
370+ type == DropdownType::kTextual ? w - button_dimension : button_dimension,
371+ get_h(),
372+ background,
373+ label),
374+ label_(label),
375+ type_(type),
376+ is_enabled_(true) {
377+ assert(max_list_height_ > 0);
378+ // Hook into highest parent that we can get so that we can drop down outside the panel.
379+ // Positioning breaks down with TabPanels, so we exclude them.
380+ while (parent->get_parent() && !is_a(UI::TabPanel, parent->get_parent())) {
381+ parent = parent->get_parent();
382+ }
383+ list_ = new UI::Listselect<uintptr_t>(
384+ parent, 0, 0, w, 0, button_background, ListselectLayout::kDropdown);
385+
386+ list_->set_visible(false);
387+ list_->set_background(background);
388+
389 button_box_.add(&display_button_);
390- button_box_.add(&push_button_);
391+ display_button_.sigclicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
392+ if (push_button_ != nullptr) {
393+ display_button_.set_perm_pressed(true);
394+ button_box_.add(push_button_);
395+ push_button_->sigclicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
396+ }
397 button_box_.set_size(w, get_h());
398-
399- display_button_.sigclicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
400- push_button_.sigclicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
401- list_.clicked.connect(boost::bind(&BaseDropdown::set_value, this));
402- list_.clicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
403+ list_->clicked.connect(boost::bind(&BaseDropdown::set_value, this));
404+ list_->clicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
405 set_can_focus(true);
406+ set_value();
407 layout();
408 }
409
410 BaseDropdown::~BaseDropdown() {
411- clear();
412+ // Listselect is already taking care of the cleanup,
413+ // so no call to clear() needed here.
414 }
415
416 void BaseDropdown::set_height(int height) {
417- max_list_height_ = height - base_height();
418+ max_list_height_ = height - base_height(button_dimension_);
419 layout();
420 }
421
422 void BaseDropdown::layout() {
423- const int base_h = base_height();
424- const int w = get_w();
425+ const int base_h = base_height(button_dimension_);
426+ const int w = type_ == DropdownType::kTextual ? get_w() : button_dimension_;
427 button_box_.set_size(w, base_h);
428- display_button_.set_desired_size(w - 24, base_h);
429+ display_button_.set_desired_size(
430+ type_ == DropdownType::kTextual ? w - button_dimension_ : w, base_h);
431 int new_list_height =
432- std::min(static_cast<int>(list_.size()) * list_.get_lineheight(), max_list_height_);
433- list_.set_size(w, new_list_height);
434+ std::min(static_cast<int>(list_->size()) * list_->get_lineheight(), max_list_height_);
435+ list_->set_size(type_ == DropdownType::kTextual ? w : list_width_, new_list_height);
436 set_desired_size(w, base_h);
437+
438+ // Update list position. The list is hooked into the highest parent that we can get so that we
439+ // can drop down outside the panel. Positioning breaks down with TabPanels, so we exclude them.
440+ UI::Panel* parent = get_parent();
441+ int new_list_y = get_y() + get_h() + parent->get_y();
442+ int new_list_x = get_x() + parent->get_x();
443+ while (parent->get_parent() && !is_a(UI::TabPanel, parent->get_parent())) {
444+ parent = parent->get_parent();
445+ new_list_y += parent->get_y();
446+ new_list_x += parent->get_x();
447+ }
448+ list_->set_pos(Vector2i(new_list_x, new_list_y));
449 }
450
451 void BaseDropdown::add(const std::string& name,
452@@ -110,7 +152,8 @@
453 const Image* pic,
454 const bool select_this,
455 const std::string& tooltip_text) {
456- list_.add(name, value, pic, select_this, tooltip_text);
457+ assert(pic != nullptr || type_ != DropdownType::kPictorial);
458+ list_->add(name, value, pic, select_this, tooltip_text);
459 if (select_this) {
460 set_value();
461 }
462@@ -118,16 +161,25 @@
463 }
464
465 bool BaseDropdown::has_selection() const {
466- return list_.has_selection();
467+ return list_->has_selection();
468 }
469
470 uint32_t BaseDropdown::get_selected() const {
471- return list_.get_selected();
472+ return list_->get_selected();
473+}
474+
475+void BaseDropdown::select(uint32_t entry) {
476+ assert(entry < list_->size());
477+ list_->select(entry);
478+ current_selection_ = list_->selection_index();
479+ update();
480 }
481
482 void BaseDropdown::set_label(const std::string& text) {
483 label_ = text;
484- display_button_.set_title(label_);
485+ if (type_ == DropdownType::kTextual) {
486+ display_button_.set_title(label_);
487+ }
488 }
489
490 void BaseDropdown::set_tooltip(const std::string& text) {
491@@ -136,68 +188,98 @@
492 }
493
494 void BaseDropdown::set_enabled(bool on) {
495+ is_enabled_ = on;
496 set_can_focus(on);
497- push_button_.set_enabled(on);
498- push_button_.set_tooltip(on ? pgettext("dropdown", "Select Item") : "");
499+ if (push_button_ != nullptr) {
500+ push_button_->set_enabled(on);
501+ push_button_->set_tooltip(on ? pgettext("dropdown", "Select Item") : "");
502+ }
503 display_button_.set_enabled(on);
504- list_.set_visible(false);
505+ list_->set_visible(false);
506+}
507+
508+void BaseDropdown::set_disable_style(UI::ButtonDisableStyle disable_style) {
509+ display_button_.set_disable_style(disable_style);
510+}
511+
512+bool BaseDropdown::is_expanded() const {
513+ return list_->is_visible();
514 }
515
516 void BaseDropdown::set_pos(Vector2i point) {
517 UI::Panel::set_pos(point);
518- list_.set_pos(Vector2i(point.x, point.y + get_h()));
519+ list_->set_pos(Vector2i(point.x, point.y + get_h()));
520 }
521
522 void BaseDropdown::clear() {
523- list_.clear();
524- list_.set_size(list_.get_w(), 0);
525+ list_->clear();
526+ current_selection_ = list_->selection_index();
527+ list_->set_size(list_->get_w(), 0);
528+ list_->set_visible(false);
529 set_layout_toplevel(false);
530 }
531
532 void BaseDropdown::think() {
533- if (list_.is_visible()) {
534+ if (list_->is_visible()) {
535 // Autocollapse with a bit of tolerance for the mouse movement to make it less fiddly.
536- if (!(has_focus() || list_.has_focus()) || is_mouse_away()) {
537+ if (!(has_focus() || list_->has_focus()) || is_mouse_away()) {
538 toggle_list();
539 }
540 }
541 }
542
543 uint32_t BaseDropdown::size() const {
544- return list_.size();
545+ return list_->size();
546 }
547
548-void BaseDropdown::set_value() {
549- const std::string name = list_.has_selection() ? list_.get_selected_name() :
550- /** TRANSLATORS: Selection in Dropdown menus. */
551+void BaseDropdown::update() {
552+ const std::string name = list_->has_selection() ?
553+ list_->get_selected_name() :
554+ /** TRANSLATORS: Selection in Dropdown menus. */
555 pgettext("dropdown", "Not Selected");
556
557- if (label_.empty()) {
558- display_button_.set_title(name);
559+ if (type_ == DropdownType::kTextual) {
560+ if (label_.empty()) {
561+ display_button_.set_title(name);
562+ } else {
563+ /** TRANSLATORS: Label: Value. */
564+ display_button_.set_title((boost::format(_("%1%: %2%")) % label_ % (name)).str());
565+ }
566+ display_button_.set_tooltip(list_->has_selection() ? list_->get_selected_tooltip() :
567+ tooltip_);
568 } else {
569- /** TRANSLATORS: Label: Value. */
570- display_button_.set_title((boost::format(_("%1%: %2%")) % label_ % (name)).str());
571+ display_button_.set_pic(list_->has_selection() ?
572+ list_->get_selected_image() :
573+ g_gr->images().get("images/ui_basic/different.png"));
574+ display_button_.set_tooltip((boost::format(_("%1%: %2%")) % label_ % name).str());
575 }
576- display_button_.set_tooltip(list_.has_selection() ? list_.get_selected_tooltip() : tooltip_);
577+}
578+
579+void BaseDropdown::set_value() {
580+ update();
581 selected();
582- current_selection_ = list_.selection_index();
583+ current_selection_ = list_->selection_index();
584 }
585
586 void BaseDropdown::toggle_list() {
587- list_.set_visible(!list_.is_visible());
588- if (list_.is_visible()) {
589- list_.move_to_top();
590+ if (!is_enabled_) {
591+ list_->set_visible(false);
592+ return;
593+ }
594+ list_->set_visible(!list_->is_visible());
595+ if (list_->is_visible()) {
596+ list_->move_to_top();
597 focus();
598 }
599 // Make sure that the list covers and deactivates the elements below it
600- set_layout_toplevel(list_.is_visible());
601+ set_layout_toplevel(list_->is_visible());
602 }
603
604 bool BaseDropdown::is_mouse_away() const {
605 return (get_mouse_position().x + mouse_tolerance_) < 0 ||
606- get_mouse_position().x > (get_w() + mouse_tolerance_) ||
607+ get_mouse_position().x > (list_->get_w() + mouse_tolerance_) ||
608 (get_mouse_position().y + mouse_tolerance_ / 2) < 0 ||
609- get_mouse_position().y > (get_h() + list_.get_h() + mouse_tolerance_);
610+ get_mouse_position().y > (get_h() + list_->get_h() + mouse_tolerance_);
611 }
612
613 bool BaseDropdown::handle_key(bool down, SDL_Keysym code) {
614@@ -205,18 +287,18 @@
615 switch (code.sym) {
616 case SDLK_KP_ENTER:
617 case SDLK_RETURN:
618- if (list_.is_visible()) {
619+ if (list_->is_visible()) {
620 set_value();
621 }
622 case SDLK_ESCAPE:
623- if (list_.is_visible()) {
624- list_.select(current_selection_);
625+ if (list_->is_visible()) {
626+ list_->select(current_selection_);
627 toggle_list();
628 return true;
629 }
630 break;
631 case SDLK_DOWN:
632- if (!list_.is_visible() && !is_mouse_away()) {
633+ if (!list_->is_visible() && !is_mouse_away()) {
634 toggle_list();
635 return true;
636 }
637@@ -225,8 +307,8 @@
638 break; // not handled
639 }
640 }
641- if (list_.is_visible()) {
642- return list_.handle_key(down, code);
643+ if (list_->is_visible()) {
644+ return list_->handle_key(down, code);
645 }
646 return false;
647 }
648
649=== modified file 'src/ui_basic/dropdown.h'
650--- src/ui_basic/dropdown.h 2017-01-25 18:55:59 +0000
651+++ src/ui_basic/dropdown.h 2017-04-03 12:24:35 +0000
652@@ -34,23 +34,30 @@
653
654 namespace UI {
655
656+enum class DropdownType { kTextual, kPictorial };
657+
658 /// Implementation for a dropdown menu that lets the user select a value.
659 class BaseDropdown : public Panel {
660 protected:
661 /// \param parent the parent panel
662 /// \param x the x-position within 'parent'
663 /// \param y the y-position within 'parent'
664- /// \param w the dropdown's width
665- /// \param h the maximum height for the dropdown list
666+ /// \param list_w the dropdown's width
667+ /// \param list_h the maximum height for the dropdown list
668+ /// \param button_dimension the width of the push button in textual dropdowns. For pictorial
669+ /// dropdowns, this is both the width and the height of the button.
670 /// \param label a label to prefix to the selected entry on the display button.
671+ /// \param type whether this is a textual or pictorial dropdown
672 /// \param background the background image for this dropdown
673 /// \param button_background the background image all buttons in this dropdown
674 BaseDropdown(Panel* parent,
675 int32_t x,
676 int32_t y,
677- uint32_t w,
678- uint32_t h,
679+ uint32_t list_w,
680+ uint32_t list_h,
681+ int button_dimension,
682 const std::string& label,
683+ const DropdownType type,
684 const Image* background,
685 const Image* button_background);
686 ~BaseDropdown();
687@@ -71,6 +78,22 @@
688 /// Enables/disables the dropdown selection.
689 void set_enabled(bool on);
690
691+ /// Whether the dropdown selection is enabled.
692+ bool is_enabled() const {
693+ return is_enabled_;
694+ }
695+
696+ /// Which visual style to use for disabled pictorial dropdowns.
697+ void set_disable_style(UI::ButtonDisableStyle disable_style);
698+
699+ /// Whether the dropdown has no elements to select.
700+ bool empty() {
701+ return size() == 0;
702+ }
703+
704+ /// Whether the dropdown has been opened by the user.
705+ bool is_expanded() const;
706+
707 /// Move the dropdown. The dropdown's position is relative to the parent in
708 /// pixels.
709 void set_pos(Vector2i point) override;
710@@ -87,7 +110,7 @@
711 /// Add an element to the list
712 /// \param name the display name of the entry
713 /// \param value the index of the entry
714- /// \param pic an image to illustrate the entry
715+ /// \param pic an image to illustrate the entry. Can be nullptr for textual dropdowns.
716 /// \param select_this whether this element should be selected
717 /// \param tooltip_text a tooltip for this entry
718 void add(const std::string& name,
719@@ -99,6 +122,9 @@
720 /// \return the index of the selected element
721 uint32_t get_selected() const;
722
723+ /// Select the entry. Assumes that it exists. Does not trigger the 'selected' signal.
724+ void select(uint32_t entry);
725+
726 /// Removes all elements from the list.
727 void clear();
728
729@@ -109,6 +135,9 @@
730 private:
731 void layout() override;
732
733+ /// Updates the buttons
734+ void update();
735+
736 /// Updates the title and tooltip of the display button and triggers a 'selected' signal.
737 void set_value();
738 /// Toggles the dropdown list on and off.
739@@ -118,14 +147,19 @@
740 bool is_mouse_away() const;
741
742 int max_list_height_;
743+ int list_width_;
744+ int button_dimension_;
745 const int mouse_tolerance_; // Allow mouse outside the panel a bit before autocollapse
746 UI::Box button_box_;
747- UI::Button push_button_;
748+ UI::Button* push_button_; // Only used in textual dropdowns
749 UI::Button display_button_;
750- UI::Listselect<uintptr_t> list_;
751+ // The list needs to be a pointer for destruction, because we hook into the highest parent that we can get.
752+ UI::Listselect<uintptr_t>* list_;
753 std::string label_;
754 std::string tooltip_;
755 uint32_t current_selection_;
756+ DropdownType type_;
757+ bool is_enabled_;
758 };
759
760 /// A dropdown menu that lets the user select a value of the datatype 'Entry'.
761@@ -134,29 +168,44 @@
762 /// \param parent the parent panel
763 /// \param x the x-position within 'parent'
764 /// \param y the y-position within 'parent'
765- /// \param w the dropdown's width
766- /// \param h the maximum height for the dropdown list
767+ /// \param list_w the dropdown's width
768+ /// \param list_h the maximum height for the dropdown list
769+ /// \param button_dimension the width of the push button in textual dropdowns. For pictorial
770+ /// dropdowns, this is both the width and the height of the button.
771 /// \param label a label to prefix to the selected entry on the display button.
772+ /// \param type whether this is a textual or pictorial dropdown
773 /// \param background the background image for this dropdown
774 /// \param button_background the background image all buttons in this dropdown
775 Dropdown(Panel* parent,
776 int32_t x,
777 int32_t y,
778- uint32_t w,
779- uint32_t h,
780+ uint32_t list_w,
781+ uint32_t list_h,
782+ int button_dimension,
783 const std::string& label,
784+ const DropdownType type = DropdownType::kTextual,
785 const Image* background = g_gr->images().get("images/ui_basic/but1.png"),
786 const Image* button_background = g_gr->images().get("images/ui_basic/but3.png"))
787- : BaseDropdown(parent, x, y, w, h, label, background, button_background) {
788+ : BaseDropdown(parent,
789+ x,
790+ y,
791+ list_w,
792+ list_h,
793+ button_dimension,
794+ label,
795+ type,
796+ background,
797+ button_background) {
798 }
799 ~Dropdown() {
800- clear();
801+ entry_cache_.clear();
802 }
803
804 /// Add an element to the list
805 /// \param name the display name of the entry
806 /// \param value the value for the entry
807- /// \param pic an image to illustrate the entry
808+ /// \param pic an image to illustrate the entry. Can be nullptr in textual dropdowns
809+ /// only.
810 /// \param select_this whether this element should be selected
811 /// \param tooltip_text a tooltip for this entry
812 void add(const std::string& name,
813@@ -173,9 +222,19 @@
814 return *entry_cache_[BaseDropdown::get_selected()];
815 }
816
817+ /// Select the entry if it exists. Does not trigger the 'selected' signal.
818+ void select(const Entry& entry) {
819+ for (uint32_t i = 0; i < entry_cache_.size(); ++i) {
820+ if (entry == *entry_cache_[i]) {
821+ BaseDropdown::select(i);
822+ }
823+ }
824+ }
825+
826 /// Removes all elements from the list.
827 void clear() {
828 BaseDropdown::clear();
829+ entry_cache_.clear();
830 }
831
832 private:
833
834=== modified file 'src/ui_basic/listselect.cc'
835--- src/ui_basic/listselect.cc 2017-02-27 13:48:29 +0000
836+++ src/ui_basic/listselect.cc 2017-04-03 12:24:35 +0000
837@@ -305,6 +305,14 @@
838 return entry_records_[selection_]->tooltip;
839 }
840
841+/**
842+ * \return The image for the currently selected entry. Requires an entry to have been selected.
843+ */
844+const Image* BaseListselect::get_selected_image() const {
845+ assert(selection_ < entry_records_.size());
846+ return entry_records_[selection_]->pic;
847+}
848+
849 int BaseListselect::get_lineheight() const {
850 return lineheight_ + kMargin;
851 }
852@@ -322,6 +330,19 @@
853 if (scrollbar_.is_enabled() && selection_mode_ == ListselectLayout::kDropdown) {
854 scrollbar_.set_steps(steps + kMargin);
855 }
856+ // For dropdowns, autoincrease width
857+ if (selection_mode_ == ListselectLayout::kDropdown) {
858+ for (size_t i = 0; i < entry_records_.size(); ++i) {
859+ const EntryRecord& er = *entry_records_[i];
860+ const Image* entry_text_im = UI::g_fh1->render(as_uifont(
861+ richtext_escape(er.name), UI_FONT_SIZE_SMALL, er.use_clr ? er.clr : UI_FONT_CLR_FG));
862+ int picw = max_pic_width_ ? max_pic_width_ + 10 : 0;
863+ int difference = entry_text_im->width() + picw + 8 - get_eff_w();
864+ if (difference > 0) {
865+ set_size(get_w() + difference, get_h());
866+ }
867+ }
868+ }
869 }
870
871 /**
872@@ -340,6 +361,8 @@
873
874 if (selection_mode_ == ListselectLayout::kDropdown) {
875 RGBAColor black(0, 0, 0, 255);
876+ // top edge
877+ dst.brighten_rect(Rectf(0.f, 0.f, get_w(), 2.f), BUTTON_EDGE_BRIGHT_FACTOR / 4);
878 // left edge
879 dst.brighten_rect(Rectf(0.f, 0.f, 2.f, get_h()), BUTTON_EDGE_BRIGHT_FACTOR);
880 // bottom edge
881
882=== modified file 'src/ui_basic/listselect.h'
883--- src/ui_basic/listselect.h 2017-02-12 09:10:57 +0000
884+++ src/ui_basic/listselect.h 2017-04-03 12:24:35 +0000
885@@ -105,6 +105,7 @@
886
887 const std::string& get_selected_name() const;
888 const std::string& get_selected_tooltip() const;
889+ const Image* get_selected_image() const;
890
891 void set_background(const Image* background) {
892 background_ = background;
893
894=== modified file 'src/ui_fsmenu/launch_spg.cc'
895--- src/ui_fsmenu/launch_spg.cc 2017-02-26 12:16:09 +0000
896+++ src/ui_fsmenu/launch_spg.cc 2017-04-03 12:24:35 +0000
897@@ -65,6 +65,7 @@
898 get_h() * 4 / 10 + buth_,
899 butw_,
900 get_h() - get_h() * 4 / 10 - buth_,
901+ buth_,
902 ""),
903 back_(this,
904 "back",
905
906=== modified file 'src/ui_fsmenu/options.cc'
907--- src/ui_fsmenu/options.cc 2017-02-28 20:07:07 +0000
908+++ src/ui_fsmenu/options.cc 2017-04-03 12:24:35 +0000
909@@ -138,12 +138,14 @@
910 0,
911 100, // 100 is arbitrary, will be resized in layout().
912 100, // 100 is arbitrary, will be resized in layout().
913+ 24,
914 _("Language")),
915 resolution_dropdown_(&box_interface_,
916 0,
917 0,
918 100, // 100 is arbitrary, will be resized in layout().
919 100, // 100 is arbitrary, will be resized in layout().
920+ 24,
921 _("In-game resolution")),
922
923 fullscreen_(&box_interface_, Vector2i(0, 0), _("Fullscreen"), "", 0),
924
925=== modified file 'src/wui/building_statistics_menu.cc'
926--- src/wui/building_statistics_menu.cc 2017-02-26 12:16:09 +0000
927+++ src/wui/building_statistics_menu.cc 2017-04-03 12:24:35 +0000
928@@ -331,6 +331,7 @@
929 kBuildGridCellHeight, g_gr->images().get("images/ui_basic/but1.png"),
930 descr.representative_image(&iplayer().get_player()->get_playercolor()), "",
931 UI::Button::Style::kFlat);
932+ building_buttons_[id]->set_disable_style(UI::ButtonDisableStyle::kMonochrome | UI::ButtonDisableStyle::kFlat);
933 button_box->add(building_buttons_[id]);
934
935 owned_labels_[id] =
936
937=== modified file 'src/wui/multiplayersetupgroup.cc'
938--- src/wui/multiplayersetupgroup.cc 2017-02-26 11:00:07 +0000
939+++ src/wui/multiplayersetupgroup.cc 2017-04-03 12:24:35 +0000
940@@ -22,6 +22,7 @@
941 #include <string>
942
943 #include <boost/format.hpp>
944+#include <boost/lexical_cast.hpp>
945
946 #include "ai/computer_player.h"
947 #include "base/i18n.h"
948@@ -33,9 +34,11 @@
949 #include "logic/game.h"
950 #include "logic/game_settings.h"
951 #include "logic/map_objects/tribes/tribe_descr.h"
952+#include "logic/map_objects/tribes/tribes.h"
953 #include "logic/player.h"
954 #include "ui_basic/button.h"
955 #include "ui_basic/checkbox.h"
956+#include "ui_basic/dropdown.h"
957 #include "ui_basic/icon.h"
958 #include "ui_basic/scrollbar.h"
959 #include "ui_basic/textarea.h"
960@@ -145,20 +148,23 @@
961 int32_t const w,
962 int32_t const h,
963 GameSettingsProvider* const settings,
964- NetworkPlayerSettingsBackend* const npsb,
965- std::map<std::string, const Image*>& tp,
966- std::map<std::string, std::string>& tn)
967+ NetworkPlayerSettingsBackend* const npsb)
968 : UI::Box(parent, 0, 0, UI::Box::Horizontal, w, h),
969 player(nullptr),
970 type(nullptr),
971- tribe(nullptr),
972 init(nullptr),
973 s(settings),
974 n(npsb),
975 id_(id),
976- tribepics_(tp),
977- tribenames_(tn) {
978+ tribes_dropdown_(this, 0, 0, 50, 200, h, _("Tribe"), UI::DropdownType::kPictorial),
979+ last_state_(PlayerSettings::stateClosed),
980+ last_player_amount_(0) {
981 set_size(w, h);
982+ tribes_dropdown_.set_visible(false);
983+ tribes_dropdown_.set_enabled(false);
984+ tribes_dropdown_.selected.connect(
985+ boost::bind(&MultiPlayerPlayerGroup::set_tribe_or_shared_in, boost::ref(*this)));
986+
987 const Image* player_image =
988 playercolor_image(id, g_gr->images().get("images/players/player_position_menu.png"),
989 g_gr->images().get("images/players/player_position_menu_pc.png"));
990@@ -170,11 +176,7 @@
991 type->sigclicked.connect(
992 boost::bind(&MultiPlayerPlayerGroup::toggle_type, boost::ref(*this)));
993 add(type);
994- tribe = new UI::Button(
995- this, "player_tribe", 0, 0, h, h, g_gr->images().get("images/ui_basic/but1.png"), "");
996- tribe->sigclicked.connect(
997- boost::bind(&MultiPlayerPlayerGroup::toggle_tribe, boost::ref(*this)));
998- add(tribe);
999+ add(&tribes_dropdown_);
1000 init = new UI::Button(this, "player_init", 0, 0, w - 4 * h, h,
1001 g_gr->images().get("images/ui_basic/but1.png"), "");
1002 init->sigclicked.connect(
1003@@ -192,9 +194,23 @@
1004 n->toggle_type(id_);
1005 }
1006
1007- /// Toggle through the tribes + handle shared in players
1008- void toggle_tribe() {
1009- n->toggle_tribe(id_);
1010+ /// This will update the game settings for the tribe or shared_in with the value
1011+ /// currently selected in the tribes dropdown.
1012+ void set_tribe_or_shared_in() {
1013+ n->set_block_tribe_selection(true);
1014+ tribes_dropdown_.set_disable_style(s->settings().players[id_].state ==
1015+ PlayerSettings::stateShared ?
1016+ UI::ButtonDisableStyle::kPermpressed :
1017+ UI::ButtonDisableStyle::kMonochrome);
1018+ if (tribes_dropdown_.has_selection()) {
1019+ if (s->settings().players[id_].state == PlayerSettings::stateShared) {
1020+ n->set_shared_in(
1021+ id_, boost::lexical_cast<unsigned int>(tribes_dropdown_.get_selected()));
1022+ } else {
1023+ n->set_tribe(id_, tribes_dropdown_.get_selected());
1024+ }
1025+ }
1026+ n->set_block_tribe_selection(false);
1027 }
1028
1029 /// Toggle through the initializations
1030@@ -207,6 +223,107 @@
1031 n->toggle_team(id_);
1032 }
1033
1034+ /// Helper function to cast shared_in for use in the dropdown.
1035+ const std::string shared_in_as_string(uint8_t shared_in) {
1036+ return boost::lexical_cast<std::string>(static_cast<unsigned int>(shared_in));
1037+ }
1038+
1039+ /// Update the tribes dropdown from the server settings if the server setting changed.
1040+ /// This will keep the host and client UIs in sync.
1041+ void update_tribes_dropdown(const PlayerSettings& player_setting) {
1042+ if (player_setting.state == PlayerSettings::stateClosed ||
1043+ player_setting.state == PlayerSettings::stateOpen) {
1044+ return;
1045+ }
1046+ if (!tribes_dropdown_.is_visible()) {
1047+ tribes_dropdown_.set_visible(true);
1048+ }
1049+ if (!tribes_dropdown_.is_expanded() && !n->tribe_selection_blocked &&
1050+ tribes_dropdown_.has_selection()) {
1051+ const std::string selected_tribe = tribes_dropdown_.get_selected();
1052+ if (player_setting.state == PlayerSettings::stateShared) {
1053+ const std::string shared_in = shared_in_as_string(player_setting.shared_in);
1054+ if (shared_in != selected_tribe) {
1055+ tribes_dropdown_.select(shared_in);
1056+ }
1057+ } else {
1058+ if (player_setting.random_tribe) {
1059+ if (selected_tribe != "random") {
1060+ tribes_dropdown_.select("random");
1061+ }
1062+ } else if (selected_tribe != player_setting.tribe) {
1063+ tribes_dropdown_.select(player_setting.tribe);
1064+ }
1065+ }
1066+ }
1067+ }
1068+
1069+ /// If the map was changed or the selection mode changed between shared_in and tribe, rebuild the
1070+ /// dropdown.
1071+ void rebuild_tribes_dropdown(const GameSettings& settings) {
1072+ const PlayerSettings& player_setting = settings.players[id_];
1073+
1074+ if (player_setting.state == PlayerSettings::stateClosed ||
1075+ player_setting.state == PlayerSettings::stateOpen) {
1076+ return;
1077+ }
1078+
1079+ if (tribes_dropdown_.empty() || last_player_amount_ != settings.players.size() ||
1080+ ((player_setting.state == PlayerSettings::stateShared ||
1081+ last_state_ == PlayerSettings::stateShared) &&
1082+ player_setting.state != last_state_)) {
1083+ tribes_dropdown_.clear();
1084+
1085+ // We need to see the playercolor if setting shared_in is disabled
1086+ tribes_dropdown_.set_disable_style(player_setting.state == PlayerSettings::stateShared ?
1087+ UI::ButtonDisableStyle::kPermpressed :
1088+ UI::ButtonDisableStyle::kMonochrome);
1089+
1090+ if (player_setting.state == PlayerSettings::stateShared) {
1091+ for (size_t i = 0; i < settings.players.size(); ++i) {
1092+ if (i != id_) {
1093+ // TODO(GunChleoc): Do not add players that are also shared_in.
1094+ const Image* player_image = playercolor_image(
1095+ i, g_gr->images().get("images/players/player_position_menu.png"),
1096+ g_gr->images().get("images/players/player_position_menu_pc.png"));
1097+ assert(player_image);
1098+ const std::string player_name =
1099+ /** TRANSLATORS: This is an option in multiplayer setup for sharing
1100+ another player's starting position. */
1101+ (boost::format(_("Shared in Player %u")) % static_cast<unsigned int>(i + 1))
1102+ .str();
1103+ tribes_dropdown_.add(
1104+ player_name, shared_in_as_string(i + 1), player_image, false, player_name);
1105+ }
1106+ }
1107+ int shared_in = 0;
1108+ while (shared_in == id_) {
1109+ ++shared_in;
1110+ }
1111+ tribes_dropdown_.select(shared_in_as_string(shared_in + 1));
1112+ tribes_dropdown_.set_enabled(tribes_dropdown_.size() > 1);
1113+ } else {
1114+ {
1115+ i18n::Textdomain td("tribes");
1116+ for (const TribeBasicInfo& tribeinfo : Widelands::get_all_tribeinfos()) {
1117+ tribes_dropdown_.add(_(tribeinfo.descname), tribeinfo.name,
1118+ g_gr->images().get(tribeinfo.icon), false,
1119+ tribeinfo.tooltip);
1120+ }
1121+ }
1122+ tribes_dropdown_.add(pgettext("tribe", "Random"), "random",
1123+ g_gr->images().get("images/ui_fsmenu/random.png"), false,
1124+ _("The tribe will be selected at random"));
1125+ if (player_setting.random_tribe) {
1126+ tribes_dropdown_.select("random");
1127+ } else {
1128+ tribes_dropdown_.select(player_setting.tribe);
1129+ }
1130+ }
1131+ }
1132+ last_player_amount_ = settings.players.size();
1133+ }
1134+
1135 /// Refresh all user interfaces
1136 void refresh() {
1137 const GameSettings& settings = s->settings();
1138@@ -225,16 +342,17 @@
1139 bool tribeaccess = s->can_change_player_tribe(id_);
1140 bool const initaccess = s->can_change_player_init(id_);
1141 bool teamaccess = s->can_change_player_team(id_);
1142-
1143 type->set_enabled(typeaccess);
1144+
1145+ rebuild_tribes_dropdown(settings);
1146+
1147 if (player_setting.state == PlayerSettings::stateClosed) {
1148 type->set_tooltip(_("Closed"));
1149 type->set_pic(g_gr->images().get("images/ui_basic/stop.png"));
1150 team->set_visible(false);
1151 team->set_enabled(false);
1152- tribe->set_visible(false);
1153- tribe->set_enabled(false);
1154- tribe->set_style(UI::Button::Style::kRaised);
1155+ tribes_dropdown_.set_visible(false);
1156+ tribes_dropdown_.set_enabled(false);
1157 init->set_visible(false);
1158 init->set_enabled(false);
1159 return;
1160@@ -243,30 +361,25 @@
1161 type->set_pic(g_gr->images().get("images/ui_basic/continue.png"));
1162 team->set_visible(false);
1163 team->set_enabled(false);
1164- tribe->set_visible(false);
1165- tribe->set_enabled(false);
1166- tribe->set_style(UI::Button::Style::kRaised);
1167+ tribes_dropdown_.set_visible(false);
1168+ tribes_dropdown_.set_enabled(false);
1169 init->set_visible(false);
1170 init->set_enabled(false);
1171 return;
1172 } else if (player_setting.state == PlayerSettings::stateShared) {
1173 type->set_tooltip(_("Shared in"));
1174 type->set_pic(g_gr->images().get("images/ui_fsmenu/shared_in.png"));
1175- const Image* player_image =
1176- playercolor_image(player_setting.shared_in - 1,
1177- g_gr->images().get("images/players/player_position_menu.png"),
1178- g_gr->images().get("images/players/player_position_menu_pc.png"));
1179- assert(player_image);
1180- tribe->set_pic(player_image);
1181- tribe->set_tooltip(
1182- (boost::format(_("Player %u")) % static_cast<unsigned int>(player_setting.shared_in))
1183- .str());
1184+
1185+ update_tribes_dropdown(player_setting);
1186+
1187+ if (tribes_dropdown_.is_enabled() != initaccess) {
1188+ tribes_dropdown_.set_enabled(initaccess && !n->tribe_selection_blocked &&
1189+ tribes_dropdown_.size() > 1);
1190+ }
1191
1192 team->set_visible(false);
1193 team->set_enabled(false);
1194- // Flat ~= icon
1195- tribe->set_style(initaccess ? UI::Button::Style::kRaised : UI::Button::Style::kFlat);
1196- tribe->set_enabled(true);
1197+
1198 } else {
1199 std::string title;
1200 std::string pic = "images/";
1201@@ -292,25 +405,12 @@
1202 }
1203 type->set_tooltip(title.c_str());
1204 type->set_pic(g_gr->images().get(pic));
1205- if (player_setting.random_tribe) {
1206- std::string random = pgettext("tribe", "Random");
1207- if (!tribenames_["random"].size())
1208- tribepics_[random] = g_gr->images().get("images/ui_fsmenu/random.png");
1209- tribe->set_tooltip(random.c_str());
1210- tribe->set_pic(tribepics_[random]);
1211- } else {
1212- if (!tribenames_[player_setting.tribe].size()) {
1213- // get tribes name and picture
1214- i18n::Textdomain td("tribes");
1215- for (const TribeBasicInfo& tribeinfo : settings.tribes) {
1216- tribenames_[tribeinfo.name] = _(tribeinfo.descname);
1217- tribepics_[tribeinfo.name] = g_gr->images().get(tribeinfo.icon);
1218- }
1219- }
1220- tribe->set_tooltip(tribenames_[player_setting.tribe].c_str());
1221- tribe->set_pic(tribepics_[player_setting.tribe]);
1222+
1223+ update_tribes_dropdown(player_setting);
1224+
1225+ if (tribes_dropdown_.is_enabled() != tribeaccess) {
1226+ tribes_dropdown_.set_enabled(tribeaccess && !n->tribe_selection_blocked);
1227 }
1228- tribe->set_style(UI::Button::Style::kRaised);
1229
1230 if (player_setting.team) {
1231 team->set_title(std::to_string(static_cast<unsigned int>(player_setting.team)));
1232@@ -319,10 +419,8 @@
1233 }
1234 team->set_visible(true);
1235 team->set_enabled(teamaccess);
1236- tribe->set_enabled(tribeaccess);
1237 }
1238 init->set_enabled(initaccess);
1239- tribe->set_visible(true);
1240 init->set_visible(true);
1241
1242 if (settings.scenario)
1243@@ -342,18 +440,19 @@
1244 }
1245 }
1246 }
1247+ last_state_ = player_setting.state;
1248 }
1249
1250 UI::Icon* player;
1251 UI::Button* type;
1252- UI::Button* tribe;
1253 UI::Button* init;
1254 UI::Button* team;
1255 GameSettingsProvider* const s;
1256 NetworkPlayerSettingsBackend* const n;
1257 uint8_t const id_;
1258- std::map<std::string, const Image*>& tribepics_;
1259- std::map<std::string, std::string>& tribenames_;
1260+ UI::Dropdown<std::string> tribes_dropdown_; /// Select the tribe or shared_in player.
1261+ PlayerSettings::State last_state_; /// The dropdown needs updating if this changes
1262+ size_t last_player_amount_; /// The dropdown needs rebuilding if this changes
1263 };
1264
1265 MultiPlayerSetupGroup::MultiPlayerSetupGroup(UI::Panel* const parent,
1266@@ -411,10 +510,9 @@
1267 playerbox.set_size(w * 9 / 15, h - buth);
1268 multi_player_player_groups.resize(kMaxPlayers);
1269 for (uint8_t i = 0; i < multi_player_player_groups.size(); ++i) {
1270- multi_player_player_groups.at(i) = new MultiPlayerPlayerGroup(
1271- &playerbox, i, 0, 0, playerbox.get_w(), buth, s, npsb.get(), tribepics_, tribenames_);
1272- playerbox.add(
1273- multi_player_player_groups.at(i), UI::Box::Resizing::kAlign, UI::Align::kCenter);
1274+ multi_player_player_groups.at(i) =
1275+ new MultiPlayerPlayerGroup(&playerbox, i, 0, 0, playerbox.get_w(), buth, s, npsb.get());
1276+ playerbox.add(multi_player_player_groups.at(i));
1277 }
1278 refresh();
1279 }

Subscribers

People subscribed via source and target branches

to status/vote changes: