changeset 12102:a381834fd694 draft

(svn r16515) -Codechange: Added scrollbar handling for nested widgets, and finding widgets by type or position in the tree.
author alberth <alberth@openttd.org>
date Thu, 04 Jun 2009 12:46:37 +0000
parents dc6c0caea002
children eecff8281292
files src/widget.cpp src/widget_type.h src/window.cpp
diffstat 3 files changed, 241 insertions(+), 38 deletions(-) [+]
line wrap: on
line diff
--- a/src/widget.cpp
+++ b/src/widget.cpp
@@ -47,25 +47,25 @@
 	return pt;
 }
 
-/** Special handling for the scrollbar widget type.
- * Handles the special scrolling buttons and other
- * scrolling.
- * @param w Window on which a scroll was performed.
- * @param wi Pointer to the scrollbar widget.
- * @param x The X coordinate of the mouse click.
- * @param y The Y coordinate of the mouse click. */
-void ScrollbarClickHandler(Window *w, const Widget *wi, int x, int y)
+/**
+ * Compute new position of the scrollbar after a click and updates the window flags.
+ * @param w   Window on which a scroll was performed.
+ * @param wtp Scrollbar widget type.
+ * @param mi  Minimum coordinate of the scroll bar.
+ * @param ma  Maximum coordinate of the scroll bar.
+ * @param x   The X coordinate of the mouse click.
+ * @param y   The Y coordinate of the mouse click.
+ */
+static void ScrollbarClickPositioning(Window *w, WidgetType wtp, int x, int y, int mi, int ma)
 {
-	int mi, ma, pos;
+	int pos;
 	Scrollbar *sb;
 
-	switch (wi->type) {
+	switch (wtp) {
 		case WWT_SCROLLBAR:
 			/* vertical scroller */
 			w->flags4 &= ~WF_HSCROLL;
 			w->flags4 &= ~WF_SCROLL2;
-			mi = wi->top;
-			ma = wi->bottom;
 			pos = y;
 			sb = &w->vscroll;
 			break;
@@ -74,8 +74,6 @@
 			/* 2nd vertical scroller */
 			w->flags4 &= ~WF_HSCROLL;
 			w->flags4 |= WF_SCROLL2;
-			mi = wi->top;
-			ma = wi->bottom;
 			pos = y;
 			sb = &w->vscroll2;
 			break;
@@ -84,8 +82,6 @@
 			/* horizontal scroller */
 			w->flags4 &= ~WF_SCROLL2;
 			w->flags4 |= WF_HSCROLL;
-			mi = wi->left;
-			ma = wi->right;
 			pos = x;
 			sb = &w->hscroll;
 			break;
@@ -106,8 +102,7 @@
 
 		if (_scroller_click_timeout == 0) {
 			_scroller_click_timeout = 6;
-			if ((byte)(sb->pos + sb->cap) < sb->count)
-				sb->pos++;
+			if (sb->pos + sb->cap < sb->count) sb->pos++;
 		}
 		_left_button_clicked = false;
 	} else {
@@ -116,10 +111,7 @@
 		if (pos < pt.x) {
 			sb->pos = max(sb->pos - sb->cap, 0);
 		} else if (pos > pt.y) {
-			sb->pos = min(
-				sb->pos + sb->cap,
-				max(sb->count - sb->cap, 0)
-			);
+			sb->pos = min(sb->pos + sb->cap, max(sb->count - sb->cap, 0));
 		} else {
 			_scrollbar_start_pos = pt.x - mi - 9;
 			_scrollbar_size = ma - mi - 23;
@@ -132,6 +124,76 @@
 	w->SetDirty();
 }
 
+/** Special handling for the scrollbar widget type.
+ * Handles the special scrolling buttons and other scrolling.
+ * @param w  Window on which a scroll was performed.
+ * @param wi Pointer to the scrollbar widget.
+ * @param x  The X coordinate of the mouse click.
+ * @param y  The Y coordinate of the mouse click.
+ */
+void ScrollbarClickHandler(Window *w, const Widget *wi, int x, int y)
+{
+	int mi, ma;
+
+	switch (wi->type) {
+		case WWT_SCROLLBAR:
+			/* vertical scroller */
+			mi = wi->top;
+			ma = wi->bottom;
+			break;
+
+		case WWT_SCROLL2BAR:
+			/* 2nd vertical scroller */
+			mi = wi->top;
+			ma = wi->bottom;
+			break;
+
+		case  WWT_HSCROLLBAR:
+			/* horizontal scroller */
+			mi = wi->left;
+			ma = wi->right;
+			break;
+
+		default: NOT_REACHED();
+	}
+	ScrollbarClickPositioning(w, wi->type, x, y, mi, ma);
+}
+
+/** Special handling for the scrollbar widget type.
+ * Handles the special scrolling buttons and other scrolling.
+ * @param w Window on which a scroll was performed.
+ * @param nw Pointer to the scrollbar widget.
+ * @param x The X coordinate of the mouse click.
+ * @param y The Y coordinate of the mouse click.
+ */
+void ScrollbarClickHandler(Window *w, const NWidgetCore *nw, int x, int y)
+{
+	int mi, ma;
+
+	switch (nw->type) {
+		case WWT_SCROLLBAR:
+			/* vertical scroller */
+			mi = nw->pos_y;
+			ma = nw->pos_y + nw->current_y;
+			break;
+
+		case WWT_SCROLL2BAR:
+			/* 2nd vertical scroller */
+			mi = nw->pos_y;
+			ma = nw->pos_y + nw->current_y;
+			break;
+
+		case  WWT_HSCROLLBAR:
+			/* horizontal scroller */
+			mi = nw->pos_x;
+			ma = nw->pos_x + nw->current_x;
+			break;
+
+		default: NOT_REACHED();
+	}
+	ScrollbarClickPositioning(w, nw->type, x, y, mi, ma);
+}
+
 /** Returns the index for the widget located at the given position
  * relative to the window. It includes all widget-corner pixels as well.
  * @param *w Window to look inside
@@ -141,8 +203,12 @@
  */
 int GetWidgetFromPos(const Window *w, int x, int y)
 {
+	if (w->nested_root != NULL) {
+		NWidgetCore *nw = w->nested_root->GetWidgetFromPos(x, y);
+		return (nw != NULL) ? nw->index : -1;
+	}
+
 	int found_index = -1;
-
 	/* Go through the widgets and check if we find the widget that the coordinate is inside. */
 	for (uint index = 0; index < w->widget_count; index++) {
 		const Widget *wi = &w->widget[index];
@@ -913,6 +979,21 @@
 }
 
 /**
+ * @fn NWidgetCore *GetWidgetFromPos(int x, int y)
+ * Retrieve a widget by its position.
+ * @param x Horizontal position relative to the left edge of the window.
+ * @param y Vertical position relative to the top edge of the window.
+ * @return Returns the deepest nested widget that covers the given position, or \c NULL if no widget can be found.
+ */
+
+/**
+ * @fn NWidgetBase *GetWidgetOfType(WidgetType tp)
+ * Retrieve a widget by its type.
+ * @param tp Widget type to search for.
+ * @return Returns the first widget of the specified type, or \c NULL if no widget can be found.
+ */
+
+/**
  * Constructor for resizable nested widgets.
  * @param tp     Nested widget type.
  * @param fill_x Allow horizontal filling from initial size.
@@ -1048,6 +1129,18 @@
 }
 
 /**
+ * @fn Scrollbar *NWidgetCore::FindScrollbar(Window *w, bool allow_next = true)
+ * Find the scrollbar of the widget through the Window::nested_array.
+ * @param w          Window containing the widgets and the scrollbar,
+ * @param allow_next Search may be extended to the next widget.
+ *
+ * @todo This implementation uses the constraint that a scrollbar must be the next item in the #Window::nested_array, and the scrollbar
+ *       data is stored in the #Window structure (#Window::vscroll, #Window::vscroll2, and #Window::hscroll).
+ *       Alternative light-weight implementations may be considered, eg by sub-classing a canvas-like widget, and/or by having
+ *       an explicit link between the scrollbar and the widget being scrolled.
+ */
+
+/**
  * Constructor container baseclass.
  * @param tp Type of the container.
  */
@@ -1067,6 +1160,16 @@
 	this->tail = NULL;
 }
 
+NWidgetBase *NWidgetContainer::GetWidgetOfType(WidgetType tp)
+{
+	if (this->type == tp) return this;
+	for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
+		NWidgetBase *nwid = child_wid->GetWidgetOfType(tp);
+		if (nwid != NULL) return nwid;
+	}
+	return NULL;
+}
+
 /**
  * Append widget \a wid to container.
  * @param wid Widget to append.
@@ -1190,6 +1293,16 @@
 	}
 }
 
+NWidgetCore *NWidgetStacked::GetWidgetFromPos(int x, int y)
+{
+	if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return NULL;
+	for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
+		NWidgetCore *nwid = child_wid->GetWidgetFromPos(x, y);
+		if (nwid != NULL) return nwid;
+	}
+	return NULL;
+}
+
 NWidgetPIPContainer::NWidgetPIPContainer(WidgetType tp) : NWidgetContainer(tp)
 {
 }
@@ -1217,6 +1330,17 @@
 	}
 }
 
+NWidgetCore *NWidgetPIPContainer::GetWidgetFromPos(int x, int y)
+{
+	if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return NULL;
+
+	for (NWidgetBase *child_wid = this->head; child_wid != NULL; child_wid = child_wid->next) {
+		NWidgetCore *nwid = child_wid->GetWidgetFromPos(x, y);
+		if (nwid != NULL) return nwid;
+	}
+	return NULL;
+}
+
 /** Horizontal container widget. */
 NWidgetHorizontal::NWidgetHorizontal() : NWidgetPIPContainer(NWID_HORIZONTAL)
 {
@@ -1503,6 +1627,16 @@
 	/* Spacer widget never need repainting. */
 }
 
+NWidgetCore *NWidgetSpacer::GetWidgetFromPos(int x, int y)
+{
+	return NULL;
+}
+
+NWidgetBase *NWidgetSpacer::GetWidgetOfType(WidgetType tp)
+{
+	return (this->type == tp) ? this : NULL;
+}
+
 /**
  * Constructor parent nested widgets.
  * @param tp     Type of parent widget.
@@ -1641,6 +1775,33 @@
 	}
 }
 
+NWidgetCore *NWidgetBackground::GetWidgetFromPos(int x, int y)
+{
+	NWidgetCore *nwid = NULL;
+	if (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) {
+		if (this->child != NULL) nwid = this->child->GetWidgetFromPos(x, y);
+		if (nwid == NULL) nwid = this;
+	}
+	return nwid;
+}
+
+Scrollbar *NWidgetBackground::FindScrollbar(Window *w, bool allow_next)
+{
+	if (this->index > 0 && allow_next && this->child == NULL && (uint)(this->index) + 1 < w->nested_array_size) {
+		NWidgetCore *next_wid = w->nested_array[this->index + 1];
+		if (next_wid != NULL) return next_wid->FindScrollbar(w, false);
+	}
+	return NULL;
+}
+
+NWidgetBase *NWidgetBackground::GetWidgetOfType(WidgetType tp)
+{
+	NWidgetBase *nwid = NULL;
+	if (this->child != NULL) nwid = this->child->GetWidgetOfType(tp);
+	if (nwid == NULL && this->type == tp) nwid = this;
+	return nwid;
+}
+
 /**
  * Nested leaf widget.
  * @param tp     Type of leaf widget.
@@ -1839,6 +2000,28 @@
 	NWidgetBase::Invalidate(w);
 }
 
+NWidgetCore *NWidgetLeaf::GetWidgetFromPos(int x, int y)
+{
+	return (IsInsideBS(x, this->pos_x, this->current_x) && IsInsideBS(y, this->pos_y, this->current_y)) ? this : NULL;
+}
+
+Scrollbar *NWidgetLeaf::FindScrollbar(Window *w, bool allow_next)
+{
+	if (this->type == WWT_SCROLLBAR) return &w->vscroll;
+	if (this->type == WWT_SCROLL2BAR) return &w->vscroll2;
+
+	if (this->index > 0 && allow_next && (uint)(this->index) + 1 < w->nested_array_size) {
+		NWidgetCore *next_wid = w->nested_array[this->index + 1];
+		if (next_wid != NULL) return next_wid->FindScrollbar(w, false);
+	}
+	return NULL;
+}
+
+NWidgetBase *NWidgetLeaf::GetWidgetOfType(WidgetType tp)
+{
+	return (this->type == tp) ? this : NULL;
+}
+
 /**
  * Intialize nested widget tree and convert to widget array.
  * @param nwid Nested widget tree.
--- a/src/widget_type.h
+++ b/src/widget_type.h
@@ -153,6 +153,7 @@
 
 /* Forward declarations. */
 class NWidgetCore;
+struct Scrollbar;
 
 /**
  * Baseclass for nested widgets.
@@ -170,6 +171,9 @@
 	virtual void StoreWidgets(Widget *widgets, int length, bool left_moving, bool top_moving, bool rtl) = 0;
 	virtual void FillNestedArray(NWidgetCore **array, uint length) = 0;
 
+	virtual NWidgetCore *GetWidgetFromPos(int x, int y) = 0;
+	virtual NWidgetBase *GetWidgetOfType(WidgetType tp) = 0;
+
 	/**
 	 * Set additional space (padding) around the widget.
 	 * @param top    Amount of additional space above the widget.
@@ -284,6 +288,8 @@
 	void StoreWidgets(Widget *widgets, int length, bool left_moving, bool top_moving, bool rtl);
 	/* virtual */ void FillNestedArray(NWidgetCore **array, uint length);
 
+	virtual Scrollbar *FindScrollbar(Window *w, bool allow_next = true) = 0;
+
 	NWidgetDisplay disp_flags; ///< Flags that affect display and interaction with the widget.
 	Colours colour;            ///< Colour of this widget.
 	int index;                 ///< Index of the nested widget in the widget array of the window (\c -1 means 'not used').
@@ -335,6 +341,8 @@
 	/** Return whether the container is empty. */
 	inline bool IsEmpty() { return head == NULL; };
 
+	/* virtual */ NWidgetBase *GetWidgetOfType(WidgetType tp);
+
 protected:
 	NWidgetBase *head; ///< Pointer to first widget in container.
 	NWidgetBase *tail; ///< Pointer to last widget in container.
@@ -352,6 +360,7 @@
 	void StoreWidgets(Widget *widgets, int length, bool left_moving, bool top_moving, bool rtl);
 
 	/* virtual */ void Draw(const Window *w);
+	/* virtual */ NWidgetCore *GetWidgetFromPos(int x, int y);
 };
 
 /** Container with pre/inter/post child space. */
@@ -362,6 +371,8 @@
 	void SetPIP(uint8 pip_pre, uint8 pip_inter, uint8 pip_post);
 
 	/* virtual */ void Draw(const Window *w);
+	/* virtual */ NWidgetCore *GetWidgetFromPos(int x, int y);
+
 protected:
 	uint8 pip_pre;     ///< Amount of space before first widget.
 	uint8 pip_inter;   ///< Amount of space between widgets.
@@ -416,6 +427,8 @@
 
 	/* virtual */ void Draw(const Window *w);
 	/* virtual */ void Invalidate(const Window *w) const;
+	/* virtual */ NWidgetCore *GetWidgetFromPos(int x, int y);
+	/* virtual */ NWidgetBase *GetWidgetOfType(WidgetType tp);
 };
 
 /** Nested widget with a child.
@@ -435,6 +448,9 @@
 	/* virtual */ void FillNestedArray(NWidgetCore **array, uint length);
 
 	/* virtual */ void Draw(const Window *w);
+	/* virtual */ NWidgetCore *GetWidgetFromPos(int x, int y);
+	/* virtual */ NWidgetBase *GetWidgetOfType(WidgetType tp);
+	/* virtual */ Scrollbar *FindScrollbar(Window *w, bool allow_next = true);
 
 private:
 	NWidgetPIPContainer *child; ///< Child widget.
@@ -448,6 +464,9 @@
 
 	/* virtual */ void Draw(const Window *w);
 	/* virtual */ void Invalidate(const Window *w) const;
+	/* virtual */ NWidgetCore *GetWidgetFromPos(int x, int y);
+	/* virtual */ NWidgetBase *GetWidgetOfType(WidgetType tp);
+	/* virtual */ Scrollbar *FindScrollbar(Window *w, bool allow_next = true);
 };
 
 Widget *InitializeNWidgets(NWidgetBase *nwid, bool rtl = false);
--- a/src/window.cpp
+++ b/src/window.cpp
@@ -399,23 +399,24 @@
 {
 	if (widget < 0) return;
 
-	const Widget *wi1 = &w->widget[widget];
-	const Widget *wi2 = &w->widget[widget + 1];
+	Scrollbar *sb = NULL;
+	if (w->widget != NULL) {
+		const Widget *wi1 = &w->widget[widget];
+		const Widget *wi2 = &w->widget[widget + 1];
+		if (wi1->type == WWT_SCROLLBAR || wi2->type == WWT_SCROLLBAR) {
+			sb = &w->vscroll;
+		} else if (wi1->type == WWT_SCROLL2BAR || wi2->type == WWT_SCROLL2BAR) {
+			sb = &w->vscroll2;
+		}
+	}
 
-	/* The listbox can only scroll if scrolling was done on the scrollbar itself,
-	 * or on the listbox (and the next item is (must be) the scrollbar)
-	 * XXX - should be rewritten as a widget-dependent scroller but that's
-	 * not happening until someone rewrites the whole widget-code */
-	Scrollbar *sb;
-	if ((sb = &w->vscroll,  wi1->type == WWT_SCROLLBAR)  || (sb = &w->vscroll2, wi1->type == WWT_SCROLL2BAR)  ||
-			(sb = &w->vscroll2, wi2->type == WWT_SCROLL2BAR) || (sb = &w->vscroll, wi2->type == WWT_SCROLLBAR) ) {
+	if (w->nested_array != NULL && (uint)widget < w->nested_array_size) sb = w->nested_array[widget]->FindScrollbar(w);
 
-		if (sb->count > sb->cap) {
-			int pos = Clamp(sb->pos + wheel, 0, sb->count - sb->cap);
-			if (pos != sb->pos) {
-				sb->pos = pos;
-				w->SetDirty();
-			}
+	if (sb != NULL && sb->count > sb->cap) {
+		int pos = Clamp(sb->pos + wheel, 0, sb->count - sb->cap);
+		if (pos != sb->pos) {
+			sb->pos = pos;
+			w->SetDirty();
 		}
 	}
 }