4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
10 /** @file newgrf_debug_gui.cpp GUIs for debugging NewGRFs. */
14 #include "window_gui.h"
15 #include "window_func.h"
16 #include "fileio_func.h"
17 #include "spritecache.h"
18 #include "string_func.h"
19 #include "strings_func.h"
20 #include "textbuf_gui.h"
22 #include "engine_base.h"
24 #include "object_base.h"
25 #include "station_base.h"
27 #include "vehicle_base.h"
28 #include "map/depot.h"
30 #include "newgrf_airporttiles.h"
31 #include "newgrf_debug.h"
32 #include "newgrf_object.h"
33 #include "newgrf_spritegroup.h"
34 #include "newgrf_station.h"
35 #include "newgrf_town.h"
36 #include "newgrf_railtype.h"
37 #include "newgrf_industries.h"
38 #include "newgrf_industrytiles.h"
40 #include "widgets/newgrf_debug_widget.h"
42 #include "table/strings.h"
44 /** The sprite picker. */
45 NewGrfDebugSpritePicker _newgrf_debug_sprite_picker
= { SPM_NONE
, NULL
, 0, SmallVector
<SpriteID
, 256>() };
48 * Get the feature index related to the window number.
49 * @param window_number The window to get the feature index from.
50 * @return the feature index
52 static inline uint
GetFeatureIndex(uint window_number
)
54 return GB(window_number
, 0, 24);
58 * Get the window number for the inspect window given a
60 * @param feature The feature we want to inspect.
61 * @param index The index/identifier of the feature to inspect.
62 * @return the InspectWindow (Window)Number
64 static inline uint
GetInspectWindowNumber(GrfSpecFeature feature
, uint index
)
66 assert((index
>> 24) == 0);
67 return (feature
<< 24) | index
;
71 * The type of a property to show. This is used to
72 * provide an appropriate representation in the GUI.
75 NIT_INT
, ///< The property is a simple integer
76 NIT_CARGO
, ///< The property is a cargo
79 /** Representation of the data from a NewGRF property. */
81 const char *name
; ///< A (human readable) name for the property
82 ptrdiff_t offset
; ///< Offset of the variable in the class
83 byte read_size
; ///< Number of bytes (i.e. byte, word, dword etc)
84 byte prop
; ///< The number of the property
90 * Representation of the available callbacks with
91 * information on when they actually apply.
94 const char *name
; ///< The human readable name of the callback
95 ptrdiff_t offset
; ///< Offset of the variable in the class
96 byte read_size
; ///< The number of bytes (i.e. byte, word, dword etc) to read
97 byte cb_bit
; ///< The bit that needs to be set for this callback to be enabled
98 uint16 cb_id
; ///< The number of the callback
100 /** Mask to show no bit needs to be enabled for the callback. */
101 static const int CBM_NO_BIT
= UINT8_MAX
;
103 /** Representation on the NewGRF variables. */
109 /** Helper class to wrap some functionality/queries in. */
112 /** Silence a warning. */
113 virtual ~NIHelper() {}
116 * Is the item with the given index inspectable?
117 * @param index the index to check.
118 * @return true iff the index is inspectable.
120 virtual bool IsInspectable(uint index
) const = 0;
123 * Get the parent "window_number" of a given instance.
124 * @param index the instance to get the parent for.
125 * @return the parent's window_number or UINT32_MAX if there is none.
127 virtual uint
GetParent(uint index
) const = 0;
130 * Get the instance given an index.
131 * @param index the index to get the instance for.
132 * @return the instance.
134 virtual const void *GetInstance(uint index
) const = 0;
137 * Get (NewGRF) specs given an index.
138 * @param index the index to get the specs for for.
141 virtual const void *GetSpec(uint index
) const = 0;
144 * Set the string parameters to write the right data for a STRINGn.
145 * @param index the index to get the string parameters for.
147 virtual void SetStringParameters(uint index
) const = 0;
150 * Get the GRFID of the file that includes this item.
151 * @param index index to check.
152 * @return GRFID of the item. 0 means that the item is not inspectable.
154 virtual uint32
GetGRFID(uint index
) const = 0;
157 * Resolve (action2) variable for a given index.
158 * @param index The (instance) index to resolve the variable for.
159 * @param var The variable to actually resolve.
160 * @param param The varaction2 0x60+x parameter to pass.
161 * @param avail Return whether the variable is available.
162 * @return The resolved variable's value.
164 virtual uint
Resolve(uint index
, uint var
, uint param
, bool *avail
) const = 0;
167 * Used to decide if the PSA needs a parameter or not.
168 * @return True iff this item has a PSA that requires a parameter.
170 virtual bool PSAWithParameter() const
176 * Allows to know the size of the persistent storage.
177 * @param index Index of the item.
178 * @param grfid Parameter for the PSA. Only required for items with parameters.
179 * @return Size of the persistent storage in indices.
181 virtual uint
GetPSASize(uint index
, uint32 grfid
) const
187 * Gets the first position of the array containing the persistent storage.
188 * @param index Index of the item.
189 * @param grfid Parameter for the PSA. Only required for items with parameters.
190 * @return Pointer to the first position of the storage array or NULL if not present.
192 virtual const int32
*GetPSAFirstPosition(uint index
, uint32 grfid
) const
199 * Helper to make setting the strings easier.
200 * @param string the string to actually draw.
201 * @param index the (instance) index for the string.
203 void SetSimpleStringParameters(StringID string
, uint32 index
) const
205 SetDParam(0, string
);
211 * Helper to make setting the strings easier for objects at a specific tile.
212 * @param string the string to draw the object's name
213 * @param index the (instance) index for the string.
214 * @param tile the tile the object is at
216 void SetObjectAtStringParameters(StringID string
, uint32 index
, TileIndex tile
) const
218 SetDParam(0, STR_NEWGRF_INSPECT_CAPTION_OBJECT_AT
);
219 SetDParam(1, string
);
226 /** Container for all information for a given feature. */
228 const NIProperty
*properties
; ///< The properties associated with this feature.
229 const NICallback
*callbacks
; ///< The callbacks associated with this feature.
230 const NIVariable
*variables
; ///< The variables associated with this feature.
231 const NIHelper
*helper
; ///< The class container all helper functions.
234 /* Load all the NewGRF debug data; externalised as it is just a huge bunch of tables. */
235 #include "table/newgrf_debug_data.h"
238 * Get the feature number related to the window number.
239 * @param window_number The window to get the feature number for.
240 * @return The feature number.
242 static inline GrfSpecFeature
GetFeatureNum(uint window_number
)
244 return (GrfSpecFeature
)GB(window_number
, 24, 8);
248 * Get the NIFeature related to the window number.
249 * @param window_number The window to get the NIFeature for.
250 * @return the NIFeature, or NULL is there isn't one.
252 static inline const NIFeature
*GetFeature(uint window_number
)
254 GrfSpecFeature idx
= GetFeatureNum(window_number
);
255 return idx
< GSF_FAKE_END
? _nifeatures
[idx
] : NULL
;
259 * Get the NIHelper related to the window number.
260 * @param window_number The window to get the NIHelper for.
261 * @pre GetFeature(window_number) != NULL
262 * @return the NIHelper
264 static inline const NIHelper
*GetFeatureHelper(uint window_number
)
266 return GetFeature(window_number
)->helper
;
269 /** Window used for inspecting NewGRFs. */
270 struct NewGRFInspectWindow
: Window
{
271 static const int LEFT_OFFSET
= 5; ///< Position of left edge
272 static const int RIGHT_OFFSET
= 5; ///< Position of right edge
273 static const int TOP_OFFSET
= 5; ///< Position of top edge
274 static const int BOTTOM_OFFSET
= 5; ///< Position of bottom edge
276 /** The value for the variable 60 parameters. */
277 static uint32 var60params
[GSF_FAKE_END
][0x20];
279 /** GRFID of the caller of this window, 0 if it has no caller. */
282 /** The currently edited parameter, to update the right one. */
283 byte current_edit_param
;
288 * Check whether the given variable has a parameter.
289 * @param variable the variable to check.
290 * @return true iff the variable has a parameter.
292 static bool HasVariableParameter(uint variable
)
294 return IsInsideBS(variable
, 0x60, 0x20);
298 * Set the GRFID of the item opening this window.
299 * @param grfid GRFID of the item opening this window, or 0 if not opened by other window.
301 void SetCallerGRFID(uint32 grfid
)
303 this->caller_grfid
= grfid
;
308 * Get the feature index.
309 * @return the feature index
311 uint
GetFeatureIndex() const
313 uint index
= ::GetFeatureIndex(this->window_number
);
317 NewGRFInspectWindow(WindowDesc
*desc
, WindowNumber wno
) : Window(desc
)
319 this->CreateNestedTree();
320 this->vscroll
= this->GetScrollbar(WID_NGRFI_SCROLLBAR
);
321 this->FinishInitNested(wno
);
323 this->vscroll
->SetCount(0);
324 this->SetWidgetDisabledState(WID_NGRFI_PARENT
, GetFeatureHelper(this->window_number
)->GetParent(this->GetFeatureIndex()) == UINT32_MAX
);
327 virtual void SetStringParameters(int widget
) const
329 if (widget
!= WID_NGRFI_CAPTION
) return;
331 GetFeatureHelper(this->window_number
)->SetStringParameters(this->GetFeatureIndex());
334 virtual void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
)
336 if (widget
!= WID_NGRFI_MAINPANEL
) return;
338 resize
->height
= max(11, FONT_HEIGHT_NORMAL
+ 1);
341 size
->height
= 5 * resize
->height
+ TOP_OFFSET
+ BOTTOM_OFFSET
;
345 * Helper function to draw a string (line) in the window.
346 * @param r The (screen) rectangle we must draw within
347 * @param offset The offset (in lines) we want to draw for
348 * @param format The format string
350 void WARN_FORMAT(4, 5) DrawString(const Rect
&r
, int offset
, const char *format
, ...) const
355 va_start(va
, format
);
356 vsnprintf(buf
, lengthof(buf
), format
, va
);
359 offset
-= this->vscroll
->GetPosition();
360 if (offset
< 0 || offset
>= this->vscroll
->GetCapacity()) return;
362 ::DrawString(r
.left
+ LEFT_OFFSET
, r
.right
- RIGHT_OFFSET
, r
.top
+ TOP_OFFSET
+ (offset
* this->resize
.step_height
), buf
, TC_BLACK
);
365 virtual void DrawWidget(const Rect
&r
, int widget
) const
367 if (widget
!= WID_NGRFI_MAINPANEL
) return;
369 uint index
= this->GetFeatureIndex();
370 const NIFeature
*nif
= GetFeature(this->window_number
);
371 const NIHelper
*nih
= nif
->helper
;
372 const void *base
= nih
->GetInstance(index
);
373 const void *base_spec
= nih
->GetSpec(index
);
376 if (nif
->variables
!= NULL
) {
377 this->DrawString(r
, i
++, "Variables:");
378 for (const NIVariable
*niv
= nif
->variables
; niv
->name
!= NULL
; niv
++) {
380 uint param
= HasVariableParameter(niv
->var
) ? NewGRFInspectWindow::var60params
[GetFeatureNum(this->window_number
)][niv
->var
- 0x60] : 0;
381 uint value
= nih
->Resolve(index
, niv
->var
, param
, &avail
);
383 if (!avail
) continue;
385 if (HasVariableParameter(niv
->var
)) {
386 this->DrawString(r
, i
++, " %02x[%02x]: %08x (%s)", niv
->var
, param
, value
, niv
->name
);
388 this->DrawString(r
, i
++, " %02x: %08x (%s)", niv
->var
, value
, niv
->name
);
393 uint psa_size
= nih
->GetPSASize(index
, this->caller_grfid
);
394 const int32
*psa
= nih
->GetPSAFirstPosition(index
, this->caller_grfid
);
395 if (psa_size
!= 0 && psa
!= NULL
) {
396 if (nih
->PSAWithParameter()) {
397 this->DrawString(r
, i
++, "Persistent storage [%08X]:", BSWAP32(this->caller_grfid
));
399 this->DrawString(r
, i
++, "Persistent storage:");
401 assert(psa_size
% 4 == 0);
402 for (uint j
= 0; j
< psa_size
; j
+= 4, psa
+= 4) {
403 this->DrawString(r
, i
++, " %i: %i %i %i %i", j
, psa
[0], psa
[1], psa
[2], psa
[3]);
407 if (nif
->properties
!= NULL
) {
408 this->DrawString(r
, i
++, "Properties:");
409 for (const NIProperty
*nip
= nif
->properties
; nip
->name
!= NULL
; nip
++) {
410 const void *ptr
= (const byte
*)base
+ nip
->offset
;
412 switch (nip
->read_size
) {
413 case 1: value
= *(const uint8
*)ptr
; break;
414 case 2: value
= *(const uint16
*)ptr
; break;
415 case 4: value
= *(const uint32
*)ptr
; break;
416 default: NOT_REACHED();
423 string
= STR_JUST_INT
;
427 string
= value
!= INVALID_CARGO
? CargoSpec::Get(value
)->name
: STR_QUANTITY_N_A
;
435 GetString(buffer
, string
, lastof(buffer
));
436 this->DrawString(r
, i
++, " %02x: %s (%s)", nip
->prop
, buffer
, nip
->name
);
440 if (nif
->callbacks
!= NULL
) {
441 this->DrawString(r
, i
++, "Callbacks:");
442 for (const NICallback
*nic
= nif
->callbacks
; nic
->name
!= NULL
; nic
++) {
443 if (nic
->cb_bit
!= CBM_NO_BIT
) {
444 const void *ptr
= (const byte
*)base_spec
+ nic
->offset
;
446 switch (nic
->read_size
) {
447 case 1: value
= *(const uint8
*)ptr
; break;
448 case 2: value
= *(const uint16
*)ptr
; break;
449 case 4: value
= *(const uint32
*)ptr
; break;
450 default: NOT_REACHED();
453 if (!HasBit(value
, nic
->cb_bit
)) continue;
454 this->DrawString(r
, i
++, " %03x: %s", nic
->cb_id
, nic
->name
);
456 this->DrawString(r
, i
++, " %03x: %s (unmasked)", nic
->cb_id
, nic
->name
);
461 /* Not nice and certainly a hack, but it beats duplicating
462 * this whole function just to count the actual number of
463 * elements. Especially because they need to be redrawn. */
464 const_cast<NewGRFInspectWindow
*>(this)->vscroll
->SetCount(i
);
467 virtual void OnClick(Point pt
, int widget
, int click_count
)
470 case WID_NGRFI_PARENT
: {
471 const NIHelper
*nih
= GetFeatureHelper(this->window_number
);
472 uint index
= nih
->GetParent(this->GetFeatureIndex());
473 ::ShowNewGRFInspectWindow((GrfSpecFeature
)GB(index
, 24, 8), ::GetFeatureIndex(index
), nih
->GetGRFID(this->GetFeatureIndex()));
477 case WID_NGRFI_MAINPANEL
: {
478 /* Does this feature have variables? */
479 const NIFeature
*nif
= GetFeature(this->window_number
);
480 if (nif
->variables
== NULL
) return;
482 /* Get the line, make sure it's within the boundaries. */
483 int line
= this->vscroll
->GetScrolledRowFromWidget(pt
.y
, this, WID_NGRFI_MAINPANEL
, TOP_OFFSET
);
484 if (line
== INT_MAX
) return;
486 /* Find the variable related to the line */
487 for (const NIVariable
*niv
= nif
->variables
; niv
->name
!= NULL
; niv
++, line
--) {
488 if (line
!= 1) continue; // 1 because of the "Variables:" line
490 if (!HasVariableParameter(niv
->var
)) break;
492 this->current_edit_param
= niv
->var
;
493 ShowQueryString(STR_EMPTY
, STR_NEWGRF_INSPECT_QUERY_CAPTION
, 9, this, CS_HEXADECIMAL
, QSF_NONE
);
499 virtual void OnQueryTextFinished(char *str
)
501 if (StrEmpty(str
)) return;
503 NewGRFInspectWindow::var60params
[GetFeatureNum(this->window_number
)][this->current_edit_param
- 0x60] = strtol(str
, NULL
, 16);
507 virtual void OnResize()
509 this->vscroll
->SetCapacityFromWidget(this, WID_NGRFI_MAINPANEL
, TOP_OFFSET
+ BOTTOM_OFFSET
);
513 /* static */ uint32
NewGRFInspectWindow::var60params
[GSF_FAKE_END
][0x20] = { {0} }; // Use spec to have 0s in whole array
515 static const NWidgetPart _nested_newgrf_inspect_widgets
[] = {
516 NWidget(NWID_HORIZONTAL
),
517 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
518 NWidget(WWT_CAPTION
, COLOUR_GREY
, WID_NGRFI_CAPTION
), SetDataTip(STR_NEWGRF_INSPECT_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
519 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_NGRFI_PARENT
), SetDataTip(STR_NEWGRF_INSPECT_PARENT_BUTTON
, STR_NEWGRF_INSPECT_PARENT_TOOLTIP
),
520 NWidget(WWT_SHADEBOX
, COLOUR_GREY
),
521 NWidget(WWT_DEFSIZEBOX
, COLOUR_GREY
),
522 NWidget(WWT_STICKYBOX
, COLOUR_GREY
),
524 NWidget(NWID_HORIZONTAL
),
525 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_NGRFI_MAINPANEL
), SetMinimalSize(300, 0), SetScrollbar(WID_NGRFI_SCROLLBAR
), EndContainer(),
526 NWidget(NWID_VERTICAL
),
527 NWidget(NWID_VSCROLLBAR
, COLOUR_GREY
, WID_NGRFI_SCROLLBAR
),
528 NWidget(WWT_RESIZEBOX
, COLOUR_GREY
),
533 static WindowDesc
_newgrf_inspect_desc(
534 WDP_AUTO
, "newgrf_inspect", 400, 300,
535 WC_NEWGRF_INSPECT
, WC_NONE
,
537 _nested_newgrf_inspect_widgets
, lengthof(_nested_newgrf_inspect_widgets
)
541 * Show the inspect window for a given feature and index.
542 * The index is normally an in-game location/identifier, such
543 * as a TileIndex or an IndustryID depending on the feature
544 * we want to inspect.
545 * @param feature The feature we want to inspect.
546 * @param index The index/identifier of the feature to inspect.
547 * @param grfid GRFID of the item opening this window, or 0 if not opened by other window.
549 void ShowNewGRFInspectWindow(GrfSpecFeature feature
, uint index
, const uint32 grfid
)
551 if (!IsNewGRFInspectable(feature
, index
)) return;
553 WindowNumber wno
= GetInspectWindowNumber(feature
, index
);
554 NewGRFInspectWindow
*w
= AllocateWindowDescFront
<NewGRFInspectWindow
>(&_newgrf_inspect_desc
, wno
);
555 if (w
== NULL
) w
= (NewGRFInspectWindow
*)FindWindowById(WC_NEWGRF_INSPECT
, wno
);
556 w
->SetCallerGRFID(grfid
);
560 * Delete inspect window for a given feature and index.
561 * The index is normally an in-game location/identifier, such
562 * as a TileIndex or an IndustryID depending on the feature
563 * we want to inspect.
564 * @param feature The feature we want to delete the window for.
565 * @param index The index/identifier of the feature to delete.
567 void DeleteNewGRFInspectWindow(GrfSpecFeature feature
, uint index
)
569 if (feature
== GSF_INVALID
) return;
571 WindowNumber wno
= GetInspectWindowNumber(feature
, index
);
572 DeleteWindowById(WC_NEWGRF_INSPECT
, wno
);
574 /* Reinitialise the land information window to remove the "debug" sprite if needed.
575 * Note: Since we might be called from a command here, it is important to not execute
576 * the invalidation immediately. The landinfo window tests commands itself. */
577 InvalidateWindowData(WC_LAND_INFO
, 0, 1);
581 * Can we inspect the data given a certain feature and index.
582 * The index is normally an in-game location/identifier, such
583 * as a TileIndex or an IndustryID depending on the feature
584 * we want to inspect.
585 * @param feature The feature we want to inspect.
586 * @param index The index/identifier of the feature to inspect.
587 * @return true if there is something to show.
589 bool IsNewGRFInspectable(GrfSpecFeature feature
, uint index
)
591 const NIFeature
*nif
= GetFeature(GetInspectWindowNumber(feature
, index
));
592 if (nif
== NULL
) return false;
593 return nif
->helper
->IsInspectable(index
);
597 * Get the GrfSpecFeature associated with the tile.
598 * @param tile The tile to get the feature from.
599 * @return the GrfSpecFeature.
601 GrfSpecFeature
GetGrfSpecFeature(TileIndex tile
)
603 if (IsHouseTile(tile
)) {
605 } else if (IsIndustryTile(tile
)) {
606 return GSF_INDUSTRYTILES
;
609 switch (GetTileType(tile
)) {
610 default: return GSF_INVALID
;
611 case TT_RAILWAY
: return IsTileSubtype(tile
, TT_TRACK
) ? GSF_RAILTYPES
: GSF_INVALID
;
612 case TT_MISC
: return (IsRailDepotTile(tile
) || IsLevelCrossingTile(tile
)) ? GSF_RAILTYPES
: GSF_INVALID
;
613 case TT_OBJECT
: return GSF_OBJECTS
;
616 switch (GetStationType(tile
)) {
617 case STATION_RAIL
: return GSF_STATIONS
;
618 case STATION_AIRPORT
: return GSF_AIRPORTTILES
;
619 default: return GSF_INVALID
;
625 * Get the GrfSpecFeature associated with the vehicle.
626 * @param type The vehicle type to get the feature from.
627 * @return the GrfSpecFeature.
629 GrfSpecFeature
GetGrfSpecFeature(VehicleType type
)
632 case VEH_TRAIN
: return GSF_TRAINS
;
633 case VEH_ROAD
: return GSF_ROADVEHICLES
;
634 case VEH_SHIP
: return GSF_SHIPS
;
635 case VEH_AIRCRAFT
: return GSF_AIRCRAFT
;
636 default: return GSF_INVALID
;
642 /**** Sprite Aligner ****/
644 /** Window used for aligning sprites. */
645 struct SpriteAlignerWindow
: Window
{
646 SpriteID current_sprite
; ///< The currently shown sprite
649 SpriteAlignerWindow(WindowDesc
*desc
, WindowNumber wno
) : Window(desc
)
651 this->CreateNestedTree();
652 this->vscroll
= this->GetScrollbar(WID_SA_SCROLLBAR
);
653 this->FinishInitNested(wno
);
655 /* Oh yes, we assume there is at least one normal sprite! */
656 while (GetSpriteType(this->current_sprite
) != ST_NORMAL
) this->current_sprite
++;
659 virtual void SetStringParameters(int widget
) const
663 SetDParam(0, this->current_sprite
);
664 SetDParamStr(1, FioGetFilename(GetOriginFileSlot(this->current_sprite
)));
667 case WID_SA_OFFSETS
: {
668 const Sprite
*spr
= GetSprite(this->current_sprite
, ST_NORMAL
);
669 SetDParam(0, spr
->x_offs
/ ZOOM_LVL_BASE
);
670 SetDParam(1, spr
->y_offs
/ ZOOM_LVL_BASE
);
679 virtual void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
)
681 if (widget
!= WID_SA_LIST
) return;
683 resize
->height
= max(11, FONT_HEIGHT_NORMAL
+ 1);
686 /* Resize to about 200 pixels (for the preview) */
687 size
->height
= (1 + 200 / resize
->height
) * resize
->height
;
690 virtual void DrawWidget(const Rect
&r
, int widget
) const
693 case WID_SA_SPRITE
: {
694 /* Center the sprite ourselves */
695 const Sprite
*spr
= GetSprite(this->current_sprite
, ST_NORMAL
);
696 int width
= r
.right
- r
.left
+ 1;
697 int height
= r
.bottom
- r
.top
+ 1;
698 int x
= r
.left
- spr
->x_offs
/ ZOOM_LVL_BASE
+ (width
- spr
->width
/ ZOOM_LVL_BASE
) / 2;
699 int y
= r
.top
- spr
->y_offs
/ ZOOM_LVL_BASE
+ (height
- spr
->height
/ ZOOM_LVL_BASE
) / 2;
701 /* And draw only the part within the sprite area */
703 spr
->x_offs
+ (spr
->width
- width
* ZOOM_LVL_BASE
) / 2 + 1,
704 spr
->y_offs
+ (spr
->height
- height
* ZOOM_LVL_BASE
) / 2 + 1,
705 spr
->x_offs
+ (spr
->width
+ width
* ZOOM_LVL_BASE
) / 2 - 1,
706 spr
->y_offs
+ (spr
->height
+ height
* ZOOM_LVL_BASE
) / 2 - 1,
709 DrawSprite(this->current_sprite
, PAL_NONE
, x
, y
, &subspr
, ZOOM_LVL_GUI
);
714 const NWidgetBase
*nwid
= this->GetWidget
<NWidgetBase
>(widget
);
715 int step_size
= nwid
->resize_y
;
717 SmallVector
<SpriteID
, 256> &list
= _newgrf_debug_sprite_picker
.sprites
;
718 int max
= min
<int>(this->vscroll
->GetPosition() + this->vscroll
->GetCapacity(), list
.Length());
720 int y
= r
.top
+ WD_FRAMERECT_TOP
;
721 for (int i
= this->vscroll
->GetPosition(); i
< max
; i
++) {
722 SetDParam(0, list
[i
]);
723 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, STR_BLACK_COMMA
, TC_FROMSTRING
, SA_RIGHT
| SA_FORCE
);
731 virtual void OnClick(Point pt
, int widget
, int click_count
)
734 case WID_SA_PREVIOUS
:
736 this->current_sprite
= (this->current_sprite
== 0 ? GetMaxSpriteID() : this->current_sprite
) - 1;
737 } while (GetSpriteType(this->current_sprite
) != ST_NORMAL
);
742 ShowQueryString(STR_EMPTY
, STR_SPRITE_ALIGNER_GOTO_CAPTION
, 7, this, CS_NUMERAL
, QSF_NONE
);
747 this->current_sprite
= (this->current_sprite
+ 1) % GetMaxSpriteID();
748 } while (GetSpriteType(this->current_sprite
) != ST_NORMAL
);
753 this->LowerWidget(WID_SA_PICKER
);
754 _newgrf_debug_sprite_picker
.mode
= SPM_WAIT_CLICK
;
759 const NWidgetBase
*nwid
= this->GetWidget
<NWidgetBase
>(widget
);
760 int step_size
= nwid
->resize_y
;
762 uint i
= this->vscroll
->GetPosition() + (pt
.y
- nwid
->pos_y
) / step_size
;
763 if (i
< _newgrf_debug_sprite_picker
.sprites
.Length()) {
764 SpriteID spr
= _newgrf_debug_sprite_picker
.sprites
[i
];
765 if (GetSpriteType(spr
) == ST_NORMAL
) this->current_sprite
= spr
;
776 * Yes... this is a hack.
778 * No... I don't think it is useful to make this less of a hack.
780 * If you want to align sprites, you just need the number. Generally
781 * the sprite caches are big enough to not remove the sprite from the
782 * cache. If that's not the case, just let the NewGRF developer
783 * increase the cache size instead of storing thousands of offsets
784 * for the incredibly small chance that it's actually going to be
785 * used by someone and the sprite cache isn't big enough for that
786 * particular NewGRF developer.
788 Sprite
*spr
= const_cast<Sprite
*>(GetSprite(this->current_sprite
, ST_NORMAL
));
790 case WID_SA_UP
: spr
->y_offs
-= ZOOM_LVL_BASE
; break;
791 case WID_SA_DOWN
: spr
->y_offs
+= ZOOM_LVL_BASE
; break;
792 case WID_SA_LEFT
: spr
->x_offs
-= ZOOM_LVL_BASE
; break;
793 case WID_SA_RIGHT
: spr
->x_offs
+= ZOOM_LVL_BASE
; break;
795 /* Of course, we need to redraw the sprite, but where is it used?
796 * Everywhere is a safe bet. */
797 MarkWholeScreenDirty();
803 virtual void OnQueryTextFinished(char *str
)
805 if (StrEmpty(str
)) return;
807 this->current_sprite
= atoi(str
);
808 if (this->current_sprite
>= GetMaxSpriteID()) this->current_sprite
= 0;
809 while (GetSpriteType(this->current_sprite
) != ST_NORMAL
) {
810 this->current_sprite
= (this->current_sprite
+ 1) % GetMaxSpriteID();
816 * Some data on this window has become invalid.
817 * @param data Information about the changed data.
818 * @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.
820 virtual void OnInvalidateData(int data
= 0, bool gui_scope
= true)
822 if (!gui_scope
) return;
824 /* Sprite picker finished */
825 this->RaiseWidget(WID_SA_PICKER
);
826 this->vscroll
->SetCount(_newgrf_debug_sprite_picker
.sprites
.Length());
830 virtual void OnResize()
832 this->vscroll
->SetCapacityFromWidget(this, WID_SA_LIST
);
836 static const NWidgetPart _nested_sprite_aligner_widgets
[] = {
837 NWidget(NWID_HORIZONTAL
),
838 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
839 NWidget(WWT_CAPTION
, COLOUR_GREY
, WID_SA_CAPTION
), SetDataTip(STR_SPRITE_ALIGNER_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
840 NWidget(WWT_SHADEBOX
, COLOUR_GREY
),
841 NWidget(WWT_STICKYBOX
, COLOUR_GREY
),
843 NWidget(WWT_PANEL
, COLOUR_GREY
),
844 NWidget(NWID_HORIZONTAL
), SetPIP(0, 0, 10),
845 NWidget(NWID_VERTICAL
), SetPIP(10, 5, 10),
846 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
), SetPIP(10, 5, 10),
847 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_SA_PREVIOUS
), SetDataTip(STR_SPRITE_ALIGNER_PREVIOUS_BUTTON
, STR_SPRITE_ALIGNER_PREVIOUS_TOOLTIP
), SetFill(1, 0),
848 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_SA_GOTO
), SetDataTip(STR_SPRITE_ALIGNER_GOTO_BUTTON
, STR_SPRITE_ALIGNER_GOTO_TOOLTIP
), SetFill(1, 0),
849 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_SA_NEXT
), SetDataTip(STR_SPRITE_ALIGNER_NEXT_BUTTON
, STR_SPRITE_ALIGNER_NEXT_TOOLTIP
), SetFill(1, 0),
851 NWidget(NWID_HORIZONTAL
), SetPIP(10, 5, 10),
852 NWidget(NWID_SPACER
), SetFill(1, 1),
853 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_SA_UP
), SetDataTip(SPR_ARROW_UP
, STR_SPRITE_ALIGNER_MOVE_TOOLTIP
), SetResize(0, 0),
854 NWidget(NWID_SPACER
), SetFill(1, 1),
856 NWidget(NWID_HORIZONTAL_LTR
), SetPIP(10, 5, 10),
857 NWidget(NWID_VERTICAL
),
858 NWidget(NWID_SPACER
), SetFill(1, 1),
859 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_SA_LEFT
), SetDataTip(SPR_ARROW_LEFT
, STR_SPRITE_ALIGNER_MOVE_TOOLTIP
), SetResize(0, 0),
860 NWidget(NWID_SPACER
), SetFill(1, 1),
862 NWidget(WWT_PANEL
, COLOUR_DARK_BLUE
, WID_SA_SPRITE
), SetDataTip(STR_NULL
, STR_SPRITE_ALIGNER_SPRITE_TOOLTIP
),
864 NWidget(NWID_VERTICAL
),
865 NWidget(NWID_SPACER
), SetFill(1, 1),
866 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_SA_RIGHT
), SetDataTip(SPR_ARROW_RIGHT
, STR_SPRITE_ALIGNER_MOVE_TOOLTIP
), SetResize(0, 0),
867 NWidget(NWID_SPACER
), SetFill(1, 1),
870 NWidget(NWID_HORIZONTAL
), SetPIP(10, 5, 10),
871 NWidget(NWID_SPACER
), SetFill(1, 1),
872 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_SA_DOWN
), SetDataTip(SPR_ARROW_DOWN
, STR_SPRITE_ALIGNER_MOVE_TOOLTIP
), SetResize(0, 0),
873 NWidget(NWID_SPACER
), SetFill(1, 1),
875 NWidget(NWID_HORIZONTAL
), SetPIP(10, 5, 10),
876 NWidget(WWT_LABEL
, COLOUR_GREY
, WID_SA_OFFSETS
), SetDataTip(STR_SPRITE_ALIGNER_OFFSETS
, STR_NULL
), SetFill(1, 0),
879 NWidget(NWID_VERTICAL
), SetPIP(10, 5, 10),
880 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_SA_PICKER
), SetDataTip(STR_SPRITE_ALIGNER_PICKER_BUTTON
, STR_SPRITE_ALIGNER_PICKER_TOOLTIP
), SetFill(1, 0),
881 NWidget(NWID_HORIZONTAL
),
882 NWidget(WWT_MATRIX
, COLOUR_GREY
, WID_SA_LIST
), SetResize(1, 1), SetMatrixDataTip(1, 0, STR_NULL
), SetFill(1, 1), SetScrollbar(WID_SA_SCROLLBAR
),
883 NWidget(NWID_VSCROLLBAR
, COLOUR_GREY
, WID_SA_SCROLLBAR
),
890 static WindowDesc
_sprite_aligner_desc(
891 WDP_AUTO
, "sprite_aligner", 400, 300,
892 WC_SPRITE_ALIGNER
, WC_NONE
,
894 _nested_sprite_aligner_widgets
, lengthof(_nested_sprite_aligner_widgets
)
898 * Show the window for aligning sprites.
900 void ShowSpriteAlignerWindow()
902 AllocateWindowDescFront
<SpriteAlignerWindow
>(&_sprite_aligner_desc
, 0);