diff --git a/src/airport_gui.cpp b/src/airport_gui.cpp
index 6f942428f9..e0e1adf461 100644
--- a/src/airport_gui.cpp
+++ b/src/airport_gui.cpp
@@ -495,8 +495,8 @@ public:
 				break;
 
 			case WID_AP_AIRPORT_LIST: {
-				int num_clicked = this->vscroll->GetScrolledRowFromWidget(pt.y, this, widget, 0, this->line_height);
-				if (num_clicked == INT_MAX) break;
+				int32_t num_clicked = this->vscroll->GetScrolledRowFromWidget(pt.y, this, widget, 0, this->line_height);
+				if (num_clicked == INT32_MAX) break;
 				const AirportSpec *as = AirportClass::Get(_selected_airport_class)->GetSpec(num_clicked);
 				if (as->IsAvailable()) this->SelectOtherAirport(num_clicked);
 				break;
diff --git a/src/depot_gui.cpp b/src/depot_gui.cpp
index a630bf26e4..ec1595460a 100644
--- a/src/depot_gui.cpp
+++ b/src/depot_gui.cpp
@@ -459,10 +459,10 @@ struct DepotWindow : Window {
 		}
 		ym = (y - matrix_widget->pos_y) % this->resize.step_height;
 
-		int row = this->vscroll->GetScrolledRowFromWidget(y, this, WID_D_MATRIX);
+		int32_t row = this->vscroll->GetScrolledRowFromWidget(y, this, WID_D_MATRIX);
 		uint pos = (row * this->num_columns) + xt;
 
-		if (row == INT_MAX || this->vehicle_list.size() + this->wagon_list.size() <= pos) {
+		if (row == INT32_MAX || this->vehicle_list.size() + this->wagon_list.size() <= pos) {
 			/* Clicking on 'line' / 'block' without a vehicle */
 			if (this->type == VEH_TRAIN) {
 				/* End the dragging */
diff --git a/src/framerate_gui.cpp b/src/framerate_gui.cpp
index 6ffacd73bd..7e32bd611b 100644
--- a/src/framerate_gui.cpp
+++ b/src/framerate_gui.cpp
@@ -601,7 +601,7 @@ struct FramerateWindow : Window {
 	void DrawElementTimesColumn(const Rect &r, StringID heading_str, const CachedDecimal *values) const
 	{
 		const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
-		uint16_t skip = sb->GetPosition();
+		int32_t skip = sb->GetPosition();
 		int drawable = this->num_displayed;
 		int y = r.top;
 		DrawString(r.left, r.right, y, heading_str, TC_FROMSTRING, SA_CENTER, true);
@@ -623,7 +623,7 @@ struct FramerateWindow : Window {
 	void DrawElementAllocationsColumn(const Rect &r) const
 	{
 		const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
-		uint16_t skip = sb->GetPosition();
+		int32_t skip = sb->GetPosition();
 		int drawable = this->num_displayed;
 		int y = r.top;
 		DrawString(r.left, r.right, y, STR_FRAMERATE_MEMORYUSE, TC_FROMSTRING, SA_CENTER, true);
@@ -657,7 +657,7 @@ struct FramerateWindow : Window {
 			case WID_FRW_TIMES_NAMES: {
 				/* Render a column of titles for performance element names */
 				const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
-				uint16_t skip = sb->GetPosition();
+				int32_t skip = sb->GetPosition();
 				int drawable = this->num_displayed;
 				int y = r.top + GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.vsep_normal; // first line contains headings in the value columns
 				for (PerformanceElement e : DISPLAY_ORDER_PFE) {
@@ -701,8 +701,8 @@ struct FramerateWindow : Window {
 			case WID_FRW_TIMES_AVERAGE: {
 				/* Open time graph windows when clicking detail measurement lines */
 				const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
-				int line = sb->GetScrolledRowFromWidget(pt.y, this, widget, WidgetDimensions::scaled.vsep_normal + GetCharacterHeight(FS_NORMAL));
-				if (line != INT_MAX) {
+				int32_t line = sb->GetScrolledRowFromWidget(pt.y, this, widget, WidgetDimensions::scaled.vsep_normal + GetCharacterHeight(FS_NORMAL));
+				if (line != INT32_MAX) {
 					line++;
 					/* Find the visible line that was clicked */
 					for (PerformanceElement e : DISPLAY_ORDER_PFE) {
diff --git a/src/newgrf_debug_gui.cpp b/src/newgrf_debug_gui.cpp
index b5135b552c..e470e55c71 100644
--- a/src/newgrf_debug_gui.cpp
+++ b/src/newgrf_debug_gui.cpp
@@ -589,8 +589,8 @@ struct NewGRFInspectWindow : Window {
 				if (nif->variables == nullptr) return;
 
 				/* Get the line, make sure it's within the boundaries. */
-				int line = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NGRFI_MAINPANEL, WidgetDimensions::scaled.frametext.top);
-				if (line == INT_MAX) return;
+				int32_t line = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NGRFI_MAINPANEL, WidgetDimensions::scaled.frametext.top);
+				if (line == INT32_MAX) return;
 
 				/* Find the variable related to the line */
 				for (const NIVariable *niv = nif->variables; niv->name != nullptr; niv++, line--) {
diff --git a/src/newgrf_gui.cpp b/src/newgrf_gui.cpp
index 695cad546a..f534996a6b 100644
--- a/src/newgrf_gui.cpp
+++ b/src/newgrf_gui.cpp
@@ -146,11 +146,11 @@ static void ShowNewGRFInfo(const GRFConfig *c, const Rect &r, bool show_params)
 struct NewGRFParametersWindow : public Window {
 	static GRFParameterInfo dummy_parameter_info; ///< Dummy info in case a newgrf didn't provide info about some parameter.
 	GRFConfig *grf_config; ///< Set the parameters of this GRFConfig.
-	uint clicked_button;   ///< The row in which a button was clicked or UINT_MAX.
+	int32_t clicked_button; ///< The row in which a button was clicked or INT_MAX when none is selected.
 	bool clicked_increase; ///< True if the increase button was clicked, false for the decrease button.
 	bool clicked_dropdown; ///< Whether the dropdown is open.
 	bool closing_dropdown; ///< True, if the dropdown list is currently closing.
-	uint clicked_row;      ///< The selected parameter
+	int32_t clicked_row; ///< The selected parameter, or INT_MAX when none is selected.
 	int line_height;       ///< Height of a row in the matrix widget.
 	Scrollbar *vscroll;
 	bool action14present;  ///< True if action14 information is present.
@@ -158,10 +158,10 @@ struct NewGRFParametersWindow : public Window {
 
 	NewGRFParametersWindow(WindowDesc *desc, bool is_baseset, GRFConfig *c, bool editable) : Window(desc),
 		grf_config(c),
-		clicked_button(UINT_MAX),
+		clicked_button(INT32_MAX),
 		clicked_dropdown(false),
 		closing_dropdown(false),
-		clicked_row(UINT_MAX),
+		clicked_row(INT32_MAX),
 		editable(editable)
 	{
 		this->action14present = (c->num_valid_params != c->param.size() || !c->param_info.empty());
@@ -282,7 +282,7 @@ struct NewGRFParametersWindow : public Window {
 
 		int button_y_offset = (this->line_height - SETTING_BUTTON_HEIGHT) / 2;
 		int text_y_offset = (this->line_height - GetCharacterHeight(FS_NORMAL)) / 2;
-		for (uint i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < this->vscroll->GetCount(); i++) {
+		for (int32_t i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < this->vscroll->GetCount(); i++) {
 			GRFParameterInfo &par_info = this->GetParameterInfo(i);
 			uint32_t current_value = par_info.GetValue(this->grf_config);
 			bool selected = (i == this->clicked_row);
@@ -354,7 +354,7 @@ struct NewGRFParametersWindow : public Window {
 
 			case WID_NP_BACKGROUND: {
 				if (!this->editable) break;
-				uint num = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NP_BACKGROUND);
+				int32_t num = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NP_BACKGROUND);
 				if (num >= this->vscroll->GetCount()) break;
 
 				if (this->clicked_row != num) {
@@ -493,15 +493,15 @@ struct NewGRFParametersWindow : public Window {
 		}
 
 		this->vscroll->SetCount(this->action14present ? this->grf_config->num_valid_params : this->grf_config->num_params);
-		if (this->clicked_row != UINT_MAX && this->clicked_row >= this->vscroll->GetCount()) {
-			this->clicked_row = UINT_MAX;
+		if (this->clicked_row != INT32_MAX && this->clicked_row >= this->vscroll->GetCount()) {
+			this->clicked_row = INT32_MAX;
 			this->CloseChildWindows(WC_QUERY_STRING);
 		}
 	}
 
 	/** When reset, unclick the button after a small timeout. */
 	TimeoutTimer<TimerWindow> unclick_timeout = {std::chrono::milliseconds(150), [this]() {
-		this->clicked_button = UINT_MAX;
+		this->clicked_button = INT32_MAX;
 		this->SetDirty();
 	}};
 };
diff --git a/src/order_gui.cpp b/src/order_gui.cpp
index 84d7f3e443..137fd1b76c 100644
--- a/src/order_gui.cpp
+++ b/src/order_gui.cpp
@@ -600,8 +600,8 @@ private:
 	 */
 	VehicleOrderID GetOrderFromPt(int y)
 	{
-		int sel = this->vscroll->GetScrolledRowFromWidget(y, this, WID_O_ORDER_LIST, WidgetDimensions::scaled.framerect.top);
-		if (sel == INT_MAX) return INVALID_VEH_ORDER_ID;
+		int32_t sel = this->vscroll->GetScrolledRowFromWidget(y, this, WID_O_ORDER_LIST, WidgetDimensions::scaled.framerect.top);
+		if (sel == INT32_MAX) return INVALID_VEH_ORDER_ID;
 		/* One past the orders is the 'End of Orders' line. */
 		assert(IsInsideBS(sel, 0, vehicle->GetNumOrders() + 1));
 		return sel;
diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp
index 4caed877d7..29f10e21f6 100644
--- a/src/settings_gui.cpp
+++ b/src/settings_gui.cpp
@@ -2560,8 +2560,8 @@ struct GameSettingsWindow : Window {
 
 		if (widget != WID_GS_OPTIONSPANEL) return;
 
-		uint btn = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GS_OPTIONSPANEL, WidgetDimensions::scaled.framerect.top);
-		if (btn == INT_MAX || (int)btn < this->warn_lines) return;
+		int32_t btn = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GS_OPTIONSPANEL, WidgetDimensions::scaled.framerect.top);
+		if (btn == INT32_MAX || btn < this->warn_lines) return;
 		btn -= this->warn_lines;
 
 		uint cur_row = 0;
diff --git a/src/story_gui.cpp b/src/story_gui.cpp
index 05d6bc44cc..f3ec75a53a 100644
--- a/src/story_gui.cpp
+++ b/src/story_gui.cpp
@@ -495,12 +495,12 @@ protected:
 	 * Get the total height of the content displayed in this window.
 	 * @return the height in pixels
 	 */
-	uint GetContentHeight()
+	int32_t GetContentHeight()
 	{
 		this->EnsureStoryPageElementLayout();
 
 		/* The largest bottom coordinate of any element is the height of the content */
-		uint max_y = std::accumulate(this->layout_cache.begin(), this->layout_cache.end(), 0, [](uint max_y, const LayoutCacheElement &ce) -> uint { return std::max<uint>(max_y, ce.bounds.bottom); });
+		int32_t max_y = std::accumulate(this->layout_cache.begin(), this->layout_cache.end(), 0, [](int32_t max_y, const LayoutCacheElement &ce) -> int32_t { return std::max<int32_t>(max_y, ce.bounds.bottom); });
 
 		return max_y;
 	}
diff --git a/src/timetable_gui.cpp b/src/timetable_gui.cpp
index 5f4f99758d..d55b7dc711 100644
--- a/src/timetable_gui.cpp
+++ b/src/timetable_gui.cpp
@@ -277,8 +277,8 @@ struct TimetableWindow : Window {
 
 	int GetOrderFromTimetableWndPt(int y, [[maybe_unused]] const Vehicle *v)
 	{
-		int sel = this->vscroll->GetScrolledRowFromWidget(y, this, WID_VT_TIMETABLE_PANEL, WidgetDimensions::scaled.framerect.top);
-		if (sel == INT_MAX) return INVALID_ORDER;
+		int32_t sel = this->vscroll->GetScrolledRowFromWidget(y, this, WID_VT_TIMETABLE_PANEL, WidgetDimensions::scaled.framerect.top);
+		if (sel == INT32_MAX) return INVALID_ORDER;
 		assert(IsInsideBS(sel, 0, v->GetNumOrders() * 2));
 		return sel;
 	}
diff --git a/src/widget.cpp b/src/widget.cpp
index 0e4f8cda46..dd91856b05 100644
--- a/src/widget.cpp
+++ b/src/widget.cpp
@@ -2262,13 +2262,13 @@ void NWidgetViewport::UpdateViewportCoordinates(Window *w)
  * @param widget      Widget number of the widget clicked in.
  * @param padding     Amount of empty space between the widget edge and the top of the first row. Default value is \c 0.
  * @param line_height Height of a single row. A negative value means using the vertical resize step of the widget.
- * @return Row number clicked at. If clicked at a wrong position, #INT_MAX is returned.
+ * @return Row number clicked at. If clicked at a wrong position, #Scrollbar::npos is returned.
  */
-int Scrollbar::GetScrolledRowFromWidget(int clickpos, const Window * const w, WidgetID widget, int padding, int line_height) const
+Scrollbar::size_type Scrollbar::GetScrolledRowFromWidget(int clickpos, const Window * const w, WidgetID widget, int padding, int line_height) const
 {
-	uint pos = w->GetRowFromWidget(clickpos, widget, padding, line_height);
+	int pos = w->GetRowFromWidget(clickpos, widget, padding, line_height);
 	if (pos != INT_MAX) pos += this->GetPosition();
-	return (pos >= this->GetCount()) ? INT_MAX : pos;
+	return (pos < 0 || pos >= this->GetCount()) ? Scrollbar::npos : pos;
 }
 
 /**
diff --git a/src/widget_type.h b/src/widget_type.h
index b7a8393fca..fad56477e3 100644
--- a/src/widget_type.h
+++ b/src/widget_type.h
@@ -678,12 +678,16 @@ public:
  * Scrollbar data structure
  */
 class Scrollbar {
+public:
+	using size_type = int32_t;
+	static constexpr size_type max_size_type = std::numeric_limits<size_type>::max();
+	static constexpr size_type npos = max_size_type;
 private:
 	const bool is_vertical; ///< Scrollbar has vertical orientation.
-	uint16_t count;           ///< Number of elements in the list.
-	uint16_t cap;             ///< Number of visible elements of the scroll bar.
-	uint16_t pos;             ///< Index of first visible item of the list.
-	uint16_t stepsize;        ///< Distance to scroll, when pressing the buttons or using the wheel.
+	size_type count; ///< Number of elements in the list.
+	size_type cap; ///< Number of visible elements of the scroll bar.
+	size_type pos; ///< Index of first visible item of the list.
+	size_type stepsize; ///< Distance to scroll, when pressing the buttons or using the wheel.
 
 public:
 	/** Stepping sizes when scrolling */
@@ -701,7 +705,7 @@ public:
 	 * Gets the number of elements in the list
 	 * @return the number of elements
 	 */
-	inline uint16_t GetCount() const
+	inline size_type GetCount() const
 	{
 		return this->count;
 	}
@@ -710,7 +714,7 @@ public:
 	 * Gets the number of visible elements of the scrollbar
 	 * @return the number of visible elements
 	 */
-	inline uint16_t GetCapacity() const
+	inline size_type GetCapacity() const
 	{
 		return this->cap;
 	}
@@ -719,7 +723,7 @@ public:
 	 * Gets the position of the first visible element in the list
 	 * @return the position of the element
 	 */
-	inline uint16_t GetPosition() const
+	inline size_type GetPosition() const
 	{
 		return this->pos;
 	}
@@ -729,7 +733,7 @@ public:
 	 * @param item to check
 	 * @return true iff the item is visible
 	 */
-	inline bool IsVisible(uint16_t item) const
+	inline bool IsVisible(size_type item) const
 	{
 		return IsInsideBS(item, this->GetPosition(), this->GetCapacity());
 	}
@@ -751,7 +755,7 @@ public:
 	{
 		assert(stepsize > 0);
 
-		this->stepsize = ClampTo<uint16_t>(stepsize);
+		this->stepsize = ClampTo<size_type>(stepsize);
 	}
 
 	/**
@@ -761,9 +765,9 @@ public:
 	 */
 	void SetCount(size_t num)
 	{
-		assert(num <= MAX_UVALUE(uint16_t));
+		assert(num < Scrollbar::max_size_type);
 
-		this->count = ClampTo<uint16_t>(num);
+		this->count = ClampTo<size_type>(num);
 		/* Ensure position is within bounds */
 		this->SetPosition(this->pos);
 	}
@@ -775,9 +779,9 @@ public:
 	 */
 	void SetCapacity(size_t capacity)
 	{
-		assert(capacity <= MAX_UVALUE(uint16_t));
+		assert(capacity < Scrollbar::max_size_type);
 
-		this->cap = ClampTo<uint16_t>(capacity);
+		this->cap = ClampTo<size_type>(capacity);
 		/* Ensure position is within bounds */
 		this->SetPosition(this->pos);
 	}
@@ -789,9 +793,9 @@ public:
 	 * @param position the position of the element
 	 * @return true iff the position has changed
 	 */
-	bool SetPosition(int position)
+	bool SetPosition(size_type position)
 	{
-		uint16_t old_pos = this->pos;
+		size_type old_pos = this->pos;
 		this->pos = Clamp(position, 0, std::max(this->count - this->cap, 0));
 		return this->pos != old_pos;
 	}
@@ -820,7 +824,7 @@ public:
 	 * the window depending on where in the list it was.
 	 * @param position the position to scroll towards.
 	 */
-	void ScrollTowards(int position)
+	void ScrollTowards(size_type position)
 	{
 		if (position < this->GetPosition()) {
 			/* scroll up to the item */
@@ -831,7 +835,7 @@ public:
 		}
 	}
 
-	int GetScrolledRowFromWidget(int clickpos, const Window * const w, WidgetID widget, int padding = 0, int line_height = -1) const;
+	size_type GetScrolledRowFromWidget(int clickpos, const Window * const w, WidgetID widget, int padding = 0, int line_height = -1) const;
 
 	/**
 	 * Get a pair of iterators for the range of visible elements in a container.
@@ -841,7 +845,7 @@ public:
 	template <typename Tcontainer>
 	auto GetVisibleRangeIterators(Tcontainer &container) const
 	{
-		assert(this->GetCount() == container.size()); // Scrollbar and container size must match.
+		assert((size_t)this->GetCount() == container.size()); // Scrollbar and container size must match.
 		auto first = std::next(std::begin(container), this->GetPosition());
 		auto last = std::next(first, std::min<size_t>(this->GetCapacity(), this->GetCount() - this->GetPosition()));
 		return std::make_pair(first, last);
@@ -860,9 +864,9 @@ public:
 	template <typename Tcontainer>
 	typename Tcontainer::iterator GetScrolledItemFromWidget(Tcontainer &container, int clickpos, const Window * const w, WidgetID widget, int padding = 0, int line_height = -1) const
 	{
-		assert(this->GetCount() == container.size()); // Scrollbar and container size must match.
-		int row = this->GetScrolledRowFromWidget(clickpos, w, widget, padding, line_height);
-		if (row == INT_MAX) return std::end(container);
+		assert((size_t)this->GetCount() == container.size()); // Scrollbar and container size must match.
+		size_type row = this->GetScrolledRowFromWidget(clickpos, w, widget, padding, line_height);
+		if (row == Scrollbar::npos) return std::end(container);
 
 		typename Tcontainer::iterator it = std::begin(container);
 		std::advance(it, row);