diff -r 258ca04a6c17 src/widget.cpp --- a/src/widget.cpp Fri Mar 06 21:07:01 2009 +0000 +++ b/src/widget.cpp Sat Mar 07 18:33:53 2009 +0100 @@ -623,3 +623,765 @@ int offset = this->IsWidgetLowered(widget) ? 1 : 0; DoDrawString(state == SBS_DOWN ? DOWNARROW : UPARROW, this->widget[widget].right - 11 + offset, this->widget[widget].top + 1 + offset, TC_BLACK); } + + +/* == Nested widgets == */ + +/** + * Base class constructor. + * @param tp Nested widget type. + */ +NWidgetBase::NWidgetBase(byte tp): ZeroedMemoryAllocator() +{ + this->type = tp; + this->min_x = 0; + this->min_y = 0; + this->fill_x = false; + this->fill_y = false; + this->resize_x = 0; + this->resize_y = 0; + this->pos_x = 0; + this->pos_y = 0; + + this->next = NULL; + this->prev = NULL; +} + +NWidgetBase::~NWidgetBase() +{ + /* ~NWidgetContainer() takes care of #next data member. */ +} + +/** + * @fn int NWidgetBase::ComputeMinimalSize() + * @brief Compute minimal size needed by the widget. + * + * The minimal size of a widget is the smallest size that a widget needs to + * display itself properly. + * In addition, filling and resizing of the widget are computed. + * @return Biggest index in the widget array of all child widgets. + * + * @note After the computation, the results can be queried by accessing the data members of the widget. + */ + +/** + * @fn void NWidgetBase::AssignPosition(int x, int y, int width, int height, bool allow_rx, bool allow_ry, bool rtl) + * @brief Assign a position to the widget. + * @param x Horizontal offset of the widget relative to the left edge of the window. + * @param y Vertical offset of the widget relative to the top edge of the window. + * @param width Width allocated to the widget. + * @param height Height allocated to the widget. + * @param allow_rx Horizontal resizing is allowed. + * @param allow_ry Vertical resizing is allowed. + * @param rtl Adapt for right-to-left languages (position contents of horizontal containers backwards). + */ + +/** + * @fn void NWidgetBase::StoreWidgets(Widget *widgets, int length, bool left_moving, bool top_moving, bool rtl) + * @brief Store all child widgets with a valid index into the widget array. + * @param widgets Widget array to store the nested widgets in. + * @param length Length of the array. + * @param left_moving Left edge of the widget may move due to resizing (right edge if \a rtl). + * @param top_moving Top edge of the widget may move due to reisizing. + * @param rtl Adapt for right-to-left languages (position contents of horizontal containers backwards). + * + * @note When storing a nested widget, the type in the \a widgets array should be #WWT_LAST. + * This is used to detect double widget allocations as well as holes in the widget array. + */ + +/** + * Constructor for resizable nested widgets. + * @param tp Nested widget type. + * @param fx Allow horizontal filling from initial size. + * @param fy Allow vertical filling from initial size. + */ +NWidgetResizeBase::NWidgetResizeBase(byte tp, bool dfx, bool dfy): NWidgetBase(tp) +{ + this->min_x = 0; + this->min_y = 0; + this->fill_x = dfx; + this->fill_y = dfy; + this->resize_x = 0; + this->resize_y = 0; + this->pos_x = 0; + this->pos_y = 0; +} + +/** + * Set minimal size of the widget. + * @param msX Horizontal minimal size of the widget (\c -1 means 'not set'). + * @param msY Vertical minimal size of the widget (\c -1 means 'not set'). + */ +void NWidgetResizeBase::SetMinimalSize(int mx, int my) +{ + assert(mx >= 0 && my >= 0); + this->min_x = mx; + this->min_y = my; +} + +/** + * Set the filling of the widget from initial size. + * @param fx Allow horizontal filling from initial size. + * @param fy Allow vertical filling from initial size. + */ +void NWidgetResizeBase::SetFill(bool fx, bool fy) +{ + this->fill_x = fx; + this->fill_y = fy; +} + +/** + * Set resize step of the widget. + * @param rx Resize step in horizontal direction. + * @param ry Resize step in vertical direction. + */ +void NWidgetResizeBase::SetResize(int rx, int ry) +{ + assert(rx >= 0 && ry >= 0); + this->resize_x = rx; + this->resize_y = ry; +} + +void NWidgetResizeBase::AssignPosition(int x, int y, int given_width, int given_height, bool allow_rx, bool allow_ry, bool rtl) +{ + assert(x >= 0 && y >= 0); + assert(given_width >= 0 && given_height >= 0); + this->pos_x = x; + this->pos_y = y; + this->min_x = given_width; + this->min_y = given_height; + if (!allow_rx) this->resize_x = 0; + if (!allow_ry) this->resize_y = 0; +} + +/** + * Initialization of a 'real' widget. + * @param tp Type of the widget. + * @param col Colour of the widget. + * @param dfx Default horizontal filling. + * @param dfy Default vertical filling. + * @param idx Index into the widget array. + */ +NWidgetCore::NWidgetCore(byte tp, Colours col, bool dfx, bool dfy, uint16 data, StringID tip): NWidgetResizeBase(tp, dfx, dfy) +{ + this->colour = col; + this->index = -1; + this->widget_data = data; + this->tool_tip = tip; +} + +int NWidgetCore::ComputeMinimalSize() +{ + /* All data is already at the right place. */ + return this->index; +} + + +void NWidgetCore::StoreWidgets(Widget *widgets, int length, bool left_moving, bool top_moving, bool rtl) +{ + if (this->index >= 0) { + assert(this->index < length); + Widget *w = widgets + this->index; + assert(w->type == WWT_LAST); + + DisplayFlags flags = RESIZE_NONE; // resize flags. + /* Compute vertical resizing. */ + if (this->resize_y == 0) { + flags |= top_moving ? RESIZE_TB : RESIZE_NONE; + } else { + flags |= top_moving ? RESIZE_TB : RESIZE_BOTTOM; // Only 1 widget can resize in the widget array. + } + + /* Compute horizontal resizing. */ + if (this->resize_x == 0) { + flags |= left_moving ? RESIZE_LR : RESIZE_NONE; + } else { + flags |= left_moving ? RESIZE_LR : RESIZE_RIGHT; // Only 1 widget can resize in the widget array. + } + + /* Copy nested widget data into its widget array entry. */ + w->type = (WidgetType)(this->type); // XXX We have made some extensions, so we used a different type + w->display_flags = flags; + w->colour = this->colour; + w->left = this->pos_x; + w->right = this->pos_x + this->min_x - 1; + w->top = this->pos_y; + w->bottom = this->pos_y + this->min_y - 1; + w->data = this->widget_data; + w->tooltips = this->tool_tip; + } +} + +/** + * Set index of the nested widget in the widget array. + * @param index Index to use. + * @return The nested widget with set index. + */ +void NWidgetCore::SetIndex(int idx) +{ + assert(idx >= 0); + this->index = idx; +} + +/** + * Set data and tool tip of the nested widget. + * @param data Data to use. + * @param tip Tool tip string to use. + */ +void NWidgetCore::SetDataTip(uint16 data, StringID tip) +{ + this->widget_data = data; + this->tool_tip = tip; +} + +/* + * Smallest common multiple is computed by splitting each value into its + * prime numbers, and counting the maximal number of occurences of each prime + * in each value. The multiplication of max-count times the prime for each + * occurring prime is the smallest common multiple. + * + * #SplitInPrimes() decodes a value into its primes. + * #SmallestCommonMultiplier() computes the resulting smallest common multiplier. + */ + +/** List of small primes used to compute smallest common multiple. */ +static const byte _primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; + +/** + * Split value \a num (bigger than 0) in prime numbers (using the #_primes + * array), and keep track of the maximal count of each prime in the \a maxcount array. + * @param num Number to split into primes. + * @param maxcount For each prime in #_primes, keep track of how often it + * was used in all values encountered so far. + */ +static void SplitInPrimes(int num, uint maxcount[lengthof(_primes)]) +{ + for (uint idx = 0; idx < lengthof(_primes) && num > 1; idx++) { + uint cnt = 0; + while (num > 1 && (num % _primes[idx]) == 0) { + cnt++; + num /= _primes[idx]; + } + if (maxcount[idx] < cnt) maxcount[idx] = cnt; + } + assert(num == 1); // otherwise the list _primes is not long enough +} + +/** + * Compute the smallest common multiple. + * @param maxcount Array of prime counts. + * @return Smallest common multiple of all values previously fed to #SplitInPrimes. + */ +static int SmallestCommonMultiple(uint maxcount[lengthof(_primes)]) +{ + uint val = 1; + for (uint idx = 0; idx < lengthof(_primes); idx++) { + if (maxcount[idx] > 0) val *= _primes[idx] * maxcount[idx]; + } + assert(val <= 0xffff); + return (uint16)(val & 0xffff); +} + +/** + * Constructor container baseclass. + * @param tp Type of the container. + */ +NWidgetContainer::NWidgetContainer(byte tp): NWidgetBase(tp) +{ + this->head = NULL; + this->tail = NULL; +} + +NWidgetContainer::~NWidgetContainer() +{ + NWidgetBase *wid; + + while (this->head != NULL) { + wid = this->head->next; + delete this->head; + this->head = wid; + } + this->tail = NULL; +} + +/** + * Append widget \a wid to container. + * @param wid Widget to append. + */ +void NWidgetContainer::Add(NWidgetBase *wid) +{ + assert(wid->next == NULL && wid->prev == NULL); + + if (this->head == NULL) { + this->head = wid; + this->tail = wid; + } else { + assert(this->tail != NULL); + assert(this->tail->next == NULL); + + this->tail->next = wid; + wid->prev = this->tail; + this->tail = wid; + } +} + +NWidgetHorizontal::NWidgetHorizontal(): NWidgetContainer(NWID_HORIZONTAL) +{ +} + +int NWidgetHorizontal::ComputeMinimalSize() +{ + int biggest_index = -1; + + uint maxcount[lengthof(_primes)]; + for (uint i = 0; i < lengthof(_primes); i++) { + maxcount[i] = 0; + } + this->min_x = 0; // Sum of minimal size of all childs. + this->min_y = 0; // Biggest child. + this->fill_x = false; // true if at least one child allows fill_x. + this->fill_y = true; // true if all childs allow fill_y. + this->resize_x = 0; // smallest non-zero child widget resize step. + + bool allow_vertical_resize = true; // Possible when all childs allow it. + for (NWidgetBase *child_wid = this->head; child_wid; child_wid = child_wid->next) { + int idx = child_wid->ComputeMinimalSize(); + biggest_index = max(biggest_index, idx); + + this->min_x += child_wid->min_x; + this->min_y = max(this->min_y, child_wid->min_y); + this->fill_x |= child_wid->fill_x; + this->fill_y &= child_wid->fill_y; + + if (child_wid->resize_x > 0) { + if (this->resize_x == 0 || this->resize_x > child_wid->resize_x) this->resize_x = child_wid->resize_x; + } + + if (allow_vertical_resize) { + if (child_wid->resize_y > 0) { + SplitInPrimes(child_wid->resize_y, maxcount); + } else { + allow_vertical_resize = false; + } + } + } + + /* smallest common resize step */ + if (allow_vertical_resize) { + this->resize_y = SmallestCommonMultiple(maxcount); + } else { + this->resize_y = 0; + } + + return biggest_index; +} + +void NWidgetHorizontal::AssignPosition(int x, int y, int given_width, int given_height, bool allow_rx, bool allow_ry, bool rtl) +{ + NWidgetBase *child_wid; + + assert(x >= 0 && y >= 0); + assert(given_width >= 0 && given_height >= 0); + this->pos_x = x; + this->pos_y = y; + this->min_x = given_width; + this->min_y = given_height; + if (!allow_rx) this->resize_x = 0; + if (!allow_ry) this->resize_y = 0; + + /* count number of childs that would like a piece of the pie. + */ + int num_changing_childs = 0; // Number of childs that can change size. + for (child_wid = this->head; child_wid; child_wid = child_wid->next) { + if (child_wid->fill_x) num_changing_childs++; + } + + /* Fill and position the child widgets. + */ + int additional_length = given_width - this->min_x; // Additional width given to us. + int position = 0; // Place to put next child relative to origin of the container. + + allow_rx = (this->resize_x > 0); + child_wid = rtl ? this->tail : this->head; + while (child_wid != NULL) { + + /* Decide about vertical filling of the child. */ + int child_height; // Height of the child widget. + int child_pos_y; // Vertical position of child relative to the top of the container. + if (child_wid->fill_y) { + child_height = given_height; + child_pos_y = 0; + } else { + child_height = child_wid->min_y; + child_pos_y = (given_height - child_height) / 2; + } + + /* Decide about horizontal filling of the child. */ + int child_width; // Width of the child widget. + if (child_wid->fill_x && num_changing_childs > 0) { + /* Hand out a piece of the pie while compensating for rounding errors. */ + int increment = additional_length / num_changing_childs; + additional_length -= increment; + num_changing_childs--; + + child_width = child_wid->min_x + increment; + } else { + child_width = child_wid->min_x; + } + + child_wid->AssignPosition(x + position, y + child_pos_y, child_width, child_height, allow_rx, (this->resize_y > 0), rtl); + position += child_width; + if (child_wid->resize_x > 0) allow_rx = false; // Widget array allows only one child resizing + + child_wid = rtl ? child_wid->prev : child_wid->next; + } +} + +void NWidgetHorizontal::StoreWidgets(Widget *widgets, int length, bool left_moving, bool top_moving, bool rtl) +{ + NWidgetBase *child_wid = rtl ? this->tail : this->head; + while (child_wid != NULL) { + child_wid->StoreWidgets(widgets, length, left_moving, top_moving, rtl); + left_moving |= (child_wid->resize_x > 0); + + child_wid = rtl ? child_wid->prev : child_wid-> next; + } +} + +NWidgetVertical::NWidgetVertical(): NWidgetContainer(NWID_VERTICAL) +{ +} + +int NWidgetVertical::ComputeMinimalSize() +{ + int biggest_index = -1; + uint maxcount[lengthof(_primes)]; + for (uint i = 0; i < lengthof(_primes); i++) { + maxcount[i] = 0; + } + this->min_x = 0; // Biggest child. + this->min_y = 0; // Sum of minimal size of all childs. + this->fill_x = true; // true if all childs allow fill_x. + this->fill_y = false; // true if at least one child allows fill_y. + this->resize_y = 0; // smallest non-zero child widget resize step. + + bool allow_horizontal_resize = true; // Possible when all childs allow it. + for (NWidgetBase *child_wid = this->head; child_wid; child_wid = child_wid->next) { + int idx = child_wid->ComputeMinimalSize(); + biggest_index = max(biggest_index, idx); + + this->min_y += child_wid->min_y; + this->min_x = max(this->min_x, child_wid->min_x); + this->fill_y |= child_wid->fill_y; + this->fill_x &= child_wid->fill_x; + + if (child_wid->resize_y > 0) { + if (this->resize_y == 0 || this->resize_y > child_wid->resize_y) this->resize_y = child_wid->resize_y; + } + + if (allow_horizontal_resize) { + if (child_wid->resize_x > 0) { + SplitInPrimes(child_wid->resize_x, maxcount); + } else { + allow_horizontal_resize = false; + } + } + } + + /* smallest common resize step */ + if (allow_horizontal_resize) { + this->resize_x = SmallestCommonMultiple(maxcount); + } else { + this->resize_x = 0; + } + + return biggest_index; +} + +void NWidgetVertical::AssignPosition(int x, int y, int given_width, int given_height, bool allow_rx, bool allow_ry, bool rtl) +{ + NWidgetBase *child_wid; + + assert(x >= 0 && y >= 0); + assert(given_width >= 0 && given_height >= 0); + this->pos_x = x; + this->pos_y = y; + this->min_x = given_width; + this->min_y = given_height; + if (!allow_rx) this->resize_x = 0; + if (!allow_ry) this->resize_y = 0; + + /* count number of childs that would like a piece of the pie. + */ + int num_changing_childs = 0; // Number of childs that can change size. + for (child_wid = this->head; child_wid; child_wid = child_wid->next) { + if (child_wid->fill_x) num_changing_childs++; + } + + /* Fill and position the child widgets. + */ + int additional_length = given_height - this->min_y; // Additional width given to us. + int position = 0; // Place to put next child relative to origin of the container. + + allow_ry = (this->resize_y > 0); + for (child_wid = this->head; child_wid; child_wid = child_wid->next) { + + /* Decide about horizontal filling of the child. */ + int child_width; // Width of the child widget. + int child_pos_x; // Horizontal position of child relative to the left of the container. + if (child_wid->fill_x) { + child_width = given_width; + child_pos_x = 0; + } else { + child_width = child_wid->min_x; + child_pos_x = (given_width - child_width) / 2; + } + + /* Decide about vertical filling of the child. */ + int child_height; // Height of the child widget. + if (child_wid->fill_y && num_changing_childs > 0) { + /* Hand out a piece of the pie while compensating for rounding errors. */ + int increment = additional_length / num_changing_childs; + additional_length -= increment; + num_changing_childs--; + + child_height = child_wid->min_y + increment; + } else { + child_height = child_wid->min_y; + } + + child_wid->AssignPosition(x + child_pos_x, y + position, child_width, child_height, (this->resize_x > 0), allow_ry, rtl); + position += child_height; + if (child_wid->resize_y > 0) allow_ry = false; // Widget array allows only one child resizing + } +} + +void NWidgetVertical::StoreWidgets(Widget *widgets, int length, bool left_moving, bool top_moving, bool rtl) +{ + for (NWidgetBase *child_wid = this->head; child_wid; child_wid = child_wid->next) { + child_wid->StoreWidgets(widgets, length, left_moving, top_moving, rtl); + top_moving |= (child_wid->resize_y > 0); + } +} + +/** + * Generic spacer widget. + * @param length Horizontal size of the spacer widget. + * @param height Vertical size of the spacer widget. + */ +NWidgetSpacer::NWidgetSpacer(int length, int height): NWidgetResizeBase(NWID_SPACER, false, false) +{ + assert(NWID_SPACER <= (int)WWT_MASK); // Make sure we don't walk out of the allocated range for widgets + + this->SetMinimalSize(length, height); + this->SetResize(0, 0); +} + +int NWidgetSpacer::ComputeMinimalSize() +{ + /* No further computation needed. */ + return -1; +} + +void NWidgetSpacer::StoreWidgets(Widget *widgets, int length, bool left_moving, bool top_moving, bool rtl) +{ + /* Spacer widgets are never stored in the widget array. */ +} + +/** + * Constructor parent nested widgets. + * @param tp Type of parent widget. + * @param colour Colour of the parent widget. + * @param index Index in the widget array used by the window system. + * @param chld Child container widget (if supplied). If not supplied, a + * vertical container will be inserted while adding the first + * child widget. + */ +NWidgetBackground::NWidgetBackground(byte tp, Colours colour, int index, NWidgetContainer *chld) + : NWidgetCore(tp, colour, true, true, 0x0, STR_NULL) +{ + this->SetIndex(index); + assert(tp == WWT_PANEL || tp == WWT_INSET || tp == WWT_FRAME); + assert(index >= 0); + this->child = chld; +} + +NWidgetBackground::~NWidgetBackground() +{ + if (this->child) delete this->child; +} + +/** + * Add a child to the parent. + * + * A parent behaves as a vertical container, you can add several childs to it, + * and they are put underneath each other. + */ +void NWidgetBackground::Add(NWidgetBase *nwid) +{ + if (this->child == NULL) { + this->child = new NWidgetVertical(); + } + this->child->Add(nwid); +} + +int NWidgetBackground::ComputeMinimalSize() +{ + int biggest_index = this->index; + if (this->child) { + int idx = this->child->ComputeMinimalSize(); + biggest_index = max(biggest_index, idx); + + this->min_x = this->child->min_x; + this->min_y = this->child->min_y; + this->fill_x = this->child->fill_x; + this->fill_y = this->child->fill_y; + this->resize_x = this->child->resize_x; + this->resize_y = this->child->resize_y; + } + /* Otherwise, the program should have already set the above values. */ + + return biggest_index; +} + +void NWidgetBackground::AssignPosition(int x, int y, int given_width, int given_height, bool allow_rx, bool allow_ry, bool rtl) +{ + assert(x >= 0 && y >= 0); + assert(given_width >= 0 && given_height >= 0); + this->pos_x = x; + this->pos_y = y; + this->min_x = given_width; + this->min_y = given_height; + if (!allow_rx) this->resize_x = 0; + if (!allow_ry) this->resize_y = 0; + + if (this->child) this->child->AssignPosition(x, y, given_width, given_height, (this->resize_x > 0), (this->resize_y > 0), rtl); +} + +void NWidgetBackground::StoreWidgets(Widget *widgets, int length, bool left_moving, bool top_moving, bool rtl) +{ + NWidgetCore::StoreWidgets(widgets, length, left_moving, top_moving, rtl); + if (this->child) this->child->StoreWidgets(widgets, length, left_moving, top_moving, rtl); +} + +/** + * Nested leaf widget. + * @param tp Type of leaf widget. + * @param index Index in the widget array used by the window system. + * @param data Data of the widget (by default \c 0). + * @param tip Tooltip of the widget (by default \c 0x0). + */ +NWidgetLeaf::NWidgetLeaf(byte tp, Colours colour, int index, uint16 data, StringID tip) + : NWidgetCore(tp, colour, true, true, data, tip) +{ + this->SetIndex(index); + this->SetMinimalSize(0, 0); + this->SetResize(0, 0); + + switch (tp) { + case WWT_PUSHBTN: + this->SetFill(false, false); + break; + + case WWT_IMGBTN: + case WWT_PUSHIMGBTN: + case WWT_IMGBTN_2: + this->SetFill(false, false); + break; + + case WWT_TEXTBTN: + case WWT_PUSHTXTBTN: + case WWT_TEXTBTN_2: + case WWT_LABEL: + case WWT_TEXT: + case WWT_MATRIX: + case WWT_EDITBOX: + this->SetFill(false, false); + break; + + case WWT_SCROLLBAR: + case WWT_SCROLL2BAR: + this->SetFill(false, true); + this->SetResize(0, 1); + this->min_x = 12; + this->SetDataTip(0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST); + break; + + case WWT_CAPTION: + this->SetFill(true, false); + this->SetResize(1, 0); + this->min_y = 14; + break; + + case WWT_HSCROLLBAR: + this->SetFill(true, false); + this->SetResize(1, 0); + this->min_y = 12; + break; + + case WWT_STICKYBOX: + this->SetFill(false, false); + this->SetMinimalSize(12, 14); + this->SetDataTip(STR_NULL, STR_STICKY_BUTTON); + break; + + case WWT_RESIZEBOX: + this->SetFill(false, false); + this->SetMinimalSize(12, 12); + this->SetDataTip(STR_NULL, STR_RESIZE_BUTTON); + break; + + case WWT_CLOSEBOX: + this->SetFill(false, false); + this->SetMinimalSize(11, 14); + this->SetDataTip(STR_00C5, STR_018B_CLOSE_WINDOW); + break; + + case WWT_DROPDOWN: + case WWT_DROPDOWNIN: + this->SetFill(false, false); + this->min_y = 12; + break; + + default: + NOT_REACHED(); + } +} + +/** + * Intialize nested widget tree and convert to widget array. + * @param nwid Nested widget tree. + * @param rtl Direction of the language. + * @return Widget array with the converted widgets. + * @note Caller should release returned widget array with \c free(widgets). + */ +Widget *InitializeNWidgets(NWidgetBase *nwid, bool rtl) +{ + int i; + + /* Initialize nested widgets. */ + int biggest_index = nwid->ComputeMinimalSize(); + nwid->AssignPosition(0, 0, nwid->min_x, nwid->min_y, (nwid->resize_x > 0), (nwid->resize_y > 0), rtl); + + /* Construct a local widget array and initialize all its types to #WWT_LAST. */ + Widget *widgets = MallocT(biggest_index + 2); + for (i = 0; i < biggest_index + 2; i++) { + widgets[i].type = WWT_LAST; + } + + /* Store nested widgets in the array. */ + nwid->StoreWidgets(widgets, biggest_index + 1, false, false, rtl); + + /* Check that all widgets are used. */ + for (i = 0; i < biggest_index + 2; i++) { + if (widgets[i].type == WWT_LAST) break; + } + assert(i == biggest_index + 1); + + /* Fill terminating widget */ + static const Widget last_widget = {WIDGETS_END}; + widgets[biggest_index + 1] = last_widget; + + return widgets; +} + diff -r 258ca04a6c17 src/widget_type.h --- a/src/widget_type.h Fri Mar 06 21:07:01 2009 +0000 +++ b/src/widget_type.h Sat Mar 07 18:33:53 2009 +0100 @@ -123,4 +123,140 @@ StringID tooltips; ///< Tooltips that are shown when rightclicking on a widget }; +/** + * Types of nested widgets. + * @note Extends #WidgetType. + */ +enum NewWidgetType { + NWID_HORIZONTAL = WWT_LAST + 1, ///< Horizontal container + NWID_VERTICAL, ///< Vertical container + NWID_SPACER, ///< Invisible widget that takes some space +}; + +class NWidgetContainer; // Forward declaration. + +/** + * Baseclass for nested widgets. + */ +class NWidgetBase: public ZeroedMemoryAllocator { +public: + NWidgetBase(byte tp); + virtual ~NWidgetBase(); + + virtual int ComputeMinimalSize() = 0; + virtual void AssignPosition(int x, int y, int width, int height, bool allow_rx, bool allow_ry, bool rtl) = 0; + + virtual void StoreWidgets(Widget *widgets, int length, bool left_moving, bool top_moving, bool rtl) = 0; + + byte type; ///< Type of the widget / nested widget. + int min_x; ///< Minimal horizontal size. + int min_y; ///< Minimal vertical size. + bool fill_x; ///< Allow horizontal filling from initial size. + bool fill_y; ///< Allow vertical filling from initial size. + int resize_x; ///< Horizontal resize step (\c 0 means not resizable). + int resize_y; ///< Vertical resize step (\c 0 means not resizable). + + int pos_x; ///< Horizontal position of top-left corner of the widget in the window. + int pos_y; ///< Vertical position of top-left corner of the widget in the window. + + NWidgetBase *next; ///< Pointer to next widget in container. Managed by parent container widget. + NWidgetBase *prev; ///< Pointer to previous widget in container. Managed by parent container widget. +}; + +/** Base class for a resizable nested widget. */ +class NWidgetResizeBase: public NWidgetBase { +public: + NWidgetResizeBase(byte tp, bool fill_x, bool fill_y); + + void SetMinimalSize(int min_x, int min_y); + void SetFill(bool fill_x, bool fill_y); + void SetResize(int resize_x, int resize_y); + + void AssignPosition(int x, int y, int given_width, int given_height, bool allow_rx, bool allow_ry, bool rtl); +}; + +/** Base class for a 'real' widget. */ +class NWidgetCore: public NWidgetResizeBase { +public: + NWidgetCore(byte tp, Colours colour, bool def_fill_x, bool def_fill_y, uint16 data, StringID tip); + + void SetIndex(int index); + void SetDataTip(uint16 data, StringID tip); + + int ComputeMinimalSize(); + void StoreWidgets(Widget *widgets, int length, bool left_moving, bool top_moving, bool rtl); + + 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'). + uint16 widget_data; ///< Data of the widget. + StringID tool_tip; ///< Tooltip of the widget. +}; + +/** Baseclass for container widgets. */ +class NWidgetContainer: public NWidgetBase { +public: + NWidgetContainer(byte tp); + ~NWidgetContainer(); + + void Add(NWidgetBase *wid); +protected: + NWidgetBase *head; ///< Pointer to first widget in container. + NWidgetBase *tail; ///< Pointer to last widget in container. +}; + +/** Horizontal container. */ +class NWidgetHorizontal: public NWidgetContainer { +public: + NWidgetHorizontal(); + + int ComputeMinimalSize(); + void AssignPosition(int x, int y, int given_width, int given_height, bool allow_rx, bool allow_ry, bool rtl); + + void StoreWidgets(Widget *widgets, int length, bool left_moving, bool top_moving, bool rtl); +}; + +/** Vertical container */ +class NWidgetVertical: public NWidgetContainer { +public: + NWidgetVertical(); + + int ComputeMinimalSize(); + void AssignPosition(int x, int y, int given_width, int given_height, bool allow_rx, bool allow_ry, bool rtl); + + void StoreWidgets(Widget *widgets, int length, bool left_moving, bool top_moving, bool rtl); +}; + + +/** Spacer widget */ +class NWidgetSpacer: public NWidgetResizeBase { +public: + NWidgetSpacer(int length, int height); + + int ComputeMinimalSize(); + void StoreWidgets(Widget *widgets, int length, bool left_moving, bool top_moving, bool rtl); +}; + +/** Nested widget with a child. */ +class NWidgetBackground: public NWidgetCore { +public: + NWidgetBackground(byte tp, Colours colour, int index, NWidgetContainer *child = NULL); + ~NWidgetBackground(); + + void Add(NWidgetBase *wid); + + int ComputeMinimalSize(); + void AssignPosition(int x, int y, int width, int height, bool allow_rx, bool allow_ry, bool rtl); + + void StoreWidgets(Widget *widgets, int length, bool left_moving, bool top_moving, bool rtl); +private: + NWidgetContainer *child; ///< Child widget. +}; + +/** Leaf widget. */ +class NWidgetLeaf: public NWidgetCore { +public: + NWidgetLeaf(byte tp, Colours colour, int index, uint16 data, StringID tip); +}; + +Widget *InitializeNWidgets(NWidgetBase *nwid, bool rtl = false); #endif /* WIDGET_TYPE_H */