diff --git a/src/widget.cpp b/src/widget.cpp index e2e9c4d5b3..3108409ea4 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -562,21 +562,43 @@ static inline void DrawCaption(const Rect &r, Colours colour, Owner owner, Strin if (str != STR_NULL) DrawString(r.left + WD_CAPTIONTEXT_LEFT, r.right - WD_CAPTIONTEXT_RIGHT, r.top + WD_CAPTIONTEXT_TOP, str, TC_FROMSTRING, SA_CENTER); } -static inline void DrawDropdown(const Rect &r, Colours colour, bool clicked, StringID str) +/** + * Draw a button with a dropdown (#WWT_DROPDOWN and #NWID_BUTTON_DRPDOWN). + * @param r Rectangle containing the widget. + * @param colour Background colour of the widget. + * @param clicked_button The button-part is lowered. + * @param clicked_dropdown The drop-down part is lowered. + * @param str Text of the button. + * + * @note Magic constants are also used in #NWidgetLeaf::ButtonHit. + */ +static inline void DrawButtonDropdown(const Rect &r, Colours colour, bool clicked_button, bool clicked_dropdown, StringID str) { if (_dynlang.text_dir == TD_LTR) { - DrawFrameRect(r.left, r.top, r.right - 12, r.bottom, colour, FR_NONE); - DrawFrameRect(r.right - 11, r.top, r.right, r.bottom, colour, clicked ? FR_LOWERED : FR_NONE); - DrawString(r.right - (clicked ? 10 : 11), r.right, r.top + (clicked ? 2 : 1), STR_BLACK_ARROW_DOWN, TC_FROMSTRING, SA_CENTER); - if (str != STR_NULL) DrawString(r.left + WD_DROPDOWNTEXT_LEFT, r.right - WD_DROPDOWNTEXT_RIGHT, r.top + WD_DROPDOWNTEXT_TOP, str, TC_BLACK); + DrawFrameRect(r.left, r.top, r.right - 12, r.bottom, colour, clicked_button ? FR_LOWERED : FR_NONE); + DrawFrameRect(r.right - 11, r.top, r.right, r.bottom, colour, clicked_dropdown ? FR_LOWERED : FR_NONE); + DrawString(r.right - (clicked_dropdown ? 10 : 11), r.right, r.top + (clicked_dropdown ? 2 : 1), STR_BLACK_ARROW_DOWN, TC_FROMSTRING, SA_CENTER); + if (str != STR_NULL) DrawString(r.left + WD_DROPDOWNTEXT_LEFT + clicked_button, r.right - WD_DROPDOWNTEXT_RIGHT + clicked_button, r.top + WD_DROPDOWNTEXT_TOP + clicked_button, str, TC_BLACK); } else { - DrawFrameRect(r.left + 12, r.top, r.right, r.bottom, colour, FR_NONE); - DrawFrameRect(r.left, r.top, r.left + 11, r.bottom, colour, clicked ? FR_LOWERED : FR_NONE); - DrawString(r.left + clicked, r.left + 11, r.top + (clicked ? 2 : 1), STR_BLACK_ARROW_DOWN, TC_FROMSTRING, SA_CENTER); - if (str != STR_NULL) DrawString(r.left + WD_DROPDOWNTEXT_RIGHT, r.right - WD_DROPDOWNTEXT_LEFT, r.top + WD_DROPDOWNTEXT_TOP, str, TC_BLACK); + DrawFrameRect(r.left + 12, r.top, r.right, r.bottom, colour, clicked_button ? FR_LOWERED : FR_NONE); + DrawFrameRect(r.left, r.top, r.left + 11, r.bottom, colour, clicked_dropdown ? FR_LOWERED : FR_NONE); + DrawString(r.left + clicked_dropdown, r.left + 11, r.top + (clicked_dropdown ? 2 : 1), STR_BLACK_ARROW_DOWN, TC_FROMSTRING, SA_CENTER); + if (str != STR_NULL) DrawString(r.left + WD_DROPDOWNTEXT_RIGHT + clicked_button, r.right - WD_DROPDOWNTEXT_LEFT + clicked_button, r.top + WD_DROPDOWNTEXT_TOP + clicked_button, str, TC_BLACK); } } +/** + * Draw a dropdown #WWT_DROPDOWN widget. + * @param r Rectangle containing the widget. + * @param colour Background colour of the widget. + * @param clicked The widget is lowered. + * @param str Text of the button. + */ +static inline void DrawDropdown(const Rect &r, Colours colour, bool clicked, StringID str) +{ + DrawButtonDropdown(r, colour, false, clicked, str); +} + /** * Paint all widgets of a window. */ @@ -2003,6 +2025,7 @@ NWidgetLeaf::NWidgetLeaf(WidgetType tp, Colours colour, int index, uint16 data, case WWT_TEXT: case WWT_MATRIX: case WWT_EDITBOX: + case NWID_BUTTON_DRPDOWN: this->SetFill(false, false); break; @@ -2177,7 +2200,8 @@ void NWidgetLeaf::SetupSmallestSize(Window *w, bool init_array) size = maxdim(size, d2); break; } - case WWT_DROPDOWN: { + case WWT_DROPDOWN: + case NWID_BUTTON_DRPDOWN: { static const Dimension extra = {WD_DROPDOWNTEXT_LEFT + WD_DROPDOWNTEXT_RIGHT, WD_DROPDOWNTEXT_TOP + WD_DROPDOWNTEXT_BOTTOM}; padding = &extra; if (this->index >= 0) w->SetStringParameters(this->index); @@ -2300,6 +2324,11 @@ void NWidgetLeaf::Draw(const Window *w) DrawDropdown(r, this->colour, clicked, this->widget_data); break; + case NWID_BUTTON_DRPDOWN: + if (this->index >= 0) w->SetStringParameters(this->index); + DrawButtonDropdown(r, this->colour, clicked, (this->disp_flags & ND_DROPDOWN_ACTIVE) != 0, this->widget_data); + break; + default: NOT_REACHED(); } @@ -2322,6 +2351,24 @@ Scrollbar *NWidgetLeaf::FindScrollbar(Window *w, bool allow_next) return NULL; } +/** + * For a #NWID_BUTTON_DRPDOWN, test whether \a pt refers to the button or to the drop-down. + * @param pt Point in the widget. + * @return The point refers to the button. + * + * @param The magic constants are also used at #DrawButtonDropdown. + */ +bool NWidgetLeaf::ButtonHit(const Point &pt) +{ + if (_dynlang.text_dir == TD_LTR) { + int button_width = this->pos_x + this->current_x - 12; + return pt.x < button_width; + } else { + int button_left = this->pos_x + 12; + return pt.x >= button_left; + } +} + /** * Intialize nested widget tree and convert to widget array. * @param nwid Nested widget tree. @@ -2545,7 +2592,7 @@ static int MakeNWidget(const NWidgetPart *parts, int count, NWidgetBase **dest, default: if (*dest != NULL) return num_used; - assert((parts->type & WWT_MASK) < WWT_LAST); + assert((parts->type & WWT_MASK) < WWT_LAST || parts->type == NWID_BUTTON_DRPDOWN); *dest = new NWidgetLeaf(parts->type, parts->u.widget.colour, parts->u.widget.index, 0x0, STR_NULL); *biggest_index = max(*biggest_index, (int)parts->u.widget.index); break; diff --git a/src/widget_type.h b/src/widget_type.h index 8683f20039..27eeb20719 100644 --- a/src/widget_type.h +++ b/src/widget_type.h @@ -121,6 +121,7 @@ enum WidgetType { NWID_SELECTION, ///< Stacked widgets, only one visible at a time (eg in a panel with tabs). NWID_LAYERED, ///< Widgets layered on top of each other, all visible at the same time. NWID_VIEWPORT, ///< Nested widget containing a viewport. + NWID_BUTTON_DRPDOWN, ///< Button with a drop-down. /* Nested widget part types. */ WPT_RESIZE, ///< Widget part for specifying resizing. @@ -278,17 +279,22 @@ public: /** Nested widget flags that affect display and interaction withe 'real' widgets. */ enum NWidgetDisplay { + /* Generic. */ NDB_LOWERED = 0, ///< Widget is lowered (pressed down) bit. NDB_DISABLED = 1, ///< Widget is disabled (greyed out) bit. + /* Viewport widget. */ NDB_NO_TRANSPARENCY = 2, ///< Viewport is never transparent. NDB_SHADE_GREY = 3, ///< Shade viewport to grey-scale. NDB_SHADE_DIMMED = 4, ///< Display dimmed colours in the viewport. + /* Button dropdown widget. */ + NDB_DROPDOWN_ACTIVE = 5, ///< Dropdown menu of the button dropdown widget is active. @see #NWID_BUTTON_DRPDOWN ND_LOWERED = 1 << NDB_LOWERED, ///< Bit value of the lowered flag. ND_DISABLED = 1 << NDB_DISABLED, ///< Bit value of the disabled flag. ND_NO_TRANSPARENCY = 1 << NDB_NO_TRANSPARENCY, ///< Bit value of the 'no transparency' flag. ND_SHADE_GREY = 1 << NDB_SHADE_GREY, ///< Bit value of the 'shade to grey' flag. ND_SHADE_DIMMED = 1 << NDB_SHADE_DIMMED, ///< Bit value of the 'dimmed colours' flag. + ND_DROPDOWN_ACTIVE = 1 << NDB_DROPDOWN_ACTIVE, ///< Bit value of the 'dropdown active' flag. }; DECLARE_ENUM_AS_BIT_SET(NWidgetDisplay); @@ -526,6 +532,8 @@ public: /* virtual */ void Draw(const Window *w); /* virtual */ Scrollbar *FindScrollbar(Window *w, bool allow_next = true); + bool ButtonHit(const Point &pt); + static void InvalidateDimensionCache(); private: static Dimension stickybox_dimension; ///< Cached size of a stickybox widget. diff --git a/src/widgets/dropdown.cpp b/src/widgets/dropdown.cpp index 0b010305c5..94c3b2b736 100644 --- a/src/widgets/dropdown.cpp +++ b/src/widgets/dropdown.cpp @@ -103,7 +103,16 @@ struct DropdownWindow : Window { { Window *w2 = FindWindowById(this->parent_wnd_class, this->parent_wnd_num); if (w2 != NULL) { - w2->RaiseWidget(this->parent_button); + if (w2->nested_array != NULL) { + NWidgetCore *nwi2 = w2->GetWidget(this->parent_button); + if (nwi2->type == NWID_BUTTON_DRPDOWN) { + nwi2->disp_flags &= ~ND_DROPDOWN_ACTIVE; + } else { + w2->RaiseWidget(this->parent_button); + } + } else { + w2->RaiseWidget(this->parent_button); + } w2->SetWidgetDirty(this->parent_button); } @@ -251,20 +260,23 @@ void ShowDropDownList(Window *w, DropDownList *list, int selected, int button, u DeleteWindowById(WC_DROPDOWN_MENU, 0); - w->LowerWidget(button); - w->SetWidgetDirty(button); - /* Our parent's button widget is used to determine where to place the drop * down list window. */ Rect wi_rect; Colours wi_colour; if (w->nested_array != NULL) { - const NWidgetCore *nwi = w->GetWidget(button); + NWidgetCore *nwi = w->GetWidget(button); wi_rect.left = nwi->pos_x; wi_rect.right = nwi->pos_x + nwi->current_x - 1; wi_rect.top = nwi->pos_y; wi_rect.bottom = nwi->pos_y + nwi->current_y - 1; wi_colour = nwi->colour; + + if (nwi->type == NWID_BUTTON_DRPDOWN) { + nwi->disp_flags |= ND_DROPDOWN_ACTIVE; + } else { + w->LowerWidget(button); + } } else { const Widget *wi = &w->widget[button]; wi_rect.left = wi->left; @@ -272,7 +284,10 @@ void ShowDropDownList(Window *w, DropDownList *list, int selected, int button, u wi_rect.top = wi->top; wi_rect.bottom = wi->bottom; wi_colour = wi->colour; + + w->LowerWidget(button); } + w->SetWidgetDirty(button); /* The preferred position is just below the dropdown calling widget */ int top = w->top + wi_rect.bottom + 1;