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/>.
8 /** @file rail_gui.cpp %File for dealing with rail construction user interface */
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"
33 #include "engine_base.h"
34 #include "vehicle_func.h"
35 #include "zoom_func.h"
37 #include "station_cmd.h"
38 #include "tunnelbridge_cmd.h"
39 #include "waypoint_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
);
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
,
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
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
);
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
);
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
);
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 {
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();
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
);
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
;
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
;
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);
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
);
296 w
->OnPlaceMouseUp(VPM_X_OR_Y
, DDSP_BUILD_BRIDGE
, pt
, other_tile
, tile
);
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();
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
);
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);
365 VpSetPlaceSizingLimit(-1);
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
);
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
);
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
);
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
));
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
);
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. */
477 bool can_build
= CanBuildVehicleInfrastructure(VEH_TRAIN
);
478 for (const WidgetID widget
: can_build_widgets
) this->SetWidgetDisabledState(widget
, !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
);
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
;
523 void UpdateRemoveWidgetStatus(WidgetID clicked_widget
)
525 switch (clicked_widget
) {
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 */
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
));
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
);
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
));
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;
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
;
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
;
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
;
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
;
593 case WID_RAT_AUTORAIL
:
594 HandlePlacePushButton(this, WID_RAT_AUTORAIL
, GetRailTypeInfo(_cur_railtype
)->cursor
.autorail
, HT_RAIL
);
595 this->last_user_action
= widget
;
598 case WID_RAT_DEMOLISH
:
599 HandlePlacePushButton(this, WID_RAT_DEMOLISH
, ANIMCURSOR_DEMOLISH
, HT_RECT
| HT_DIAGONAL
);
600 this->last_user_action
= widget
;
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
;
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);
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
;
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);
633 case WID_RAT_BUILD_BRIDGE
:
634 HandlePlacePushButton(this, WID_RAT_BUILD_BRIDGE
, SPR_CURSOR_BRIDGE
, HT_RECT
);
635 this->last_user_action
= widget
;
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
;
644 BuildRailClick_Remove(this);
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
;
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
);
671 case WID_RAT_BUILD_X
:
672 VpStartPlaceSizing(tile
, VPM_FIX_Y
| VPM_RAILDIRS
, DDSP_PLACE_RAIL
);
675 case WID_RAT_BUILD_EW
:
676 VpStartPlaceSizing(tile
, VPM_FIX_HORIZONTAL
| VPM_RAILDIRS
, DDSP_PLACE_RAIL
);
679 case WID_RAT_BUILD_Y
:
680 VpStartPlaceSizing(tile
, VPM_FIX_X
| VPM_RAILDIRS
, DDSP_PLACE_RAIL
);
683 case WID_RAT_AUTORAIL
:
684 VpStartPlaceSizing(tile
, VPM_RAILDIRS
, DDSP_PLACE_RAIL
);
687 case WID_RAT_DEMOLISH
:
688 PlaceProc_DemolishArea(tile
);
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
);
695 case WID_RAT_BUILD_WAYPOINT
:
696 PlaceRail_Waypoint(tile
);
699 case WID_RAT_BUILD_STATION
:
700 PlaceRail_Station(tile
);
703 case WID_RAT_BUILD_SIGNALS
:
704 VpStartPlaceSizing(tile
, VPM_SIGNALDIRS
, DDSP_BUILD_SIGNALS
);
707 case WID_RAT_BUILD_BRIDGE
:
708 PlaceRail_Bridge(tile
, this);
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
);
715 case WID_RAT_CONVERT_RAIL
:
716 VpStartPlaceSizing(tile
, VPM_X_AND_Y
, DDSP_CONVERT_RAIL
);
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
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
);
741 case DDSP_PLACE_RAIL
:
742 HandleAutodirPlacement();
745 case DDSP_BUILD_SIGNALS
:
746 HandleAutoSignalPlacement();
749 case DDSP_DEMOLISH_AREA
:
750 GUIPlaceProcDragXY(select_proc
, start_tile
, end_tile
);
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
);
757 case DDSP_REMOVE_STATION
:
758 case DDSP_BUILD_STATION
:
759 if (this->IsWidgetLowered(WID_RAT_BUILD_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
);
765 HandleStationPlacement(start_tile
, end_tile
);
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
);
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 {
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();
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
);
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
),
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
),
901 static WindowDesc
_build_rail_desc(
902 WDP_ALIGN_TOOLBAR
, "toolbar_rail", 0, 0,
903 WC_BUILD_TOOLBAR
, WC_NONE
,
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!
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 {
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();
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;
968 class StationPickerCallbacks
: public PickerCallbacksNewGRFClass
<StationClass
> {
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;
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
;
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
{
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
);
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
);
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
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);
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
);
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();
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
;
1165 void UpdateWidgetSize(WidgetID widget
, Dimension
&size
, [[maybe_unused
]] const Dimension
&padding
, [[maybe_unused
]] Dimension
&fill
, [[maybe_unused
]] Dimension
&resize
) override
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();
1174 case WID_BRAS_COVERAGE_TEXTS
:
1175 size
.height
= this->coverage_height
;
1179 this->PickerWindow::UpdateWidgetSize(widget
, size
, padding
, fill
, resize
);
1184 void DrawWidget(const Rect
&r
, WidgetID widget
) const override
1186 DrawPixelInfo tmp_dpi
;
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);
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);
1218 this->PickerWindow::DrawWidget(r
, widget
);
1223 void OnClick([[maybe_unused
]] Point pt
, WidgetID widget
, [[maybe_unused
]] int click_count
) override
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
);
1233 CloseWindowById(WC_SELECT_STATION
, 0);
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;
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
);
1265 CloseWindowById(WC_SELECT_STATION
, 0);
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;
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
);
1298 CloseWindowById(WC_SELECT_STATION
, 0);
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;
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;
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
);
1332 CloseWindowById(WC_SELECT_STATION
, 0);
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
);
1344 SetViewportCatchmentStation(nullptr, true);
1348 this->PickerWindow::OnClick(pt
, widget
, click_count
);
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
),
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(),
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
),
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
),
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
),
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
),
1421 NWidget(WWT_EMPTY
, INVALID_COLOUR
, WID_BRAS_COVERAGE_TEXTS
), SetFill(1, 1), SetResize(1, 0), SetMinimalTextLines(2, 0),
1425 NWidgetFunction(MakePickerTypeWidgets
),
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
,
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
{
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
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
);
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
++) {
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
1523 case WID_BS_DRAG_SIGNALS_DENSITY_LABEL
:
1524 SetDParam(0, _settings_client
.gui
.drag_signals_density
);
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
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
);
1572 case WID_BS_CONVERT
:
1573 _convert_signal_button
= !_convert_signal_button
;
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
);
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
);
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();
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
),
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(),
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(),
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(),
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(),
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(),
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
),
1685 /** Signal selection window description */
1686 static WindowDesc
_signal_builder_desc(
1687 WDP_AUTO
, nullptr, 0, 0,
1688 WC_BUILD_SIGNAL
, WC_BUILD_TOOLBAR
,
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
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
);
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
),
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
),
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
),
1767 static WindowDesc
_build_depot_desc(
1768 WDP_AUTO
, nullptr, 0, 0,
1769 WC_BUILD_DEPOT
, WC_BUILD_TOOLBAR
,
1771 _nested_build_depot_widgets
1774 static void ShowBuildTrainDepotPicker(Window
*parent
)
1776 new BuildRailDepotWindow(_build_depot_desc
, parent
);
1779 class WaypointPickerCallbacks
: public PickerCallbacksNewGRFClass
<StationClass
> {
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;
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
;
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
),
1872 NWidget(NWID_HORIZONTAL
),
1873 NWidgetFunction(MakePickerClassWidgets
),
1874 NWidgetFunction(MakePickerTypeWidgets
),
1878 static WindowDesc
_build_waypoint_desc(
1879 WDP_AUTO
, "build_waypoint", 0, 0,
1880 WC_BUILD_WAYPOINT
, WC_BUILD_TOOLBAR
,
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
;
1926 switch (_settings_client
.gui
.default_rail_type
) {
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 */
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
;
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
;
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);
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);
2023 avail_railtypes
= c
->avail_railtypes
;
2024 used_railtypes
= GetRailTypes(true);
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
)));
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
)));
2060 /* Empty dropdowns are not allowed */
2061 list
.push_back(MakeDropDownListStringItem(STR_NONE
, INVALID_RAILTYPE
, true));