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

Proposed by GunChleoc
Status: Merged
Merged at revision: 8158
Proposed branch: lp:~widelands-dev/widelands/bug-536489-dropdown
Merge into: lp:widelands
Diff against target: 1660 lines (+726/-199)
23 files modified
src/editor/ui_menus/main_menu_map_options.cc (+1/-1)
src/ui_basic/CMakeLists.txt (+2/-0)
src/ui_basic/button.cc (+9/-3)
src/ui_basic/button.h (+1/-2)
src/ui_basic/checkbox.cc (+3/-1)
src/ui_basic/dropdown.cc (+215/-0)
src/ui_basic/dropdown.h (+166/-0)
src/ui_basic/listselect.cc (+85/-34)
src/ui_basic/listselect.h (+45/-8)
src/ui_basic/panel.cc (+1/-1)
src/ui_basic/panel.h (+1/-1)
src/ui_basic/progressbar.cc (+2/-2)
src/ui_basic/scrollbar.cc (+2/-1)
src/ui_basic/slider.cc (+4/-0)
src/ui_basic/tabpanel.cc (+10/-12)
src/ui_basic/window.cc (+17/-13)
src/ui_fsmenu/launch_mpg.cc (+1/-1)
src/ui_fsmenu/launch_spg.cc (+101/-63)
src/ui_fsmenu/launch_spg.h (+20/-6)
src/ui_fsmenu/mapselect.cc (+2/-1)
src/ui_fsmenu/options.cc (+34/-40)
src/ui_fsmenu/options.h (+3/-8)
src/wui/game_objectives_menu.cc (+1/-1)
To merge this branch: bzr merge lp:~widelands-dev/widelands/bug-536489-dropdown
Reviewer Review Type Date Requested Status
SirVer Approve
GunChleoc Needs Resubmitting
kaputtnik (community) testing Approve
Klaus Halfmann compile, test Approve
Review via email: mp+306303@code.launchpad.net

Commit message

Implemented a textual dropdown menu. It is used in:
- Options: Screen resolution
- Options: Language. Got rid of the extra Language tab and moved it to the Interface tab.
- Launch Single Player Game: Win Condition

Description of the change

My first branch for Build 20 ;)

Implemented a textual dropdown menu. This is already quite a bit of code, so I'll do the win condition for multiplayer in a separate branch. We will also want pictorial dropdowns.

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

Continuous integration builds have changed state:

Travis build 1345. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/161548228.
Appveyor build 1187. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_bug_536489_dropdown-1187.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

This Drodown is missing a feature found in all normal GUIs:

when you click outside the dropdown it will NOT collapse,
allowing the user to click somewhere else.
This implementation will do nothing in this case.
So if I forgot that the Dropdown is open and click somwhere else
It seems stuck as I get no visual feedback what to do.
This may frustrate players upto a level where theey will kill the game.

In additon it does not collapse when pressing Escape,
instead the complete dialog is canceled.

Sorry Gun.

Good work otherwise. I will continue to review the usage and code.

review: Needs Fixing (compile, test)
Revision history for this message
GunChleoc (gunchleoc) wrote :

Good points. The escape key thing should be easy to do, for the collapse when clicked outside of it I will need to dig around a bit.

Thanks for testing!

Revision history for this message
GunChleoc (gunchleoc) wrote :

Ready for the next round. It is now possible to navigate through the list with the keyboard as well.

Note: I forgot that Esc should cancel the change in the list, will still do that.

Handling a mouse click outside of a panel is tricky, so I autoclose if the mouse moves outside the panel. I gave this some distance so that the list won't disappear on users when they move the mouse 1 pixel off, let me know if we need to widen that. The tolerance is currently 50 pixels, and 25 for moving up.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 1345. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/161548228.
Appveyor build 1187. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_bug_536489_dropdown-1187.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Hello Gun,
thanks for your work.

* the Autoclose when leaving feels reasonbale. OK for me
* Esc-Key is handled as expected.

There is one confusion however:
The menu has a Selection - via a background color - and a checkmark.
When I use the cursor keys to scroll both, the selection and the
checkmark change. So I assume I made the choice. When I then leave
the list, I find that my selection was not applied (so the checkmark is wrong).

Please improve as follows: do not move the checkmark until the selection
in finally confirmed by clicking or by pressing return.

review: Needs Fixing (compile, test)
Revision history for this message
kaputtnik (franku) wrote :

I get a crash as soon i click on one of the dropdown menus in options:

src/ui_basic/panel.h:265: bool UI::Panel::has_focus() const: Assertion `get_can_focus()' failed.

Backtrace: https://bugs.launchpad.net/widelands/+bug/536489/+attachment/4750279/+files/backtrace.txt

review: Needs Fixing
Revision history for this message
GunChleoc (gunchleoc) wrote :

How would it be if there was no checkmark at all? Would that be confusing when the dropdown gets opens?

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

> How would it be if there was no checkmark at all?
> Would that be confusing when the dropdown gets opens?

Hm, the checkmark is a good and normal hint what the
current selection is. But you can completly drop it, in case
it is to diffult otherwise.

In the End you are our User-Interface Department.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Maybe I am, but your input is very helpful!

I just gave it a spin, I think it would be best if the mouse position / current selection gets highlighted, and no tick. Back to the drawing board :)

Revision history for this message
kaputtnik (franku) wrote :

Thanks for fixing the crash :-)

The checkmark is IMHO good now.

But clicking on the vertical slider in Options->Language let the drop down menu disappear.

review: Needs Fixing
Revision history for this message
GunChleoc (gunchleoc) wrote :

Collapse with the scrollbar should be fixed now. Also instant highlighting for mouse movement.

review: Needs Resubmitting
Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

last night I updated to OSX-Sierra

* The good news: Widelands starts (last status compiled form this branch)
* The bad one: When compiling it complains about some system headers.

I will open another Bug to collect this Issues, maybe I can fix them easily,

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

OK, fine for me, I did not take a deep look at that code, sorry.
As I can test macOS (Sierra) only, I would like to see Windows and Linux approvals, too

review: Approve (compile, test)
Revision history for this message
kaputtnik (franku) wrote :

One little thing: If the dropdown box is once opened and closed (with moving away the mouse pointer) and hitting the down arrow of the keyboard, the dropdownmenu sometimes pops up for little time. Best to see if you keep the down arrow down for a longer time.

Not really an issue, so i leave it up to you to fix this.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Good point - deactivated the key when the mouse is not on the dropdown.

Revision history for this message
kaputtnik (franku) wrote :

Great :-)

review: Approve (testing)
Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 1382. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/164389381.
Appveyor build 1224. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_bug_536489_dropdown-1224.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 1384. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/164458580.
Appveyor build 1226. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_bug_536489_dropdown-1226.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

After merging the trunk I now get (in bzr8108[bug-536489-dropdown])

Spieldatenfehler
economies: player 3: Stream ended unexpectedly (0 bytes read, 4 expected)

So I was unable to continue playing my last save game.
As this happens in trunk to I will not complain more ...

Revision history for this message
GunChleoc (gunchleoc) wrote :

That error can't possibly be related to this branch, since you already said that it happens in trunk as well. It's worth reporting as a bug though.

Revision history for this message
SirVer (sirver) wrote :

Just tested under Mac OS. Really nice change. One possible eye candy nit/improvement could be a small line outside of the drop down at the bottom and the sides - to connect the drop down better with the original button.

I will review the code eventually.... no hurry here, build 19 is still a few weeks off.

Revision history for this message
kaputtnik (franku) wrote :

> One possible eye candy
> nit/improvement could be a small line outside of the drop down at the bottom
> and the sides - to connect the drop down better with the original button.

+1 (i thought something like this also)

Revision history for this message
GunChleoc (gunchleoc) wrote :

Beautification done :)

I also want to give the scrollbar some love, but I'll have to do that in a separate branch, since that will affect all kinds of stuff.

Revision history for this message
kaputtnik (franku) wrote :

Looks better now. Have you tested showing it flattened instead of raised?

Revision history for this message
GunChleoc (gunchleoc) wrote :

No, I think flattened is wrong for it - it is dropped down on top of the other elements, not below them.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 1445. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/170387162.
Appveyor build 1288. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_bug_536489_dropdown-1288.

Revision history for this message
SirVer (sirver) wrote :

A couple of change suggestions and questions in code

review: Needs Fixing
Revision history for this message
GunChleoc (gunchleoc) wrote :

Added some replies/questions.

Revision history for this message
SirVer (sirver) wrote :

you also need to merge trunk it seems.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 1527. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/171318976.
Appveyor build 1288. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_bug_536489_dropdown-1288.

Revision history for this message
GunChleoc (gunchleoc) wrote :

All fixed except for 1 comment - have added a comment on my own to the source code.

review: Needs Resubmitting
Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 1531. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/171467686.
Appveyor build 1373. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_bug_536489_dropdown-1373.

Revision history for this message
SirVer (sirver) wrote :

I added a inline comment there. Essentially, I still think it is worthwhile for clarity to pull this function out.

otherwise lgtm.

review: Approve
Revision history for this message
GunChleoc (gunchleoc) wrote :

You are right, it makes the function easier to read, so I've pulled it out.
Thanks for the review :)

@bunnybot merge

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/editor/ui_menus/main_menu_map_options.cc'
2--- src/editor/ui_menus/main_menu_map_options.cc 2016-10-16 09:31:42 +0000
3+++ src/editor/ui_menus/main_menu_map_options.cc 2016-10-29 10:23:37 +0000
4@@ -79,7 +79,7 @@
5 author_(&main_box_, 0, 0, max_w_, 0, 2, g_gr->images().get("images/ui_basic/but1.png")),
6 size_(&main_box_, 0, 0, max_w_ - indent_, labelh_, ""),
7
8- teams_list_(&teams_box_, 0, 0, max_w_, 60, true),
9+ teams_list_(&teams_box_, 0, 0, max_w_, 60, UI::ListselectLayout::kShowCheck),
10
11 modal_(modal) {
12
13
14=== modified file 'src/ui_basic/CMakeLists.txt'
15--- src/ui_basic/CMakeLists.txt 2016-04-02 16:45:53 +0000
16+++ src/ui_basic/CMakeLists.txt 2016-10-29 10:23:37 +0000
17@@ -6,6 +6,8 @@
18 button.h
19 checkbox.cc
20 checkbox.h
21+ dropdown.cc
22+ dropdown.h
23 editbox.cc
24 editbox.h
25 fileview_panel.cc
26
27=== modified file 'src/ui_basic/button.cc'
28--- src/ui_basic/button.cc 2016-10-24 14:04:00 +0000
29+++ src/ui_basic/button.cc 2016-10-29 10:23:37 +0000
30@@ -65,6 +65,7 @@
31 set_size(w, new_height);
32 }
33 set_thinks(false);
34+ set_can_focus(true);
35 }
36
37 Button::Button // for pictorial buttons
38@@ -91,6 +92,7 @@
39 pic_custom_(fg_pic),
40 clr_down_(229, 161, 2) {
41 set_thinks(false);
42+ set_can_focus(true);
43 }
44
45 Button::~Button() {
46@@ -127,6 +129,8 @@
47 if (enabled_ == on)
48 return;
49
50+ set_can_focus(on);
51+
52 // disabled buttons should look different...
53 if (on)
54 enabled_ = true;
55@@ -148,7 +152,8 @@
56 // Draw the background
57 if (pic_background_) {
58 dst.fill_rect(Rectf(0.f, 0.f, get_w(), get_h()), RGBAColor(0, 0, 0, 255));
59- dst.tile(Recti(Vector2i(0, 0), get_w(), get_h()), pic_background_, Vector2i(get_x(), get_y()));
60+ dst.tile(
61+ Recti(Vector2i(0, 0), get_w(), get_h()), pic_background_, Vector2i(get_x(), get_y()));
62 }
63
64 if (enabled_ && highlighted_ && style_ != Style::kFlat)
65@@ -198,8 +203,8 @@
66 enabled_ ? UI_FONT_CLR_FG : UI_FONT_CLR_DISABLED);
67 // Blit on pixel boundary (not float), so that the text is blitted pixel perfect.
68 dst.blit(
69- Vector2f((get_w() - entry_text_im->width()) / 2, (get_h() - entry_text_im->height()) / 2),
70- entry_text_im);
71+ Vector2f((get_w() - entry_text_im->width()) / 2, (get_h() - entry_text_im->height()) / 2),
72+ entry_text_im);
73 }
74
75 // draw border
76@@ -294,6 +299,7 @@
77 return false;
78
79 if (enabled_) {
80+ focus();
81 grab_mouse(true);
82 pressed_ = true;
83 if (repeating_) {
84
85=== modified file 'src/ui_basic/button.h'
86--- src/ui_basic/button.h 2016-09-25 13:07:06 +0000
87+++ src/ui_basic/button.h 2016-10-29 10:23:37 +0000
88@@ -39,7 +39,7 @@
89 enum class Style {
90 kRaised, // Normal raised Button
91 kPermpressed, // Button will appear pressed
92- kFlat // Flat button with simple coloured outline
93+ kFlat // Flat button with simple coloured outline
94 };
95
96 enum class ImageMode {
97@@ -108,7 +108,6 @@
98 /// Convenience function. If 'pressed', sets the style to kPermpressed, otherwise to kRaised.
99 void set_perm_pressed(bool pressed);
100
101-
102 /// Convenience function. Toggles between raised and permpressed style
103 void toggle();
104
105
106=== modified file 'src/ui_basic/checkbox.cc'
107--- src/ui_basic/checkbox.cc 2016-10-16 20:35:47 +0000
108+++ src/ui_basic/checkbox.cc 2016-10-29 10:23:37 +0000
109@@ -43,7 +43,7 @@
110 uint16_t h = pic->height();
111 set_desired_size(w, h);
112 set_size(w, h);
113-
114+ set_can_focus(true);
115 set_flags(Has_Custom_Picture, true);
116 pic_graphics_ = pic;
117 }
118@@ -79,6 +79,7 @@
119 * Args: enabled true if the checkbox should be enabled, false otherwise
120 */
121 void Statebox::set_enabled(bool const enabled) {
122+ set_can_focus(enabled);
123 if (((flags_ & Is_Enabled) > 1) && enabled)
124 return;
125
126@@ -157,6 +158,7 @@
127 */
128 bool Statebox::handle_mousepress(const uint8_t btn, int32_t, int32_t) {
129 if (btn == SDL_BUTTON_LEFT && (flags_ & Is_Enabled)) {
130+ focus();
131 clicked();
132 return true;
133 }
134
135=== added file 'src/ui_basic/dropdown.cc'
136--- src/ui_basic/dropdown.cc 1970-01-01 00:00:00 +0000
137+++ src/ui_basic/dropdown.cc 2016-10-29 10:23:37 +0000
138@@ -0,0 +1,215 @@
139+/*
140+ * Copyright (C) 2016 by the Widelands Development Team
141+ *
142+ * This program is free software; you can redistribute it and/or
143+ * modify it under the terms of the GNU General Public License
144+ * as published by the Free Software Foundation; either version 2
145+ * of the License, or (at your option) any later version.
146+ *
147+ * This program is distributed in the hope that it will be useful,
148+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
149+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
150+ * GNU General Public License for more details.
151+ *
152+ * You should have received a copy of the GNU General Public License
153+ * along with this program; if not, write to the Free Software
154+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
155+ *
156+ */
157+
158+#include "ui_basic/dropdown.h"
159+
160+#include <algorithm>
161+
162+#include <boost/format.hpp>
163+
164+#include "base/i18n.h"
165+#include "graphic/align.h"
166+#include "graphic/font_handler1.h"
167+#include "graphic/graphic.h"
168+#include "graphic/image.h"
169+#include "graphic/rendertarget.h"
170+#include "ui_basic/mouse_constants.h"
171+
172+namespace UI {
173+
174+BaseDropdown::BaseDropdown(
175+ UI::Panel* parent, int32_t x, int32_t y, uint32_t w, uint32_t h, const std::string& label)
176+ : UI::Panel(
177+ parent,
178+ x,
179+ y,
180+ w,
181+ std::max(24,
182+ UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))
183+ ->height() +
184+ 2)), // Height only to fit the button, so we can use this in Box layout.
185+ max_list_height_(h - 2 * get_h()),
186+ mouse_tolerance_(50),
187+ button_box_(this, 0, 0, UI::Box::Horizontal, w, h),
188+ push_button_(&button_box_,
189+ "dropdown_select",
190+ 0,
191+ 0,
192+ 24,
193+ get_h(),
194+ g_gr->images().get("images/ui_basic/but3.png"),
195+ g_gr->images().get("images/ui_basic/scrollbar_down.png"),
196+ pgettext("dropdown", "Select Item")),
197+ display_button_(&button_box_,
198+ "dropdown_label",
199+ 0,
200+ 0,
201+ w - 24,
202+ get_h(),
203+ g_gr->images().get("images/ui_basic/but1.png"),
204+ label),
205+ // Hook into parent so we can drop down outside the panel
206+ list_(parent, x, y + get_h(), w, 0, ListselectLayout::kDropdown),
207+ label_(label) {
208+ list_.set_visible(false);
209+ list_.set_background(g_gr->images().get("images/ui_basic/but1.png"));
210+ display_button_.set_perm_pressed(true);
211+ button_box_.add(&display_button_, UI::Align::kLeft);
212+ button_box_.add(&push_button_, UI::Align::kLeft);
213+ button_box_.set_size(w, get_h());
214+
215+ display_button_.sigclicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
216+ push_button_.sigclicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
217+ list_.clicked.connect(boost::bind(&BaseDropdown::set_value, this));
218+ list_.clicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
219+ set_can_focus(true);
220+}
221+
222+BaseDropdown::~BaseDropdown() {
223+ clear();
224+}
225+
226+void BaseDropdown::add(const std::string& name,
227+ const uint32_t value,
228+ const Image* pic,
229+ const bool select_this,
230+ const std::string& tooltip_text) {
231+ list_.set_size(
232+ list_.get_w(), std::min(list_.get_h() + list_.get_lineheight(), max_list_height_));
233+ list_.add(name, value, pic, select_this, tooltip_text);
234+ if (select_this) {
235+ set_value();
236+ }
237+}
238+
239+bool BaseDropdown::has_selection() const {
240+ return list_.has_selection();
241+}
242+
243+uint32_t BaseDropdown::get_selected() const {
244+ return list_.get_selected();
245+}
246+
247+void BaseDropdown::set_label(const std::string& text) {
248+ label_ = text;
249+ display_button_.set_title(label_);
250+}
251+
252+void BaseDropdown::set_tooltip(const std::string& text) {
253+ tooltip_ = text;
254+ display_button_.set_tooltip(tooltip_);
255+}
256+
257+void BaseDropdown::set_enabled(bool on) {
258+ set_can_focus(on);
259+ push_button_.set_enabled(on);
260+ push_button_.set_tooltip(on ? pgettext("dropdown", "Select Item") : "");
261+ display_button_.set_enabled(on);
262+ list_.set_visible(false);
263+}
264+
265+void BaseDropdown::set_pos(Vector2i point) {
266+ UI::Panel::set_pos(point);
267+ list_.set_pos(Vector2i(point.x, point.y + get_h()));
268+}
269+
270+void BaseDropdown::clear() {
271+ list_.clear();
272+ list_.set_size(list_.get_w(), 0);
273+ set_layout_toplevel(false);
274+}
275+
276+void BaseDropdown::think() {
277+ if (list_.is_visible()) {
278+ // Autocollapse with a bit of tolerance for the mouse movement to make it less fiddly.
279+ if (!(has_focus() || list_.has_focus()) || is_mouse_away()) {
280+ toggle_list();
281+ }
282+ }
283+}
284+
285+uint32_t BaseDropdown::size() const {
286+ return list_.size();
287+}
288+
289+void BaseDropdown::set_value() {
290+ const std::string name = list_.has_selection() ? list_.get_selected_name() :
291+ /** TRANSLATORS: Selection in Dropdown menus. */
292+ pgettext("dropdown", "Not Selected");
293+
294+ if (label_.empty()) {
295+ display_button_.set_title(name);
296+ } else {
297+ /** TRANSLATORS: Label: Value. */
298+ display_button_.set_title((boost::format(_("%1%: %2%")) % label_ % (name)).str());
299+ }
300+ display_button_.set_tooltip(list_.has_selection() ? list_.get_selected_tooltip() : tooltip_);
301+ selected();
302+ current_selection_ = list_.selection_index();
303+}
304+
305+void BaseDropdown::toggle_list() {
306+ list_.set_visible(!list_.is_visible());
307+ if (list_.is_visible()) {
308+ list_.move_to_top();
309+ focus();
310+ }
311+ // Make sure that the list covers and deactivates the elements below it
312+ set_layout_toplevel(list_.is_visible());
313+}
314+
315+bool BaseDropdown::is_mouse_away() const {
316+ return (get_mouse_position().x + mouse_tolerance_) < 0 ||
317+ get_mouse_position().x > (get_w() + mouse_tolerance_) ||
318+ (get_mouse_position().y + mouse_tolerance_ / 2) < 0 ||
319+ get_mouse_position().y > (get_h() + list_.get_h() + mouse_tolerance_);
320+}
321+
322+bool BaseDropdown::handle_key(bool down, SDL_Keysym code) {
323+ if (down) {
324+ switch (code.sym) {
325+ case SDLK_KP_ENTER:
326+ case SDLK_RETURN:
327+ if (list_.is_visible()) {
328+ set_value();
329+ }
330+ case SDLK_ESCAPE:
331+ if (list_.is_visible()) {
332+ list_.select(current_selection_);
333+ toggle_list();
334+ return true;
335+ }
336+ break;
337+ case SDLK_DOWN:
338+ if (!list_.is_visible() && !is_mouse_away()) {
339+ toggle_list();
340+ return true;
341+ }
342+ break;
343+ default:
344+ break; // not handled
345+ }
346+ }
347+ if (list_.is_visible()) {
348+ return list_.handle_key(down, code);
349+ }
350+ return false;
351+}
352+
353+} // namespace UI
354
355=== added file 'src/ui_basic/dropdown.h'
356--- src/ui_basic/dropdown.h 1970-01-01 00:00:00 +0000
357+++ src/ui_basic/dropdown.h 2016-10-29 10:23:37 +0000
358@@ -0,0 +1,166 @@
359+/*
360+ * Copyright (C) 2016 by the Widelands Development Team
361+ *
362+ * This program is free software; you can redistribute it and/or
363+ * modify it under the terms of the GNU General Public License
364+ * as published by the Free Software Foundation; either version 2
365+ * of the License, or (at your option) any later version.
366+ *
367+ * This program is distributed in the hope that it will be useful,
368+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
369+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
370+ * GNU General Public License for more details.
371+ *
372+ * You should have received a copy of the GNU General Public License
373+ * along with this program; if not, write to the Free Software
374+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
375+ *
376+ */
377+
378+#ifndef WL_UI_BASIC_DROPDOWN_H
379+#define WL_UI_BASIC_DROPDOWN_H
380+
381+#include <deque>
382+#include <memory>
383+
384+#include <boost/signals2.hpp>
385+
386+#include "ui_basic/box.h"
387+#include "ui_basic/button.h"
388+#include "ui_basic/listselect.h"
389+#include "ui_basic/panel.h"
390+
391+namespace UI {
392+
393+/// Implementation for a dropdown menu that lets the user select a value.
394+class BaseDropdown : public Panel {
395+protected:
396+ /// \param parent the parent panel
397+ /// \param x the x-position within 'parent'
398+ /// \param y the y-position within 'parent'
399+ /// \param w the dropdown's width
400+ /// \param h the maximum height for the dropdown list
401+ /// \param label a label to prefix to the selected entry on the display button.
402+ BaseDropdown(
403+ Panel* parent, int32_t x, int32_t y, uint32_t w, uint32_t h, const std::string& label);
404+ ~BaseDropdown();
405+
406+public:
407+ boost::signals2::signal<void()> selected;
408+
409+ /// \return true if an element has been selected from the list
410+ bool has_selection() const;
411+
412+ /// Sets a label that will be prefixed to the currently selected element's name
413+ /// and displayed on the display button.
414+ void set_label(const std::string& text);
415+
416+ /// Sets the tooltip for the display button.
417+ void set_tooltip(const std::string& text);
418+
419+ /// Enables/disables the dropdown selection.
420+ void set_enabled(bool on);
421+
422+ /// Move the dropdown. The dropdown's position is relative to the parent in
423+ /// pixels.
424+ void set_pos(Vector2i point) override;
425+
426+ /// The number of elements listed in the dropdown.
427+ uint32_t size() const;
428+
429+ /// Handle keypresses
430+ bool handle_key(bool down, SDL_Keysym code) override;
431+
432+protected:
433+ /// Add an element to the list
434+ /// \param name the display name of the entry
435+ /// \param value the index of the entry
436+ /// \param pic an image to illustrate the entry
437+ /// \param select_this whether this element should be selected
438+ /// \param tooltip_text a tooltip for this entry
439+ void add(const std::string& name,
440+ uint32_t value,
441+ const Image* pic = nullptr,
442+ const bool select_this = false,
443+ const std::string& tooltip_text = std::string());
444+
445+ /// \return the index of the selected element
446+ uint32_t get_selected() const;
447+
448+ /// Removes all elements from the list.
449+ void clear();
450+
451+ /// Automatically collapses the list if the mouse gets too far away from the dropdown, or if it
452+ /// loses focus.
453+ void think() override;
454+
455+private:
456+ /// Updates the title and tooltip of the display button and triggers a 'selected' signal.
457+ void set_value();
458+ /// Toggles the dropdown list on and off.
459+ void toggle_list();
460+
461+ /// Returns true if the mouse pointer left the vicinity of the dropdown.
462+ bool is_mouse_away() const;
463+
464+ uint32_t max_list_height_;
465+ const int mouse_tolerance_; // Allow mouse outside the panel a bit before autocollapse
466+ UI::Box button_box_;
467+ UI::Button push_button_;
468+ UI::Button display_button_;
469+ UI::Listselect<uintptr_t> list_;
470+ std::string label_;
471+ std::string tooltip_;
472+ uint32_t current_selection_;
473+};
474+
475+/// A dropdown menu that lets the user select a value of the datatype 'Entry'.
476+template <typename Entry> class Dropdown : public BaseDropdown {
477+public:
478+ /// \param parent the parent panel
479+ /// \param x the x-position within 'parent'
480+ /// \param y the y-position within 'parent'
481+ /// \param w the dropdown's width
482+ /// \param h the maximum height for the dropdown list
483+ /// \param label a label to prefix to the selected entry on the display button.
484+ /// entry.
485+ Dropdown(Panel* parent, int32_t x, int32_t y, uint32_t w, uint32_t h, const std::string& label)
486+ : BaseDropdown(parent, x, y, w, h, label) {
487+ }
488+ ~Dropdown() {
489+ clear();
490+ }
491+
492+ /// Add an element to the list
493+ /// \param name the display name of the entry
494+ /// \param value the value for the entry
495+ /// \param pic an image to illustrate the entry
496+ /// \param select_this whether this element should be selected
497+ /// \param tooltip_text a tooltip for this entry
498+ void add(const std::string& name,
499+ Entry value,
500+ const Image* pic = nullptr,
501+ const bool select_this = false,
502+ const std::string& tooltip_text = std::string()) {
503+ entry_cache_.push_back(std::unique_ptr<Entry>(new Entry(value)));
504+ BaseDropdown::add(name, size(), pic, select_this, tooltip_text);
505+ }
506+
507+ /// \return the selected element
508+ const Entry& get_selected() const {
509+ return *entry_cache_[BaseDropdown::get_selected()];
510+ }
511+
512+ /// Removes all elements from the list.
513+ void clear() {
514+ BaseDropdown::clear();
515+ }
516+
517+private:
518+ // Contains the actual elements. The BaseDropdown registers the indices only.
519+ std::deque<std::unique_ptr<Entry>> entry_cache_;
520+};
521+
522+} // namespace UI
523+
524+#endif // end of include guard: WL_UI_BASIC_DROPDOWN_H
525
526=== modified file 'src/ui_basic/listselect.cc'
527--- src/ui_basic/listselect.cc 2016-10-16 20:35:47 +0000
528+++ src/ui_basic/listselect.cc 2016-10-29 10:23:37 +0000
529@@ -31,6 +31,7 @@
530 #include "graphic/text/bidi.h"
531 #include "graphic/text_constants.h"
532 #include "graphic/text_layout.h"
533+#include "ui_basic/mouse_constants.h"
534 #include "wlapplication.h"
535
536 constexpr int kMargin = 2;
537@@ -46,11 +47,11 @@
538 * h
539 */
540 BaseListselect::BaseListselect(Panel* const parent,
541- int32_t const x,
542- int32_t const y,
543- uint32_t const w,
544- uint32_t const h,
545- bool const show_check)
546+ const int32_t x,
547+ const int32_t y,
548+ const uint32_t w,
549+ const uint32_t h,
550+ const ListselectLayout selection_mode)
551 : Panel(parent, x, y, w, h),
552 lineheight_(
553 UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height() +
554@@ -60,7 +61,8 @@
555 selection_(no_selection_index()),
556 last_click_time_(-10000),
557 last_selection_(no_selection_index()),
558- show_check_(show_check) {
559+ selection_mode_(selection_mode),
560+ background_(nullptr) {
561 set_thinks(false);
562
563 scrollbar_.moved.connect(boost::bind(&BaseListselect::set_scrollpos, this, _1));
564@@ -68,7 +70,7 @@
565 scrollbar_.set_pagesize(h - 2 * lineheight_);
566 scrollbar_.set_steps(1);
567
568- if (show_check) {
569+ if (selection_mode_ == ListselectLayout::kShowCheck) {
570 uint32_t pic_h;
571 check_pic_ = g_gr->images().get("images/ui_basic/list_selected.png");
572 max_pic_width_ = check_pic_->width();
573@@ -252,7 +254,7 @@
574 if (selection_ == i)
575 return;
576
577- if (show_check_) {
578+ if (selection_mode_ == ListselectLayout::kShowCheck) {
579 if (selection_ != no_selection_index())
580 entry_records_[selection_]->pic = nullptr;
581 entry_records_[i]->pic = check_pic_;
582@@ -274,26 +276,37 @@
583 * \return the ID/entry value of the currently selected item.
584 * The entry value is given as a parameter to \ref add
585 *
586- * Throws an exception when no item is selected.
587+ * Returns no_selection_index() if no item has been selected.
588 */
589 uint32_t BaseListselect::get_selected() const {
590- if (selection_ == no_selection_index())
591- throw NoSelection();
592-
593- return entry_records_[selection_]->entry_;
594+ return selection_ < entry_records_.size() ? entry_records_[selection_]->entry_ :
595+ no_selection_index();
596 }
597
598 /**
599- * Remove the currently selected item. Throws an exception when no
600- * item is selected.
601+ * Remove the currently selected item. Requires an element to have been selected first.
602 */
603 void BaseListselect::remove_selected() {
604- if (selection_ == no_selection_index())
605- throw NoSelection();
606-
607+ assert(selection_ != no_selection_index());
608 remove(selection_);
609 }
610
611+/**
612+ * \return The name of the currently selected entry. Requires an entry to have been selected.
613+ */
614+const std::string& BaseListselect::get_selected_name() const {
615+ assert(selection_ < entry_records_.size());
616+ return entry_records_[selection_]->name;
617+}
618+
619+/**
620+ * \return The tooltip for the currently selected entry. Requires an entry to have been selected.
621+ */
622+const std::string& BaseListselect::get_selected_tooltip() const {
623+ assert(selection_ < entry_records_.size());
624+ return entry_records_[selection_]->tooltip;
625+}
626+
627 uint32_t BaseListselect::get_lineheight() const {
628 return lineheight_ + kMargin;
629 }
630@@ -302,27 +315,56 @@
631 return scrollbar_.is_enabled() ? get_w() - scrollbar_.get_w() : get_w();
632 }
633
634+void BaseListselect::layout() {
635+ scrollbar_.set_size(scrollbar_.get_w(), get_h());
636+ scrollbar_.set_pagesize(get_h() - 2 * get_lineheight());
637+ scrollbar_.set_steps(entry_records_.size() * get_lineheight() - get_h());
638+}
639+
640 /**
641 Redraw the listselect box
642 */
643 void BaseListselect::draw(RenderTarget& dst) {
644 // draw text lines
645- const uint32_t lineheight = get_lineheight();
646- uint32_t idx = scrollpos_ / lineheight;
647- float y = 1 + idx * lineheight - scrollpos_;
648-
649- dst.brighten_rect(Rectf(0.f, 0.f, get_w(), get_h()), ms_darken_value);
650-
651+ const int eff_h =
652+ selection_mode_ == ListselectLayout::kDropdown ? get_inner_h() - 4 : get_inner_h();
653+ uint32_t idx = scrollpos_ / get_lineheight();
654+ int y = 1 + idx * get_lineheight() - scrollpos_;
655+
656+ if (background_ != nullptr) {
657+ dst.tile(Recti(Vector2i(0, 0), get_w(), get_h()), background_, Vector2i(0, 0));
658+ }
659+
660+ if (selection_mode_ == ListselectLayout::kDropdown) {
661+ RGBAColor black(0, 0, 0, 255);
662+ // left edge
663+ dst.brighten_rect(Rectf(0.f, 0.f, 2.f, get_h()), BUTTON_EDGE_BRIGHT_FACTOR);
664+ // bottom edge
665+ dst.fill_rect(Rectf(2.f, get_h() - 2.f, get_eff_w() - 2.f, 1.f), black);
666+ dst.fill_rect(Rectf(1.f, get_h() - 1.f, get_eff_w() - 1.f, 1.f), black);
667+ // right edge
668+ dst.fill_rect(Rectf(get_w() - 2.f, 1.f, 1.f, get_h() - 1.f), black);
669+ dst.fill_rect(Rectf(get_w() - 1.f, 0.f, 1.f, get_h()), black);
670+ } else {
671+ dst.brighten_rect(Rectf(0.f, 0.f, get_w(), get_h()), ms_darken_value);
672+ }
673+
674+ int lineheight = lineheight_;
675 while (idx < entry_records_.size()) {
676- assert(get_h() < std::numeric_limits<int32_t>::max());
677- if (y >= get_h()) {
678+ assert(eff_h < std::numeric_limits<int32_t>::max());
679+
680+ // Don't draw over the bottom edge
681+ lineheight = std::min(eff_h - y, lineheight);
682+ if (lineheight < 0) {
683 break;
684 }
685
686 const EntryRecord& er = *entry_records_[idx];
687
688- Vector2f point(1.f, y);
689- uint32_t maxw = get_eff_w() - 2;
690+ Vector2f point(selection_mode_ == ListselectLayout::kDropdown ? 3.f : 1.f, y);
691+ uint32_t maxw =
692+ get_eff_w() -
693+ (selection_mode_ == ListselectLayout::kDropdown ? scrollbar_.is_enabled() ? 4 : 5 : 2);
694
695 // Highlight the current selected entry
696 if (idx == selection_) {
697@@ -347,7 +389,7 @@
698 // Now draw pictures
699 if (er.pic) {
700 dst.blit(Vector2f(UI::g_fh1->fontset()->is_rtl() ? get_eff_w() - er.pic->width() - 1 : 1,
701- y + (get_lineheight() - er.pic->height()) / 2.f),
702+ y + (lineheight_ - er.pic->height()) / 2),
703 er.pic);
704 }
705
706@@ -368,12 +410,18 @@
707 }
708
709 // Fix vertical position for mixed font heights
710- if (get_lineheight() > static_cast<uint32_t>(entry_text_im->height())) {
711+ if (lineheight_ > static_cast<uint32_t>(entry_text_im->height())) {
712 point.y += (lineheight_ - entry_text_im->height()) / 2;
713 } else {
714 point.y -= (entry_text_im->height() - lineheight_) / 2;
715 }
716
717+ // Don't draw over the bottom edge
718+ lineheight = std::min(eff_h - static_cast<int>(point.y), lineheight);
719+ if (lineheight < 0) {
720+ break;
721+ }
722+
723 // Crop to column width while blitting
724 if (static_cast<int>(alignment & UI::Align::kRight) &&
725 (maxw + picw) < static_cast<uint32_t>(entry_text_im->width())) {
726@@ -382,13 +430,13 @@
727
728 // We want this always on, e.g. for mixed language savegame filenames, or the languages
729 // list
730- dst.blitrect(point, entry_text_im,
731- Recti(entry_text_im->width() - maxw + picw, 0, maxw, entry_text_im->height()));
732+ dst.blitrect(point, entry_text_im, Recti(entry_text_im->width() - maxw + picw, 0, maxw,
733+ entry_text_im->height()));
734 } else {
735- dst.blitrect(point, entry_text_im, Recti(0, 0, maxw, entry_text_im->height()));
736+ dst.blitrect(point, entry_text_im, Recti(0, 0, maxw, lineheight));
737 }
738
739- y += lineheight;
740+ y += get_lineheight();
741 ++idx;
742 }
743 }
744@@ -445,6 +493,9 @@
745 set_tooltip("");
746 return false;
747 }
748+ if (selection_mode_ == ListselectLayout::kDropdown) {
749+ select(y);
750+ }
751 set_tooltip(entry_records_.at(y)->tooltip);
752 return true;
753 }
754
755=== modified file 'src/ui_basic/listselect.h'
756--- src/ui_basic/listselect.h 2016-08-04 15:49:05 +0000
757+++ src/ui_basic/listselect.h 2016-10-29 10:23:37 +0000
758@@ -32,6 +32,12 @@
759 namespace UI {
760 struct Scrollbar;
761
762+enum class ListselectLayout {
763+ kPlain, // Highlight the selected element
764+ kDropdown, // When the mouse moves, instantly select the element that the mouse hovers over
765+ kShowCheck // Show a green arrow in front of the selected element
766+};
767+
768 /**
769 * This class defines a list-select box whose entries are defined by a name
770 * and an associated numeric ID.
771@@ -39,8 +45,12 @@
772 * Use the \ref Listselect template to use arbitrary IDs.
773 */
774 struct BaseListselect : public Panel {
775- BaseListselect(
776- Panel* parent, int32_t x, int32_t y, uint32_t w, uint32_t h, bool show_check = false);
777+ BaseListselect(Panel* parent,
778+ int32_t x,
779+ int32_t y,
780+ uint32_t w,
781+ uint32_t h,
782+ ListselectLayout selection_mode = ListselectLayout::kPlain);
783 ~BaseListselect();
784
785 boost::signals2::signal<void(uint32_t)> selected;
786@@ -88,15 +98,23 @@
787 void select(uint32_t i);
788 bool has_selection() const;
789
790- struct NoSelection {};
791 uint32_t get_selected() const;
792 void remove_selected();
793
794+ const std::string& get_selected_name() const;
795+ const std::string& get_selected_tooltip() const;
796+
797+ void set_background(const Image* background) {
798+ background_ = background;
799+ }
800+
801 /// Return the total height (text + spacing) occupied by a single line.
802 uint32_t get_lineheight() const;
803
804 uint32_t get_eff_w() const;
805
806+ void layout() override;
807+
808 // Drawing and event handling
809 void draw(RenderTarget&) override;
810 bool handle_mousepress(uint8_t btn, int32_t x, int32_t y) override;
811@@ -132,14 +150,20 @@
812 uint32_t selection_;
813 uint32_t last_click_time_;
814 uint32_t last_selection_; // for double clicks
815- bool show_check_; // show a green arrow left of selected element
816+ ListselectLayout selection_mode_;
817 const Image* check_pic_;
818+ const Image* background_;
819 std::string current_tooltip_;
820 };
821
822 template <typename Entry> struct Listselect : public BaseListselect {
823- Listselect(Panel* parent, int32_t x, int32_t y, uint32_t w, uint32_t h, bool show_check = false)
824- : BaseListselect(parent, x, y, w, h, show_check) {
825+ Listselect(Panel* parent,
826+ int32_t x,
827+ int32_t y,
828+ uint32_t w,
829+ uint32_t h,
830+ ListselectLayout selection_mode = ListselectLayout::kPlain)
831+ : BaseListselect(parent, x, y, w, h, selection_mode) {
832 }
833
834 void add(const std::string& name,
835@@ -167,6 +191,10 @@
836 return entry_cache_[BaseListselect::get_selected()];
837 }
838
839+ void set_background(const Image* background) {
840+ BaseListselect::set_background(background);
841+ }
842+
843 private:
844 std::deque<Entry> entry_cache_;
845 };
846@@ -181,8 +209,13 @@
847 template <typename Entry> struct Listselect<Entry&> : public Listselect<Entry*> {
848 using Base = Listselect<Entry*>;
849
850- Listselect(Panel* parent, int32_t x, int32_t y, uint32_t w, uint32_t h, bool show_check = false)
851- : Base(parent, x, y, w, h, show_check) {
852+ Listselect(Panel* parent,
853+ int32_t x,
854+ int32_t y,
855+ uint32_t w,
856+ uint32_t h,
857+ ListselectLayout selection_mode = ListselectLayout::kPlain)
858+ : Base(parent, x, y, w, h, selection_mode) {
859 }
860
861 void add(const std::string& name,
862@@ -207,6 +240,10 @@
863 Entry& get_selected() const {
864 return *Base::get_selected();
865 }
866+
867+ void set_background(const Image* background) {
868+ *Base::set_background(background);
869+ }
870 };
871 }
872
873
874=== modified file 'src/ui_basic/panel.cc'
875--- src/ui_basic/panel.cc 2016-10-16 20:35:47 +0000
876+++ src/ui_basic/panel.cc 2016-10-29 10:23:37 +0000
877@@ -820,7 +820,7 @@
878 // Found a child at the position
879 if (child->do_mousewheel(
880 which, x, y, rel_mouse_pos - Vector2i(child->get_x() + child->get_lborder(),
881- child->get_y() + child->get_tborder()))) {
882+ child->get_y() + child->get_tborder()))) {
883 return true;
884 }
885 // Break after the first hit panel in the list. The panels are ordered from top to bottom,
886
887=== modified file 'src/ui_basic/panel.h'
888--- src/ui_basic/panel.h 2016-10-16 09:31:42 +0000
889+++ src/ui_basic/panel.h 2016-10-29 10:23:37 +0000
890@@ -113,7 +113,7 @@
891 // Geometry
892 void set_size(int nw, int nh);
893 void set_desired_size(int w, int h);
894- void set_pos(Vector2i);
895+ virtual void set_pos(Vector2i);
896 virtual void move_inside_parent();
897 virtual void layout();
898
899
900=== modified file 'src/ui_basic/progressbar.cc'
901--- src/ui_basic/progressbar.cc 2016-10-16 20:35:47 +0000
902+++ src/ui_basic/progressbar.cc 2016-10-29 10:23:37 +0000
903@@ -67,8 +67,8 @@
904 assert(fraction <= 1);
905
906 const RGBColor color = fraction <= 0.33f ? RGBColor(255, 0, 0) : fraction <= 0.67f ?
907- RGBColor(255, 255, 0) :
908- RGBColor(0, 255, 0);
909+ RGBColor(255, 255, 0) :
910+ RGBColor(0, 255, 0);
911
912 // Draw the actual bar
913 if (orientation_ == Horizontal) {
914
915=== modified file 'src/ui_basic/scrollbar.cc'
916--- src/ui_basic/scrollbar.cc 2016-10-23 12:59:11 +0000
917+++ src/ui_basic/scrollbar.cc 2016-10-29 10:23:37 +0000
918@@ -271,7 +271,8 @@
919 dst.fill_rect(Rectf(r.origin() + Vector2f(r.w - 1, 1), 1, r.h - 1), black);
920 } else {
921 // bottom edge
922- dst.brighten_rect(Rectf(r.origin() + Vector2f(0, r.h - 2), r.w, 2), BUTTON_EDGE_BRIGHT_FACTOR);
923+ dst.brighten_rect(
924+ Rectf(r.origin() + Vector2f(0, r.h - 2), r.w, 2), BUTTON_EDGE_BRIGHT_FACTOR);
925 // right edge
926 dst.brighten_rect(
927 Rectf(r.origin() + Vector2f(r.w - 2, 0), 2, r.h - 2), BUTTON_EDGE_BRIGHT_FACTOR);
928
929=== modified file 'src/ui_basic/slider.cc'
930--- src/ui_basic/slider.cc 2016-10-16 20:35:47 +0000
931+++ src/ui_basic/slider.cc 2016-10-29 10:23:37 +0000
932@@ -75,6 +75,7 @@
933 bar_size_(bar_size),
934 cursor_size_(cursor_size) {
935 set_thinks(false);
936+ set_can_focus(true);
937 calculate_cursor_position();
938 }
939
940@@ -200,6 +201,7 @@
941 if (enabled_ == enabled)
942 return;
943
944+ set_can_focus(enabled);
945 enabled_ = enabled;
946 if (!enabled) {
947 pressed_ = false;
948@@ -399,6 +401,7 @@
949 if (btn != SDL_BUTTON_LEFT)
950 return false;
951
952+ focus();
953 if (x >= cursor_pos_ && x <= cursor_pos_ + cursor_size_) {
954 // click on cursor
955 cursor_pressed(x);
956@@ -469,6 +472,7 @@
957 if (btn != SDL_BUTTON_LEFT)
958 return false;
959
960+ focus();
961 if (y >= cursor_pos_ && y <= cursor_pos_ + cursor_size_) {
962 // click on cursor
963 cursor_pressed(y);
964
965=== modified file 'src/ui_basic/tabpanel.cc'
966--- src/ui_basic/tabpanel.cc 2016-10-16 20:35:47 +0000
967+++ src/ui_basic/tabpanel.cc 2016-10-29 10:23:37 +0000
968@@ -260,13 +260,13 @@
969 if (pic_background_) {
970 if (!tabs_.empty()) {
971 dst.tile(Recti(Vector2i(0, 0), tabs_.back()->get_x() + tabs_.back()->get_w(),
972- kTabPanelButtonHeight - 2),
973+ kTabPanelButtonHeight - 2),
974 pic_background_, Vector2i(get_x(), get_y()));
975 }
976 assert(kTabPanelButtonHeight - 2 <= get_h());
977- dst.tile(
978- Recti(Vector2i(0, kTabPanelButtonHeight - 2), get_w(), get_h() - kTabPanelButtonHeight + 2),
979- pic_background_, Vector2i(get_x(), get_y() + kTabPanelButtonHeight - 2));
980+ dst.tile(Recti(Vector2i(0, kTabPanelButtonHeight - 2), get_w(),
981+ get_h() - kTabPanelButtonHeight + 2),
982+ pic_background_, Vector2i(get_x(), get_y() + kTabPanelButtonHeight - 2));
983 }
984
985 RGBColor black(0, 0, 0);
986@@ -279,8 +279,7 @@
987 tab_width = tabs_[idx]->get_w();
988
989 if (highlight_ == idx) {
990- dst.brighten_rect(
991- Rectf(x, 0, tab_width, kTabPanelButtonHeight), MOUSE_OVER_BRIGHT_FACTOR);
992+ dst.brighten_rect(Rectf(x, 0, tab_width, kTabPanelButtonHeight), MOUSE_OVER_BRIGHT_FACTOR);
993 }
994
995 assert(tabs_[idx]->pic);
996@@ -297,13 +296,13 @@
997 uint16_t picture_height = image_scale * tabs_[idx]->pic->height();
998 dst.blitrect_scale(
999 Rectf(x + (kTabPanelButtonHeight - picture_width) / 2.f,
1000- (kTabPanelButtonHeight - picture_height) / 2.f, picture_width, picture_height),
1001+ (kTabPanelButtonHeight - picture_height) / 2.f, picture_width, picture_height),
1002 tabs_[idx]->pic, Recti(0, 0, tabs_[idx]->pic->width(), tabs_[idx]->pic->height()), 1.,
1003 BlendMode::UseAlpha);
1004 } else {
1005- dst.blit(
1006- Vector2f(x + kTabPanelTextMargin, (kTabPanelButtonHeight - tabs_[idx]->pic->height()) / 2.f),
1007- tabs_[idx]->pic, BlendMode::UseAlpha, UI::Align::kLeft);
1008+ dst.blit(Vector2f(x + kTabPanelTextMargin,
1009+ (kTabPanelButtonHeight - tabs_[idx]->pic->height()) / 2.f),
1010+ tabs_[idx]->pic, BlendMode::UseAlpha, UI::Align::kLeft);
1011 }
1012
1013 // Draw top part of border
1014@@ -317,8 +316,7 @@
1015 dst.brighten_rect(
1016 Rectf(x, kTabPanelButtonHeight - 2, tab_width, 2), 2 * BUTTON_EDGE_BRIGHT_FACTOR);
1017 else {
1018- dst.brighten_rect(
1019- Rectf(x, kTabPanelButtonHeight - 2, 2, 2), BUTTON_EDGE_BRIGHT_FACTOR);
1020+ dst.brighten_rect(Rectf(x, kTabPanelButtonHeight - 2, 2, 2), BUTTON_EDGE_BRIGHT_FACTOR);
1021
1022 dst.brighten_rect(Rectf(x + tab_width - 2, kTabPanelButtonHeight - 2, 2, 2),
1023 2 * BUTTON_EDGE_BRIGHT_FACTOR);
1024
1025=== modified file 'src/ui_basic/window.cc'
1026--- src/ui_basic/window.cc 2016-10-23 12:59:11 +0000
1027+++ src/ui_basic/window.cc 2016-10-29 10:23:37 +0000
1028@@ -226,7 +226,7 @@
1029 Panel& parent = *get_parent();
1030
1031 set_pos(Vector2i((static_cast<int32_t>(parent.get_inner_w()) - get_w()) / 2,
1032- (static_cast<int32_t>(parent.get_inner_h()) - get_h()) / 2));
1033+ (static_cast<int32_t>(parent.get_inner_h()) - get_h()) / 2));
1034 }
1035
1036 /**
1037@@ -234,7 +234,8 @@
1038 */
1039 void Window::draw(RenderTarget& dst) {
1040 if (!is_minimal()) {
1041- dst.tile(Recti(Vector2i(0, 0), get_inner_w(), get_inner_h()), pic_background_, Vector2i(0, 0));
1042+ dst.tile(
1043+ Recti(Vector2i(0, 0), get_inner_w(), get_inner_h()), pic_background_, Vector2i(0, 0));
1044 }
1045 }
1046
1047@@ -257,8 +258,9 @@
1048 // top bar
1049 static_assert(0 <= HZ_B_CORNER_PIXMAP_LEN, "assert(0 <= HZ_B_CORNER_PIXMAP_LEN) failed.");
1050 for (; pos < hz_bar_end_minus_middle; pos += HZ_B_MIDDLE_PIXMAP_LEN)
1051- dst.blitrect(Vector2f(pos, 0), pic_top_, Recti(Vector2i(HZ_B_CORNER_PIXMAP_LEN, 0),
1052- HZ_B_MIDDLE_PIXMAP_LEN, TP_B_PIXMAP_THICKNESS));
1053+ dst.blitrect(
1054+ Vector2f(pos, 0), pic_top_, Recti(Vector2i(HZ_B_CORNER_PIXMAP_LEN, 0),
1055+ HZ_B_MIDDLE_PIXMAP_LEN, TP_B_PIXMAP_THICKNESS));
1056
1057 // odd pixels of top bar and top right corner
1058 const int32_t width = hz_bar_end - pos + HZ_B_CORNER_PIXMAP_LEN;
1059@@ -295,15 +297,16 @@
1060 // left bar
1061 static_assert(0 <= VT_B_THINGY_PIXMAP_LEN, "assert(0 <= VT_B_THINGY_PIXMAP_LEN) failed.");
1062 for (; pos < vt_bar_end_minus_middle; pos += VT_B_MIDDLE_PIXMAP_LEN)
1063- dst.blitrect(
1064- Vector2f(0, pos), pic_lborder_, Recti(Vector2i(0, VT_B_THINGY_PIXMAP_LEN),
1065- VT_B_PIXMAP_THICKNESS, VT_B_MIDDLE_PIXMAP_LEN));
1066+ dst.blitrect(Vector2f(0, pos), pic_lborder_,
1067+ Recti(Vector2i(0, VT_B_THINGY_PIXMAP_LEN), VT_B_PIXMAP_THICKNESS,
1068+ VT_B_MIDDLE_PIXMAP_LEN));
1069
1070 // odd pixels of left bar and left bottom thingy
1071 const int32_t height = vt_bar_end - pos + VT_B_THINGY_PIXMAP_LEN;
1072 assert(0 <= VT_B_TOTAL_PIXMAP_LEN - height);
1073- dst.blitrect(Vector2f(0, pos), pic_lborder_, Recti(Vector2i(0, VT_B_TOTAL_PIXMAP_LEN - height),
1074- VT_B_PIXMAP_THICKNESS, height));
1075+ dst.blitrect(
1076+ Vector2f(0, pos), pic_lborder_,
1077+ Recti(Vector2i(0, VT_B_TOTAL_PIXMAP_LEN - height), VT_B_PIXMAP_THICKNESS, height));
1078 }
1079
1080 { // Right border
1081@@ -320,7 +323,7 @@
1082 for (; pos < vt_bar_end_minus_middle; pos += VT_B_MIDDLE_PIXMAP_LEN)
1083 dst.blitrect(Vector2f(right_border_x, pos), pic_rborder_,
1084 Recti(Vector2i(0, VT_B_THINGY_PIXMAP_LEN), VT_B_PIXMAP_THICKNESS,
1085- VT_B_MIDDLE_PIXMAP_LEN));
1086+ VT_B_MIDDLE_PIXMAP_LEN));
1087
1088 // odd pixels of right bar and right bottom thingy
1089 const int32_t height = vt_bar_end - pos + VT_B_THINGY_PIXMAP_LEN;
1090@@ -340,12 +343,13 @@
1091 for (; pos < hz_bar_end_minus_middle; pos += HZ_B_MIDDLE_PIXMAP_LEN)
1092 dst.blitrect(Vector2f(pos, get_h() - BT_B_PIXMAP_THICKNESS), pic_bottom_,
1093 Recti(Vector2i(HZ_B_CORNER_PIXMAP_LEN, 0), HZ_B_MIDDLE_PIXMAP_LEN,
1094- BT_B_PIXMAP_THICKNESS));
1095+ BT_B_PIXMAP_THICKNESS));
1096
1097 // odd pixels of bottom bar and bottom right corner
1098 const int32_t width = hz_bar_end - pos + HZ_B_CORNER_PIXMAP_LEN;
1099- dst.blitrect(Vector2f(pos, get_h() - BT_B_PIXMAP_THICKNESS), pic_bottom_,
1100- Recti(Vector2i(HZ_B_TOTAL_PIXMAP_LEN - width, 0), width, BT_B_PIXMAP_THICKNESS));
1101+ dst.blitrect(
1102+ Vector2f(pos, get_h() - BT_B_PIXMAP_THICKNESS), pic_bottom_,
1103+ Recti(Vector2i(HZ_B_TOTAL_PIXMAP_LEN - width, 0), width, BT_B_PIXMAP_THICKNESS));
1104 }
1105 }
1106 }
1107
1108=== modified file 'src/ui_fsmenu/launch_mpg.cc'
1109--- src/ui_fsmenu/launch_mpg.cc 2016-10-25 07:07:14 +0000
1110+++ src/ui_fsmenu/launch_mpg.cc 2016-10-29 10:23:37 +0000
1111@@ -650,7 +650,7 @@
1112 suggested_teams_box_->show(map.get_suggested_teams());
1113 suggested_teams_box_->set_pos(
1114 Vector2i(suggested_teams_box_->get_x(),
1115- back_.get_y() - padding_ - suggested_teams_box_->get_h() - padding_));
1116+ back_.get_y() - padding_ - suggested_teams_box_->get_h() - padding_));
1117 }
1118
1119 /// Show help
1120
1121=== modified file 'src/ui_fsmenu/launch_spg.cc'
1122--- src/ui_fsmenu/launch_spg.cc 2016-10-24 14:05:58 +0000
1123+++ src/ui_fsmenu/launch_spg.cc 2016-10-29 10:23:37 +0000
1124@@ -33,7 +33,6 @@
1125 #include "logic/game.h"
1126 #include "logic/game_controller.h"
1127 #include "logic/game_settings.h"
1128-#include "logic/map.h"
1129 #include "logic/map_objects/map_object.h"
1130 #include "logic/player.h"
1131 #include "map_io/map_loader.h"
1132@@ -61,14 +60,12 @@
1133 buth_,
1134 g_gr->images().get("images/ui_basic/but1.png"),
1135 _("Select map")),
1136- wincondition_(this,
1137- "win_condition",
1138- get_w() * 7 / 10,
1139- get_h() * 4 / 10 + buth_,
1140- butw_,
1141- buth_,
1142- g_gr->images().get("images/ui_basic/but1.png"),
1143- ""),
1144+ win_condition_dropdown_(this,
1145+ get_w() * 7 / 10,
1146+ get_h() * 4 / 10 + buth_,
1147+ butw_,
1148+ get_h() - get_h() * 4 / 10 - buth_,
1149+ ""),
1150 back_(this,
1151 "back",
1152 get_w() * 7 / 10,
1153@@ -127,15 +124,12 @@
1154 is_scenario_(false) {
1155 select_map_.sigclicked.connect(
1156 boost::bind(&FullscreenMenuLaunchSPG::select_map, boost::ref(*this)));
1157- wincondition_.sigclicked.connect(
1158- boost::bind(&FullscreenMenuLaunchSPG::win_condition_clicked, boost::ref(*this)));
1159+ win_condition_dropdown_.selected.connect(
1160+ boost::bind(&FullscreenMenuLaunchSPG::win_condition_selected, this));
1161 back_.sigclicked.connect(boost::bind(&FullscreenMenuLaunchSPG::clicked_back, boost::ref(*this)));
1162 ok_.sigclicked.connect(boost::bind(&FullscreenMenuLaunchSPG::clicked_ok, boost::ref(*this)));
1163
1164 lua_ = new LuaInterface();
1165- win_condition_scripts_ = settings_->settings().win_condition_scripts;
1166- cur_wincondition_ = -1;
1167- win_condition_clicked();
1168
1169 title_.set_fontsize(UI_FONT_SIZE_BIG);
1170
1171@@ -203,68 +197,113 @@
1172 }
1173
1174 /**
1175- * WinCondition button has been pressed
1176- */
1177-void FullscreenMenuLaunchSPG::win_condition_clicked() {
1178- if (settings_->can_change_map()) {
1179- cur_wincondition_++;
1180- cur_wincondition_ %= win_condition_scripts_.size();
1181- settings_->set_win_condition_script(win_condition_scripts_[cur_wincondition_]);
1182- }
1183-
1184- win_condition_update();
1185-}
1186-
1187-/**
1188- * update win conditions information
1189- */
1190-void FullscreenMenuLaunchSPG::win_condition_update() {
1191+ * Fill the dropdown with the available win conditions.
1192+ */
1193+void FullscreenMenuLaunchSPG::update_win_conditions() {
1194+ win_condition_dropdown_.clear();
1195 if (settings_->settings().scenario) {
1196- wincondition_.set_title(_("Scenario"));
1197- wincondition_.set_tooltip(_("Win condition is set through the scenario"));
1198+ win_condition_dropdown_.set_label(_("Scenario"));
1199+ win_condition_dropdown_.set_tooltip(_("Win condition is set through the scenario"));
1200+ win_condition_dropdown_.set_enabled(false);
1201 } else {
1202- win_condition_load();
1203- }
1204-}
1205-
1206-/**
1207- * Loads the current win condition script from the settings provider.
1208- * Calls win_condition_clicked() if the current map can't handle the win condition.
1209- */
1210-void FullscreenMenuLaunchSPG::win_condition_load() {
1211+ win_condition_dropdown_.set_label("");
1212+ win_condition_dropdown_.set_tooltip("");
1213+ Widelands::Map map;
1214+ std::unique_ptr<Widelands::MapLoader> ml =
1215+ map.get_correct_loader(settings_->settings().mapfilename);
1216+ if (ml != nullptr) {
1217+ ml->preload_map(true);
1218+ load_win_conditions(map);
1219+ } else {
1220+ const std::string error_message =
1221+ (boost::format(_("Unable to determine valid win conditions because the map '%s' could "
1222+ "not be loaded.")) %
1223+ settings_->settings().mapfilename)
1224+ .str();
1225+ win_condition_dropdown_.set_label(_("Error"));
1226+ win_condition_dropdown_.set_tooltip(error_message);
1227+ log("LaunchSPG: No map loader: %s\n", error_message.c_str());
1228+ }
1229+ win_condition_dropdown_.set_enabled(true);
1230+ }
1231+}
1232+
1233+void FullscreenMenuLaunchSPG::load_win_conditions(const Widelands::Map& map) {
1234+ try {
1235+ const std::set<std::string> tags = map.get_tags();
1236+ // Make sure that the last win condition is still valid. If not, pick the first one
1237+ // available.
1238+ if (last_win_condition_.empty()) {
1239+ last_win_condition_ = settings_->settings().win_condition_scripts.front();
1240+ }
1241+ std::unique_ptr<LuaTable> t = win_condition_if_valid(last_win_condition_, tags);
1242+ for (const std::string& win_condition_script : settings_->settings().win_condition_scripts) {
1243+ if (t) {
1244+ break;
1245+ } else {
1246+ last_win_condition_ = win_condition_script;
1247+ t = win_condition_if_valid(last_win_condition_, tags);
1248+ }
1249+ }
1250+
1251+ // Now fill the dropdown.
1252+ for (const std::string& win_condition_script : settings_->settings().win_condition_scripts) {
1253+ try {
1254+ t = win_condition_if_valid(win_condition_script, tags);
1255+ if (t) {
1256+ i18n::Textdomain td("win_conditions");
1257+ win_condition_dropdown_.add(_(t->get_string("name")), win_condition_script, nullptr,
1258+ win_condition_script == last_win_condition_,
1259+ t->get_string("description"));
1260+ }
1261+ } catch (LuaTableKeyError& e) {
1262+ log("LaunchSPG: Error loading win condition: %s %s\n", win_condition_script.c_str(),
1263+ e.what());
1264+ }
1265+ }
1266+ } catch (const std::exception& e) {
1267+ const std::string error_message =
1268+ (boost::format(_("Unable to determine valid win conditions because the map '%s' "
1269+ "could not be loaded.")) %
1270+ settings_->settings().mapfilename)
1271+ .str();
1272+ win_condition_dropdown_.set_label(_("Error"));
1273+ win_condition_dropdown_.set_tooltip(error_message);
1274+ log("LaunchSPG: Exception: %s %s\n", error_message.c_str(), e.what());
1275+ }
1276+}
1277+
1278+void FullscreenMenuLaunchSPG::win_condition_selected() {
1279+ last_win_condition_ = win_condition_dropdown_.get_selected();
1280+}
1281+
1282+// TODO(GunChleoc): Turn this into a free standing function. It seems it is not using any state.
1283+std::unique_ptr<LuaTable>
1284+FullscreenMenuLaunchSPG::win_condition_if_valid(const std::string& win_condition_script,
1285+ std::set<std::string> tags) const {
1286 bool is_usable = true;
1287+ std::unique_ptr<LuaTable> t;
1288 try {
1289- std::unique_ptr<LuaTable> t = lua_->run_script(settings_->get_win_condition_script());
1290+ t = lua_->run_script(win_condition_script);
1291 t->do_not_warn_about_unaccessed_keys();
1292
1293 // Skip this win condition if the map doesn't have all the required tags
1294- if (t->has_key("map_tags") && !settings_->settings().mapfilename.empty()) {
1295- Widelands::Map map;
1296- std::unique_ptr<Widelands::MapLoader> ml =
1297- map.get_correct_loader(settings_->settings().mapfilename);
1298- ml->preload_map(true);
1299+ if (t->has_key("map_tags")) {
1300 for (const std::string& map_tag : t->get_table("map_tags")->array_entries<std::string>()) {
1301- if (!map.has_tag(map_tag)) {
1302+ if (!tags.count(map_tag)) {
1303 is_usable = false;
1304 break;
1305 }
1306 }
1307 }
1308-
1309- const std::string name = t->get_string("name");
1310- const std::string descr = t->get_string("description");
1311- {
1312- i18n::Textdomain td("win_conditions");
1313- wincondition_.set_title(_(name));
1314- }
1315- wincondition_.set_tooltip(descr.c_str());
1316- } catch (LuaTableKeyError&) {
1317- // might be that this is not a win condition after all.
1318- is_usable = false;
1319+ } catch (LuaTableKeyError& e) {
1320+ log(
1321+ "LaunchSPG: Error loading win condition: %s %s\n", win_condition_script.c_str(), e.what());
1322 }
1323 if (!is_usable) {
1324- win_condition_clicked();
1325+ t.reset(nullptr);
1326 }
1327+ return t;
1328 }
1329
1330 /**
1331@@ -285,6 +324,7 @@
1332 if (is_scenario_) {
1333 end_modal<FullscreenMenuBase::MenuTarget>(FullscreenMenuBase::MenuTarget::kScenarioGame);
1334 } else {
1335+ settings_->set_win_condition_script(win_condition_dropdown_.get_selected());
1336 end_modal<FullscreenMenuBase::MenuTarget>(FullscreenMenuBase::MenuTarget::kNormalGame);
1337 }
1338 }
1339@@ -310,7 +350,6 @@
1340
1341 select_map_.set_visible(settings_->can_change_map());
1342 select_map_.set_enabled(settings_->can_change_map());
1343- wincondition_.set_enabled(settings_->can_change_map() && !settings.scenario);
1344
1345 if (settings.scenario) {
1346 set_scenario_values();
1347@@ -329,8 +368,6 @@
1348 // update the player description groups
1349 for (uint32_t i = 0; i < kMaxPlayers; ++i)
1350 players_[i]->refresh();
1351-
1352- win_condition_update();
1353 }
1354
1355 /**
1356@@ -357,6 +394,7 @@
1357
1358 safe_place_for_host(nr_players_);
1359 settings_->set_map(mapdata.name, mapdata.filename, nr_players_);
1360+ update_win_conditions();
1361 }
1362
1363 /**
1364
1365=== modified file 'src/ui_fsmenu/launch_spg.h'
1366--- src/ui_fsmenu/launch_spg.h 2016-10-21 08:21:41 +0000
1367+++ src/ui_fsmenu/launch_spg.h 2016-10-29 10:23:37 +0000
1368@@ -20,10 +20,13 @@
1369 #ifndef WL_UI_FSMENU_LAUNCH_SPG_H
1370 #define WL_UI_FSMENU_LAUNCH_SPG_H
1371
1372+#include <memory>
1373 #include <string>
1374
1375 #include "graphic/playercolor.h"
1376+#include "logic/map.h"
1377 #include "ui_basic/button.h"
1378+#include "ui_basic/dropdown.h"
1379 #include "ui_basic/multilinetextarea.h"
1380 #include "ui_basic/textarea.h"
1381 #include "ui_fsmenu/base.h"
1382@@ -64,9 +67,19 @@
1383 LuaInterface* lua_;
1384
1385 void select_map();
1386- void win_condition_clicked();
1387- void win_condition_update();
1388- void win_condition_load();
1389+ /// Loads all win conditions that can be played with the map into the selection dropdown.
1390+ /// Disables the dropdown if the map is a scenario.
1391+ void update_win_conditions();
1392+ /// Reads the win conditions that are available for the given map and adds the entries to the
1393+ /// dropdown.
1394+ void load_win_conditions(const Widelands::Map& map);
1395+ /// Remembers the win condition that is currently selected in the dropdown.
1396+ void win_condition_selected();
1397+ /// If the win condition in 'win_condition_script' can be played with the map tags,
1398+ /// parses the win condition and returns it as a std::unique_ptr<LuaTable>.
1399+ /// If this win condition can't be played with the map tags, returns a unique_ptr to nullptr.
1400+ std::unique_ptr<LuaTable> win_condition_if_valid(const std::string& win_condition_script,
1401+ std::set<std::string> tags) const;
1402 void set_scenario_values();
1403 void switch_to_position(uint8_t);
1404 void safe_place_for_host(uint8_t);
1405@@ -74,7 +87,10 @@
1406 uint32_t butw_;
1407 uint32_t buth_;
1408
1409- UI::Button select_map_, wincondition_, back_, ok_;
1410+ UI::Button select_map_;
1411+ UI::Dropdown<std::string> win_condition_dropdown_;
1412+ std::string last_win_condition_;
1413+ UI::Button back_, ok_;
1414 UI::Button* pos_[kMaxPlayers];
1415 UI::Textarea title_, mapname_;
1416 UI::Textarea name_, type_, team_, tribe_, init_, wincondition_type_;
1417@@ -87,8 +103,6 @@
1418 std::string player_save_tribe_[kMaxPlayers];
1419 int8_t nr_players_;
1420 bool is_scenario_;
1421- std::vector<std::string> win_condition_scripts_;
1422- uint8_t cur_wincondition_;
1423 };
1424
1425 #endif // end of include guard: WL_UI_FSMENU_LAUNCH_SPG_H
1426
1427=== modified file 'src/ui_fsmenu/mapselect.cc'
1428--- src/ui_fsmenu/mapselect.cc 2016-10-24 14:04:00 +0000
1429+++ src/ui_fsmenu/mapselect.cc 2016-10-29 10:23:37 +0000
1430@@ -81,7 +81,8 @@
1431 new UI::Box(this, tablex_, checkboxes_y_, UI::Box::Horizontal, checkbox_space_, get_w());
1432
1433 // Must be initialized before tag checkboxes
1434- cb_dont_localize_mapnames_ = new UI::Checkbox(vbox, Vector2i(0, 0), _("Show original map names"));
1435+ cb_dont_localize_mapnames_ =
1436+ new UI::Checkbox(vbox, Vector2i(0, 0), _("Show original map names"));
1437 cb_dont_localize_mapnames_->set_state(false);
1438 cb_dont_localize_mapnames_->changedto.connect(
1439 boost::bind(&FullscreenMenuMapSelect::fill_table, boost::ref(*this)));
1440
1441=== modified file 'src/ui_fsmenu/options.cc'
1442--- src/ui_fsmenu/options.cc 2016-10-24 14:04:00 +0000
1443+++ src/ui_fsmenu/options.cc 2016-10-29 10:23:37 +0000
1444@@ -144,11 +144,20 @@
1445 box_sound_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_),
1446 box_saving_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_),
1447 box_game_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_),
1448- box_language_(&tabs_, 0, 0, UI::Box::Vertical, 0, 0, padding_),
1449
1450 // Interface options
1451- label_resolution_(&box_interface_, _("In-game resolution"), UI::Align::kLeft),
1452- resolution_list_(&box_interface_, 0, 0, column_width_ / 2, 80, true),
1453+ language_dropdown_(&box_interface_,
1454+ 0,
1455+ 0,
1456+ column_width_ / 2,
1457+ get_inner_h() - tab_panel_y_ - buth_ - hmargin_ - 4 * padding_,
1458+ _("Language")),
1459+ resolution_dropdown_(&box_interface_,
1460+ 0,
1461+ 0,
1462+ column_width_ / 2,
1463+ get_inner_h() - tab_panel_y_ - 2 * buth_ - hmargin_ - 5 * padding_,
1464+ _("In-game resolution")),
1465
1466 fullscreen_(&box_interface_, Vector2i(0, 0), _("Fullscreen"), "", column_width_),
1467 inputgrab_(&box_interface_, Vector2i(0, 0), _("Grab Input"), "", column_width_),
1468@@ -236,7 +245,8 @@
1469 column_width_),
1470
1471 // Game options
1472- auto_roadbuild_mode_(&box_game_, Vector2i(0, 0), _("Start building road after placing a flag")),
1473+ auto_roadbuild_mode_(
1474+ &box_game_, Vector2i(0, 0), _("Start building road after placing a flag")),
1475 show_workarea_preview_(&box_game_, Vector2i(0, 0), _("Show buildings area preview")),
1476 transparent_chat_(&box_game_,
1477 Vector2i(0, 0),
1478@@ -248,15 +258,6 @@
1479 /** TRANSLATORS: and it also lets you jump to it on the map. */
1480 single_watchwin_(&box_game_, Vector2i(0, 0), _("Use single watchwindow mode")),
1481
1482- // Language options
1483- label_language_(&box_language_, _("Language"), UI::Align::kLeft),
1484- language_list_(&box_language_,
1485- 0,
1486- 0,
1487- column_width_ / 2,
1488- get_inner_h() - tab_panel_y_ - 2 * buth_ - hmargin_ - 5 * padding_,
1489- true),
1490-
1491 os_(opt) {
1492 // Set up UI Elements
1493 title_.set_fontsize(UI_FONT_SIZE_BIG);
1494@@ -266,7 +267,6 @@
1495 tabs_.add("options_sound", _("Sound"), &box_sound_, "");
1496 tabs_.add("options_saving", _("Saving"), &box_saving_, "");
1497 tabs_.add("options_game", _("Game"), &box_game_, "");
1498- tabs_.add("options_language", _("Language"), &box_language_, "");
1499
1500 // We want the last active tab when "Apply" was clicked.
1501 if (os_.active_tab < tabs_.tabs().size()) {
1502@@ -280,11 +280,10 @@
1503 box_sound_.set_size(tabs_.get_inner_w(), tabs_.get_inner_h());
1504 box_saving_.set_size(tabs_.get_inner_w(), tabs_.get_inner_h());
1505 box_game_.set_size(tabs_.get_inner_w(), tabs_.get_inner_h());
1506- box_language_.set_size(tabs_.get_inner_w(), tabs_.get_inner_h());
1507
1508 // Interface
1509- box_interface_.add(&label_resolution_, UI::Align::kLeft);
1510- box_interface_.add(&resolution_list_, UI::Align::kLeft);
1511+ box_interface_.add(&language_dropdown_, UI::Align::kLeft);
1512+ box_interface_.add(&resolution_dropdown_, UI::Align::kLeft);
1513 box_interface_.add(&fullscreen_, UI::Align::kLeft);
1514 box_interface_.add(&inputgrab_, UI::Align::kLeft);
1515 box_interface_.add(&sb_maxfps_, UI::Align::kLeft);
1516@@ -312,10 +311,6 @@
1517 box_game_.add(&transparent_chat_, UI::Align::kLeft);
1518 box_game_.add(&single_watchwin_, UI::Align::kLeft);
1519
1520- // Language
1521- box_language_.add(&label_language_, UI::Align::kLeft);
1522- box_language_.add(&language_list_, UI::Align::kLeft);
1523-
1524 // Bind actions
1525 cancel_.sigclicked.connect(boost::bind(&FullscreenMenuOptions::clicked_back, this));
1526 apply_.sigclicked.connect(boost::bind(&FullscreenMenuOptions::clicked_apply, this));
1527@@ -346,18 +341,18 @@
1528 for (uint32_t i = 0; i < resolutions_.size(); ++i) {
1529 const bool selected = resolutions_[i].xres == opt.xres && resolutions_[i].yres == opt.yres;
1530 did_select_a_res |= selected;
1531- /** TRANSLATORS: Screen resolution, e.g. 800 x 600*/
1532- resolution_list_.add(
1533- (boost::format(_("%1% x %2%")) % resolutions_[i].xres % resolutions_[i].yres).str(),
1534- nullptr, nullptr, selected);
1535+ resolution_dropdown_.add(
1536+ /** TRANSLATORS: Screen resolution, e.g. 800 x 600*/
1537+ (boost::format(_("%1% x %2%")) % resolutions_[i].xres % resolutions_[i].yres).str(), i,
1538+ nullptr, selected);
1539 }
1540 if (!did_select_a_res) {
1541- resolution_list_.add(
1542- (boost::format(_("%1% x %2%")) % opt.xres % opt.yres).str(), nullptr, nullptr, true);
1543 uint32_t entry = resolutions_.size();
1544 resolutions_.resize(entry + 1);
1545 resolutions_[entry].xres = opt.xres;
1546 resolutions_[entry].yres = opt.yres;
1547+ resolution_dropdown_.add(
1548+ (boost::format(_("%1% x %2%")) % opt.xres % opt.yres).str(), entry, nullptr, true);
1549 }
1550
1551 fullscreen_.set_state(opt.fullscreen);
1552@@ -386,14 +381,13 @@
1553
1554 // Language options
1555 add_languages_to_list(opt.language);
1556- language_list_.focus();
1557 }
1558
1559 void FullscreenMenuOptions::add_languages_to_list(const std::string& current_locale) {
1560
1561 // We want these two entries on top - the most likely user's choice and the default.
1562- language_list_.add(_("Try system language"), "", nullptr, current_locale == "");
1563- language_list_.add("English", "en", nullptr, current_locale == "en");
1564+ language_dropdown_.add(_("Try system language"), "", nullptr, current_locale == "");
1565+ language_dropdown_.add("English", "en", nullptr, current_locale == "en");
1566
1567 // Add translation directories to the list
1568 std::vector<LanguageEntry> entries;
1569@@ -442,8 +436,8 @@
1570 find_selected_locale(&selected_locale, current_locale);
1571 std::sort(entries.begin(), entries.end());
1572 for (const LanguageEntry& entry : entries) {
1573- language_list_.add(entry.descname.c_str(), entry.localename, nullptr,
1574- entry.localename == selected_locale, "");
1575+ language_dropdown_.add(entry.descname.c_str(), entry.localename, nullptr,
1576+ entry.localename == selected_locale, "");
1577 }
1578 }
1579
1580@@ -454,9 +448,14 @@
1581 OptionsCtrl::OptionsStruct FullscreenMenuOptions::get_values() {
1582 // Write all data from UI elements
1583 // Interface options
1584- const uint32_t res_index = resolution_list_.selection_index();
1585- os_.xres = resolutions_[res_index].xres;
1586- os_.yres = resolutions_[res_index].yres;
1587+ if (language_dropdown_.has_selection()) {
1588+ os_.language = language_dropdown_.get_selected();
1589+ }
1590+ if (resolution_dropdown_.has_selection()) {
1591+ const uint32_t res_index = resolution_dropdown_.get_selected();
1592+ os_.xres = resolutions_[res_index].xres;
1593+ os_.yres = resolutions_[res_index].yres;
1594+ }
1595 os_.fullscreen = fullscreen_.get_state();
1596 os_.inputgrab = inputgrab_.get_state();
1597 os_.maxfps = sb_maxfps_.get_value();
1598@@ -484,11 +483,6 @@
1599 os_.transparent_chat = transparent_chat_.get_state();
1600 os_.single_watchwin = single_watchwin_.get_state();
1601
1602- // Language options
1603- if (language_list_.has_selection()) {
1604- os_.language = language_list_.get_selected();
1605- }
1606-
1607 // Last tab for reloading the options menu
1608 os_.active_tab = tabs_.active();
1609 return os_;
1610
1611=== modified file 'src/ui_fsmenu/options.h'
1612--- src/ui_fsmenu/options.h 2016-08-04 15:49:05 +0000
1613+++ src/ui_fsmenu/options.h 2016-10-29 10:23:37 +0000
1614@@ -27,7 +27,7 @@
1615
1616 #include "ui_basic/button.h"
1617 #include "ui_basic/checkbox.h"
1618-#include "ui_basic/listselect.h"
1619+#include "ui_basic/dropdown.h"
1620 #include "ui_basic/multilinetextarea.h"
1621 #include "ui_basic/spinbox.h"
1622 #include "ui_basic/tabpanel.h"
1623@@ -122,11 +122,10 @@
1624 UI::Box box_sound_;
1625 UI::Box box_saving_;
1626 UI::Box box_game_;
1627- UI::Box box_language_;
1628
1629 // Interface options
1630- UI::Textarea label_resolution_;
1631- UI::Listselect<void*> resolution_list_;
1632+ UI::Dropdown<std::string> language_dropdown_;
1633+ UI::Dropdown<uintptr_t> resolution_dropdown_;
1634 UI::Checkbox fullscreen_;
1635 UI::Checkbox inputgrab_;
1636 UI::SpinBox sb_maxfps_;
1637@@ -154,10 +153,6 @@
1638 UI::Checkbox transparent_chat_;
1639 UI::Checkbox single_watchwin_;
1640
1641- // Language options
1642- UI::Textarea label_language_;
1643- UI::Listselect<std::string> language_list_;
1644-
1645 OptionsCtrl::OptionsStruct os_;
1646
1647 class ScreenResolution {
1648
1649=== modified file 'src/wui/game_objectives_menu.cc'
1650--- src/wui/game_objectives_menu.cc 2016-08-04 15:49:05 +0000
1651+++ src/wui/game_objectives_menu.cc 2016-10-29 10:23:37 +0000
1652@@ -40,7 +40,7 @@
1653 580,
1654 5 + OBJECTIVE_LIST + 5 + FULL_OBJECTIVE_TEXT + 5 + BUTTON_HEIGHT + 5,
1655 _("Objectives")),
1656- list(this, 5, 5, get_inner_w() - 10, OBJECTIVE_LIST, false),
1657+ list(this, 5, 5, get_inner_w() - 10, OBJECTIVE_LIST),
1658 objectivetext(this,
1659 5,
1660 130,

Subscribers

People subscribed via source and target branches

to status/vote changes: