Skip to content

Commit 30f7ec8

Browse files
authored
Merge pull request #547 from cesarBLG/etcs-dual-screen
Add support for dual-screen ETCS DMI
2 parents 6a8df6d + 8421185 commit 30f7ec8

File tree

3 files changed

+105
-67
lines changed

3 files changed

+105
-67
lines changed

Source/Documentation/Manual/options.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,14 @@ instead::
720720
Type ( ORTS_ETCS SCREEN_DISPLAY )
721721
Position ( 280 272 320 240 )
722722
Units ( KM_PER_HOUR )
723+
Parameters (
724+
Mode FullSize
725+
)
723726
)
727+
728+
The following DMI size variants are available: FullSize (displays the whole DMI), SpeedArea
729+
(displays only the left part with information about distance and speed) and PlanningArea
730+
(displays only the planning area and navigation buttons).
724731

725732
The information displayed in the DMI is controlled via the TCS script. For more details,
726733
see :ref:`C# engine scripting - Train Control System <features-scripting-tcs>`.

Source/RunActivity/Viewer3D/RollingStock/SubSystems/ETCS/DriverMachineInterface.cs

Lines changed: 96 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
// This file is the responsibility of the 3D & Environment Team.
1919

20+
using System;
21+
using System.Collections.Generic;
22+
using System.Linq;
2023
using Microsoft.Xna.Framework;
2124
using Microsoft.Xna.Framework.Graphics;
2225
using Orts.Formats.Msts;
@@ -25,17 +28,13 @@
2528
using Orts.Viewer3D.RollingStock.SubSystems.ETCS;
2629
using ORTS.Common;
2730
using ORTS.Scripting.Api.ETCS;
28-
using System;
29-
using System.Collections.Generic;
30-
using System.Linq;
3131
using static Orts.Viewer3D.RollingStock.Subsystems.ETCS.DriverMachineInterface;
3232

3333
namespace Orts.Viewer3D.RollingStock.Subsystems.ETCS
3434
{
3535
public class DriverMachineInterface
3636
{
3737
public readonly MSTSLocomotive Locomotive;
38-
public readonly bool GaugeOnly;
3938
public readonly Viewer Viewer;
4039
public IList<DMIWindow> Windows = new List<DMIWindow>();
4140
float PrevScale = 1;
@@ -44,8 +43,8 @@ public class DriverMachineInterface
4443
bool Active;
4544
public float Scale { get; private set; }
4645
public float MipMapScale { get; private set; }
47-
readonly int Height = 480;
48-
readonly int Width = 640;
46+
public readonly int Height;
47+
public readonly int Width;
4948

5049
public readonly ETCSDefaultWindow ETCSDefaultWindow;
5150

@@ -70,6 +69,16 @@ public class DriverMachineInterface
7069

7170
public bool Blinker2Hz { get; private set; }
7271
public bool Blinker4Hz { get; private set; }
72+
73+
public enum DMIMode
74+
{
75+
FullSize,
76+
SpeedArea,
77+
PlanningArea,
78+
GaugeOnly
79+
}
80+
public DMIMode CurrentDMIMode;
81+
7382
float BlinkerTime;
7483

7584
public float CurrentTime => (float)Viewer.Simulator.ClockTime;
@@ -88,12 +97,40 @@ public class DriverMachineInterface
8897
DMIButton ActiveButton;
8998
public DriverMachineInterface(float height, float width, MSTSLocomotive locomotive, Viewer viewer, CabViewControl control)
9099
{
100+
if (control is CVCScreen)
101+
{
102+
CurrentDMIMode = DMIMode.FullSize;
103+
if ((control as CVCScreen).CustomParameters.TryGetValue("mode", out string mode))
104+
{
105+
if (mode == "planningarea") CurrentDMIMode = DMIMode.PlanningArea;
106+
else if (mode == "speedarea") CurrentDMIMode = DMIMode.SpeedArea;
107+
}
108+
}
109+
else
110+
{
111+
CurrentDMIMode = DMIMode.GaugeOnly;
112+
}
113+
switch(CurrentDMIMode)
114+
{
115+
case DMIMode.GaugeOnly:
116+
Width = 280;
117+
Height = 300;
118+
break;
119+
case DMIMode.FullSize:
120+
Width = 640;
121+
Height = 480;
122+
break;
123+
case DMIMode.PlanningArea:
124+
case DMIMode.SpeedArea:
125+
Width = 334;
126+
Height = 480;
127+
break;
128+
}
91129
Viewer = viewer;
92130
Locomotive = locomotive;
93131
Scale = Math.Min(width / Width, height / Height);
94132
if (Scale < 0.5) MipMapScale = 2;
95133
else MipMapScale = 1;
96-
GaugeOnly = control is CVCDigital;
97134

98135
Shader = new DriverMachineInterfaceShader(Viewer.GraphicsDevice);
99136
ETCSDefaultWindow = new ETCSDefaultWindow(this, control);
@@ -259,20 +296,22 @@ public class ETCSDefaultWindow : DMIWindow
259296
TargetDistance TargetDistance;
260297
TTIandLSSMArea TTIandLSSMArea;
261298
MenuBar MenuBar;
262-
public ETCSDefaultWindow(DriverMachineInterface dmi, CabViewControl control) : base(dmi, 640, 480)
299+
public ETCSDefaultWindow(DriverMachineInterface dmi, CabViewControl control) : base(dmi, dmi.Width, dmi.Height)
263300
{
264-
if (control is CVCDigital)
301+
if (dmi.CurrentDMIMode == DMIMode.GaugeOnly)
265302
{
266303
var dig = control as CVCDigital;
267304
CircularSpeedGauge = new CircularSpeedGauge(
268-
(int)dig.MaxValue,
269-
dig.Units != CABViewControlUnits.MILES_PER_HOUR,
270-
dig.Units != CABViewControlUnits.NONE,
271-
dig.MaxValue == 240 || dig.MaxValue == 260,
272-
(int)dig.MinValue,
273-
DMI);
305+
(int)dig.MaxValue,
306+
dig.Units != CABViewControlUnits.MILES_PER_HOUR,
307+
dig.Units != CABViewControlUnits.NONE,
308+
dig.MaxValue == 240 || dig.MaxValue == 260,
309+
(int)dig.MinValue,
310+
DMI);
311+
AddToLayout(CircularSpeedGauge, new Point(0, 0));
312+
return;
274313
}
275-
else
314+
if (dmi.CurrentDMIMode != DMIMode.PlanningArea)
276315
{
277316
var param = (control as CVCScreen).CustomParameters;
278317
int maxSpeed = 400;
@@ -286,34 +325,37 @@ public ETCSDefaultWindow(DriverMachineInterface dmi, CabViewControl control) : b
286325
maxSpeed == 240 || maxSpeed == 260,
287326
maxVisibleSpeed,
288327
dmi
289-
);
290-
}
291-
if (DMI.GaugeOnly)
292-
{
293-
AddToLayout(CircularSpeedGauge, new Point(0, 0));
294-
return;
295-
}
296-
PlanningWindow = new PlanningWindow(dmi);
297-
TTIandLSSMArea = new TTIandLSSMArea(dmi);
298-
TargetDistance = new TargetDistance(dmi);
299-
MessageArea = new MessageArea(dmi);
300-
MenuBar = new MenuBar(dmi);
301-
CircularSpeedGauge.Layer = -1;
302-
TargetDistance.Layer = -1;
303-
TTIandLSSMArea.Layer = -1;
304-
MessageArea.Layer = -1;
305-
AddToLayout(CircularSpeedGauge, new Point(54, DMI.IsSoftLayout ? 0 : 15));
306-
AddToLayout(PlanningWindow, new Point(334, DMI.IsSoftLayout ? 0 : 15));
307-
AddToLayout(PlanningWindow.ButtonScaleDown, new Point(334, DMI.IsSoftLayout ? 0 : 15));
308-
AddToLayout(PlanningWindow.ButtonScaleUp, new Point(334, 285 + (DMI.IsSoftLayout ? 0 : 15)));
309-
AddToLayout(TTIandLSSMArea, new Point(0, DMI.IsSoftLayout ? 0 : 15));
310-
AddToLayout(TargetDistance, new Point(0, 54 + (DMI.IsSoftLayout ? 0 : 15)));
311-
AddToLayout(MessageArea, new Point(54, DMI.IsSoftLayout ? 350 : 365));
312-
AddToLayout(MessageArea.ButtonScrollUp, new Point(54+234, DMI.IsSoftLayout ? 350 : 365));
313-
AddToLayout(MessageArea.ButtonScrollDown, new Point(54+234, MessageArea.Height / 2 + (DMI.IsSoftLayout ? 350 : 365)));
314-
foreach (int i in Enumerable.Range(0, MenuBar.Buttons.Count))
315-
{
316-
AddToLayout(MenuBar.Buttons[i], new Point(580, 15 + 50*i));
328+
);
329+
TTIandLSSMArea = new TTIandLSSMArea(dmi);
330+
TargetDistance = new TargetDistance(dmi);
331+
MessageArea = new MessageArea(dmi);
332+
CircularSpeedGauge.Layer = -1;
333+
TargetDistance.Layer = -1;
334+
TTIandLSSMArea.Layer = -1;
335+
MessageArea.Layer = -1;
336+
AddToLayout(CircularSpeedGauge, new Point(54, DMI.IsSoftLayout ? 0 : 15));
337+
AddToLayout(TTIandLSSMArea, new Point(0, DMI.IsSoftLayout ? 0 : 15));
338+
AddToLayout(TargetDistance, new Point(0, 54 + (DMI.IsSoftLayout ? 0 : 15)));
339+
AddToLayout(MessageArea, new Point(54, DMI.IsSoftLayout ? 350 : 365));
340+
AddToLayout(MessageArea.ButtonScrollUp, new Point(54 + 234, DMI.IsSoftLayout ? 350 : 365));
341+
AddToLayout(MessageArea.ButtonScrollDown, new Point(54 + 234, MessageArea.Height / 2 + (DMI.IsSoftLayout ? 350 : 365)));
342+
}
343+
if (dmi.CurrentDMIMode != DMIMode.SpeedArea)
344+
{
345+
// Calculate start position of the planning area when a two-screen display is used
346+
// Real width of the left area in ETCS specs is 306 px, however in order to have
347+
// both screens with the same size I assumed both have 334 px
348+
// To be checked
349+
int startPos = dmi.CurrentDMIMode == DMIMode.FullSize ? 334 : (334-306)/2;
350+
PlanningWindow = new PlanningWindow(dmi);
351+
MenuBar = new MenuBar(dmi);
352+
AddToLayout(PlanningWindow, new Point(startPos, DMI.IsSoftLayout ? 0 : 15));
353+
AddToLayout(PlanningWindow.ButtonScaleDown, new Point(startPos, DMI.IsSoftLayout ? 0 : 15));
354+
AddToLayout(PlanningWindow.ButtonScaleUp, new Point(startPos, 285 + (DMI.IsSoftLayout ? 0 : 15)));
355+
foreach (int i in Enumerable.Range(0, MenuBar.Buttons.Count))
356+
{
357+
AddToLayout(MenuBar.Buttons[i], new Point(580, 15 + 50 * i));
358+
}
317359
}
318360
}
319361
}
@@ -324,8 +366,8 @@ public class DMIArea
324366
public readonly DriverMachineInterface DMI;
325367
protected Texture2D ColorTexture => DMI.ColorTexture;
326368
public float Scale => DMI.Scale;
327-
public int Height;
328-
public int Width;
369+
public readonly int Height;
370+
public readonly int Width;
329371
protected List<RectanglePrimitive> Rectangles = new List<RectanglePrimitive>();
330372
protected List<TextPrimitive> Texts = new List<TextPrimitive>();
331373
protected List<TexturePrimitive> Textures = new List<TexturePrimitive>();
@@ -574,13 +616,6 @@ public class DMIButton : DMIArea
574616
public bool ShowButtonBorder;
575617
public float FirstPressed;
576618
public float LastPressed;
577-
public DMIButton(string displayName, bool upType, DriverMachineInterface dmi, bool showButtonBorder) : base(dmi)
578-
{
579-
DisplayName = displayName;
580-
Enabled = false;
581-
UpType = upType;
582-
ShowButtonBorder = showButtonBorder;
583-
}
584619
public DMIButton(string displayName, bool upType, Action pressedAction, int width, int height, DriverMachineInterface dmi, bool showButtonBorder=false) : base(dmi, width, height)
585620
{
586621
DisplayName = displayName;
@@ -725,13 +760,11 @@ public CircularSpeedGaugeRenderer(Viewer viewer, MSTSLocomotive locomotive, CVCD
725760
: base(viewer, locomotive, control, shader)
726761
{
727762
// Height is adjusted to keep compatibility
728-
DMI = new DriverMachineInterface((int)(Control.Width * 640 / 280), (int)(Control.Height * 480 / 300), locomotive, viewer, control);
763+
DMI = new DriverMachineInterface((int)Control.Width, (int)Control.Height, locomotive, viewer, control);
729764
}
730765
public override void PrepareFrame(RenderFrame frame, ElapsedTime elapsedTime)
731766
{
732767
base.PrepareFrame(frame, elapsedTime);
733-
DrawPosition.Width = DrawPosition.Width * 640 / 280;
734-
DrawPosition.Height = DrawPosition.Height * 480 / 300;
735768
DMI.SizeTo(DrawPosition.Width, DrawPosition.Height);
736769
DMI.ETCSDefaultWindow.BackgroundColor = Color.Transparent;
737770
DMI.PrepareFrame(elapsedTime.ClockSeconds);
@@ -769,19 +802,19 @@ public override void PrepareFrame(RenderFrame frame, ElapsedTime elapsedTime)
769802
return;
770803

771804
base.PrepareFrame(frame, elapsedTime);
772-
var xScale = Viewer.CabWidthPixels / 640f;
773-
var yScale = Viewer.CabHeightPixels / 480f;
805+
var xScale = (float)Viewer.CabWidthPixels / 640;
806+
var yScale = (float)Viewer.CabHeightPixels / 480;
774807
DrawPosition.X = (int)(Position.X * xScale) - Viewer.CabXOffsetPixels + Viewer.CabXLetterboxPixels;
775808
DrawPosition.Y = (int)(Position.Y * yScale) + Viewer.CabYOffsetPixels + Viewer.CabYLetterboxPixels;
776809
DrawPosition.Width = (int)(Control.Width * xScale);
777810
DrawPosition.Height = (int)(Control.Height * yScale);
778811
if (Zoomed)
779812
{
780-
DrawPosition.Width = 640;
781-
DrawPosition.Height = 480;
813+
DrawPosition.Width = DMI.Width;
814+
DrawPosition.Height = DMI.Height;
782815
DMI.SizeTo(DrawPosition.Width, DrawPosition.Height);
783-
DrawPosition.X -= 320;
784-
DrawPosition.Y -= 240;
816+
DrawPosition.X -= DMI.Width/2;
817+
DrawPosition.Y -= DMI.Height/2;
785818
DMI.ETCSDefaultWindow.BackgroundColor = ColorBackground;
786819
}
787820
else
@@ -796,7 +829,7 @@ public bool IsMouseWithin()
796829
{
797830
int x = (int)((UserInput.MouseX - DrawPosition.X) / DMI.Scale);
798831
int y = (int)((UserInput.MouseY - DrawPosition.Y) / DMI.Scale);
799-
if (UserInput.IsMouseRightButtonPressed && new Rectangle(0, 0, 640, 480).Contains(x, y)) Zoomed = !Zoomed;
832+
if (UserInput.IsMouseRightButtonPressed && new Rectangle(0, 0, DMI.Width, DMI.Height).Contains(x, y)) Zoomed = !Zoomed;
800833
foreach (var area in DMI.ActiveWindow.SubAreas)
801834
{
802835
if (!(area is DMIButton)) continue;

Source/RunActivity/Viewer3D/RollingStock/SubSystems/ETCS/TextMessages.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,16 @@ public class MessageArea : DMIButton
4747

4848
readonly int MaxTextLines;
4949

50-
readonly int RowHeight = 20;
50+
const int RowHeight = 20;
5151

5252
readonly TextPrimitive[] DisplayedTexts;
5353
readonly TextPrimitive[] DisplayedTimes;
5454

5555
List<TextMessage> MessageList;
5656
TextMessage? AcknowledgingMessage;
57-
public MessageArea(DriverMachineInterface dmi) : base(Viewer.Catalog.GetString("Acknowledge"), true, dmi, false)
57+
public MessageArea(DriverMachineInterface dmi) : base(Viewer.Catalog.GetString("Acknowledge"), true, null, 234, (dmi.IsSoftLayout ? 4 : 5)*RowHeight, dmi, false)
5858
{
5959
MaxTextLines = dmi.IsSoftLayout ? 4 : 5;
60-
Height = MaxTextLines * RowHeight;
61-
Width = 234;
6260

6361
DisplayedTexts = new TextPrimitive[MaxTextLines];
6462
DisplayedTimes = new TextPrimitive[MaxTextLines];

0 commit comments

Comments
 (0)