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/>.
8 /** @file newgrf_debug_gui.cpp GUIs for debugging NewGRFs. */
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"
25 #include "object_base.h"
26 #include "station_base.h"
28 #include "vehicle_base.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
>() };
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);
64 * Get the window number for the inspect window given a
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
;
77 * The type of a property to show. This is used to
78 * provide an appropriate representation in the GUI.
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. */
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
98 * Representation of the available callbacks with
99 * information on when they actually apply.
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. */
117 /** Helper class to wrap some functionality/queries in. */
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.
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
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
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
);
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
);
223 /** Container for all information for a given feature. */
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. */
277 /** The currently edited parameter, to update the right one. */
278 uint8_t current_edit_param
;
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
;
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
;
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
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());
370 case WID_NGRFI_MAINPANEL
:
371 resize
.height
= std::max(11, GetCharacterHeight(FS_NORMAL
) + WidgetDimensions::scaled
.vsep_normal
);
374 size
.height
= 5 * resize
.height
+ WidgetDimensions::scaled
.frametext
.Vertical();
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());
403 for (const Vehicle
*u
= v
->First(); u
!= nullptr; u
= u
->Next()) {
404 if (u
== v
) sel_start
= total_width
;
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();
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
);
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
);
447 if (nif
->variables
!= nullptr) {
448 this->DrawString(r
, i
++, "Variables:");
449 for (const NIVariable
*niv
= nif
->variables
; niv
->name
!= nullptr; niv
++) {
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
));
459 this->DrawString(r
, i
++, fmt::format(" {:02x}: {:08x} ({})", niv
->var
, value
, niv
->name
));
464 auto psa
= nih
->GetPSA(index
, this->caller_grfid
);
466 if (nih
->PSAWithParameter()) {
467 this->DrawString(r
, i
++, fmt::format("Persistent storage [{:08X}]:", BSWAP32(this->caller_grfid
)));
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
);
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();
493 string
= STR_JUST_INT
;
497 string
= IsValidCargoID(value
) ? CargoSpec::Get(value
)->name
: STR_QUANTITY_N_A
;
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
);
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
));
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
538 case WID_NGRFI_VEH_CHAIN
:
539 this->DrawVehicleChainWidget(r
);
542 case WID_NGRFI_MAINPANEL
:
543 this->DrawMainPanelWidget(r
);
548 void OnClick([[maybe_unused
]] Point pt
, WidgetID widget
, [[maybe_unused
]] int click_count
) override
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()));
558 case WID_NGRFI_VEH_PREV
:
559 if (this->chain_index
> 0) {
561 this->InvalidateData();
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) {
571 this->InvalidateData();
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);
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
),
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),
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
),
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
),
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
),
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
;
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
)
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.
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
;
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
);
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
));
854 SetDParam(0, STR_SPRITE_ALIGNER_CAPTION_NO_ACTION
);
855 SetDParamStr(1, GetOriginFile(this->current_sprite
)->GetSimplifiedFilename());
856 SetDParam(2, GetSpriteLocalID(this->current_sprite
));
860 case WID_SA_OFFSETS_ABS
:
861 SetDParam(0, UnScaleByZoom(spr
->x_offs
, SpriteAlignerWindow::zoom
));
862 SetDParam(1, UnScaleByZoom(spr
->y_offs
, SpriteAlignerWindow::zoom
));
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
));
885 void UpdateWidgetSize(WidgetID widget
, Dimension
&size
, [[maybe_unused
]] const Dimension
&padding
, [[maybe_unused
]] Dimension
&fill
, [[maybe_unused
]] Dimension
&resize
) override
889 size
.height
= ScaleGUITrad(200);
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
;
902 fill
.height
= resize
.height
;
911 void DrawWidget(const Rect
&r
, WidgetID widget
) const override
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
);
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;
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);
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) {
960 DrawString(ir
, STR_JUST_COMMA
, *it
== this->current_sprite
? TC_WHITE
: (TC_GREY
| TC_NO_SHADE
), SA_RIGHT
| SA_FORCE
);
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
);
973 void OnClick([[maybe_unused
]] Point pt
, WidgetID widget
, [[maybe_unused
]] int click_count
) override
976 case WID_SA_PREVIOUS
:
978 this->current_sprite
= (this->current_sprite
== 0 ? GetMaxSpriteID() : this->current_sprite
) - 1;
979 } while (GetSpriteType(this->current_sprite
) != SpriteType::Normal
);
980 this->SelectAction5Type();
985 ShowQueryString(STR_EMPTY
, STR_SPRITE_ALIGNER_GOTO_CAPTION
, 7, this, CS_NUMERAL
, QSF_NONE
);
990 this->current_sprite
= (this->current_sprite
+ 1) % GetMaxSpriteID();
991 } while (GetSpriteType(this->current_sprite
) != SpriteType::Normal
);
992 this->SelectAction5Type();
997 this->LowerWidget(WID_SA_PICKER
);
998 _newgrf_debug_sprite_picker
.mode
= SPM_WAIT_CLICK
;
1003 auto it
= this->vscroll
->GetScrolledItemFromWidget(_newgrf_debug_sprite_picker
.sprites
, pt
.y
, this, widget
);
1004 if (it
!= _newgrf_debug_sprite_picker
.sprites
.end()) {
1006 if (GetSpriteType(spr
) == SpriteType::Normal
) this->current_sprite
= spr
;
1008 this->SelectAction5Type();
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
);
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();
1050 case WID_SA_RESET_REL
:
1051 /* Reset the starting offsets for the current sprite. */
1052 this->offs_start_map
.erase(this->current_sprite
);
1057 SpriteAlignerWindow::centre
= !SpriteAlignerWindow::centre
;
1058 this->SetWidgetLoweredState(widget
, SpriteAlignerWindow::centre
);
1062 case WID_SA_CROSSHAIR
:
1063 SpriteAlignerWindow::crosshair
= !SpriteAlignerWindow::crosshair
;
1064 this->SetWidgetLoweredState(widget
, SpriteAlignerWindow::crosshair
);
1069 if (IsInsideBS(widget
, WID_SA_ZOOM
, ZOOM_LVL_END
)) {
1070 SpriteAlignerWindow::zoom
= ZoomLevel(widget
- WID_SA_ZOOM
);
1071 this->InvalidateData(0, true);
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();
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;
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
);
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
;
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
),
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),
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),
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),
1159 NWidget(WWT_PANEL
, COLOUR_DARK_BLUE
, WID_SA_SPRITE
), SetDataTip(STR_NULL
, STR_SPRITE_ALIGNER_SPRITE_TOOLTIP
), SetResize(1, 1), SetFill(1, 1),
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),
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),
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),
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
),
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),
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
),
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);