Update: Translations from eints
[openttd-github.git] / src / newgrf_debug_gui.cpp
blob1b6c36752020dab3e517f4bdd3b48b68c626ab3f
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file newgrf_debug_gui.cpp GUIs for debugging NewGRFs. */
10 #include "stdafx.h"
11 #include "core/backup_type.hpp"
12 #include "core/geometry_func.hpp"
13 #include "window_gui.h"
14 #include "window_func.h"
15 #include "random_access_file_type.h"
16 #include "spritecache.h"
17 #include "string_func.h"
18 #include "strings_func.h"
19 #include "textbuf_gui.h"
20 #include "vehicle_gui.h"
21 #include "zoom_func.h"
23 #include "engine_base.h"
24 #include "industry.h"
25 #include "object_base.h"
26 #include "station_base.h"
27 #include "town.h"
28 #include "vehicle_base.h"
29 #include "train.h"
30 #include "roadveh.h"
32 #include "newgrf_act5.h"
33 #include "newgrf_airport.h"
34 #include "newgrf_airporttiles.h"
35 #include "newgrf_debug.h"
36 #include "newgrf_object.h"
37 #include "newgrf_spritegroup.h"
38 #include "newgrf_station.h"
39 #include "newgrf_town.h"
40 #include "newgrf_railtype.h"
41 #include "newgrf_industries.h"
42 #include "newgrf_industrytiles.h"
44 #include "widgets/newgrf_debug_widget.h"
46 #include "table/strings.h"
48 #include "safeguards.h"
50 /** The sprite picker. */
51 NewGrfDebugSpritePicker _newgrf_debug_sprite_picker = { SPM_NONE, nullptr, std::vector<SpriteID>() };
53 /**
54 * Get the feature index related to the window number.
55 * @param window_number The window to get the feature index from.
56 * @return the feature index
58 static inline uint GetFeatureIndex(uint window_number)
60 return GB(window_number, 0, 24);
63 /**
64 * Get the window number for the inspect window given a
65 * feature and index.
66 * @param feature The feature we want to inspect.
67 * @param index The index/identifier of the feature to inspect.
68 * @return the InspectWindow (Window)Number
70 static inline uint GetInspectWindowNumber(GrfSpecFeature feature, uint index)
72 assert((index >> 24) == 0);
73 return (feature << 24) | index;
76 /**
77 * The type of a property to show. This is used to
78 * provide an appropriate representation in the GUI.
80 enum NIType {
81 NIT_INT, ///< The property is a simple integer
82 NIT_CARGO, ///< The property is a cargo
85 typedef const void *NIOffsetProc(const void *b);
87 /** Representation of the data from a NewGRF property. */
88 struct NIProperty {
89 const char *name; ///< A (human readable) name for the property
90 NIOffsetProc *offset_proc; ///< Callback proc to get the actual variable address in memory
91 uint8_t read_size; ///< Number of bytes (i.e. byte, word, dword etc)
92 uint8_t prop; ///< The number of the property
93 uint8_t type;
97 /**
98 * Representation of the available callbacks with
99 * information on when they actually apply.
101 struct NICallback {
102 const char *name; ///< The human readable name of the callback
103 NIOffsetProc *offset_proc; ///< Callback proc to get the actual variable address in memory
104 uint8_t read_size; ///< The number of bytes (i.e. byte, word, dword etc) to read
105 uint8_t cb_bit; ///< The bit that needs to be set for this callback to be enabled
106 uint16_t cb_id; ///< The number of the callback
108 /** Mask to show no bit needs to be enabled for the callback. */
109 static const int CBM_NO_BIT = UINT8_MAX;
111 /** Representation on the NewGRF variables. */
112 struct NIVariable {
113 const char *name;
114 uint8_t var;
117 /** Helper class to wrap some functionality/queries in. */
118 class NIHelper {
119 public:
120 /** Silence a warning. */
121 virtual ~NIHelper() = default;
124 * Is the item with the given index inspectable?
125 * @param index the index to check.
126 * @return true iff the index is inspectable.
128 virtual bool IsInspectable(uint index) const = 0;
131 * Get the parent "window_number" of a given instance.
132 * @param index the instance to get the parent for.
133 * @return the parent's window_number or UINT32_MAX if there is none.
135 virtual uint GetParent(uint index) const = 0;
138 * Get the instance given an index.
139 * @param index the index to get the instance for.
140 * @return the instance.
142 virtual const void *GetInstance(uint index) const = 0;
145 * Get (NewGRF) specs given an index.
146 * @param index the index to get the specs for for.
147 * @return the specs.
149 virtual const void *GetSpec(uint index) const = 0;
152 * Set the string parameters to write the right data for a STRINGn.
153 * @param index the index to get the string parameters for.
155 virtual void SetStringParameters(uint index) const = 0;
158 * Get the GRFID of the file that includes this item.
159 * @param index index to check.
160 * @return GRFID of the item. 0 means that the item is not inspectable.
162 virtual uint32_t GetGRFID(uint index) const = 0;
165 * Resolve (action2) variable for a given index.
166 * @param index The (instance) index to resolve the variable for.
167 * @param var The variable to actually resolve.
168 * @param param The varaction2 0x60+x parameter to pass.
169 * @param avail Return whether the variable is available.
170 * @return The resolved variable's value.
172 virtual uint Resolve(uint index, uint var, uint param, bool &avail) const = 0;
175 * Used to decide if the PSA needs a parameter or not.
176 * @return True iff this item has a PSA that requires a parameter.
178 virtual bool PSAWithParameter() const
180 return false;
184 * Gets the span containing the persistent storage.
185 * @param index Index of the item.
186 * @param grfid Parameter for the PSA. Only required for items with parameters.
187 * @return Span of the storage array or an empty span when not present.
189 virtual const std::span<int32_t> GetPSA([[maybe_unused]] uint index, [[maybe_unused]] uint32_t grfid) const
191 return {};
194 protected:
196 * Helper to make setting the strings easier.
197 * @param string the string to actually draw.
198 * @param index the (instance) index for the string.
200 void SetSimpleStringParameters(StringID string, uint32_t index) const
202 SetDParam(0, string);
203 SetDParam(1, index);
208 * Helper to make setting the strings easier for objects at a specific tile.
209 * @param string the string to draw the object's name
210 * @param index the (instance) index for the string.
211 * @param tile the tile the object is at
213 void SetObjectAtStringParameters(StringID string, uint32_t index, TileIndex tile) const
215 SetDParam(0, STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT);
216 SetDParam(1, string);
217 SetDParam(2, index);
218 SetDParam(3, tile);
223 /** Container for all information for a given feature. */
224 struct NIFeature {
225 const NIProperty *properties; ///< The properties associated with this feature.
226 const NICallback *callbacks; ///< The callbacks associated with this feature.
227 const NIVariable *variables; ///< The variables associated with this feature.
228 const NIHelper *helper; ///< The class container all helper functions.
231 /* Load all the NewGRF debug data; externalised as it is just a huge bunch of tables. */
232 #include "table/newgrf_debug_data.h"
235 * Get the feature number related to the window number.
236 * @param window_number The window to get the feature number for.
237 * @return The feature number.
239 static inline GrfSpecFeature GetFeatureNum(uint window_number)
241 return (GrfSpecFeature)GB(window_number, 24, 8);
245 * Get the NIFeature related to the window number.
246 * @param window_number The window to get the NIFeature for.
247 * @return the NIFeature, or nullptr is there isn't one.
249 static inline const NIFeature *GetFeature(uint window_number)
251 GrfSpecFeature idx = GetFeatureNum(window_number);
252 return idx < GSF_FAKE_END ? _nifeatures[idx] : nullptr;
256 * Get the NIHelper related to the window number.
257 * @param window_number The window to get the NIHelper for.
258 * @pre GetFeature(window_number) != nullptr
259 * @return the NIHelper
261 static inline const NIHelper *GetFeatureHelper(uint window_number)
263 return GetFeature(window_number)->helper;
266 /** Window used for inspecting NewGRFs. */
267 struct NewGRFInspectWindow : Window {
268 /** The value for the variable 60 parameters. */
269 static uint32_t var60params[GSF_FAKE_END][0x20];
271 /** GRFID of the caller of this window, 0 if it has no caller. */
272 uint32_t caller_grfid;
274 /** For ground vehicles: Index in vehicle chain. */
275 uint chain_index;
277 /** The currently edited parameter, to update the right one. */
278 uint8_t current_edit_param;
280 Scrollbar *vscroll;
283 * Check whether the given variable has a parameter.
284 * @param variable the variable to check.
285 * @return true iff the variable has a parameter.
287 static bool HasVariableParameter(uint variable)
289 return IsInsideBS(variable, 0x60, 0x20);
293 * Set the GRFID of the item opening this window.
294 * @param grfid GRFID of the item opening this window, or 0 if not opened by other window.
296 void SetCallerGRFID(uint32_t grfid)
298 this->caller_grfid = grfid;
299 this->SetDirty();
303 * Check whether this feature has chain index, i.e. refers to ground vehicles.
305 bool HasChainIndex() const
307 GrfSpecFeature f = GetFeatureNum(this->window_number);
308 return f == GSF_TRAINS || f == GSF_ROADVEHICLES;
312 * Get the feature index.
313 * @return the feature index
315 uint GetFeatureIndex() const
317 uint index = ::GetFeatureIndex(this->window_number);
318 if (this->chain_index > 0) {
319 assert(this->HasChainIndex());
320 const Vehicle *v = Vehicle::Get(index);
321 v = v->Move(this->chain_index);
322 if (v != nullptr) index = v->index;
324 return index;
328 * Ensure that this->chain_index is in range.
330 void ValidateChainIndex()
332 if (this->chain_index == 0) return;
334 assert(this->HasChainIndex());
336 const Vehicle *v = Vehicle::Get(::GetFeatureIndex(this->window_number));
337 v = v->Move(this->chain_index);
338 if (v == nullptr) this->chain_index = 0;
341 NewGRFInspectWindow(WindowDesc &desc, WindowNumber wno) : Window(desc)
343 this->CreateNestedTree();
344 this->vscroll = this->GetScrollbar(WID_NGRFI_SCROLLBAR);
345 this->FinishInitNested(wno);
347 this->vscroll->SetCount(0);
348 this->SetWidgetDisabledState(WID_NGRFI_PARENT, GetFeatureHelper(this->window_number)->GetParent(this->GetFeatureIndex()) == UINT32_MAX);
350 this->OnInvalidateData(0, true);
353 void SetStringParameters(WidgetID widget) const override
355 if (widget != WID_NGRFI_CAPTION) return;
357 GetFeatureHelper(this->window_number)->SetStringParameters(this->GetFeatureIndex());
360 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
362 switch (widget) {
363 case WID_NGRFI_VEH_CHAIN: {
364 assert(this->HasChainIndex());
365 GrfSpecFeature f = GetFeatureNum(this->window_number);
366 size.height = std::max(size.height, GetVehicleImageCellSize((VehicleType)(VEH_TRAIN + (f - GSF_TRAINS)), EIT_IN_DEPOT).height + 2 + WidgetDimensions::scaled.bevel.Vertical());
367 break;
370 case WID_NGRFI_MAINPANEL:
371 resize.height = std::max(11, GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.vsep_normal);
372 resize.width = 1;
374 size.height = 5 * resize.height + WidgetDimensions::scaled.frametext.Vertical();
375 break;
380 * Helper function to draw a string (line) in the window.
381 * @param r The (screen) rectangle we must draw within
382 * @param offset The offset (in lines) we want to draw for
383 * @param string The string to draw
385 void DrawString(const Rect &r, int offset, const std::string &string) const
387 offset -= this->vscroll->GetPosition();
388 if (offset < 0 || offset >= this->vscroll->GetCapacity()) return;
390 ::DrawString(r.Shrink(WidgetDimensions::scaled.frametext).Shrink(0, offset * this->resize.step_height, 0, 0), string, TC_BLACK);
394 * Helper function to draw the vehicle chain widget.
395 * @param r The rectangle to draw within.
397 void DrawVehicleChainWidget(const Rect &r) const
399 const Vehicle *v = Vehicle::Get(this->GetFeatureIndex());
400 int total_width = 0;
401 int sel_start = 0;
402 int sel_end = 0;
403 for (const Vehicle *u = v->First(); u != nullptr; u = u->Next()) {
404 if (u == v) sel_start = total_width;
405 switch (u->type) {
406 case VEH_TRAIN: total_width += Train::From(u)->GetDisplayImageWidth(); break;
407 case VEH_ROAD: total_width += RoadVehicle::From(u)->GetDisplayImageWidth(); break;
408 default: NOT_REACHED();
410 if (u == v) sel_end = total_width;
413 Rect br = r.Shrink(WidgetDimensions::scaled.bevel);
414 int width = br.Width();
415 int skip = 0;
416 if (total_width > width) {
417 int sel_center = (sel_start + sel_end) / 2;
418 if (sel_center > width / 2) skip = std::min(total_width - width, sel_center - width / 2);
421 GrfSpecFeature f = GetFeatureNum(this->window_number);
422 int h = GetVehicleImageCellSize((VehicleType)(VEH_TRAIN + (f - GSF_TRAINS)), EIT_IN_DEPOT).height;
423 int y = CenterBounds(br.top, br.bottom, h);
424 DrawVehicleImage(v->First(), br, INVALID_VEHICLE, EIT_IN_DETAILS, skip);
426 /* Highlight the articulated part (this is different to the whole-vehicle highlighting of DrawVehicleImage */
427 if (_current_text_dir == TD_RTL) {
428 DrawFrameRect(r.right - sel_end + skip, y, r.right - sel_start + skip, y + h, COLOUR_WHITE, FR_BORDERONLY);
429 } else {
430 DrawFrameRect(r.left + sel_start - skip, y, r.left + sel_end - skip, y + h, COLOUR_WHITE, FR_BORDERONLY);
435 * Helper function to draw the main panel widget.
436 * @param r The rectangle to draw within.
438 void DrawMainPanelWidget(const Rect &r) const
440 uint index = this->GetFeatureIndex();
441 const NIFeature *nif = GetFeature(this->window_number);
442 const NIHelper *nih = nif->helper;
443 const void *base = nih->GetInstance(index);
444 const void *base_spec = nih->GetSpec(index);
446 uint i = 0;
447 if (nif->variables != nullptr) {
448 this->DrawString(r, i++, "Variables:");
449 for (const NIVariable *niv = nif->variables; niv->name != nullptr; niv++) {
450 bool avail = true;
451 uint param = HasVariableParameter(niv->var) ? NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][niv->var - 0x60] : 0;
452 uint value = nih->Resolve(index, niv->var, param, avail);
454 if (!avail) continue;
456 if (HasVariableParameter(niv->var)) {
457 this->DrawString(r, i++, fmt::format(" {:02x}[{:02x}]: {:08x} ({})", niv->var, param, value, niv->name));
458 } else {
459 this->DrawString(r, i++, fmt::format(" {:02x}: {:08x} ({})", niv->var, value, niv->name));
464 auto psa = nih->GetPSA(index, this->caller_grfid);
465 if (!psa.empty()) {
466 if (nih->PSAWithParameter()) {
467 this->DrawString(r, i++, fmt::format("Persistent storage [{:08X}]:", BSWAP32(this->caller_grfid)));
468 } else {
469 this->DrawString(r, i++, "Persistent storage:");
471 assert(psa.size() % 4 == 0);
472 for (size_t j = 0; j < psa.size(); j += 4) {
473 this->DrawString(r, i++, fmt::format(" {}: {} {} {} {}", j, psa[j], psa[j + 1], psa[j + 2], psa[j + 3]));
477 if (nif->properties != nullptr) {
478 this->DrawString(r, i++, "Properties:");
479 for (const NIProperty *nip = nif->properties; nip->name != nullptr; nip++) {
480 const void *ptr = nip->offset_proc(base);
481 uint value;
482 switch (nip->read_size) {
483 case 1: value = *(const uint8_t *)ptr; break;
484 case 2: value = *(const uint16_t *)ptr; break;
485 case 4: value = *(const uint32_t *)ptr; break;
486 default: NOT_REACHED();
489 StringID string;
490 SetDParam(0, value);
491 switch (nip->type) {
492 case NIT_INT:
493 string = STR_JUST_INT;
494 break;
496 case NIT_CARGO:
497 string = IsValidCargoID(value) ? CargoSpec::Get(value)->name : STR_QUANTITY_N_A;
498 break;
500 default:
501 NOT_REACHED();
504 this->DrawString(r, i++, fmt::format(" {:02x}: {} ({})", nip->prop, GetString(string), nip->name));
508 if (nif->callbacks != nullptr) {
509 this->DrawString(r, i++, "Callbacks:");
510 for (const NICallback *nic = nif->callbacks; nic->name != nullptr; nic++) {
511 if (nic->cb_bit != CBM_NO_BIT) {
512 const void *ptr = nic->offset_proc(base_spec);
513 uint value;
514 switch (nic->read_size) {
515 case 1: value = *(const uint8_t *)ptr; break;
516 case 2: value = *(const uint16_t *)ptr; break;
517 case 4: value = *(const uint32_t *)ptr; break;
518 default: NOT_REACHED();
521 if (!HasBit(value, nic->cb_bit)) continue;
522 this->DrawString(r, i++, fmt::format(" {:03x}: {}", nic->cb_id, nic->name));
523 } else {
524 this->DrawString(r, i++, fmt::format(" {:03x}: {} (unmasked)", nic->cb_id, nic->name));
529 /* Not nice and certainly a hack, but it beats duplicating
530 * this whole function just to count the actual number of
531 * elements. Especially because they need to be redrawn. */
532 const_cast<NewGRFInspectWindow*>(this)->vscroll->SetCount(i);
535 void DrawWidget(const Rect &r, WidgetID widget) const override
537 switch (widget) {
538 case WID_NGRFI_VEH_CHAIN:
539 this->DrawVehicleChainWidget(r);
540 break;
542 case WID_NGRFI_MAINPANEL:
543 this->DrawMainPanelWidget(r);
544 break;
548 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
550 switch (widget) {
551 case WID_NGRFI_PARENT: {
552 const NIHelper *nih = GetFeatureHelper(this->window_number);
553 uint index = nih->GetParent(this->GetFeatureIndex());
554 ::ShowNewGRFInspectWindow(GetFeatureNum(index), ::GetFeatureIndex(index), nih->GetGRFID(this->GetFeatureIndex()));
555 break;
558 case WID_NGRFI_VEH_PREV:
559 if (this->chain_index > 0) {
560 this->chain_index--;
561 this->InvalidateData();
563 break;
565 case WID_NGRFI_VEH_NEXT:
566 if (this->HasChainIndex()) {
567 uint index = this->GetFeatureIndex();
568 Vehicle *v = Vehicle::Get(index);
569 if (v != nullptr && v->Next() != nullptr) {
570 this->chain_index++;
571 this->InvalidateData();
574 break;
576 case WID_NGRFI_MAINPANEL: {
577 /* Does this feature have variables? */
578 const NIFeature *nif = GetFeature(this->window_number);
579 if (nif->variables == nullptr) return;
581 /* Get the line, make sure it's within the boundaries. */
582 int32_t line = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NGRFI_MAINPANEL, WidgetDimensions::scaled.frametext.top);
583 if (line == INT32_MAX) return;
585 /* Find the variable related to the line */
586 for (const NIVariable *niv = nif->variables; niv->name != nullptr; niv++, line--) {
587 if (line != 1) continue; // 1 because of the "Variables:" line
589 if (!HasVariableParameter(niv->var)) break;
591 this->current_edit_param = niv->var;
592 ShowQueryString(STR_EMPTY, STR_NEWGRF_INSPECT_QUERY_CAPTION, 9, this, CS_HEXADECIMAL, QSF_NONE);
598 void OnQueryTextFinished(std::optional<std::string> str) override
600 if (!str.has_value() || str->empty()) return;
602 NewGRFInspectWindow::var60params[GetFeatureNum(this->window_number)][this->current_edit_param - 0x60] = std::strtol(str->c_str(), nullptr, 16);
603 this->SetDirty();
606 void OnResize() override
608 this->vscroll->SetCapacityFromWidget(this, WID_NGRFI_MAINPANEL, WidgetDimensions::scaled.frametext.Vertical());
612 * Some data on this window has become invalid.
613 * @param data Information about the changed data.
614 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
616 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
618 if (!gui_scope) return;
619 if (this->HasChainIndex()) {
620 this->ValidateChainIndex();
621 this->SetWidgetDisabledState(WID_NGRFI_VEH_PREV, this->chain_index == 0);
622 Vehicle *v = Vehicle::Get(this->GetFeatureIndex());
623 this->SetWidgetDisabledState(WID_NGRFI_VEH_NEXT, v == nullptr || v->Next() == nullptr);
628 /* static */ uint32_t NewGRFInspectWindow::var60params[GSF_FAKE_END][0x20] = { {0} }; // Use spec to have 0s in whole array
630 static constexpr NWidgetPart _nested_newgrf_inspect_chain_widgets[] = {
631 NWidget(NWID_HORIZONTAL),
632 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
633 NWidget(WWT_CAPTION, COLOUR_GREY, WID_NGRFI_CAPTION), SetDataTip(STR_NEWGRF_INSPECT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
634 NWidget(WWT_SHADEBOX, COLOUR_GREY),
635 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
636 NWidget(WWT_STICKYBOX, COLOUR_GREY),
637 EndContainer(),
638 NWidget(WWT_PANEL, COLOUR_GREY),
639 NWidget(NWID_HORIZONTAL),
640 NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_NGRFI_VEH_PREV), SetDataTip(AWV_DECREASE, STR_NULL),
641 NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_NGRFI_VEH_NEXT), SetDataTip(AWV_INCREASE, STR_NULL),
642 NWidget(WWT_EMPTY, COLOUR_GREY, WID_NGRFI_VEH_CHAIN), SetFill(1, 0), SetResize(1, 0),
643 EndContainer(),
644 EndContainer(),
645 NWidget(NWID_HORIZONTAL),
646 NWidget(WWT_PANEL, COLOUR_GREY, WID_NGRFI_MAINPANEL), SetMinimalSize(300, 0), SetScrollbar(WID_NGRFI_SCROLLBAR), EndContainer(),
647 NWidget(NWID_VERTICAL),
648 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_NGRFI_SCROLLBAR),
649 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
650 EndContainer(),
651 EndContainer(),
654 static constexpr NWidgetPart _nested_newgrf_inspect_widgets[] = {
655 NWidget(NWID_HORIZONTAL),
656 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
657 NWidget(WWT_CAPTION, COLOUR_GREY, WID_NGRFI_CAPTION), SetDataTip(STR_NEWGRF_INSPECT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
658 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NGRFI_PARENT), SetDataTip(STR_NEWGRF_INSPECT_PARENT_BUTTON, STR_NEWGRF_INSPECT_PARENT_TOOLTIP),
659 NWidget(WWT_SHADEBOX, COLOUR_GREY),
660 NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
661 NWidget(WWT_STICKYBOX, COLOUR_GREY),
662 EndContainer(),
663 NWidget(NWID_HORIZONTAL),
664 NWidget(WWT_PANEL, COLOUR_GREY, WID_NGRFI_MAINPANEL), SetMinimalSize(300, 0), SetScrollbar(WID_NGRFI_SCROLLBAR), EndContainer(),
665 NWidget(NWID_VERTICAL),
666 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_NGRFI_SCROLLBAR),
667 NWidget(WWT_RESIZEBOX, COLOUR_GREY),
668 EndContainer(),
669 EndContainer(),
672 static WindowDesc _newgrf_inspect_chain_desc(
673 WDP_AUTO, "newgrf_inspect_chain", 400, 300,
674 WC_NEWGRF_INSPECT, WC_NONE,
676 _nested_newgrf_inspect_chain_widgets
679 static WindowDesc _newgrf_inspect_desc(
680 WDP_AUTO, "newgrf_inspect", 400, 300,
681 WC_NEWGRF_INSPECT, WC_NONE,
683 _nested_newgrf_inspect_widgets
687 * Show the inspect window for a given feature and index.
688 * The index is normally an in-game location/identifier, such
689 * as a TileIndex or an IndustryID depending on the feature
690 * we want to inspect.
691 * @param feature The feature we want to inspect.
692 * @param index The index/identifier of the feature to inspect.
693 * @param grfid GRFID of the item opening this window, or 0 if not opened by other window.
695 void ShowNewGRFInspectWindow(GrfSpecFeature feature, uint index, const uint32_t grfid)
697 if (!IsNewGRFInspectable(feature, index)) return;
699 WindowNumber wno = GetInspectWindowNumber(feature, index);
700 WindowDesc &desc = (feature == GSF_TRAINS || feature == GSF_ROADVEHICLES) ? _newgrf_inspect_chain_desc : _newgrf_inspect_desc;
701 NewGRFInspectWindow *w = AllocateWindowDescFront<NewGRFInspectWindow>(desc, wno, true);
702 w->SetCallerGRFID(grfid);
706 * Invalidate the inspect window for a given feature and index.
707 * The index is normally an in-game location/identifier, such
708 * as a TileIndex or an IndustryID depending on the feature
709 * we want to inspect.
710 * @param feature The feature we want to invalidate the window for.
711 * @param index The index/identifier of the feature to invalidate.
713 void InvalidateNewGRFInspectWindow(GrfSpecFeature feature, uint index)
715 if (feature == GSF_INVALID) return;
717 WindowNumber wno = GetInspectWindowNumber(feature, index);
718 InvalidateWindowData(WC_NEWGRF_INSPECT, wno);
722 * Delete inspect window for a given feature and index.
723 * The index is normally an in-game location/identifier, such
724 * as a TileIndex or an IndustryID depending on the feature
725 * we want to inspect.
726 * @param feature The feature we want to delete the window for.
727 * @param index The index/identifier of the feature to delete.
729 void DeleteNewGRFInspectWindow(GrfSpecFeature feature, uint index)
731 if (feature == GSF_INVALID) return;
733 WindowNumber wno = GetInspectWindowNumber(feature, index);
734 CloseWindowById(WC_NEWGRF_INSPECT, wno);
736 /* Reinitialise the land information window to remove the "debug" sprite if needed.
737 * Note: Since we might be called from a command here, it is important to not execute
738 * the invalidation immediately. The landinfo window tests commands itself. */
739 InvalidateWindowData(WC_LAND_INFO, 0, 1);
743 * Can we inspect the data given a certain feature and index.
744 * The index is normally an in-game location/identifier, such
745 * as a TileIndex or an IndustryID depending on the feature
746 * we want to inspect.
747 * @param feature The feature we want to inspect.
748 * @param index The index/identifier of the feature to inspect.
749 * @return true if there is something to show.
751 bool IsNewGRFInspectable(GrfSpecFeature feature, uint index)
753 const NIFeature *nif = GetFeature(GetInspectWindowNumber(feature, index));
754 if (nif == nullptr) return false;
755 return nif->helper->IsInspectable(index);
759 * Get the GrfSpecFeature associated with the tile.
760 * @param tile The tile to get the feature from.
761 * @return the GrfSpecFeature.
763 GrfSpecFeature GetGrfSpecFeature(TileIndex tile)
765 switch (GetTileType(tile)) {
766 default: return GSF_INVALID;
767 case MP_RAILWAY: return GSF_RAILTYPES;
768 case MP_ROAD: return IsLevelCrossing(tile) ? GSF_RAILTYPES : GSF_ROADTYPES;
769 case MP_HOUSE: return GSF_HOUSES;
770 case MP_INDUSTRY: return GSF_INDUSTRYTILES;
771 case MP_OBJECT: return GSF_OBJECTS;
773 case MP_STATION:
774 switch (GetStationType(tile)) {
775 case STATION_RAIL: return GSF_STATIONS;
776 case STATION_AIRPORT: return GSF_AIRPORTTILES;
777 case STATION_BUS: return GSF_ROADSTOPS;
778 case STATION_TRUCK: return GSF_ROADSTOPS;
779 default: return GSF_INVALID;
785 * Get the GrfSpecFeature associated with the vehicle.
786 * @param type The vehicle type to get the feature from.
787 * @return the GrfSpecFeature.
789 GrfSpecFeature GetGrfSpecFeature(VehicleType type)
791 switch (type) {
792 case VEH_TRAIN: return GSF_TRAINS;
793 case VEH_ROAD: return GSF_ROADVEHICLES;
794 case VEH_SHIP: return GSF_SHIPS;
795 case VEH_AIRCRAFT: return GSF_AIRCRAFT;
796 default: return GSF_INVALID;
801 /**** Sprite Aligner ****/
803 /** Window used for aligning sprites. */
804 struct SpriteAlignerWindow : Window {
805 typedef std::pair<int16_t, int16_t> XyOffs; ///< Pair for x and y offsets of the sprite before alignment. First value contains the x offset, second value y offset.
807 SpriteID current_sprite; ///< The currently shown sprite.
808 Scrollbar *vscroll;
809 std::map<SpriteID, XyOffs> offs_start_map; ///< Mapping of starting offsets for the sprites which have been aligned in the sprite aligner window.
811 static inline ZoomLevel zoom = ZOOM_LVL_END;
812 static bool centre;
813 static bool crosshair;
814 const Action5Type *act5_type = nullptr; ///< Sprite Area of current selected sprite.
816 SpriteAlignerWindow(WindowDesc &desc, WindowNumber wno) : Window(desc)
818 /* On first opening, set initial zoom to current zoom level. */
819 if (SpriteAlignerWindow::zoom == ZOOM_LVL_END) SpriteAlignerWindow::zoom = _gui_zoom;
820 SpriteAlignerWindow::zoom = Clamp(SpriteAlignerWindow::zoom, _settings_client.gui.zoom_min, _settings_client.gui.zoom_max);
822 /* Oh yes, we assume there is at least one normal sprite! */
823 while (GetSpriteType(this->current_sprite) != SpriteType::Normal) this->current_sprite++;
824 this->SelectAction5Type();
826 this->CreateNestedTree();
827 this->vscroll = this->GetScrollbar(WID_SA_SCROLLBAR);
828 this->vscroll->SetCount(_newgrf_debug_sprite_picker.sprites.size());
829 this->FinishInitNested(wno);
831 this->SetWidgetLoweredState(WID_SA_CENTRE, SpriteAlignerWindow::centre);
832 this->SetWidgetLoweredState(WID_SA_CROSSHAIR, SpriteAlignerWindow::crosshair);
834 this->InvalidateData(0, true);
837 void SetStringParameters(WidgetID widget) const override
839 const Sprite *spr = GetSprite(this->current_sprite, SpriteType::Normal);
840 switch (widget) {
841 case WID_SA_CAPTION:
842 if (this->act5_type != nullptr) {
843 SetDParam(0, STR_SPRITE_ALIGNER_CAPTION_ACTION5);
844 SetDParam(1, this->act5_type - GetAction5Types().data());
845 SetDParam(2, this->current_sprite - this->act5_type->sprite_base);
846 SetDParamStr(3, GetOriginFile(this->current_sprite)->GetSimplifiedFilename());
847 SetDParam(4, GetSpriteLocalID(this->current_sprite));
848 } else if (this->current_sprite < SPR_OPENTTD_BASE) {
849 SetDParam(0, STR_SPRITE_ALIGNER_CAPTION_ACTIONA);
850 SetDParam(1, this->current_sprite);
851 SetDParamStr(2, GetOriginFile(this->current_sprite)->GetSimplifiedFilename());
852 SetDParam(3, GetSpriteLocalID(this->current_sprite));
853 } else {
854 SetDParam(0, STR_SPRITE_ALIGNER_CAPTION_NO_ACTION);
855 SetDParamStr(1, GetOriginFile(this->current_sprite)->GetSimplifiedFilename());
856 SetDParam(2, GetSpriteLocalID(this->current_sprite));
858 break;
860 case WID_SA_OFFSETS_ABS:
861 SetDParam(0, UnScaleByZoom(spr->x_offs, SpriteAlignerWindow::zoom));
862 SetDParam(1, UnScaleByZoom(spr->y_offs, SpriteAlignerWindow::zoom));
863 break;
865 case WID_SA_OFFSETS_REL: {
866 /* Relative offset is new absolute offset - starting absolute offset.
867 * Show 0, 0 as the relative offsets if entry is not in the map (meaning they have not been changed yet).
869 const auto key_offs_pair = this->offs_start_map.find(this->current_sprite);
870 if (key_offs_pair != this->offs_start_map.end()) {
871 SetDParam(0, UnScaleByZoom(spr->x_offs - key_offs_pair->second.first, SpriteAlignerWindow::zoom));
872 SetDParam(1, UnScaleByZoom(spr->y_offs - key_offs_pair->second.second, SpriteAlignerWindow::zoom));
873 } else {
874 SetDParam(0, 0);
875 SetDParam(1, 0);
877 break;
880 default:
881 break;
885 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
887 switch (widget) {
888 case WID_SA_SPRITE:
889 size.height = ScaleGUITrad(200);
890 break;
892 case WID_SA_LIST: {
893 Dimension d = {};
894 for (const auto &spritefile : GetCachedSpriteFiles()) {
895 SetDParamStr(0, spritefile->GetSimplifiedFilename());
896 SetDParamMaxDigits(1, 6);
897 d = maxdim(d, GetStringBoundingBox(STR_SPRITE_ALIGNER_SPRITE));
899 size.width = d.width + padding.width;
900 resize.height = GetCharacterHeight(FS_NORMAL) + padding.height;
901 resize.width = 1;
902 fill.height = resize.height;
903 break;
906 default:
907 break;
911 void DrawWidget(const Rect &r, WidgetID widget) const override
913 switch (widget) {
914 case WID_SA_SPRITE: {
915 /* Center the sprite ourselves */
916 const Sprite *spr = GetSprite(this->current_sprite, SpriteType::Normal);
917 Rect ir = r.Shrink(WidgetDimensions::scaled.bevel);
918 int x;
919 int y;
920 if (SpriteAlignerWindow::centre) {
921 x = -UnScaleByZoom(spr->x_offs, SpriteAlignerWindow::zoom) + (ir.Width() - UnScaleByZoom(spr->width, SpriteAlignerWindow::zoom)) / 2;
922 y = -UnScaleByZoom(spr->y_offs, SpriteAlignerWindow::zoom) + (ir.Height() - UnScaleByZoom(spr->height, SpriteAlignerWindow::zoom)) / 2;
923 } else {
924 x = ir.Width() / 2;
925 y = ir.Height() / 2;
928 DrawPixelInfo new_dpi;
929 if (!FillDrawPixelInfo(&new_dpi, ir)) break;
930 AutoRestoreBackup dpi_backup(_cur_dpi, &new_dpi);
932 DrawSprite(this->current_sprite, PAL_NONE, x, y, nullptr, SpriteAlignerWindow::zoom);
934 Rect outline = {0, 0, UnScaleByZoom(spr->width, SpriteAlignerWindow::zoom) - 1, UnScaleByZoom(spr->height, SpriteAlignerWindow::zoom) - 1};
935 outline = outline.Translate(x + UnScaleByZoom(spr->x_offs, SpriteAlignerWindow::zoom), y + UnScaleByZoom(spr->y_offs, SpriteAlignerWindow::zoom));
936 DrawRectOutline(outline.Expand(1), PC_LIGHT_BLUE, 1, 1);
938 if (SpriteAlignerWindow::crosshair) {
939 GfxDrawLine(x, 0, x, ir.Height() - 1, PC_WHITE, 1, 1);
940 GfxDrawLine(0, y, ir.Width() - 1, y, PC_WHITE, 1, 1);
942 break;
945 case WID_SA_LIST: {
946 /* Don't redraw sprite list while it is still being filled by picker. */
947 if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW) break;
949 const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
950 int step_size = nwid->resize_y;
952 const std::vector<SpriteID> &list = _newgrf_debug_sprite_picker.sprites;
954 Rect ir = r.Shrink(WidgetDimensions::scaled.matrix);
955 auto [first, last] = this->vscroll->GetVisibleRangeIterators(list);
956 for (auto it = first; it != last; ++it) {
957 const SpriteFile *file = GetOriginFile(*it);
958 if (file == nullptr) {
959 SetDParam(0, *it);
960 DrawString(ir, STR_JUST_COMMA, *it == this->current_sprite ? TC_WHITE : (TC_GREY | TC_NO_SHADE), SA_RIGHT | SA_FORCE);
961 } else {
962 SetDParamStr(0, file->GetSimplifiedFilename());
963 SetDParam(1, GetSpriteLocalID(*it));
964 DrawString(ir, STR_SPRITE_ALIGNER_SPRITE, *it == this->current_sprite ? TC_WHITE : TC_BLACK);
966 ir.top += step_size;
968 break;
973 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
975 switch (widget) {
976 case WID_SA_PREVIOUS:
977 do {
978 this->current_sprite = (this->current_sprite == 0 ? GetMaxSpriteID() : this->current_sprite) - 1;
979 } while (GetSpriteType(this->current_sprite) != SpriteType::Normal);
980 this->SelectAction5Type();
981 this->SetDirty();
982 break;
984 case WID_SA_GOTO:
985 ShowQueryString(STR_EMPTY, STR_SPRITE_ALIGNER_GOTO_CAPTION, 7, this, CS_NUMERAL, QSF_NONE);
986 break;
988 case WID_SA_NEXT:
989 do {
990 this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
991 } while (GetSpriteType(this->current_sprite) != SpriteType::Normal);
992 this->SelectAction5Type();
993 this->SetDirty();
994 break;
996 case WID_SA_PICKER:
997 this->LowerWidget(WID_SA_PICKER);
998 _newgrf_debug_sprite_picker.mode = SPM_WAIT_CLICK;
999 this->SetDirty();
1000 break;
1002 case WID_SA_LIST: {
1003 auto it = this->vscroll->GetScrolledItemFromWidget(_newgrf_debug_sprite_picker.sprites, pt.y, this, widget);
1004 if (it != _newgrf_debug_sprite_picker.sprites.end()) {
1005 SpriteID spr = *it;
1006 if (GetSpriteType(spr) == SpriteType::Normal) this->current_sprite = spr;
1008 this->SelectAction5Type();
1009 this->SetDirty();
1010 break;
1013 case WID_SA_UP:
1014 case WID_SA_DOWN:
1015 case WID_SA_LEFT:
1016 case WID_SA_RIGHT: {
1018 * Yes... this is a hack.
1020 * No... I don't think it is useful to make this less of a hack.
1022 * If you want to align sprites, you just need the number. Generally
1023 * the sprite caches are big enough to not remove the sprite from the
1024 * cache. If that's not the case, just let the NewGRF developer
1025 * increase the cache size instead of storing thousands of offsets
1026 * for the incredibly small chance that it's actually going to be
1027 * used by someone and the sprite cache isn't big enough for that
1028 * particular NewGRF developer.
1030 Sprite *spr = const_cast<Sprite *>(GetSprite(this->current_sprite, SpriteType::Normal));
1032 /* Remember the original offsets of the current sprite, if not already in mapping. */
1033 if (this->offs_start_map.count(this->current_sprite) == 0) {
1034 this->offs_start_map[this->current_sprite] = XyOffs(spr->x_offs, spr->y_offs);
1036 int amt = ScaleByZoom(_ctrl_pressed ? 8 : 1, SpriteAlignerWindow::zoom);
1037 switch (widget) {
1038 /* Move eight units at a time if ctrl is pressed. */
1039 case WID_SA_UP: spr->y_offs -= amt; break;
1040 case WID_SA_DOWN: spr->y_offs += amt; break;
1041 case WID_SA_LEFT: spr->x_offs -= amt; break;
1042 case WID_SA_RIGHT: spr->x_offs += amt; break;
1044 /* Of course, we need to redraw the sprite, but where is it used?
1045 * Everywhere is a safe bet. */
1046 MarkWholeScreenDirty();
1047 break;
1050 case WID_SA_RESET_REL:
1051 /* Reset the starting offsets for the current sprite. */
1052 this->offs_start_map.erase(this->current_sprite);
1053 this->SetDirty();
1054 break;
1056 case WID_SA_CENTRE:
1057 SpriteAlignerWindow::centre = !SpriteAlignerWindow::centre;
1058 this->SetWidgetLoweredState(widget, SpriteAlignerWindow::centre);
1059 this->SetDirty();
1060 break;
1062 case WID_SA_CROSSHAIR:
1063 SpriteAlignerWindow::crosshair = !SpriteAlignerWindow::crosshair;
1064 this->SetWidgetLoweredState(widget, SpriteAlignerWindow::crosshair);
1065 this->SetDirty();
1066 break;
1068 default:
1069 if (IsInsideBS(widget, WID_SA_ZOOM, ZOOM_LVL_END)) {
1070 SpriteAlignerWindow::zoom = ZoomLevel(widget - WID_SA_ZOOM);
1071 this->InvalidateData(0, true);
1073 break;
1077 void OnQueryTextFinished(std::optional<std::string> str) override
1079 if (!str.has_value() || str->empty()) return;
1081 this->current_sprite = atoi(str->c_str());
1082 if (this->current_sprite >= GetMaxSpriteID()) this->current_sprite = 0;
1083 while (GetSpriteType(this->current_sprite) != SpriteType::Normal) {
1084 this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
1086 this->SelectAction5Type();
1087 this->SetDirty();
1091 * Some data on this window has become invalid.
1092 * @param data Information about the changed data.
1093 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
1095 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1097 if (!gui_scope) return;
1098 if (data == 1) {
1099 /* Sprite picker finished */
1100 this->RaiseWidget(WID_SA_PICKER);
1101 this->vscroll->SetCount(_newgrf_debug_sprite_picker.sprites.size());
1104 SpriteAlignerWindow::zoom = Clamp(SpriteAlignerWindow::zoom, _settings_client.gui.zoom_min, _settings_client.gui.zoom_max);
1105 for (ZoomLevel z = ZOOM_LVL_BEGIN; z < ZOOM_LVL_END; z++) {
1106 this->SetWidgetsDisabledState(z < _settings_client.gui.zoom_min || z > _settings_client.gui.zoom_max, WID_SA_ZOOM + z);
1107 this->SetWidgetsLoweredState(SpriteAlignerWindow::zoom == z, WID_SA_ZOOM + z);
1111 void OnResize() override
1113 this->vscroll->SetCapacityFromWidget(this, WID_SA_LIST);
1116 private:
1117 void SelectAction5Type()
1119 const auto act5types = GetAction5Types();
1120 for (auto it = std::begin(act5types); it != std::end(act5types); ++it) {
1121 if (it->sprite_base <= this->current_sprite && this->current_sprite < it->sprite_base + it->max_sprites) {
1122 this->act5_type = &*it;
1123 return;
1126 this->act5_type = nullptr;
1130 bool SpriteAlignerWindow::centre = true;
1131 bool SpriteAlignerWindow::crosshair = true;
1133 static constexpr NWidgetPart _nested_sprite_aligner_widgets[] = {
1134 NWidget(NWID_HORIZONTAL),
1135 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1136 NWidget(WWT_CAPTION, COLOUR_GREY, WID_SA_CAPTION), SetDataTip(STR_JUST_STRING4, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1137 NWidget(WWT_SHADEBOX, COLOUR_GREY),
1138 NWidget(WWT_STICKYBOX, COLOUR_GREY),
1139 EndContainer(),
1140 NWidget(WWT_PANEL, COLOUR_GREY),
1141 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0), SetPadding(WidgetDimensions::unscaled.sparse_resize),
1142 NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_sparse, 0),
1143 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
1144 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_PREVIOUS), SetDataTip(STR_SPRITE_ALIGNER_PREVIOUS_BUTTON, STR_SPRITE_ALIGNER_PREVIOUS_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1145 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_GOTO), SetDataTip(STR_SPRITE_ALIGNER_GOTO_BUTTON, STR_SPRITE_ALIGNER_GOTO_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1146 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_NEXT), SetDataTip(STR_SPRITE_ALIGNER_NEXT_BUTTON, STR_SPRITE_ALIGNER_NEXT_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1147 EndContainer(),
1148 NWidget(NWID_HORIZONTAL),
1149 NWidget(NWID_SPACER), SetFill(1, 1), SetResize(1, 0),
1150 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_UP), SetDataTip(SPR_ARROW_UP, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0), SetMinimalSize(11, 11),
1151 NWidget(NWID_SPACER), SetFill(1, 1), SetResize(1, 0),
1152 EndContainer(),
1153 NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
1154 NWidget(NWID_VERTICAL),
1155 NWidget(NWID_SPACER), SetFill(1, 1), SetResize(0, 1),
1156 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_LEFT), SetDataTip(SPR_ARROW_LEFT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0), SetMinimalSize(11, 11),
1157 NWidget(NWID_SPACER), SetFill(1, 1), SetResize(0, 1),
1158 EndContainer(),
1159 NWidget(WWT_PANEL, COLOUR_DARK_BLUE, WID_SA_SPRITE), SetDataTip(STR_NULL, STR_SPRITE_ALIGNER_SPRITE_TOOLTIP), SetResize(1, 1), SetFill(1, 1),
1160 EndContainer(),
1161 NWidget(NWID_VERTICAL),
1162 NWidget(NWID_SPACER), SetFill(1, 1), SetResize(0, 1),
1163 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_RIGHT), SetDataTip(SPR_ARROW_RIGHT, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0), SetMinimalSize(11, 11),
1164 NWidget(NWID_SPACER), SetFill(1, 1), SetResize(0, 1),
1165 EndContainer(),
1166 EndContainer(),
1167 NWidget(NWID_HORIZONTAL),
1168 NWidget(NWID_SPACER), SetFill(1, 1), SetResize(1, 0),
1169 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_SA_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_SPRITE_ALIGNER_MOVE_TOOLTIP), SetResize(0, 0), SetMinimalSize(11, 11),
1170 NWidget(NWID_SPACER), SetFill(1, 1), SetResize(1, 0),
1171 EndContainer(),
1172 NWidget(WWT_LABEL, COLOUR_GREY, WID_SA_OFFSETS_ABS), SetDataTip(STR_SPRITE_ALIGNER_OFFSETS_ABS, STR_NULL), SetFill(1, 0), SetResize(1, 0),
1173 NWidget(WWT_LABEL, COLOUR_GREY, WID_SA_OFFSETS_REL), SetDataTip(STR_SPRITE_ALIGNER_OFFSETS_REL, STR_NULL), SetFill(1, 0), SetResize(1, 0),
1174 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
1175 NWidget(WWT_TEXTBTN_2, COLOUR_GREY, WID_SA_CENTRE), SetDataTip(STR_SPRITE_ALIGNER_CENTRE_OFFSET, STR_NULL), SetFill(1, 0), SetResize(1, 0),
1176 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SA_RESET_REL), SetDataTip(STR_SPRITE_ALIGNER_RESET_BUTTON, STR_SPRITE_ALIGNER_RESET_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
1177 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SA_CROSSHAIR), SetDataTip(STR_SPRITE_ALIGNER_CROSSHAIR, STR_NULL), SetFill(1, 0), SetResize(1, 0),
1178 EndContainer(),
1179 EndContainer(),
1180 NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_sparse, 0),
1181 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SA_PICKER), SetDataTip(STR_SPRITE_ALIGNER_PICKER_BUTTON, STR_SPRITE_ALIGNER_PICKER_TOOLTIP), SetFill(1, 0),
1182 NWidget(NWID_HORIZONTAL),
1183 NWidget(WWT_MATRIX, COLOUR_GREY, WID_SA_LIST), SetResize(1, 1), SetMatrixDataTip(1, 0, STR_NULL), SetFill(1, 1), SetScrollbar(WID_SA_SCROLLBAR),
1184 NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SA_SCROLLBAR),
1185 EndContainer(),
1186 NWidget(NWID_VERTICAL),
1187 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SA_ZOOM + ZOOM_LVL_IN_4X), SetDataTip(STR_CONFIG_SETTING_ZOOM_LVL_MIN, STR_NULL), SetFill(1, 0),
1188 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SA_ZOOM + ZOOM_LVL_IN_2X), SetDataTip(STR_CONFIG_SETTING_ZOOM_LVL_IN_2X, STR_NULL), SetFill(1, 0),
1189 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SA_ZOOM + ZOOM_LVL_NORMAL), SetDataTip(STR_CONFIG_SETTING_ZOOM_LVL_NORMAL, STR_NULL), SetFill(1, 0),
1190 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SA_ZOOM + ZOOM_LVL_OUT_2X), SetDataTip(STR_CONFIG_SETTING_ZOOM_LVL_OUT_2X, STR_NULL), SetFill(1, 0),
1191 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SA_ZOOM + ZOOM_LVL_OUT_4X), SetDataTip(STR_CONFIG_SETTING_ZOOM_LVL_OUT_4X, STR_NULL), SetFill(1, 0),
1192 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SA_ZOOM + ZOOM_LVL_OUT_8X), SetDataTip(STR_CONFIG_SETTING_ZOOM_LVL_OUT_8X, STR_NULL), SetFill(1, 0),
1193 EndContainer(),
1194 EndContainer(),
1195 EndContainer(),
1196 NWidget(NWID_HORIZONTAL),
1197 NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
1198 NWidget(WWT_RESIZEBOX, COLOUR_GREY), SetDataTip(RWV_HIDE_BEVEL, STR_TOOLTIP_RESIZE),
1199 EndContainer(),
1200 EndContainer(),
1203 static WindowDesc _sprite_aligner_desc(
1204 WDP_AUTO, "sprite_aligner", 400, 300,
1205 WC_SPRITE_ALIGNER, WC_NONE,
1207 _nested_sprite_aligner_widgets
1211 * Show the window for aligning sprites.
1213 void ShowSpriteAlignerWindow()
1215 AllocateWindowDescFront<SpriteAlignerWindow>(_sprite_aligner_desc, 0);