=== modified file 'source.list'
--- source.list 2008-08-16 06:38:56 +0000
+++ source.list 2008-08-16 12:13:11 +0000
@@ -28,6 +28,7 @@
fontcache.cpp
gamelog.cpp
genworld.cpp
+core/geometry_func.cpp
gfx.cpp
gfxinit.cpp
heightmap.cpp
@@ -180,6 +181,7 @@
functions.h
gamelog.h
genworld.h
+core/geometry_func.h
gfx_func.h
gfx_type.h
gfxinit.h
=== modified file 'src/core/geometry_type.hpp'
--- src/core/geometry_type.hpp 2008-05-04 13:31:57 +0000
+++ src/core/geometry_type.hpp 2008-08-16 12:09:20 +0000
@@ -29,6 +29,17 @@
int height;
};
+/**
+ * Constructor for Dimension structure
+ * @param p_width Width to store in the returned Dimension
+ * @param p_height Height to store in the returned Dimension
+ */
+Dimension FORCEINLINE MakeDimension(int p_width, int p_height)
+{
+ Dimension d = {p_width, p_height};
+ return d;
+}
+
/** Specification of a rectangle with absolute coordinates of all edges */
struct Rect {
int left;
=== modified file 'src/intro_gui.cpp'
--- src/intro_gui.cpp 2008-08-16 08:47:44 +0000
+++ src/intro_gui.cpp 2008-08-16 09:36:56 +0000
@@ -23,30 +23,6 @@
#include "table/strings.h"
#include "table/sprites.h"
-static const Widget _select_game_widgets[] = {
-{ WWT_CAPTION, RESIZE_NONE, COLOUR_BROWN, 0, 335, 0, 13, STR_0307_OPENTTD, STR_NULL},
-{ WWT_PANEL, RESIZE_NONE, COLOUR_BROWN, 0, 335, 14, 194, 0x0, STR_NULL},
-{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 10, 167, 22, 33, STR_0140_NEW_GAME, STR_02FB_START_A_NEW_GAME},
-{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 168, 325, 22, 33, STR_0141_LOAD_GAME, STR_02FC_LOAD_A_SAVED_GAME},
-{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 10, 167, 40, 51, STR_029A_PLAY_SCENARIO, STR_0303_START_A_NEW_GAME_USING},
-{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 168, 325, 40, 51, STR_PLAY_HEIGHTMAP, STR_PLAY_HEIGHTMAP_HINT},
-{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 10, 167, 58, 69, STR_SCENARIO_EDITOR, STR_02FE_CREATE_A_CUSTOMIZED_GAME},
-{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 168, 325, 58, 69, STR_MULTIPLAYER, STR_0300_SELECT_MULTIPLAYER_GAME},
-
-{ WWT_IMGBTN_2, RESIZE_NONE, COLOUR_ORANGE, 10, 86, 77, 131, SPR_SELECT_TEMPERATE, STR_030E_SELECT_TEMPERATE_LANDSCAPE},
-{ WWT_IMGBTN_2, RESIZE_NONE, COLOUR_ORANGE, 90, 166, 77, 131, SPR_SELECT_SUB_ARCTIC, STR_030F_SELECT_SUB_ARCTIC_LANDSCAPE},
-{ WWT_IMGBTN_2, RESIZE_NONE, COLOUR_ORANGE, 170, 246, 77, 131, SPR_SELECT_SUB_TROPICAL, STR_0310_SELECT_SUB_TROPICAL_LANDSCAPE},
-{ WWT_IMGBTN_2, RESIZE_NONE, COLOUR_ORANGE, 250, 326, 77, 131, SPR_SELECT_TOYLAND, STR_0311_SELECT_TOYLAND_LANDSCAPE},
-
-{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 10, 167, 139, 150, STR_0148_GAME_OPTIONS, STR_0301_DISPLAY_GAME_OPTIONS},
-{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 168, 325, 139, 150, STR_01FE_DIFFICULTY, STR_0302_DISPLAY_DIFFICULTY_OPTIONS},
-{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 10, 167, 157, 168, STR_CONFIG_PATCHES, STR_CONFIG_PATCHES_TIP},
-{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 168, 325, 157, 168, STR_NEWGRF_SETTINGS_BUTTON, STR_NULL},
-
-{ WWT_PUSHTXTBTN, RESIZE_NONE, COLOUR_ORANGE, 104, 231, 175, 186, STR_0304_QUIT, STR_0305_QUIT_OPENTTD},
-{ WIDGETS_END},
-};
-
static inline void SetNewLandscapeType(byte landscape)
{
_settings_newgame.game_creation.landscape = landscape;
@@ -74,33 +50,45 @@
};
public:
- SelectGameWindow(const WindowDesc *desc) : Window(desc)
+ SelectGameWindow(const WindowDesc *desc) : Window(desc), m_difficulty_param(STR_01FE_DIFFICULTY, STR_6801_EASY)
{
+ m_temperate_button = NULL;
+ m_arctic_button = NULL;
+ m_tropical_button = NULL;
+ m_toyland_button = NULL;
+
InitializeSize(desc);
-
- this->LowerWidget(_settings_newgame.game_creation.landscape + SGI_TEMPERATE_LANDSCAPE);
+ SetClimateButtons();
this->FindWindowPlacementAndResize(desc);
}
+ void SetClimateButtons()
+ {
+ m_temperate_button->SetLowered(_settings_newgame.game_creation.landscape == LT_TEMPERATE);
+ m_arctic_button->SetLowered(_settings_newgame.game_creation.landscape == LT_ARCTIC);
+ m_tropical_button->SetLowered(_settings_newgame.game_creation.landscape == LT_TROPIC);
+ m_toyland_button->SetLowered(_settings_newgame.game_creation.landscape == LT_TOYLAND);
+ }
+
virtual void OnPaint()
{
- this->SetWidgetLoweredState(SGI_TEMPERATE_LANDSCAPE, _settings_newgame.game_creation.landscape == LT_TEMPERATE);
- this->SetWidgetLoweredState(SGI_ARCTIC_LANDSCAPE, _settings_newgame.game_creation.landscape == LT_ARCTIC);
- this->SetWidgetLoweredState(SGI_TROPIC_LANDSCAPE, _settings_newgame.game_creation.landscape == LT_TROPIC);
- this->SetWidgetLoweredState(SGI_TOYLAND_LANDSCAPE, _settings_newgame.game_creation.landscape == LT_TOYLAND);
- SetDParam(0, STR_6801_EASY + _settings_newgame.difficulty.diff_level);
+ SetClimateButtons();
+ m_difficulty_param.param0 = STR_6801_EASY + _settings_newgame.difficulty.diff_level;
this->DrawWidgets();
}
- virtual void OnClick(Point pt, int widget)
+ virtual void OnClickWidget(const Point& pt, BaseWidget *widget)
{
+ assert(widget);
+ int widnum = widget->GetWidgetNumber();
+
#ifdef ENABLE_NETWORK
/* Do not create a network server when you (just) have closed one of the game
* creation/load windows for the network server. */
- if (IsInsideMM(widget, SGI_GENERATE_GAME, SGI_EDIT_SCENARIO + 1)) _is_network_server = false;
+ if (IsInsideMM(widnum, SGI_GENERATE_GAME, SGI_EDIT_SCENARIO + 1)) _is_network_server = false;
#endif /* ENABLE_NETWORK */
- switch (widget) {
+ switch (widnum) {
case SGI_GENERATE_GAME: ShowGenerateLandscape(); break;
case SGI_LOAD_GAME: ShowSaveLoadDialog(SLD_LOAD_GAME); break;
case SGI_PLAY_SCENARIO: ShowSaveLoadDialog(SLD_LOAD_SCENARIO); break;
@@ -117,8 +105,8 @@
case SGI_TEMPERATE_LANDSCAPE: case SGI_ARCTIC_LANDSCAPE:
case SGI_TROPIC_LANDSCAPE: case SGI_TOYLAND_LANDSCAPE:
- this->RaiseWidget(_settings_newgame.game_creation.landscape + SGI_TEMPERATE_LANDSCAPE);
- SetNewLandscapeType(widget - SGI_TEMPERATE_LANDSCAPE);
+ SetNewLandscapeType(widnum - SGI_TEMPERATE_LANDSCAPE);
+ SetClimateButtons();
break;
case SGI_OPTIONS: ShowGameOptions(); break;
@@ -128,13 +116,105 @@
case SGI_EXIT: HandleExitGameRequest(); break;
}
}
+private:
+ BaseWidget *InitializeWidgets();
+ ImageButtonWidget *m_temperate_button;
+ ImageButtonWidget *m_arctic_button;
+ ImageButtonWidget *m_tropical_button;
+ ImageButtonWidget *m_toyland_button;
+
+ SingleParmText m_difficulty_param; // Difficulty setting text
};
+/** @todo Add tool-tip for NewGRF settings button in intro screen */
+BaseWidget *SelectGameWindow::InitializeWidgets()
+{
+ ContainerWidget *hor_cont;
+
+ /* widget root */
+ ContainerWidget *root = new ContainerWidget(false, false);
+ /* window header */
+ root->push_back(new FixedTextCaptionWidget(COLOUR_BROWN, 335, STR_0307_OPENTTD, STR_NULL));
+
+ /* window body, vertical container with 7 pixels inter-spacing */
+ ContainerWidget *vert_cont = new ContainerWidget(false, false, 0, 6, 0);
+
+ /* new game/load game */
+ hor_cont = new ContainerWidget(true, true);
+ hor_cont->push_back(new FixedTextPushButtonWidget(SGI_GENERATE_GAME, COLOUR_ORANGE,
+ STR_0140_NEW_GAME, STR_02FB_START_A_NEW_GAME));
+ hor_cont->push_back(new FixedTextPushButtonWidget(SGI_LOAD_GAME, COLOUR_ORANGE,
+ STR_0141_LOAD_GAME, STR_02FC_LOAD_A_SAVED_GAME));
+ vert_cont->push_back(hor_cont);
+
+ /* play scenario/play heightmap */
+ hor_cont = new ContainerWidget(true, true);
+ hor_cont->push_back(new FixedTextPushButtonWidget(SGI_PLAY_SCENARIO, COLOUR_ORANGE,
+ STR_029A_PLAY_SCENARIO, STR_0303_START_A_NEW_GAME_USING));
+ hor_cont->push_back(new FixedTextPushButtonWidget(SGI_PLAY_HEIGHTMAP, COLOUR_ORANGE,
+ STR_PLAY_HEIGHTMAP, STR_PLAY_HEIGHTMAP_HINT));
+ vert_cont->push_back(hor_cont);
+
+ /* scenario editor/multiplayer */
+ hor_cont = new ContainerWidget(true, true);
+ hor_cont->push_back(new FixedTextPushButtonWidget(SGI_EDIT_SCENARIO, COLOUR_ORANGE,
+ STR_SCENARIO_EDITOR, STR_02FE_CREATE_A_CUSTOMIZED_GAME));
+ hor_cont->push_back(new FixedTextPushButtonWidget(SGI_PLAY_NETWORK, COLOUR_ORANGE,
+ STR_MULTIPLAYER, STR_0300_SELECT_MULTIPLAYER_GAME));
+ vert_cont->push_back(hor_cont);
+
+ /* climates */
+ m_temperate_button = new ImageButtonWidget(SGI_TEMPERATE_LANDSCAPE, COLOUR_ORANGE,
+ SPR_SELECT_TEMPERATE, STR_030E_SELECT_TEMPERATE_LANDSCAPE, true);
+ m_arctic_button = new ImageButtonWidget(SGI_ARCTIC_LANDSCAPE, COLOUR_ORANGE,
+ SPR_SELECT_SUB_ARCTIC, STR_030F_SELECT_SUB_ARCTIC_LANDSCAPE, true);
+ m_tropical_button = new ImageButtonWidget(SGI_TROPIC_LANDSCAPE, COLOUR_ORANGE,
+ SPR_SELECT_SUB_TROPICAL, STR_0310_SELECT_SUB_TROPICAL_LANDSCAPE, true);
+ m_toyland_button = new ImageButtonWidget(SGI_TOYLAND_LANDSCAPE, COLOUR_ORANGE,
+ SPR_SELECT_TOYLAND, STR_0311_SELECT_TOYLAND_LANDSCAPE, true);
+ hor_cont = new ContainerWidget(true, false, 0, 3, 0);
+ hor_cont->push_back(m_temperate_button);
+ hor_cont->push_back(m_arctic_button);
+ hor_cont->push_back(m_tropical_button);
+ hor_cont->push_back(m_toyland_button);
+ vert_cont->push_back(hor_cont);
+
+ /* game options/difficulty settings */
+ hor_cont = new ContainerWidget(true, true);
+ hor_cont->push_back(new FixedTextPushButtonWidget(SGI_OPTIONS, COLOUR_ORANGE,
+ STR_0148_GAME_OPTIONS, STR_0301_DISPLAY_GAME_OPTIONS));
+ hor_cont->push_back(new ParmTextPushButtonWidget(SGI_DIFFICULTIES, COLOUR_ORANGE,
+ &m_difficulty_param, STR_0302_DISPLAY_DIFFICULTY_OPTIONS));
+ vert_cont->push_back(hor_cont);
+
+ /* configure patches/newgrf settings */
+ hor_cont = new ContainerWidget(true, true);
+ hor_cont->push_back(new FixedTextPushButtonWidget(SGI_PATCHES_OPTIONS, COLOUR_ORANGE,
+ STR_CONFIG_PATCHES, STR_CONFIG_PATCHES_TIP));
+ hor_cont->push_back(new FixedTextPushButtonWidget(SGI_GRF_SETTINGS, COLOUR_ORANGE,
+ STR_NEWGRF_SETTINGS_BUTTON, STR_NULL));
+ vert_cont->push_back(hor_cont);
+
+ /* quit */
+ BaseWidget *wid = new FixedTextPushButtonWidget(SGI_EXIT, COLOUR_ORANGE,
+ STR_0304_QUIT, STR_0305_QUIT_OPENTTD);
+ wid = new PaddingWidget(MakePadding(100 - 10, 100 - 10, 0, 0), wid);
+ vert_cont->push_back(wid);
+
+ /* padding around + panel */
+ wid = new PaddingWidget(MakePadding(10, 10, 7, 6), vert_cont);
+ wid = new PanelWidget(COLOUR_BROWN, STR_NULL, wid);
+
+ root->push_back(wid);
+
+ return root;
+}
+
static const WindowDesc _select_game_desc = {
WDP_CENTER, WDP_CENTER, 336, 195, 336, 195,
WC_SELECT_GAME, WC_NONE,
WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS,
- _select_game_widgets,
+ NULL
};
void ShowSelectGameWindow()
=== modified file 'src/settings_gui.cpp'
--- src/settings_gui.cpp 2008-08-16 08:47:44 +0000
+++ src/settings_gui.cpp 2008-08-16 10:33:17 +0000
@@ -300,6 +300,7 @@
CheckForMissingGlyphsInLoadedLanguagePack();
UpdateAllStationVirtCoord();
UpdateAllWaypointSigns();
+ UpdateAllWindows();
MarkWholeScreenDirty();
break;
=== modified file 'src/widget.cpp'
--- src/widget.cpp 2008-08-12 19:27:18 +0000
+++ src/widget.cpp 2008-08-16 12:57:50 +0000
@@ -2,14 +2,107 @@
/** @file widget.cpp Handling of the default/simple widgets. */
+/** @defgroup HierarchicalWidgets Hierarchical widgets
+ *
+ * Hierarchical widgets form a tree, where child widgets are contained in
+ * parent widgets in a window. In addition, the widgets are now more
+ * object-oriented. They can compute their own size, and by using the tree
+ * widget structure, the position of all widgets, and the size of the window as
+ * a whole can be computed automatically.
+ *
+ * \section Overview Overview
+ * Hierarchical widgets form a widget tree, where child widgets are contained in
+ * parent widgets in a window. In addition, the widgets are now more
+ * object-oriented. They can compute their own size, and by using the tree
+ * structure, the position of all widgets, and the size of the window as
+ * a whole can be computed automatically.
+ *
+ * This is not only convenient for GUI programmers, it also makes life for the
+ * translators much easier. After switching to a different language, the
+ * position and size of all the widgets and the window is simply re-calculated,
+ * adopting the new lengths of all text strings.
+ *
+ * Last but not least, having more object-oriented widgets
+ * makes it possible to derive your own special-purpose widgets quite easily.
+ *
+ * The following hierarchical widgets exist
+ * - Layout
+ * - EmptyWidget (NOT IPLEMENTED YET), an invisible widget that takes up space
+ * - PaddingWidget, a widget that adds additional padding space around its child widget
+ * - FixedTextFrameWidget and ParmTextFrameWidget (NOT IPLEMENTED YET), a widget with a frame and optional text around its child widget
+ * - ContainerWidget, a widget that aligns a number of child widgets
+ * - PanelWidget, a widget that adds a background to its child widget
+ *
+ * - %Window identification and manipulation
+ * - CloseBoxWidget (NOT IPLEMENTED YET), close window button at top-left of the window
+ * - FixedTextCaptionWidget and ParmTextCaptionWidget, widget displaying the window title, and hook for dragging the window
+ * - StickyBoxWidget (NOT IPLEMENTED YET), sticky window button, at top-right of the window
+ * - ResizeBoxWidget (NOT IPLEMENTED YET), resize button, at bottom-right of the window
+ *
+ * - Dropdown windows
+ * - FixedTextDropDownWidget and ParmTextDropDownWidget (NOT IMPLEMENTED YET)
+ * - FixedTextDropDownInWidget and ParmTextDropDownInWidget (NOT IMPLEMENTED YET)
+ *
+ * - Simple widgets
+ * - FixedTextLabelWidget and ParmTextLabelWidget (NOT IMPLEMENTED YET), widget that displays a string centered or left-aligned
+ * - FixedTextWidget and ParmTextWidget (NOT IMPLEMENTED YET), widget that displays a possibly truncated string
+ * - EditBoxWidget (NOT IPLEMENTED YET), a widget for entering short texts
+ * - CanvasWidget (NOT IPLEMENTED YET), a panel for drawing own contents
+ * - ViewportWidget (NOT IMPLEMENTED YET)
+ *
+ * - Buttons
+ * - ImageButtonWidget, a button with an image on it
+ * - ImagePushButtonWidget, a push button with an image on it
+ * - FixedTextButtonWidget and ParmTextButtonWidget, a button with a text on it
+ * - FixedTextPushButtonWidget and ParmTextPushButtonWidget, a push button with a text on it
+ *
+ * - Grids
+ * - MatrixWidget (NOT IPLEMENTED YET)
+ * - ScrollbarWidget (NOT IPLEMENTED YET), a generic scrollbar
+ *
+ * Often, the root of the widget tree starts with a vertical container with the window header, and the window body underneath it.
+ * The window header is normally a horizontal container with a close button, a caption, and optionally a sticky button.
+ * The window body is a PanelWidget with the window background. Below it are one or more nested containers and/or spacers,
+ * and finally (at the bottom of the tree) the various visible widgets that make up the controls of the window.
+ *
+ * \section SizeComputations Size computations
+ * There exist three sizes (a width and height in pixels) for each widget.
+ * - Minimal size the smallest size such that the widget can paint itself entirely,
+ * - Allocated size the smallest size such that all widgets together cover the entrie window, and
+ * - Current size the size of the widgets after (possibly) reisizing the window.
+ *
+ * Each widget knows its own size to paint its own contents on, either by querying dimensions of images or
+ * text, or by specification by the user (where eg minimal width is specified). From this, the minimal size of
+ * a window is computed in a bottom-up fashion. (This is implemented in the virtual BaseWidget::GetMinimalSize() method.)
+ *
+ * In general, different widgets will not cover a window completely with just their minimal size. For example,
+ * two text widgets underneath each other, the upper one with the text "a" and the bottom one with "aa", then
+ * the top one will be the width of "a" less wide.
+ * To compensate, a process called filling is performed in a top-down fashion. Widgets can state
+ * whether they allow horizontal and/or vertical filling (queried with the virtual BaseWidget::GetFill().),
+ * and if they do, and it is needed, they are lengthened to create a fully covered window area. (Done with the
+ * virtual BaseWidget::SetAllocatedSize().) The size given to the widgets after filling is called the
+ * allocated size. It is the size of all widgets such that the window has minimal size, and all widget sizes
+ * are consistent (together they cover the entire window). (The allocated size can be obtained from the
+ * BaseWidget::alloc_width and BaseWidget::alloc_height variables.)
+ *
+ * The allocated size of the widgets is the base size for resizing the window. Each widget knows its resize step-size
+ * (the step-size exists to ensure for example that a widget can always display a line of text completely). (The resize
+ * step-size can be queried with the virtual BaseWidget::GetResizeStep().) From this information, the new window size is
+ * computed relative to the allocated size (that is, the minimal window size).
+ */
+
#include "stdafx.h"
#include "openttd.h"
#include "core/math_func.hpp"
+#include "core/geometry_func.h"
#include "player_func.h"
#include "gfx_func.h"
#include "window_gui.h"
#include "window_func.h"
#include "widgets/dropdown_func.h"
+#include "strings_func.h"
+#include "spritecache.h"
#include "table/sprites.h"
#include "table/strings.h"
@@ -202,12 +295,20 @@
}
-/**
- * Paint all widgets of a window.
- * @param w Window
- */
+/** Paint all widgets of a window. */
void Window::DrawWidgets() const
{
+ if (root_widget != NULL) {
+ if (!root_widget->IsHidden())
+ root_widget->Draw(this);
+
+ if (this->flags4 & WF_WHITE_BORDER_MASK) {
+ DrawFrameRect(0, 0, this->width - 1, this->height - 1, 0xF, FR_BORDERONLY);
+ }
+
+ return;
+ }
+
const DrawPixelInfo* dpi = _cur_dpi;
for (uint i = 0; i < this->widget_count; i++) {
@@ -628,3 +729,1630 @@
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);
}
+
+/* ====================================================================== */
+
+/*
+ * WidgetFill
+ */
+
+/**
+ * Constructor
+ * @param xf Filling capability of a widget in horizontal direction
+ * @param yf Filling capability of a widget in vertical direction
+ */
+WidgetFill::WidgetFill(bool xf, bool yf)
+{
+ x_fill = xf;
+ y_fill = yf;
+}
+
+/** Copy constructor */
+WidgetFill::WidgetFill(const WidgetFill& wf)
+{
+ x_fill = wf.x_fill;
+ y_fill = wf.y_fill;
+}
+
+/** Assignment operator */
+WidgetFill& WidgetFill::operator=(const WidgetFill& wf)
+{
+ if (this != &wf) {
+ x_fill = wf.x_fill;
+ y_fill = wf.y_fill;
+ }
+ return *this;
+}
+
+/*
+ * Dimensions of text and images
+ */
+
+/**
+ * Compute width and height of provided string ID.
+ * @param str String id to measure
+ * @return Width and height of the string
+ */
+Dimension GetStringIdDimension(StringID str)
+{
+ if (str == STR_NULL) {
+ return MakeDimension(0,0);
+ }
+ char buffer[512];
+ GetString(buffer, str, lastof(buffer));
+ return GetStringBoundingBox(buffer);
+}
+
+/**
+ * Get size of a sprite.
+ * @param spr_id Sprite number used for query.
+ * @return Dimension of the given sprite
+ */
+Dimension GetSpriteIdDimension(SpriteID spr_id)
+{
+ const Sprite *sprite = GetSprite(spr_id);
+ assert(sprite != NULL);
+
+ return MakeDimension(sprite->width, sprite->height);
+}
+
+/*
+ * Padding
+ */
+
+Padding MakePadding(byte p_left, byte p_right, byte p_top, byte p_bottom)
+{
+ Padding p;
+
+ p.left_padding = p_left;
+ p.right_padding = p_right;
+ p.top_padding = p_top;
+ p.bottom_padding = p_bottom;
+ return p;
+}
+
+
+/* ====================================================================== */
+
+
+/**
+ * Constructor for self-supporting fixed string
+ * @param p_strid String to return
+ */
+BaseParmText::BaseParmText(StringID p_strid)
+{
+ strid = p_strid;
+}
+
+BaseParmText::~BaseParmText()
+{
+}
+
+/**
+ * Sets up environment for the string operation.
+ *
+ * Called just before returning the string to the caller
+ */
+void BaseParmText::Prepare()
+{
+}
+
+/** Function that sets up the string environment for the string operation, and returns the string */
+StringID BaseParmText::GetStrId()
+{
+ Prepare();
+ return strid;
+}
+
+/**
+ * Constructor for self-supporting string with single parameter
+ * @param p_strid String to return
+ * @param p_param0 Parameter to set just before returning the string
+ */
+SingleParmText::SingleParmText(StringID p_strid, uint64 p_param0)
+ : BaseParmText(p_strid)
+{
+ param0 = p_param0;
+}
+
+void SingleParmText::Prepare()
+{
+ SetDParam(0, param0);
+}
+
+
+/* ====================================================================== */
+
+/*
+ * BaseWidget
+ */
+
+/**
+ * Base class of hierarchical widgets.
+ */
+BaseWidget::BaseWidget()
+{
+ hidden = false;
+}
+
+BaseWidget::~BaseWidget()
+{
+}
+
+/* Size computations */
+
+/**
+ * Return width and height widget filling capabilities.
+ *
+ * This function is assumed to be cheap to call. If the value is expensive to
+ * compute, do it as part of a call to \c GetMinimalSize(false) and use a cache.
+ *
+ * For more information of how this function is used in size computations, read the documentation of the GetMinimalSize() function.
+ *
+ * @return Horizontal and vertical filling capabilities
+ */
+WidgetFill BaseWidget::GetFill()
+{
+ return WidgetFill(false, false);
+}
+
+/**
+ * Return resize step in horizontal (width) and vertical (height) direction.
+ *
+ * This function is assumed to be cheap to call. If the value is expensive to
+ * compute, do it as part of a call to \c GetMinimalSize(false) and use a cache.
+ *
+ * Returned values should always be non-negative, \c 0 means that no resize is allowed in that direction.
+ * For more information on how resize steps work, see the documentation of the SetSize() function.
+ *
+ * @return Resize step in horizontal and vertical direction
+ */
+Dimension BaseWidget::GetResizeStep()
+{
+ return MakeDimension(0, 0);
+}
+
+/**
+ * Set the top-left position of the widget relative to the top-left corner of its window
+ * @param p_left Offset of the left edge widget from the left edge of its window
+ * @param p_top Offset of the top edge widget from the top edge of its window
+ */
+void BaseWidget::SetPosition(uint16 p_left, uint16 p_top)
+{
+ left = p_left;
+ top = p_top;
+}
+
+/**
+ * @fn Dimension BaseWidget::GetMinimalSize(bool p_use_cache)
+ * @brief Get minimal size needed by the widget.
+ *
+ * The minimal size of a widget is the width and height (in pixels) needed for
+ * correctly drawing itself. Elementary widgets such as ImageWidget can compute
+ * this size by themselves. Other widgets (such as the ContainerWidget) consult
+ * their child widgets for the computation. The latter causes exponentially
+ * increasing computing costs. To decrease these costs, computed values should
+ * be cached such that they are cheap to reproduce.
+ *
+ * The minimal size of a widget is normally different for each widget (for
+ * example, a widget containing "aa" needs twice as much display space as a
+ * widget containing "a"). In a window these differences in width and height
+ * between widgets causes inconsistencies (not all parts of the window are
+ * covered as they should be). The process of enlarging (not resizing!) some of
+ * the widgets to make them consistent for viewing in a window is called
+ * 'filling' (and the resulting size of a widget is called 'allocated size').
+ * The GetFill() function returns for a widget whether it can be filled in
+ * horizontal (width) direction and/or in vertical (height) direction.
+ *
+ * The final value that is computed in the \c GetMinimalSize(false) function is
+ * the resize step. Resizing of a window is not done pixel by pixel, but in
+ * multiples thereof. For example, a window may get resized in multiples of 12
+ * pixels horizontally, and 0 pixels (ie no resize allowed) vertically.
+ * This value can be queried with the GetResizeStep() function. See the
+ * documentation of the SetSize() function for more information.
+ *
+ * @param p_use_cache If \c true, do not compute the value, but use a cheap
+ * alternative for example return a cached value.
+ * The caller should only set this flag when it is safe to
+ * do so, for example immediately after a call without this
+ * flag set.
+ *
+ * @return Minimal size of the widget needed for drawing itself correctly.
+ */
+
+/**
+ * @fn void BaseWidget::SetAllocatedSize(uint16 p_width, uint16 p_height)
+ * @brief Set the allocated size of the widget.
+ *
+ * The function should perform size allocation on itself and its child widgets, and may perform filling from minimal
+ * size on its child widgets to make them fit nicely in its allocated space.
+ *
+ * The set values can be obtained again from the widget using the alloc_width and alloc_height variables.
+ *
+ * @param p_width Allocated width of the widget
+ * @param p_height Allocated height of the widget
+ */
+
+/**
+ * @fn void BaseWidget::SetSize(uint16 p_width, uint16 p_height)
+ * Set size of the widget.
+ *
+ * Caller may have applied a number of resize steps from the allocated size of the widget to reach the specified size.
+ *
+ * @param p_width Width of the widget after resizing
+ * @param p_height Height of the widget after resizing
+ */
+
+/**
+ * @fn void BaseWidget::Draw(const Window *w)
+ * @brief Draw the widget.
+ * @param w Window that the widget belongs to (used for accessing window::flags4 and Window::caption_color)
+ */
+
+/**
+ * Get the widget number of the widget
+ * @return The widget number of the widget if available, else it returns INVALID_WIDGET_NUMBER
+ * @see WidgetNumber
+ */
+byte BaseWidget::GetWidgetNumber()
+{
+ return INVALID_WIDGET_NUMBER;
+}
+
+/**
+ * @fn BaseWidget::GetWidgetFromPos(const Point& pt)
+ * Find the deepest widget at position \a pt
+ *
+ * This function performs the conversion of a x/y position in a window to a widget at that position.
+ * It is used for finding click targets and for getting tool tips.
+ * @param pt The given x/y position
+ * @return A pointer to the deepest widget at the given position or \c NULL if no widget found.
+ */
+
+/**
+ * Get tooltip string of the widget
+ * @return The string identification of the tooltip. \c STR_NULL means that no tooltip exists for the widget
+ */
+StringID BaseWidget::GetTooltip()
+{
+ return STR_NULL;
+}
+
+/**
+ * Return whether the widget is lowered (pushed down).
+ *
+ * Set the value by the SetLowered() function.
+ * Several widgets cannot be lowered nor raised. They return the default value \c false
+ * @return \c true if the widget is lowered (pressed), \c false otherwise
+ */
+bool BaseWidget::IsLowered() const
+{
+ return false;
+}
+
+/**
+ * Return whether the widget is disabled (greyed out).
+ *
+ * Set the value by the SetDisabled() function.
+ * Several widgets cannot be disabled nor enabled. They return the default value \c false
+ * @return \c true if the widget is disabled, \c false if it is enabled
+ */
+bool BaseWidget::IsDisabled() const
+{
+ return false;
+}
+
+/**
+ * Return whether the widget is hidden (not shown to the user).
+ *
+ * Set the value by the SetHidden() function. By default, widgets are visible
+ * (shown). To make functionality invisible for the user, hide the top widget.
+ * To make the widget available to the user, show the top widget (\c
+ * SetHidden(false) ), and re-initalize the window (Window::ReInitializeWindow()).
+ *
+ * @return \c true if the widget is hidden, \c false if it is shown
+ */
+bool BaseWidget::IsHidden() const { return hidden; }
+
+/**
+ * Hide or show the widget
+ *
+ * Enable or disable visibility of the widget. See documentation of IsHidden() for more details.
+ * @param p_hide If \c true, hide the widget. If \c false, make it visible (show it to the user)
+ *
+ * @note After toggling this value, the size and positions of the entire window
+ * become invalid. Recompute them with Window::ReInitializeWindow()
+ */
+void BaseWidget::SetHidden(bool p_hide)
+{
+ hidden = p_hide;
+}
+
+/**
+ * Mark widget as dirty (in need of repaint)
+ * @ingroup Dirty
+ *
+ * @param w Window that the widget belongs to
+ */
+void BaseWidget::Invalidate(const Window *w)
+{
+ const int abs_left = w->left + left;
+ const int abs_top = w->top + top;
+
+ SetDirtyBlocks(abs_left, abs_top, abs_left + width, abs_top + height);
+}
+
+/**
+ * \fn void BaseWidget::RaiseAndInvalidateAll(const Window *w)
+ * Raise all lowered push buttons, and mark them as invalid (in need of repainting) to get their state updated at the screen too.
+ * @param w Window that the widget belongs to
+ */
+
+/**
+ * \fn BaseCaptionWidget *BaseWidget::FindCaptionWidget()
+ * Find the caption widget of the window by traversing the widget tree
+ */
+
+/* ====================================================================== */
+
+/*
+ * Container widget
+ */
+
+/**
+ * Widget containing one or more child widgets
+ * @param p_is_hor If \c true, it is a horizontal container (childs are put after each other).
+ * Otherwise it is a vertical container (childs are put below each other).
+ * @param p_eq_size Childs that can resize in the direction of the container are kept equally large
+ * (as much as possible)
+ * @param p_pre Additional amount of padding space before/above first child
+ * @param p_inter Additional amount of padding between two childs
+ * @param p_post Additional amount of padding after/below last child
+ */
+ContainerWidget::ContainerWidget(bool p_is_hor, bool p_eq_size, byte p_pre, byte p_inter, byte p_post)
+ : BaseWidget()
+{
+ is_horizontal = p_is_hor;
+ equal_size = p_eq_size;
+ pre_padding = p_pre;
+ inter_padding = p_inter;
+ post_padding = p_post;
+
+ /* empty minimal area means that we (very very likely) have no cached value */
+ minimal_width = 0;
+ minimal_height = 0;
+}
+
+ContainerWidget::~ContainerWidget()
+{
+ while (!widgets.empty()) {
+ delete widgets.back();
+ widgets.pop_back();
+ }
+}
+
+/**
+ * Add child widget to the container
+ * @param wid Child widget to add
+ */
+void ContainerWidget::push_back(BaseWidget *wid)
+{
+ widgets.push_back(wid);
+}
+
+/*
+ * 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.
+ *
+ * ContainerWidget::SplitInPrimes() decodes a value into its primes.
+ * ContainerWidget::SmallestCommonMultiplier() computes the resulting smallest
+ * common multiplier.
+ */
+
+/* static */ const byte ContainerWidget::m_primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
+
+/**
+ * Split value \a num (bigger than 0) in prime numbers (using the ContainerWidget::m_primes
+ * array), and keep track of the maximal count of each prime in the \a
+ * maxcount array */
+void ContainerWidget::SplitInPrimes(uint num, uint maxcount[lengthof(m_primes)])
+{
+ for (uint idx = 0; idx < lengthof(m_primes) && num > 1; idx++) {
+ uint cnt = 0;
+ while (num > 1 && (num % m_primes[idx]) == 0) {
+ cnt++;
+ num /= m_primes[idx];
+ }
+ if (maxcount[idx] < cnt)
+ maxcount[idx] = cnt;
+ }
+ assert(num == 1); // otherwise the list prim is not long enough
+}
+
+/**
+ * Compute the smallest common multiple
+ * @param maxcount Array of prime counts
+ */
+uint16 ContainerWidget::SmallestCommonMultiple(uint maxcount[lengthof(m_primes)])
+{
+ uint val = 1;
+ for (uint idx = 0; idx < lengthof(m_primes); idx++) {
+ if (maxcount[idx] > 0)
+ val *= m_primes[idx] * maxcount[idx];
+ }
+ assert(val <= 0xffff);
+ return (uint16)(val & 0xffff);
+}
+
+Dimension ContainerWidget::GetMinimalSize(bool p_use_cache)
+{
+ WidgetList::iterator iter;
+
+ if (IsHidden())
+ return MakeDimension(0, 0);
+
+ if (!p_use_cache || (minimal_width == 0 && minimal_height == 0)) {
+ /* Container needs to re-compute minimal size */
+
+ /* Have childs compute their minimal size, filling, and resize steps */
+ for (iter = widgets.begin(); iter != widgets.end(); iter++) {
+ if ((*iter)->IsHidden()) continue;
+
+ (*iter)->GetMinimalSize(p_use_cache);
+ }
+ ComputeMinimalSize();
+ ComputeFill();
+ ComputeResizeStep();
+ }
+ return MakeDimension(minimal_width, minimal_height);
+}
+
+/**
+ * Compute minimal size of the container, and store the answer in \a minimal_width and \a minimal_height
+ * @pre If is safe to used cached GetMinimalSize()/GetFill()/GetResizeStep() values of childs
+ *
+ * @note Due to the repackaging done by ChildGetMinimalSize() and ChildGetFill(), most of the code
+ * can be read as-if the container is horizontal.
+ */
+void ContainerWidget::ComputeMinimalSize()
+{
+ uint16 largest_eq_size = 0; // Maximal size of the childs that must be equally sized
+ int num_eq_size = 0; // Number of childs that must have equal size
+ bool first = true; // Yet to do first child
+
+ uint16 needed_width = 0; // Needed (minimal) width by the container
+ uint16 needed_height = 0; // Needed (minimal) height by the container
+ /* Compute horizontal and vertical dimensions */
+ for (WidgetList::iterator iter = widgets.begin(); iter != widgets.end(); iter++) {
+ if ((*iter)->IsHidden()) continue;
+
+ /* add pre/inter padding */
+ if (first) {
+ first = false;
+ needed_width += pre_padding;
+ } else {
+ needed_width += inter_padding;
+ }
+
+ Dimension dim = ChildGetMinimalSize(*iter, true);
+ /* handle width */
+ if (!equal_size)
+ needed_width += dim.width;
+ else {
+ WidgetFill wf = ChildGetFill(*iter);
+ if (wf.x_fill) {
+ /* Child must have unknown equal initial size.
+ * Rather than adding the size now, we keep an administration of maximal size and count,
+ * and add the size afterwards
+ */
+ num_eq_size++;
+ assert(dim.width >= 0 && dim.width <= 0xffff);
+ largest_eq_size = max(largest_eq_size, (uint16)dim.width);
+ } else
+ needed_width += dim.width; // child cannot resize
+ }
+
+ /* handle height */
+ assert(dim.height >= 0 && dim.height <= 0xffff);
+ needed_height = max(needed_height, (uint16)dim.height);
+ }
+
+ /* add post padding */
+ if (!first)
+ needed_width += post_padding;
+
+ /* add width for equally sized childs */
+ needed_width += num_eq_size * largest_eq_size;
+
+ /* Store computed minimal size in minimal_width and minimal_height, depending on the container direction */
+ if (is_horizontal) {
+ minimal_width = needed_width;
+ minimal_height = needed_height;
+ } else {
+ minimal_width = needed_height;
+ minimal_height = needed_width;
+ }
+}
+
+/**
+ * Retrieve child minimal dimensions and re-package.
+ *
+ * This is an internal method, it queries the child minimal size, and re-packs the answer so the class needs only one
+ * minimal size computation routine.
+ * @param wid Child widget to query
+ * @param p_use_cache Allow the child to use a cached value
+ * @return Repackaged Dimension where \em Dimension::width is \b always in the direction of the container, and
+ * Dimension::height is the minimal size in the other direction
+ */
+Dimension ContainerWidget::ChildGetMinimalSize(BaseWidget *wid, bool p_use_cache)
+{
+ Dimension dim = wid->GetMinimalSize(p_use_cache);
+ if (!is_horizontal)
+ Swap(dim.width, dim.height);
+
+ return dim;
+}
+
+/**
+ * Compute filling capabilities of the container, and store the answer in \a x_fill and \a y_fill
+ *
+ * @note Due to the repackaging done by ChildGetFill(), most of the code can be read as-if the container is horizontal.
+ */
+void ContainerWidget::ComputeFill()
+{
+ bool hor_fill = false; // Container can fill if at least one child can
+ bool vert_fill = true; // Container can fill if all childs can
+
+ for (WidgetList::iterator iter = widgets.begin(); iter != widgets.end(); iter++) {
+ if ((*iter)->IsHidden()) continue;
+
+ WidgetFill fill = ChildGetFill(*iter);
+ hor_fill |= fill.x_fill;
+ vert_fill &= fill.y_fill;
+ }
+
+ /* Assign computed values to the structure variables depending on the direction of the container */
+ if (is_horizontal) {
+ x_fill = hor_fill;
+ y_fill = vert_fill;
+ } else {
+ x_fill = vert_fill;
+ y_fill = hor_fill;
+ }
+}
+
+/**
+ * Retrieve child filling capabilities and re-package.
+ *
+ * This is an internal method, it queries the filling capabilities of the child, and re-packages the answer so the
+ * class only needs one computation routine.
+ * @param child Child widget to query
+ * @return Repackaged WidgetFill where \em WidgetFill::x_fill is \b always the filling capability in the direction of
+ * the container, and \em WidgetFill::y_fill is the filling capability in the other direction
+ */
+WidgetFill ContainerWidget::ChildGetFill(BaseWidget *wid)
+{
+ WidgetFill wf = wid->GetFill();
+ if (!is_horizontal)
+ Swap(wf.x_fill, wf.y_fill);
+
+ return wf;
+}
+
+WidgetFill ContainerWidget::GetFill()
+{
+ return WidgetFill(x_fill, y_fill);
+}
+
+/**
+ * Compute resize step capabilities of the container, and store them in \a x_resize_step and \a y_resize_step.
+ * Vertical resize step is the smallest common multiple of all vertical child resize steps iff all childs can resize
+ * vertically. Otherwise, it is \c 0.
+ * Horizontal resize step is \c 1 when at least one child can resize horizontally with step size \c 1. Otherwise, all
+ * non-zero horizontal child resize steps must be equal (and the container will also be able to resize with that
+ * amount in horizontal direction). In all other cases, horizontal resize is \c 0.
+ *
+ * @note Due to the repackaging done by ChildGetResizeStep(), most of the code can be read as-if the container is horizontal.
+ */
+void ContainerWidget::ComputeResizeStep()
+{
+ /* array for keeping prime counts of vertical resize steps */
+ uint maxcount[lengthof(m_primes)];
+ for (uint idx = 0; idx < lengthof(m_primes); idx++)
+ maxcount[idx] = 0;
+ bool vert_resizable = true; // All childs considered until now are vertically resizable
+
+ uint16 hor_smallest = 0; // Smallest non-zero horizontal child resize found (0 means no resizing child found yet)
+ uint16 hor_biggest = 0; // Biggest horizontal child resize found so far
+
+ for (WidgetList::iterator iter = widgets.begin(); iter != widgets.end(); iter++) {
+ Dimension child_resize = ChildGetResizeStep(*iter);
+ if (child_resize.width > 0) {
+ if (hor_smallest == 0 || hor_smallest > child_resize.width)
+ hor_smallest = child_resize.width;
+ hor_biggest = max(hor_biggest, (uint16)(child_resize.width));
+ }
+
+ if (vert_resizable) {
+ if (child_resize.height == 0)
+ vert_resizable = false;
+ else {
+ SplitInPrimes(child_resize.height, maxcount);
+ }
+ }
+ }
+
+ uint16 hor_step;
+ if (hor_smallest == 0 || hor_smallest == 1)
+ /* 0 means no child can resize -> container can also not resize -> 0
+ * 1 means at least one child has horizontal resize step size 1 -> 1
+ */
+ hor_step = hor_smallest;
+ else
+ hor_step = (hor_smallest == hor_biggest) ? hor_smallest : 0;
+
+ uint16 vert_step = vert_resizable ? SmallestCommonMultiple(maxcount) : 0;
+
+ /* Assign computed values to the right struct variable, depending on the direction of the container */
+ if (is_horizontal) {
+ x_resize_step = hor_step;
+ y_resize_step = vert_step;
+ } else {
+ x_resize_step = vert_step;
+ y_resize_step = hor_step;
+ }
+}
+
+/**
+ * Retrieve child resize step and re-package.
+ *
+ * This is an internal method, it queries the resize step of the child, and re-packages the answer so the
+ * class only needs one computation routine.
+ * @param child Child widget to query
+ * @return Repackaged Dimension where \em Dimension::width is \b always the resize step in the direction of
+ * the container, and \em Dimension::height is the resize step in the other direction
+ */
+Dimension ContainerWidget::ChildGetResizeStep(BaseWidget *wid)
+{
+ Dimension dim = wid->GetResizeStep();
+ if (!is_horizontal)
+ Swap(dim.width, dim.height);
+
+ return dim;
+}
+
+Dimension ContainerWidget::GetResizeStep()
+{
+ return MakeDimension(x_resize_step, y_resize_step);
+}
+
+
+/** Draw the contents of the container to the screen */
+void ContainerWidget::Draw(const Window *w)
+{
+ if (IsHidden()) return;
+
+ const DrawPixelInfo *const dpi = _cur_dpi;
+ const int right = left + width - 1;
+ const int bottom = top + height - 1;
+ if (dpi->left > right || dpi->left + dpi->width <= left || dpi->top > bottom || dpi->top + dpi->height <= top) return;
+
+ for (WidgetList::iterator iter = widgets.begin(); iter != widgets.end(); iter++) {
+ if ((*iter)->IsHidden()) continue;
+
+ (*iter)->Draw(w);
+ }
+}
+
+/**
+ * Assign sizes to the child widgets
+ *
+ * This is a generic size setting routine. It is used both for resizing and filling (where \c true is translated to \c
+ * 1). In addition, it uses re-packaging towards the childs to avoid duplication.
+ *
+ * Basic strategy is to divide the provided additional space between the childs that can grow in size, starting with the
+ * childs with the biggest resize step (so we can distribute the space that was rounded away to the other childs).
+ * To prevent further rounding errors (eg dividing 2 pixels between 3 childs), an incremental form of the formula is used.
+ *
+ * @param given_width The provided horizontal length for the container (not re-packaged!)
+ * @param given_height The provided vertical length for the container (not re-packaged!)
+ * @param min_width Our computed minimal or allocated horizontal length (non re-packaged!)
+ * @param min_height Our computed minimal or allocated vertical length (non re-packaged!)
+ * @param resize_step_func %ContainerWidget function to obtain re-packaged child resize step
+ * @param size_func %ContainerWidget function to obtain size of the child
+ * @param set_size_func %ContainerWidget function to set new size of the child
+ */
+void ContainerWidget::GenericSetSize(uint16 given_width, uint16 given_height, uint16 min_width, uint16 min_height,
+ Dimension (ContainerWidget::*resize_step_func)(BaseWidget *),
+ Dimension (ContainerWidget::*size_func)(BaseWidget *),
+ void (ContainerWidget::*set_size_func)(BaseWidget *, const Dimension&))
+{
+ WidgetList::iterator iter;
+
+ /* re-package our parameters */
+ if (!is_horizontal) {
+ Swap(given_width, given_height);
+ Swap(min_width, min_height);
+ }
+
+ int additional_length = given_width - min_width; // Additional width given to us
+
+ /* count number of childs that would like a piece of the pie. Also decide the largest resize step as preparation
+ * for the next step. Finally, childs that cannot resize (in direction of the container) are handled directly.
+ */
+ int num_changing_childs = 0; // Number of childs that can change size
+ uint largest_child_size = 0; // Largest child (needed for equal_size computing)
+ uint largest_step_size = 0; // Largest step size so far
+ for (iter = widgets.begin(); iter != widgets.end(); iter++) {
+ if ((*iter)->IsHidden()) continue;
+
+ Dimension step = (this->*resize_step_func)(*iter);
+ Dimension dim = (this->*size_func)(*iter);
+ if (step.width == 0) {
+ /* Non-resizing child, deal with it now */
+
+ /* Set appropiate height, if allowed to do so */
+ if (step.height > 0 && given_height > dim.height) {
+ int increment = given_height - dim.height;
+ increment -= increment % step.height;
+ assert(increment >= 0);
+ dim.height += increment;
+ }
+ (this->*set_size_func)(*iter, dim);
+ } else {
+ num_changing_childs++;
+ largest_child_size = max(largest_child_size, (uint)(dim.width));
+ largest_step_size = max(largest_step_size, (uint)(step.width));
+ }
+ }
+
+ /* Divide the pie of additional space between the resizing childs */
+ while (num_changing_childs > 0) {
+ assert(largest_step_size > 0);
+
+ uint second_biggest_resize = 0; // As side effect, compute the next largest step size to use
+ for (iter = widgets.begin(); iter != widgets.end(); iter++) {
+ if ((*iter)->IsHidden()) continue;
+
+ Dimension step = (this->*resize_step_func)(*iter);
+ if ((uint)(step.width) > largest_step_size) continue; // child already handled
+ if ((uint)(step.width) == largest_step_size) {
+ /* Found a child that should be resized now */
+ Dimension dim = (this->*size_func)(*iter);
+
+ /* Set appropiate width */
+ int increment = additional_length / num_changing_childs; // give child piece of the pie
+ /* If needed, stretch child for equal_size requirements. This is not part of the pie to divide!! */
+ int eq_size_stretching = equal_size ? (largest_child_size - dim.width) : 0;
+
+ increment -= (increment + eq_size_stretching) % largest_step_size;
+ assert(increment + eq_size_stretching >= 0);
+ dim.width += increment + eq_size_stretching;
+ additional_length -= increment;
+
+ /* Set appropiate height, if allowed to do so */
+ if (step.height > 0 && given_height > dim.height) {
+ int increment = given_height - dim.height;
+ increment -= increment % step.height;
+ assert(increment >= 0);
+ dim.height += increment;
+ }
+ (this->*set_size_func)(*iter, dim);
+ num_changing_childs--;
+ } else // step.width < largest_step_size holds here
+ second_biggest_resize = max(second_biggest_resize, (uint)(step.width));
+ }
+
+ largest_step_size = second_biggest_resize;
+ }
+}
+
+/**
+ * Internal function to obtain child filling capabilities as resize step
+ * @param wid Child widget to query filling capabilities from
+ * @return The filling capabilities, converted to a resize step of 0/1, and re-packaged so
+ * Dimension::width is the filling in the direction of the container
+ * @pre It is safe to use cached filling value
+ */
+Dimension ContainerWidget::ChildFillingResizeStep(BaseWidget *wid)
+{
+ WidgetFill wf = ChildGetFill(wid);
+ return MakeDimension(wf.x_fill, wf.y_fill);
+}
+
+/**
+ * Internal function to obtain child minimal size as base size
+ * @param wid Child widget to get minimal size from
+ * @return The minimal size, re-packaged so Dimension::width is the filling in the direction of the container
+ * @pre It is safe to use cached filling value
+ */
+Dimension ContainerWidget::ChildGetMinimalSize(BaseWidget *wid)
+{
+ return ChildGetMinimalSize(wid, true);
+}
+
+/**
+ * Internal function to push allocated size to child
+ * @param wid Child widget
+ * @param dim Re-packaged size to push to the child
+ */
+void ContainerWidget::ChildSetAllocatedSize(BaseWidget *wid, const Dimension& dim)
+{
+ if (is_horizontal)
+ wid->SetAllocatedSize(dim.width, dim.height);
+ else
+ wid->SetAllocatedSize(dim.height, dim.width);
+}
+
+void ContainerWidget::SetAllocatedSize(uint16 p_width, uint16 p_height)
+{
+ alloc_width = p_width;
+ alloc_height = p_height;
+
+ GenericSetSize(p_width, p_height, minimal_width, minimal_height, &ContainerWidget::ChildFillingResizeStep,
+ &ContainerWidget::ChildGetMinimalSize, &ContainerWidget::ChildSetAllocatedSize);
+}
+
+/**
+ * Internal function to get allocated size of a child
+ * @param wid Child widget
+ * @return Re-packaged allocated size of the child
+ */
+Dimension ContainerWidget::ChildGetAllocatedSize(BaseWidget *wid)
+{
+ if (is_horizontal)
+ return MakeDimension(wid->alloc_width, wid->alloc_height);
+ else
+ return MakeDimension(wid->alloc_height, wid->alloc_width);
+}
+
+/**
+ * Internal function to push size after resizing to child
+ * @param wid Child widget
+ * @param dim Re-packaged size to push to the child
+ */
+void ContainerWidget::ChildSetSize(BaseWidget *wid, const Dimension& dim)
+{
+ if (is_horizontal)
+ wid->SetSize(dim.width, dim.height);
+ else
+ wid->SetSize(dim.height, dim.width);
+}
+
+void ContainerWidget::SetSize(uint16 p_width, uint16 p_height)
+{
+ width = p_width;
+ height = p_height;
+
+ GenericSetSize(p_width, p_height, alloc_width, alloc_height, &ContainerWidget::ChildGetResizeStep,
+ &ContainerWidget::ChildGetAllocatedSize, &ContainerWidget::ChildSetSize);
+}
+
+void ContainerWidget::SetPosition(uint16 p_left, uint16 p_top)
+{
+ bool first = true; // First child not yet encountered
+ int offset = 0; // Offset relative to direction of the container
+
+ top = p_top;
+ left = p_left;
+
+ for (WidgetList::iterator iter = widgets.begin(); iter != widgets.end(); iter++) {
+ if ((*iter)->IsHidden()) continue;
+
+ if (first) {
+ offset += pre_padding;
+ first = false;
+ } else
+ offset += inter_padding;
+
+ if (is_horizontal) {
+ (*iter)->SetPosition(p_left + offset, p_top + ((height - (*iter)->height) >> 1));
+ offset += (*iter)->width;
+ } else {
+ (*iter)->SetPosition(p_left + ((width - (*iter)->width) >> 1), p_top + offset);
+ offset += (*iter)->height;
+ }
+ }
+}
+
+/**
+ * Find widget under a position.
+ * @param pt Position being investigated
+ * @return Widget address if one can be found
+ * @note The container widget is never returned because no event handler can do anything with it
+ */
+BaseWidget *ContainerWidget::GetWidgetFromPos(const Point& pt)
+{
+ if (!IsHidden() && IsInsideBS(pt.x, left, width) && IsInsideBS(pt.y, top, height)) {
+ for (WidgetList::iterator iter = widgets.begin(); iter != widgets.end(); iter++) {
+ BaseWidget *wid = (*iter)->GetWidgetFromPos(pt);
+ if (wid != NULL) return wid;
+ }
+ }
+ return NULL;
+}
+
+void ContainerWidget::RaiseAndInvalidateAll(const Window *w)
+{
+ for (WidgetList::iterator iter = widgets.begin(); iter != widgets.end(); iter++) {
+ if ((*iter)->IsHidden()) continue;
+
+ (*iter)->RaiseAndInvalidateAll(w);
+ }
+}
+
+BaseCaptionWidget *ContainerWidget::FindCaptionWidget()
+{
+ for (WidgetList::iterator iter = widgets.begin(); iter != widgets.end(); iter++) {
+ if ((*iter)->IsHidden()) continue;
+
+ BaseCaptionWidget *wid = (*iter)->FindCaptionWidget();
+ if (wid != NULL) return wid;
+ }
+ return NULL;
+}
+
+/* ====================================================================== */
+
+/*
+ * VisibleWidget
+ */
+
+/**
+ * Constructor
+ * @param p_number Widget number of the widget. See WidgetNumber for more information
+ * @param p_color Background color of the widget, often a value of the Colours enum
+ * @param p_tip Tool tip string
+ */
+VisibleWidget::VisibleWidget(byte p_number, byte p_color, StringID p_tip): BaseWidget()
+{
+ number = p_number;
+ color = p_color;
+ tip = p_tip;
+ disabled = false;
+}
+
+void VisibleWidget::SetDisabled(bool p_disabled)
+{
+ disabled = p_disabled;
+}
+
+bool VisibleWidget::IsDisabled() const
+{
+ return disabled;
+}
+
+StringID VisibleWidget::GetTooltip()
+{
+ return tip;
+}
+
+byte VisibleWidget::GetWidgetNumber()
+{
+ return number;
+}
+
+void VisibleWidget::SetAllocatedSize(uint16 p_width, uint16 p_height)
+{
+ alloc_width = p_width;
+ alloc_height = p_height;
+}
+
+void VisibleWidget::SetSize(uint16 p_width, uint16 p_height)
+{
+ width = p_width;
+ height = p_height;
+}
+
+BaseWidget *VisibleWidget::GetWidgetFromPos(const Point& pt)
+{
+ if (!IsHidden() && IsInsideBS(pt.x, left, width) && IsInsideBS(pt.y, top, height))
+ return this;
+ return NULL;
+}
+
+void VisibleWidget::Draw(const Window *w)
+{
+ if (IsHidden()) return; // widget is hidden, don't draw
+
+ /* If widget is not dirty, don't draw */
+ const DrawPixelInfo *const dpi = _cur_dpi;
+ const int right = left + width - 1;
+ const int bottom = top + height - 1;
+ if (dpi->left > right || dpi->left + dpi->width <= left || dpi->top > bottom || dpi->top + dpi->height <= top) return;
+
+ DrawBorder(w);
+ DrawContents(w);
+
+ /* Draw greyed out checker pattern */
+ if (IsDisabled()) {
+ GfxFillRect(left + 1, top + 1, right - 1, bottom - 1, _colour_gradient[color & 0xF][2], FILLRECT_CHECKER);
+ }
+}
+
+/**
+ * Draw the border of the widget.
+ *
+ * By default, it draws nothing. Overload to draw the border of your choice.
+ *
+ * @param w Window that the widget belongs to
+ */
+void VisibleWidget::DrawBorder(const Window *w)
+{
+}
+
+/**
+ * Draw inner part of the widget.
+ *
+ * By default, it draws nothing. Overload to draw the inner part of the widget
+ *
+ * @param w Window that the widget belongs to
+ */
+void VisibleWidget::DrawContents(const Window *w)
+{
+}
+
+BaseCaptionWidget *VisibleWidget::FindCaptionWidget()
+{
+ return NULL;
+}
+
+void VisibleWidget::RaiseAndInvalidateAll(const Window *w)
+{
+}
+
+/* ====================================================================== */
+
+/*
+ * PanelWidget
+ */
+
+/**
+ * Constructor
+ * @param p_backcol Colour of the background
+ * @param p_tip Tooltip to display
+ * @param p_child Pointer to the child widget
+ */
+PanelWidget::PanelWidget(byte p_backcol, StringID p_tip, BaseWidget *p_child): ParentWidget(p_child)
+{
+ color = p_backcol;
+ tip = p_tip;
+}
+
+StringID PanelWidget::GetTooltip()
+{
+ return tip;
+}
+
+byte PanelWidget::GetWidgetNumber()
+{
+ return INVALID_WIDGET_NUMBER;
+}
+
+BaseWidget *PanelWidget::GetWidgetFromPos(const Point& pt)
+{
+ if (!IsHidden() && IsInsideBS(pt.x, left, width) && IsInsideBS(pt.y, top, height)) {
+ BaseWidget *child_wid = child->GetWidgetFromPos(pt);
+ return (child_wid != NULL) ? child_wid : this;
+ }
+ return NULL;
+}
+
+Dimension PanelWidget::GetMinimalSize(bool p_use_cache)
+{
+ Dimension dim = child->GetMinimalSize(p_use_cache);
+ dim.width += 4; // keep 2 pixel edge around the child
+ dim.height += 4;
+ return dim;
+}
+
+void PanelWidget::SetAllocatedSize(uint16 p_width, uint16 p_height)
+{
+ alloc_width = p_width;
+ alloc_height = p_height;
+
+ child->SetAllocatedSize(p_width - 4, p_height - 4);
+}
+
+void PanelWidget::SetSize(uint16 p_width, uint16 p_height)
+{
+ width = p_width;
+ height = p_height;
+
+ child->SetSize(p_width - 4, p_height - 4);
+}
+
+void PanelWidget::SetPosition(uint16 p_left, uint16 p_top)
+{
+ left = p_left;
+ top = p_top;
+
+ child->SetPosition(p_left + 2, p_top + 2);
+}
+
+void PanelWidget::Draw(const Window *w)
+{
+ if (IsHidden()) return;
+
+ const DrawPixelInfo *const dpi = _cur_dpi;
+ const int right = left + width - 1;
+ const int bottom = top + height - 1;
+ if (dpi->left > right || dpi->left + dpi->width <= left || dpi->top > bottom || dpi->top + dpi->height <= top) return;
+
+ DrawFrameRect(left, top, right, bottom, color, FR_NONE);
+
+ child->Draw(w);
+}
+
+/* ====================================================================== */
+
+/*
+ * LowerableWidget
+ */
+
+/**
+ * Constructor of a raisable widget
+ *
+ * By default, a widget is raised.
+ * A new raised or lowered state can be set with SetLowered(). The current state can be queried with IsLowered().
+ *
+ * @see BaseWidget::IsLowered()
+ */
+LowerableWidget::LowerableWidget(byte p_number, byte p_color, StringID p_tip): VisibleWidget(p_number, p_color, p_tip)
+{
+ lowered = false;
+}
+
+bool LowerableWidget::IsLowered() const
+{
+ return lowered;
+}
+
+/**
+ * Raise or lower the widget
+ * @param p_lower New state of the widget. \c true means that the widget is lowered, \c false means it is raised
+ */
+void LowerableWidget::SetLowered(bool p_lower)
+{
+ lowered = p_lower;
+}
+
+/**
+ * Raise all lowered push buttons, and mark them as invalid (in need of repainting) to get their state updated at the screen too.
+ * @param w Window that the widget belongs to
+ */
+void LowerableWidget::RaiseAndInvalidateAll(const Window *w)
+{
+ if (IsLowered()) {
+ SetLowered(false);
+ Invalidate(w);
+ }
+}
+
+/* ====================================================================== */
+
+/**
+ * Base class widget with a single child
+ * @param p_child Child widget
+ */
+ParentWidget::ParentWidget(BaseWidget *p_child): BaseWidget()
+{
+ child = p_child;
+ assert(child != NULL);
+}
+
+ParentWidget::~ParentWidget()
+{
+ delete child;
+}
+
+WidgetFill ParentWidget::GetFill()
+{
+ if (BaseWidget::IsHidden())
+ return WidgetFill(false, false);
+
+ return child->GetFill();
+}
+
+Dimension ParentWidget::GetResizeStep()
+{
+ if (BaseWidget::IsHidden())
+ return MakeDimension(0, 0);
+
+ return child->GetResizeStep();
+}
+
+StringID ParentWidget::GetTooltip()
+{
+ return child->GetTooltip();
+}
+
+byte ParentWidget::GetWidgetNumber()
+{
+ if (BaseWidget::IsHidden())
+ return INVALID_WIDGET_NUMBER;
+
+ return child->GetWidgetNumber();
+}
+
+BaseWidget *ParentWidget::GetWidgetFromPos(const Point& pt)
+{
+ if (BaseWidget::IsHidden()) return NULL;
+ return child->GetWidgetFromPos(pt);
+}
+
+bool ParentWidget::IsLowered() const
+{
+ return child->IsLowered();
+}
+
+bool ParentWidget::IsDisabled() const
+{
+ return child->IsDisabled();
+}
+
+void ParentWidget::RaiseAndInvalidateAll(const Window *w)
+{
+ child->RaiseAndInvalidateAll(w);
+}
+
+bool ParentWidget::IsHidden() const
+{
+ return BaseWidget::IsHidden() || child->IsHidden();
+}
+
+BaseCaptionWidget *ParentWidget::FindCaptionWidget()
+{
+ return child->FindCaptionWidget();
+}
+
+/* ====================================================================== */
+
+/**
+ *
+ * Constructor
+ * @param p_padding Additional space around the child widget
+ * @param p_child Child widget (managed by class)
+ */
+PaddingWidget::PaddingWidget(const Padding& p_padding, BaseWidget *p_child): ParentWidget(p_child)
+{
+ padding = p_padding;
+ min_size = MakeDimension(-1, 0);
+}
+
+Dimension PaddingWidget::GetMinimalSize(bool p_use_cache)
+{
+ if (BaseWidget::IsHidden())
+ return MakeDimension(0, 0);
+
+ if (!p_use_cache || min_size.width < 0) {
+ min_size = child->GetMinimalSize(p_use_cache);
+ min_size.width += padding.left_padding + padding.right_padding;
+ min_size.height += padding.top_padding + padding.bottom_padding;
+ }
+ return min_size;
+}
+
+void PaddingWidget::SetAllocatedSize(uint16 p_width, uint16 p_height)
+{
+ alloc_width = p_width;
+ alloc_height = p_height;
+
+ const uint16 hor_padding = padding.left_padding + padding.right_padding;
+ const uint16 vert_padding = padding.top_padding + padding.bottom_padding;
+
+ assert(p_width >= hor_padding);
+ assert(p_height >= vert_padding);
+
+ child->SetAllocatedSize(p_width - hor_padding, p_height - vert_padding);
+}
+
+void PaddingWidget::SetSize(uint16 p_width, uint16 p_height)
+{
+ width = p_width;
+ height = p_height;
+
+ const uint16 hor_padding = padding.left_padding + padding.right_padding;
+ const uint16 vert_padding = padding.top_padding + padding.bottom_padding;
+
+ assert(p_width >= hor_padding);
+ assert(p_height >= vert_padding);
+
+ child->SetSize(p_width - hor_padding, p_height - vert_padding);
+}
+
+void PaddingWidget::SetPosition(uint16 p_left, uint16 p_top)
+{
+ left = p_left;
+ top = p_top;
+
+ child->SetPosition(p_left + padding.left_padding, p_top + padding.top_padding);
+}
+
+void PaddingWidget::Draw(const Window *w)
+{
+ if (IsHidden() || child->IsHidden()) return;
+
+
+ const DrawPixelInfo *const dpi = _cur_dpi;
+ const int right = left + width - 1;
+ const int bottom = top + height - 1;
+ if (dpi->left > right || dpi->left + dpi->width <= left || dpi->top > bottom || dpi->top + dpi->height <= top) return;
+
+ child->Draw(w);
+}
+
+/* ====================================================================== */
+
+/*
+ * Text button widgets
+ */
+
+/**
+ * Constructor base text button widget class
+ * @param p_number Number of the widget, see WidgetNumber enum for more information
+ * @param p_backcol Background colour of the widget
+ * @param p_tip Tool tip of the widget
+ * @param p_two_texts If \c true and button is lowered, use next string (\c GetString() + 1)
+ */
+BaseTextButtonWidget::BaseTextButtonWidget(byte p_number, byte p_backcol, StringID p_tip, bool p_two_texts)
+ : LowerableWidget(p_number, p_backcol, p_tip)
+{
+ two_texts = p_two_texts;
+}
+
+Dimension BaseTextButtonWidget::GetMinimalSize(bool p_use_cache)
+{
+ StringID str = GetString();
+ Dimension dim = GetStringIdDimension(str);
+ dim.width += 2 + 2*3; // 1 pixel around the entire text, 3 pixels left, 3 pixels right
+ dim.height += 2;
+ if (two_texts) {
+ Dimension dim2 = GetStringIdDimension(str + 1);
+ dim2.width += 2 + 2*3;
+ dim2.height += 2;
+ dim = max(dim, dim2);
+ }
+ return dim;
+}
+
+WidgetFill BaseTextButtonWidget::GetFill()
+{
+ return WidgetFill(true, false);
+}
+
+void BaseTextButtonWidget::DrawBorder(const Window *w)
+{
+ const int right = left + width - 1;
+ const int bottom = top + height - 1;
+
+ bool clicked = IsLowered();
+ DrawFrameRect(left, top, right, bottom, color, (clicked) ? FR_LOWERED : FR_NONE);
+}
+
+void BaseTextButtonWidget::DrawContents(const Window *w)
+{
+ const int right = left + width - 1;
+ const int bottom = top + height - 1;
+
+ bool clicked = IsLowered();
+ StringID str = GetString() + (two_texts && clicked);
+ DrawStringCentered(((left + right + 1) >> 1) + clicked, ((top + bottom + 1) >> 1) - 5 + clicked, str, TC_FROMSTRING);
+}
+
+/**
+ * \fn BaseTextButtonWidget::GetString()
+ * Function to obtain the string to render in the label
+ */
+
+/**
+ * Text button widget with fixed text string
+ * @param p_number Number of the widget, see WidgetNumber enum for more information
+ * @param p_backcol Background colour of the widget
+ * @param p_strid Fixed string to render in the widget
+ * @param p_tip Tool tip of the widget
+ * @param p_two_texts If \c true and button is lowered, use next string (\a p_strid + 1)
+ */
+FixedTextButtonWidget::FixedTextButtonWidget(byte p_number, byte p_backcol, StringID p_strid, StringID p_tip, bool p_two_texts)
+ : BaseTextButtonWidget(p_number, p_backcol, p_tip, p_two_texts)
+{
+ strid = p_strid;
+}
+
+StringID FixedTextButtonWidget::GetString()
+{
+ return strid;
+}
+
+/**
+ * Text button widget with parameterized text string
+ * @param p_number Number of the widget, see WidgetNumber enum for more information
+ * @param p_backcol Background colour of the widget
+ * @param p_parmtxt Parameterized string to render in the widget
+ * @param p_tip Tool tip of the widget
+ * @param p_two_texts If \c true and button is lowered, use next string (\c p_parmtxt->GetStrId() + 1)
+ *
+ * @note Memory pointed to by \a p_parmtxt is not managed by the widget
+ */
+ParmTextButtonWidget::ParmTextButtonWidget(byte p_number, byte p_backcol, BaseParmText *p_parmtxt, StringID p_tip, bool p_two_texts)
+ : BaseTextButtonWidget(p_number, p_backcol, p_tip, p_two_texts)
+{
+ parm_text = p_parmtxt;
+}
+
+StringID ParmTextButtonWidget::GetString()
+{
+ return parm_text->GetStrId();
+}
+
+
+FixedTextPushButtonWidget::FixedTextPushButtonWidget(byte p_number, byte p_backcol, StringID p_strid, StringID p_tip, bool p_two_texts)
+ : FixedTextButtonWidget(p_number, p_backcol, p_strid, p_tip, p_two_texts)
+{
+}
+
+ParmTextPushButtonWidget::ParmTextPushButtonWidget(byte p_number, byte p_backcol, BaseParmText *p_parmtxt, StringID p_tip, bool p_two_texts)
+ : ParmTextButtonWidget(p_number, p_backcol, p_parmtxt, p_tip, p_two_texts)
+{
+}
+
+/* ====================================================================== */
+
+/*
+ * Image buttons
+ */
+
+/**
+ * Button with an image on it.
+ * @param p_number Number of the widget, see WidgetNumber enum for more information
+ * @param p_backcol Background colour of the widget
+ * @param p_img Image to display, if button is lowered and \a p_two_images, \c p_img+1 is displayed
+ * @param p_tip Tool tip of the widget
+ * @param p_two_images If \c true, use \a p_img + 1 for displaying
+ */
+ImageButtonWidget::ImageButtonWidget(byte p_number, byte p_backcol, SpriteID p_img, StringID p_tip, bool p_two_images)
+ : LowerableWidget(p_number, p_backcol, p_tip)
+{
+ image = p_img;
+ two_images = p_two_images;
+}
+
+Dimension ImageButtonWidget::GetMinimalSize(bool p_use_cache)
+{
+ Dimension dim = GetSpriteIdDimension(image);
+ dim.width += 5;
+ dim.height += 5;
+ if (two_images) {
+ Dimension dim2 = GetSpriteIdDimension(image + 1);
+ dim2.width += 5;
+ dim2.height += 5;
+ dim = max(dim, dim2);
+ }
+ return dim;
+}
+
+void ImageButtonWidget::DrawBorder(const Window *w)
+{
+ const int right = left + width - 1;
+ const int bottom = top + height - 1;
+
+ bool clicked = IsLowered();
+ DrawFrameRect(left, top, right, bottom, color, (clicked) ? FR_LOWERED : FR_NONE);
+}
+
+void ImageButtonWidget::DrawContents(const Window *w)
+{
+ bool clicked = IsLowered();
+ DrawSprite(image + (two_images && clicked), PAL_NONE, left + 1 + clicked, top + 1 + clicked);
+}
+
+
+
+/**
+ * A push button with an image on it
+ * @param p_number Number of the widget, see WidgetNumber enum for more information
+ * @param p_backcol Background colour of the widget
+ * @param p_img Image to display, if button is lowered and \a p_two_images, \c p_img+1 is displayed
+ * @param p_tip Tool tip of the widget
+ * @param p_two_images If \c true, use \a p_img + 1 for displaying
+ */
+ImagePushButtonWidget::ImagePushButtonWidget(byte p_number, byte p_backcol, SpriteID p_img, StringID p_tip, bool p_two_images)
+ : ImageButtonWidget(p_number, p_backcol, p_img, p_tip, p_two_images)
+{
+}
+
+/* ====================================================================== */
+
+/*
+ * Caption widgets
+ */
+
+BaseCaptionWidget::BaseCaptionWidget(byte p_backcol, uint16 p_minwidth, StringID p_tip)
+ : VisibleWidget(INVALID_WIDGET_NUMBER, p_backcol, p_tip)
+{
+ min_width = p_minwidth;
+}
+
+Dimension BaseCaptionWidget::GetMinimalSize(bool p_use_cache)
+{
+ return MakeDimension(min_width, 14);
+}
+
+WidgetFill BaseCaptionWidget::GetFill()
+{
+ return WidgetFill(true, false);
+}
+
+Dimension BaseCaptionWidget::GetResizeStep()
+{
+ return MakeDimension(1, 0);
+}
+
+void BaseCaptionWidget::DrawBorder(const Window *w)
+{
+ const int right = left + width - 1;
+ const int bottom = top + height - 1;
+
+ assert(bottom - top == 13); // To ensure the same sizes are used everywhere!
+
+ DrawFrameRect(left, top, right, bottom, color, FR_BORDERONLY);
+ DrawFrameRect(left + 1, top + 1, right - 1, bottom - 1, color,
+ (w->caption_color == 0xFF) ? FR_LOWERED | FR_DARKENED : FR_LOWERED | FR_DARKENED | FR_BORDERONLY);
+
+ if (w->caption_color != 0xFF) {
+ GfxFillRect(left + 2, top + 2, right - 2, bottom - 2, _colour_gradient[_player_colors[w->caption_color]][4]);
+ }
+}
+
+void BaseCaptionWidget::DrawContents(const Window *w)
+{
+ const int right = left + width - 1;
+
+ StringID str = GetString();
+ DrawStringCenteredTruncated(left + 2, right - 2, top + 2, str, TC_FROMSTRING);
+}
+
+/**
+ * Caption widget with fixed caption text
+ * @param p_backcol Background colour of the widget
+ * @param p_minwidth Minimal width of the widget in pixels
+ * @param p_strid String to display in the caption
+ * @param p_tip Tool tip string, use STR_NULL to disable
+ */
+FixedTextCaptionWidget::FixedTextCaptionWidget(byte p_backcol, uint16 p_minwidth, StringID p_strid, StringID p_tip)
+ : BaseCaptionWidget(p_backcol, p_minwidth, p_tip)
+{
+ strid = p_strid;
+}
+
+StringID FixedTextCaptionWidget::GetString()
+{
+ return strid;
+}
+
+/**
+ * Caption widget with self-supporting caption text
+ * @param p_backcol Background colour of the widget
+ * @param p_minwidth Minimal width of the widget in pixels
+ * @param p_txtparms Parameterized text to display
+ * @param p_tip Tool tip string, use STR_NULL to disable
+ */
+ParmTextCaptionWidget::ParmTextCaptionWidget(byte p_backcol, uint16 p_minwidth, BaseParmText *p_parmtxt, StringID p_tip)
+ : BaseCaptionWidget(p_backcol, p_minwidth, p_tip)
+{
+ parm_text = p_parmtxt;
+}
+
+StringID ParmTextCaptionWidget::GetString()
+{
+ return parm_text->GetStrId();
+}
+
+
+BaseCaptionWidget *BaseCaptionWidget::FindCaptionWidget()
+{
+ return this;
+}
+
=== modified file 'src/window.cpp'
--- src/window.cpp 2008-08-16 08:47:44 +0000
+++ src/window.cpp 2008-08-16 12:54:58 +0000
@@ -95,6 +95,12 @@
void Window::RaiseButtons()
{
+ if (this->root_widget != NULL) {
+ this->root_widget->RaiseAndInvalidateAll(this);
+ return;
+ }
+
+ /* else: use already existing handler */
for (uint i = 0; i < this->widget_count; i++) {
if (this->IsWidgetLowered(i)) {
this->RaiseWidget(i);
@@ -105,6 +111,8 @@
void Window::InvalidateWidget(byte widget_index) const
{
+ assert(this->root_widget == NULL); // cannot invalidate hierarchical widget by index
+
const Widget *wi = &this->widget[widget_index];
/* Don't redraw the window if the widget is invisible or of no-type */
@@ -115,11 +123,24 @@
void Window::HandleButtonClick(byte widget)
{
+ assert(this->root_widget == NULL); // cannot handle button click by index, use Window::HandleButtonClickWidget()
+
this->LowerWidget(widget);
this->flags4 |= 5 << WF_TIMEOUT_SHL;
this->InvalidateWidget(widget);
}
+/**
+ * Lower widget, and set window timeout, so it gets automatically raised again
+ * @param wid Pointer to the widget to lower
+ */
+void Window::HandleButtonClickWidget(LowerableWidget *low_wid)
+{
+ low_wid->SetLowered(true);
+ this->flags4 |= 5 << WF_TIMEOUT_SHL;
+ low_wid->Invalidate(this);
+}
+
static void StartWindowDrag(Window *w);
static void StartWindowSizing(Window *w);
@@ -129,9 +150,61 @@
* @param x X coordinate of the click
* @param y Y coordinate of the click
* @param double_click Was it a double click?
+ * @todo NewWidgets: Handle resizing
+ */
+static void DispatchLeftClickEventWidget(Window *w, int x, int y, bool double_click)
+{
+
+ /* Code below is for new style widgets only */
+ Point pt = { x, y };
+ BaseWidget *wid = NULL;
+
+ if (w->desc_flags & WDF_DEF_WIDGET) {
+ wid = w->root_widget->GetWidgetFromPos(pt);
+ if (wid == NULL) return; // exit if clicked outside of widgets
+
+ /* don't allow any interaction if the button has been disabled */
+ if (wid->IsDisabled()) return;
+
+ if (dynamic_cast(wid) != NULL
+ || dynamic_cast(wid) != NULL
+ || dynamic_cast(wid) != NULL) {
+ LowerableWidget *low_wid = dynamic_cast(wid);
+ assert(low_wid != NULL);
+ w->HandleButtonClickWidget(low_wid);
+ }
+
+
+ if (w->desc_flags & WDF_STD_BTN) {
+ if (dynamic_cast(wid) != NULL) { // 'Title bar'
+ StartWindowDrag(w);
+ return;
+ }
+ }
+ }
+
+
+ if (double_click) {
+ w->OnDoubleClickWidget(pt, wid);
+ } else {
+ w->OnClickWidget(pt, wid);
+ }
+}
+
+/**
+ * Dispatch left mouse-button (possibly double) click in window.
+ * @param w Window to dispatch event in
+ * @param x X coordinate of the click
+ * @param y Y coordinate of the click
+ * @param double_click Was it a double click?
*/
static void DispatchLeftClickEvent(Window *w, int x, int y, bool double_click)
{
+ if (w->root_widget != NULL) {
+ DispatchLeftClickEventWidget(w, x, y, double_click);
+ return;
+ }
+
int widget = 0;
if (w->desc_flags & WDF_DEF_WIDGET) {
widget = GetWidgetFromPos(w, x, y);
@@ -197,6 +270,27 @@
*/
static void DispatchRightClickEvent(Window *w, int x, int y)
{
+ if (w->root_widget != NULL) {
+ Point pt = { x, y };
+ BaseWidget *wid = NULL;
+
+ /* default tooltips handler? */
+ if (w->desc_flags & WDF_STD_TOOLTIPS) {
+ wid = w->root_widget->GetWidgetFromPos(pt);
+ if (wid == NULL) return; // exit if clicked outside of widgets
+
+ StringID tip = wid->GetTooltip();
+ if (tip != STR_NULL) {
+ GuiShowTooltips(tip);
+ return;
+ }
+ }
+
+ w->OnRightClickWidget(pt, wid);
+ return;
+ }
+
+ /* else: use existing handler */
int widget = 0;
/* default tooltips handler? */
@@ -223,6 +317,8 @@
*/
static void DispatchMouseWheelEvent(Window *w, int widget, int wheel)
{
+ if (w->root_widget != NULL) return; // mouse wheel disabled for hierarchical widgets
+
if (widget < 0) return;
const Widget *wi1 = &w->widget[widget];
@@ -424,6 +520,7 @@
if (this->viewport != NULL) DeleteWindowViewport(this);
this->SetDirty();
+ delete this->root_widget;
free(this->widget);
}
@@ -692,6 +789,9 @@
this->width = min_width;
this->height = min_height;
AssignWidgetToWindow(this, widget);
+
+ this->root_widget = NULL; // Safe default
+
this->resize.width = min_width;
this->resize.height = min_height;
this->resize.step_width = 1;
@@ -1071,14 +1171,91 @@
}
this->left = pt.x;
this->top = pt.y;
- this->width = desc->minimum_width;
- this->height = desc->minimum_height;
- this->resize.width = desc->minimum_width;
- this->resize.height = desc->minimum_height;
+
+ root_widget = InitializeWidgets();
+ if (root_widget == NULL) {
+ this->width = desc->minimum_width;
+ this->height = desc->minimum_height;
+ this->resize.width = desc->minimum_width;
+ this->resize.height = desc->minimum_height;
+ } else {
+ Dimension min_dim = root_widget->GetMinimalSize(false); // Compute minimal window size
+ root_widget->SetAllocatedSize(min_dim.width, min_dim.height); // Allocate widgets by filling
+
+ this->width = min_dim.width; // initialize window data members, mainly for compability reasons
+ this->height = min_dim.height;
+ this->resize.width = min_dim.width;
+ this->resize.height = min_dim.height;
+
+ Dimension resize = root_widget->GetResizeStep(); // set resize step data members
+ this->resize.step_width = resize.width;
+ this->resize.step_height = resize.height;
+
+ assert(root_widget->alloc_width == min_dim.width); // broadcast minimal window size to all widgets
+ assert(root_widget->alloc_height == min_dim.height);
+ root_widget->SetSize(root_widget->alloc_width, root_widget->alloc_height);
+ assert(this->width == root_widget->width);
+ assert(this->height == root_widget->height);
+ root_widget->SetPosition(0, 0); // let all widgets get positioned using the sizes
+ }
this->SetDirty();
}
+/**
+ * Re-initialize the widget tree after a major change
+ *
+ * A major change has occurred in the widget tree (different language, widgets switched hidden'ess)
+ * Re-initialize the the entire window to make everything fit nicely in their widgets again.
+ * @note For compability, the resize data structure is filled here.
+ * @pre Window has been initialized already
+ */
+void Window::ReInitializeWidgets()
+{
+ if (root_widget == NULL) return;
+
+ /* Remember the previous additional size */
+ int16 extra_width = root_widget->width - root_widget->alloc_width;
+ int16 extra_height = root_widget->height - root_widget->alloc_height;
+ assert(extra_width >= 0);
+ assert(extra_height >= 0);
+
+ SetDirty();
+ /* Initialize the widgets from scratch */
+ Dimension min_dim = root_widget->GetMinimalSize(false); // Compute minimal window size
+ root_widget->SetAllocatedSize(min_dim.width, min_dim.height); // Allocate widgets by filling
+
+ this->width = min_dim.width;
+ this->height = min_dim.height;
+ this->resize.width = min_dim.width;
+ this->resize.height = min_dim.height;
+
+ Dimension resize = root_widget->GetResizeStep();
+ this->resize.step_width = resize.width;
+ this->resize.step_height = resize.height;
+
+ assert((resize.width == 0 && extra_width == 0) || (resize.width > 0 && (extra_width % resize.width) == 0));
+ assert((resize.height == 0 && extra_height == 0) || (resize.height > 0 && (extra_height % resize.height) == 0));
+
+ root_widget->SetSize(root_widget->alloc_width + extra_width, root_widget->alloc_height + extra_height);
+ root_widget->SetPosition(0, 0);
+ this->width = root_widget->width;
+ this->height = root_widget->height;
+
+ SetDirty();
+}
+
+/** After a language change, re-initialize all windows */
+void UpdateAllWindows()
+{
+ Window* const *wz;
+
+ FOR_ALL_WINDOWS(wz) {
+ Window *w = *wz;
+ w->ReInitializeWidgets();
+ }
+}
+
/** Do a search for a window at specific coordinates. For this we start
* at the topmost window, obviously and work our way down to the bottom
* @param x position x to query
@@ -1183,6 +1360,7 @@
Window *w = GetCallbackWnd();
if (w != NULL) {
+ assert(w->root_widget == NULL); // we should never get here
/* send an event in client coordinates. */
Point pt;
pt.x = _cursor.pos.x - w->left;
@@ -1203,7 +1381,10 @@
if (_mouseover_last_w != NULL && _mouseover_last_w != w) {
/* Reset mouse-over coordinates of previous window */
Point pt = { -1, -1 };
- _mouseover_last_w->OnMouseOver(pt, 0);
+ if (_mouseover_last_w->root_widget == NULL)
+ _mouseover_last_w->OnMouseOver(pt, 0);
+ else
+ _mouseover_last_w->OnMouseOverWidget(pt, NULL);
}
/* _mouseover_last_w will get reset when the window is deleted, see DeleteWindow() */
@@ -1212,11 +1393,16 @@
if (w != NULL) {
/* send an event in client coordinates. */
Point pt = { _cursor.pos.x - w->left, _cursor.pos.y - w->top };
- int widget = 0;
- if (w->widget != NULL) {
- widget = GetWidgetFromPos(w, pt.x, pt.y);
+ if (w->root_widget == NULL) {
+ int widget = 0;
+ if (w->widget != NULL) {
+ widget = GetWidgetFromPos(w, pt.x, pt.y);
+ }
+ w->OnMouseOver(pt, widget);
+ } else {
+ BaseWidget *wid = w->root_widget->GetWidgetFromPos(pt);
+ w->OnMouseOverWidget(pt, wid);
}
- w->OnMouseOver(pt, widget);
}
/* Mouseover never stops execution */
@@ -1234,12 +1420,33 @@
*/
void ResizeWindow(Window *w, int x, int y)
{
+ if (x == 0 && y == 0) return;
+
+ w->SetDirty();
+ if (w->root_widget != NULL) {
+ Dimension resize = w->root_widget->GetResizeStep();
+
+ uint16 new_width = w->root_widget->width + x;
+ if (resize.width == 0) new_width = w->root_widget->alloc_width;
+ else if (new_width < w->root_widget->alloc_width) new_width = w->root_widget->alloc_width;
+
+ uint16 new_height = w->root_widget->height + y;
+ if (resize.height == 0) new_height = w->root_widget->alloc_height;
+ else if (new_height < w->root_widget->alloc_height) new_height = w->root_widget->alloc_height;
+
+ w->root_widget->SetSize(new_width, new_height);
+ w->root_widget->SetPosition(0, 0);
+ w->width = w->root_widget->width;
+ w->height = w->root_widget->height;
+
+ w->SetDirty();
+ return;
+ }
+
+ /* else it is a widget-array window, resize window with existing code */
bool resize_height = false;
bool resize_width = false;
- if (x == 0 && y == 0) return;
-
- w->SetDirty();
for (Widget *wi = w->widget; wi->type != WWT_LAST; wi++) {
/* Isolate the resizing flags */
byte rsizeflag = GB(wi->display_flags, 0, 4);
@@ -1288,8 +1495,6 @@
Window *w = *wz;
if (w->flags4 & WF_DRAGGING) {
- const Widget *t = &w->widget[1]; // the title bar ... ugh
-
/* Stop the dragging if the left mouse button was released */
if (!_left_button_down) {
w->flags4 &= ~WF_DRAGGING;
@@ -1383,7 +1588,23 @@
/* Make sure the window doesn't leave the screen
* 13 is the height of the title bar */
- nx = Clamp(nx, 13 - t->right, _screen.width - 13 - t->left);
+
+ int cap_left, cap_right, cap_top;
+
+ if (w->root_widget == NULL) {
+ const Widget *t = &w->widget[1]; // the title bar ... ugh
+ cap_left = t->left;
+ cap_top = t->top;
+ cap_right = t->right;
+ } else {
+ const BaseWidget *caption = w->root_widget->FindCaptionWidget();
+ assert(caption != NULL);
+ cap_left = caption->left;
+ cap_top = caption->top;
+ cap_right = caption->left + caption->width;
+ }
+
+ nx = Clamp(nx, 13 - cap_right, _screen.width - 13 - cap_left);
ny = Clamp(ny, 0, _screen.height - 13);
/* Make sure the title bar isn't hidden by behind the main tool bar */
@@ -1391,19 +1612,19 @@
if (v != NULL) {
int v_bottom = v->top + v->height;
int v_right = v->left + v->width;
- if (ny + t->top >= v->top && ny + t->top < v_bottom) {
- if ((v->left < 13 && nx + t->left < v->left) ||
- (v_right > _screen.width - 13 && nx + t->right > v_right)) {
+ if (ny + cap_top >= v->top && ny + cap_top < v_bottom) {
+ if ((v->left < 13 && nx + cap_left < v->left) ||
+ (v_right > _screen.width - 13 && nx + cap_right > v_right)) {
ny = v_bottom;
} else {
- if (nx + t->left > v->left - 13 &&
- nx + t->right < v_right + 13) {
+ if (nx + cap_left > v->left - 13 &&
+ nx + cap_right < v_right + 13) {
if (w->top >= v_bottom) {
ny = v_bottom;
} else if (w->left < nx) {
- nx = v->left - 13 - t->left;
+ nx = v->left - 13 - cap_left;
} else {
- nx = v_right + 13 - t->right;
+ nx = v_right + 13 - cap_right;
}
}
}
@@ -1503,7 +1724,6 @@
DeleteWindowById(WC_DROPDOWN_MENU, 0);
}
-
static bool HandleScrollbarScrolling()
{
Window* const *wz;
=== modified file 'src/window_func.h'
--- src/window_func.h 2008-05-10 14:58:01 +0000
+++ src/window_func.h 2008-08-16 12:05:28 +0000
@@ -9,6 +9,7 @@
#include "player_type.h"
void SetWindowDirty(const Window *w);
+void UpdateAllWindows();
Window *FindWindowById(WindowClass cls, WindowNumber number);
void ChangeWindowOwner(PlayerID old_player, PlayerID new_player);
=== modified file 'src/window_gui.h'
--- src/window_gui.h 2008-08-16 08:47:44 +0000
+++ src/window_gui.h 2008-08-16 12:03:36 +0000
@@ -14,6 +14,9 @@
#include "core/alloc_type.hpp"
#include "window_type.h"
#include "tile_type.h"
+#include
+#include "gfx_type.h"
+#include "table/strings.h"
/**
* The maximum number of windows that can be opened.
@@ -180,6 +183,480 @@
};
/**
+ * Widget numbers
+ *
+ * A widget address (for example, from the GetWidgetFromPos() function) doesn't
+ * give much information what widget of a window you are actually holding.
+ * To improve on this, each widget has a widget number. It is a byte with
+ * freely choosable values, except for the special values listed in the
+ * WidgetNumber enumeration.
+ */
+enum WidgetNumber {
+ INVALID_WIDGET_NUMBER = 0xff
+};
+
+/** Data structure for keeping the horizontal and vertical filling capabilities of a widget (tree) */
+struct WidgetFill
+{
+ WidgetFill(bool xf, bool yf);
+ WidgetFill(const WidgetFill& wf);
+ WidgetFill& operator=(const WidgetFill&);
+
+ bool x_fill; ///< Widget can fill horizontally
+ bool y_fill; ///< Widget can fill vertically
+};
+
+
+/**
+ * Base class for self-supporting string printing.
+ *
+ * To output parameterized text in the middle of widget drawing, string length
+ * computations, etc, preparation is needed just before the text operation is
+ * performed. This class takes care of that preparation. Each time the StringID
+ * is obtained, preparation is performed by calling the Prepare method. In this
+ * base class, the Prepare does nothing, but that can be changed in any way
+ * desired in a derived class.
+ */
+struct BaseParmText: ZeroedMemoryAllocator {
+ BaseParmText(StringID p_strid);
+ ~BaseParmText();
+
+ StringID GetStrId();
+protected:
+ virtual void Prepare();
+
+ StringID strid; ///< Id of the text
+};
+
+
+/** Self-supporting parameterized text string with a single parameter */
+struct SingleParmText: BaseParmText {
+ SingleParmText(StringID p_strid, uint64 p_param0);
+
+ uint64 param0;
+protected:
+ virtual void Prepare(void);
+};
+
+
+struct BaseCaptionWidget;
+
+/**
+ * Base class for hierarchically ordered widgets.
+ *
+ * These widgets have a WidgetNumber and a tool tip. They can compute their size and position, and can draw themselves.
+ * For specific windows, dedicated widgets can be created by deriving from existing ones.
+ *
+ * @ingroup HierarchicalWidgets
+ */
+struct BaseWidget: ZeroedMemoryAllocator {
+ BaseWidget();
+ virtual ~BaseWidget();
+
+ /*** Size computations, position setting ***/
+
+ virtual Dimension GetMinimalSize(bool p_use_cache) = 0;
+ virtual WidgetFill GetFill();
+ virtual Dimension GetResizeStep();
+
+ virtual void SetAllocatedSize(uint16 p_width, uint16 p_height) = 0;
+ virtual void SetSize(uint16 p_width, uint16 p_height) = 0;
+ virtual void SetPosition(uint16 p_left, uint16 p_top);
+
+
+ /*** Properties querying ***/
+
+ virtual StringID GetTooltip();
+ virtual byte GetWidgetNumber();
+ virtual BaseWidget *GetWidgetFromPos(const Point& pt) = 0;
+ virtual BaseCaptionWidget *FindCaptionWidget() = 0;
+
+ virtual bool IsLowered() const;
+ virtual bool IsDisabled() const;
+ virtual bool IsHidden() const;
+
+ /*** Actions ***/
+
+ void SetHidden(bool p_hidden_state);
+ void Invalidate(const Window *w);
+
+ virtual void RaiseAndInvalidateAll(const Window *w) = 0;
+
+ virtual void Draw(const Window *w) = 0;
+
+
+ /*** Data members ***/
+
+ bool hidden; ///< Widget is hidden (not visible)
+
+ uint16 alloc_width; ///< Allocated width of widget (used in derived classes)
+ uint16 alloc_height; ///< Allocated height of widget (used in derived classes)
+
+ uint16 left; ///< Current position (possibly after resize) of left corner of widget
+ uint16 top; ///< Current position (possibly after resize) of top corner of widget
+ uint16 width; ///< Current width (possibly after resize) of widget
+ uint16 height; ///< Current height (possibly after resize) of widget
+};
+
+/**
+ * Container for several child widgets
+ *
+ * The child widgets can be put after each other (a horizontal container) or underneath each other (a
+ * vertical container). Additionally, you can specify that (refillable/resizable) children should get
+ * an equal amount of space (namely that of the largest widget of them). Finally, to make your life easier,
+ * you can specify additionally padding before, in between, and after the child widgets.
+ *
+ * @ingroup HierarchicalWidgets
+ */
+struct ContainerWidget: BaseWidget
+{
+ typedef std::list WidgetList;
+
+ ContainerWidget(bool p_is_hor, bool p_eq_size, byte p_pre = 0, byte p_inter = 0, byte p_post = 0);
+ ~ContainerWidget();
+
+ void push_back(BaseWidget *wid);
+
+
+
+ /* virtual */ Dimension GetMinimalSize(bool p_use_cache);
+ /* virtual */ WidgetFill GetFill();
+ /* virtual */ Dimension GetResizeStep();
+
+ /* virtual */ void SetAllocatedSize(uint16 p_width, uint16 p_height);
+ /* virtual */ void SetSize(uint16 p_width, uint16 p_height);
+ /* virtual */ void SetPosition(uint16 p_left, uint16 p_top);
+
+ /* virtual */ BaseWidget *GetWidgetFromPos(const Point& pt);
+ /* virtual */ BaseCaptionWidget *FindCaptionWidget();
+
+ /* virtual */ void RaiseAndInvalidateAll(const Window *w);
+ /* virtual */ void Draw(const Window *w);
+
+
+ WidgetList widgets; ///< Widgets contained in the ContainerWidget
+ bool is_horizontal; ///< Container aligns its widget horizontally (if \c false, it aligns them vertically)
+ bool equal_size; ///< Fillable widgets (in same direction as the container) all get equal size initially
+ byte pre_padding; ///< Amount of padding before first child
+ byte inter_padding; ///< Amount of padding between two childs
+ byte post_padding; ///< Amount of padding after last child
+private:
+ static const byte m_primes[];
+ void SplitInPrimes(uint num, uint maxcount[]);
+ uint16 SmallestCommonMultiple(uint maxcount[]);
+
+ uint16 minimal_width; ///< Minimal width needed by the container
+ uint16 minimal_height; ///< Minimal height needed by the container
+ bool x_fill; ///< Horizontal filling capabilities of the container
+ bool y_fill; ///< Vertical filling capabilities of the container
+ uint16 x_resize_step; ///< Horizontal resize step of the container
+ uint16 y_resize_step; ///< Vertical resize step of the container
+
+ void ComputeMinimalSize();
+ void ComputeFill();
+ void ComputeResizeStep();
+
+ Dimension ChildGetMinimalSize(BaseWidget *wid, bool p_use_cache);
+ Dimension ChildGetMinimalSize(BaseWidget *wid);
+ WidgetFill ChildGetFill(BaseWidget *wid);
+ Dimension ChildFillingResizeStep(BaseWidget *wid);
+ void ChildSetAllocatedSize(BaseWidget *wid, const Dimension& dim);
+
+ Dimension ChildGetAllocatedSize(BaseWidget *wid);
+ Dimension ChildGetResizeStep(BaseWidget *wid);
+ void ChildSetSize(BaseWidget *wid, const Dimension& dim);
+
+ void GenericSetSize(uint16 given_width, uint16 given_height, uint16 min_width, uint16 min_height,
+ Dimension (ContainerWidget::*resize_step_func)(BaseWidget *),
+ Dimension (ContainerWidget::*size_func)(BaseWidget *),
+ void (ContainerWidget::*set_size_func)(BaseWidget *, const Dimension&));
+};
+
+/**
+ * Abstract base class of visible widgets
+ *
+ * The class takes care of common functionality of all visible widgets.
+ * Derived classes may want to overload the following functions
+ * - Dimension BaseWidget::GetMinimalSize(bool p_use_cache)
+ * - WidgetFill BaseWidget::GetFill()
+ * - Dimension BaseWidget::GetResizeStep()
+ * - void VisibleWidget::DrawBorder(const Window *w)
+ * - void VisibleWidget::DrawContents(const Window *w)
+ * @ingroup HierarchicalWidgets
+ */
+struct VisibleWidget: BaseWidget {
+ VisibleWidget(byte p_number, byte p_color, StringID p_tip);
+
+ /* virtual */ void SetAllocatedSize(uint16 p_width, uint16 p_height);
+ /* virtual */ void SetSize(uint16 p_width, uint16 p_height);
+
+ void SetDisabled(bool p_disabled);
+ /* virtual */ bool IsDisabled() const;
+ /* virtual */ StringID GetTooltip();
+ /* virtual */ byte GetWidgetNumber();
+ /* virtual */ BaseWidget *GetWidgetFromPos(const Point& pt);
+ /* virtual */ BaseCaptionWidget *FindCaptionWidget();
+
+ /* virtual */ void RaiseAndInvalidateAll(const Window *w);
+ /* virtual */ void Draw(const Window *w);
+ virtual void DrawBorder(const Window *w);
+ virtual void DrawContents(const Window *w);
+
+ byte number; ///< Widget number. @see WidgetNumber
+ byte color; ///< Background color of the widget, see docs/ottd-colourtext-palette.png
+ StringID tip; ///< Tool tip to display for this widget
+ bool disabled; ///< Widget is disabled (greyed out)
+};
+
+
+/**
+ * Abstract widget that can be raised and lowered
+ * @ingroup HierarchicalWidgets
+ */
+struct LowerableWidget: VisibleWidget {
+ LowerableWidget(byte p_number, byte p_color, StringID p_tip);
+
+ void SetLowered(bool p_lower);
+ /* virtual */ bool IsLowered() const;
+ /* virtual */ void RaiseAndInvalidateAll(const Window *w);
+
+ bool lowered;
+};
+
+/**
+ * Widget with a single child widget
+ *
+ * Derived classes may want to overload
+ * - GetMinimalSize(bool p_use_cache)
+ * - SetAllocatedSize(uint16 p_width, uint16 p_height)
+ * - SetSize(uint16 p_width, uint16 p_height)
+ * - SetPosition(uint16 p_left, uint16 p_top)
+ * - Draw(const Window *w)
+ * @ingroup HierarchicalWidgets
+ */
+struct ParentWidget: BaseWidget {
+ ParentWidget(BaseWidget *p_child);
+ ~ParentWidget();
+
+ /* virtual */ WidgetFill GetFill();
+ /* virtual */ Dimension GetResizeStep();
+ /* virtual */ StringID GetTooltip();
+ /* virtual */ byte GetWidgetNumber();
+ /* virtual */ BaseWidget *GetWidgetFromPos(const Point& pt);
+ /* virtual */ BaseCaptionWidget *FindCaptionWidget();
+ /* virtual */ bool IsLowered() const;
+ /* virtual */ bool IsDisabled() const;
+ /* virtual */ bool IsHidden() const;
+ /* virtual */ void RaiseAndInvalidateAll(const Window *w);
+
+ BaseWidget *child; ///< Pointer to child widget (managed by the class)
+};
+
+
+/**
+ * Widget that adds a background to its child widget
+ * @ingroup HierarchicalWidgets
+ */
+struct PanelWidget: ParentWidget {
+ PanelWidget(byte p_backcol, StringID p_tip, BaseWidget *p_child);
+
+ /* virtual */ StringID GetTooltip();
+ /* virtual */ byte GetWidgetNumber();
+ /* virtual */ BaseWidget *GetWidgetFromPos(const Point& pt);
+ /* virtual */ Dimension GetMinimalSize(bool p_use_cache);
+ /* virtual */ void SetAllocatedSize(uint16 p_width, uint16 p_height);
+ /* virtual */ void SetSize(uint16 p_width, uint16 p_height);
+ /* virtual */ void SetPosition(uint16 p_left, uint16 p_top);
+ /* virtual */ void Draw(const Window *w);
+
+ byte color;
+ byte tip;
+};
+
+
+/** Padding distances */
+struct Padding
+{
+ byte left_padding; ///< Amount of padding at the left of the child
+ byte right_padding; ///< Amount of padding at the right of the child
+ byte top_padding; ///< Amount of padding above the child
+ byte bottom_padding; ///< Amount of padding below the child
+};
+
+/**
+ * Function to construct a padding value
+ * @param left_padding; Amount of padding at the left of the child
+ * @param right_padding; Amount of padding at the right of the child
+ * @param top_padding; Amount of padding above the child
+ * @param bottom_padding Amount of padding below the child
+ * @remark: It could be changed into a constructor
+ * */
+Padding MakePadding(byte p_left, byte p_right, byte p_top, byte p_bottom);
+
+/**
+ * Widget with some padding (empty space) around the child widget
+ * @ingroup HierarchicalWidgets
+ */
+struct PaddingWidget: ParentWidget {
+ PaddingWidget(const Padding& p_padding, BaseWidget *p_child);
+
+ /* virtual */ Dimension GetMinimalSize(bool p_use_cache);
+ /* virtual */ void SetAllocatedSize(uint16 p_width, uint16 p_height);
+ /* virtual */ void SetSize(uint16 p_width, uint16 p_height);
+ /* virtual */ void SetPosition(uint16 p_left, uint16 p_top);
+ /* virtual */ void Draw(const Window *w);
+
+ Padding padding; ///< Padding distance around the child widget
+ Dimension min_size; ///< Minimal size of the padding widget
+};
+
+
+/* ========================================================
+ * TextButton widgets
+ */
+
+/**
+ * Base class for textbutton widgets
+ *
+ * This class contains everything needed to render a text button widget, except the source of the string
+ * @ingroup HierarchicalWidgets
+ */
+struct BaseTextButtonWidget: LowerableWidget {
+ BaseTextButtonWidget(byte p_number, byte p_backcol, StringID p_tip, bool p_two_texts);
+
+ /* virtual */ Dimension GetMinimalSize(bool p_use_cache);
+ /* virtual */ WidgetFill GetFill();
+ /* virtual */ void DrawBorder(const Window *w);
+ /* virtual */ void DrawContents(const Window *w);
+ virtual StringID GetString() = 0;
+
+ bool two_texts; ///< Use different text when lowered (\f$GetString() + 1\f$)
+};
+
+/**
+ * TextButton widget with fixed text
+ * @ingroup HierarchicalWidgets
+ */
+struct FixedTextButtonWidget: BaseTextButtonWidget {
+ FixedTextButtonWidget(byte p_number, byte p_backcol, StringID p_strid, StringID p_tip, bool p_two_texts = false);
+
+ /* virtual */ StringID GetString();
+
+ StringID strid; ///< Fixed string text to draw in the button
+};
+
+/**
+ * TextButton widget with parameterized text
+ * @ingroup HierarchicalWidgets
+ * @note Object pointed to by \a parm_text is not managed by the widget
+ */
+struct ParmTextButtonWidget: BaseTextButtonWidget {
+ ParmTextButtonWidget(byte p_number, byte p_backcol, BaseParmText *p_parmtext, StringID p_tip, bool p_two_texts = false);
+
+ /* virtual */ StringID GetString();
+
+ BaseParmText *parm_text; ///< Self-supporting string to draw in the button, memory pointed to not managed by the object
+};
+
+/**
+ * Text push button with fixed string
+ * @ingroup HierarchicalWidgets
+ * @note The only difference with the FixedTextButtonWidget class is the class name; being a push button is
+ * tested with a \c isinstance<> so the button gets auto-raised
+ */
+struct FixedTextPushButtonWidget: FixedTextButtonWidget {
+ FixedTextPushButtonWidget(byte p_number, byte p_backcol, StringID p_strid, StringID p_tip, bool p_two_texts = false);
+};
+
+/**
+ * Text push button with parameterized string
+ * @ingroup HierarchicalWidgets
+ * @note The only difference with the ParmTextButtonWidget class is the class name; being a push button is
+ * tested with a \c isinstance<> so the button gets auto-raised
+ */
+struct ParmTextPushButtonWidget: ParmTextButtonWidget {
+ ParmTextPushButtonWidget(byte p_number, byte p_backcol, BaseParmText *p_parmtext, StringID p_tip, bool p_two_texts = false);
+};
+
+
+/* ========================================================
+ * Image button widgets
+ */
+
+/** Button with an image on it */
+struct ImageButtonWidget: LowerableWidget {
+ ImageButtonWidget(byte p_number, byte p_col, SpriteID p_img, StringID p_tip, bool p_two_images = false);
+
+ /* virtual */ Dimension GetMinimalSize(bool p_use_cache);
+ /* virtual */ void DrawBorder(const Window *w);
+ /* virtual */ void DrawContents(const Window *w);
+
+ SpriteID image; ///< Sprite to display, if \a two_images and lowered, \c img+1 is used
+ bool two_images; ///< If \c true, use the next image (\c img+1) if the button is lowered
+};
+
+/**
+ * Push button with an image on it
+ * @ingroup HierarchicalWidgets
+ * @note The only difference with the ImageButtonWidget class is the class name; being a push button is
+ * tested with a \c isinstance<> so the button gets auto-raised
+ */
+struct ImagePushButtonWidget: ImageButtonWidget {
+ ImagePushButtonWidget(byte p_number, byte p_col, SpriteID p_img, StringID p_tip, bool p_two_images = false);
+};
+
+/* ========================================================
+ * Caption widgets
+ */
+
+/**
+ * Base class for caption widgets
+ * @ingroup HierarchicalWidgets
+ */
+struct BaseCaptionWidget: VisibleWidget {
+ BaseCaptionWidget(byte p_backcol, uint16 p_minwidth, StringID p_tip = STR_018C_WINDOW_TITLE_DRAG_THIS);
+
+ /* virtual */ Dimension GetMinimalSize(bool p_use_cache);
+ /* virtual */ WidgetFill GetFill();
+ /* virtual */ Dimension GetResizeStep();
+ /* virtual */ void DrawBorder(const Window *w);
+ /* virtual */ void DrawContents(const Window *w);
+ /* virtual */ BaseCaptionWidget *FindCaptionWidget();
+ virtual StringID GetString() = 0;
+
+ uint16 min_width; ///< Minimal width of the caption in pixels
+};
+
+/**
+ * Caption widget with fixed text
+ * @ingroup HierarchicalWidgets
+ */
+struct FixedTextCaptionWidget: BaseCaptionWidget {
+ FixedTextCaptionWidget(byte p_backcol, uint16 p_minwidth, StringID p_str, StringID p_tip = STR_018C_WINDOW_TITLE_DRAG_THIS);
+
+ /* virtual */ StringID GetString();
+
+ StringID strid; ///< Fixed caption string text
+};
+
+/**
+ * Caption widget with self-supporting text
+ * @ingroup HierarchicalWidgets
+ * @note Object pointed to by \a parm_text is not managed by the widget
+ */
+struct ParmTextCaptionWidget: BaseCaptionWidget {
+ ParmTextCaptionWidget(byte p_backcol, uint16 p_minwidth, BaseParmText *p_txtparms,
+ StringID p_tip = STR_018C_WINDOW_TITLE_DRAG_THIS);
+
+ /* virtual */ StringID GetString();
+
+ BaseParmText *parm_text; ///< Self-supporting caption string
+};
+
+/* ** End of hierarchical widgets ** */
+
+
+/**
* Data structure for an opened window
*/
struct Window : ZeroedMemoryAllocator {
@@ -224,7 +701,10 @@
Window *parent; ///< Parent window
+ BaseWidget *root_widget; ///< Root of the BaseWidget widget tree
+
void HandleButtonClick(byte widget);
+ void HandleButtonClickWidget(LowerableWidget *low_wid);
void SetWidgetDisabledState(byte widget_index, bool disab_stat);
void DisableWidget(byte widget_index);
@@ -254,6 +734,15 @@
void SetDirty() const;
+ /**
+ * Construct the widget tree, and return the root of the tree.
+ * Returns \c NULL if not using new style widgets
+ */
+ virtual BaseWidget *InitializeWidgets() { return NULL; }
+
+ void ReInitializeWidgets();
+
+
/*** Event handling ***/
/**
@@ -285,6 +774,7 @@
* @param widget the clicked widget.
*/
virtual void OnClick(Point pt, int widget) {}
+ virtual void OnClickWidget(const Point& pt, BaseWidget *widget) {}
/**
* A double click with the left mouse button has been made on the window.
@@ -292,6 +782,7 @@
* @param widget the clicked widget.
*/
virtual void OnDoubleClick(Point pt, int widget) {}
+ virtual void OnDoubleClickWidget(const Point& pt, BaseWidget *widget) {}
/**
* A click with the right mouse button has been made on the window.
@@ -299,6 +790,7 @@
* @param widget the clicked widget.
*/
virtual void OnRightClick(Point pt, int widget) {}
+ virtual void OnRightClickWidget(const Point& pt, BaseWidget *widget) {}
/**
* A dragged 'object' has been released.
@@ -320,6 +812,7 @@
* @param widget the widget the mouse hovers over.
*/
virtual void OnMouseOver(Point pt, int widget) {}
+ virtual void OnMouseOverWidget(const Point& pt, BaseWidget *widget) {}
/**
* The mouse wheel has been turned.
@@ -353,6 +846,7 @@
* Called when the window got resized.
* @param new_size the new size of the window.
* @param delta the amount of which the window size changed.
+ * @todo Use const references instead of copying the arguments
*/
virtual void OnResize(Point new_size, Point delta) {}