diff --git a/Source/ORTS.Common/Input/UserCommand.cs b/Source/ORTS.Common/Input/UserCommand.cs
index 1fcdf5ce03..054b1e9cea 100644
--- a/Source/ORTS.Common/Input/UserCommand.cs
+++ b/Source/ORTS.Common/Input/UserCommand.cs
@@ -1,4 +1,22 @@
-namespace ORTS.Common.Input
+// COPYRIGHT 2024 by the Open Rails project.
+//
+// This file is part of Open Rails.
+//
+// Open Rails 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, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Open Rails is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Open Rails. If not, see .
+//
+
+namespace ORTS.Common.Input
{
///
/// Specifies game commands.
@@ -50,6 +68,7 @@ public enum UserCommand
[GetString("Display Compass Window")] DisplayCompassWindow,
[GetString("Display Train List Window")] DisplayTrainListWindow,
[GetString("Display EOT List Window")] DisplayEOTListWindow,
+ [GetString("Display Control Rectangles")] DisplayControlRectangle,
[GetString("Debug Speed Up")] DebugSpeedUp,
[GetString("Debug Speed Down")] DebugSpeedDown,
diff --git a/Source/ORTS.Settings/InputSettings.cs b/Source/ORTS.Settings/InputSettings.cs
index d05329b395..d05164c28c 100644
--- a/Source/ORTS.Settings/InputSettings.cs
+++ b/Source/ORTS.Settings/InputSettings.cs
@@ -516,6 +516,7 @@ static void InitializeCommands(UserCommandInput[] Commands)
Commands[(int)UserCommand.DisplayTrainOperationsWindow] = new UserCommandKeyInput(0x43);
Commands[(int)UserCommand.DisplayTrainDpuWindow] = new UserCommandKeyInput(0x43, KeyModifiers.Shift);
Commands[(int)UserCommand.DisplayEOTListWindow] = new UserCommandKeyInput(0x43, KeyModifiers.Control);
+ Commands[(int)UserCommand.DisplayControlRectangle] = new UserCommandKeyInput(0x3F, KeyModifiers.Control);
Commands[(int)UserCommand.GameAutopilotMode] = new UserCommandKeyInput(0x1E, KeyModifiers.Alt);
Commands[(int)UserCommand.GameChangeCab] = new UserCommandKeyInput(0x12, KeyModifiers.Control);
diff --git a/Source/RunActivity/Viewer3D/Popups/ControlRectangle.cs b/Source/RunActivity/Viewer3D/Popups/ControlRectangle.cs
new file mode 100644
index 0000000000..3fcf57632d
--- /dev/null
+++ b/Source/RunActivity/Viewer3D/Popups/ControlRectangle.cs
@@ -0,0 +1,94 @@
+// COPYRIGHT 2024 by the Open Rails project.
+//
+// This file is part of Open Rails.
+//
+// Open Rails 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, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Open Rails is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Open Rails. If not, see .
+
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Orts.Viewer3D.RollingStock;
+
+namespace Orts.Viewer3D.Popups
+{
+ public class ControlRectangle : Window
+ {
+ private readonly Texture2D Line;
+ private readonly int Thickness = 3;
+ private readonly Color Color = Color.Yellow;
+ private readonly Viewer Viewer;
+
+ public ControlRectangle(WindowManager owner, Viewer viewer) : base(owner)
+ {
+ Line = new Texture2D(Owner.Viewer.GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
+ Line.SetData(new[] { Color });
+ Viewer = viewer;
+ }
+
+ public override void Draw(SpriteBatch spriteBatch)
+ {
+ if (Viewer.Camera is CabCamera && (Viewer.PlayerLocomotiveViewer as MSTSLocomotiveViewer)._hasCabRenderer)
+ {
+ var cabRenderer = (Viewer.PlayerLocomotiveViewer as MSTSLocomotiveViewer)._CabRenderer;
+ foreach (var controlRenderer in cabRenderer.ControlMap.Values)
+ {
+ if ((Viewer.Camera as CabCamera).SideLocation == controlRenderer.Control.CabViewpoint && controlRenderer is ICabViewMouseControlRenderer mouseRenderer)
+ {
+ if (mouseRenderer.isMouseControl())
+ {
+ Rectangle rectangle = mouseRenderer.DestinationRectangleGet();
+
+ int width = rectangle.Width;
+ int height = rectangle.Height;
+
+ if (width > 0)
+ {
+ // do not know why rectangles with width and height = 0 are there
+
+ // top line
+ DrawLine(spriteBatch, rectangle.X, rectangle.Y, width, Thickness, 0);
+
+ // bottom line
+ DrawLine(spriteBatch, rectangle.X, rectangle.Y + height - Thickness, width, Thickness, 0);
+
+ // left line
+ DrawLine(spriteBatch, rectangle.X + Thickness, rectangle.Y, height, Thickness, 90);
+
+ // right line
+ DrawLine(spriteBatch, rectangle.X + width, rectangle.Y, height, Thickness, 90);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void DrawLine(SpriteBatch spriteBatch, int X, int Y, int width, int height, int degrees)
+ {
+ spriteBatch.Draw(
+ Line,
+ new Rectangle(X, Y, width, height),
+ null,
+ Color,
+ ConvertToRadiansFromDegrees(degrees),
+ new Vector2(0, 0),
+ SpriteEffects.None, 0);
+ }
+
+ private float ConvertToRadiansFromDegrees(int angle)
+ {
+ return (float)((System.Math.PI / 180) * angle);
+ }
+ }
+}
diff --git a/Source/RunActivity/Viewer3D/RollingStock/MSTSLocomotiveViewer.cs b/Source/RunActivity/Viewer3D/RollingStock/MSTSLocomotiveViewer.cs
index 73a4c704ff..7ed93dc022 100644
--- a/Source/RunActivity/Viewer3D/RollingStock/MSTSLocomotiveViewer.cs
+++ b/Source/RunActivity/Viewer3D/RollingStock/MSTSLocomotiveViewer.cs
@@ -1699,6 +1699,8 @@ public interface ICabViewMouseControlRenderer
void HandleUserInput();
string GetControlName();
string ControlLabel { get; }
+ Rectangle DestinationRectangleGet();
+ bool isMouseControl();
}
///
@@ -2033,11 +2035,32 @@ public CabViewDiscreteRenderer(Viewer viewer, MSTSLocomotive locomotive, CVCWith
ChangedValue = (value) =>
{
IntermediateValue %= 0.5f;
+ if (UserInput.IsMouseLeftButtonDown)
+ {
IntermediateValue += NormalizedMouseMovement();
+ }
+ else
+ {
+ // mousewheel
+ IntermediateValue += (float)UserInput.MouseWheelChange / 700;
+ }
return IntermediateValue > 0.5f ? 1 : IntermediateValue < -0.5f ? -1 : 0;
};
break;
- default: ChangedValue = (value) => value + NormalizedMouseMovement(); break;
+ default:
+ ChangedValue = (value) =>
+ {
+ if (UserInput.IsMouseLeftButtonDown)
+ {
+ return value + NormalizedMouseMovement();
+ }
+ else
+ {
+ // mousewheel
+ return value + (float)UserInput.MouseWheelChange / 1500;
+ }
+ };
+ break;
}
}
@@ -2341,11 +2364,22 @@ public virtual int GetDrawIndex()
///
float NormalizedMouseMovement()
{
+ if (UserInput.IsMouseLeftButtonDown)
+ {
return (ControlDiscrete.Orientation > 0
? (float)UserInput.MouseMoveY / (float)Control.Height
: (float)UserInput.MouseMoveX / (float)Control.Width)
* (ControlDiscrete.Direction > 0 ? -1 : 1);
}
+ else
+ {
+ // mousewheel
+ return (ControlDiscrete.Orientation > 0
+ ? (float)UserInput.MouseWheelChange / (float)Control.Height
+ : (float)UserInput.MouseWheelChange / (float)Control.Width)
+ * (ControlDiscrete.Direction > 0 ? -1 : 1);
+ }
+ }
public bool IsMouseWithin()
{
@@ -2851,6 +2885,16 @@ protected int PercentToIndex(float percent)
return index;
}
+
+ public Rectangle DestinationRectangleGet()
+ {
+ return DestinationRectangle;
+ }
+
+ public bool isMouseControl()
+ {
+ return ControlDiscrete.MouseControl;
+ }
}
///
diff --git a/Source/RunActivity/Viewer3D/RollingStock/SubSystems/ETCS/DriverMachineInterface.cs b/Source/RunActivity/Viewer3D/RollingStock/SubSystems/ETCS/DriverMachineInterface.cs
index 96640d4f63..9f561f9092 100644
--- a/Source/RunActivity/Viewer3D/RollingStock/SubSystems/ETCS/DriverMachineInterface.cs
+++ b/Source/RunActivity/Viewer3D/RollingStock/SubSystems/ETCS/DriverMachineInterface.cs
@@ -863,5 +863,14 @@ public override void Draw(GraphicsDevice graphicsDevice)
ControlView.SpriteBatch.End();
ControlView.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, DepthStencilState.Default, null, Shader);
}
+
+ public Rectangle DestinationRectangleGet()
+ {
+ return DrawPosition;
+ }
+ public bool isMouseControl()
+ {
+ return true;
+ }
}
}
diff --git a/Source/RunActivity/Viewer3D/Viewer.cs b/Source/RunActivity/Viewer3D/Viewer.cs
index 4822e5af50..1e9f9797d4 100644
--- a/Source/RunActivity/Viewer3D/Viewer.cs
+++ b/Source/RunActivity/Viewer3D/Viewer.cs
@@ -107,6 +107,8 @@ public class Viewer
public TrainListWindow TrainListWindow { get; private set; } // for switching driven train
public TTDetachWindow TTDetachWindow { get; private set; } // for detaching player train in timetable mode
public EOTListWindow EOTListWindow { get; private set; } // to select EOT
+ public ControlRectangle ControlRectangle { get; private set; } // to display the control rectangles
+
private OutOfFocusWindow OutOfFocusWindow; // to show colored rectangle around the main window when not in focus
public EditorShapes EditorShapes { get; set; }
@@ -526,6 +528,7 @@ internal void Initialize()
TrainListWindow = new TrainListWindow(WindowManager);
TTDetachWindow = new TTDetachWindow(WindowManager);
EOTListWindow = new EOTListWindow(WindowManager);
+ ControlRectangle = new ControlRectangle(WindowManager, this);
if (Settings.SuppressConfirmations < (int)ConfirmLevel.Error)
// confirm level Error might be set to suppressed when taking a movie
// do not show the out of focus red square in that case
@@ -1043,6 +1046,7 @@ void HandleUserInput(ElapsedTime elapsedTime)
if (UserInput.IsPressed(UserCommand.DebugSignalling)) if (UserInput.IsDown(UserCommand.DisplayNextWindowTab)) SignallingDebugWindow.TabAction(); else SignallingDebugWindow.Visible = !SignallingDebugWindow.Visible;
if (UserInput.IsPressed(UserCommand.DisplayTrainListWindow)) TrainListWindow.Visible = !TrainListWindow.Visible;
if (UserInput.IsPressed(UserCommand.DisplayEOTListWindow)) EOTListWindow.Visible = !EOTListWindow.Visible;
+ if (UserInput.IsPressed(UserCommand.DisplayControlRectangle)) ControlRectangle.Visible = !ControlRectangle.Visible;
if (UserInput.IsPressed(UserCommand.GameChangeCab))
@@ -1408,12 +1412,14 @@ void HandleUserInput(ElapsedTime elapsedTime)
if (Camera is CabCamera && (PlayerLocomotiveViewer as MSTSLocomotiveViewer)._hasCabRenderer)
{
- if (UserInput.IsMouseLeftButtonPressed)
+ if (UserInput.IsMouseLeftButtonPressed || UserInput.IsMouseWheelChanged)
{
var cabRenderer = (PlayerLocomotiveViewer as MSTSLocomotiveViewer)._CabRenderer;
foreach (var controlRenderer in cabRenderer.ControlMap.Values)
{
- if ((Camera as CabCamera).SideLocation == controlRenderer.Control.CabViewpoint && controlRenderer is ICabViewMouseControlRenderer mouseRenderer && mouseRenderer.IsMouseWithin())
+ if ((Camera as CabCamera).SideLocation == controlRenderer.Control.CabViewpoint && controlRenderer is ICabViewMouseControlRenderer mouseRenderer)
+ {
+ if (mouseRenderer.IsMouseWithin())
{
if ((controlRenderer.Control.Screens == null || controlRenderer.Control.Screens[0] == "all"))
{
@@ -1435,11 +1441,12 @@ void HandleUserInput(ElapsedTime elapsedTime)
}
}
}
+ }
if (MouseChangingControl != null)
{
MouseChangingControl.HandleUserInput();
- if (UserInput.IsMouseLeftButtonReleased)
+ if (UserInput.IsMouseLeftButtonReleased || UserInput.IsMouseWheelChanged)
{
MouseChangingControl = null;
UserInput.Handled();