Codefix: Documentation comment in IndustryDirectoryWindow (#13059)
[openttd-github.git] / src / rail_gui.cpp
blobd64b8b54f265907adf9676d20ccfb981ee3dd086
1 /*
2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
6 */
8 /** @file rail_gui.cpp %File for dealing with rail construction user interface */
10 #include "stdafx.h"
11 #include "gui.h"
12 #include "station_base.h"
13 #include "waypoint_base.h"
14 #include "window_gui.h"
15 #include "station_gui.h"
16 #include "terraform_gui.h"
17 #include "viewport_func.h"
18 #include "command_func.h"
19 #include "waypoint_func.h"
20 #include "newgrf_station.h"
21 #include "company_base.h"
22 #include "strings_func.h"
23 #include "window_func.h"
24 #include "sound_func.h"
25 #include "company_func.h"
26 #include "dropdown_type.h"
27 #include "dropdown_func.h"
28 #include "tunnelbridge.h"
29 #include "tilehighlight_func.h"
30 #include "spritecache.h"
31 #include "core/geometry_func.hpp"
32 #include "hotkeys.h"
33 #include "engine_base.h"
34 #include "vehicle_func.h"
35 #include "zoom_func.h"
36 #include "rail_gui.h"
37 #include "station_cmd.h"
38 #include "tunnelbridge_cmd.h"
39 #include "waypoint_cmd.h"
40 #include "rail_cmd.h"
41 #include "timer/timer.h"
42 #include "timer/timer_game_calendar.h"
43 #include "picker_gui.h"
45 #include "station_map.h"
46 #include "tunnelbridge_map.h"
48 #include "widgets/rail_widget.h"
50 #include "safeguards.h"
53 static RailType _cur_railtype; ///< Rail type of the current build-rail toolbar.
54 static bool _remove_button_clicked; ///< Flag whether 'remove' toggle-button is currently enabled
55 static DiagDirection _build_depot_direction; ///< Currently selected depot direction
56 static bool _convert_signal_button; ///< convert signal button in the signal GUI pressed
57 static SignalVariant _cur_signal_variant; ///< set the signal variant (for signal GUI)
58 static SignalType _cur_signal_type; ///< set the signal type (for signal GUI)
60 struct WaypointPickerSelection {
61 StationClassID sel_class; ///< Selected station class.
62 uint16_t sel_type; ///< Selected station type within the class.
64 static WaypointPickerSelection _waypoint_gui; ///< Settings of the waypoint picker.
66 struct StationPickerSelection {
67 StationClassID sel_class; ///< Selected station class.
68 uint16_t sel_type; ///< Selected station type within the class.
69 Axis axis; ///< Selected orientation of the station.
71 static StationPickerSelection _station_gui; ///< Settings of the station picker.
74 static void HandleStationPlacement(TileIndex start, TileIndex end);
75 static void ShowBuildTrainDepotPicker(Window *parent);
76 static void ShowBuildWaypointPicker(Window *parent);
77 static Window *ShowStationBuilder(Window *parent);
78 static void ShowSignalBuilder(Window *parent);
80 /**
81 * Check whether a station type can be build.
82 * @return true if building is allowed.
84 static bool IsStationAvailable(const StationSpec *statspec)
86 if (statspec == nullptr || !HasBit(statspec->callback_mask, CBM_STATION_AVAIL)) return true;
88 uint16_t cb_res = GetStationCallback(CBID_STATION_AVAILABILITY, 0, 0, statspec, nullptr, INVALID_TILE);
89 if (cb_res == CALLBACK_FAILED) return true;
91 return Convert8bitBooleanCallback(statspec->grf_prop.grffile, CBID_STATION_AVAILABILITY, cb_res);
94 void CcPlaySound_CONSTRUCTION_RAIL(Commands, const CommandCost &result, TileIndex tile)
96 if (result.Succeeded() && _settings_client.sound.confirm) SndPlayTileFx(SND_20_CONSTRUCTION_RAIL, tile);
99 static void GenericPlaceRail(TileIndex tile, Track track)
101 if (_remove_button_clicked) {
102 Command<CMD_REMOVE_SINGLE_RAIL>::Post(STR_ERROR_CAN_T_REMOVE_RAILROAD_TRACK, CcPlaySound_CONSTRUCTION_RAIL,
103 tile, track);
104 } else {
105 Command<CMD_BUILD_SINGLE_RAIL>::Post(STR_ERROR_CAN_T_BUILD_RAILROAD_TRACK, CcPlaySound_CONSTRUCTION_RAIL,
106 tile, _cur_railtype, track, _settings_client.gui.auto_remove_signals);
111 * Try to add an additional rail-track at the entrance of a depot
112 * @param tile Tile to use for adding the rail-track
113 * @param dir Direction to check for already present tracks
114 * @param track Track to add
115 * @see CcRailDepot()
117 static void PlaceExtraDepotRail(TileIndex tile, DiagDirection dir, Track track)
119 if (GetRailTileType(tile) == RAIL_TILE_DEPOT) return;
120 if (GetRailTileType(tile) == RAIL_TILE_SIGNALS && !_settings_client.gui.auto_remove_signals) return;
121 if ((GetTrackBits(tile) & DiagdirReachesTracks(dir)) == 0) return;
123 Command<CMD_BUILD_SINGLE_RAIL>::Post(tile, _cur_railtype, track, _settings_client.gui.auto_remove_signals);
126 /** Additional pieces of track to add at the entrance of a depot. */
127 static const Track _place_depot_extra_track[12] = {
128 TRACK_LEFT, TRACK_UPPER, TRACK_UPPER, TRACK_RIGHT, // First additional track for directions 0..3
129 TRACK_X, TRACK_Y, TRACK_X, TRACK_Y, // Second additional track
130 TRACK_LOWER, TRACK_LEFT, TRACK_RIGHT, TRACK_LOWER, // Third additional track
133 /** Direction to check for existing track pieces. */
134 static const DiagDirection _place_depot_extra_dir[12] = {
135 DIAGDIR_SE, DIAGDIR_SW, DIAGDIR_SE, DIAGDIR_SW,
136 DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_SE,
137 DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_NW, DIAGDIR_NE,
140 void CcRailDepot(Commands, const CommandCost &result, TileIndex tile, RailType, DiagDirection dir)
142 if (result.Failed()) return;
144 if (_settings_client.sound.confirm) SndPlayTileFx(SND_20_CONSTRUCTION_RAIL, tile);
145 if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
147 tile += TileOffsByDiagDir(dir);
149 if (IsTileType(tile, MP_RAILWAY)) {
150 PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir], _place_depot_extra_track[dir]);
152 /* Don't place the rail straight out of the depot of there is another depot across from it. */
153 Tile double_depot_tile = tile + TileOffsByDiagDir(dir);
154 bool is_double_depot = IsValidTile(double_depot_tile) && IsRailDepotTile(double_depot_tile);
155 if (!is_double_depot) PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 4], _place_depot_extra_track[dir + 4]);
157 PlaceExtraDepotRail(tile, _place_depot_extra_dir[dir + 8], _place_depot_extra_track[dir + 8]);
162 * Place a rail waypoint.
163 * @param tile Position to start dragging a waypoint.
165 static void PlaceRail_Waypoint(TileIndex tile)
167 if (_remove_button_clicked) {
168 VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_REMOVE_STATION);
169 return;
172 Axis axis = GetAxisForNewRailWaypoint(tile);
173 if (IsValidAxis(axis)) {
174 /* Valid tile for waypoints */
175 VpStartPlaceSizing(tile, axis == AXIS_X ? VPM_X_LIMITED : VPM_Y_LIMITED, DDSP_BUILD_STATION);
176 VpSetPlaceSizingLimit(_settings_game.station.station_spread);
177 } else {
178 /* Tile where we can't build rail waypoints. This is always going to fail,
179 * but provides the user with a proper error message. */
180 Command<CMD_BUILD_RAIL_WAYPOINT>::Post(STR_ERROR_CAN_T_BUILD_TRAIN_WAYPOINT, tile, AXIS_X, 1, 1, STAT_CLASS_WAYP, 0, INVALID_STATION, false);
184 void CcStation(Commands, const CommandCost &result, TileIndex tile)
186 if (result.Failed()) return;
188 if (_settings_client.sound.confirm) SndPlayTileFx(SND_20_CONSTRUCTION_RAIL, tile);
189 /* Only close the station builder window if the default station and non persistent building is chosen. */
190 if (_station_gui.sel_class == STAT_CLASS_DFLT && _station_gui.sel_type == 0 && !_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
194 * Place a rail station.
195 * @param tile Position to place or start dragging a station.
197 static void PlaceRail_Station(TileIndex tile)
199 if (_remove_button_clicked) {
200 VpStartPlaceSizing(tile, VPM_X_AND_Y_LIMITED, DDSP_REMOVE_STATION);
201 VpSetPlaceSizingLimit(-1);
202 } else if (_settings_client.gui.station_dragdrop) {
203 VpStartPlaceSizing(tile, VPM_X_AND_Y_LIMITED, DDSP_BUILD_STATION);
204 VpSetPlaceSizingLimit(_settings_game.station.station_spread);
205 } else {
206 int w = _settings_client.gui.station_numtracks;
207 int h = _settings_client.gui.station_platlength;
208 if (!_station_gui.axis) Swap(w, h);
210 StationPickerSelection params = _station_gui;
211 RailType rt = _cur_railtype;
212 uint8_t numtracks = _settings_client.gui.station_numtracks;
213 uint8_t platlength = _settings_client.gui.station_platlength;
214 bool adjacent = _ctrl_pressed;
216 auto proc = [=](bool test, StationID to_join) -> bool {
217 if (test) {
218 return Command<CMD_BUILD_RAIL_STATION>::Do(CommandFlagsToDCFlags(GetCommandFlags<CMD_BUILD_RAIL_STATION>()), tile, rt, params.axis, numtracks, platlength, params.sel_class, params.sel_type, INVALID_STATION, adjacent).Succeeded();
219 } else {
220 return Command<CMD_BUILD_RAIL_STATION>::Post(STR_ERROR_CAN_T_BUILD_RAILROAD_STATION, CcStation, tile, rt, params.axis, numtracks, platlength, params.sel_class, params.sel_type, to_join, adjacent);
224 ShowSelectStationIfNeeded(TileArea(tile, w, h), proc);
229 * Build a new signal or edit/remove a present signal, use CmdBuildSingleSignal() or CmdRemoveSingleSignal() in rail_cmd.cpp
231 * @param tile The tile where the signal will build or edit
233 static void GenericPlaceSignals(TileIndex tile)
235 TrackBits trackbits = TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_RAIL, 0));
237 if (trackbits & TRACK_BIT_VERT) { // N-S direction
238 trackbits = (_tile_fract_coords.x <= _tile_fract_coords.y) ? TRACK_BIT_RIGHT : TRACK_BIT_LEFT;
241 if (trackbits & TRACK_BIT_HORZ) { // E-W direction
242 trackbits = (_tile_fract_coords.x + _tile_fract_coords.y <= 15) ? TRACK_BIT_UPPER : TRACK_BIT_LOWER;
245 Track track = FindFirstTrack(trackbits);
247 if (_remove_button_clicked) {
248 Command<CMD_REMOVE_SINGLE_SIGNAL>::Post(STR_ERROR_CAN_T_REMOVE_SIGNALS_FROM, CcPlaySound_CONSTRUCTION_RAIL, tile, track);
249 } else {
250 /* Which signals should we cycle through? */
251 bool tile_has_signal = IsPlainRailTile(tile) && IsValidTrack(track) && HasSignalOnTrack(tile, track);
252 SignalType cur_signal_on_tile = tile_has_signal ? GetSignalType(tile, track) : _cur_signal_type;
253 SignalType cycle_start;
254 SignalType cycle_end;
256 /* Start with the least restrictive case: the player wants to cycle through all signals they can see. */
257 if (_settings_client.gui.cycle_signal_types == SIGNAL_CYCLE_ALL) {
258 cycle_start = _settings_client.gui.signal_gui_mode == SIGNAL_GUI_ALL ? SIGTYPE_BLOCK : SIGTYPE_PBS;
259 cycle_end = SIGTYPE_LAST;
260 } else {
261 /* Only cycle through signals of the same group (block or path) as the current signal on the tile. */
262 if (cur_signal_on_tile <= SIGTYPE_LAST_NOPBS) {
263 /* Block signals only. */
264 cycle_start = SIGTYPE_BLOCK;
265 cycle_end = SIGTYPE_LAST_NOPBS;
266 } else {
267 /* Path signals only. */
268 cycle_start = SIGTYPE_PBS;
269 cycle_end = SIGTYPE_LAST;
273 if (FindWindowById(WC_BUILD_SIGNAL, 0) != nullptr) {
274 /* signal GUI is used */
275 Command<CMD_BUILD_SINGLE_SIGNAL>::Post(_convert_signal_button ? STR_ERROR_SIGNAL_CAN_T_CONVERT_SIGNALS_HERE : STR_ERROR_CAN_T_BUILD_SIGNALS_HERE, CcPlaySound_CONSTRUCTION_RAIL,
276 tile, track, _cur_signal_type, _cur_signal_variant, _convert_signal_button, false, _ctrl_pressed, cycle_start, cycle_end, 0, 0);
277 } else {
278 SignalVariant sigvar = TimerGameCalendar::year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC;
279 Command<CMD_BUILD_SINGLE_SIGNAL>::Post(STR_ERROR_CAN_T_BUILD_SIGNALS_HERE, CcPlaySound_CONSTRUCTION_RAIL,
280 tile, track, _settings_client.gui.default_signal_type, sigvar, false, false, _ctrl_pressed, cycle_start, cycle_end, 0, 0);
287 * Start placing a rail bridge.
288 * @param tile Position of the first tile of the bridge.
289 * @param w Rail toolbar window.
291 static void PlaceRail_Bridge(TileIndex tile, Window *w)
293 if (IsBridgeTile(tile)) {
294 TileIndex other_tile = GetOtherTunnelBridgeEnd(tile);
295 Point pt = {0, 0};
296 w->OnPlaceMouseUp(VPM_X_OR_Y, DDSP_BUILD_BRIDGE, pt, other_tile, tile);
297 } else {
298 VpStartPlaceSizing(tile, VPM_X_OR_Y, DDSP_BUILD_BRIDGE);
302 /** Command callback for building a tunnel */
303 void CcBuildRailTunnel(Commands, const CommandCost &result, TileIndex tile)
305 if (result.Succeeded()) {
306 if (_settings_client.sound.confirm) SndPlayTileFx(SND_20_CONSTRUCTION_RAIL, tile);
307 if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
308 } else {
309 SetRedErrorSquare(_build_tunnel_endtile);
314 * Toggles state of the Remove button of Build rail toolbar
315 * @param w window the button belongs to
317 static void ToggleRailButton_Remove(Window *w)
319 CloseWindowById(WC_SELECT_STATION, 0);
320 w->ToggleWidgetLoweredState(WID_RAT_REMOVE);
321 w->SetWidgetDirty(WID_RAT_REMOVE);
322 _remove_button_clicked = w->IsWidgetLowered(WID_RAT_REMOVE);
323 SetSelectionRed(_remove_button_clicked);
327 * Updates the Remove button because of Ctrl state change
328 * @param w window the button belongs to
329 * @return true iff the remove button was changed
331 static bool RailToolbar_CtrlChanged(Window *w)
333 if (w->IsWidgetDisabled(WID_RAT_REMOVE)) return false;
335 /* allow ctrl to switch remove mode only for these widgets */
336 for (WidgetID i = WID_RAT_BUILD_NS; i <= WID_RAT_BUILD_STATION; i++) {
337 if ((i <= WID_RAT_AUTORAIL || i >= WID_RAT_BUILD_WAYPOINT) && w->IsWidgetLowered(i)) {
338 ToggleRailButton_Remove(w);
339 return true;
343 return false;
348 * The "remove"-button click proc of the build-rail toolbar.
349 * @param w Build-rail toolbar window
350 * @see BuildRailToolbarWindow::OnClick()
352 static void BuildRailClick_Remove(Window *w)
354 if (w->IsWidgetDisabled(WID_RAT_REMOVE)) return;
355 ToggleRailButton_Remove(w);
356 if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
358 /* handle station builder */
359 if (w->IsWidgetLowered(WID_RAT_BUILD_STATION)) {
360 if (_remove_button_clicked) {
361 /* starting drag & drop remove */
362 if (!_settings_client.gui.station_dragdrop) {
363 SetTileSelectSize(1, 1);
364 } else {
365 VpSetPlaceSizingLimit(-1);
367 } else {
368 /* starting station build mode */
369 if (!_settings_client.gui.station_dragdrop) {
370 int x = _settings_client.gui.station_numtracks;
371 int y = _settings_client.gui.station_platlength;
372 if (_station_gui.axis == 0) Swap(x, y);
373 SetTileSelectSize(x, y);
374 } else {
375 VpSetPlaceSizingLimit(_settings_game.station.station_spread);
381 static void DoRailroadTrack(Track track)
383 if (_remove_button_clicked) {
384 Command<CMD_REMOVE_RAILROAD_TRACK>::Post(STR_ERROR_CAN_T_REMOVE_RAILROAD_TRACK, CcPlaySound_CONSTRUCTION_RAIL,
385 TileVirtXY(_thd.selend.x, _thd.selend.y), TileVirtXY(_thd.selstart.x, _thd.selstart.y), track);
386 } else {
387 Command<CMD_BUILD_RAILROAD_TRACK>::Post(STR_ERROR_CAN_T_BUILD_RAILROAD_TRACK, CcPlaySound_CONSTRUCTION_RAIL,
388 TileVirtXY(_thd.selend.x, _thd.selend.y), TileVirtXY(_thd.selstart.x, _thd.selstart.y), _cur_railtype, track, _settings_client.gui.auto_remove_signals, false);
392 static void HandleAutodirPlacement()
394 Track trackstat = static_cast<Track>( _thd.drawstyle & HT_DIR_MASK); // 0..5
396 if (_thd.drawstyle & HT_RAIL) { // one tile case
397 GenericPlaceRail(TileVirtXY(_thd.selend.x, _thd.selend.y), trackstat);
398 return;
401 DoRailroadTrack(trackstat);
405 * Build new signals or remove signals or (if only one tile marked) edit a signal.
407 * If one tile marked abort and use GenericPlaceSignals()
408 * else use CmdBuildSingleSignal() or CmdRemoveSingleSignal() in rail_cmd.cpp to build many signals
410 static void HandleAutoSignalPlacement()
412 Track track = (Track)GB(_thd.drawstyle, 0, 3); // 0..5
414 if ((_thd.drawstyle & HT_DRAG_MASK) == HT_RECT) { // one tile case
415 GenericPlaceSignals(TileVirtXY(_thd.selend.x, _thd.selend.y));
416 return;
419 /* _settings_client.gui.drag_signals_density is given as a parameter such that each user
420 * in a network game can specify their own signal density */
421 if (_remove_button_clicked) {
422 Command<CMD_REMOVE_SIGNAL_TRACK>::Post(STR_ERROR_CAN_T_REMOVE_SIGNALS_FROM, CcPlaySound_CONSTRUCTION_RAIL,
423 TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y), track, _ctrl_pressed);
424 } else {
425 bool sig_gui = FindWindowById(WC_BUILD_SIGNAL, 0) != nullptr;
426 SignalType sigtype = sig_gui ? _cur_signal_type : _settings_client.gui.default_signal_type;
427 SignalVariant sigvar = sig_gui ? _cur_signal_variant : (TimerGameCalendar::year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC);
428 Command<CMD_BUILD_SIGNAL_TRACK>::Post(STR_ERROR_CAN_T_BUILD_SIGNALS_HERE, CcPlaySound_CONSTRUCTION_RAIL,
429 TileVirtXY(_thd.selstart.x, _thd.selstart.y), TileVirtXY(_thd.selend.x, _thd.selend.y), track, sigtype, sigvar, false, _ctrl_pressed, !_settings_client.gui.drag_signals_fixed_distance, _settings_client.gui.drag_signals_density);
434 /** Rail toolbar management class. */
435 struct BuildRailToolbarWindow : Window {
436 RailType railtype; ///< Rail type to build.
437 int last_user_action; ///< Last started user action.
439 BuildRailToolbarWindow(WindowDesc &desc, RailType railtype) : Window(desc)
441 this->railtype = railtype;
442 this->CreateNestedTree();
443 this->FinishInitNested(TRANSPORT_RAIL);
444 this->DisableWidget(WID_RAT_REMOVE);
445 this->OnInvalidateData();
446 this->last_user_action = INVALID_WID_RAT;
448 if (_settings_client.gui.link_terraform_toolbar) ShowTerraformToolbar(this);
451 void Close([[maybe_unused]] int data = 0) override
453 if (this->IsWidgetLowered(WID_RAT_BUILD_STATION)) SetViewportCatchmentStation(nullptr, true);
454 if (this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT)) SetViewportCatchmentWaypoint(nullptr, true);
455 if (_settings_client.gui.link_terraform_toolbar) CloseWindowById(WC_SCEN_LAND_GEN, 0, false);
456 CloseWindowById(WC_SELECT_STATION, 0);
457 this->Window::Close();
460 /** List of widgets to be disabled if infrastructure limit prevents building. */
461 static inline const std::initializer_list<WidgetID> can_build_widgets = {
462 WID_RAT_BUILD_NS, WID_RAT_BUILD_X, WID_RAT_BUILD_EW, WID_RAT_BUILD_Y, WID_RAT_AUTORAIL,
463 WID_RAT_BUILD_DEPOT, WID_RAT_BUILD_WAYPOINT, WID_RAT_BUILD_STATION, WID_RAT_BUILD_SIGNALS,
464 WID_RAT_BUILD_BRIDGE, WID_RAT_BUILD_TUNNEL, WID_RAT_CONVERT_RAIL,
467 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
469 if (!gui_scope) return;
471 if (!ValParamRailType(this->railtype)) {
472 /* Close toolbar if rail type is not available. */
473 this->Close();
474 return;
477 bool can_build = CanBuildVehicleInfrastructure(VEH_TRAIN);
478 for (const WidgetID widget : can_build_widgets) this->SetWidgetDisabledState(widget, !can_build);
479 if (!can_build) {
480 CloseWindowById(WC_BUILD_SIGNAL, TRANSPORT_RAIL);
481 CloseWindowById(WC_BUILD_STATION, TRANSPORT_RAIL);
482 CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_RAIL);
483 CloseWindowById(WC_BUILD_WAYPOINT, TRANSPORT_RAIL);
484 CloseWindowById(WC_SELECT_STATION, 0);
488 bool OnTooltip([[maybe_unused]] Point pt, WidgetID widget, TooltipCloseCondition close_cond) override
490 bool can_build = CanBuildVehicleInfrastructure(VEH_TRAIN);
491 if (can_build) return false;
493 if (std::find(std::begin(can_build_widgets), std::end(can_build_widgets), widget) == std::end(can_build_widgets)) return false;
495 GuiShowTooltips(this, STR_TOOLBAR_DISABLED_NO_VEHICLE_AVAILABLE, close_cond);
496 return true;
499 void OnInit() override
501 /* Configure the rail toolbar for the railtype. */
502 const RailTypeInfo *rti = GetRailTypeInfo(this->railtype);
503 this->GetWidget<NWidgetCore>(WID_RAT_BUILD_NS)->widget_data = rti->gui_sprites.build_ns_rail;
504 this->GetWidget<NWidgetCore>(WID_RAT_BUILD_X)->widget_data = rti->gui_sprites.build_x_rail;
505 this->GetWidget<NWidgetCore>(WID_RAT_BUILD_EW)->widget_data = rti->gui_sprites.build_ew_rail;
506 this->GetWidget<NWidgetCore>(WID_RAT_BUILD_Y)->widget_data = rti->gui_sprites.build_y_rail;
507 this->GetWidget<NWidgetCore>(WID_RAT_AUTORAIL)->widget_data = rti->gui_sprites.auto_rail;
508 this->GetWidget<NWidgetCore>(WID_RAT_BUILD_DEPOT)->widget_data = rti->gui_sprites.build_depot;
509 this->GetWidget<NWidgetCore>(WID_RAT_CONVERT_RAIL)->widget_data = rti->gui_sprites.convert_rail;
510 this->GetWidget<NWidgetCore>(WID_RAT_BUILD_TUNNEL)->widget_data = rti->gui_sprites.build_tunnel;
514 * Switch to another rail type.
515 * @param railtype New rail type.
517 void ModifyRailType(RailType railtype)
519 this->railtype = railtype;
520 this->ReInit();
523 void UpdateRemoveWidgetStatus(WidgetID clicked_widget)
525 switch (clicked_widget) {
526 case WID_RAT_REMOVE:
527 /* If it is the removal button that has been clicked, do nothing,
528 * as it is up to the other buttons to drive removal status */
529 return;
531 case WID_RAT_BUILD_NS:
532 case WID_RAT_BUILD_X:
533 case WID_RAT_BUILD_EW:
534 case WID_RAT_BUILD_Y:
535 case WID_RAT_AUTORAIL:
536 case WID_RAT_BUILD_WAYPOINT:
537 case WID_RAT_BUILD_STATION:
538 case WID_RAT_BUILD_SIGNALS:
539 /* Removal button is enabled only if the rail/signal/waypoint/station
540 * button is still lowered. Once raised, it has to be disabled */
541 this->SetWidgetDisabledState(WID_RAT_REMOVE, !this->IsWidgetLowered(clicked_widget));
542 break;
544 default:
545 /* When any other buttons than rail/signal/waypoint/station, raise and
546 * disable the removal button */
547 this->DisableWidget(WID_RAT_REMOVE);
548 this->RaiseWidget(WID_RAT_REMOVE);
549 break;
553 void SetStringParameters(WidgetID widget) const override
555 if (widget == WID_RAT_CAPTION) {
556 const RailTypeInfo *rti = GetRailTypeInfo(this->railtype);
557 if (rti->max_speed > 0) {
558 SetDParam(0, STR_TOOLBAR_RAILTYPE_VELOCITY);
559 SetDParam(1, rti->strings.toolbar_caption);
560 SetDParam(2, PackVelocity(rti->max_speed, VEH_TRAIN));
561 } else {
562 SetDParam(0, rti->strings.toolbar_caption);
567 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
569 if (widget < WID_RAT_BUILD_NS) return;
571 _remove_button_clicked = false;
572 switch (widget) {
573 case WID_RAT_BUILD_NS:
574 HandlePlacePushButton(this, WID_RAT_BUILD_NS, GetRailTypeInfo(_cur_railtype)->cursor.rail_ns, HT_LINE | HT_DIR_VL);
575 this->last_user_action = widget;
576 break;
578 case WID_RAT_BUILD_X:
579 HandlePlacePushButton(this, WID_RAT_BUILD_X, GetRailTypeInfo(_cur_railtype)->cursor.rail_swne, HT_LINE | HT_DIR_X);
580 this->last_user_action = widget;
581 break;
583 case WID_RAT_BUILD_EW:
584 HandlePlacePushButton(this, WID_RAT_BUILD_EW, GetRailTypeInfo(_cur_railtype)->cursor.rail_ew, HT_LINE | HT_DIR_HL);
585 this->last_user_action = widget;
586 break;
588 case WID_RAT_BUILD_Y:
589 HandlePlacePushButton(this, WID_RAT_BUILD_Y, GetRailTypeInfo(_cur_railtype)->cursor.rail_nwse, HT_LINE | HT_DIR_Y);
590 this->last_user_action = widget;
591 break;
593 case WID_RAT_AUTORAIL:
594 HandlePlacePushButton(this, WID_RAT_AUTORAIL, GetRailTypeInfo(_cur_railtype)->cursor.autorail, HT_RAIL);
595 this->last_user_action = widget;
596 break;
598 case WID_RAT_DEMOLISH:
599 HandlePlacePushButton(this, WID_RAT_DEMOLISH, ANIMCURSOR_DEMOLISH, HT_RECT | HT_DIAGONAL);
600 this->last_user_action = widget;
601 break;
603 case WID_RAT_BUILD_DEPOT:
604 if (HandlePlacePushButton(this, WID_RAT_BUILD_DEPOT, GetRailTypeInfo(_cur_railtype)->cursor.depot, HT_RECT)) {
605 ShowBuildTrainDepotPicker(this);
606 this->last_user_action = widget;
608 break;
610 case WID_RAT_BUILD_WAYPOINT:
611 this->last_user_action = widget;
612 if (HandlePlacePushButton(this, WID_RAT_BUILD_WAYPOINT, SPR_CURSOR_WAYPOINT, HT_RECT)) {
613 ShowBuildWaypointPicker(this);
615 break;
617 case WID_RAT_BUILD_STATION:
618 if (HandlePlacePushButton(this, WID_RAT_BUILD_STATION, SPR_CURSOR_RAIL_STATION, HT_RECT)) {
619 ShowStationBuilder(this);
620 this->last_user_action = widget;
622 break;
624 case WID_RAT_BUILD_SIGNALS: {
625 this->last_user_action = widget;
626 bool started = HandlePlacePushButton(this, WID_RAT_BUILD_SIGNALS, ANIMCURSOR_BUILDSIGNALS, HT_RECT);
627 if (started != _ctrl_pressed) {
628 ShowSignalBuilder(this);
630 break;
633 case WID_RAT_BUILD_BRIDGE:
634 HandlePlacePushButton(this, WID_RAT_BUILD_BRIDGE, SPR_CURSOR_BRIDGE, HT_RECT);
635 this->last_user_action = widget;
636 break;
638 case WID_RAT_BUILD_TUNNEL:
639 HandlePlacePushButton(this, WID_RAT_BUILD_TUNNEL, GetRailTypeInfo(_cur_railtype)->cursor.tunnel, HT_SPECIAL);
640 this->last_user_action = widget;
641 break;
643 case WID_RAT_REMOVE:
644 BuildRailClick_Remove(this);
645 break;
647 case WID_RAT_CONVERT_RAIL:
648 HandlePlacePushButton(this, WID_RAT_CONVERT_RAIL, GetRailTypeInfo(_cur_railtype)->cursor.convert, HT_RECT | HT_DIAGONAL);
649 this->last_user_action = widget;
650 break;
652 default: NOT_REACHED();
654 this->UpdateRemoveWidgetStatus(widget);
655 if (_ctrl_pressed) RailToolbar_CtrlChanged(this);
658 EventState OnHotkey(int hotkey) override
660 MarkTileDirtyByTile(TileVirtXY(_thd.pos.x, _thd.pos.y)); // redraw tile selection
661 return Window::OnHotkey(hotkey);
664 void OnPlaceObject([[maybe_unused]] Point pt, TileIndex tile) override
666 switch (this->last_user_action) {
667 case WID_RAT_BUILD_NS:
668 VpStartPlaceSizing(tile, VPM_FIX_VERTICAL | VPM_RAILDIRS, DDSP_PLACE_RAIL);
669 break;
671 case WID_RAT_BUILD_X:
672 VpStartPlaceSizing(tile, VPM_FIX_Y | VPM_RAILDIRS, DDSP_PLACE_RAIL);
673 break;
675 case WID_RAT_BUILD_EW:
676 VpStartPlaceSizing(tile, VPM_FIX_HORIZONTAL | VPM_RAILDIRS, DDSP_PLACE_RAIL);
677 break;
679 case WID_RAT_BUILD_Y:
680 VpStartPlaceSizing(tile, VPM_FIX_X | VPM_RAILDIRS, DDSP_PLACE_RAIL);
681 break;
683 case WID_RAT_AUTORAIL:
684 VpStartPlaceSizing(tile, VPM_RAILDIRS, DDSP_PLACE_RAIL);
685 break;
687 case WID_RAT_DEMOLISH:
688 PlaceProc_DemolishArea(tile);
689 break;
691 case WID_RAT_BUILD_DEPOT:
692 Command<CMD_BUILD_TRAIN_DEPOT>::Post(STR_ERROR_CAN_T_BUILD_TRAIN_DEPOT, CcRailDepot, tile, _cur_railtype, _build_depot_direction);
693 break;
695 case WID_RAT_BUILD_WAYPOINT:
696 PlaceRail_Waypoint(tile);
697 break;
699 case WID_RAT_BUILD_STATION:
700 PlaceRail_Station(tile);
701 break;
703 case WID_RAT_BUILD_SIGNALS:
704 VpStartPlaceSizing(tile, VPM_SIGNALDIRS, DDSP_BUILD_SIGNALS);
705 break;
707 case WID_RAT_BUILD_BRIDGE:
708 PlaceRail_Bridge(tile, this);
709 break;
711 case WID_RAT_BUILD_TUNNEL:
712 Command<CMD_BUILD_TUNNEL>::Post(STR_ERROR_CAN_T_BUILD_TUNNEL_HERE, CcBuildRailTunnel, tile, TRANSPORT_RAIL, _cur_railtype);
713 break;
715 case WID_RAT_CONVERT_RAIL:
716 VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_CONVERT_RAIL);
717 break;
719 default: NOT_REACHED();
723 void OnPlaceDrag(ViewportPlaceMethod select_method, [[maybe_unused]] ViewportDragDropSelectionProcess select_proc, [[maybe_unused]] Point pt) override
725 /* no dragging if you have pressed the convert button */
726 if (FindWindowById(WC_BUILD_SIGNAL, 0) != nullptr && _convert_signal_button && this->IsWidgetLowered(WID_RAT_BUILD_SIGNALS)) return;
728 VpSelectTilesWithMethod(pt.x, pt.y, select_method);
731 void OnPlaceMouseUp([[maybe_unused]] ViewportPlaceMethod select_method, ViewportDragDropSelectionProcess select_proc, [[maybe_unused]] Point pt, TileIndex start_tile, TileIndex end_tile) override
733 if (pt.x != -1) {
734 switch (select_proc) {
735 default: NOT_REACHED();
736 case DDSP_BUILD_BRIDGE:
737 if (!_settings_client.gui.persistent_buildingtools) ResetObjectToPlace();
738 ShowBuildBridgeWindow(start_tile, end_tile, TRANSPORT_RAIL, _cur_railtype);
739 break;
741 case DDSP_PLACE_RAIL:
742 HandleAutodirPlacement();
743 break;
745 case DDSP_BUILD_SIGNALS:
746 HandleAutoSignalPlacement();
747 break;
749 case DDSP_DEMOLISH_AREA:
750 GUIPlaceProcDragXY(select_proc, start_tile, end_tile);
751 break;
753 case DDSP_CONVERT_RAIL:
754 Command<CMD_CONVERT_RAIL>::Post(STR_ERROR_CAN_T_CONVERT_RAIL, CcPlaySound_CONSTRUCTION_RAIL, end_tile, start_tile, _cur_railtype, _ctrl_pressed);
755 break;
757 case DDSP_REMOVE_STATION:
758 case DDSP_BUILD_STATION:
759 if (this->IsWidgetLowered(WID_RAT_BUILD_STATION)) {
760 /* Station */
761 if (_remove_button_clicked) {
762 bool keep_rail = !_ctrl_pressed;
763 Command<CMD_REMOVE_FROM_RAIL_STATION>::Post(STR_ERROR_CAN_T_REMOVE_PART_OF_STATION, CcPlaySound_CONSTRUCTION_RAIL, end_tile, start_tile, keep_rail);
764 } else {
765 HandleStationPlacement(start_tile, end_tile);
767 } else {
768 /* Waypoint */
769 if (_remove_button_clicked) {
770 bool keep_rail = !_ctrl_pressed;
771 Command<CMD_REMOVE_FROM_RAIL_WAYPOINT>::Post(STR_ERROR_CAN_T_REMOVE_TRAIN_WAYPOINT, CcPlaySound_CONSTRUCTION_RAIL, end_tile, start_tile, keep_rail);
772 } else {
773 TileArea ta(start_tile, end_tile);
774 Axis axis = select_method == VPM_X_LIMITED ? AXIS_X : AXIS_Y;
775 bool adjacent = _ctrl_pressed;
777 auto proc = [=](bool test, StationID to_join) -> bool {
778 if (test) {
779 return Command<CMD_BUILD_RAIL_WAYPOINT>::Do(CommandFlagsToDCFlags(GetCommandFlags<CMD_BUILD_RAIL_WAYPOINT>()), ta.tile, axis, ta.w, ta.h, _waypoint_gui.sel_class, _waypoint_gui.sel_type, INVALID_STATION, adjacent).Succeeded();
780 } else {
781 return Command<CMD_BUILD_RAIL_WAYPOINT>::Post(STR_ERROR_CAN_T_BUILD_TRAIN_WAYPOINT, CcPlaySound_CONSTRUCTION_RAIL, ta.tile, axis, ta.w, ta.h, _waypoint_gui.sel_class, _waypoint_gui.sel_type, to_join, adjacent);
785 ShowSelectRailWaypointIfNeeded(ta, proc);
788 break;
793 void OnPlaceObjectAbort() override
795 if (this->IsWidgetLowered(WID_RAT_BUILD_STATION)) SetViewportCatchmentStation(nullptr, true);
796 if (this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT)) SetViewportCatchmentWaypoint(nullptr, true);
798 this->RaiseButtons();
799 this->DisableWidget(WID_RAT_REMOVE);
800 this->SetWidgetDirty(WID_RAT_REMOVE);
802 CloseWindowById(WC_BUILD_SIGNAL, TRANSPORT_RAIL);
803 CloseWindowById(WC_BUILD_STATION, TRANSPORT_RAIL);
804 CloseWindowById(WC_BUILD_DEPOT, TRANSPORT_RAIL);
805 CloseWindowById(WC_BUILD_WAYPOINT, TRANSPORT_RAIL);
806 CloseWindowById(WC_SELECT_STATION, 0);
807 CloseWindowByClass(WC_BUILD_BRIDGE);
810 void OnPlacePresize([[maybe_unused]] Point pt, TileIndex tile) override
812 Command<CMD_BUILD_TUNNEL>::Do(DC_AUTO, tile, TRANSPORT_RAIL, _cur_railtype);
813 VpSetPresizeRange(tile, _build_tunnel_endtile == 0 ? tile : _build_tunnel_endtile);
816 EventState OnCTRLStateChange() override
818 /* do not toggle Remove button by Ctrl when placing station */
819 if (!this->IsWidgetLowered(WID_RAT_BUILD_STATION) && !this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT) && RailToolbar_CtrlChanged(this)) return ES_HANDLED;
820 return ES_NOT_HANDLED;
823 void OnRealtimeTick([[maybe_unused]] uint delta_ms) override
825 if (this->IsWidgetLowered(WID_RAT_BUILD_WAYPOINT)) CheckRedrawRailWaypointCoverage(this);
829 * Handler for global hotkeys of the BuildRailToolbarWindow.
830 * @param hotkey Hotkey
831 * @return ES_HANDLED if hotkey was accepted.
833 static EventState RailToolbarGlobalHotkeys(int hotkey)
835 if (_game_mode != GM_NORMAL) return ES_NOT_HANDLED;
836 extern RailType _last_built_railtype;
837 Window *w = ShowBuildRailToolbar(_last_built_railtype);
838 if (w == nullptr) return ES_NOT_HANDLED;
839 return w->OnHotkey(hotkey);
842 static inline HotkeyList hotkeys{"railtoolbar", {
843 Hotkey('1', "build_ns", WID_RAT_BUILD_NS),
844 Hotkey('2', "build_x", WID_RAT_BUILD_X),
845 Hotkey('3', "build_ew", WID_RAT_BUILD_EW),
846 Hotkey('4', "build_y", WID_RAT_BUILD_Y),
847 Hotkey({'5', 'A' | WKC_GLOBAL_HOTKEY}, "autorail", WID_RAT_AUTORAIL),
848 Hotkey('6', "demolish", WID_RAT_DEMOLISH),
849 Hotkey('7', "depot", WID_RAT_BUILD_DEPOT),
850 Hotkey('8', "waypoint", WID_RAT_BUILD_WAYPOINT),
851 Hotkey('9', "station", WID_RAT_BUILD_STATION),
852 Hotkey('S', "signal", WID_RAT_BUILD_SIGNALS),
853 Hotkey('B', "bridge", WID_RAT_BUILD_BRIDGE),
854 Hotkey('T', "tunnel", WID_RAT_BUILD_TUNNEL),
855 Hotkey('R', "remove", WID_RAT_REMOVE),
856 Hotkey('C', "convert", WID_RAT_CONVERT_RAIL),
857 }, RailToolbarGlobalHotkeys};
860 static constexpr NWidgetPart _nested_build_rail_widgets[] = {
861 NWidget(NWID_HORIZONTAL),
862 NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
863 NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_RAT_CAPTION), SetDataTip(STR_JUST_STRING2, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), SetTextStyle(TC_WHITE),
864 NWidget(WWT_STICKYBOX, COLOUR_DARK_GREEN),
865 EndContainer(),
866 NWidget(NWID_HORIZONTAL),
867 NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_NS),
868 SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_NS, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK),
869 NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_X),
870 SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_NE, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK),
871 NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_EW),
872 SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_EW, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK),
873 NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_Y),
874 SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_NW, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TRACK),
875 NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_AUTORAIL),
876 SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_AUTORAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_AUTORAIL),
878 NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetMinimalSize(4, 22), EndContainer(),
880 NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_DEMOLISH),
881 SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DYNAMITE, STR_TOOLTIP_DEMOLISH_BUILDINGS_ETC),
882 NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_DEPOT),
883 SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_DEPOT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_TRAIN_DEPOT_FOR_BUILDING),
884 NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_WAYPOINT),
885 SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_WAYPOINT, STR_RAIL_TOOLBAR_TOOLTIP_CONVERT_RAIL_TO_WAYPOINT),
886 NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_STATION),
887 SetFill(0, 1), SetMinimalSize(42, 22), SetDataTip(SPR_IMG_RAIL_STATION, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_STATION),
888 NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_SIGNALS),
889 SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_RAIL_SIGNALS, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_SIGNALS),
890 NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_BRIDGE),
891 SetFill(0, 1), SetMinimalSize(42, 22), SetDataTip(SPR_IMG_BRIDGE, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_BRIDGE),
892 NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_BUILD_TUNNEL),
893 SetFill(0, 1), SetMinimalSize(20, 22), SetDataTip(SPR_IMG_TUNNEL_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_BUILD_RAILROAD_TUNNEL),
894 NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_REMOVE),
895 SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_REMOVE, STR_RAIL_TOOLBAR_TOOLTIP_TOGGLE_BUILD_REMOVE_FOR),
896 NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_RAT_CONVERT_RAIL),
897 SetFill(0, 1), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_CONVERT_RAIL, STR_RAIL_TOOLBAR_TOOLTIP_CONVERT_RAIL),
898 EndContainer(),
901 static WindowDesc _build_rail_desc(
902 WDP_ALIGN_TOOLBAR, "toolbar_rail", 0, 0,
903 WC_BUILD_TOOLBAR, WC_NONE,
904 WDF_CONSTRUCTION,
905 _nested_build_rail_widgets,
906 &BuildRailToolbarWindow::hotkeys
911 * Open the build rail toolbar window for a specific rail type.
913 * If the terraform toolbar is linked to the toolbar, that window is also opened.
915 * @param railtype Rail type to open the window for
916 * @return newly opened rail toolbar, or nullptr if the toolbar could not be opened.
918 Window *ShowBuildRailToolbar(RailType railtype)
920 if (!Company::IsValidID(_local_company)) return nullptr;
921 if (!ValParamRailType(railtype)) return nullptr;
923 CloseWindowByClass(WC_BUILD_TOOLBAR);
924 _cur_railtype = railtype;
925 _remove_button_clicked = false;
926 return new BuildRailToolbarWindow(_build_rail_desc, railtype);
929 /* TODO: For custom stations, respect their allowed platforms/lengths bitmasks!
930 * --pasky */
932 static void HandleStationPlacement(TileIndex start, TileIndex end)
934 TileArea ta(start, end);
935 uint numtracks = ta.w;
936 uint platlength = ta.h;
938 if (_station_gui.axis == AXIS_X) Swap(numtracks, platlength);
940 StationPickerSelection params = _station_gui;
941 RailType rt = _cur_railtype;
942 bool adjacent = _ctrl_pressed;
944 auto proc = [=](bool test, StationID to_join) -> bool {
945 if (test) {
946 return Command<CMD_BUILD_RAIL_STATION>::Do(CommandFlagsToDCFlags(GetCommandFlags<CMD_BUILD_RAIL_STATION>()), ta.tile, rt, params.axis, numtracks, platlength, params.sel_class, params.sel_type, INVALID_STATION, adjacent).Succeeded();
947 } else {
948 return Command<CMD_BUILD_RAIL_STATION>::Post(STR_ERROR_CAN_T_BUILD_RAILROAD_STATION, CcStation, ta.tile, rt, params.axis, numtracks, platlength, params.sel_class, params.sel_type, to_join, adjacent);
952 ShowSelectStationIfNeeded(ta, proc);
956 * Test if a station/waypoint uses the default graphics.
957 * @param bst Station to test.
958 * @return true if at least one of its rail station tiles uses the default graphics.
960 static bool StationUsesDefaultType(const BaseStation *bst)
962 for (TileIndex t : bst->train_station) {
963 if (bst->TileBelongsToRailStation(t) && HasStationRail(t) && GetCustomStationSpecIndex(t) == 0) return true;
965 return false;
968 class StationPickerCallbacks : public PickerCallbacksNewGRFClass<StationClass> {
969 public:
970 StationPickerCallbacks() : PickerCallbacksNewGRFClass<StationClass>("fav_stations") {}
972 StringID GetClassTooltip() const override { return STR_PICKER_STATION_CLASS_TOOLTIP; }
973 StringID GetTypeTooltip() const override { return STR_PICKER_STATION_TYPE_TOOLTIP; }
975 bool IsActive() const override
977 for (const auto &cls : StationClass::Classes()) {
978 if (IsWaypointClass(cls)) continue;
979 for (const auto *spec : cls.Specs()) {
980 if (spec != nullptr) return true;
983 return false;
986 bool HasClassChoice() const override
988 return std::count_if(std::begin(StationClass::Classes()), std::end(StationClass::Classes()), std::not_fn(IsWaypointClass)) > 1;
991 int GetSelectedClass() const override { return _station_gui.sel_class; }
992 void SetSelectedClass(int id) const override { _station_gui.sel_class = this->GetClassIndex(id); }
994 StringID GetClassName(int id) const override
996 const auto *sc = GetClass(id);
997 if (IsWaypointClass(*sc)) return INVALID_STRING_ID;
998 return sc->name;
1001 int GetSelectedType() const override { return _station_gui.sel_type; }
1002 void SetSelectedType(int id) const override { _station_gui.sel_type = id; }
1004 StringID GetTypeName(int cls_id, int id) const override
1006 const auto *spec = this->GetSpec(cls_id, id);
1007 return (spec == nullptr) ? STR_STATION_CLASS_DFLT_STATION : spec->name;
1010 bool IsTypeAvailable(int cls_id, int id) const override
1012 return IsStationAvailable(this->GetSpec(cls_id, id));
1015 void DrawType(int x, int y, int cls_id, int id) const override
1017 if (!DrawStationTile(x, y, _cur_railtype, _station_gui.axis, this->GetClassIndex(cls_id), id)) {
1018 StationPickerDrawSprite(x, y, STATION_RAIL, _cur_railtype, INVALID_ROADTYPE, 2 + _station_gui.axis);
1022 void FillUsedItems(std::set<PickerItem> &items) override
1024 bool default_added = false;
1025 for (const Station *st : Station::Iterate()) {
1026 if (st->owner != _local_company) continue;
1027 if (!default_added && StationUsesDefaultType(st)) {
1028 items.insert({0, 0, STAT_CLASS_DFLT, 0});
1029 default_added = true;
1031 for (const auto &sm : st->speclist) {
1032 if (sm.spec == nullptr) continue;
1033 items.insert({sm.grfid, sm.localidx, sm.spec->class_index, sm.spec->index});
1038 static StationPickerCallbacks instance;
1040 /* static */ StationPickerCallbacks StationPickerCallbacks::instance;
1042 struct BuildRailStationWindow : public PickerWindow {
1043 private:
1044 uint coverage_height; ///< Height of the coverage texts.
1047 * Verify whether the currently selected station size is allowed after selecting a new station class/type.
1048 * If not, change the station size variables ( _settings_client.gui.station_numtracks and _settings_client.gui.station_platlength ).
1049 * @param statspec Specification of the new station class/type
1051 void CheckSelectedSize(const StationSpec *statspec)
1053 if (statspec == nullptr || _settings_client.gui.station_dragdrop) return;
1055 /* If current number of tracks is not allowed, make it as big as possible */
1056 if (HasBit(statspec->disallowed_platforms, _settings_client.gui.station_numtracks - 1)) {
1057 this->RaiseWidget(_settings_client.gui.station_numtracks + WID_BRAS_PLATFORM_NUM_BEGIN);
1058 _settings_client.gui.station_numtracks = 1;
1059 if (statspec->disallowed_platforms != UINT8_MAX) {
1060 while (HasBit(statspec->disallowed_platforms, _settings_client.gui.station_numtracks - 1)) {
1061 _settings_client.gui.station_numtracks++;
1063 this->LowerWidget(_settings_client.gui.station_numtracks + WID_BRAS_PLATFORM_NUM_BEGIN);
1067 if (HasBit(statspec->disallowed_lengths, _settings_client.gui.station_platlength - 1)) {
1068 this->RaiseWidget(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN);
1069 _settings_client.gui.station_platlength = 1;
1070 if (statspec->disallowed_lengths != UINT8_MAX) {
1071 while (HasBit(statspec->disallowed_lengths, _settings_client.gui.station_platlength - 1)) {
1072 _settings_client.gui.station_platlength++;
1074 this->LowerWidget(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN);
1079 public:
1080 BuildRailStationWindow(WindowDesc &desc, Window *parent) : PickerWindow(desc, parent, TRANSPORT_RAIL, StationPickerCallbacks::instance)
1082 this->coverage_height = 2 * GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.vsep_normal;
1083 this->ConstructWindow();
1084 this->InvalidateData();
1087 void OnInit() override
1089 this->LowerWidget(WID_BRAS_PLATFORM_DIR_X + _station_gui.axis);
1090 if (_settings_client.gui.station_dragdrop) {
1091 this->LowerWidget(WID_BRAS_PLATFORM_DRAG_N_DROP);
1092 } else {
1093 this->LowerWidget(WID_BRAS_PLATFORM_NUM_BEGIN + _settings_client.gui.station_numtracks);
1094 this->LowerWidget(WID_BRAS_PLATFORM_LEN_BEGIN + _settings_client.gui.station_platlength);
1096 this->SetWidgetLoweredState(WID_BRAS_HIGHLIGHT_OFF, !_settings_client.gui.station_show_coverage);
1097 this->SetWidgetLoweredState(WID_BRAS_HIGHLIGHT_ON, _settings_client.gui.station_show_coverage);
1099 this->PickerWindow::OnInit();
1102 void Close([[maybe_unused]] int data = 0) override
1104 CloseWindowById(WC_SELECT_STATION, 0);
1105 this->PickerWindow::Close();
1108 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1110 if (gui_scope) {
1111 const StationSpec *statspec = StationClass::Get(_station_gui.sel_class)->GetSpec(_station_gui.sel_type);
1112 this->CheckSelectedSize(statspec);
1115 this->PickerWindow::OnInvalidateData(data, gui_scope);
1118 void OnPaint() override
1120 const StationSpec *statspec = StationClass::Get(_station_gui.sel_class)->GetSpec(_station_gui.sel_type);
1122 if (_settings_client.gui.station_dragdrop) {
1123 SetTileSelectSize(1, 1);
1124 } else {
1125 int x = _settings_client.gui.station_numtracks;
1126 int y = _settings_client.gui.station_platlength;
1127 if (_station_gui.axis == AXIS_X) Swap(x, y);
1128 if (!_remove_button_clicked) {
1129 SetTileSelectSize(x, y);
1133 int rad = (_settings_game.station.modified_catchment) ? CA_TRAIN : CA_UNMODIFIED;
1135 if (_settings_client.gui.station_show_coverage) SetTileSelectBigSize(-rad, -rad, 2 * rad, 2 * rad);
1137 for (uint bits = 0; bits < 7; bits++) {
1138 bool disable = bits >= _settings_game.station.station_spread;
1139 if (statspec == nullptr) {
1140 this->SetWidgetDisabledState(bits + WID_BRAS_PLATFORM_NUM_1, disable);
1141 this->SetWidgetDisabledState(bits + WID_BRAS_PLATFORM_LEN_1, disable);
1142 } else {
1143 this->SetWidgetDisabledState(bits + WID_BRAS_PLATFORM_NUM_1, HasBit(statspec->disallowed_platforms, bits) || disable);
1144 this->SetWidgetDisabledState(bits + WID_BRAS_PLATFORM_LEN_1, HasBit(statspec->disallowed_lengths, bits) || disable);
1148 this->DrawWidgets();
1150 if (this->IsShaded()) return;
1151 /* 'Accepts' and 'Supplies' texts. */
1152 Rect r = this->GetWidget<NWidgetBase>(WID_BRAS_COVERAGE_TEXTS)->GetCurrentRect();
1153 int top = r.top;
1154 top = DrawStationCoverageAreaText(r.left, r.right, top, SCT_ALL, rad, false) + WidgetDimensions::scaled.vsep_normal;
1155 top = DrawStationCoverageAreaText(r.left, r.right, top, SCT_ALL, rad, true);
1156 /* Resize background if the window is too small.
1157 * Never make the window smaller to avoid oscillating if the size change affects the acceptance.
1158 * (This is the case, if making the window bigger moves the mouse into the window.) */
1159 if (top > r.bottom) {
1160 this->coverage_height += top - r.bottom;
1161 this->ReInit();
1165 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1167 switch (widget) {
1168 case WID_BRAS_PLATFORM_DIR_X:
1169 case WID_BRAS_PLATFORM_DIR_Y:
1170 size.width = ScaleGUITrad(PREVIEW_WIDTH) + WidgetDimensions::scaled.fullbevel.Horizontal();
1171 size.height = ScaleGUITrad(PREVIEW_HEIGHT) + WidgetDimensions::scaled.fullbevel.Vertical();
1172 break;
1174 case WID_BRAS_COVERAGE_TEXTS:
1175 size.height = this->coverage_height;
1176 break;
1178 default:
1179 this->PickerWindow::UpdateWidgetSize(widget, size, padding, fill, resize);
1180 break;
1184 void DrawWidget(const Rect &r, WidgetID widget) const override
1186 DrawPixelInfo tmp_dpi;
1188 switch (widget) {
1189 case WID_BRAS_PLATFORM_DIR_X: {
1190 /* Set up a clipping area for the '/' station preview */
1191 Rect ir = r.Shrink(WidgetDimensions::scaled.bevel);
1192 if (FillDrawPixelInfo(&tmp_dpi, ir)) {
1193 AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
1194 int x = (ir.Width() - ScaleSpriteTrad(PREVIEW_WIDTH)) / 2 + ScaleSpriteTrad(PREVIEW_LEFT);
1195 int y = (ir.Height() + ScaleSpriteTrad(PREVIEW_HEIGHT)) / 2 - ScaleSpriteTrad(PREVIEW_BOTTOM);
1196 if (!DrawStationTile(x, y, _cur_railtype, AXIS_X, _station_gui.sel_class, _station_gui.sel_type)) {
1197 StationPickerDrawSprite(x, y, STATION_RAIL, _cur_railtype, INVALID_ROADTYPE, 2);
1200 break;
1203 case WID_BRAS_PLATFORM_DIR_Y: {
1204 /* Set up a clipping area for the '\' station preview */
1205 Rect ir = r.Shrink(WidgetDimensions::scaled.bevel);
1206 if (FillDrawPixelInfo(&tmp_dpi, ir)) {
1207 AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
1208 int x = (ir.Width() - ScaleSpriteTrad(PREVIEW_WIDTH)) / 2 + ScaleSpriteTrad(PREVIEW_LEFT);
1209 int y = (ir.Height() + ScaleSpriteTrad(PREVIEW_HEIGHT)) / 2 - ScaleSpriteTrad(PREVIEW_BOTTOM);
1210 if (!DrawStationTile(x, y, _cur_railtype, AXIS_Y, _station_gui.sel_class, _station_gui.sel_type)) {
1211 StationPickerDrawSprite(x, y, STATION_RAIL, _cur_railtype, INVALID_ROADTYPE, 3);
1214 break;
1217 default:
1218 this->PickerWindow::DrawWidget(r, widget);
1219 break;
1223 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1225 switch (widget) {
1226 case WID_BRAS_PLATFORM_DIR_X:
1227 case WID_BRAS_PLATFORM_DIR_Y:
1228 this->RaiseWidget(WID_BRAS_PLATFORM_DIR_X + _station_gui.axis);
1229 _station_gui.axis = (Axis)(widget - WID_BRAS_PLATFORM_DIR_X);
1230 this->LowerWidget(WID_BRAS_PLATFORM_DIR_X + _station_gui.axis);
1231 if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
1232 this->SetDirty();
1233 CloseWindowById(WC_SELECT_STATION, 0);
1234 break;
1236 case WID_BRAS_PLATFORM_NUM_1:
1237 case WID_BRAS_PLATFORM_NUM_2:
1238 case WID_BRAS_PLATFORM_NUM_3:
1239 case WID_BRAS_PLATFORM_NUM_4:
1240 case WID_BRAS_PLATFORM_NUM_5:
1241 case WID_BRAS_PLATFORM_NUM_6:
1242 case WID_BRAS_PLATFORM_NUM_7: {
1243 this->RaiseWidget(WID_BRAS_PLATFORM_NUM_BEGIN + _settings_client.gui.station_numtracks);
1244 this->RaiseWidget(WID_BRAS_PLATFORM_DRAG_N_DROP);
1246 _settings_client.gui.station_numtracks = widget - WID_BRAS_PLATFORM_NUM_BEGIN;
1247 _settings_client.gui.station_dragdrop = false;
1249 const StationSpec *statspec = StationClass::Get(_station_gui.sel_class)->GetSpec(_station_gui.sel_type);
1250 if (statspec != nullptr && HasBit(statspec->disallowed_lengths, _settings_client.gui.station_platlength - 1)) {
1251 /* The previously selected number of platforms in invalid */
1252 for (uint i = 0; i < 7; i++) {
1253 if (!HasBit(statspec->disallowed_lengths, i)) {
1254 this->RaiseWidget(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN);
1255 _settings_client.gui.station_platlength = i + 1;
1256 break;
1261 this->LowerWidget(_settings_client.gui.station_numtracks + WID_BRAS_PLATFORM_NUM_BEGIN);
1262 this->LowerWidget(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN);
1263 if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
1264 this->SetDirty();
1265 CloseWindowById(WC_SELECT_STATION, 0);
1266 break;
1269 case WID_BRAS_PLATFORM_LEN_1:
1270 case WID_BRAS_PLATFORM_LEN_2:
1271 case WID_BRAS_PLATFORM_LEN_3:
1272 case WID_BRAS_PLATFORM_LEN_4:
1273 case WID_BRAS_PLATFORM_LEN_5:
1274 case WID_BRAS_PLATFORM_LEN_6:
1275 case WID_BRAS_PLATFORM_LEN_7: {
1276 this->RaiseWidget(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN);
1277 this->RaiseWidget(WID_BRAS_PLATFORM_DRAG_N_DROP);
1279 _settings_client.gui.station_platlength = widget - WID_BRAS_PLATFORM_LEN_BEGIN;
1280 _settings_client.gui.station_dragdrop = false;
1282 const StationSpec *statspec = StationClass::Get(_station_gui.sel_class)->GetSpec(_station_gui.sel_type);
1283 if (statspec != nullptr && HasBit(statspec->disallowed_platforms, _settings_client.gui.station_numtracks - 1)) {
1284 /* The previously selected number of tracks in invalid */
1285 for (uint i = 0; i < 7; i++) {
1286 if (!HasBit(statspec->disallowed_platforms, i)) {
1287 this->RaiseWidget(_settings_client.gui.station_numtracks + WID_BRAS_PLATFORM_NUM_BEGIN);
1288 _settings_client.gui.station_numtracks = i + 1;
1289 break;
1294 this->LowerWidget(_settings_client.gui.station_numtracks + WID_BRAS_PLATFORM_NUM_BEGIN);
1295 this->LowerWidget(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN);
1296 if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
1297 this->SetDirty();
1298 CloseWindowById(WC_SELECT_STATION, 0);
1299 break;
1302 case WID_BRAS_PLATFORM_DRAG_N_DROP: {
1303 _settings_client.gui.station_dragdrop ^= true;
1305 this->ToggleWidgetLoweredState(WID_BRAS_PLATFORM_DRAG_N_DROP);
1307 /* get the first allowed length/number of platforms */
1308 const StationSpec *statspec = StationClass::Get(_station_gui.sel_class)->GetSpec(_station_gui.sel_type);
1309 if (statspec != nullptr && HasBit(statspec->disallowed_lengths, _settings_client.gui.station_platlength - 1)) {
1310 for (uint i = 0; i < 7; i++) {
1311 if (!HasBit(statspec->disallowed_lengths, i)) {
1312 this->RaiseWidget(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN);
1313 _settings_client.gui.station_platlength = i + 1;
1314 break;
1318 if (statspec != nullptr && HasBit(statspec->disallowed_platforms, _settings_client.gui.station_numtracks - 1)) {
1319 for (uint i = 0; i < 7; i++) {
1320 if (!HasBit(statspec->disallowed_platforms, i)) {
1321 this->RaiseWidget(_settings_client.gui.station_numtracks + WID_BRAS_PLATFORM_NUM_BEGIN);
1322 _settings_client.gui.station_numtracks = i + 1;
1323 break;
1328 this->SetWidgetLoweredState(_settings_client.gui.station_numtracks + WID_BRAS_PLATFORM_NUM_BEGIN, !_settings_client.gui.station_dragdrop);
1329 this->SetWidgetLoweredState(_settings_client.gui.station_platlength + WID_BRAS_PLATFORM_LEN_BEGIN, !_settings_client.gui.station_dragdrop);
1330 if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
1331 this->SetDirty();
1332 CloseWindowById(WC_SELECT_STATION, 0);
1333 break;
1336 case WID_BRAS_HIGHLIGHT_OFF:
1337 case WID_BRAS_HIGHLIGHT_ON:
1338 _settings_client.gui.station_show_coverage = (widget != WID_BRAS_HIGHLIGHT_OFF);
1340 this->SetWidgetLoweredState(WID_BRAS_HIGHLIGHT_OFF, !_settings_client.gui.station_show_coverage);
1341 this->SetWidgetLoweredState(WID_BRAS_HIGHLIGHT_ON, _settings_client.gui.station_show_coverage);
1342 if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
1343 this->SetDirty();
1344 SetViewportCatchmentStation(nullptr, true);
1345 break;
1347 default:
1348 this->PickerWindow::OnClick(pt, widget, click_count);
1349 break;
1353 void OnRealtimeTick([[maybe_unused]] uint delta_ms) override
1355 CheckRedrawStationCoverage(this);
1359 * Handler for global hotkeys of the BuildRailStationWindow.
1360 * @param hotkey Hotkey
1361 * @return ES_HANDLED if hotkey was accepted.
1363 static EventState BuildRailStationGlobalHotkeys(int hotkey)
1365 if (_game_mode == GM_MENU) return ES_NOT_HANDLED;
1366 Window *w = ShowStationBuilder(FindWindowById(WC_BUILD_TOOLBAR, TRANSPORT_RAIL));
1367 if (w == nullptr) return ES_NOT_HANDLED;
1368 return w->OnHotkey(hotkey);
1371 static inline HotkeyList hotkeys{"buildrailstation", {
1372 Hotkey('F', "focus_filter_box", PCWHK_FOCUS_FILTER_BOX),
1373 }, BuildRailStationGlobalHotkeys};
1376 static constexpr NWidgetPart _nested_station_builder_widgets[] = {
1377 NWidget(NWID_HORIZONTAL),
1378 NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
1379 NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_RAIL_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1380 NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN),
1381 NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
1382 EndContainer(),
1383 NWidget(NWID_HORIZONTAL),
1384 NWidget(NWID_VERTICAL),
1385 NWidgetFunction(MakePickerClassWidgets),
1386 NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
1387 NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_picker, 0), SetPadding(WidgetDimensions::unscaled.picker),
1388 NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetMinimalSize(144, 11), SetFill(1, 0), SetDataTip(STR_STATION_BUILD_ORIENTATION, STR_NULL),
1389 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1),
1390 NWidget(WWT_PANEL, COLOUR_GREY, WID_BRAS_PLATFORM_DIR_X), SetFill(0, 0), SetDataTip(0x0, STR_STATION_BUILD_RAILROAD_ORIENTATION_TOOLTIP), EndContainer(),
1391 NWidget(WWT_PANEL, COLOUR_GREY, WID_BRAS_PLATFORM_DIR_Y), SetFill(0, 0), SetDataTip(0x0, STR_STATION_BUILD_RAILROAD_ORIENTATION_TOOLTIP), EndContainer(),
1392 EndContainer(),
1393 NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetMinimalSize(144, 11), SetFill(1, 0), SetDataTip(STR_STATION_BUILD_NUMBER_OF_TRACKS, STR_NULL),
1394 NWidget(NWID_HORIZONTAL), SetPIPRatio(1, 0, 1),
1395 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_NUM_1), SetAspect(1.25f), SetDataTip(STR_BLACK_1, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
1396 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_NUM_2), SetAspect(1.25f), SetDataTip(STR_BLACK_2, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
1397 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_NUM_3), SetAspect(1.25f), SetDataTip(STR_BLACK_3, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
1398 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_NUM_4), SetAspect(1.25f), SetDataTip(STR_BLACK_4, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
1399 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_NUM_5), SetAspect(1.25f), SetDataTip(STR_BLACK_5, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
1400 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_NUM_6), SetAspect(1.25f), SetDataTip(STR_BLACK_6, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
1401 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_NUM_7), SetAspect(1.25f), SetDataTip(STR_BLACK_7, STR_STATION_BUILD_NUMBER_OF_TRACKS_TOOLTIP),
1402 EndContainer(),
1403 NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetMinimalSize(144, 11), SetFill(1, 0), SetDataTip(STR_STATION_BUILD_PLATFORM_LENGTH, STR_NULL),
1404 NWidget(NWID_HORIZONTAL), SetPIPRatio(1, 0, 1),
1405 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_LEN_1), SetAspect(1.25f), SetDataTip(STR_BLACK_1, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
1406 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_LEN_2), SetAspect(1.25f), SetDataTip(STR_BLACK_2, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
1407 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_LEN_3), SetAspect(1.25f), SetDataTip(STR_BLACK_3, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
1408 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_LEN_4), SetAspect(1.25f), SetDataTip(STR_BLACK_4, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
1409 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_LEN_5), SetAspect(1.25f), SetDataTip(STR_BLACK_5, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
1410 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_LEN_6), SetAspect(1.25f), SetDataTip(STR_BLACK_6, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
1411 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_LEN_7), SetAspect(1.25f), SetDataTip(STR_BLACK_7, STR_STATION_BUILD_PLATFORM_LENGTH_TOOLTIP),
1412 EndContainer(),
1413 NWidget(NWID_HORIZONTAL), SetPIPRatio(1, 0, 1),
1414 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_PLATFORM_DRAG_N_DROP), SetMinimalSize(75, 12), SetDataTip(STR_STATION_BUILD_DRAG_DROP, STR_STATION_BUILD_DRAG_DROP_TOOLTIP),
1415 EndContainer(),
1416 NWidget(WWT_LABEL, COLOUR_DARK_GREEN), SetDataTip(STR_STATION_BUILD_COVERAGE_AREA_TITLE, STR_NULL), SetFill(1, 0),
1417 NWidget(NWID_HORIZONTAL), SetPIPRatio(1, 0, 1),
1418 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_HIGHLIGHT_OFF), SetMinimalSize(60, 12), SetDataTip(STR_STATION_BUILD_COVERAGE_OFF, STR_STATION_BUILD_COVERAGE_AREA_OFF_TOOLTIP),
1419 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAS_HIGHLIGHT_ON), SetMinimalSize(60, 12), SetDataTip(STR_STATION_BUILD_COVERAGE_ON, STR_STATION_BUILD_COVERAGE_AREA_ON_TOOLTIP),
1420 EndContainer(),
1421 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_BRAS_COVERAGE_TEXTS), SetFill(1, 1), SetResize(1, 0), SetMinimalTextLines(2, 0),
1422 EndContainer(),
1423 EndContainer(),
1424 EndContainer(),
1425 NWidgetFunction(MakePickerTypeWidgets),
1426 EndContainer(),
1429 /** High level window description of the station-build window (default & newGRF) */
1430 static WindowDesc _station_builder_desc(
1431 WDP_AUTO, "build_station_rail", 0, 0,
1432 WC_BUILD_STATION, WC_BUILD_TOOLBAR,
1433 WDF_CONSTRUCTION,
1434 _nested_station_builder_widgets,
1435 &BuildRailStationWindow::hotkeys
1438 /** Open station build window */
1439 static Window *ShowStationBuilder(Window *parent)
1441 return new BuildRailStationWindow(_station_builder_desc, parent);
1444 struct BuildSignalWindow : public PickerWindowBase {
1445 private:
1446 Dimension sig_sprite_size; ///< Maximum size of signal GUI sprites.
1447 int sig_sprite_bottom_offset; ///< Maximum extent of signal GUI sprite from reference point towards bottom.
1450 * Draw dynamic a signal-sprite in a button in the signal GUI
1451 * @param image the sprite to draw
1453 void DrawSignalSprite(const Rect &r, SpriteID image) const
1455 Point offset;
1456 Dimension sprite_size = GetSpriteSize(image, &offset);
1457 Rect ir = r.Shrink(WidgetDimensions::scaled.imgbtn);
1458 int x = CenterBounds(ir.left, ir.right, sprite_size.width - offset.x) - offset.x; // centered
1459 int y = ir.top - sig_sprite_bottom_offset +
1460 (ir.Height() + sig_sprite_size.height) / 2; // aligned to bottom
1462 DrawSprite(image, PAL_NONE, x, y);
1465 /** Show or hide buttons for non-path signals in the signal GUI */
1466 void SetSignalUIMode()
1468 bool show_non_path_signals = (_settings_client.gui.signal_gui_mode == SIGNAL_GUI_ALL);
1470 this->GetWidget<NWidgetStacked>(WID_BS_BLOCK_SEL)->SetDisplayedPlane(show_non_path_signals ? 0 : SZSP_NONE);
1471 this->GetWidget<NWidgetStacked>(WID_BS_BLOCK_SPACER_SEL)->SetDisplayedPlane(show_non_path_signals ? 0 : SZSP_NONE);
1474 public:
1475 BuildSignalWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent)
1477 this->CreateNestedTree();
1478 this->SetSignalUIMode();
1479 this->FinishInitNested(TRANSPORT_RAIL);
1480 this->OnInvalidateData();
1483 void Close([[maybe_unused]] int data = 0) override
1485 _convert_signal_button = false;
1486 this->PickerWindowBase::Close();
1489 void OnInit() override
1491 /* Calculate maximum signal sprite size. */
1492 this->sig_sprite_size.width = 0;
1493 this->sig_sprite_size.height = 0;
1494 this->sig_sprite_bottom_offset = 0;
1495 const RailTypeInfo *rti = GetRailTypeInfo(_cur_railtype);
1496 for (uint type = SIGTYPE_BLOCK; type < SIGTYPE_END; type++) {
1497 for (uint variant = SIG_ELECTRIC; variant <= SIG_SEMAPHORE; variant++) {
1498 for (uint lowered = 0; lowered < 2; lowered++) {
1499 Point offset;
1500 Dimension sprite_size = GetSpriteSize(rti->gui_sprites.signals[type][variant][lowered], &offset);
1501 this->sig_sprite_bottom_offset = std::max<int>(this->sig_sprite_bottom_offset, sprite_size.height);
1502 this->sig_sprite_size.width = std::max<int>(this->sig_sprite_size.width, sprite_size.width - offset.x);
1503 this->sig_sprite_size.height = std::max<int>(this->sig_sprite_size.height, sprite_size.height - offset.y);
1509 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1511 if (widget == WID_BS_DRAG_SIGNALS_DENSITY_LABEL) {
1512 /* Two digits for signals density. */
1513 size.width = std::max(size.width, 2 * GetDigitWidth() + padding.width + WidgetDimensions::scaled.framerect.Horizontal());
1514 } else if (IsInsideMM(widget, WID_BS_SEMAPHORE_NORM, WID_BS_ELECTRIC_PBS_OWAY + 1)) {
1515 size.width = std::max(size.width, this->sig_sprite_size.width + padding.width);
1516 size.height = std::max(size.height, this->sig_sprite_size.height + padding.height);
1520 void SetStringParameters(WidgetID widget) const override
1522 switch (widget) {
1523 case WID_BS_DRAG_SIGNALS_DENSITY_LABEL:
1524 SetDParam(0, _settings_client.gui.drag_signals_density);
1525 break;
1529 void DrawWidget(const Rect &r, WidgetID widget) const override
1531 if (IsInsideMM(widget, WID_BS_SEMAPHORE_NORM, WID_BS_ELECTRIC_PBS_OWAY + 1)) {
1532 /* Extract signal from widget number. */
1533 int type = (widget - WID_BS_SEMAPHORE_NORM) % SIGTYPE_END;
1534 int var = SIG_SEMAPHORE - (widget - WID_BS_SEMAPHORE_NORM) / SIGTYPE_END; // SignalVariant order is reversed compared to the widgets.
1535 SpriteID sprite = GetRailTypeInfo(_cur_railtype)->gui_sprites.signals[type][var][this->IsWidgetLowered(widget)];
1537 this->DrawSignalSprite(r, sprite);
1541 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1543 switch (widget) {
1544 case WID_BS_SEMAPHORE_NORM:
1545 case WID_BS_SEMAPHORE_ENTRY:
1546 case WID_BS_SEMAPHORE_EXIT:
1547 case WID_BS_SEMAPHORE_COMBO:
1548 case WID_BS_SEMAPHORE_PBS:
1549 case WID_BS_SEMAPHORE_PBS_OWAY:
1550 case WID_BS_ELECTRIC_NORM:
1551 case WID_BS_ELECTRIC_ENTRY:
1552 case WID_BS_ELECTRIC_EXIT:
1553 case WID_BS_ELECTRIC_COMBO:
1554 case WID_BS_ELECTRIC_PBS:
1555 case WID_BS_ELECTRIC_PBS_OWAY:
1556 this->RaiseWidget((_cur_signal_variant == SIG_ELECTRIC ? WID_BS_ELECTRIC_NORM : WID_BS_SEMAPHORE_NORM) + _cur_signal_type);
1558 _cur_signal_type = (SignalType)((uint)((widget - WID_BS_SEMAPHORE_NORM) % (SIGTYPE_LAST + 1)));
1559 _cur_signal_variant = widget >= WID_BS_ELECTRIC_NORM ? SIG_ELECTRIC : SIG_SEMAPHORE;
1561 /* Update default (last-used) signal type in config file. */
1562 _settings_client.gui.default_signal_type = _cur_signal_type;
1564 /* If 'remove' button of rail build toolbar is active, disable it. */
1565 if (_remove_button_clicked) {
1566 Window *w = FindWindowById(WC_BUILD_TOOLBAR, TRANSPORT_RAIL);
1567 if (w != nullptr) ToggleRailButton_Remove(w);
1570 break;
1572 case WID_BS_CONVERT:
1573 _convert_signal_button = !_convert_signal_button;
1574 break;
1576 case WID_BS_DRAG_SIGNALS_DENSITY_DECREASE:
1577 if (_settings_client.gui.drag_signals_density > 1) {
1578 _settings_client.gui.drag_signals_density--;
1579 SetWindowDirty(WC_GAME_OPTIONS, WN_GAME_OPTIONS_GAME_SETTINGS);
1581 break;
1583 case WID_BS_DRAG_SIGNALS_DENSITY_INCREASE:
1584 if (_settings_client.gui.drag_signals_density < 20) {
1585 _settings_client.gui.drag_signals_density++;
1586 SetWindowDirty(WC_GAME_OPTIONS, WN_GAME_OPTIONS_GAME_SETTINGS);
1588 break;
1590 case WID_BS_TOGGLE_SIZE:
1591 _settings_client.gui.signal_gui_mode = (_settings_client.gui.signal_gui_mode == SIGNAL_GUI_ALL) ? SIGNAL_GUI_PATH : SIGNAL_GUI_ALL;
1592 this->SetSignalUIMode();
1593 this->ReInit();
1594 break;
1596 default: break;
1599 this->InvalidateData();
1603 * Some data on this window has become invalid.
1604 * @param data Information about the changed data.
1605 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
1607 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
1609 if (!gui_scope) return;
1610 this->LowerWidget((_cur_signal_variant == SIG_ELECTRIC ? WID_BS_ELECTRIC_NORM : WID_BS_SEMAPHORE_NORM) + _cur_signal_type);
1612 this->SetWidgetLoweredState(WID_BS_CONVERT, _convert_signal_button);
1614 this->SetWidgetDisabledState(WID_BS_DRAG_SIGNALS_DENSITY_DECREASE, _settings_client.gui.drag_signals_density == 1);
1615 this->SetWidgetDisabledState(WID_BS_DRAG_SIGNALS_DENSITY_INCREASE, _settings_client.gui.drag_signals_density == 20);
1619 /** Nested widget definition of the build signal window */
1620 static constexpr NWidgetPart _nested_signal_builder_widgets[] = {
1621 /* Title bar and buttons. */
1622 NWidget(NWID_HORIZONTAL),
1623 NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
1624 NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_BS_CAPTION), SetDataTip(STR_BUILD_SIGNAL_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1625 NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_BS_TOGGLE_SIZE), SetDataTip(SPR_LARGE_SMALL_WINDOW, STR_BUILD_SIGNAL_TOGGLE_ADVANCED_SIGNAL_TOOLTIP), SetAspect(WidgetDimensions::ASPECT_TOGGLE_SIZE),
1626 EndContainer(),
1628 /* Container for both signal groups, spacers, and convert/autofill buttons. */
1629 NWidget(NWID_HORIZONTAL),
1630 /* Block signals (can be hidden). */
1631 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_BLOCK_SEL),
1632 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
1633 /* Semaphore block signals. */
1634 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1635 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_NORM), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_NORM_TOOLTIP), SetMinimalSize(22, 22), EndContainer(),
1636 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_ENTRY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_ENTRY_TOOLTIP), SetMinimalSize(22, 22), EndContainer(),
1637 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_EXIT), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_EXIT_TOOLTIP), SetMinimalSize(22, 22), EndContainer(),
1638 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_COMBO), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_COMBO_TOOLTIP), SetMinimalSize(22, 22), EndContainer(),
1639 EndContainer(),
1640 /* Electric block signals. */
1641 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1642 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_NORM), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_NORM_TOOLTIP), SetMinimalSize(22, 22), EndContainer(),
1643 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_ENTRY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_ENTRY_TOOLTIP), SetMinimalSize(22, 22), EndContainer(),
1644 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_EXIT), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_EXIT_TOOLTIP), SetMinimalSize(22, 22), EndContainer(),
1645 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_COMBO), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_COMBO_TOOLTIP), SetMinimalSize(22, 22), EndContainer(),
1646 EndContainer(),
1647 EndContainer(),
1648 EndContainer(),
1650 /* Divider (only shown if block signals visible). */
1651 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_BS_BLOCK_SPACER_SEL),
1652 NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetFill(0, 0), SetMinimalSize(4, 0), EndContainer(),
1653 EndContainer(),
1655 /* Path signals. */
1656 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
1657 /* Semaphore path signals. */
1658 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1659 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_PBS), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_PBS_TOOLTIP), SetMinimalSize(22, 22), EndContainer(),
1660 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_SEMAPHORE_PBS_OWAY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_SEMAPHORE_PBS_OWAY_TOOLTIP), SetMinimalSize(22, 22), EndContainer(),
1661 EndContainer(),
1662 /* Electric path signals. */
1663 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1664 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_PBS), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_PBS_TOOLTIP), SetMinimalSize(22, 22), EndContainer(),
1665 NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_BS_ELECTRIC_PBS_OWAY), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_ELECTRIC_PBS_OWAY_TOOLTIP), SetMinimalSize(22, 22), EndContainer(),
1666 EndContainer(),
1667 EndContainer(),
1669 /* Convert/autofill buttons. */
1670 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
1671 NWidget(WWT_IMGBTN, COLOUR_DARK_GREEN, WID_BS_CONVERT), SetDataTip(SPR_IMG_SIGNAL_CONVERT, STR_BUILD_SIGNAL_CONVERT_TOOLTIP), SetFill(0, 1),
1672 NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetDataTip(STR_NULL, STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_TOOLTIP), SetFill(0, 1),
1673 NWidget(NWID_VERTICAL), SetPadding(2), SetPIPRatio(1, 0, 1),
1674 NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_BS_DRAG_SIGNALS_DENSITY_LABEL), SetDataTip(STR_JUST_INT, STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_TOOLTIP), SetTextStyle(TC_ORANGE), SetFill(1, 1),
1675 NWidget(NWID_HORIZONTAL), SetPIPRatio(1, 0, 1),
1676 NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_BS_DRAG_SIGNALS_DENSITY_DECREASE), SetMinimalSize(9, 12), SetDataTip(AWV_DECREASE, STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_DECREASE_TOOLTIP),
1677 NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_BS_DRAG_SIGNALS_DENSITY_INCREASE), SetMinimalSize(9, 12), SetDataTip(AWV_INCREASE, STR_BUILD_SIGNAL_DRAG_SIGNALS_DENSITY_INCREASE_TOOLTIP),
1678 EndContainer(),
1679 EndContainer(),
1680 EndContainer(),
1681 EndContainer(),
1682 EndContainer(),
1685 /** Signal selection window description */
1686 static WindowDesc _signal_builder_desc(
1687 WDP_AUTO, nullptr, 0, 0,
1688 WC_BUILD_SIGNAL, WC_BUILD_TOOLBAR,
1689 WDF_CONSTRUCTION,
1690 _nested_signal_builder_widgets
1694 * Open the signal selection window
1696 static void ShowSignalBuilder(Window *parent)
1698 new BuildSignalWindow(_signal_builder_desc, parent);
1701 struct BuildRailDepotWindow : public PickerWindowBase {
1702 BuildRailDepotWindow(WindowDesc &desc, Window *parent) : PickerWindowBase(desc, parent)
1704 this->InitNested(TRANSPORT_RAIL);
1705 this->LowerWidget(WID_BRAD_DEPOT_NE + _build_depot_direction);
1708 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
1710 if (!IsInsideMM(widget, WID_BRAD_DEPOT_NE, WID_BRAD_DEPOT_NW + 1)) return;
1712 size.width = ScaleGUITrad(64) + WidgetDimensions::scaled.fullbevel.Horizontal();
1713 size.height = ScaleGUITrad(48) + WidgetDimensions::scaled.fullbevel.Vertical();
1716 void DrawWidget(const Rect &r, WidgetID widget) const override
1718 if (!IsInsideMM(widget, WID_BRAD_DEPOT_NE, WID_BRAD_DEPOT_NW + 1)) return;
1720 DrawPixelInfo tmp_dpi;
1721 Rect ir = r.Shrink(WidgetDimensions::scaled.bevel);
1722 if (FillDrawPixelInfo(&tmp_dpi, ir)) {
1723 AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
1724 int x = (ir.Width() - ScaleSpriteTrad(64)) / 2 + ScaleSpriteTrad(31);
1725 int y = (ir.Height() + ScaleSpriteTrad(48)) / 2 - ScaleSpriteTrad(31);
1726 DrawTrainDepotSprite(x, y, widget - WID_BRAD_DEPOT_NE + DIAGDIR_NE, _cur_railtype);
1730 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
1732 switch (widget) {
1733 case WID_BRAD_DEPOT_NE:
1734 case WID_BRAD_DEPOT_SE:
1735 case WID_BRAD_DEPOT_SW:
1736 case WID_BRAD_DEPOT_NW:
1737 this->RaiseWidget(WID_BRAD_DEPOT_NE + _build_depot_direction);
1738 _build_depot_direction = (DiagDirection)(widget - WID_BRAD_DEPOT_NE);
1739 this->LowerWidget(WID_BRAD_DEPOT_NE + _build_depot_direction);
1740 if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP);
1741 this->SetDirty();
1742 break;
1747 /** Nested widget definition of the build rail depot window */
1748 static constexpr NWidgetPart _nested_build_depot_widgets[] = {
1749 NWidget(NWID_HORIZONTAL),
1750 NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
1751 NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_BUILD_DEPOT_TRAIN_ORIENTATION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1752 EndContainer(),
1753 NWidget(WWT_PANEL, COLOUR_DARK_GREEN),
1754 NWidget(NWID_HORIZONTAL_LTR), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPIPRatio(1, 0, 1), SetPadding(WidgetDimensions::unscaled.picker),
1755 NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
1756 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAD_DEPOT_NW), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP),
1757 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAD_DEPOT_SW), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP),
1758 EndContainer(),
1759 NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
1760 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAD_DEPOT_NE), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP),
1761 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_BRAD_DEPOT_SE), SetDataTip(0x0, STR_BUILD_DEPOT_TRAIN_ORIENTATION_TOOLTIP),
1762 EndContainer(),
1763 EndContainer(),
1764 EndContainer(),
1767 static WindowDesc _build_depot_desc(
1768 WDP_AUTO, nullptr, 0, 0,
1769 WC_BUILD_DEPOT, WC_BUILD_TOOLBAR,
1770 WDF_CONSTRUCTION,
1771 _nested_build_depot_widgets
1774 static void ShowBuildTrainDepotPicker(Window *parent)
1776 new BuildRailDepotWindow(_build_depot_desc, parent);
1779 class WaypointPickerCallbacks : public PickerCallbacksNewGRFClass<StationClass> {
1780 public:
1781 WaypointPickerCallbacks() : PickerCallbacksNewGRFClass<StationClass>("fav_waypoints") {}
1783 StringID GetClassTooltip() const override { return STR_PICKER_WAYPOINT_CLASS_TOOLTIP; }
1784 StringID GetTypeTooltip() const override { return STR_PICKER_WAYPOINT_TYPE_TOOLTIP; }
1786 bool IsActive() const override
1788 for (const auto &cls : StationClass::Classes()) {
1789 if (!IsWaypointClass(cls)) continue;
1790 for (const auto *spec : cls.Specs()) {
1791 if (spec != nullptr) return true;
1794 return false;
1797 bool HasClassChoice() const override
1799 return std::count_if(std::begin(StationClass::Classes()), std::end(StationClass::Classes()), IsWaypointClass) > 1;
1802 void Close(int) override { ResetObjectToPlace(); }
1803 int GetSelectedClass() const override { return _waypoint_gui.sel_class; }
1804 void SetSelectedClass(int id) const override { _waypoint_gui.sel_class = this->GetClassIndex(id); }
1806 StringID GetClassName(int id) const override
1808 const auto *sc = GetClass(id);
1809 if (!IsWaypointClass(*sc)) return INVALID_STRING_ID;
1810 return sc->name;
1813 int GetSelectedType() const override { return _waypoint_gui.sel_type; }
1814 void SetSelectedType(int id) const override { _waypoint_gui.sel_type = id; }
1816 StringID GetTypeName(int cls_id, int id) const override
1818 const auto *spec = this->GetSpec(cls_id, id);
1819 return (spec == nullptr) ? STR_STATION_CLASS_WAYP_WAYPOINT : spec->name;
1822 bool IsTypeAvailable(int cls_id, int id) const override
1824 return IsStationAvailable(this->GetSpec(cls_id, id));
1827 void DrawType(int x, int y, int cls_id, int id) const override
1829 DrawWaypointSprite(x, y, this->GetClassIndex(cls_id), id, _cur_railtype);
1832 void FillUsedItems(std::set<PickerItem> &items) override
1834 bool default_added = false;
1835 for (const Waypoint *wp : Waypoint::Iterate()) {
1836 if (wp->owner != _local_company || HasBit(wp->waypoint_flags, WPF_ROAD)) continue;
1837 if (!default_added && StationUsesDefaultType(wp)) {
1838 items.insert({0, 0, STAT_CLASS_WAYP, 0});
1839 default_added = true;
1841 for (const auto &sm : wp->speclist) {
1842 if (sm.spec == nullptr) continue;
1843 items.insert({sm.grfid, sm.localidx, sm.spec->class_index, sm.spec->index});
1848 static WaypointPickerCallbacks instance;
1850 /* static */ WaypointPickerCallbacks WaypointPickerCallbacks::instance;
1852 struct BuildRailWaypointWindow : public PickerWindow {
1853 BuildRailWaypointWindow(WindowDesc &desc, Window *parent) : PickerWindow(desc, parent, TRANSPORT_RAIL, WaypointPickerCallbacks::instance)
1855 this->ConstructWindow();
1856 this->InvalidateData();
1859 static inline HotkeyList hotkeys{"buildrailwaypoint", {
1860 Hotkey('F', "focus_filter_box", PCWHK_FOCUS_FILTER_BOX),
1864 /** Nested widget definition for the build NewGRF rail waypoint window */
1865 static constexpr NWidgetPart _nested_build_waypoint_widgets[] = {
1866 NWidget(NWID_HORIZONTAL),
1867 NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN),
1868 NWidget(WWT_CAPTION, COLOUR_DARK_GREEN), SetDataTip(STR_WAYPOINT_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1869 NWidget(WWT_SHADEBOX, COLOUR_DARK_GREEN),
1870 NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN),
1871 EndContainer(),
1872 NWidget(NWID_HORIZONTAL),
1873 NWidgetFunction(MakePickerClassWidgets),
1874 NWidgetFunction(MakePickerTypeWidgets),
1875 EndContainer(),
1878 static WindowDesc _build_waypoint_desc(
1879 WDP_AUTO, "build_waypoint", 0, 0,
1880 WC_BUILD_WAYPOINT, WC_BUILD_TOOLBAR,
1881 WDF_CONSTRUCTION,
1882 _nested_build_waypoint_widgets,
1883 &BuildRailWaypointWindow::hotkeys
1886 static void ShowBuildWaypointPicker(Window *parent)
1888 if (!WaypointPickerCallbacks::instance.IsActive()) return;
1889 new BuildRailWaypointWindow(_build_waypoint_desc, parent);
1893 * Initialize rail building GUI settings
1895 void InitializeRailGui()
1897 _build_depot_direction = DIAGDIR_NW;
1898 _station_gui.sel_class = StationClassID::STAT_CLASS_DFLT;
1899 _station_gui.sel_type = 0;
1900 _waypoint_gui.sel_class = StationClassID::STAT_CLASS_WAYP;
1901 _waypoint_gui.sel_type = 0;
1905 * Re-initialize rail-build toolbar after toggling support for electric trains
1906 * @param disable Boolean whether electric trains are disabled (removed from the game)
1908 void ReinitGuiAfterToggleElrail(bool disable)
1910 extern RailType _last_built_railtype;
1911 if (disable && _last_built_railtype == RAILTYPE_ELECTRIC) {
1912 _last_built_railtype = _cur_railtype = RAILTYPE_RAIL;
1913 BuildRailToolbarWindow *w = dynamic_cast<BuildRailToolbarWindow *>(FindWindowById(WC_BUILD_TOOLBAR, TRANSPORT_RAIL));
1914 if (w != nullptr) w->ModifyRailType(_cur_railtype);
1916 MarkWholeScreenDirty();
1919 /** Set the initial (default) railtype to use */
1920 static void SetDefaultRailGui()
1922 if (_local_company == COMPANY_SPECTATOR || !Company::IsValidID(_local_company)) return;
1924 extern RailType _last_built_railtype;
1925 RailType rt;
1926 switch (_settings_client.gui.default_rail_type) {
1927 case 2: {
1928 /* Find the most used rail type */
1929 uint count[RAILTYPE_END];
1930 memset(count, 0, sizeof(count));
1931 for (TileIndex t = 0; t < Map::Size(); t++) {
1932 if (IsTileType(t, MP_RAILWAY) || IsLevelCrossingTile(t) || HasStationTileRail(t) ||
1933 (IsTileType(t, MP_TUNNELBRIDGE) && GetTunnelBridgeTransportType(t) == TRANSPORT_RAIL)) {
1934 count[GetRailType(t)]++;
1938 rt = static_cast<RailType>(std::max_element(count + RAILTYPE_BEGIN, count + RAILTYPE_END) - count);
1939 if (count[rt] > 0) break;
1941 /* No rail, just get the first available one */
1942 [[fallthrough]];
1944 case 0: {
1945 /* Use first available type */
1946 std::vector<RailType>::const_iterator it = std::find_if(_sorted_railtypes.begin(), _sorted_railtypes.end(),
1947 [](RailType r) { return HasRailTypeAvail(_local_company, r); });
1948 rt = it != _sorted_railtypes.end() ? *it : RAILTYPE_BEGIN;
1949 break;
1951 case 1: {
1952 /* Use last available type */
1953 std::vector<RailType>::const_reverse_iterator it = std::find_if(_sorted_railtypes.rbegin(), _sorted_railtypes.rend(),
1954 [](RailType r){ return HasRailTypeAvail(_local_company, r); });
1955 rt = it != _sorted_railtypes.rend() ? *it : RAILTYPE_BEGIN;
1956 break;
1958 default:
1959 NOT_REACHED();
1962 _last_built_railtype = _cur_railtype = rt;
1963 BuildRailToolbarWindow *w = dynamic_cast<BuildRailToolbarWindow *>(FindWindowById(WC_BUILD_TOOLBAR, TRANSPORT_RAIL));
1964 if (w != nullptr) w->ModifyRailType(_cur_railtype);
1968 * Updates the current signal variant used in the signal GUI
1969 * to the one adequate to current year.
1971 void ResetSignalVariant(int32_t)
1973 SignalVariant new_variant = (TimerGameCalendar::year < _settings_client.gui.semaphore_build_before ? SIG_SEMAPHORE : SIG_ELECTRIC);
1975 if (new_variant != _cur_signal_variant) {
1976 Window *w = FindWindowById(WC_BUILD_SIGNAL, 0);
1977 if (w != nullptr) {
1978 w->SetDirty();
1979 w->RaiseWidget((_cur_signal_variant == SIG_ELECTRIC ? WID_BS_ELECTRIC_NORM : WID_BS_SEMAPHORE_NORM) + _cur_signal_type);
1981 _cur_signal_variant = new_variant;
1985 static IntervalTimer<TimerGameCalendar> _check_reset_signal({TimerGameCalendar::YEAR, TimerGameCalendar::Priority::NONE}, [](auto)
1987 if (TimerGameCalendar::year != _settings_client.gui.semaphore_build_before) return;
1989 ResetSignalVariant();
1993 * Resets the rail GUI - sets default railtype to build
1994 * and resets the signal GUI
1996 void InitializeRailGUI()
1998 SetDefaultRailGui();
2000 _convert_signal_button = false;
2001 _cur_signal_type = _settings_client.gui.default_signal_type;
2002 ResetSignalVariant();
2006 * Create a drop down list for all the rail types of the local company.
2007 * @param for_replacement Whether this list is for the replacement window.
2008 * @param all_option Whether to add an 'all types' item.
2009 * @return The populated and sorted #DropDownList.
2011 DropDownList GetRailTypeDropDownList(bool for_replacement, bool all_option)
2013 RailTypes used_railtypes;
2014 RailTypes avail_railtypes;
2016 const Company *c = Company::Get(_local_company);
2018 /* Find the used railtypes. */
2019 if (for_replacement) {
2020 avail_railtypes = GetCompanyRailTypes(c->index, false);
2021 used_railtypes = GetRailTypes(false);
2022 } else {
2023 avail_railtypes = c->avail_railtypes;
2024 used_railtypes = GetRailTypes(true);
2027 DropDownList list;
2029 if (all_option) {
2030 list.push_back(MakeDropDownListStringItem(STR_REPLACE_ALL_RAILTYPE, INVALID_RAILTYPE));
2033 Dimension d = { 0, 0 };
2034 /* Get largest icon size, to ensure text is aligned on each menu item. */
2035 if (!for_replacement) {
2036 for (const auto &rt : _sorted_railtypes) {
2037 if (!HasBit(used_railtypes, rt)) continue;
2038 const RailTypeInfo *rti = GetRailTypeInfo(rt);
2039 d = maxdim(d, GetSpriteSize(rti->gui_sprites.build_x_rail));
2043 for (const auto &rt : _sorted_railtypes) {
2044 /* If it's not used ever, don't show it to the user. */
2045 if (!HasBit(used_railtypes, rt)) continue;
2047 const RailTypeInfo *rti = GetRailTypeInfo(rt);
2049 SetDParam(0, rti->strings.menu_text);
2050 SetDParam(1, rti->max_speed);
2051 if (for_replacement) {
2052 list.push_back(MakeDropDownListStringItem(rti->strings.replace_text, rt, !HasBit(avail_railtypes, rt)));
2053 } else {
2054 StringID str = rti->max_speed > 0 ? STR_TOOLBAR_RAILTYPE_VELOCITY : STR_JUST_STRING;
2055 list.push_back(MakeDropDownListIconItem(d, rti->gui_sprites.build_x_rail, PAL_NONE, str, rt, !HasBit(avail_railtypes, rt)));
2059 if (list.empty()) {
2060 /* Empty dropdowns are not allowed */
2061 list.push_back(MakeDropDownListStringItem(STR_NONE, INVALID_RAILTYPE, true));
2064 return list;