← Back to team overview

widelands-dev team mailing list archive

[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