1
0
mirror of https://github.com/OpenTTD/OpenTTD.git synced 2025-08-22 22:19:08 +00:00

Compare commits

...

9 Commits

Author SHA1 Message Date
c0ea0589b4 Fix: AirportGetNearestTown incorrectly assumed first TileIterator result was origin. (#11565)
Not all TileIterators are equal, and some do not start at the top-corner, so the perimeter check was wrong. As the caller already has thie origin tile, use that instead.
2023-12-09 09:47:41 +00:00
10f94fb0dd Codechange: Remove runtime variable size assertion. 2023-12-09 08:13:03 +00:00
9f853c10b0 Codechange: Add compile-time check that variable size matches saveload entry. 2023-12-09 08:13:03 +00:00
cb53fed229 Codechange: Move VarType helpers to allow earlier use. 2023-12-09 08:13:03 +00:00
a05d6ee404 Fix: Ensure saveload type match variable type.
This either changes the saveload definition or changes the variable depending on which makes more sense.
2023-12-09 08:13:03 +00:00
54d45a6047 Codechange: Don't keep autosave_interval in std::chrono::minutes.
This variable is saved as a setting which requires the variable type to be known, but std::chrono::minutes may vary depending on system type.

Instead, keep as uint32_t and convert to std::chrono::minutes only when setting the timer.
2023-12-09 08:13:03 +00:00
a29766d6cc Codechange: Move dropdown list size and position calculation to window.
This simplifies initialization of DropdownWindow, as instead of both the caller code and the class needing to know about list sizes and available space, only the DropdownWindow needs to know.
2023-12-09 08:12:34 +00:00
f1cceb43a1 Codechange: Move initialization of DropdownWindow members. 2023-12-09 08:12:34 +00:00
52b121942b Codechange: DropdownWindow's selected_index is not actually an index. 2023-12-09 08:12:34 +00:00
10 changed files with 214 additions and 208 deletions

View File

@@ -1475,7 +1475,7 @@ static IntervalTimer<TimerGameRealtime> _autosave_interval({std::chrono::millise
*/
void ChangeAutosaveFrequency(bool reset)
{
_autosave_interval.SetInterval({_settings_client.gui.autosave_interval, TimerGameRealtime::AUTOSAVE}, reset);
_autosave_interval.SetInterval({std::chrono::minutes(_settings_client.gui.autosave_interval), TimerGameRealtime::AUTOSAVE}, reset);
}
/**

View File

@@ -1513,54 +1513,8 @@ size_t SlCalcObjMemberLength(const void *object, const SaveLoad &sld)
return 0;
}
/**
* Check whether the variable size of the variable in the saveload configuration
* matches with the actual variable size.
* @param sld The saveload configuration to test.
*/
[[maybe_unused]] static bool IsVariableSizeRight(const SaveLoad &sld)
{
if (GetVarMemType(sld.conv) == SLE_VAR_NULL) return true;
switch (sld.cmd) {
case SL_VAR:
switch (GetVarMemType(sld.conv)) {
case SLE_VAR_BL:
return sld.size == sizeof(bool);
case SLE_VAR_I8:
case SLE_VAR_U8:
return sld.size == sizeof(int8_t);
case SLE_VAR_I16:
case SLE_VAR_U16:
return sld.size == sizeof(int16_t);
case SLE_VAR_I32:
case SLE_VAR_U32:
return sld.size == sizeof(int32_t);
case SLE_VAR_I64:
case SLE_VAR_U64:
return sld.size == sizeof(int64_t);
case SLE_VAR_NAME:
return sld.size == sizeof(std::string);
default:
return sld.size == sizeof(void *);
}
case SL_REF:
/* These should all be pointer sized. */
return sld.size == sizeof(void *);
case SL_STDSTR:
/* These should be all pointers to std::string. */
return sld.size == sizeof(std::string);
default:
return true;
}
}
static bool SlObjectMember(void *object, const SaveLoad &sld)
{
assert(IsVariableSizeRight(sld));
if (!SlIsObjectValidInSavegame(sld)) return false;
VarType conv = GB(sld.conv, 0, 8);

View File

@@ -709,6 +709,86 @@ struct SaveLoadCompat {
SaveLoadVersion version_to; ///< Save/load the variable before this savegame version.
};
/**
* Get the NumberType of a setting. This describes the integer type
* as it is represented in memory
* @param type VarType holding information about the variable-type
* @return the SLE_VAR_* part of a variable-type description
*/
static inline constexpr VarType GetVarMemType(VarType type)
{
return GB(type, 4, 4) << 4;
}
/**
* Get the FileType of a setting. This describes the integer type
* as it is represented in a savegame/file
* @param type VarType holding information about the file-type
* @return the SLE_FILE_* part of a variable-type description
*/
static inline constexpr VarType GetVarFileType(VarType type)
{
return GB(type, 0, 4);
}
/**
* Check if the given saveload type is a numeric type.
* @param conv the type to check
* @return True if it's a numeric type.
*/
static inline constexpr bool IsNumericType(VarType conv)
{
return GetVarMemType(conv) <= SLE_VAR_U64;
}
/**
* Return expect size in bytes of a VarType
* @param type VarType to get size of.
* @return size of type in bytes.
*/
static inline constexpr size_t SlVarSize(VarType type)
{
switch (GetVarMemType(type)) {
case SLE_VAR_BL: return sizeof(bool);
case SLE_VAR_I8: return sizeof(int8_t);
case SLE_VAR_U8: return sizeof(uint8_t);
case SLE_VAR_I16: return sizeof(int16_t);
case SLE_VAR_U16: return sizeof(uint16_t);
case SLE_VAR_I32: return sizeof(int32_t);
case SLE_VAR_U32: return sizeof(uint32_t);
case SLE_VAR_I64: return sizeof(int64_t);
case SLE_VAR_U64: return sizeof(uint64_t);
case SLE_VAR_NULL: return sizeof(void *);
case SLE_VAR_STR: return sizeof(std::string);
case SLE_VAR_STRQ: return sizeof(std::string);
case SLE_VAR_NAME: return sizeof(std::string);
default: NOT_REACHED();
}
}
/**
* Check if a saveload cmd/type/length entry matches the size of the variable.
* @param cmd SaveLoadType of entry.
* @param type VarType of entry.
* @param length Array length of entry.
* @param size Actual size of variable.
* @return true iff the sizes match.
*/
static inline constexpr bool SlCheckVarSize(SaveLoadType cmd, VarType type, size_t length, size_t size)
{
switch (cmd) {
case SL_VAR: return SlVarSize(type) == size;
case SL_REF: return sizeof(void *) == size;
case SL_STDSTR: return SlVarSize(type) == size;
case SL_ARR: return SlVarSize(type) * length <= size; // Partial load of array is permitted.
case SL_DEQUE: return sizeof(std::deque<void *>) == size;
case SL_VECTOR: return sizeof(std::vector<void *>) == size;
case SL_REFLIST: return sizeof(std::list<void *>) == size;
case SL_SAVEBYTE: return true;
default: NOT_REACHED();
}
}
/**
* Storage of simple variables, references (pointers), and arrays.
* @param cmd Load/save type. @see SaveLoadType
@@ -716,12 +796,18 @@ struct SaveLoadCompat {
* @param base Name of the class or struct containing the variable.
* @param variable Name of the variable in the class or struct referenced by \a base.
* @param type Storage of the data in memory and in the savegame.
* @param length Number of elements in the array.
* @param from First savegame version that has the field.
* @param to Last savegame version that has the field.
* @param extra Extra data to pass to the address callback function.
* @note In general, it is better to use one of the SLE_* macros below.
*/
#define SLE_GENERAL_NAME(cmd, name, base, variable, type, length, from, to, extra) SaveLoad {name, cmd, type, length, from, to, cpp_sizeof(base, variable), [] (void *b, size_t) -> void * { assert(b != nullptr); return const_cast<void *>(static_cast<const void *>(std::addressof(static_cast<base *>(b)->variable))); }, extra, nullptr}
#define SLE_GENERAL_NAME(cmd, name, base, variable, type, length, from, to, extra) \
SaveLoad {name, cmd, type, length, from, to, cpp_sizeof(base, variable), [] (void *b, size_t) -> void * { \
static_assert(SlCheckVarSize(cmd, type, length, sizeof(static_cast<base *>(b)->variable))); \
assert(b != nullptr); \
return const_cast<void *>(static_cast<const void *>(std::addressof(static_cast<base *>(b)->variable))); \
}, extra, nullptr}
/**
* Storage of simple variables, references (pointers), and arrays with a custom name.
@@ -729,6 +815,7 @@ struct SaveLoadCompat {
* @param base Name of the class or struct containing the variable.
* @param variable Name of the variable in the class or struct referenced by \a base.
* @param type Storage of the data in memory and in the savegame.
* @param length Number of elements in the array.
* @param from First savegame version that has the field.
* @param to Last savegame version that has the field.
* @param extra Extra data to pass to the address callback function.
@@ -934,7 +1021,10 @@ struct SaveLoadCompat {
* @param extra Extra data to pass to the address callback function.
* @note In general, it is better to use one of the SLEG_* macros below.
*/
#define SLEG_GENERAL(name, cmd, variable, type, length, from, to, extra) SaveLoad {name, cmd, type, length, from, to, sizeof(variable), [] (void *, size_t) -> void * { return static_cast<void *>(std::addressof(variable)); }, extra, nullptr}
#define SLEG_GENERAL(name, cmd, variable, type, length, from, to, extra) \
SaveLoad {name, cmd, type, length, from, to, sizeof(variable), [] (void *, size_t) -> void * { \
static_assert(SlCheckVarSize(cmd, type, length, sizeof(variable))); \
return static_cast<void *>(std::addressof(variable)); }, extra, nullptr}
/**
* Storage of a global variable in some savegame versions.
@@ -1133,38 +1223,6 @@ static inline bool SlIsObjectCurrentlyValid(SaveLoadVersion version_from, SaveLo
return version_from <= SAVEGAME_VERSION && SAVEGAME_VERSION < version_to;
}
/**
* Get the NumberType of a setting. This describes the integer type
* as it is represented in memory
* @param type VarType holding information about the variable-type
* @return the SLE_VAR_* part of a variable-type description
*/
static inline VarType GetVarMemType(VarType type)
{
return GB(type, 4, 4) << 4;
}
/**
* Get the FileType of a setting. This describes the integer type
* as it is represented in a savegame/file
* @param type VarType holding information about the file-type
* @return the SLE_FILE_* part of a variable-type description
*/
static inline VarType GetVarFileType(VarType type)
{
return GB(type, 0, 4);
}
/**
* Check if the given saveload type is a numeric type.
* @param conv the type to check
* @return True if it's a numeric type.
*/
static inline bool IsNumericType(VarType conv)
{
return GetVarMemType(conv) <= SLE_VAR_U64;
}
/**
* Get the address of the variable. Null-variables don't have an address,
* everything else has a callback function that returns the address based

View File

@@ -139,7 +139,7 @@
if (_settings_game.economy.station_noise_level) {
AirportTileTableIterator it(as->table[0], tile);
uint dist;
AirportGetNearestTown(as, it, dist);
AirportGetNearestTown(as, tile, it, dist);
return GetAirportNoiseLevelForDistance(as, dist);
}
@@ -155,7 +155,7 @@
if (!as->IsWithinMapBounds(0, tile)) return INVALID_TOWN;
uint dist;
return AirportGetNearestTown(as, AirportTileTableIterator(as->table[0], tile), dist)->index;
return AirportGetNearestTown(as, tile, AirportTileTableIterator(as->table[0], tile), dist)->index;
}
/* static */ SQInteger ScriptAirport::GetMaintenanceCostFactor(AirportType type)

View File

@@ -1397,11 +1397,11 @@ void LoadFromConfig(bool startup)
auto old_value = OneOfManySettingDesc::ParseSingleValue(old_item->value->c_str(), old_item->value->size(), _old_autosave_interval);
switch (old_value) {
case 0: _settings_client.gui.autosave_interval = std::chrono::minutes::zero(); break;
case 1: _settings_client.gui.autosave_interval = std::chrono::minutes(10); break;
case 2: _settings_client.gui.autosave_interval = std::chrono::minutes(30); break;
case 3: _settings_client.gui.autosave_interval = std::chrono::minutes(60); break;
case 4: _settings_client.gui.autosave_interval = std::chrono::minutes(120); break;
case 0: _settings_client.gui.autosave_interval = 0; break;
case 1: _settings_client.gui.autosave_interval = 10; break;
case 2: _settings_client.gui.autosave_interval = 30; break;
case 3: _settings_client.gui.autosave_interval = 60; break;
case 4: _settings_client.gui.autosave_interval = 120; break;
default: break;
}
}

View File

@@ -60,12 +60,12 @@ static const StringID _autosave_dropdown[] = {
};
/** Available settings for autosave intervals. */
static const std::chrono::minutes _autosave_dropdown_to_minutes[] = {
std::chrono::minutes::zero(), ///< never
std::chrono::minutes(10),
std::chrono::minutes(30),
std::chrono::minutes(60),
std::chrono::minutes(120),
static const uint32_t _autosave_dropdown_to_minutes[] = {
0, ///< never
10,
30,
60,
120,
};
static Dimension _circle_size; ///< Dimension of the circle +/- icon. This is here as not all users are within the class of the settings window.

View File

@@ -64,14 +64,14 @@ enum IndustryDensity {
};
/** Possible values for "use_relay_service" setting. */
enum UseRelayService {
enum UseRelayService : uint8_t {
URS_NEVER = 0,
URS_ASK,
URS_ALLOW,
};
/** Possible values for "participate_survey" setting. */
enum ParticipateSurvey {
enum ParticipateSurvey : uint8_t {
PS_ASK = 0,
PS_NO,
PS_YES,
@@ -149,7 +149,7 @@ struct GUISettings {
ZoomLevel zoom_min; ///< minimum zoom out level
ZoomLevel zoom_max; ///< maximum zoom out level
ZoomLevel sprite_zoom_min; ///< maximum zoom level at which higher-resolution alternative sprites will be used (if available) instead of scaling a lower resolution sprite
std::chrono::minutes autosave_interval; ///< how often should we do autosaves?
uint32_t autosave_interval; ///< how often should we do autosaves?
bool threaded_saves; ///< should we do threaded saves?
bool keep_all_autosave; ///< name the autosave in a different way
bool autosave_on_exit; ///< save an autosave when you quit the game, but do not ask "Do you really want to quit?"

View File

@@ -2303,18 +2303,19 @@ uint8_t GetAirportNoiseLevelForDistance(const AirportSpec *as, uint distance)
* Finds the town nearest to given airport. Based on minimal manhattan distance to any airport's tile.
* If two towns have the same distance, town with lower index is returned.
* @param as airport's description
* @param tile origin tile (top corner of the airport)
* @param it An iterator over all airport tiles
* @param[out] mindist Minimum distance to town
* @return nearest town to airport
*/
Town *AirportGetNearestTown(const AirportSpec *as, const TileIterator &it, uint &mindist)
Town *AirportGetNearestTown(const AirportSpec *as, TileIndex tile, const TileIterator &it, uint &mindist)
{
assert(Town::GetNumItems() > 0);
Town *nearest = nullptr;
uint perimeter_min_x = TileX(it);
uint perimeter_min_y = TileY(it);
uint perimeter_min_x = TileX(tile);
uint perimeter_min_y = TileY(tile);
uint perimeter_max_x = perimeter_min_x + as->size_x - 1;
uint perimeter_max_y = perimeter_min_y + as->size_y - 1;
@@ -2322,6 +2323,8 @@ Town *AirportGetNearestTown(const AirportSpec *as, const TileIterator &it, uint
std::unique_ptr<TileIterator> copy(it.Clone());
for (TileIndex cur_tile = *copy; cur_tile != INVALID_TILE; cur_tile = ++*copy) {
assert(IsInsideBS(TileX(cur_tile), perimeter_min_x, as->size_x));
assert(IsInsideBS(TileY(cur_tile), perimeter_min_y, as->size_y));
if (TileX(cur_tile) == perimeter_min_x || TileX(cur_tile) == perimeter_max_x || TileY(cur_tile) == perimeter_min_y || TileY(cur_tile) == perimeter_max_y) {
Town *t = CalcClosestTownFromTile(cur_tile, mindist + 1);
if (t == nullptr) continue;
@@ -2349,7 +2352,7 @@ void UpdateAirportsNoise()
const AirportSpec *as = st->airport.GetSpec();
AirportTileIterator it(st);
uint dist;
Town *nearest = AirportGetNearestTown(as, it, dist);
Town *nearest = AirportGetNearestTown(as, st->airport.tile, it, dist);
nearest->noise_reached += GetAirportNoiseLevelForDistance(as, dist);
}
}
@@ -2399,7 +2402,7 @@ CommandCost CmdBuildAirport(DoCommandFlag flags, TileIndex tile, byte airport_ty
/* The noise level is the noise from the airport and reduce it to account for the distance to the town center. */
uint dist;
Town *nearest = AirportGetNearestTown(as, tile_iter, dist);
Town *nearest = AirportGetNearestTown(as, tile, tile_iter, dist);
uint newnoise_level = GetAirportNoiseLevelForDistance(as, dist);
/* Check if local auth would allow a new airport */
@@ -2527,7 +2530,7 @@ static CommandCost RemoveAirport(TileIndex tile, DoCommandFlag flags)
* need of recalculation */
AirportTileIterator it(st);
uint dist;
Town *nearest = AirportGetNearestTown(as, it, dist);
Town *nearest = AirportGetNearestTown(as, st->airport.tile, it, dist);
nearest->noise_reached -= GetAirportNoiseLevelForDistance(as, dist);
if (_settings_game.economy.station_noise_level) {

View File

@@ -16,7 +16,7 @@
enum StationClassID : byte;
enum RoadStopClassID : byte;
extern Town *AirportGetNearestTown(const struct AirportSpec *as, const TileIterator &it, uint &mindist);
extern Town *AirportGetNearestTown(const struct AirportSpec *as, TileIndex tile, const TileIterator &it, uint &mindist);
extern uint8_t GetAirportNoiseLevelForDistance(const struct AirportSpec *as, uint distance);
CommandCost CmdBuildAirport(DoCommandFlag flags, TileIndex tile, byte airport_type, byte layout, StationID station_to_join, bool allow_adjacent);

View File

@@ -24,7 +24,7 @@
static const NWidgetPart _nested_dropdown_menu_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_END, WID_DM_ITEMS), SetMinimalSize(1, 1), SetScrollbar(WID_DM_SCROLL), EndContainer(),
NWidget(WWT_PANEL, COLOUR_END, WID_DM_ITEMS), SetScrollbar(WID_DM_SCROLL), EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_DM_SHOW_SCROLL),
NWidget(NWID_VSCROLLBAR, COLOUR_END, WID_DM_SCROLL),
EndContainer(),
@@ -41,67 +41,49 @@ static WindowDesc _dropdown_desc(__FILE__, __LINE__,
/** Drop-down menu window */
struct DropdownWindow : Window {
int parent_button; ///< Parent widget number where the window is dropped from.
Rect wi_rect; ///< Rect of the button that opened the dropdown.
const DropDownList list; ///< List with dropdown menu items.
int selected_index; ///< Index of the selected item in the list.
byte click_delay; ///< Timer to delay selection.
bool drag_mode;
int selected_result; ///< Result value of the selected item in the list.
byte click_delay = 0; ///< Timer to delay selection.
bool drag_mode = true;
bool instant_close; ///< Close the window when the mouse button is raised.
int scrolling; ///< If non-zero, auto-scroll the item list (one time).
int scrolling = 0; ///< If non-zero, auto-scroll the item list (one time).
Point position; ///< Position of the topleft corner of the window.
Scrollbar *vscroll;
Dimension items_dim; ///< Calculated cropped and padded dimension for the items widget.
/**
* Create a dropdown menu.
* @param parent Parent window.
* @param list Dropdown item list.
* @param selected Index of the selected item in the list.
* @param selected Initial selected result of the list.
* @param button Widget of the parent window doing the dropdown.
* @param wi_rect Rect of the button that opened the dropdown.
* @param instant_close Close the window when the mouse button is raised.
* @param position Topleft position of the dropdown menu window.
* @param size Size of the dropdown menu window.
* @param wi_colour Colour of the parent widget.
* @param scroll Dropdown menu has a scrollbar.
*/
DropdownWindow(Window *parent, DropDownList &&list, int selected, int button, bool instant_close, const Point &position, const Dimension &size, Colours wi_colour, bool scroll)
: Window(&_dropdown_desc), list(std::move(list))
DropdownWindow(Window *parent, DropDownList &&list, int selected, int button, const Rect wi_rect, bool instant_close, Colours wi_colour)
: Window(&_dropdown_desc)
, parent_button(button)
, wi_rect(wi_rect)
, list(std::move(list))
, selected_result(selected)
, instant_close(instant_close)
{
assert(!this->list.empty());
this->position = position;
this->parent = parent;
this->CreateNestedTree();
this->GetWidget<NWidgetCore>(WID_DM_ITEMS)->colour = wi_colour;
this->GetWidget<NWidgetCore>(WID_DM_SCROLL)->colour = wi_colour;
this->vscroll = this->GetScrollbar(WID_DM_SCROLL);
uint items_width = size.width - (scroll ? NWidgetScrollbar::GetVerticalDimension().width : 0);
NWidgetCore *nwi = this->GetWidget<NWidgetCore>(WID_DM_ITEMS);
nwi->SetMinimalSizeAbsolute(items_width, size.height + WidgetDimensions::scaled.dropdownlist.Vertical());
nwi->colour = wi_colour;
nwi = this->GetWidget<NWidgetCore>(WID_DM_SCROLL);
nwi->colour = wi_colour;
this->GetWidget<NWidgetStacked>(WID_DM_SHOW_SCROLL)->SetDisplayedPlane(scroll ? 0 : SZSP_NONE);
this->UpdateSizeAndPosition();
this->FinishInitNested(0);
CLRBITS(this->flags, WF_WHITE_BORDER);
/* Total length of list */
int list_height = 0;
for (const auto &item : this->list) {
list_height += item->Height();
}
/* Capacity is the average number of items visible */
this->vscroll->SetCapacity(size.height * this->list.size() / list_height);
this->vscroll->SetCount(this->list.size());
this->parent = parent;
this->parent_button = button;
this->selected_index = selected;
this->click_delay = 0;
this->drag_mode = true;
this->instant_close = instant_close;
}
void Close([[maybe_unused]] int data = 0) override
@@ -113,7 +95,7 @@ struct DropdownWindow : Window {
Point pt = _cursor.pos;
pt.x -= this->parent->left;
pt.y -= this->parent->top;
this->parent->OnDropdownClose(pt, this->parent_button, this->selected_index, this->instant_close);
this->parent->OnDropdownClose(pt, this->parent_button, this->selected_result, this->instant_close);
/* Set flag on parent widget to indicate that we have just closed. */
NWidgetCore *nwc = this->parent->GetWidget<NWidgetCore>(this->parent_button);
@@ -128,6 +110,73 @@ struct DropdownWindow : Window {
}
}
/**
* Fit dropdown list into available height, rounding to average item size. Width is adjusted if scrollbar is present.
* @param[in,out] desired Desired dimensions of dropdown list.
* @param list Dimensions of the list itself, without padding or cropping.
* @param available_height Available height to fit list within.
*/
void FitAvailableHeight(Dimension &desired, const Dimension &list, uint available_height)
{
if (desired.height < available_height) return;
/* If the dropdown doesn't fully fit, we a need a dropdown. */
uint avg_height = list.height / (uint)this->list.size();
uint rows = std::max((available_height - WidgetDimensions::scaled.dropdownlist.Vertical()) / avg_height, 1U);
desired.width = std::max(list.width, desired.width - NWidgetScrollbar::GetVerticalDimension().width);
desired.height = rows * avg_height + WidgetDimensions::scaled.dropdownlist.Vertical();
}
/**
* Update size and position of window to fit dropdown list into available space.
*/
void UpdateSizeAndPosition()
{
Rect button_rect = this->wi_rect.Translate(this->parent->left, this->parent->top);
/* Get the dimensions required for the list. */
Dimension list_dim = GetDropDownListDimension(this->list);
/* Set up dimensions for the items widget. */
Dimension widget_dim = list_dim;
widget_dim.width += WidgetDimensions::scaled.dropdownlist.Horizontal();
widget_dim.height += WidgetDimensions::scaled.dropdownlist.Vertical();
/* Width should match at least the width of the parent widget. */
widget_dim.width = std::max<uint>(widget_dim.width, button_rect.Width());
/* Available height below (or above, if the dropdown is placed above the widget). */
uint available_height_below = std::max(GetMainViewBottom() - button_rect.bottom - 1, 0);
uint available_height_above = std::max(button_rect.top - 1 - GetMainViewTop(), 0);
/* Is it better to place the dropdown above the widget? */
if (widget_dim.height > available_height_below && available_height_above > available_height_below) {
FitAvailableHeight(widget_dim, list_dim, available_height_above);
this->position.y = button_rect.top - widget_dim.height;
} else {
FitAvailableHeight(widget_dim, list_dim, available_height_below);
this->position.y = button_rect.bottom + 1;
}
this->position.x = (_current_text_dir == TD_RTL) ? button_rect.right + 1 - (int)widget_dim.width : button_rect.left;
this->items_dim = widget_dim;
this->GetWidget<NWidgetStacked>(WID_DM_SHOW_SCROLL)->SetDisplayedPlane(list_dim.height > widget_dim.height ? 0 : SZSP_NONE);
/* Capacity is the average number of items visible */
this->vscroll->SetCapacity((widget_dim.height - WidgetDimensions::scaled.dropdownlist.Vertical()) * this->list.size() / list_dim.height);
this->vscroll->SetCount(this->list.size());
/* If the dropdown is positioned above the parent widget, start selection at the bottom. */
if (this->position.y < button_rect.top && list_dim.height > widget_dim.height) this->vscroll->UpdatePosition(INT_MAX);
}
void UpdateWidgetSize(int widget, Dimension *size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension *fill, [[maybe_unused]] Dimension *resize) override
{
if (widget == WID_DM_ITEMS) *size = this->items_dim;
}
Point OnInitialPosition([[maybe_unused]] int16_t sm_width, [[maybe_unused]] int16_t sm_height, [[maybe_unused]] int window_number) override
{
return this->position;
@@ -182,7 +231,7 @@ struct DropdownWindow : Window {
if (y + item_height - 1 <= ir.bottom) {
Rect full{ir.left, y, ir.right, y + item_height - 1};
bool selected = (this->selected_index == item->result) && item->Selectable();
bool selected = (this->selected_result == item->result) && item->Selectable();
if (selected) GfxFillRect(full, PC_BLACK);
item->Draw(full, full.Shrink(WidgetDimensions::scaled.dropdowntext, RectPadding::zero), selected, colour);
@@ -197,7 +246,7 @@ struct DropdownWindow : Window {
int item;
if (this->GetDropDownItem(item)) {
this->click_delay = 4;
this->selected_index = item;
this->selected_result = item;
this->SetDirty();
}
}
@@ -217,7 +266,7 @@ struct DropdownWindow : Window {
/* Close the dropdown, so it doesn't affect new window placement.
* Also mark it dirty in case the callback deals with the screen. (e.g. screenshots). */
this->Close();
this->parent->OnDropdownSelect(this->parent_button, this->selected_index);
this->parent->OnDropdownSelect(this->parent_button, this->selected_result);
return;
}
@@ -245,8 +294,8 @@ struct DropdownWindow : Window {
if (!this->GetDropDownItem(item)) return;
}
if (this->selected_index != item) {
this->selected_index = item;
if (this->selected_result != item) {
this->selected_result = item;
this->SetDirty();
}
}
@@ -283,65 +332,7 @@ Dimension GetDropDownListDimension(const DropDownList &list)
void ShowDropDownListAt(Window *w, DropDownList &&list, int selected, int button, Rect wi_rect, Colours wi_colour, bool instant_close)
{
CloseWindowByClass(WC_DROPDOWN_MENU);
/* The preferred position is just below the dropdown calling widget */
int top = w->top + wi_rect.bottom + 1;
/* The preferred width equals the calling widget */
uint width = wi_rect.Width();
/* Get the height and width required for the list. */
Dimension dim = GetDropDownListDimension(list);
dim.width += WidgetDimensions::scaled.dropdownlist.Horizontal();
/* Scrollbar needed? */
bool scroll = false;
/* Is it better to place the dropdown above the widget? */
bool above = false;
/* Available height below (or above, if the dropdown is placed above the widget). */
uint available_height = std::max(GetMainViewBottom() - top - (int)WidgetDimensions::scaled.dropdownlist.Vertical(), 0);
/* If the dropdown doesn't fully fit below the widget... */
if (dim.height > available_height) {
uint available_height_above = std::max(w->top + wi_rect.top - GetMainViewTop() - (int)WidgetDimensions::scaled.dropdownlist.Vertical(), 0);
/* Put the dropdown above if there is more available space. */
if (available_height_above > available_height) {
above = true;
available_height = available_height_above;
}
/* If the dropdown doesn't fully fit, we need a dropdown. */
if (dim.height > available_height) {
scroll = true;
uint avg_height = dim.height / (uint)list.size();
/* Fit the list; create at least one row, even if there is no height available. */
uint rows = std::max<uint>(available_height / avg_height, 1);
dim.height = rows * avg_height;
/* Add space for the scrollbar. */
dim.width += NWidgetScrollbar::GetVerticalDimension().width;
}
/* Set the top position if needed. */
if (above) {
top = w->top + wi_rect.top - dim.height - WidgetDimensions::scaled.dropdownlist.Vertical();
}
}
dim.width = std::max(width, dim.width);
Point dw_pos = { w->left + (_current_text_dir == TD_RTL ? wi_rect.right + 1 - (int)width : wi_rect.left), top};
DropdownWindow *dropdown = new DropdownWindow(w, std::move(list), selected, button, instant_close, dw_pos, dim, wi_colour, scroll);
/* The dropdown starts scrolling downwards when opening it towards
* the top and holding down the mouse button. It can be fooled by
* opening the dropdown scrolled to the very bottom. */
if (above && scroll) dropdown->vscroll->UpdatePosition(INT_MAX);
new DropdownWindow(w, std::move(list), selected, button, wi_rect, instant_close, wi_colour);
}
/**