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();