Change: Unify station/waypoint/roadstop/object build-picker window code.

These windows now share a common code base for choosing and display class and types.

An additional text filter is added to search types by name instead of just classes.
This commit is contained in:
Peter Nelson 2024-05-07 12:13:48 +01:00
parent 80a79787f9
commit bfcd3609f5
No known key found for this signature in database
GPG Key ID: 8EF8F0A467DF75ED
16 changed files with 1261 additions and 1691 deletions

View File

@ -338,6 +338,8 @@ add_files(
palette_func.h
pbs.cpp
pbs.h
picker_gui.cpp
picker_gui.h
progress.cpp
progress.h
querystring_gui.h

View File

@ -2794,7 +2794,6 @@ STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP :{BLACK}Select r
# Rail waypoint construction window
STR_WAYPOINT_CAPTION :{WHITE}Waypoint
STR_WAYPOINT_GRAPHICS_TOOLTIP :{BLACK}Select waypoint type
# Rail station construction window
STR_STATION_BUILD_RAIL_CAPTION :{WHITE}Rail Station Selection
@ -2807,8 +2806,16 @@ STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP :{BLACK}Select l
STR_STATION_BUILD_DRAG_DROP :{BLACK}Drag & Drop
STR_STATION_BUILD_DRAG_DROP_TOOLTIP :{BLACK}Build a station using drag & drop
STR_STATION_BUILD_STATION_CLASS_TOOLTIP :{BLACK}Select a station class to display
STR_STATION_BUILD_STATION_TYPE_TOOLTIP :{BLACK}Select the station type to build
STR_PICKER_STATION_CLASS_TOOLTIP :Select a station class to display
STR_PICKER_STATION_TYPE_TOOLTIP :Select a station type to build. Ctrl+Click to add or remove in saved items
STR_PICKER_WAYPOINT_CLASS_TOOLTIP :Select a waypoint class to display
STR_PICKER_WAYPOINT_TYPE_TOOLTIP :Select a waypoint to build. Ctrl+Click to add or remove in saved items
STR_PICKER_ROADSTOP_BUS_CLASS_TOOLTIP :Select a bus station class to display
STR_PICKER_ROADSTOP_BUS_TYPE_TOOLTIP :Select a bus station type to build. Ctrl+Click to add or remove in saved items
STR_PICKER_ROADSTOP_TRUCK_CLASS_TOOLTIP :Select a lorry station class to display
STR_PICKER_ROADSTOP_TRUCK_TYPE_TOOLTIP :Select a lorry station type to build. Ctrl+Click to add or remove in saved items
STR_PICKER_OBJECT_CLASS_TOOLTIP :Select an object class to display
STR_PICKER_OBJECT_TYPE_TOOLTIP :Select an object type to build. Ctrl+Click to add or remove in saved items. Ctrl+Click+Drag to select the area diagonally. Also press Shift to show cost estimate only
STR_STATION_CLASS_DFLT :Default
STR_STATION_CLASS_DFLT_STATION :Default station
@ -2951,8 +2958,6 @@ STR_LANDSCAPING_TOOLTIP_PURCHASE_LAND :{BLACK}Purchase
# Object construction window
STR_OBJECT_BUILD_CAPTION :{WHITE}Object Selection
STR_OBJECT_BUILD_TOOLTIP :{BLACK}Select object to build. Ctrl+Click+Drag to select the area diagonally. Also press Shift to show cost estimate only
STR_OBJECT_BUILD_CLASS_TOOLTIP :{BLACK}Select class of the object to build
STR_OBJECT_BUILD_PREVIEW_TOOLTIP :{BLACK}Preview of the object
STR_OBJECT_BUILD_SIZE :{BLACK}Size: {GOLD}{NUM} x {NUM} tiles

View File

@ -42,6 +42,9 @@ private:
static void InsertDefaults();
public:
using spec_type = Tspec;
using index_type = Tindex;
uint32_t global_id; ///< Global ID for class, e.g. 'DFLT', 'WAYP', etc.
StringID name; ///< Name of this class.
@ -67,8 +70,6 @@ public:
uint GetSpecCount() const { return static_cast<uint>(this->spec.size()); }
/** Get the number of potentially user-available specs within the class. */
uint GetUISpecCount() const { return this->ui_count; }
int GetUIFromIndex(int index) const;
int GetIndexFromUI(int ui_index) const;
const Tspec *GetSpec(uint index) const;
@ -80,7 +81,6 @@ public:
static void Assign(Tspec *spec);
static uint GetClassCount();
static uint GetUIClassCount();
static Tindex GetUIClass(uint index);
static NewGRFClass *Get(Tindex class_index);
static const Tspec *GetByGrf(uint32_t grfid, uint16_t local_id);

View File

@ -105,21 +105,6 @@ uint NewGRFClass<Tspec, Tindex, Tmax>::GetUIClassCount()
return std::count_if(std::begin(NewGRFClass::classes), std::end(NewGRFClass::classes), [](const auto &cls) { return cls.GetUISpecCount() > 0; });
}
/**
* Get the nth-class with user available specs.
* @param index UI index of a class.
* @return The class ID of the class.
*/
template <typename Tspec, typename Tindex, Tindex Tmax>
Tindex NewGRFClass<Tspec, Tindex, Tmax>::GetUIClass(uint index)
{
for (const auto &cls : NewGRFClass::classes) {
if (cls.GetUISpecCount() == 0) continue;
if (index-- == 0) return cls.Index();
}
NOT_REACHED();
}
/**
* Get a spec from the class at a given index.
* @param index The index where to find the spec.
@ -132,38 +117,6 @@ const Tspec *NewGRFClass<Tspec, Tindex, Tmax>::GetSpec(uint index) const
return index < this->GetSpecCount() ? this->spec[index] : nullptr;
}
/**
* Translate a UI spec index into a spec index.
* @param ui_index UI index of the spec.
* @return index of the spec, or -1 if out of range.
*/
template <typename Tspec, typename Tindex, Tindex Tmax>
int NewGRFClass<Tspec, Tindex, Tmax>::GetIndexFromUI(int ui_index) const
{
if (ui_index < 0) return -1;
for (uint i = 0; i < this->GetSpecCount(); i++) {
if (!this->IsUIAvailable(i)) continue;
if (ui_index-- == 0) return i;
}
return -1;
}
/**
* Translate a spec index into a UI spec index.
* @param index index of the spec.
* @return UI index of the spec, or -1 if out of range.
*/
template <typename Tspec, typename Tindex, Tindex Tmax>
int NewGRFClass<Tspec, Tindex, Tmax>::GetUIFromIndex(int index) const
{
if ((uint)index >= this->GetSpecCount()) return -1;
uint ui_index = 0;
for (int i = 0; i < index; i++) {
if (this->IsUIAvailable(i)) ui_index++;
}
return ui_index;
}
/**
* Retrieve a spec by GRF location.
* @param grfid GRF ID of spec.
@ -177,7 +130,8 @@ const Tspec *NewGRFClass<Tspec, Tindex, Tmax>::GetByGrf(uint32_t grfid, uint16_t
for (const auto &cls : NewGRFClass::classes) {
for (const auto &spec : cls.spec) {
if (spec == nullptr) continue;
if (spec->grf_prop.grffile->grfid == grfid && spec->grf_prop.local_id == local_id) return spec;
if (spec->grf_prop.local_id != local_id) continue;
if ((spec->grf_prop.grffile == nullptr ? 0 : spec->grf_prop.grffile->grfid) == grfid) return spec;
}
}

View File

@ -14,10 +14,8 @@
#include "newgrf_object.h"
#include "newgrf_text.h"
#include "object.h"
#include "querystring_gui.h"
#include "sortlist_type.h"
#include "stringfilter_type.h"
#include "string_func.h"
#include "picker_gui.h"
#include "sound_func.h"
#include "strings_func.h"
#include "viewport_func.h"
#include "tilehighlight_func.h"
@ -34,199 +32,108 @@
#include "safeguards.h"
static ObjectClassID _selected_object_class; ///< Currently selected available object class.
static int _selected_object_index; ///< Index of the currently selected object if existing, else \c -1.
static uint8_t _selected_object_view; ///< the view of the selected object
/** Enum referring to the Hotkeys in the build object window */
enum BuildObjectHotkeys {
BOHK_FOCUS_FILTER_BOX, ///< Focus the edit box for editing the filter string
struct ObjectPickerSelection {
ObjectClassID sel_class; ///< Selected object class.
uint16_t sel_type; ///< Selected object type within the class.
uint8_t sel_view; ///< Selected view of the object.
};
static ObjectPickerSelection _object_gui; ///< Settings of the object picker.
class ObjectPickerCallbacks : public PickerCallbacksNewGRFClass<ObjectClass> {
public:
StringID GetClassTooltip() const override { return STR_PICKER_OBJECT_CLASS_TOOLTIP; }
StringID GetTypeTooltip() const override { return STR_PICKER_OBJECT_TYPE_TOOLTIP; }
bool IsActive() const override
{
for (const auto &cls : ObjectClass::Classes()) {
for (const auto *spec : cls.Specs()) {
if (spec != nullptr && spec->IsEverAvailable()) return true;
}
}
return false;
}
int GetSelectedClass() const override { return _object_gui.sel_class; }
void SetSelectedClass(int id) const override { _object_gui.sel_class = this->GetClassIndex(id); }
StringID GetClassName(int id) const override
{
const auto *objclass = this->GetClass(id);
if (objclass->GetUISpecCount() == 0) return INVALID_STRING_ID;
return objclass->name;
}
int GetSelectedType() const override { return _object_gui.sel_type; }
void SetSelectedType(int id) const override { _object_gui.sel_type = id; }
StringID GetTypeName(int cls_id, int id) const override
{
const auto *spec = this->GetSpec(cls_id, id);
return (spec == nullptr || !spec->IsEverAvailable()) ? INVALID_STRING_ID : spec->name;
}
bool IsTypeAvailable(int cls_id, int id) const override
{
const auto *spec = this->GetSpec(cls_id, id);
return spec->IsAvailable();
}
void DrawType(int x, int y, int cls_id, int id) const override
{
const auto *spec = this->GetSpec(cls_id, id);
if (spec->grf_prop.grffile == nullptr) {
extern const DrawTileSprites _objects[];
const DrawTileSprites *dts = &_objects[spec->grf_prop.local_id];
DrawOrigTileSeqInGUI(x, y, dts, PAL_NONE);
} else {
DrawNewObjectTileInGUI(x, y, spec, std::min<int>(_object_gui.sel_view, spec->views - 1));
}
}
static ObjectPickerCallbacks instance;
};
/* static */ ObjectPickerCallbacks ObjectPickerCallbacks::instance;
/** The window used for building objects. */
class BuildObjectWindow : public Window {
typedef GUIList<ObjectClassID, std::nullptr_t, StringFilter &> GUIObjectClassList; ///< Type definition for the list to hold available object classes.
static const uint EDITBOX_MAX_SIZE = 16; ///< The maximum number of characters for the filter edit box.
int object_margin; ///< The margin (in pixels) around an object.
int line_height; ///< The height of a single line.
int info_height; ///< The height of the info box.
Scrollbar *vscroll; ///< The scrollbar.
static Listing last_sorting; ///< Default sorting of #GUIObjectClassList.
static Filtering last_filtering; ///< Default filtering of #GUIObjectClassList.
static const std::initializer_list<GUIObjectClassList::SortFunction * const> sorter_funcs; ///< Sort functions of the #GUIObjectClassList.
static const std::initializer_list<GUIObjectClassList::FilterFunction * const> filter_funcs; ///< Filter functions of the #GUIObjectClassList.
GUIObjectClassList object_classes; ///< Available object classes.
StringFilter string_filter; ///< Filter for available objects.
QueryString filter_editbox; ///< Filter editbox.
/** Scroll #WID_BO_CLASS_LIST so that the selected object class is visible. */
void EnsureSelectedObjectClassIsVisible()
{
uint pos = 0;
for (auto object_class_id : this->object_classes) {
if (object_class_id == _selected_object_class) break;
pos++;
}
this->vscroll->ScrollTowards(pos);
}
/**
* Tests whether the previously selected object can be selected.
* @return \c true if the selected object is available, \c false otherwise.
*/
bool CanRestoreSelectedObject()
{
if (_selected_object_index == -1) return false;
ObjectClass *objclass = ObjectClass::Get(_selected_object_class);
if ((int)objclass->GetSpecCount() <= _selected_object_index) return false;
return objclass->GetSpec(_selected_object_index)->IsAvailable();
}
class BuildObjectWindow : public PickerWindow {
int info_height; ///< The height of the info box.
public:
BuildObjectWindow(WindowDesc *desc, WindowNumber number) : Window(desc), info_height(1), filter_editbox(EDITBOX_MAX_SIZE * MAX_CHAR_LENGTH, EDITBOX_MAX_SIZE)
BuildObjectWindow(WindowDesc *desc, WindowNumber) : PickerWindow(desc, nullptr, 0, ObjectPickerCallbacks::instance), info_height(1)
{
this->CreateNestedTree();
this->vscroll = this->GetScrollbar(WID_BO_SCROLLBAR);
this->querystrings[WID_BO_FILTER] = &this->filter_editbox;
this->object_classes.SetListing(this->last_sorting);
this->object_classes.SetFiltering(this->last_filtering);
this->object_classes.SetSortFuncs(this->sorter_funcs);
this->object_classes.SetFilterFuncs(this->filter_funcs);
this->object_classes.ForceRebuild();
BuildObjectClassesAvailable();
SelectClassAndObject();
this->FinishInitNested(number);
NWidgetMatrix *matrix = this->GetWidget<NWidgetMatrix>(WID_BO_SELECT_MATRIX);
matrix->SetScrollbar(this->GetScrollbar(WID_BO_SELECT_SCROLL));
matrix->SetCount(ObjectClass::Get(_selected_object_class)->GetUISpecCount());
this->GetWidget<NWidgetMatrix>(WID_BO_OBJECT_MATRIX)->SetCount(4);
ResetObjectToPlace();
this->vscroll->SetCount(this->object_classes.size());
EnsureSelectedObjectClassIsVisible();
this->ConstructWindow();
this->InvalidateData();
}
/** Sort object classes by ObjectClassID. */
static bool ObjectClassIDSorter(ObjectClassID const &a, ObjectClassID const &b)
{
return a < b;
}
/** Filter object classes by class name. */
static bool TagNameFilter(ObjectClassID const *oc, StringFilter &filter)
{
ObjectClass *objclass = ObjectClass::Get(*oc);
filter.ResetState();
filter.AddLine(GetString(objclass->name));
return filter.GetState();
}
/** Builds the filter list of available object classes. */
void BuildObjectClassesAvailable()
{
if (!this->object_classes.NeedRebuild()) return;
this->object_classes.clear();
this->object_classes.reserve(ObjectClass::GetClassCount());
for (const auto &cls : ObjectClass::Classes()) {
if (cls.GetUISpecCount() == 0) continue; // Is this needed here?
object_classes.push_back(cls.Index());
}
this->object_classes.Filter(this->string_filter);
this->object_classes.RebuildDone();
this->object_classes.Sort();
this->vscroll->SetCount(this->object_classes.size());
}
/**
* Checks if the previously selected current object class and object
* can be shown as selected to the user when the dialog is opened.
*/
void SelectClassAndObject()
{
assert(!this->object_classes.empty()); // object GUI should be disabled elsewise
if (_selected_object_class == ObjectClassID::OBJECT_CLASS_BEGIN) {
/* This happens during the first time the window is open during the game life cycle. */
this->SelectOtherClass(this->object_classes[0]);
} else {
/* Check if the previously selected object class is not available anymore as a
* result of starting a new game without the corresponding NewGRF. */
bool available = _selected_object_class < ObjectClass::GetClassCount();
this->SelectOtherClass(available ? _selected_object_class : this->object_classes[0]);
}
if (this->CanRestoreSelectedObject()) {
this->SelectOtherObject(_selected_object_index);
} else {
this->SelectFirstAvailableObject(true);
}
assert(ObjectClass::Get(_selected_object_class)->GetUISpecCount() > 0); // object GUI should be disabled elsewise
}
void SetStringParameters(WidgetID widget) const override
{
switch (widget) {
case WID_BO_OBJECT_NAME: {
ObjectClass *objclass = ObjectClass::Get(_selected_object_class);
const ObjectSpec *spec = objclass->GetSpec(_selected_object_index);
SetDParam(0, spec != nullptr ? spec->name : STR_EMPTY);
break;
}
case WID_BO_OBJECT_SIZE: {
ObjectClass *objclass = ObjectClass::Get(_selected_object_class);
const ObjectSpec *spec = objclass->GetSpec(_selected_object_index);
ObjectClass *objclass = ObjectClass::Get(_object_gui.sel_class);
const ObjectSpec *spec = objclass->GetSpec(_object_gui.sel_type);
int size = spec == nullptr ? 0 : spec->size;
SetDParam(0, GB(size, HasBit(_selected_object_view, 0) ? 4 : 0, 4));
SetDParam(1, GB(size, HasBit(_selected_object_view, 0) ? 0 : 4, 4));
SetDParam(0, GB(size, HasBit(_object_gui.sel_view, 0) ? 4 : 0, 4));
SetDParam(1, GB(size, HasBit(_object_gui.sel_view, 0) ? 0 : 4, 4));
break;
}
default: break;
default:
this->PickerWindow::SetStringParameters(widget);
break;
}
}
void OnInit() override
{
this->object_margin = ScaleGUITrad(4);
this->GetWidget<NWidgetMatrix>(WID_BO_OBJECT_MATRIX)->SetCount(4);
this->PickerWindow::OnInit();
}
void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
{
switch (widget) {
case WID_BO_CLASS_LIST: {
for (auto object_class_id : this->object_classes) {
ObjectClass *objclass = ObjectClass::Get(object_class_id);
if (objclass->GetUISpecCount() == 0) continue;
size.width = std::max(size.width, GetStringBoundingBox(objclass->name).width + padding.width);
}
this->line_height = GetCharacterHeight(FS_NORMAL) + padding.height;
resize.height = this->line_height;
size.height = 5 * this->line_height;
break;
}
case WID_BO_OBJECT_NAME:
case WID_BO_OBJECT_SIZE:
/* We do not want the window to resize when selecting objects; better clip texts */
size.width = 0;
@ -234,8 +141,8 @@ public:
case WID_BO_OBJECT_MATRIX: {
/* Get the right amount of buttons based on the current spec. */
ObjectClass *objclass = ObjectClass::Get(_selected_object_class);
const ObjectSpec *spec = objclass->GetSpec(_selected_object_index);
const ObjectClass *objclass = ObjectClass::Get(_object_gui.sel_class);
const ObjectSpec *spec = objclass->GetSpec(_object_gui.sel_type);
if (spec != nullptr) {
if (spec->views >= 2) size.width += resize.width;
if (spec->views >= 4) size.height += resize.height;
@ -246,41 +153,14 @@ public:
}
case WID_BO_OBJECT_SPRITE: {
bool two_wide = false; // Whether there will be two widgets next to each other in the matrix or not.
uint height[2] = {0, 0}; // The height for the different views; in this case views 1/2 and 4.
/* Get the height and view information. */
for (const auto &spec : ObjectSpec::Specs()) {
if (!spec.IsEverAvailable()) continue;
two_wide |= spec.views >= 2;
height[spec.views / 4] = std::max<int>(spec.height, height[spec.views / 4]);
}
/* Determine the pixel heights. */
for (auto &h : height) {
h *= ScaleGUITrad(TILE_HEIGHT);
h += ScaleGUITrad(TILE_PIXELS) + 2 * this->object_margin;
}
/* Now determine the size of the minimum widgets. When there are two columns, then
* we want these columns to be slightly less wide. When there are two rows, then
* determine the size of the widgets based on the maximum size for a single row
* of widgets, or just the twice the widget height of the two row ones. */
size.height = std::max(height[0], height[1] * 2);
if (two_wide) {
size.width = (3 * ScaleGUITrad(TILE_PIXELS) + 2 * this->object_margin) * 2;
} else {
size.width = 4 * ScaleGUITrad(TILE_PIXELS) + 2 * this->object_margin;
}
/* Get the right size for the single widget based on the current spec. */
ObjectClass *objclass = ObjectClass::Get(_selected_object_class);
const ObjectSpec *spec = objclass->GetSpec(_selected_object_index);
/* Get the right amount of buttons based on the current spec. */
const ObjectClass *objclass = ObjectClass::Get(_object_gui.sel_class);
const ObjectSpec *spec = objclass->GetSpec(_object_gui.sel_type);
size.width = ScaleGUITrad(PREVIEW_WIDTH) + WidgetDimensions::scaled.fullbevel.Horizontal();
size.height = ScaleGUITrad(PREVIEW_HEIGHT) + WidgetDimensions::scaled.fullbevel.Vertical();
if (spec != nullptr) {
if (spec->views <= 1) size.width += WidgetDimensions::scaled.hsep_normal;
if (spec->views <= 2) size.height += WidgetDimensions::scaled.vsep_normal;
if (spec->views >= 2) size.width /= 2;
if (spec->views >= 4) size.height /= 2;
if (spec->views <= 1) size.width = size.width * 2 + WidgetDimensions::scaled.hsep_normal;
if (spec->views <= 2) size.height = size.height * 2 + WidgetDimensions::scaled.vsep_normal;
}
break;
}
@ -289,103 +169,49 @@ public:
size.height = this->info_height;
break;
case WID_BO_SELECT_MATRIX:
fill.height = 1;
resize.height = 1;
default:
this->PickerWindow::UpdateWidgetSize(widget, size, padding, fill, resize);
break;
case WID_BO_SELECT_IMAGE:
size.width = ScaleGUITrad(64) + WidgetDimensions::scaled.fullbevel.Horizontal();
size.height = ScaleGUITrad(58) + WidgetDimensions::scaled.fullbevel.Vertical();
break;
default: break;
}
}
void DrawWidget(const Rect &r, WidgetID widget) const override
{
switch (widget) {
case WID_BO_CLASS_LIST: {
Rect mr = r.Shrink(WidgetDimensions::scaled.matrix);
uint pos = 0;
for (auto object_class_id : this->object_classes) {
ObjectClass *objclass = ObjectClass::Get(object_class_id);
if (objclass->GetUISpecCount() == 0) continue;
if (!this->vscroll->IsVisible(pos++)) continue;
DrawString(mr, objclass->name,
(object_class_id == _selected_object_class) ? TC_WHITE : TC_BLACK);
mr.top += this->line_height;
}
break;
}
case WID_BO_OBJECT_SPRITE: {
if (_selected_object_index == -1) break;
ObjectClass *objclass = ObjectClass::Get(_selected_object_class);
const ObjectSpec *spec = objclass->GetSpec(_selected_object_index);
const ObjectClass *objclass = ObjectClass::Get(_object_gui.sel_class);
const ObjectSpec *spec = objclass->GetSpec(_object_gui.sel_type);
if (spec == nullptr) break;
/* Height of the selection matrix.
* Depending on the number of views, the matrix has a 1x1, 1x2, 2x1 or 2x2 layout. To make the previews
* look nice in all layouts, we use the 4x4 layout (smallest previews) as starting point. For the bigger
* previews in the layouts with less views we add space homogeneously on all sides, so the 4x4 preview-rectangle
* is centered in the 2x1, 1x2 resp. 1x1 buttons. */
const NWidgetMatrix *matrix = this->GetWidget<NWidgetBase>(widget)->GetParentWidget<NWidgetMatrix>();
uint matrix_height = matrix->current_y;
DrawPixelInfo tmp_dpi;
/* Set up a clipping area for the preview. */
Rect ir = r.Shrink(WidgetDimensions::scaled.bevel);
if (FillDrawPixelInfo(&tmp_dpi, ir)) {
AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
int x = (ir.Width() - ScaleSpriteTrad(PREVIEW_WIDTH)) / 2 + ScaleSpriteTrad(PREVIEW_LEFT);
int y = (ir.Height() + ScaleSpriteTrad(PREVIEW_HEIGHT)) / 2 - ScaleSpriteTrad(PREVIEW_BOTTOM);
if (spec->grf_prop.grffile == nullptr) {
extern const DrawTileSprites _objects[];
const DrawTileSprites *dts = &_objects[spec->grf_prop.local_id];
DrawOrigTileSeqInGUI(ir.Width() / 2 - 1, (ir.Height() + matrix_height / 2) / 2 - this->object_margin - ScaleSpriteTrad(TILE_PIXELS), dts, PAL_NONE);
DrawOrigTileSeqInGUI(x, y, dts, PAL_NONE);
} else {
DrawNewObjectTileInGUI(ir.Width() / 2 - 1, (ir.Height() + matrix_height / 2) / 2 - this->object_margin - ScaleSpriteTrad(TILE_PIXELS), spec, matrix->GetCurrentElement());
DrawNewObjectTileInGUI(x, y, spec, matrix->GetCurrentElement());
}
}
break;
}
case WID_BO_SELECT_IMAGE: {
ObjectClass *objclass = ObjectClass::Get(_selected_object_class);
int obj_index = objclass->GetIndexFromUI(this->GetWidget<NWidgetBase>(widget)->GetParentWidget<NWidgetMatrix>()->GetCurrentElement());
if (obj_index < 0) break;
const ObjectSpec *spec = objclass->GetSpec(obj_index);
if (spec == nullptr) break;
DrawPixelInfo tmp_dpi;
/* Set up a clipping area for the preview. */
Rect ir = r.Shrink(WidgetDimensions::scaled.bevel);
if (FillDrawPixelInfo(&tmp_dpi, ir)) {
AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
if (spec->grf_prop.grffile == nullptr) {
extern const DrawTileSprites _objects[];
const DrawTileSprites *dts = &_objects[spec->grf_prop.local_id];
DrawOrigTileSeqInGUI(ir.Width() / 2 - 1, ir.Height() - this->object_margin - ScaleSpriteTrad(TILE_PIXELS), dts, PAL_NONE);
} else {
DrawNewObjectTileInGUI(ir.Width() / 2 - 1, ir.Height() - this->object_margin - ScaleSpriteTrad(TILE_PIXELS), spec,
std::min<int>(_selected_object_view, spec->views - 1));
}
}
if (!spec->IsAvailable()) {
GfxFillRect(ir, PC_BLACK, FILLRECT_CHECKER);
}
break;
}
case WID_BO_INFO: {
ObjectClass *objclass = ObjectClass::Get(_selected_object_class);
const ObjectSpec *spec = objclass->GetSpec(_selected_object_index);
const ObjectClass *objclass = ObjectClass::Get(_object_gui.sel_class);
const ObjectSpec *spec = objclass->GetSpec(_object_gui.sel_type);
if (spec == nullptr) break;
/* Get the extra message for the GUI */
if (HasBit(spec->callback_mask, CBM_OBJ_FUND_MORE_TEXT)) {
uint16_t callback_res = GetObjectCallback(CBID_OBJECT_FUND_MORE_TEXT, 0, 0, spec, nullptr, INVALID_TILE, _selected_object_view);
uint16_t callback_res = GetObjectCallback(CBID_OBJECT_FUND_MORE_TEXT, 0, 0, spec, nullptr, INVALID_TILE, _object_gui.sel_view);
if (callback_res != CALLBACK_FAILED && callback_res != 0x400) {
if (callback_res > 0x400) {
ErrorUnknownCallbackResult(spec->grf_prop.grffile->grfid, CBID_OBJECT_FUND_MORE_TEXT, callback_res);
@ -407,131 +233,80 @@ public:
}
}
}
break;
}
default:
this->PickerWindow::DrawWidget(r, widget);
break;
}
}
/**
* Select the specified object class.
* @param object_class Object class select.
*/
void SelectOtherClass(ObjectClassID object_class)
void UpdateSelectSize(const ObjectSpec *spec)
{
_selected_object_class = object_class;
ObjectClass *objclass = ObjectClass::Get(object_class);
this->GetWidget<NWidgetMatrix>(WID_BO_SELECT_MATRIX)->SetCount(objclass->GetUISpecCount());
}
/**
* Select the specified object in #_selected_object_class class.
* @param object_index Object index to select, \c -1 means select nothing.
*/
void SelectOtherObject(int object_index)
{
_selected_object_index = object_index;
if (_selected_object_index != -1) {
ObjectClass *objclass = ObjectClass::Get(_selected_object_class);
const ObjectSpec *spec = objclass->GetSpec(_selected_object_index);
_selected_object_view = std::min<int>(_selected_object_view, spec->views - 1);
this->ReInit();
} else {
_selected_object_view = 0;
}
if (_selected_object_index != -1) {
SetObjectToPlaceWnd(SPR_CURSOR_TRANSMITTER, PAL_NONE, HT_RECT | HT_DIAGONAL, this);
} else {
ResetObjectToPlace();
}
this->UpdateButtons(_selected_object_class, _selected_object_index, _selected_object_view);
}
void UpdateSelectSize()
{
if (_selected_object_index == -1) {
if (spec == nullptr) {
SetTileSelectSize(1, 1);
ResetObjectToPlace();
} else {
ObjectClass *objclass = ObjectClass::Get(_selected_object_class);
const ObjectSpec *spec = objclass->GetSpec(_selected_object_index);
int w = GB(spec->size, HasBit(_selected_object_view, 0) ? 4 : 0, 4);
int h = GB(spec->size, HasBit(_selected_object_view, 0) ? 0 : 4, 4);
_object_gui.sel_view = std::min<int>(_object_gui.sel_view, spec->views - 1);
SetObjectToPlaceWnd(SPR_CURSOR_TRANSMITTER, PAL_NONE, HT_RECT | HT_DIAGONAL, this);
int w = GB(spec->size, HasBit(_object_gui.sel_view, 0) ? 4 : 0, 4);
int h = GB(spec->size, HasBit(_object_gui.sel_view, 0) ? 0 : 4, 4);
SetTileSelectSize(w, h);
this->ReInit();
}
}
/**
* Update buttons to show the selection to the user.
* @param object_class The class of the selected object.
* @param sel_index Index of the object to select, or \c -1 .
* @param sel_view View of the object to select.
* @param spec The object spec of the selected object.
*/
void UpdateButtons(ObjectClassID object_class, int sel_index, uint sel_view)
void UpdateButtons(const ObjectSpec *spec)
{
int view_number, object_number;
if (sel_index == -1) {
view_number = -1; // If no object selected, also hide the selected view.
object_number = -1;
} else {
view_number = sel_view;
ObjectClass *objclass = ObjectClass::Get(object_class);
object_number = objclass->GetUIFromIndex(sel_index);
}
this->GetWidget<NWidgetMatrix>(WID_BO_OBJECT_MATRIX)->SetClicked(view_number);
this->GetWidget<NWidgetMatrix>(WID_BO_SELECT_MATRIX)->SetClicked(object_number);
this->UpdateSelectSize();
this->GetWidget<NWidgetMatrix>(WID_BO_OBJECT_MATRIX)->SetClicked(_object_gui.sel_view);
this->UpdateSelectSize(spec);
this->SetDirty();
}
void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
{
this->PickerWindow::OnInvalidateData(data, gui_scope);
if (!gui_scope) return;
this->BuildObjectClassesAvailable();
}
void OnResize() override
{
this->vscroll->SetCapacityFromWidget(this, WID_BO_CLASS_LIST);
if ((data & PickerWindow::PFI_POSITION) != 0) {
const auto objclass = ObjectClass::Get(_object_gui.sel_class);
const auto spec = objclass->GetSpec(_object_gui.sel_type);
_object_gui.sel_view = std::min<int>(_object_gui.sel_view, spec->views - 1);
this->UpdateButtons(spec);
}
}
void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
{
switch (widget) {
case WID_BO_CLASS_LIST: {
auto it = this->vscroll->GetScrolledItemFromWidget(this->object_classes, pt.y, this, widget);
if (it == this->object_classes.end()) break;
this->SelectOtherClass(*it);
this->SelectFirstAvailableObject(false);
break;
}
case WID_BO_SELECT_IMAGE: {
ObjectClass *objclass = ObjectClass::Get(_selected_object_class);
int num_clicked = objclass->GetIndexFromUI(this->GetWidget<NWidgetBase>(widget)->GetParentWidget<NWidgetMatrix>()->GetCurrentElement());
if (num_clicked >= 0 && objclass->GetSpec(num_clicked)->IsAvailable()) this->SelectOtherObject(num_clicked);
break;
}
case WID_BO_OBJECT_SPRITE:
if (_selected_object_index != -1) {
_selected_object_view = this->GetWidget<NWidgetBase>(widget)->GetParentWidget<NWidgetMatrix>()->GetCurrentElement();
this->SelectOtherObject(_selected_object_index); // Re-select the object for a different view.
if (_object_gui.sel_type != MAX_UVALUE(uint16_t)) {
_object_gui.sel_view = this->GetWidget<NWidgetBase>(widget)->GetParentWidget<NWidgetMatrix>()->GetCurrentElement();
this->InvalidateData(PickerWindow::PFI_POSITION);
if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
}
break;
default:
this->PickerWindow::OnClick(pt, widget, click_count);
break;
}
}
void OnPlaceObject([[maybe_unused]] Point pt, TileIndex tile) override
{
const ObjectSpec *spec = ObjectClass::Get(_selected_object_class)->GetSpec(_selected_object_index);
const ObjectSpec *spec = ObjectClass::Get(_object_gui.sel_class)->GetSpec(_object_gui.sel_type);
if (spec->size == OBJECT_SIZE_1X1) {
VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_BUILD_OBJECT);
} else {
Command<CMD_BUILD_OBJECT>::Post(STR_ERROR_CAN_T_BUILD_OBJECT, CcPlaySound_CONSTRUCTION_OTHER, tile, spec->Index(), _selected_object_view);
Command<CMD_BUILD_OBJECT>::Post(STR_ERROR_CAN_T_BUILD_OBJECT, CcPlaySound_CONSTRUCTION_OTHER, tile, spec->Index(), _object_gui.sel_view);
}
}
@ -552,85 +327,14 @@ public:
if (TileX(end_tile) == Map::MaxX()) end_tile += TileDiffXY(-1, 0);
if (TileY(end_tile) == Map::MaxY()) end_tile += TileDiffXY(0, -1);
}
const ObjectSpec *spec = ObjectClass::Get(_selected_object_class)->GetSpec(_selected_object_index);
const ObjectSpec *spec = ObjectClass::Get(_object_gui.sel_class)->GetSpec(_object_gui.sel_type);
Command<CMD_BUILD_OBJECT_AREA>::Post(STR_ERROR_CAN_T_BUILD_OBJECT, CcPlaySound_CONSTRUCTION_OTHER,
end_tile, start_tile, spec->Index(), _selected_object_view, (_ctrl_pressed ? true : false));
end_tile, start_tile, spec->Index(), _object_gui.sel_view, (_ctrl_pressed ? true : false));
}
void OnPlaceObjectAbort() override
{
this->UpdateButtons(_selected_object_class, -1, _selected_object_view);
}
EventState OnHotkey(int hotkey) override
{
switch (hotkey) {
case BOHK_FOCUS_FILTER_BOX:
this->SetFocusedWidget(WID_BO_FILTER);
SetFocusedWindow(this); // The user has asked to give focus to the text box, so make sure this window is focused.
break;
default:
return ES_NOT_HANDLED;
}
return ES_HANDLED;
}
void OnEditboxChanged(WidgetID widget) override
{
if (widget == WID_BO_FILTER) {
string_filter.SetFilterTerm(this->filter_editbox.text.buf);
this->object_classes.SetFilterState(!string_filter.IsEmpty());
this->object_classes.ForceRebuild();
this->InvalidateData();
}
}
/**
* Select the first available object.
* @param change_class If true, change the class if no object in the current
* class is available.
*/
void SelectFirstAvailableObject(bool change_class)
{
ObjectClass *objclass = ObjectClass::Get(_selected_object_class);
/* First try to select an object in the selected class. */
for (uint i = 0; i < objclass->GetSpecCount(); i++) {
const ObjectSpec *spec = objclass->GetSpec(i);
if (spec->IsAvailable()) {
this->SelectOtherObject(i);
return;
}
}
if (change_class) {
/* If that fails, select the first available object
* from a random class. */
for (auto object_class_id : this->object_classes) {
ObjectClass *objclass = ObjectClass::Get(object_class_id);
for (uint i = 0; i < objclass->GetSpecCount(); i++) {
const ObjectSpec *spec = objclass->GetSpec(i);
if (spec->IsAvailable()) {
this->SelectOtherClass(object_class_id);
this->SelectOtherObject(i);
return;
}
}
}
}
/* If all objects are unavailable, select nothing... */
if (objclass->GetUISpecCount() == 0) {
/* ... but make sure that the class is not empty. */
for (auto object_class_id : this->object_classes) {
ObjectClass *objclass = ObjectClass::Get(object_class_id);
if (objclass->GetUISpecCount() > 0) {
this->SelectOtherClass(object_class_id);
break;
}
}
}
this->SelectOtherObject(-1);
this->UpdateButtons(nullptr);
}
/**
@ -647,22 +351,10 @@ public:
}
static inline HotkeyList hotkeys{"buildobject", {
Hotkey('F', "focus_filter_box", BOHK_FOCUS_FILTER_BOX),
Hotkey('F', "focus_filter_box", PCWHK_FOCUS_FILTER_BOX),
}, BuildObjectGlobalHotkeys};
};
Listing BuildObjectWindow::last_sorting = { false, 0 };
Filtering BuildObjectWindow::last_filtering = { false, 0 };
const std::initializer_list<BuildObjectWindow::GUIObjectClassList::SortFunction * const> BuildObjectWindow::sorter_funcs = {
&ObjectClassIDSorter,
};
const std::initializer_list<BuildObjectWindow::GUIObjectClassList::FilterFunction * const> BuildObjectWindow::filter_funcs = {
&TagNameFilter,
};
static constexpr NWidgetPart _nested_build_object_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
@ -672,41 +364,22 @@ static constexpr NWidgetPart _nested_build_object_widgets[] = {
NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN),
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPadding(WidgetDimensions::unscaled.picker),
NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_picker, 0),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_TEXT, COLOUR_DARK_GREEN), SetFill(0, 1), SetDataTip(STR_LIST_FILTER_TITLE, STR_NULL),
NWidget(WWT_EDITBOX, COLOUR_GREY, WID_BO_FILTER), SetFill(1, 0), SetResize(1, 0),
SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_MATRIX, COLOUR_GREY, WID_BO_CLASS_LIST), SetFill(1, 0), SetMatrixDataTip(1, 0, STR_OBJECT_BUILD_CLASS_TOOLTIP), SetScrollbar(WID_BO_SCROLLBAR),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_BO_SCROLLBAR),
EndContainer(),
NWidget(NWID_VERTICAL),
NWidgetFunction(MakePickerClassWidgets),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_picker, 0), SetPadding(WidgetDimensions::unscaled.picker),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_ORIENTATION, STR_NULL), SetFill(1, 0),
NWidget(NWID_HORIZONTAL), SetPIPRatio(1, 0, 1),
NWidget(NWID_MATRIX, COLOUR_DARK_GREEN, WID_BO_OBJECT_MATRIX), SetPIP(0, 2, 0),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BO_OBJECT_SPRITE), SetDataTip(0x0, STR_OBJECT_BUILD_PREVIEW_TOOLTIP), EndContainer(),
EndContainer(),
EndContainer(),
EndContainer(),
NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_BO_OBJECT_NAME), SetDataTip(STR_JUST_STRING, STR_NULL), SetTextStyle(TC_ORANGE), SetAlignment(SA_CENTER),
NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_BO_OBJECT_SIZE), SetDataTip(STR_OBJECT_BUILD_SIZE, STR_NULL), SetAlignment(SA_CENTER),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetScrollbar(WID_BO_SELECT_SCROLL),
NWidget(NWID_MATRIX, COLOUR_DARK_GREEN, WID_BO_SELECT_MATRIX), SetPIP(0, 2, 0),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BO_SELECT_IMAGE), SetDataTip(0x0, STR_OBJECT_BUILD_TOOLTIP),
SetFill(0, 0), SetResize(0, 0), SetScrollbar(WID_BO_SELECT_SCROLL),
EndContainer(),
NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_BO_OBJECT_SIZE), SetDataTip(STR_OBJECT_BUILD_SIZE, STR_NULL), SetAlignment(SA_CENTER),
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_BO_INFO), SetFill(1, 0), SetResize(1, 0),
EndContainer(),
EndContainer(),
EndContainer(),
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_BO_INFO), SetPadding(WidgetDimensions::unscaled.framerect), SetFill(1, 0), SetResize(1, 0),
EndContainer(),
NWidget(NWID_VERTICAL),
NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_BO_SELECT_SCROLL),
NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
EndContainer(),
NWidgetFunction(MakePickerTypeWidgets),
EndContainer(),
};
@ -722,7 +395,7 @@ static WindowDesc _build_object_desc(
Window *ShowBuildObjectPicker()
{
/* Don't show the place object button when there are no objects to place. */
if (ObjectClass::GetUIClassCount() > 0) {
if (ObjectPickerCallbacks::instance.IsActive()) {
return AllocateWindowDescFront<BuildObjectWindow>(&_build_object_desc, 0);
}
return nullptr;
@ -731,5 +404,5 @@ Window *ShowBuildObjectPicker()
/** Reset all data of the object GUI. */
void InitializeObjectGui()
{
_selected_object_class = ObjectClassID::OBJECT_CLASS_BEGIN;
_object_gui.sel_class = ObjectClassID::OBJECT_CLASS_BEGIN;
}

465
src/picker_gui.cpp Normal file
View File

@ -0,0 +1,465 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file picker_gui.cpp %File for dealing with picker windows */
#include "stdafx.h"
#include "core/backup_type.hpp"
#include "gui.h"
#include "hotkeys.h"
#include "picker_gui.h"
#include "querystring_gui.h"
#include "settings_type.h"
#include "sortlist_type.h"
#include "sound_func.h"
#include "sound_type.h"
#include "stringfilter_type.h"
#include "strings_func.h"
#include "widget_type.h"
#include "window_func.h"
#include "window_gui.h"
#include "window_type.h"
#include "zoom_func.h"
#include "widgets/picker_widget.h"
#include "safeguards.h"
/** Sort classes by id. */
static bool ClassIDSorter(int const &a, int const &b)
{
return a < b;
}
/** Filter classes by class name. */
static bool ClassTagNameFilter(int const *item, PickerFilterData &filter)
{
filter.ResetState();
filter.AddLine(GetString(filter.callbacks->GetClassName(*item)));
return filter.GetState();
}
/** Sort types by id. */
static bool TypeIDSorter(PickerItem const &a, PickerItem const &b)
{
int r = a.class_index - b.class_index;
if (r == 0) r = a.index - b.index;
return r < 0;
}
/** Filter types by class name. */
static bool TypeTagNameFilter(PickerItem const *item, PickerFilterData &filter)
{
filter.ResetState();
filter.AddLine(GetString(filter.callbacks->GetTypeName(item->class_index, item->index)));
return filter.GetState();
}
static const std::initializer_list<PickerClassList::SortFunction * const> _class_sorter_funcs = { &ClassIDSorter }; ///< Sort functions of the #PickerClassList
static const std::initializer_list<PickerClassList::FilterFunction * const> _class_filter_funcs = { &ClassTagNameFilter }; ///< Filter functions of the #PickerClassList.
static const std::initializer_list<PickerTypeList::SortFunction * const> _type_sorter_funcs = { TypeIDSorter }; ///< Sort functions of the #PickerTypeList.
static const std::initializer_list<PickerTypeList::FilterFunction * const> _type_filter_funcs = { TypeTagNameFilter }; ///< Filter functions of the #PickerTypeList.
PickerWindow::PickerWindow(WindowDesc *desc, Window *parent, int window_number, PickerCallbacks &callbacks) : PickerWindowBase(desc, parent), callbacks(callbacks),
class_editbox(EDITBOX_MAX_SIZE * MAX_CHAR_LENGTH, EDITBOX_MAX_SIZE),
type_editbox(EDITBOX_MAX_SIZE * MAX_CHAR_LENGTH, EDITBOX_MAX_SIZE)
{
this->window_number = window_number;
/* Init of nested tree is deferred.
* PickerWindow::ConstructWindow must be called by the inheriting window. */
}
void PickerWindow::ConstructWindow()
{
this->CreateNestedTree();
/* Test if pickers should be active.*/
bool isActive = this->callbacks.IsActive();
/* Functionality depends on widgets being present, not window class. */
this->has_class_picker = isActive && this->GetWidget<NWidgetBase>(WID_PW_CLASS_LIST) != nullptr && this->callbacks.HasClassChoice();
this->has_type_picker = isActive && this->GetWidget<NWidgetBase>(WID_PW_TYPE_MATRIX) != nullptr;
if (this->has_class_picker) {
this->GetWidget<NWidgetCore>(WID_PW_CLASS_LIST)->tool_tip = this->callbacks.GetClassTooltip();
this->querystrings[WID_PW_CLASS_FILTER] = &this->class_editbox;
} else {
if (auto *nwid = this->GetWidget<NWidgetStacked>(WID_PW_CLASS_SEL); nwid != nullptr) {
/* Check the container orientation. MakeNWidgets adds an additional NWID_VERTICAL container so we check the grand-parent. */
bool is_vertical = (nwid->parent->parent->type == NWID_VERTICAL);
nwid->SetDisplayedPlane(is_vertical ? SZSP_HORIZONTAL : SZSP_VERTICAL);
}
}
this->class_editbox.cancel_button = QueryString::ACTION_CLEAR;
this->class_string_filter.SetFilterTerm(this->class_editbox.text.buf);
this->class_string_filter.callbacks = &this->callbacks;
this->classes.SetListing(this->callbacks.class_last_sorting);
this->classes.SetFiltering(this->callbacks.class_last_filtering);
this->classes.SetSortFuncs(_class_sorter_funcs);
this->classes.SetFilterFuncs(_class_filter_funcs);
if (this->has_type_picker) {
this->GetWidget<NWidgetCore>(WID_PW_TYPE_ITEM)->tool_tip = this->callbacks.GetTypeTooltip();
auto *matrix = this->GetWidget<NWidgetMatrix>(WID_PW_TYPE_MATRIX);
matrix->SetScrollbar(this->GetScrollbar(WID_PW_TYPE_SCROLL));
this->querystrings[WID_PW_TYPE_FILTER] = &this->type_editbox;
} else {
if (auto *nwid = this->GetWidget<NWidgetStacked>(WID_PW_TYPE_SEL); nwid != nullptr) {
/* Check the container orientation. MakeNWidgets adds an additional NWID_VERTICAL container so we check the grand-parent. */
bool is_vertical = (nwid->parent->parent->type == NWID_VERTICAL);
nwid->SetDisplayedPlane(is_vertical ? SZSP_HORIZONTAL : SZSP_VERTICAL);
}
}
this->type_editbox.cancel_button = QueryString::ACTION_CLEAR;
this->type_string_filter.SetFilterTerm(this->type_editbox.text.buf);
this->type_string_filter.callbacks = &this->callbacks;
this->types.SetListing(this->callbacks.type_last_sorting);
this->types.SetFiltering(this->callbacks.type_last_filtering);
this->types.SetSortFuncs(_type_sorter_funcs);
this->types.SetFilterFuncs(_type_filter_funcs);
this->FinishInitNested(this->window_number);
this->InvalidateData(PFI_CLASS | PFI_TYPE | PFI_POSITION | PFI_VALIDATE);
}
void PickerWindow::Close(int data)
{
this->callbacks.Close(data);
this->PickerWindowBase::Close(data);
}
void PickerWindow::UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize)
{
switch (widget) {
/* Class picker */
case WID_PW_CLASS_LIST:
resize.height = GetCharacterHeight(FS_NORMAL) + padding.height;
size.height = 5 * resize.height;
break;
/* Type picker */
case WID_PW_TYPE_MATRIX:
/* At least two items wide. */
size.width += resize.width;
fill.width = resize.width;
fill.height = 1;
/* Resizing in X direction only at blob size, but at pixel level in Y. */
resize.height = 1;
break;
/* Type picker */
case WID_PW_TYPE_ITEM:
size.width = ScaleGUITrad(PREVIEW_WIDTH) + WidgetDimensions::scaled.fullbevel.Horizontal();
size.height = ScaleGUITrad(PREVIEW_HEIGHT) + WidgetDimensions::scaled.fullbevel.Vertical();
break;
}
}
void PickerWindow::DrawWidget(const Rect &r, WidgetID widget) const
{
switch (widget) {
/* Class picker */
case WID_PW_CLASS_LIST: {
Rect ir = r.Shrink(WidgetDimensions::scaled.matrix);
const int selected = this->callbacks.GetSelectedClass();
const auto vscroll = this->GetScrollbar(WID_PW_CLASS_SCROLL);
auto [first, last] = vscroll->GetVisibleRangeIterators(this->classes);
for (auto it = first; it != last; ++it) {
DrawString(ir, this->callbacks.GetClassName(*it), *it == selected ? TC_WHITE : TC_BLACK);
ir.top += this->resize.step_height;
}
break;
}
/* Type picker */
case WID_PW_TYPE_ITEM: {
assert(this->GetWidget<NWidgetBase>(widget)->GetParentWidget<NWidgetMatrix>()->GetCurrentElement() < static_cast<int>(this->types.size()));
const auto &item = this->types[this->GetWidget<NWidgetBase>(widget)->GetParentWidget<NWidgetMatrix>()->GetCurrentElement()];
DrawPixelInfo tmp_dpi;
Rect ir = r.Shrink(WidgetDimensions::scaled.bevel);
if (FillDrawPixelInfo(&tmp_dpi, ir)) {
AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
int x = (ir.Width() - ScaleSpriteTrad(PREVIEW_WIDTH)) / 2 + ScaleSpriteTrad(PREVIEW_LEFT);
int y = (ir.Height() + ScaleSpriteTrad(PREVIEW_HEIGHT)) / 2 - ScaleSpriteTrad(PREVIEW_BOTTOM);
this->callbacks.DrawType(x, y, item.class_index, item.index);
}
if (!this->callbacks.IsTypeAvailable(item.class_index, item.index)) {
GfxFillRect(ir, GetColourGradient(COLOUR_GREY, SHADE_DARKER), FILLRECT_CHECKER);
}
break;
}
case WID_PW_TYPE_NAME:
DrawString(r, this->callbacks.GetTypeName(this->callbacks.GetSelectedClass(), this->callbacks.GetSelectedType()), TC_ORANGE, SA_CENTER);
break;
}
}
void PickerWindow::OnResize()
{
if (this->has_class_picker) {
this->GetScrollbar(WID_PW_CLASS_SCROLL)->SetCapacityFromWidget(this, WID_PW_CLASS_LIST);
}
}
void PickerWindow::OnClick(Point pt, WidgetID widget, int)
{
switch (widget) {
/* Class Picker */
case WID_PW_CLASS_LIST: {
const auto vscroll = this->GetWidget<NWidgetScrollbar>(WID_PW_CLASS_SCROLL);
auto it = vscroll->GetScrolledItemFromWidget(this->classes, pt.y, this, WID_PW_CLASS_LIST);
if (it == this->classes.end()) return;
if (this->callbacks.GetSelectedClass() != *it) {
this->callbacks.SetSelectedClass(*it);
this->InvalidateData(PFI_TYPE | PFI_POSITION | PFI_VALIDATE);
}
if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
CloseWindowById(WC_SELECT_STATION, 0);
break;
}
/* Type Picker */
case WID_PW_TYPE_ITEM: {
int sel = this->GetWidget<NWidgetBase>(widget)->GetParentWidget<NWidgetMatrix>()->GetCurrentElement();
assert(sel < (int)this->types.size());
const auto &item = this->types[sel];
if (this->callbacks.IsTypeAvailable(item.class_index, item.index)) {
this->callbacks.SetSelectedClass(item.class_index);
this->callbacks.SetSelectedType(item.index);
this->InvalidateData(PFI_POSITION);
}
if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
CloseWindowById(WC_SELECT_STATION, 0);
break;
}
}
}
void PickerWindow::OnInvalidateData(int data, bool gui_scope)
{
if (!gui_scope) return;
if ((data & PFI_CLASS) != 0) this->classes.ForceRebuild();
if ((data & PFI_TYPE) != 0) this->types.ForceRebuild();
this->BuildPickerClassList();
if ((data & PFI_VALIDATE) != 0) this->EnsureSelectedClassIsValid();
if ((data & PFI_POSITION) != 0) this->EnsureSelectedClassIsVisible();
this->BuildPickerTypeList();
if ((data & PFI_VALIDATE) != 0) this->EnsureSelectedTypeIsValid();
if ((data & PFI_POSITION) != 0) this->EnsureSelectedTypeIsVisible();
}
EventState PickerWindow::OnHotkey(int hotkey)
{
switch (hotkey) {
case PCWHK_FOCUS_FILTER_BOX:
/* Cycle between the two edit boxes. */
if (this->has_type_picker && (this->nested_focus == nullptr || this->nested_focus->index != WID_PW_TYPE_FILTER)) {
this->SetFocusedWidget(WID_PW_TYPE_FILTER);
} else if (this->has_class_picker && (this->nested_focus == nullptr || this->nested_focus->index != WID_PW_CLASS_FILTER)) {
this->SetFocusedWidget(WID_PW_CLASS_FILTER);
}
SetFocusedWindow(this);
return ES_HANDLED;
default:
return ES_NOT_HANDLED;
}
}
void PickerWindow::OnEditboxChanged(WidgetID wid)
{
switch (wid) {
case WID_PW_CLASS_FILTER:
this->class_string_filter.SetFilterTerm(this->class_editbox.text.buf);
this->classes.SetFilterState(!class_string_filter.IsEmpty());
this->InvalidateData(PFI_CLASS);
break;
case WID_PW_TYPE_FILTER:
this->type_string_filter.SetFilterTerm(this->type_editbox.text.buf);
this->types.SetFilterState(!type_string_filter.IsEmpty());
this->InvalidateData(PFI_TYPE);
break;
default:
break;
}
}
/** Builds the filter list of classes. */
void PickerWindow::BuildPickerClassList()
{
if (!this->classes.NeedRebuild()) return;
int count = this->callbacks.GetClassCount();
this->classes.clear();
this->classes.reserve(count);
for (int i = 0; i < count; i++) {
if (this->callbacks.GetClassName(i) == INVALID_STRING_ID) continue;
this->classes.emplace_back(i);
}
this->classes.Filter(this->class_string_filter);
this->classes.RebuildDone();
this->classes.Sort();
if (!this->has_class_picker) return;
this->GetScrollbar(WID_PW_CLASS_SCROLL)->SetCount(this->classes.size());
}
void PickerWindow::EnsureSelectedClassIsValid()
{
int class_index = this->callbacks.GetSelectedClass();
if (std::binary_search(std::begin(this->classes), std::end(this->classes), class_index)) return;
class_index = this->classes.front();
this->callbacks.SetSelectedClass(class_index);
}
void PickerWindow::EnsureSelectedClassIsVisible()
{
if (!this->has_class_picker) return;
if (this->classes.empty()) return;
auto it = std::find(std::begin(this->classes), std::end(this->classes), this->callbacks.GetSelectedClass());
if (it == std::end(this->classes)) return;
int pos = static_cast<int>(std::distance(std::begin(this->classes), it));
this->GetScrollbar(WID_PW_CLASS_SCROLL)->ScrollTowards(pos);
}
/** Builds the filter list of types. */
void PickerWindow::BuildPickerTypeList()
{
if (!this->types.NeedRebuild()) return;
this->types.clear();
int cls_id = this->callbacks.GetSelectedClass();
{
/* Add types in only the selected class. */
if (cls_id >= 0 && cls_id < this->callbacks.GetClassCount()) {
int count = this->callbacks.GetTypeCount(cls_id);
this->types.reserve(count);
for (int i = 0; i < count; i++) {
if (this->callbacks.GetTypeName(cls_id, i) == INVALID_STRING_ID) continue;
this->types.emplace_back(this->callbacks.GetPickerItem(cls_id, i));
}
}
}
this->types.Filter(this->type_string_filter);
this->types.RebuildDone();
this->types.Sort();
if (!this->has_type_picker) return;
this->GetWidget<NWidgetMatrix>(WID_PW_TYPE_MATRIX)->SetCount(static_cast<int>(this->types.size()));
}
void PickerWindow::EnsureSelectedTypeIsValid()
{
int class_index = this->callbacks.GetSelectedClass();
int index = this->callbacks.GetSelectedType();
if (std::any_of(std::begin(this->types), std::end(this->types), [class_index, index](const auto &item) { return item.class_index == class_index && item.index == index; })) return;
class_index = this->types.front().class_index;
index = this->types.front().index;
this->callbacks.SetSelectedClass(class_index);
this->callbacks.SetSelectedType(index);
}
void PickerWindow::EnsureSelectedTypeIsVisible()
{
if (!this->has_type_picker) return;
if (this->types.empty()) {
this->GetWidget<NWidgetMatrix>(WID_PW_TYPE_MATRIX)->SetClicked(-1);
return;
}
int class_index = this->callbacks.GetSelectedClass();
int index = this->callbacks.GetSelectedType();
auto it = std::find_if(std::begin(this->types), std::end(this->types), [class_index, index](const auto &item) { return item.class_index == class_index && item.index == index; });
if (it == std::end(this->types)) return;
int pos = static_cast<int>(std::distance(std::begin(this->types), it));
this->GetWidget<NWidgetMatrix>(WID_PW_TYPE_MATRIX)->SetClicked(pos);
}
/** Create nested widgets for the class picker widgets. */
std::unique_ptr<NWidgetBase> MakePickerClassWidgets()
{
static constexpr NWidgetPart picker_class_widgets[] = {
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_PW_CLASS_SEL),
NWidget(NWID_VERTICAL),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
NWidget(WWT_EDITBOX, COLOUR_DARK_GREEN, WID_PW_CLASS_FILTER), SetMinimalSize(144, 0), SetPadding(2), SetFill(1, 0), SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
NWidget(WWT_MATRIX, COLOUR_GREY, WID_PW_CLASS_LIST), SetFill(1, 1), SetResize(1, 1), SetPadding(WidgetDimensions::unscaled.picker),
SetMatrixDataTip(1, 0, STR_NULL), SetScrollbar(WID_PW_CLASS_SCROLL),
EndContainer(),
NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_PW_CLASS_SCROLL),
EndContainer(),
EndContainer(),
EndContainer(),
};
return MakeNWidgets(std::begin(picker_class_widgets), std::end(picker_class_widgets), nullptr);
}
/** Create nested widgets for the type picker widgets. */
std::unique_ptr<NWidgetBase> MakePickerTypeWidgets()
{
static constexpr NWidgetPart picker_type_widgets[] = {
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_PW_TYPE_SEL),
NWidget(NWID_VERTICAL),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
NWidget(WWT_EDITBOX, COLOUR_DARK_GREEN, WID_PW_TYPE_FILTER), SetPadding(2), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetScrollbar(WID_PW_TYPE_SCROLL),
NWidget(NWID_MATRIX, COLOUR_DARK_GREEN, WID_PW_TYPE_MATRIX), SetPIP(0, 2, 0), SetPadding(WidgetDimensions::unscaled.picker),
NWidget(WWT_PANEL, COLOUR_GREY, WID_PW_TYPE_ITEM), SetScrollbar(WID_PW_TYPE_SCROLL),
EndContainer(),
EndContainer(),
EndContainer(),
NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_PW_TYPE_SCROLL),
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_PW_TYPE_NAME), SetPadding(WidgetDimensions::unscaled.framerect), SetResize(1, 0), SetFill(1, 0), SetMinimalTextLines(1, 0),
EndContainer(),
NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN, WID_PW_TYPE_RESIZE),
EndContainer(),
EndContainer(),
EndContainer(),
};
return MakeNWidgets(std::begin(picker_type_widgets), std::end(picker_type_widgets), nullptr);
}

181
src/picker_gui.h Normal file
View File

@ -0,0 +1,181 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file picker_gui.h Functions/types etc. related to the picker GUI. */
#ifndef PICKER_GUI_H
#define PICKER_GUI_H
#include "querystring_gui.h"
#include "sortlist_type.h"
#include "stringfilter_type.h"
#include "strings_type.h"
#include "timer/timer.h"
#include "timer/timer_game_calendar.h"
#include "window_gui.h"
#include "window_type.h"
struct PickerItem {
uint32_t grfid;
uint16_t local_id;
int class_index;
int index;
inline auto operator<=>(const PickerItem &other) const
{
if (auto cmp = this->grfid <=> other.grfid; cmp != 0) return cmp;
return this->local_id <=> other.local_id;
}
};
/** Class for PickerClassWindow to collect information and retain state. */
class PickerCallbacks {
public:
virtual ~PickerCallbacks() {}
virtual void Close(int) { }
/** Should picker class/type selection be enabled? */
virtual bool IsActive() const = 0;
/** Are there multiple classes to chose from? */
virtual bool HasClassChoice() const = 0;
/* Class callbacks */
/** Get the tooltip string for the class list. */
virtual StringID GetClassTooltip() const = 0;
/** Get the number of classes. @note Used only to estimate space requirements. */
virtual int GetClassCount() const = 0;
/** Get the index of the selected class. */
virtual int GetSelectedClass() const = 0;
/** Set the selected class. */
virtual void SetSelectedClass(int id) const = 0;
/** Get the name of a class. */
virtual StringID GetClassName(int id) const = 0;
/* Type callbacks */
/** Get the tooltip string for the type grid. */
virtual StringID GetTypeTooltip() const = 0;
/** Get the number of types in a class. @note Used only to estimate space requirements. */
virtual int GetTypeCount(int cls_id) const = 0;
/** Get the selected type. */
virtual int GetSelectedType() const = 0;
/** Set the selected type. */
virtual void SetSelectedType(int id) const = 0;
/** Get data about an item. */
virtual PickerItem GetPickerItem(int cls_id, int id) const = 0;
/** Get the item of a type. */
virtual StringID GetTypeName(int cls_id, int id) const = 0;
/** Test if an item is currently buildable. */
virtual bool IsTypeAvailable(int cls_id, int id) const = 0;
/** Draw preview image of an item. */
virtual void DrawType(int x, int y, int cls_id, int id) const = 0;
Listing class_last_sorting = { false, 0 }; ///< Default sorting of #PickerClassList.
Filtering class_last_filtering = { false, 0 }; ///< Default filtering of #PickerClassList.
Listing type_last_sorting = { false, 0 }; ///< Default sorting of #PickerTypeList.
Filtering type_last_filtering = { false, 0 }; ///< Default filtering of #PickerTypeList.
};
/** Helper for PickerCallbacks when the class system is based on NewGRFClass. */
template <typename T>
class PickerCallbacksNewGRFClass : public PickerCallbacks {
public:
inline typename T::index_type GetClassIndex(int cls_id) const { return static_cast<typename T::index_type>(cls_id); }
inline const T *GetClass(int cls_id) const { return T::Get(this->GetClassIndex(cls_id)); }
inline const typename T::spec_type *GetSpec(int cls_id, int id) const { return this->GetClass(cls_id)->GetSpec(id); }
bool HasClassChoice() const override { return T::GetUIClassCount() > 1; }
int GetClassCount() const override { return T::GetClassCount(); }
int GetTypeCount(int cls_id) const override { return this->GetClass(cls_id)->GetSpecCount(); }
PickerItem GetPickerItem(const typename T::spec_type *spec, int cls_id = -1, int id = -1) const
{
if (spec == nullptr) return {0, 0, cls_id, id};
return {spec->grf_prop.grffile == nullptr ? 0 : spec->grf_prop.grffile->grfid, spec->grf_prop.local_id, spec->class_index, spec->index};
}
PickerItem GetPickerItem(int cls_id, int id) const override
{
return GetPickerItem(GetClass(cls_id)->GetSpec(id), cls_id, id);
}
};
struct PickerFilterData : StringFilter {
const PickerCallbacks *callbacks; ///< Callbacks for filter functions to access to callbacks.
};
using PickerClassList = GUIList<int, std::nullptr_t, PickerFilterData &>; ///< GUIList holding classes to display.
using PickerTypeList = GUIList<PickerItem, std::nullptr_t, PickerFilterData &>; ///< GUIList holding classes/types to display.
class PickerWindow : public PickerWindowBase {
public:
enum PickerFilterInvalidation {
PFI_CLASS = 1U << 0, ///< Refresh the class list.
PFI_TYPE = 1U << 1, ///< Refresh the type list.
PFI_POSITION = 1U << 2, ///< Update scroll positions.
PFI_VALIDATE = 1U << 3, ///< Validate selected item.
};
static const int PREVIEW_WIDTH = 64; ///< Width of each preview button.
static const int PREVIEW_HEIGHT = 48; ///< Height of each preview button.
static const int PREVIEW_LEFT = 31; ///< Offset from left edge to draw preview.
static const int PREVIEW_BOTTOM = 31; ///< Offset from bottom edge to draw preview.
static const uint EDITBOX_MAX_SIZE = 16; ///< The maximum number of characters for the filter edit box.
bool has_class_picker = false; ///< Set if this window has a class picker 'component'.
bool has_type_picker = false; ///< Set if this window has a type picker 'component'.
PickerWindow(WindowDesc *desc, Window *parent, int window_number, PickerCallbacks &callbacks);
void Close(int data = 0) override;
void UpdateWidgetSize(WidgetID widget, Dimension &size, const Dimension &padding, Dimension &fill, Dimension &resize) override;
void DrawWidget(const Rect &r, WidgetID widget) const override;
void OnResize() override;
void OnClick(Point pt, WidgetID widget, int click_count) override;
void OnInvalidateData(int data = 0, bool gui_scope = true) override;
EventState OnHotkey(int hotkey) override;
void OnEditboxChanged(WidgetID wid) override;
/** Enum referring to the Hotkeys in the picker window */
enum PickerClassWindowHotkeys {
PCWHK_FOCUS_FILTER_BOX, ///< Focus the edit box for editing the filter string
};
protected:
void ConstructWindow();
PickerCallbacks &callbacks;
private:
PickerClassList classes; ///< List of classes.
PickerFilterData class_string_filter;
QueryString class_editbox; ///< Filter editbox.
void BuildPickerClassList();
void EnsureSelectedClassIsValid();
void EnsureSelectedClassIsVisible();
PickerTypeList types; ///< List of types.
PickerFilterData type_string_filter;
QueryString type_editbox; ///< Filter editbox
void BuildPickerTypeList();
void EnsureSelectedTypeIsValid();
void EnsureSelectedTypeIsVisible();
IntervalTimer<TimerGameCalendar> yearly_interval = {{TimerGameCalendar::YEAR, TimerGameCalendar::Priority::NONE}, [this](auto) {
this->SetDirty();
}};
};
class NWidgetBase;
std::unique_ptr<NWidgetBase> MakePickerClassWidgets();
std::unique_ptr<NWidgetBase> MakePickerTypeWidgets();
#endif /* PICKER_GUI_H */

File diff suppressed because it is too large Load Diff

View File

@ -36,10 +36,7 @@
#include "road_cmd.h"
#include "tunnelbridge_cmd.h"
#include "newgrf_roadstop.h"
#include "querystring_gui.h"
#include "sortlist_type.h"
#include "stringfilter_type.h"
#include "string_func.h"
#include "picker_gui.h"
#include "timer/timer.h"
#include "timer/timer_game_calendar.h"
@ -64,40 +61,43 @@ static RoadType _cur_roadtype;
static DiagDirection _road_depot_orientation;
struct RoadStopGUISettings {
DiagDirection orientation;
RoadStopClassID roadstop_class;
uint16_t roadstop_type;
uint16_t roadstop_count;
struct RoadStopPickerSelection {
RoadStopClassID sel_class; ///< Selected road stop class.
uint16_t sel_type; ///< Selected road stop type within the class.
DiagDirection orientation; ///< Selected orientation of the road stop.
};
static RoadStopGUISettings _roadstop_gui_settings;
static RoadStopPickerSelection _roadstop_gui;
static bool IsRoadStopEverAvailable(const RoadStopSpec *spec, StationType type)
{
if (spec == nullptr) return true;
if (HasBit(spec->flags, RSF_BUILD_MENU_ROAD_ONLY) && !RoadTypeIsRoad(_cur_roadtype)) return false;
if (HasBit(spec->flags, RSF_BUILD_MENU_TRAM_ONLY) && !RoadTypeIsTram(_cur_roadtype)) return false;
switch (spec->stop_type) {
case ROADSTOPTYPE_ALL: return true;
case ROADSTOPTYPE_PASSENGER: return type == STATION_BUS;
case ROADSTOPTYPE_FREIGHT: return type == STATION_TRUCK;
default: NOT_REACHED();
}
}
/**
* Check whether a road stop type can be built.
* @return true if building is allowed.
*/
static bool IsRoadStopAvailable(const RoadStopSpec *roadstopspec, StationType type)
static bool IsRoadStopAvailable(const RoadStopSpec *spec, StationType type)
{
if (roadstopspec == nullptr) return true;
if (spec == nullptr) return true;
if (IsRoadStopEverAvailable(spec, type)) return true;
if (HasBit(roadstopspec->flags, RSF_BUILD_MENU_ROAD_ONLY) && !RoadTypeIsRoad(_cur_roadtype)) return false;
if (HasBit(roadstopspec->flags, RSF_BUILD_MENU_TRAM_ONLY) && !RoadTypeIsTram(_cur_roadtype)) return false;
if (!HasBit(spec->callback_mask, CBM_ROAD_STOP_AVAIL)) return true;
if (roadstopspec->stop_type != ROADSTOPTYPE_ALL) {
switch (type) {
case STATION_BUS: if (roadstopspec->stop_type != ROADSTOPTYPE_PASSENGER) return false; break;
case STATION_TRUCK: if (roadstopspec->stop_type != ROADSTOPTYPE_FREIGHT) return false; break;
default: break;
}
}
if (!HasBit(roadstopspec->callback_mask, CBM_ROAD_STOP_AVAIL)) return true;
uint16_t cb_res = GetRoadStopCallback(CBID_STATION_AVAILABILITY, 0, 0, roadstopspec, nullptr, INVALID_TILE, _cur_roadtype, type, 0);
uint16_t cb_res = GetRoadStopCallback(CBID_STATION_AVAILABILITY, 0, 0, spec, nullptr, INVALID_TILE, _cur_roadtype, type, 0);
if (cb_res == CALLBACK_FAILED) return true;
return Convert8bitBooleanCallback(roadstopspec->grf_prop.grffile, CBID_STATION_AVAILABILITY, cb_res);
return Convert8bitBooleanCallback(spec->grf_prop.grffile, CBID_STATION_AVAILABILITY, cb_res);
}
void CcPlaySound_CONSTRUCTION_OTHER(Commands, const CommandCost &result, TileIndex tile)
@ -217,11 +217,11 @@ void CcRoadStop(Commands, const CommandCost &result, TileIndex tile, uint8_t wid
static void PlaceRoadStop(TileIndex start_tile, TileIndex end_tile, RoadStopType stop_type, bool adjacent, RoadType rt, StringID err_msg)
{
TileArea ta(start_tile, end_tile);
DiagDirection ddir = _roadstop_gui_settings.orientation;
DiagDirection ddir = _roadstop_gui.orientation;
bool drive_through = ddir >= DIAGDIR_END;
if (drive_through) ddir = static_cast<DiagDirection>(ddir - DIAGDIR_END); // Adjust picker result to actual direction.
RoadStopClassID spec_class = _roadstop_gui_settings.roadstop_class;
uint16_t spec_index = _roadstop_gui_settings.roadstop_type;
RoadStopClassID spec_class = _roadstop_gui.sel_class;
uint16_t spec_index = _roadstop_gui.sel_type;
auto proc = [=](bool test, StationID to_join) -> bool {
if (test) {
@ -245,8 +245,8 @@ static void PlaceRoad_BusStation(TileIndex tile)
if (_remove_button_clicked) {
VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_REMOVE_BUSSTOP);
} else {
if (_roadstop_gui_settings.orientation < DIAGDIR_END) { // Not a drive-through stop.
VpStartPlaceSizing(tile, (DiagDirToAxis(_roadstop_gui_settings.orientation) == AXIS_X) ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_BUSSTOP);
if (_roadstop_gui.orientation < DIAGDIR_END) { // Not a drive-through stop.
VpStartPlaceSizing(tile, (DiagDirToAxis(_roadstop_gui.orientation) == AXIS_X) ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_BUSSTOP);
} else {
VpStartPlaceSizing(tile, VPM_X_AND_Y_LIMITED, DDSP_BUILD_BUSSTOP);
}
@ -263,8 +263,8 @@ static void PlaceRoad_TruckStation(TileIndex tile)
if (_remove_button_clicked) {
VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_REMOVE_TRUCKSTOP);
} else {
if (_roadstop_gui_settings.orientation < DIAGDIR_END) { // Not a drive-through stop.
VpStartPlaceSizing(tile, (DiagDirToAxis(_roadstop_gui_settings.orientation) == AXIS_X) ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_TRUCKSTOP);
if (_roadstop_gui.orientation < DIAGDIR_END) { // Not a drive-through stop.
VpStartPlaceSizing(tile, (DiagDirToAxis(_roadstop_gui.orientation) == AXIS_X) ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_TRUCKSTOP);
} else {
VpStartPlaceSizing(tile, VPM_X_AND_Y_LIMITED, DDSP_BUILD_TRUCKSTOP);
}
@ -708,7 +708,7 @@ struct BuildRoadToolbarWindow : Window {
case DDSP_BUILD_BUSSTOP:
case DDSP_REMOVE_BUSSTOP:
if (this->IsWidgetLowered(WID_ROT_BUS_STATION) && GetIfClassHasNewStopsByType(RoadStopClass::Get(_roadstop_gui_settings.roadstop_class), ROADSTOP_BUS, _cur_roadtype)) {
if (this->IsWidgetLowered(WID_ROT_BUS_STATION) && GetIfClassHasNewStopsByType(RoadStopClass::Get(_roadstop_gui.sel_class), ROADSTOP_BUS, _cur_roadtype)) {
if (_remove_button_clicked) {
TileArea ta(start_tile, end_tile);
Command<CMD_REMOVE_ROAD_STOP>::Post(this->rti->strings.err_remove_station[ROADSTOP_BUS], CcPlaySound_CONSTRUCTION_OTHER, ta.tile, ta.w, ta.h, ROADSTOP_BUS, _ctrl_pressed);
@ -720,7 +720,7 @@ struct BuildRoadToolbarWindow : Window {
case DDSP_BUILD_TRUCKSTOP:
case DDSP_REMOVE_TRUCKSTOP:
if (this->IsWidgetLowered(WID_ROT_TRUCK_STATION) && GetIfClassHasNewStopsByType(RoadStopClass::Get(_roadstop_gui_settings.roadstop_class), ROADSTOP_TRUCK, _cur_roadtype)) {
if (this->IsWidgetLowered(WID_ROT_TRUCK_STATION) && GetIfClassHasNewStopsByType(RoadStopClass::Get(_roadstop_gui.sel_class), ROADSTOP_TRUCK, _cur_roadtype)) {
if (_remove_button_clicked) {
TileArea ta(start_tile, end_tile);
Command<CMD_REMOVE_ROAD_STOP>::Post(this->rti->strings.err_remove_station[ROADSTOP_TRUCK], CcPlaySound_CONSTRUCTION_OTHER, ta.tile, ta.w, ta.h, ROADSTOP_TRUCK, _ctrl_pressed);
@ -1093,101 +1093,126 @@ static void ShowRoadDepotPicker(Window *parent)
new BuildRoadDepotWindow(&_build_road_depot_desc, parent);
}
/** Enum referring to the Hotkeys in the build road stop window */
enum BuildRoadStopHotkeys {
BROSHK_FOCUS_FILTER_BOX, ///< Focus the edit box for editing the filter string
template <RoadStopType roadstoptype>
class RoadStopPickerCallbacks : public PickerCallbacksNewGRFClass<RoadStopClass> {
public:
StringID GetClassTooltip() const override;
StringID GetTypeTooltip() const override;
bool IsActive() const override
{
for (const auto &cls : RoadStopClass::Classes()) {
if (IsWaypointClass(cls)) continue;
for (const auto *spec : cls.Specs()) {
if (spec == nullptr) continue;
if (roadstoptype == ROADSTOP_TRUCK && spec->stop_type != ROADSTOPTYPE_FREIGHT && spec->stop_type != ROADSTOPTYPE_ALL) continue;
if (roadstoptype == ROADSTOP_BUS && spec->stop_type != ROADSTOPTYPE_PASSENGER && spec->stop_type != ROADSTOPTYPE_ALL) continue;
return true;
}
}
return false;
}
static bool IsClassChoice(const RoadStopClass &cls)
{
return !IsWaypointClass(cls) && GetIfClassHasNewStopsByType(&cls, roadstoptype, _cur_roadtype);
}
bool HasClassChoice() const override
{
return std::count_if(std::begin(RoadStopClass::Classes()), std::end(RoadStopClass::Classes()), IsClassChoice);
}
int GetSelectedClass() const override { return _roadstop_gui.sel_class; }
void SetSelectedClass(int id) const override { _roadstop_gui.sel_class = this->GetClassIndex(id); }
StringID GetClassName(int id) const override
{
const auto *rsc = this->GetClass(id);
if (!IsClassChoice(*rsc)) return INVALID_STRING_ID;
return rsc->name;
}
int GetSelectedType() const override { return _roadstop_gui.sel_type; }
void SetSelectedType(int id) const override { _roadstop_gui.sel_type = id; }
StringID GetTypeName(int cls_id, int id) const override
{
const auto *spec = this->GetSpec(cls_id, id);
if (!IsRoadStopEverAvailable(spec, roadstoptype == ROADSTOP_BUS ? STATION_BUS : STATION_TRUCK)) return INVALID_STRING_ID;
return (spec == nullptr) ? STR_STATION_CLASS_DFLT_ROADSTOP : spec->name;
}
bool IsTypeAvailable(int cls_id, int id) const override
{
const auto *spec = this->GetSpec(cls_id, id);
return IsRoadStopAvailable(spec, roadstoptype == ROADSTOP_BUS ? STATION_BUS : STATION_TRUCK);
}
void DrawType(int x, int y, int cls_id, int id) const override
{
const auto *spec = this->GetSpec(cls_id, id);
if (spec == nullptr) {
StationPickerDrawSprite(x, y, roadstoptype == ROADSTOP_BUS ? STATION_BUS : STATION_TRUCK, INVALID_RAILTYPE, _cur_roadtype, _roadstop_gui.orientation);
} else {
DiagDirection orientation = _roadstop_gui.orientation;
if (orientation < DIAGDIR_END && HasBit(spec->flags, RSF_DRIVE_THROUGH_ONLY)) orientation = DIAGDIR_END;
DrawRoadStopTile(x, y, _cur_roadtype, spec, roadstoptype == ROADSTOP_BUS ? STATION_BUS : STATION_TRUCK, (uint8_t)orientation);
}
}
};
struct BuildRoadStationWindow : public PickerWindowBase {
template <> StringID RoadStopPickerCallbacks<ROADSTOP_BUS>::GetClassTooltip() const { return STR_PICKER_ROADSTOP_BUS_CLASS_TOOLTIP; }
template <> StringID RoadStopPickerCallbacks<ROADSTOP_BUS>::GetTypeTooltip() const { return STR_PICKER_ROADSTOP_BUS_TYPE_TOOLTIP; }
template <> StringID RoadStopPickerCallbacks<ROADSTOP_TRUCK>::GetClassTooltip() const { return STR_PICKER_ROADSTOP_TRUCK_CLASS_TOOLTIP; }
template <> StringID RoadStopPickerCallbacks<ROADSTOP_TRUCK>::GetTypeTooltip() const { return STR_PICKER_ROADSTOP_TRUCK_TYPE_TOOLTIP; }
static RoadStopPickerCallbacks<ROADSTOP_BUS> _bus_callback_instance;
static RoadStopPickerCallbacks<ROADSTOP_TRUCK> _truck_callback_instance;
static PickerCallbacks &GetRoadStopPickerCallbacks(RoadStopType rs)
{
return rs == ROADSTOP_BUS ? static_cast<PickerCallbacks &>(_bus_callback_instance) : static_cast<PickerCallbacks &>(_truck_callback_instance);
}
struct BuildRoadStationWindow : public PickerWindow {
private:
RoadStopType roadStopType; ///< The RoadStopType for this Window.
uint line_height; ///< Height of a single line in the newstation selection matrix.
uint coverage_height; ///< Height of the coverage texts.
Scrollbar *vscrollList; ///< Vertical scrollbar of the new station list.
Scrollbar *vscrollMatrix; ///< Vertical scrollbar of the station picker matrix.
typedef GUIList<RoadStopClassID, std::nullptr_t, StringFilter &> GUIRoadStopClassList; ///< Type definition for the list to hold available road stop classes.
static const uint EDITBOX_MAX_SIZE = 16; ///< The maximum number of characters for the filter edit box.
static Listing last_sorting; ///< Default sorting of #GUIRoadStopClassList.
static Filtering last_filtering; ///< Default filtering of #GUIRoadStopClassList.
static const std::initializer_list<GUIRoadStopClassList::SortFunction * const> sorter_funcs; ///< Sort functions of the #GUIRoadStopClassList.
static const std::initializer_list<GUIRoadStopClassList::FilterFunction * const> filter_funcs; ///< Filter functions of the #GUIRoadStopClassList.
GUIRoadStopClassList roadstop_classes; ///< Available road stop classes.
StringFilter string_filter; ///< Filter for available road stop classes.
QueryString filter_editbox; ///< Filter editbox.
void EnsureSelectedClassIsVisible()
{
uint pos = 0;
for (auto rs_class : this->roadstop_classes) {
if (rs_class == _roadstop_gui_settings.roadstop_class) break;
pos++;
}
this->vscrollList->SetCount(this->roadstop_classes.size());
this->vscrollList->ScrollTowards(pos);
}
void CheckOrientationValid()
{
const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(_roadstop_gui_settings.roadstop_type);
const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui.sel_class)->GetSpec(_roadstop_gui.sel_type);
/* Raise and lower to ensure the correct widget is lowered after changing displayed orientation plane. */
if (RoadTypeIsRoad(_cur_roadtype)) {
this->RaiseWidget(WID_BROS_STATION_NE + _roadstop_gui_settings.orientation);
this->RaiseWidget(WID_BROS_STATION_NE + _roadstop_gui.orientation);
this->GetWidget<NWidgetStacked>(WID_BROS_AVAILABLE_ORIENTATIONS)->SetDisplayedPlane((spec != nullptr && HasBit(spec->flags, RSF_DRIVE_THROUGH_ONLY)) ? 1 : 0);
this->LowerWidget(WID_BROS_STATION_NE + _roadstop_gui_settings.orientation);
this->LowerWidget(WID_BROS_STATION_NE + _roadstop_gui.orientation);
}
if (_roadstop_gui_settings.orientation >= DIAGDIR_END) return;
if (_roadstop_gui.orientation >= DIAGDIR_END) return;
if (spec != nullptr && HasBit(spec->flags, RSF_DRIVE_THROUGH_ONLY)) {
this->RaiseWidget(WID_BROS_STATION_NE + _roadstop_gui_settings.orientation);
_roadstop_gui_settings.orientation = DIAGDIR_END;
this->LowerWidget(WID_BROS_STATION_NE + _roadstop_gui_settings.orientation);
this->RaiseWidget(WID_BROS_STATION_NE + _roadstop_gui.orientation);
_roadstop_gui.orientation = DIAGDIR_END;
this->LowerWidget(WID_BROS_STATION_NE + _roadstop_gui.orientation);
this->SetDirty();
CloseWindowById(WC_SELECT_STATION, 0);
}
}
public:
BuildRoadStationWindow(WindowDesc *desc, Window *parent, RoadStopType rs) : PickerWindowBase(desc, parent), filter_editbox(EDITBOX_MAX_SIZE * MAX_CHAR_LENGTH, EDITBOX_MAX_SIZE)
BuildRoadStationWindow(WindowDesc *desc, Window *parent, RoadStopType rs) : PickerWindow(desc, parent, TRANSPORT_ROAD, GetRoadStopPickerCallbacks(rs))
{
this->coverage_height = 2 * GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.vsep_normal;
this->vscrollList = nullptr;
this->vscrollMatrix = nullptr;
this->roadStopType = rs;
bool newstops = GetIfNewStopsByType(rs, _cur_roadtype);
this->CreateNestedTree();
/* Hide the station class filter if no stations other than the default one are available. */
this->GetWidget<NWidgetStacked>(WID_BROS_SHOW_NEWST_DEFSIZE)->SetDisplayedPlane(newstops ? 0 : SZSP_NONE);
this->GetWidget<NWidgetStacked>(WID_BROS_FILTER_CONTAINER)->SetDisplayedPlane(newstops ? 0 : SZSP_HORIZONTAL);
this->GetWidget<NWidgetStacked>(WID_BROS_SHOW_NEWST_ADDITIONS)->SetDisplayedPlane(newstops ? 0 : SZSP_HORIZONTAL);
this->GetWidget<NWidgetStacked>(WID_BROS_SHOW_NEWST_ORIENTATION)->SetDisplayedPlane(newstops ? 0 : SZSP_HORIZONTAL);
this->GetWidget<NWidgetStacked>(WID_BROS_SHOW_NEWST_TYPE_SEL)->SetDisplayedPlane(newstops ? 0 : SZSP_HORIZONTAL);
this->GetWidget<NWidgetStacked>(WID_BROS_SHOW_NEWST_MATRIX)->SetDisplayedPlane(newstops ? 0 : SZSP_NONE);
this->GetWidget<NWidgetStacked>(WID_BROS_SHOW_NEWST_RESIZE)->SetDisplayedPlane(newstops ? 0 : SZSP_NONE);
if (newstops) {
this->vscrollList = this->GetScrollbar(WID_BROS_NEWST_SCROLL);
this->vscrollMatrix = this->GetScrollbar(WID_BROS_MATRIX_SCROLL);
this->querystrings[WID_BROS_FILTER_EDITBOX] = &this->filter_editbox;
this->roadstop_classes.SetListing(this->last_sorting);
this->roadstop_classes.SetFiltering(this->last_filtering);
this->roadstop_classes.SetSortFuncs(this->sorter_funcs);
this->roadstop_classes.SetFilterFuncs(this->filter_funcs);
}
this->roadstop_classes.ForceRebuild();
BuildRoadStopClassesAvailable();
/* Trams don't have non-drivethrough stations */
if (RoadTypeIsTram(_cur_roadtype) && _roadstop_gui_settings.orientation < DIAGDIR_END) {
_roadstop_gui_settings.orientation = DIAGDIR_END;
if (RoadTypeIsTram(_cur_roadtype) && _roadstop_gui.orientation < DIAGDIR_END) {
_roadstop_gui.orientation = DIAGDIR_END;
}
this->ConstructWindow();
const RoadTypeInfo *rti = GetRoadTypeInfo(_cur_roadtype);
this->GetWidget<NWidgetCore>(WID_BROS_CAPTION)->widget_data = rti->strings.picker_title[rs];
@ -1195,130 +1220,24 @@ public:
this->GetWidget<NWidgetCore>(i)->tool_tip = rti->strings.picker_tooltip[rs];
}
this->LowerWidget(WID_BROS_STATION_NE + _roadstop_gui_settings.orientation);
this->LowerWidget(WID_BROS_STATION_NE + _roadstop_gui.orientation);
this->LowerWidget(WID_BROS_LT_OFF + _settings_client.gui.station_show_coverage);
this->FinishInitNested(TRANSPORT_ROAD);
this->window_class = (rs == ROADSTOP_BUS) ? WC_BUS_STATION : WC_TRUCK_STATION;
if (!newstops || _roadstop_gui_settings.roadstop_class >= RoadStopClass::GetClassCount()) {
/* There's no new stops available or the list has reduced in size.
* Now, set the default road stops as selected. */
_roadstop_gui_settings.roadstop_class = ROADSTOP_CLASS_DFLT;
_roadstop_gui_settings.roadstop_type = 0;
}
if (newstops) {
/* The currently selected class doesn't have any stops for this RoadStopType, reset the selection. */
if (!GetIfClassHasNewStopsByType(RoadStopClass::Get(_roadstop_gui_settings.roadstop_class), rs, _cur_roadtype)) {
_roadstop_gui_settings.roadstop_class = ROADSTOP_CLASS_DFLT;
_roadstop_gui_settings.roadstop_type = 0;
}
_roadstop_gui_settings.roadstop_count = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpecCount();
_roadstop_gui_settings.roadstop_type = std::min((int)_roadstop_gui_settings.roadstop_type, _roadstop_gui_settings.roadstop_count - 1);
/* Reset back to default class if the previously selected class is not available for this road stop type. */
if (!GetIfClassHasNewStopsByType(RoadStopClass::Get(_roadstop_gui_settings.roadstop_class), roadStopType, _cur_roadtype)) {
_roadstop_gui_settings.roadstop_class = ROADSTOP_CLASS_DFLT;
}
this->SelectFirstAvailableTypeIfUnavailable();
NWidgetMatrix *matrix = this->GetWidget<NWidgetMatrix>(WID_BROS_MATRIX);
matrix->SetScrollbar(this->vscrollMatrix);
matrix->SetCount(_roadstop_gui_settings.roadstop_count);
matrix->SetClicked(_roadstop_gui_settings.roadstop_type);
this->EnsureSelectedClassIsVisible();
this->CheckOrientationValid();
}
}
void Close([[maybe_unused]] int data = 0) override
{
CloseWindowById(WC_SELECT_STATION, 0);
this->PickerWindowBase::Close();
}
/** Sort classes by RoadStopClassID. */
static bool RoadStopClassIDSorter(RoadStopClassID const &a, RoadStopClassID const &b)
{
return a < b;
}
/** Filter classes by class name. */
static bool TagNameFilter(RoadStopClassID const *sc, StringFilter &filter)
{
filter.ResetState();
filter.AddLine(GetString(RoadStopClass::Get(*sc)->name));
return filter.GetState();
}
inline bool ShowNewStops() const
{
return this->vscrollList != nullptr;
}
void BuildRoadStopClassesAvailable()
{
if (!this->roadstop_classes.NeedRebuild()) return;
this->roadstop_classes.clear();
this->roadstop_classes.reserve(RoadStopClass::GetClassCount());
for (const auto &cls : RoadStopClass::Classes()) {
/* Skip waypoints. */
if (IsWaypointClass(cls)) continue;
if (GetIfClassHasNewStopsByType(&cls, this->roadStopType, _cur_roadtype)) this->roadstop_classes.push_back(cls.Index());
}
if (this->ShowNewStops()) {
this->roadstop_classes.Filter(this->string_filter);
this->roadstop_classes.RebuildDone();
this->roadstop_classes.Sort();
this->vscrollList->SetCount(this->roadstop_classes.size());
}
}
void SelectFirstAvailableTypeIfUnavailable()
{
const RoadStopClass *rs_class = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class);
StationType st = GetRoadStationTypeByWindowClass(this->window_class);
if (IsRoadStopAvailable(rs_class->GetSpec(_roadstop_gui_settings.roadstop_type), st)) return;
for (uint i = 0; i < _roadstop_gui_settings.roadstop_count; i++) {
if (IsRoadStopAvailable(rs_class->GetSpec(i), st)) {
_roadstop_gui_settings.roadstop_type = i;
break;
}
}
this->PickerWindow::Close();
}
void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
{
if (!gui_scope) return;
this->PickerWindow::OnInvalidateData(data, gui_scope);
this->BuildRoadStopClassesAvailable();
}
EventState OnHotkey(int hotkey) override
{
if (hotkey == BROSHK_FOCUS_FILTER_BOX) {
this->SetFocusedWidget(WID_BROS_FILTER_EDITBOX);
SetFocusedWindow(this); // The user has asked to give focus to the text box, so make sure this window is focused.
return ES_HANDLED;
}
return ES_NOT_HANDLED;
}
void OnEditboxChanged(WidgetID widget) override
{
if (widget == WID_BROS_FILTER_EDITBOX) {
string_filter.SetFilterTerm(this->filter_editbox.text.buf);
this->roadstop_classes.SetFilterState(!string_filter.IsEmpty());
this->roadstop_classes.ForceRebuild();
this->InvalidateData();
if (gui_scope) {
this->CheckOrientationValid();
}
}
@ -1353,52 +1272,23 @@ public:
void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
{
switch (widget) {
case WID_BROS_NEWST_LIST: {
Dimension d = { 0, 0 };
for (auto rs_class : this->roadstop_classes) {
d = maxdim(d, GetStringBoundingBox(RoadStopClass::Get(rs_class)->name));
}
size.width = std::max(size.width, d.width + padding.width);
this->line_height = GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.matrix.Vertical();
size.height = 5 * this->line_height;
resize.height = this->line_height;
break;
}
case WID_BROS_SHOW_NEWST_TYPE: {
Dimension d = {0, 0};
StringID str = this->GetWidget<NWidgetCore>(widget)->widget_data;
for (auto roadstop_class : this->roadstop_classes) {
RoadStopClass *rs_class = RoadStopClass::Get(roadstop_class);
for (uint j = 0; j < rs_class->GetSpecCount(); j++) {
const RoadStopSpec *roadstopspec = rs_class->GetSpec(j);
SetDParam(0, (roadstopspec != nullptr && roadstopspec->name != 0) ? roadstopspec->name : STR_STATION_CLASS_DFLT_ROADSTOP);
d = maxdim(d, GetStringBoundingBox(str));
}
}
size.width = std::max(size.width, d.width + padding.width);
break;
}
case WID_BROS_STATION_NE:
case WID_BROS_STATION_SE:
case WID_BROS_STATION_SW:
case WID_BROS_STATION_NW:
case WID_BROS_STATION_X:
case WID_BROS_STATION_Y:
case WID_BROS_IMAGE:
size.width = ScaleGUITrad(64) + WidgetDimensions::scaled.fullbevel.Horizontal();
size.height = ScaleGUITrad(48) + WidgetDimensions::scaled.fullbevel.Vertical();
break;
case WID_BROS_MATRIX:
fill.height = 1;
resize.height = 1;
size.width = ScaleGUITrad(PREVIEW_WIDTH) + WidgetDimensions::scaled.fullbevel.Horizontal();
size.height = ScaleGUITrad(PREVIEW_HEIGHT) + WidgetDimensions::scaled.fullbevel.Vertical();
break;
case WID_BROS_ACCEPTANCE:
size.height = this->coverage_height;
break;
default:
this->PickerWindow::UpdateWidgetSize(widget, size, padding, fill, resize);
break;
}
}
@ -1424,13 +1314,13 @@ public:
case WID_BROS_STATION_X:
case WID_BROS_STATION_Y: {
StationType st = GetRoadStationTypeByWindowClass(this->window_class);
const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(_roadstop_gui_settings.roadstop_type);
const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui.sel_class)->GetSpec(_roadstop_gui.sel_type);
DrawPixelInfo tmp_dpi;
Rect ir = r.Shrink(WidgetDimensions::scaled.bevel);
if (FillDrawPixelInfo(&tmp_dpi, ir)) {
AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
int x = (ir.Width() - ScaleSpriteTrad(64)) / 2 + ScaleSpriteTrad(31);
int y = (ir.Height() + ScaleSpriteTrad(48)) / 2 - ScaleSpriteTrad(31);
int x = (ir.Width() - ScaleSpriteTrad(PREVIEW_WIDTH)) / 2 + ScaleSpriteTrad(PREVIEW_LEFT);
int y = (ir.Height() + ScaleSpriteTrad(PREVIEW_HEIGHT)) / 2 - ScaleSpriteTrad(PREVIEW_BOTTOM);
if (spec == nullptr) {
StationPickerDrawSprite(x, y, st, INVALID_RAILTYPE, _cur_roadtype, widget - WID_BROS_STATION_NE);
} else {
@ -1440,63 +1330,9 @@ public:
break;
}
case WID_BROS_NEWST_LIST: {
uint statclass = 0;
uint row = 0;
for (auto rs_class : this->roadstop_classes) {
if (this->vscrollList->IsVisible(statclass)) {
DrawString(r.left + WidgetDimensions::scaled.matrix.left, r.right, row * this->line_height + r.top + WidgetDimensions::scaled.matrix.top,
RoadStopClass::Get(rs_class)->name,
rs_class == _roadstop_gui_settings.roadstop_class ? TC_WHITE : TC_BLACK);
row++;
}
statclass++;
}
default:
this->PickerWindow::DrawWidget(r, widget);
break;
}
case WID_BROS_IMAGE: {
uint16_t type = this->GetWidget<NWidgetBase>(widget)->GetParentWidget<NWidgetMatrix>()->GetCurrentElement();
assert(type < _roadstop_gui_settings.roadstop_count);
const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(type);
StationType st = GetRoadStationTypeByWindowClass(this->window_class);
/* Set up a clipping area for the sprite preview. */
DrawPixelInfo tmp_dpi;
Rect ir = r.Shrink(WidgetDimensions::scaled.bevel);
if (FillDrawPixelInfo(&tmp_dpi, ir)) {
AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
int x = (ir.Width() - ScaleSpriteTrad(64)) / 2 + ScaleSpriteTrad(31);
int y = (ir.Height() + ScaleSpriteTrad(48)) / 2 - ScaleSpriteTrad(31);
if (spec == nullptr) {
StationPickerDrawSprite(x, y, st, INVALID_RAILTYPE, _cur_roadtype, _roadstop_gui_settings.orientation);
} else {
DiagDirection orientation = _roadstop_gui_settings.orientation;
if (orientation < DIAGDIR_END && HasBit(spec->flags, RSF_DRIVE_THROUGH_ONLY)) orientation = DIAGDIR_END;
DrawRoadStopTile(x, y, _cur_roadtype, spec, st, (uint8_t)orientation);
}
}
if (!IsRoadStopAvailable(spec, st)) {
GfxFillRect(ir, PC_BLACK, FILLRECT_CHECKER);
}
break;
}
}
}
void OnResize() override
{
if (this->vscrollList != nullptr) {
this->vscrollList->SetCapacityFromWidget(this, WID_BROS_NEWST_LIST);
}
}
void SetStringParameters(WidgetID widget) const override
{
if (widget == WID_BROS_SHOW_NEWST_TYPE) {
const RoadStopSpec *roadstopspec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(_roadstop_gui_settings.roadstop_type);
SetDParam(0, (roadstopspec != nullptr && roadstopspec->name != 0) ? roadstopspec->name : STR_STATION_CLASS_DFLT_ROADSTOP);
}
}
@ -1510,12 +1346,12 @@ public:
case WID_BROS_STATION_X:
case WID_BROS_STATION_Y:
if (widget < WID_BROS_STATION_X) {
const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(_roadstop_gui_settings.roadstop_type);
const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui.sel_class)->GetSpec(_roadstop_gui.sel_type);
if (spec != nullptr && HasBit(spec->flags, RSF_DRIVE_THROUGH_ONLY)) return;
}
this->RaiseWidget(WID_BROS_STATION_NE + _roadstop_gui_settings.orientation);
_roadstop_gui_settings.orientation = (DiagDirection)(widget - WID_BROS_STATION_NE);
this->LowerWidget(WID_BROS_STATION_NE + _roadstop_gui_settings.orientation);
this->RaiseWidget(WID_BROS_STATION_NE + _roadstop_gui.orientation);
_roadstop_gui.orientation = (DiagDirection)(widget - WID_BROS_STATION_NE);
this->LowerWidget(WID_BROS_STATION_NE + _roadstop_gui.orientation);
if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
this->SetDirty();
CloseWindowById(WC_SELECT_STATION, 0);
@ -1531,49 +1367,8 @@ public:
SetViewportCatchmentStation(nullptr, true);
break;
case WID_BROS_NEWST_LIST: {
auto it = this->vscrollList->GetScrolledItemFromWidget(this->roadstop_classes, pt.y, this, WID_BROS_NEWST_LIST);
if (it == this->roadstop_classes.end()) return;
RoadStopClassID class_id = *it;
if (_roadstop_gui_settings.roadstop_class != class_id && GetIfClassHasNewStopsByType(RoadStopClass::Get(class_id), roadStopType, _cur_roadtype)) {
_roadstop_gui_settings.roadstop_class = class_id;
RoadStopClass *rsclass = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class);
_roadstop_gui_settings.roadstop_count = rsclass->GetSpecCount();
_roadstop_gui_settings.roadstop_type = std::min((int)_roadstop_gui_settings.roadstop_type, std::max(0, (int)_roadstop_gui_settings.roadstop_count - 1));
this->SelectFirstAvailableTypeIfUnavailable();
NWidgetMatrix *matrix = this->GetWidget<NWidgetMatrix>(WID_BROS_MATRIX);
matrix->SetCount(_roadstop_gui_settings.roadstop_count);
matrix->SetClicked(_roadstop_gui_settings.roadstop_type);
}
if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
this->SetDirty();
CloseWindowById(WC_SELECT_STATION, 0);
this->CheckOrientationValid();
break;
}
case WID_BROS_IMAGE: {
uint16_t y = this->GetWidget<NWidgetBase>(widget)->GetParentWidget<NWidgetMatrix>()->GetCurrentElement();
if (y >= _roadstop_gui_settings.roadstop_count) return;
const RoadStopSpec *spec = RoadStopClass::Get(_roadstop_gui_settings.roadstop_class)->GetSpec(y);
StationType st = GetRoadStationTypeByWindowClass(this->window_class);
if (!IsRoadStopAvailable(spec, st)) return;
_roadstop_gui_settings.roadstop_type = y;
this->GetWidget<NWidgetBase>(widget)->GetParentWidget<NWidgetMatrix>()->SetClicked(_roadstop_gui_settings.roadstop_type);
if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
this->SetDirty();
CloseWindowById(WC_SELECT_STATION, 0);
this->CheckOrientationValid();
break;
}
default:
this->PickerWindow::OnClick(pt, widget, click_count);
break;
}
}
@ -1583,119 +1378,66 @@ public:
CheckRedrawStationCoverage(this);
}
IntervalTimer<TimerGameCalendar> yearly_interval = {{TimerGameCalendar::YEAR, TimerGameCalendar::Priority::NONE}, [this](auto) {
this->InvalidateData();
}};
static inline HotkeyList road_hotkeys{"buildroadstop", {
Hotkey('F', "focus_filter_box", BROSHK_FOCUS_FILTER_BOX),
Hotkey('F', "focus_filter_box", PCWHK_FOCUS_FILTER_BOX),
}};
static inline HotkeyList tram_hotkeys{"buildtramstop", {
Hotkey('F', "focus_filter_box", BROSHK_FOCUS_FILTER_BOX),
Hotkey('F', "focus_filter_box", PCWHK_FOCUS_FILTER_BOX),
}};
};
Listing BuildRoadStationWindow::last_sorting = { false, 0 };
Filtering BuildRoadStationWindow::last_filtering = { false, 0 };
const std::initializer_list<BuildRoadStationWindow::GUIRoadStopClassList::SortFunction * const> BuildRoadStationWindow::sorter_funcs = {
&RoadStopClassIDSorter,
};
const std::initializer_list<BuildRoadStationWindow::GUIRoadStopClassList::FilterFunction * const> BuildRoadStationWindow::filter_funcs = {
&TagNameFilter,
};
/** Widget definition of the build road station window */
static constexpr NWidgetPart _nested_road_station_picker_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BROS_CAPTION),
NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_DEFSIZE),
NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
EndContainer(),
NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), SetPadding(WidgetDimensions::unscaled.picker),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1),
NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_picker, 0),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_FILTER_CONTAINER),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_TEXT, COLOUR_DARK_GREEN), SetFill(0, 1), SetDataTip(STR_LIST_FILTER_TITLE, STR_NULL),
NWidget(WWT_EDITBOX, COLOUR_GREY, WID_BROS_FILTER_EDITBOX), SetFill(1, 0), SetResize(1, 0),
SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
NWidget(NWID_VERTICAL),
NWidgetFunction(MakePickerClassWidgets),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_picker, 0), SetPadding(WidgetDimensions::unscaled.picker),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_AVAILABLE_ORIENTATIONS),
/* 6-orientation plane. */
NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1),
NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_NW), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_NE), SetFill(0, 0), EndContainer(),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_X), SetFill(0, 0), EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1),
NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_SW), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_SE), SetFill(0, 0), EndContainer(),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_Y), SetFill(0, 0), EndContainer(),
EndContainer(),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_ADDITIONS),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_MATRIX, COLOUR_GREY, WID_BROS_NEWST_LIST), SetMinimalSize(122, 71), SetFill(1, 0),
SetMatrixDataTip(1, 0, STR_STATION_BUILD_STATION_CLASS_TOOLTIP), SetScrollbar(WID_BROS_NEWST_SCROLL),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_BROS_NEWST_SCROLL),
EndContainer(),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_ORIENTATION),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_ORIENTATION, STR_NULL), SetFill(1, 0),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_AVAILABLE_ORIENTATIONS),
/* 6-orientation plane. */
NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1),
NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_NW), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_NE), SetFill(0, 0), EndContainer(),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_X), SetFill(0, 0), EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1),
NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_SW), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_SE), SetFill(0, 0), EndContainer(),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_Y), SetFill(0, 0), EndContainer(),
EndContainer(),
EndContainer(),
/* 2-orientation plane. */
NWidget(NWID_VERTICAL), SetPIPRatio(0, 0, 1),
NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_X), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_Y), SetFill(0, 0), EndContainer(),
EndContainer(),
EndContainer(),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_TYPE_SEL),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_BROS_SHOW_NEWST_TYPE), SetMinimalSize(144, 8), SetDataTip(STR_JUST_STRING, STR_NULL), SetTextStyle(TC_ORANGE), SetFill(1, 0),
EndContainer(),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetFill(1, 0),
NWidget(NWID_HORIZONTAL), SetPIPRatio(1, 0, 1),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_OFF), SetMinimalSize(60, 12),
SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_ON), SetMinimalSize(60, 12),
SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
EndContainer(),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_MATRIX),
/* Hidden panel as NWID_MATRIX does not support SetScrollbar() */
NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetScrollbar(WID_BROS_MATRIX_SCROLL),
NWidget(NWID_MATRIX, COLOUR_DARK_GREEN, WID_BROS_MATRIX), SetPIP(0, 2, 0),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BROS_IMAGE),
SetFill(0, 0), SetResize(0, 0), SetDataTip(0x0, STR_STATION_BUILD_STATION_TYPE_TOOLTIP), SetScrollbar(WID_BROS_MATRIX_SCROLL),
EndContainer(),
/* 2-orientation plane. */
NWidget(NWID_VERTICAL), SetPIPRatio(0, 0, 1),
NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_X), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_Y), SetFill(0, 0), EndContainer(),
EndContainer(),
EndContainer(),
EndContainer(),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetFill(1, 0),
NWidget(NWID_HORIZONTAL), SetPIPRatio(1, 0, 1),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_OFF), SetMinimalSize(60, 12),
SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_ON), SetMinimalSize(60, 12),
SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
EndContainer(),
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_BROS_ACCEPTANCE), SetFill(1, 1), SetResize(1, 0), SetMinimalTextLines(2, 0),
EndContainer(),
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_BROS_ACCEPTANCE), SetFill(1, 1), SetResize(1, 0), SetMinimalTextLines(2, WidgetDimensions::unscaled.vsep_normal),
EndContainer(),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_RESIZE),
NWidget(NWID_VERTICAL),
NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_BROS_MATRIX_SCROLL),
NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
EndContainer(),
EndContainer(),
NWidgetFunction(MakePickerTypeWidgets),
EndContainer(),
};
@ -1713,67 +1455,29 @@ static constexpr NWidgetPart _nested_tram_station_picker_widgets[] = {
NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BROS_CAPTION),
NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_DEFSIZE),
NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
EndContainer(),
NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), SetPadding(WidgetDimensions::unscaled.picker),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1),
NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_picker, 0),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_FILTER_CONTAINER),
NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
NWidget(WWT_TEXT, COLOUR_DARK_GREEN), SetFill(0, 1), SetDataTip(STR_LIST_FILTER_TITLE, STR_NULL),
NWidget(WWT_EDITBOX, COLOUR_GREY, WID_BROS_FILTER_EDITBOX), SetFill(1, 0), SetResize(1, 0),
SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
EndContainer(),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_ADDITIONS),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_MATRIX, COLOUR_GREY, WID_BROS_NEWST_LIST), SetMinimalSize(122, 71), SetFill(1, 0),
SetMatrixDataTip(1, 0, STR_STATION_BUILD_STATION_CLASS_TOOLTIP), SetScrollbar(WID_BROS_NEWST_SCROLL),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_BROS_NEWST_SCROLL),
EndContainer(),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_ORIENTATION),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetMinimalSize(144, 11), SetDataTip(STR_STATION_BUILD_ORIENTATION, STR_NULL), SetFill(1, 0),
EndContainer(),
NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_X), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_Y), SetFill(0, 0), EndContainer(),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_TYPE_SEL),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_BROS_SHOW_NEWST_TYPE), SetMinimalSize(144, 8), SetDataTip(STR_JUST_STRING, STR_NULL), SetTextStyle(TC_ORANGE), SetFill(1, 0),
EndContainer(),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetFill(1, 0),
NWidget(NWID_HORIZONTAL), SetPIPRatio(1, 0, 1),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_OFF), SetMinimalSize(60, 12),
SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_ON), SetMinimalSize(60, 12),
SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
EndContainer(),
NWidget(NWID_VERTICAL),
NWidgetFunction(MakePickerClassWidgets),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_picker, 0), SetPadding(WidgetDimensions::unscaled.picker),
NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_X), SetFill(0, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_BROS_STATION_Y), SetFill(0, 0), EndContainer(),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_MATRIX),
/* Hidden panel as NWID_MATRIX does not support SetScrollbar() */
NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetScrollbar(WID_BROS_MATRIX_SCROLL),
NWidget(NWID_MATRIX, COLOUR_DARK_GREEN, WID_BROS_MATRIX), SetPIP(0, 2, 0),
NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BROS_IMAGE),
SetFill(0, 0), SetResize(0, 0), SetDataTip(0x0, STR_STATION_BUILD_STATION_TYPE_TOOLTIP), SetScrollbar(WID_BROS_MATRIX_SCROLL),
EndContainer(),
EndContainer(),
EndContainer(),
NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetFill(1, 0),
NWidget(NWID_HORIZONTAL), SetPIPRatio(1, 0, 1),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_OFF), SetMinimalSize(60, 12),
SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BROS_LT_ON), SetMinimalSize(60, 12),
SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
EndContainer(),
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_BROS_ACCEPTANCE), SetFill(1, 1), SetResize(1, 0), SetMinimalTextLines(2, 0),
EndContainer(),
NWidget(WWT_EMPTY, INVALID_COLOUR, WID_BROS_ACCEPTANCE), SetFill(1, 1), SetResize(1, 0), SetMinimalTextLines(2, WidgetDimensions::unscaled.vsep_normal),
EndContainer(),
EndContainer(),
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BROS_SHOW_NEWST_RESIZE),
NWidget(NWID_VERTICAL),
NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_BROS_MATRIX_SCROLL),
NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN),
EndContainer(),
EndContainer(),
NWidgetFunction(MakePickerTypeWidgets),
EndContainer(),
};
@ -1793,7 +1497,7 @@ static void ShowRVStationPicker(Window *parent, RoadStopType rs)
void InitializeRoadGui()
{
_road_depot_orientation = DIAGDIR_NW;
_roadstop_gui_settings.orientation = DIAGDIR_NW;
_roadstop_gui.orientation = DIAGDIR_NW;
}
/**

View File

@ -21,12 +21,13 @@
* Draw a waypoint
* @param x coordinate
* @param y coordinate
* @param stat_id station id
* @param station_class Station class.
* @param station_type Station type within class.
* @param railtype RailType to use for
*/
void DrawWaypointSprite(int x, int y, int stat_id, RailType railtype)
void DrawWaypointSprite(int x, int y, StationClassID station_class, uint16_t station_type, RailType railtype)
{
if (!DrawStationTile(x, y, railtype, AXIS_X, STAT_CLASS_WAYP, stat_id)) {
if (!DrawStationTile(x, y, railtype, AXIS_X, station_class, station_type)) {
StationPickerDrawSprite(x, y, STATION_WAYPOINT, railtype, INVALID_ROADTYPE, AXIS_X);
}
}

View File

@ -14,10 +14,12 @@
#include "command_type.h"
#include "station_type.h"
enum StationClassID : uint16_t;
CommandCost RemoveBuoy(TileIndex tile, DoCommandFlag flags);
Axis GetAxisForNewWaypoint(TileIndex tile);
void ShowWaypointWindow(const Waypoint *wp);
void DrawWaypointSprite(int x, int y, int stat_id, RailType railtype);
void DrawWaypointSprite(int x, int y, StationClassID station_class, uint16_t station_type, RailType railtype);
#endif /* WAYPOINT_FUNC_H */

View File

@ -39,6 +39,7 @@ add_files(
object_widget.h
order_widget.h
osk_widget.h
picker_widget.h
rail_widget.h
road_widget.h
screenshot_widget.h

View File

@ -12,18 +12,10 @@
/** Widgets of the #BuildObjectWindow class. */
enum BuildObjectWidgets : WidgetID {
WID_BO_FILTER, ///< The filter text box for the object list.
WID_BO_CLASS_LIST, ///< The list with classes.
WID_BO_SCROLLBAR, ///< The scrollbar associated with the list.
WID_BO_OBJECT_MATRIX, ///< The matrix with preview sprites.
WID_BO_OBJECT_SPRITE, ///< A preview sprite of the object.
WID_BO_OBJECT_NAME, ///< The name of the selected object.
WID_BO_OBJECT_SIZE, ///< The size of the selected object.
WID_BO_INFO, ///< Other information about the object (from the NewGRF).
WID_BO_SELECT_MATRIX, ///< Selection preview matrix of objects of a given class.
WID_BO_SELECT_IMAGE, ///< Preview image in the #WID_BO_SELECT_MATRIX.
WID_BO_SELECT_SCROLL, ///< Scrollbar next to the #WID_BO_SELECT_MATRIX.
};
#endif /* WIDGETS_OBJECT_WIDGET_H */

View File

@ -0,0 +1,31 @@
/*
* This file is part of OpenTTD.
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file picker_widget.h Types related to the picker widgets. */
#ifndef WIDGETS_PICKER_WIDGET_H
#define WIDGETS_PICKER_WIDGET_H
/** Widgets of the #PickerWindow class. */
enum PickerClassWindowWidgets : WidgetID {
WID_PW_START = 1 << 16, ///< Dummy to ensure widgets don't overlap.
WID_PW_CLASS_SEL, ///< Stack to hide the class picker.
WID_PW_CLASS_FILTER, ///< Editbox filter.
WID_PW_CLASS_LIST, ///< List of classes.
WID_PW_CLASS_SCROLL, ///< Scrollbar for list of classes.
WID_PW_TYPE_SEL, ///< Stack to hide the type picker.
WID_PW_TYPE_FILTER, ///< Text filter.
WID_PW_TYPE_MATRIX, ///< Matrix with items.
WID_PW_TYPE_ITEM, ///< A single item.
WID_PW_TYPE_SCROLL, ///< Scrollbar for the matrix.
WID_PW_TYPE_NAME, ///< Name of selected item.
WID_PW_TYPE_RESIZE, ///< Type resize handle.
};
#endif /* WIDGETS_PICKER_WIDGET_H */

View File

@ -60,20 +60,6 @@ enum BuildRailStationWidgets : WidgetID {
WID_BRAS_HIGHLIGHT_ON, ///< Button for turning coverage highlighting on.
WID_BRAS_COVERAGE_TEXTS, ///< Empty space for the coverage texts.
WID_BRAS_MATRIX, ///< Matrix widget displaying the available stations.
WID_BRAS_IMAGE, ///< Panel used at each cell of the matrix.
WID_BRAS_MATRIX_SCROLL, ///< Scrollbar of the matrix widget.
WID_BRAS_FILTER_CONTAINER, ///< Container for the filter text box for the station class list.
WID_BRAS_FILTER_EDITBOX, ///< Filter text box for the station class list.
WID_BRAS_SHOW_NEWST_DEFSIZE, ///< Selection for default-size button for newstation.
WID_BRAS_SHOW_NEWST_ADDITIONS, ///< Selection for newstation class selection list.
WID_BRAS_SHOW_NEWST_MATRIX, ///< Selection for newstation image matrix.
WID_BRAS_SHOW_NEWST_RESIZE, ///< Selection for panel and resize at bottom right for newstation.
WID_BRAS_SHOW_NEWST_TYPE, ///< Display of selected station type.
WID_BRAS_NEWST_LIST, ///< List with available newstation classes.
WID_BRAS_NEWST_SCROLL, ///< Scrollbar of the #WID_BRAS_NEWST_LIST.
WID_BRAS_PLATFORM_NUM_BEGIN = WID_BRAS_PLATFORM_NUM_1 - 1, ///< Helper for determining the chosen platform width.
WID_BRAS_PLATFORM_LEN_BEGIN = WID_BRAS_PLATFORM_LEN_1 - 1, ///< Helper for determining the chosen platform length.
};

View File

@ -53,21 +53,7 @@ enum BuildRoadStationWidgets : WidgetID {
WID_BROS_LT_OFF, ///< Turn off area highlight.
WID_BROS_LT_ON, ///< Turn on area highlight.
WID_BROS_ACCEPTANCE, ///< Station acceptance info.
WID_BROS_MATRIX, ///< Matrix widget displaying all available road stops.
WID_BROS_IMAGE, ///< Panel used for each image of the matrix.
WID_BROS_MATRIX_SCROLL, ///< Scrollbar of the #WID_BROS_SHOW_NEWST_ADDITIONS.
WID_BROS_FILTER_CONTAINER, ///< Container for the filter text box for the road stop class list.
WID_BROS_FILTER_EDITBOX, ///< Filter text box for the road stop class list.
WID_BROS_AVAILABLE_ORIENTATIONS, ///< Selection for selecting 6 or 2 orientations.
WID_BROS_SHOW_NEWST_DEFSIZE, ///< Selection for default-size button for new road stops.
WID_BROS_SHOW_NEWST_ADDITIONS, ///< Selection for new class selection list.
WID_BROS_SHOW_NEWST_MATRIX, ///< Selection for new stop image matrix.
WID_BROS_SHOW_NEWST_RESIZE, ///< Selection for panel and resize at bottom right for new stops.
WID_BROS_SHOW_NEWST_ORIENTATION, ///< Selection for the orientation string for new stops.
WID_BROS_SHOW_NEWST_TYPE_SEL, ///< Selection for the type name.
WID_BROS_SHOW_NEWST_TYPE, ///< Display of selected stop type.
WID_BROS_NEWST_LIST, ///< List with new road stops.
WID_BROS_NEWST_SCROLL, ///< Scrollbar of the #WID_BROS_NEWST_LIST.
};
#endif /* WIDGETS_ROAD_WIDGET_H */