Merge lp:~widelands-dev/widelands/bug-536489-pictorial-dropdown into lp:widelands
- bug-536489-pictorial-dropdown
- Merge into trunk
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 | ||||
Related bugs: |
|
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 : | # |
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 | } |
Continuous integration builds have changed state:
Travis build 2051. State: passed. Details: https:/ /travis- ci.org/ widelands/ widelands/ builds/ 208120603. /ci.appveyor. com/project/ widelands- dev/widelands/ build/_ widelands_ dev_widelands_ bug_536489_ pictorial_ dropdown- 1886.
Appveyor build 1886. State: success. Details: https:/