Changeset - r28050:7462e0a9ad17
[Not reviewed]
master
0 5 0
Peter Nelson - 13 months ago 2023-10-28 18:07:50
peter1138@openttd.org
Add: Debug tool to draw widget outlines. (#11401)

This is considered a developer tool and is controlled from the help menu (or default hotkey Ctrl-O).

This draws a white dashed outline around widgets. NWidgetSpacer and (unused) WWT_EMPTY widgets are also filled with check pattern to highlight them, as they usually indicate a design issue.
5 files changed with 96 insertions and 34 deletions:
0 comments (0 inline, 0 general)
src/lang/english.txt
Show inline comments
 
@@ -518,24 +518,25 @@ STR_TOOLBAR_SOUND_MUSIC                 
 
###length 3
 
STR_NEWS_MENU_LAST_MESSAGE_NEWS_REPORT                          :Last message/news report
 
STR_NEWS_MENU_MESSAGE_HISTORY_MENU                              :Message history
 
STR_NEWS_MENU_DELETE_ALL_MESSAGES                               :Delete all messages
 

	
 
# About menu
 
###length 11
 
###length 12
 
STR_ABOUT_MENU_LAND_BLOCK_INFO                                  :Land area information
 
STR_ABOUT_MENU_HELP                                             :Help & manuals
 
STR_ABOUT_MENU_SEPARATOR                                        :
 
STR_ABOUT_MENU_TOGGLE_CONSOLE                                   :Toggle console
 
STR_ABOUT_MENU_AI_DEBUG                                         :AI/Game script debug
 
STR_ABOUT_MENU_SCREENSHOT                                       :Screenshot
 
STR_ABOUT_MENU_SHOW_FRAMERATE                                   :Show frame rate
 
STR_ABOUT_MENU_ABOUT_OPENTTD                                    :About 'OpenTTD'
 
STR_ABOUT_MENU_SPRITE_ALIGNER                                   :Sprite aligner
 
STR_ABOUT_MENU_TOGGLE_BOUNDING_BOXES                            :Toggle bounding boxes
 
STR_ABOUT_MENU_TOGGLE_DIRTY_BLOCKS                              :Toggle colouring of dirty blocks
 
STR_ABOUT_MENU_TOGGLE_WIDGET_OUTLINES                           :Toggle widget outlines
 

	
 
# Place in highscore window
 
###length 15
 
STR_ORDINAL_NUMBER_1ST                                          :1st
 
STR_ORDINAL_NUMBER_2ND                                          :2nd
 
STR_ORDINAL_NUMBER_3RD                                          :3rd
src/main_gui.cpp
Show inline comments
 
@@ -183,12 +183,13 @@ static const struct NWidgetPart _nested_
 
enum {
 
	GHK_QUIT,
 
	GHK_ABANDON,
 
	GHK_CONSOLE,
 
	GHK_BOUNDING_BOXES,
 
	GHK_DIRTY_BLOCKS,
 
	GHK_WIDGET_OUTLINES,
 
	GHK_CENTER,
 
	GHK_CENTER_ZOOM,
 
	GHK_RESET_OBJECT_TO_PLACE,
 
	GHK_DELETE_WINDOWS,
 
	GHK_DELETE_NONVITAL_WINDOWS,
 
	GHK_DELETE_ALL_MESSAGES,
 
@@ -304,12 +305,16 @@ struct MainWindow : Window
 
				ToggleBoundingBoxes();
 
				return ES_HANDLED;
 

	
 
			case GHK_DIRTY_BLOCKS:
 
				ToggleDirtyBlocks();
 
				return ES_HANDLED;
 

	
 
			case GHK_WIDGET_OUTLINES:
 
				ToggleWidgetOutlines();
 
				return ES_HANDLED;
 
		}
 

	
 
		if (_game_mode == GM_MENU) return ES_NOT_HANDLED;
 

	
 
		switch (hotkey) {
 
			case GHK_CENTER:
 
@@ -464,12 +469,13 @@ struct MainWindow : Window
 
	static inline HotkeyList hotkeys{"global", {
 
		Hotkey({'Q' | WKC_CTRL, 'Q' | WKC_META}, "quit", GHK_QUIT),
 
		Hotkey({'W' | WKC_CTRL, 'W' | WKC_META}, "abandon", GHK_ABANDON),
 
		Hotkey(WKC_BACKQUOTE, "console", GHK_CONSOLE),
 
		Hotkey('B' | WKC_CTRL, "bounding_boxes", GHK_BOUNDING_BOXES),
 
		Hotkey('I' | WKC_CTRL, "dirty_blocks", GHK_DIRTY_BLOCKS),
 
		Hotkey('O' | WKC_CTRL, "widget_outlines", GHK_WIDGET_OUTLINES),
 
		Hotkey('C', "center", GHK_CENTER),
 
		Hotkey('Z', "center_zoom", GHK_CENTER_ZOOM),
 
		Hotkey(WKC_ESC, "reset_object_to_place", GHK_RESET_OBJECT_TO_PLACE),
 
		Hotkey(WKC_DELETE, "delete_windows", GHK_DELETE_WINDOWS),
 
		Hotkey(WKC_DELETE | WKC_SHIFT, "delete_all_windows", GHK_DELETE_NONVITAL_WINDOWS),
 
		Hotkey(WKC_DELETE | WKC_CTRL, "delete_all_messages", GHK_DELETE_ALL_MESSAGES),
src/toolbar_gui.cpp
Show inline comments
 
@@ -1097,13 +1097,13 @@ static CallBackFunction PlaceLandBlockIn
 
		return CBF_PLACE_LANDINFO;
 
	}
 
}
 

	
 
static CallBackFunction ToolbarHelpClick(Window *w)
 
{
 
	PopupMainToolbMenu(w, _game_mode == GM_EDITOR ? (int)WID_TE_HELP : (int)WID_TN_HELP, STR_ABOUT_MENU_LAND_BLOCK_INFO, _settings_client.gui.newgrf_developer_tools ? 11 : 8);
 
	PopupMainToolbMenu(w, _game_mode == GM_EDITOR ? (int)WID_TE_HELP : (int)WID_TN_HELP, STR_ABOUT_MENU_LAND_BLOCK_INFO, _settings_client.gui.newgrf_developer_tools ? 12 : 8);
 
	return CBF_NONE;
 
}
 

	
 
/**
 
 * Toggle drawing of sprites' bounding boxes.
 
 * @note has only an effect when newgrf_developer_tools are active.
 
@@ -1136,12 +1136,26 @@ void ToggleDirtyBlocks()
 
		_draw_dirty_blocks = !_draw_dirty_blocks;
 
		MarkWholeScreenDirty();
 
	}
 
}
 

	
 
/**
 
 * Toggle drawing of widget outlihes.
 
 * @note has only an effect when newgrf_developer_tools are active.
 
 */
 
void ToggleWidgetOutlines()
 
{
 
	extern bool _draw_widget_outlines;
 
	/* Always allow to toggle them off */
 
	if (_settings_client.gui.newgrf_developer_tools || _draw_widget_outlines) {
 
		_draw_widget_outlines = !_draw_widget_outlines;
 
		MarkWholeScreenDirty();
 
	}
 
}
 

	
 
/**
 
 * Set the starting year for a scenario.
 
 * @param year New starting year.
 
 */
 
void SetStartingYear(TimerGameCalendar::Year year)
 
{
 
	_settings_game.game_creation.starting_year = Clamp(year, CalendarTime::MIN_YEAR, CalendarTime::MAX_YEAR);
 
@@ -1166,12 +1180,13 @@ static CallBackFunction MenuClickHelp(in
 
		case  5: ShowScreenshotWindow();           break;
 
		case  6: ShowFramerateWindow();            break;
 
		case  7: ShowAboutWindow();                break;
 
		case  8: ShowSpriteAlignerWindow();        break;
 
		case  9: ToggleBoundingBoxes();            break;
 
		case 10: ToggleDirtyBlocks();              break;
 
		case 11: ToggleWidgetOutlines();           break;
 
	}
 
	return CBF_NONE;
 
}
 

	
 
/* --- Switch toolbar button --- */
 

	
src/toolbar_gui.h
Show inline comments
 
@@ -53,10 +53,11 @@ enum MainToolbarHotkeys {
 
	MTHK_SIGN_LIST
 
};
 

	
 
void AllocateToolbar();
 
void ToggleBoundingBoxes();
 
void ToggleDirtyBlocks();
 
void ToggleWidgetOutlines();
 

	
 
extern uint _toolbar_width;
 

	
 
#endif /* TOOLBAR_GUI_H */
src/widget.cpp
Show inline comments
 
@@ -912,12 +912,24 @@ void Window::DrawSortButtonState(int wid
 
 */
 
int Window::SortButtonWidth()
 
{
 
	return NWidgetScrollbar::GetVerticalDimension().width + 1;
 
}
 

	
 
bool _draw_widget_outlines;
 

	
 
void DrawOutline(const Window *, const NWidgetBase *wid)
 
{
 
	if (!_draw_widget_outlines || wid->current_x == 0 || wid->current_y == 0) return;
 

	
 
	Rect r = wid->GetCurrentRect();
 
	GfxDrawLine(r.left,  r.top,    r.right, r.top,    PC_WHITE, 1, 4);
 
	GfxDrawLine(r.left,  r.top,    r.left,  r.bottom, PC_WHITE, 1, 4);
 
	GfxDrawLine(r.right, r.top,    r.right, r.bottom, PC_WHITE, 1, 4);
 
	GfxDrawLine(r.left,  r.bottom, r.right, r.bottom, PC_WHITE, 1, 4);
 
}
 

	
 
/**
 
 * @defgroup NestedWidgets Hierarchical widgets
 
 * Hierarchical widgets, also known as nested widgets, are widgets stored in a tree. At the leafs of the tree are (mostly) the 'real' widgets
 
 * visible to the user. At higher levels, widgets get organized in container widgets, until all widgets of the window are merged.
 
 *
 
@@ -1305,12 +1317,14 @@ void NWidgetContainer::FillNestedArray(N
 

	
 
void NWidgetContainer::Draw(const Window *w)
 
{
 
	for (NWidgetBase *child_wid = this->head; child_wid != nullptr; child_wid = child_wid->next) {
 
		child_wid->Draw(w);
 
	}
 

	
 
	DrawOutline(w, this);
 
}
 

	
 
NWidgetCore *NWidgetContainer::GetWidgetFromPos(int x, int y)
 
{
 
	if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return nullptr;
 

	
 
@@ -1417,12 +1431,13 @@ void NWidgetStacked::Draw(const Window *
 
	if (this->shown_plane >= SZSP_BEGIN) return;
 

	
 
	int plane = 0;
 
	for (NWidgetBase *child_wid = this->head; child_wid != nullptr; plane++, child_wid = child_wid->next) {
 
		if (plane == this->shown_plane) {
 
			child_wid->Draw(w);
 
			DrawOutline(w, this);
 
			return;
 
		}
 
	}
 

	
 
	NOT_REACHED();
 
}
 
@@ -1846,15 +1861,22 @@ void NWidgetSpacer::SetupSmallestSize(Wi
 
}
 

	
 
void NWidgetSpacer::FillNestedArray(NWidgetBase **, uint)
 
{
 
}
 

	
 
void NWidgetSpacer::Draw(const Window *)
 
void NWidgetSpacer::Draw(const Window *w)
 
{
 
	/* Spacer widget is never visible. */
 
	/* Spacer widget is never normally visible. */
 

	
 
	if (_draw_widget_outlines && this->current_x != 0 && this->current_y != 0) {
 
		/* Spacers indicate a potential design issue, so get extra highlighting. */
 
		GfxFillRect(this->GetCurrentRect(), PC_WHITE, FILLRECT_CHECKER);
 

	
 
		DrawOutline(w, this);
 
	}
 
}
 

	
 
void NWidgetSpacer::SetDirty(const Window *) const
 
{
 
	/* Spacer widget never need repainting. */
 
}
 
@@ -2027,45 +2049,50 @@ NWidgetCore *NWidgetMatrix::GetWidgetFro
 
	GfxFillRect(this->pos_x, this->pos_y, this->pos_x + this->current_x - 1, this->pos_y + this->current_y - 1, _colour_gradient[this->colour & 0xF][5]);
 

	
 
	/* Set up a clipping area for the previews. */
 
	bool rtl = _current_text_dir == TD_RTL;
 
	DrawPixelInfo tmp_dpi;
 
	if (!FillDrawPixelInfo(&tmp_dpi, this->pos_x + (rtl ? this->pip_post : this->pip_pre), this->pos_y + this->pip_pre, this->current_x - this->pip_pre - this->pip_post, this->current_y - this->pip_pre - this->pip_post)) return;
 
	AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
 

	
 
	/* Get the appropriate offsets so we can draw the right widgets. */
 
	NWidgetCore *child = dynamic_cast<NWidgetCore *>(this->head);
 
	assert(child != nullptr);
 
	int start_x, start_y, base_offs_x, base_offs_y;
 
	this->GetScrollOffsets(start_x, start_y, base_offs_x, base_offs_y);
 

	
 
	int offs_y = base_offs_y;
 
	for (int y = start_y; y < start_y + this->widgets_y + 1; y++, offs_y += this->widget_h) {
 
		/* Are we within bounds? */
 
		if (offs_y + child->smallest_y <= 0) continue;
 
		if (offs_y >= (int)this->current_y) break;
 

	
 
		/* We've passed our amount of widgets. */
 
		if (y * this->widgets_x >= this->count) break;
 

	
 
		int offs_x = base_offs_x;
 
		for (int x = start_x; x < start_x + this->widgets_x + 1; x++, offs_x += rtl ? -this->widget_w : this->widget_w) {
 

	
 
	{
 
		AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
 

	
 
		/* Get the appropriate offsets so we can draw the right widgets. */
 
		NWidgetCore *child = dynamic_cast<NWidgetCore *>(this->head);
 
		assert(child != nullptr);
 
		int start_x, start_y, base_offs_x, base_offs_y;
 
		this->GetScrollOffsets(start_x, start_y, base_offs_x, base_offs_y);
 

	
 
		int offs_y = base_offs_y;
 
		for (int y = start_y; y < start_y + this->widgets_y + 1; y++, offs_y += this->widget_h) {
 
			/* Are we within bounds? */
 
			if (offs_x + child->smallest_x <= 0) continue;
 
			if (offs_x >= (int)this->current_x) continue;
 

	
 
			/* Do we have this many widgets? */
 
			int sub_wid = y * this->widgets_x + x;
 
			if (sub_wid >= this->count) break;
 

	
 
			child->AssignSizePosition(ST_RESIZE, offs_x, offs_y, child->smallest_x, child->smallest_y, rtl);
 
			child->SetLowered(this->clicked == sub_wid);
 
			SB(child->index, 16, 16, sub_wid);
 
			child->Draw(w);
 
			if (offs_y + child->smallest_y <= 0) continue;
 
			if (offs_y >= (int)this->current_y) break;
 

	
 
			/* We've passed our amount of widgets. */
 
			if (y * this->widgets_x >= this->count) break;
 

	
 
			int offs_x = base_offs_x;
 
			for (int x = start_x; x < start_x + this->widgets_x + 1; x++, offs_x += rtl ? -this->widget_w : this->widget_w) {
 
				/* Are we within bounds? */
 
				if (offs_x + child->smallest_x <= 0) continue;
 
				if (offs_x >= (int)this->current_x) continue;
 

	
 
				/* Do we have this many widgets? */
 
				int sub_wid = y * this->widgets_x + x;
 
				if (sub_wid >= this->count) break;
 

	
 
				child->AssignSizePosition(ST_RESIZE, offs_x, offs_y, child->smallest_x, child->smallest_y, rtl);
 
				child->SetLowered(this->clicked == sub_wid);
 
				SB(child->index, 16, 16, sub_wid);
 
				child->Draw(w);
 
			}
 
		}
 
	}
 

	
 
	DrawOutline(w, this);
 
}
 

	
 
/**
 
 * Get the different offsets that are influenced by scrolling.
 
 * @param[out] start_x     The start position in columns (index of the left-most column, swapped in RTL).
 
 * @param[out] start_y     The start position in rows.
 
@@ -2272,12 +2299,14 @@ void NWidgetBackground::Draw(const Windo
 
	if (this->index >= 0) w->DrawWidget(r, this->index);
 
	if (this->child != nullptr) this->child->Draw(w);
 

	
 
	if (this->IsDisabled()) {
 
		GfxFillRect(r.Shrink(WidgetDimensions::scaled.bevel), _colour_gradient[this->colour & 0xF][2], FILLRECT_CHECKER);
 
	}
 

	
 
	DrawOutline(w, this);
 
}
 

	
 
NWidgetCore *NWidgetBackground::GetWidgetFromPos(int x, int y)
 
{
 
	NWidgetCore *nwid = nullptr;
 
	if (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) {
 
@@ -2325,12 +2354,14 @@ void NWidgetViewport::Draw(const Window 
 

	
 
	/* Optionally shade the viewport. */
 
	if (this->disp_flags & (ND_SHADE_GREY | ND_SHADE_DIMMED)) {
 
		GfxFillRect(this->pos_x, this->pos_y, this->pos_x + this->current_x - 1, this->pos_y + this->current_y - 1,
 
				(this->disp_flags & ND_SHADE_DIMMED) ? PALETTE_TO_TRANSPARENT : PALETTE_NEWSPAPER, FILLRECT_RECOLOUR);
 
	}
 

	
 
	DrawOutline(w, this);
 
}
 

	
 
/**
 
 * Initialize the viewport of the window.
 
 * @param w            Window owning the viewport.
 
 * @param focus        Either the tile index or vehicle ID to focus.
 
@@ -2524,12 +2555,14 @@ void NWidgetScrollbar::Draw(const Window
 
		DrawVerticalScrollbar(r, this->colour, up_lowered, middle_lowered, down_lowered, this);
 
	}
 

	
 
	if (this->IsDisabled()) {
 
		GfxFillRect(r.Shrink(WidgetDimensions::scaled.bevel), _colour_gradient[this->colour & 0xF][2], FILLRECT_CHECKER);
 
	}
 

	
 
	DrawOutline(w, this);
 
}
 

	
 
/* static */ void NWidgetScrollbar::InvalidateDimensionCache()
 
{
 
	vertical_dimension.width   = vertical_dimension.height   = 0;
 
	horizontal_dimension.width = horizontal_dimension.height = 0;
 
@@ -2869,12 +2902,16 @@ void NWidgetLeaf::Draw(const Window *w)
 

	
 
	Rect r = this->GetCurrentRect();
 

	
 
	bool clicked = this->IsLowered();
 
	switch (this->type) {
 
		case WWT_EMPTY:
 
			/* WWT_EMPTY used as a spacer indicates a potential design issue. */
 
			if (this->index == -1 && _draw_widget_outlines) {
 
				GfxFillRect(r, PC_BLACK, FILLRECT_CHECKER);
 
			}
 
			break;
 

	
 
		case WWT_PUSHBTN:
 
			assert(this->widget_data == 0);
 
			DrawFrameRect(r.left, r.top, r.right, r.bottom, this->colour, (clicked) ? FR_LOWERED : FR_NONE);
 
			break;
 
@@ -2976,12 +3013,14 @@ void NWidgetLeaf::Draw(const Window *w)
 
	}
 
	if (this->index >= 0) w->DrawWidget(r, this->index);
 

	
 
	if (this->IsDisabled()) {
 
		GfxFillRect(r.Shrink(WidgetDimensions::scaled.bevel), _colour_gradient[this->colour & 0xF][2], FILLRECT_CHECKER);
 
	}
 

	
 
	DrawOutline(w, this);
 
}
 

	
 
/**
 
 * For a #NWID_BUTTON_DROPDOWN, test whether \a pt refers to the button or to the drop-down.
 
 * @param pt Point in the widget.
 
 * @return The point refers to the button.
0 comments (0 inline, 0 general)