Merge lp:~widelands-dev/widelands/toggle_resources into lp:widelands

Proposed by SirVer
Status: Merged
Merged at revision: 8421
Proposed branch: lp:~widelands-dev/widelands/toggle_resources
Merge into: lp:widelands
Prerequisite: lp:~widelands-dev/widelands/buildhelp_overlay
Diff against target: 337 lines (+107/-74)
6 files modified
data/scripting/editor/editor_controls.lua (+2/-0)
src/editor/editorinteractive.cc (+37/-19)
src/editor/editorinteractive.h (+3/-1)
src/graphic/game_renderer.cc (+7/-13)
src/wui/field_overlay_manager.cc (+13/-28)
src/wui/field_overlay_manager.h (+45/-13)
To merge this branch: bzr merge lp:~widelands-dev/widelands/toggle_resources
Reviewer Review Type Date Requested Status
GunChleoc Approve
Review via email: mp+328958@code.launchpad.net

Commit message

Make resources toggable in the editor.

- Changes drawing of resources to use a callback instead of a vector of objects which saves some code and might be slightly more efficient.

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

Might also fix bug 1696064, at least I could not repro it with this branch.

Bug 1649958 contains more discussion about this feature, for example I did not add a hotkey yet (which should be simple to do), because I am unsure about the current consensus.

Not drawing map objects is not part of this branch, but should be relatively simple to implement.

Revision history for this message
SirVer (sirver) wrote :

Oh, and this is diffbased on https://code.launchpad.net/~widelands-dev/widelands/buildhelp_overlay which must be merged first.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2534. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/264163602.
Appveyor build 2358. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_toggle_resources-2358.

Revision history for this message
toptopple (7010622-q-deactivatedaccount) wrote :

Thanks for taking this up, SirVer!

Looks good to me on first sight (not extensively tested) with one exception:
-- issue: the resource editing tool has to be deactivated while the RESOURCE VIEW is switched OFF. Deactivated means it must not be possible to create or remove any resources on the map.

Extended:
-- the 'q' key is targeted as a shortcut for the resource view button

Revision history for this message
SirVer (sirver) wrote :

> the 'q' key is targeted as a shortcut for the resource view button

done.

> issue: the resource editing tool has to be deactivated while the RESOURCE VIEW is switched OFF. Deactivated means it must not be possible to create or remove any resources on the map.

I disagree with this design for two reasons:

1) precedence: right now it is already possible to change stuff with UI disabled. For example you can change terrain and this changes the buildhelp even if it is toggled off. You can also add/remove port spaces without the buildhelp being enabled.

2) mental model: right now, the editor is simple: you have exactly one tool selected and you can toggle some visualization (buildhelp) on and off. Both are independent. Now we add a new toggle for another visualization (resources), but it is another toggle, not bound to any of the other state.

If you now make some tools dependent on which visualization is toggled on you get a decision tree that the user has to understand first. I argue this is more complex in code and more complex to use.

Revision history for this message
toptopple (7010622-q-deactivatedaccount) wrote :

I assume people, including me, can live with such arrangement as you propose. I find it not so convincing, however. While I see your point, I don't quite agree that the current state of the Buildhelp view and related tools has to remain as is.

If I had to decide I probably would prefer the WYSIWIG principle and regard it undesirable you have feasibility to perform editions which you are not visibly aware of, or made aware of, so to speak. So I would agree the edition principles should hold strong for all similar tools, but I rather suggest to investigate to change the Buildhelp settings than allow for invisible editions for other tools.

The modifications of terrain can be ignored as Buildhelp display can be defined as derivation from the current terrain shape, so it's not really that you create or remove Buildhelp patterns. In that there is no tool involved. Only thing relevant are edifice creation or Portspace creation and these would have to be changed and made dependent on Buildhelp visibility. On a sidenote: once Portspaces are indicated by Anchors, as is planned for the future, the Buildhelp arguments falls aside for this tool.

Revision history for this message
SirVer (sirver) wrote :

>>> from Gun
I think we could further simplify FieldOverlayManager::get_overlays by making kLevelForBuildHelp part of the OverlayLevel enum class, something like this:

 auto it = overlays_.lower_bound(c);
 while (it != overlays_.end() && it->first == c) {
            if (buildhelp_ && it->second.level == OverlayLevel::kLevelForBuildHelp) {
                result->emplace_back(buildhelp_infos_[get_buildhelp_overlay(c)]);
            } else {
  result->emplace_back(it->second.pic, it->second.hotspot);
  ++it;
            }
 }

We should then be able to use a range-bases for loop too?
<<<

OverlayLevel is part of the public API of this class and kLevelForBuildHelp should never be used when 'register_overlay' is called. I therefore opted to not make it part of the enum class to make misuse harder.

Also, you cannot range base over all values in an enum class easily [1] and since no overlay will ever be inserted into 'overlays_' where 'it->second.level == OverlayLevel::kLevelForBuildHelp' - since buildhelp is handled differently from all other overlays - the suggested loop simplification unfortunately does not work.

I am not super happy with how this class looks and agree with your push to make it simpler. Unfortunately the correct way I see for this would be to make buildhelp not be special - which would mean that the class will require much more memory which is also not super desirable. Alternatively we could scratch the multimap and instead have callbacks for each OverlayLevel and calculate which overlays are required each frame on the fly. But that is also a pretty deep change. I think for now I am satisfied with having pushed the class a bit towards simplification.

[1] https://stackoverflow.com/questions/8498300/allow-for-range-based-for-with-enum-classes

Revision history for this message
SirVer (sirver) wrote :

At #12: I think we just disagree here. I prefer a simple mental model, you prefer a tool that always shows what is currently changing. I would then argue: what happens for undo-redo, would this toggle resources on and off? Also, tools like Gimp or photoshop also support my position [1]: If you make a layer invisible in gimp and draw into the image, the layer is modified though there is no visual feedback. I also argue that your implementation is harder to get right because it has more cross-dependencies between tools and UI.

For now I suggest we submit this as is and see if people actually have trouble with the simple mental model - we can always revisit in the future.

[1] https://www.youtube.com/watch?v=heFsYfQjOKg

Revision history for this message
toptopple (7010622-q-deactivatedaccount) wrote :

Ok, I don't think I have any personal problem here! ;)

Are you going to work on the second view filter as well? This should switch all objects of the "Immovable" class/tool. And independent thereof: can you implement or make a suggestion for the Immovable View switch button?

Revision history for this message
SirVer (sirver) wrote :

> Are you going to work on the second view filter as well? This should switch all objects of the "Immovable" class/tool.

This and independent toggling of animals/bobs is done in https://code.launchpad.net/~widelands-dev/widelands/toggle_immovables/+merge/328977.

> And independent thereof: can you implement or make a suggestion for the Immovable View switch button?

Not sure I understand the question. Are you talking about the button graphic? I am not an artist, I simply copied the icons for the corresponding tool menus. Together with the grouping of the buttons into toggable UI things I think that is sufficiently understandable.

Revision history for this message
GunChleoc (gunchleoc) wrote :

> OverlayLevel is part of the public API of this class and kLevelForBuildHelp should never be used when 'register_overlay' is called. I therefore opted to not make it part of the enum class to make misuse harder.

This has convinced me, plus that we really don't want to use more memory. Let's keep it as it is then.

Regarding the overlay on/off thing, how about switching it on when the tool gets activated? IMO it might be annoying with the increase/decrease tool if the user had to remember to switch it on in order to see what they are doing.

I agree that deactivating the tool when the overlay is switched off would be confusing for the user and cause some hair-pulling - reactions would probably be "Where on Earth has my bl** tool gone?"

Code LGTM, I'll do a bit of testing.

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

Testing works. I'll leave it up to you if you want to real with the auto-switchon in this merge request or in the follow-up branch.

Revision history for this message
SirVer (sirver) wrote :

> Regarding the overlay on/off thing, how about switching it on when the tool gets activated?

I do not like this sort of covenience magic. It has the ability to annoy users just as much as help them. If we design the hotkeys correctly it will be cheap to toggle the overlays on and off, so this convenience will not add much.

Revision history for this message
SirVer (sirver) wrote :

@bunnybot merge

Revision history for this message
GunChleoc (gunchleoc) wrote :

OK, let's leave this as it is then.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'data/images/wui/menus/menu_toggle_resources.png'
2Binary files data/images/wui/menus/menu_toggle_resources.png 1970-01-01 00:00:00 +0000 and data/images/wui/menus/menu_toggle_resources.png 2017-08-15 09:35:42 +0000 differ
3=== modified file 'data/scripting/editor/editor_controls.lua'
4--- data/scripting/editor/editor_controls.lua 2016-10-01 07:45:42 +0000
5+++ data/scripting/editor/editor_controls.lua 2017-08-15 09:35:42 +0000
6@@ -23,6 +23,8 @@
7 toggle_building_spaces_hotkey ..
8 -- TRANSLATORS: This is an access key combination. The hotkey is 'p'
9 dl(help_format_hotkey("P"), _"Toggle player menu") ..
10+ -- TRANSLATORS: This is an access key combination. The hotkey is 'q'
11+ dl(help_format_hotkey("Q"), _"Toggle resources display") ..
12 -- TRANSLATORS: This is an access key combination. Localize, but do not change the key.
13 dl(help_format_hotkey("Ctrl + Z"), _"Undo") ..
14 -- TRANSLATORS: This is an access key combination. Localize, but do not change the key.
15
16=== modified file 'src/editor/editorinteractive.cc'
17--- src/editor/editorinteractive.cc 2017-08-13 18:38:27 +0000
18+++ src/editor/editorinteractive.cc 2017-08-15 09:35:42 +0000
19@@ -115,16 +115,22 @@
20
21 toolbar_.add_space(15);
22
23- add_toolbar_button(
24- "wui/menus/menu_toggle_minimap", "minimap", _("Minimap"), &minimap_registry(), true);
25- minimap_registry().open_window = [this] { toggle_minimap(); };
26-
27 toggle_buildhelp_ = add_toolbar_button(
28 "wui/menus/menu_toggle_buildhelp", "buildhelp", _("Show Building Spaces (on/off)"));
29 toggle_buildhelp_->sigclicked.connect(boost::bind(&EditorInteractive::toggle_buildhelp, this));
30-
31- reset_zoom_ = add_toolbar_button("wui/menus/menu_reset_zoom", "reset_zoom", _("Reset zoom"));
32- reset_zoom_->sigclicked.connect([this] {
33+ toggle_resources_ = add_toolbar_button(
34+ "wui/menus/menu_toggle_resources", "resources", _("Show Resources (on/off)"));
35+ toggle_resources_->set_perm_pressed(true);
36+ toggle_resources_->sigclicked.connect([this]() { toggle_resources(); });
37+
38+ toolbar_.add_space(15);
39+
40+ add_toolbar_button(
41+ "wui/menus/menu_toggle_minimap", "minimap", _("Minimap"), &minimap_registry(), true);
42+ minimap_registry().open_window = [this] { toggle_minimap(); };
43+
44+ auto zoom = add_toolbar_button("wui/menus/menu_reset_zoom", "reset_zoom", _("Reset zoom"));
45+ zoom->sigclicked.connect([this] {
46 zoom_around(1.f, Vector2f(get_w() / 2.f, get_h() / 2.f), MapView::Transition::Smooth);
47 });
48
49@@ -326,6 +332,13 @@
50 toggle_buildhelp_->set_perm_pressed(value);
51 }
52
53+void EditorInteractive::toggle_resources() {
54+ auto* overlay_manager = mutable_field_overlay_manager();
55+ const bool value = !overlay_manager->is_enabled(OverlayLevel::kResource);
56+ overlay_manager->set_enabled(OverlayLevel::kResource, value);
57+ toggle_resources_->set_perm_pressed(value);
58+}
59+
60 bool EditorInteractive::handle_key(bool const down, SDL_Keysym const code) {
61 bool handled = InteractiveBase::handle_key(down, code);
62
63@@ -416,22 +429,27 @@
64 handled = true;
65 break;
66
67+ case SDLK_l:
68+ if (code.mod & (KMOD_LCTRL | KMOD_RCTRL))
69+ new MainMenuLoadMap(*this);
70+ handled = true;
71+ break;
72+
73 case SDLK_m:
74 minimap_registry().toggle();
75 handled = true;
76 break;
77
78- case SDLK_l:
79- if (code.mod & (KMOD_LCTRL | KMOD_RCTRL))
80- new MainMenuLoadMap(*this);
81- handled = true;
82- break;
83-
84 case SDLK_p:
85 playermenu_.toggle();
86 handled = true;
87 break;
88
89+ case SDLK_q:
90+ toggle_resources();
91+ handled = true;
92+ break;
93+
94 case SDLK_s:
95 if (code.mod & (KMOD_LCTRL | KMOD_RCTRL))
96 new MainMenuSaveMap(*this);
97@@ -443,6 +461,12 @@
98 handled = true;
99 break;
100
101+ case SDLK_y:
102+ if (code.mod & (KMOD_LCTRL | KMOD_RCTRL))
103+ history_->redo_action(egbase().world());
104+ handled = true;
105+ break;
106+
107 case SDLK_z:
108 if ((code.mod & (KMOD_LCTRL | KMOD_RCTRL)) && (code.mod & (KMOD_LSHIFT | KMOD_RSHIFT)))
109 history_->redo_action(egbase().world());
110@@ -451,12 +475,6 @@
111 handled = true;
112 break;
113
114- case SDLK_y:
115- if (code.mod & (KMOD_LCTRL | KMOD_RCTRL))
116- history_->redo_action(egbase().world());
117- handled = true;
118- break;
119-
120 case SDLK_F1:
121 helpmenu_.toggle();
122 handled = true;
123
124=== modified file 'src/editor/editorinteractive.h'
125--- src/editor/editorinteractive.h 2017-06-24 08:47:46 +0000
126+++ src/editor/editorinteractive.h 2017-08-15 09:35:42 +0000
127@@ -156,6 +156,8 @@
128
129 void on_buildhelp_changed(const bool value) override;
130
131+ void toggle_resources();
132+
133 // state variables
134 bool need_save_;
135 std::vector<PlayerReferences> player_tribe_references_;
136@@ -178,7 +180,7 @@
137 UI::UniqueWindow::Registry helpmenu_;
138
139 UI::Button* toggle_buildhelp_;
140- UI::Button* reset_zoom_;
141+ UI::Button* toggle_resources_;
142 UI::Button* undo_;
143 UI::Button* redo_;
144
145
146=== modified file 'src/graphic/game_renderer.cc'
147--- src/graphic/game_renderer.cc 2017-08-13 18:02:53 +0000
148+++ src/graphic/game_renderer.cc 2017-08-15 09:35:42 +0000
149@@ -210,7 +210,6 @@
150 const Player* player,
151 const TextToDraw text_to_draw,
152 RenderTarget* dst) {
153- std::vector<FieldOverlayManager::OverlayInfo> overlay_info;
154 for (size_t current_index = 0; current_index < fields_to_draw.size(); ++current_index) {
155 const FieldsToDraw::Field& field = fields_to_draw.at(current_index);
156 if (!field.all_neighbors_valid()) {
157@@ -248,18 +247,13 @@
158 draw_objects_for_formerly_visible_field(field, player_field, zoom, dst);
159 }
160
161- const FieldOverlayManager& overlay_manager = egbase.get_ibase()->field_overlay_manager();
162- {
163- overlay_info.clear();
164- overlay_manager.get_overlays(field.fcoords, &overlay_info);
165- for (const auto& overlay : overlay_info) {
166- dst->blitrect_scale(
167- Rectf(field.rendertarget_pixel - overlay.hotspot.cast<float>() * zoom,
168- overlay.pic->width() * zoom, overlay.pic->height() * zoom),
169- overlay.pic, Recti(0, 0, overlay.pic->width(), overlay.pic->height()), 1.f,
170- BlendMode::UseAlpha);
171- }
172- }
173+ egbase.get_ibase()->field_overlay_manager().foreach_overlay(
174+ field.fcoords, [dst, &field, zoom](const Image* pic, const Vector2i& hotspot) {
175+ dst->blitrect_scale(Rectf(field.rendertarget_pixel - hotspot.cast<float>() * zoom,
176+ pic->width() * zoom, pic->height() * zoom),
177+ pic, Recti(0, 0, pic->width(), pic->height()), 1.f,
178+ BlendMode::UseAlpha);
179+ });
180 }
181 }
182
183
184=== modified file 'src/wui/field_overlay_manager.cc'
185--- src/wui/field_overlay_manager.cc 2017-08-15 07:47:21 +0000
186+++ src/wui/field_overlay_manager.cc 2017-08-15 09:35:42 +0000
187@@ -26,12 +26,6 @@
188 #include "graphic/graphic.h"
189 #include "logic/field.h"
190
191-namespace {
192-
193-constexpr int kLevelForBuildHelp = 5;
194-
195-} // namespace
196-
197 FieldOverlayManager::FieldOverlayManager() : current_overlay_id_(0) {
198 OverlayInfo* buildhelp_info = buildhelp_infos_;
199 const char* filenames[] = {"images/wui/overlays/set_flag.png", "images/wui/overlays/small.png",
200@@ -64,28 +58,6 @@
201 buildhelp_ = value;
202 }
203
204-void FieldOverlayManager::get_overlays(const Widelands::FCoords& c,
205- std::vector<OverlayInfo>* result) const {
206- auto it = overlays_.lower_bound(c);
207- while (it != overlays_.end() && it->first == c &&
208- static_cast<int>(it->second.level) <= kLevelForBuildHelp) {
209- result->emplace_back(it->second.pic, it->second.hotspot);
210- ++it;
211- }
212-
213- if (buildhelp_) {
214- int buildhelp_overlay_index = get_buildhelp_overlay(c);
215- if (buildhelp_overlay_index < Widelands::Field::Buildhelp_None) {
216- result->emplace_back(buildhelp_infos_[buildhelp_overlay_index]);
217- }
218- }
219-
220- while (it != overlays_.end() && it->first == c) {
221- result->emplace_back(it->second.pic, it->second.hotspot);
222- ++it;
223- }
224-}
225-
226 int FieldOverlayManager::get_buildhelp_overlay(const Widelands::FCoords& fc) const {
227 Widelands::NodeCaps const caps =
228 callback_ ? static_cast<Widelands::NodeCaps>(callback_(fc)) : fc.field->nodecaps();
229@@ -195,3 +167,16 @@
230 ++current_overlay_id_;
231 return current_overlay_id_;
232 }
233+
234+bool FieldOverlayManager::is_enabled(const OverlayLevel& level) const {
235+ return disabled_layers_.count(level) == 0;
236+}
237+
238+void FieldOverlayManager::set_enabled(const OverlayLevel& level, const bool enabled) {
239+ if (enabled) {
240+ disabled_layers_.erase(level);
241+ } else {
242+ disabled_layers_.insert(level);
243+ }
244+}
245+
246
247=== modified file 'src/wui/field_overlay_manager.h'
248--- src/wui/field_overlay_manager.h 2017-08-15 07:47:21 +0000
249+++ src/wui/field_overlay_manager.h 2017-08-15 09:35:42 +0000
250@@ -63,17 +63,6 @@
251 /// A unique id identifying a registered overlay.
252 using OverlayId = uint32_t;
253
254- /// A overlay as drawn onto the screen.
255- struct OverlayInfo {
256- OverlayInfo() = default;
257- OverlayInfo(const Image* init_pic, const Vector2i& init_hotspot)
258- : pic(init_pic), hotspot(init_hotspot) {
259- }
260-
261- const Image* pic;
262- Vector2i hotspot = Vector2i::zero();
263- };
264-
265 /// A function returning Field::nodecaps() for the build overlay. This can be
266 /// registered to hide or change some of the nodecaps during rendering.
267 using CallbackFn = std::function<int32_t(const Widelands::FCoords& coordinates)>;
268@@ -89,6 +78,10 @@
269 /// Register callback function.
270 void register_overlay_callback_function(CallbackFn function);
271
272+ /// Like 'buildhelp', but for an individual layer.
273+ bool is_enabled(const OverlayLevel& level) const;
274+ void set_enabled(const OverlayLevel& level, bool value);
275+
276 /// Get a unique, unused id that can be passed to register_overlay.
277 OverlayId next_overlay_id();
278
279@@ -111,10 +104,44 @@
280 // TODO(sirver): It would be preferable to just delete and recreate the object.
281 void remove_all_overlays();
282
283- /// Returns the currently registered overlays and the buildhelp for a node.
284- void get_overlays(const Widelands::FCoords& c, std::vector<OverlayInfo>* result) const;
285+ /// Calls 'func' for each of the the currently registered and enabled
286+ /// overlays and the buildhelp.
287+ template <typename T> void foreach_overlay(const Widelands::FCoords& c, T func) const {
288+ auto it = overlays_.lower_bound(c);
289+ while (it != overlays_.end() && it->first == c &&
290+ static_cast<int>(it->second.level) <= kLevelForBuildHelp) {
291+ if (is_enabled(it->second.level)) {
292+ func(it->second.pic, it->second.hotspot);
293+ }
294+ ++it;
295+ }
296+
297+ if (buildhelp_) {
298+ int buildhelp_overlay_index = get_buildhelp_overlay(c);
299+ if (buildhelp_overlay_index < Widelands::Field::Buildhelp_None) {
300+ auto& overlay_info = buildhelp_infos_[buildhelp_overlay_index];
301+ func(overlay_info.pic, overlay_info.hotspot);
302+ }
303+ }
304+
305+ while (it != overlays_.end() && it->first == c) {
306+ if (is_enabled(it->second.level)) {
307+ func(it->second.pic, it->second.hotspot);
308+ }
309+ ++it;
310+ }
311+}
312+
313
314 private:
315+ static constexpr int kLevelForBuildHelp = 5;
316+
317+ /// A overlay as drawn onto the screen.
318+ struct OverlayInfo {
319+ const Image* pic = nullptr;
320+ Vector2i hotspot = Vector2i::zero();
321+ };
322+
323 struct RegisteredOverlays {
324 RegisteredOverlays(const OverlayId init_overlay_id,
325 const Image* init_pic,
326@@ -137,6 +164,11 @@
327
328 OverlayInfo buildhelp_infos_[Widelands::Field::Buildhelp_None];
329 bool buildhelp_;
330+ // We are inverting the logic here, since new layers are by default enabled
331+ // and we only support to toggle some of them off. Otherwise whenever a new
332+ // layer is added in 'OverlayLevel' we would also need to add it to the
333+ // 'enabled_layers_' set on construction.
334+ std::set<OverlayLevel> disabled_layers_;
335
336 // this callback is used to define where overlays are drawn.
337 CallbackFn callback_;

Subscribers

People subscribed via source and target branches

to status/vote changes: