summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/widget.cpp234
-rw-r--r--src/widget_type.h44
2 files changed, 275 insertions, 3 deletions
diff --git a/src/widget.cpp b/src/widget.cpp
index 59aff4fcc..45825db8f 100644
--- a/src/widget.cpp
+++ b/src/widget.cpp
@@ -591,6 +591,7 @@ void Window::DrawSortButtonState(int widget, SortButtonState state) const
* <li> #NWidgetHorizontalLTR for organizing child widgets in a (horizontal) row, always in the same order. All childs below this container will also
* never swap order.
* <li> #NWidgetVertical for organizing child widgets underneath each other.
+ * <li> #NWidgetMatrix for organizing child widgets in a matrix form.
* <li> #NWidgetBackground for adding a background behind its child widget.
* <li> #NWidgetStacked for stacking child widgets on top of each other.
* </ul>
@@ -1352,6 +1353,226 @@ NWidgetCore *NWidgetSpacer::GetWidgetFromPos(int x, int y)
return NULL;
}
+NWidgetMatrix::NWidgetMatrix() : NWidgetPIPContainer(NWID_MATRIX, NC_EQUALSIZE), index(-1), clicked(-1), count(-1)
+{
+}
+
+void NWidgetMatrix::SetIndex(int index)
+{
+ this->index = index;
+}
+
+void NWidgetMatrix::SetColour(Colours colour)
+{
+ this->colour = colour;
+}
+
+/**
+ * Sets the clicked widget in the matrix.
+ * @param clicked The clicked widget.
+ */
+void NWidgetMatrix::SetClicked(int clicked)
+{
+ this->clicked = clicked;
+}
+
+/**
+ * Set the number of elements in this matrix.
+ * @note Updates the number of elements/capacity of the real scrollbar.
+ * @param count The number of elements.
+ */
+void NWidgetMatrix::SetCount(int count)
+{
+ this->count = count;
+
+ if (this->sb == NULL || this->widgets_x == 0) return;
+
+ /* We need to get the number of pixels the matrix is high/wide.
+ * So, determine the number of rows/columns based on the number of
+ * columns/rows (one is constant/unscrollable).
+ * Then multiply that by the height of a widget, and add the pre
+ * and post spacing "offsets". */
+ count = CeilDiv(count, this->sb->IsVertical() ? this->widgets_x : this->widgets_y);
+ count *= (this->sb->IsVertical() ? this->head->smallest_y : this->head->smallest_x) + this->pip_inter;
+ count += -this->pip_inter + this->pip_pre + this->pip_post; // We counted an inter too much in the multiplication above
+ this->sb->SetCount(count);
+ this->sb->SetCapacity(this->sb->IsVertical() ? this->current_y : this->current_x);
+}
+
+/**
+ * Assign a scrollbar to this matrix.
+ * @param sb The scrollbar to assign to us.
+ */
+void NWidgetMatrix::SetScrollbar(Scrollbar *sb)
+{
+ this->sb = sb;
+}
+
+void NWidgetMatrix::SetupSmallestSize(Window *w, bool init_array)
+{
+ assert(this->head != NULL);
+ assert(this->head->next == NULL);
+
+ if (this->index >= 0 && init_array) { // Fill w->nested_array[]
+ assert(w->nested_array_size > (uint)this->index);
+ w->nested_array[this->index] = this;
+ }
+
+ this->head->SetupSmallestSize(w, init_array);
+
+ Dimension padding = {this->pip_pre + this->pip_post, this->pip_pre + this->pip_post};
+ Dimension size = {this->head->smallest_x + padding.width, this->head->smallest_y + padding.height};
+ Dimension fill = {0, 0};
+ Dimension resize = {this->pip_inter + this->head->smallest_x, this->pip_inter + this->head->smallest_y};
+
+ if (this->index >= 0) w->UpdateWidgetSize(this->index, &size, padding, &fill, &resize);
+
+ this->smallest_x = size.width;
+ this->smallest_y = size.height;
+ this->fill_x = fill.width;
+ this->fill_y = fill.height;
+ this->resize_x = resize.width;
+ this->resize_y = resize.height;
+}
+
+void NWidgetMatrix::AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl)
+{
+ assert(given_width >= this->smallest_x && given_height >= this->smallest_y);
+
+ this->pos_x = x;
+ this->pos_y = y;
+ this->current_x = given_width;
+ this->current_y = given_height;
+
+ /* Determine the size of the widgets, and the number of visible widgets on each of the axis. */
+ this->widget_w = this->head->smallest_x + this->pip_inter;
+ this->widget_h = this->head->smallest_y + this->pip_inter;
+
+ /* Account for the pip_inter is between widgets, so we need to account for that when
+ * the division assumes pip_inter is used for all widgets. */
+ this->widgets_x = CeilDiv(this->current_x - this->pip_pre - this->pip_post + this->pip_inter, this->widget_w);
+ this->widgets_y = CeilDiv(this->current_y - this->pip_pre - this->pip_post + this->pip_inter, this->widget_h);
+
+ /* When resizing, update the scrollbar's count. E.g. with a vertical
+ * scrollbar becoming wider or narrower means the amount of rows in
+ * the scrollbar becomes respectively smaller or higher. */
+ if (sizing == ST_RESIZE) {
+ this->SetCount(this->count);
+ }
+}
+
+void NWidgetMatrix::FillNestedArray(NWidgetBase **array, uint length)
+{
+ if (this->index >= 0 && (uint)(this->index) < length) array[this->index] = this;
+ NWidgetContainer::FillNestedArray(array, length);
+}
+
+NWidgetCore *NWidgetMatrix::GetWidgetFromPos(int x, int y)
+{
+ /* Falls outside of the matrix widget. */
+ if (!IsInsideBS(x, this->pos_x, this->current_x) || !IsInsideBS(y, this->pos_y, this->current_y)) return NULL;
+
+ int start_x, start_y, base_offs_x, base_offs_y;
+ this->GetScrollOffsets(start_x, start_y, base_offs_x, base_offs_y);
+
+ /* Swap the x offset around for RTL, so it'll behave like LTR for RTL as well. */
+ bool rtl = _current_text_dir == TD_RTL;
+ if (rtl) base_offs_x -= (this->widgets_x - 1) * this->widget_w;
+
+ int widget_col = (x - base_offs_x - (int)this->pip_pre - (int)this->pos_x) / this->widget_w;
+ int widget_row = (y - base_offs_y - (int)this->pip_pre - (int)this->pos_y) / this->widget_h;
+
+ if (widget_row * this->widgets_x + widget_col >= this->count) return NULL;
+
+ NWidgetCore *child = dynamic_cast<NWidgetCore *>(this->head);
+ child->AssignSizePosition(ST_RESIZE,
+ this->pos_x + this->pip_pre + widget_col * this->widget_w + base_offs_x,
+ this->pos_y + this->pip_pre + widget_row * this->widget_h + base_offs_y,
+ child->smallest_x, child->smallest_y, rtl);
+
+ /* "Undo" the RTL swap here to get the right widget index. */
+ if (rtl) widget_col = this->widgets_x - widget_col - 1;
+ SB(child->index, 16, 16, (widget_row + start_y) * this->widgets_x + widget_col + start_x);
+
+ return child->GetWidgetFromPos(x, y);
+}
+
+/* virtual */ void NWidgetMatrix::Draw(const Window *w)
+{
+ /* Fill the background. */
+ 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. */
+ DrawPixelInfo tmp_dpi;
+ if (!FillDrawPixelInfo(&tmp_dpi, this->pos_x + 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;
+ DrawPixelInfo *old_dpi = _cur_dpi;
+ _cur_dpi = &tmp_dpi;
+
+ /* Get the appropriate offsets so we can draw the right widgets. */
+ NWidgetCore *child = dynamic_cast<NWidgetCore *>(this->head);
+ bool rtl = _current_text_dir == TD_RTL;
+ 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) {
+ /* 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);
+ }
+ }
+
+ /* Restore the clipping area. */
+ _cur_dpi = old_dpi;
+}
+
+/**
+ * Get the different offsets that are influenced by scrolling.
+ * @param [out] start_x The start position in columns,
+ * @param [out] start_y The start position in rows.
+ * @param [out] base_offs_x The base horizontal offset in pixels.
+ * @param [out] base_offs_y The base vertical offset in pixels.
+ */
+void NWidgetMatrix::GetScrollOffsets(int &start_x, int &start_y, int &base_offs_x, int &base_offs_y)
+{
+ base_offs_x = 0;
+ base_offs_y = 0;
+ start_x = 0;
+ start_y = 0;
+ if (this->sb != NULL) {
+ if (this->sb->IsVertical()) {
+ start_y = this->sb->GetPosition() / this->widget_h;
+ base_offs_y = -this->sb->GetPosition() + start_y * this->widget_h;
+ if (_current_text_dir == TD_RTL) base_offs_x = this->pip_pre + this->widget_w * (this->widgets_x - 1) - this->pip_inter;
+ } else {
+ start_x = this->sb->GetPosition() / this->widget_w;
+ if (_current_text_dir == TD_RTL) {
+ base_offs_x = this->sb->GetCapacity() + this->sb->GetPosition() - (start_x + 1) * this->widget_w + this->pip_inter - this->pip_post - this->pip_pre;
+ } else {
+ base_offs_x = -this->sb->GetPosition() + start_x * this->widget_w;
+ }
+ }
+ }
+}
+
/**
* Constructor parent nested widgets.
* @param tp Type of parent widget.
@@ -2165,6 +2386,17 @@ static int MakeNWidget(const NWidgetPart *parts, int count, NWidgetBase **dest,
*fill_dest = true;
break;
+ case NWID_MATRIX: {
+ if (*dest != NULL) return num_used;
+ NWidgetMatrix *nwm = new NWidgetMatrix();
+ *dest = nwm;
+ *fill_dest = true;
+ nwm->SetIndex(parts->u.widget.index);
+ nwm->SetColour(parts->u.widget.colour);
+ *biggest_index = max(*biggest_index, (int)parts->u.widget.index);
+ break;
+ }
+
case WPT_FUNCTION: {
if (*dest != NULL) return num_used;
/* Ensure proper functioning even when the called code simply writes its largest index. */
@@ -2308,7 +2540,7 @@ static int MakeWidgetTree(const NWidgetPart *parts, int count, NWidgetBase **par
/* If sub-widget is a container, recursively fill that container. */
WidgetType tp = sub_widget->type;
- if (fill_sub && (tp == NWID_HORIZONTAL || tp == NWID_HORIZONTAL_LTR || tp == NWID_VERTICAL
+ if (fill_sub && (tp == NWID_HORIZONTAL || tp == NWID_HORIZONTAL_LTR || tp == NWID_VERTICAL || tp == NWID_MATRIX
|| tp == WWT_PANEL || tp == WWT_FRAME || tp == WWT_INSET || tp == NWID_SELECTION)) {
NWidgetBase *sub_ptr = sub_widget;
int num_used = MakeWidgetTree(parts, count - total_used, &sub_ptr, biggest_index);
diff --git a/src/widget_type.h b/src/widget_type.h
index 54183b0d0..5850443e1 100644
--- a/src/widget_type.h
+++ b/src/widget_type.h
@@ -73,6 +73,7 @@ enum WidgetType {
NWID_HORIZONTAL, ///< Horizontal container.
NWID_HORIZONTAL_LTR, ///< Horizontal container that doesn't change the order of the widgets for RTL languages.
NWID_VERTICAL, ///< Vertical container.
+ NWID_MATRIX, ///< Matrix container.
NWID_SPACER, ///< Invisible widget that takes some space.
NWID_SELECTION, ///< Stacked widgets, only one visible at a time (eg in a panel with tabs).
NWID_VIEWPORT, ///< Nested widget containing a viewport.
@@ -445,6 +446,45 @@ public:
void AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl);
};
+/**
+ * Matrix container with implicitly equal sized (virtual) sub-widgets.
+ * This widget must have exactly one sub-widget. After that this sub-widget
+ * is used to draw all of the data within the matrix piece by piece.
+ * DrawWidget and OnClick calls will be done to that sub-widget, where the
+ * 16 high bits are used to encode the index into the matrix.
+ * @ingroup NestedWidgets
+ */
+class NWidgetMatrix : public NWidgetPIPContainer {
+public:
+ NWidgetMatrix();
+
+ void SetIndex(int index);
+ void SetColour(Colours colour);
+ void SetClicked(int clicked);
+ void SetCount(int count);
+ void SetScrollbar(Scrollbar *sb);
+
+ void SetupSmallestSize(Window *w, bool init_array);
+ void AssignSizePosition(SizingType sizing, uint x, uint y, uint given_width, uint given_height, bool rtl);
+ /* virtual */ void FillNestedArray(NWidgetBase **array, uint length);
+
+ /* virtual */ NWidgetCore *GetWidgetFromPos(int x, int y);
+ /* virtual */ void Draw(const Window *w);
+protected:
+ int index; ///< If non-negative, index in the #Window::nested_array.
+ Colours colour; ///< Colour of this widget.
+ int clicked; ///< The currently clicked widget.
+ int count; ///< Amount of valid widgets.
+ Scrollbar *sb; ///< The scrollbar we're associated with.
+private:
+ int widget_w; ///< The width of the child widget including inter spacing.
+ int widget_h; ///< The height of the child widget including inter spacing.
+ int widgets_x; ///< The number of visible widgets in horizontal direction.
+ int widgets_y; ///< The number of visible widgets in vertical direction.
+
+ void GetScrollOffsets(int &start_x, int &start_y, int &base_offs_x, int &base_offs_y);
+};
+
/**
* Spacer widget.
@@ -714,7 +754,7 @@ static FORCEINLINE uint ComputeMaxSize(uint base, uint max_space, uint step)
* - #SetResize Define how the widget may resize.
* - #SetPadding Create additional space around the widget.
*
- * - Container widgets #NWidgetHorizontal, #NWidgetHorizontalLTR, and #NWidgetVertical, start with a #NWidget(WidgetType tp) part.
+ * - Container widgets #NWidgetHorizontal, #NWidgetHorizontalLTR, #NWidgetVertical, and #NWidgetMatrix, start with a #NWidget(WidgetType tp) part.
* Their properties are derived from the child widgets so they cannot be specified.
* You can however use
* - #SetPadding Define additional padding around the container.
@@ -1001,7 +1041,7 @@ static inline NWidgetPart NWidget(WidgetType tp, Colours col, int16 idx = -1)
/**
* Widget part function for starting a new horizontal container, vertical container, or spacer widget.
- * @param tp Type of the new nested widget, #NWID_HORIZONTAL(_LTR), #NWID_VERTICAL, #NWID_SPACER, or #NWID_SELECTION.
+ * @param tp Type of the new nested widget, #NWID_HORIZONTAL(_LTR), #NWID_VERTICAL, #NWID_SPACER, #NWID_SELECTION, and #NWID_MATRIX.
* @param cont_flags Flags for the containers (#NWID_HORIZONTAL(_LTR) and #NWID_VERTICAL).
* @ingroup NestedWidgetParts
*/