From b91ae2483e681cec65ae3e8f7dc57ebd47041b47 Mon Sep 17 00:00:00 2001 From: rubidium Date: Fri, 10 Dec 2010 17:47:11 +0000 Subject: (svn r21448) -Add: generic widget for creating a scrollable (one direction) and resizable (both directions) area with same sized widgets --- src/widget.cpp | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++++- src/widget_type.h | 44 +++++++++- 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 *
  • #NWidgetHorizontalLTR for organizing child widgets in a (horizontal) row, always in the same order. All childs below this container will also * never swap order. *
  • #NWidgetVertical for organizing child widgets underneath each other. + *
  • #NWidgetMatrix for organizing child widgets in a matrix form. *
  • #NWidgetBackground for adding a background behind its child widget. *
  • #NWidgetStacked for stacking child widgets on top of each other. * @@ -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(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(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 */ -- cgit v1.2.3-70-g09d2