using SFML.Graphics; using SFML.System; using Car_simulation.Core.Models; using Car_simulation.UI.Instruments; namespace Car_simulation.UI { public class DisplayManager { private InstrumentPanel _instrumentPanel; private RenderWindow _window; private Font _font; private Color _backgroundColor = new Color(20, 20, 30); private Text _fpsText; private Clock _fpsClock; private int _frameCount = 0; private float _fpsUpdateInterval = 0.5f; private float _fpsAccumulator = 0f; // Debug information private bool _showDebugInfo = false; private RectangleShape _debugPanel; private List _debugTexts; // Performance tracking private Queue _frameTimes = new Queue(); private const int FRAME_TIME_HISTORY = 60; private Text _performanceText; public DisplayManager(RenderWindow window, Font font) { _window = window; _font = font; Initialize(); } private void Initialize() { _instrumentPanel = new InstrumentPanel(_font, new Vector2f(750, 50), new Vector2f(750, 275)); // FPS counter _fpsText = new Text("FPS: 0", _font, 12); _fpsText.FillColor = new Color(180, 180, 180); _fpsText.Position = new Vector2f(10, _window.Size.Y - 25); _fpsClock = new Clock(); // Performance text _performanceText = new Text("", _font, 12); _performanceText.FillColor = new Color(180, 180, 220); _performanceText.Position = new Vector2f(_window.Size.X - 200, _window.Size.Y - 25); // Debug panel InitializeDebugPanel(); } private void InitializeDebugPanel() { _debugPanel = new RectangleShape(new Vector2f(300, 200)); _debugPanel.Position = new Vector2f(_window.Size.X - 320, 20); _debugPanel.FillColor = new Color(0, 0, 0, 200); _debugPanel.OutlineThickness = 1; _debugPanel.OutlineColor = Color.Cyan; _debugTexts = new List(); for (int i = 0; i < 10; i++) { Text text = new Text("", _font, 12); text.FillColor = Color.Cyan; text.Position = new Vector2f(_window.Size.X - 310, 30 + i * 18); _debugTexts.Add(text); } } public void Update(Car car, float deltaTime, float totalTime, long updateCount) { UpdateFPS(deltaTime); UpdatePerformanceInfo(deltaTime); _instrumentPanel.Update(car); if (_showDebugInfo) { UpdateDebugInfo(car, deltaTime, totalTime, updateCount); } } private void UpdateFPS(float deltaTime) { _frameCount++; _fpsAccumulator += deltaTime; if (_fpsAccumulator >= _fpsUpdateInterval) { float fps = _frameCount / _fpsAccumulator; _fpsText.DisplayedString = $"FPS: {fps:F1}"; _frameCount = 0; _fpsAccumulator = 0f; } } private void UpdatePerformanceInfo(float deltaTime) { // Track frame times for smoothing _frameTimes.Enqueue(deltaTime * 1000); // Convert to milliseconds if (_frameTimes.Count > FRAME_TIME_HISTORY) { _frameTimes.Dequeue(); } // Calculate average frame time float avgFrameTime = 0; foreach (var time in _frameTimes) { avgFrameTime += time; } avgFrameTime /= _frameTimes.Count; _performanceText.DisplayedString = $"Frame: {deltaTime * 1000:F1}ms (Avg: {avgFrameTime:F1}ms)"; } private void UpdateDebugInfo(Car car, float deltaTime, float totalTime, long updateCount) { int line = 0; if (line < _debugTexts.Count) _debugTexts[line++].DisplayedString = $"Time: {totalTime:F2}s"; if (line < _debugTexts.Count) _debugTexts[line++].DisplayedString = $"Updates: {updateCount}"; if (line < _debugTexts.Count) _debugTexts[line++].DisplayedString = $"Delta: {deltaTime:F4}s"; if (line < _debugTexts.Count) _debugTexts[line++].DisplayedString = $"Position: ({car.Position.X:F1}, {car.Position.Y:F1})"; if (line < _debugTexts.Count) _debugTexts[line++].DisplayedString = $"Velocity: {car.Velocity.Length:F2} m/s"; if (line < _debugTexts.Count) _debugTexts[line++].DisplayedString = $"Engine Omega: {car.Engine.AngularVelocity:F2} rad/s"; if (line < _debugTexts.Count) _debugTexts[line++].DisplayedString = $"Wheel Omega: {car.WheelSystem.AngularVelocity:F2} rad/s"; if (line < _debugTexts.Count) _debugTexts[line++].DisplayedString = $"Clutch Eng: {car.Drivetrain.ClutchEngagement:P0}"; if (line < _debugTexts.Count) _debugTexts[line++].DisplayedString = $"Force Clutch: {car.ForceClutch}"; } public void Draw() { _window.Clear(_backgroundColor); _instrumentPanel.Draw(_window); // Draw FPS counter _window.Draw(_fpsText); // Draw performance info _window.Draw(_performanceText); // Draw debug panel if enabled if (_showDebugInfo) { _window.Draw(_debugPanel); foreach (var text in _debugTexts) { if (!string.IsNullOrEmpty(text.DisplayedString)) _window.Draw(text); } } // Draw version info DrawVersionInfo(); } private void DrawVersionInfo() { Text versionText = new Text("Car Simulation v1.0", _font, 10); versionText.FillColor = new Color(100, 100, 100); versionText.Position = new Vector2f(_window.Size.X - 120, _window.Size.Y - 15); _window.Draw(versionText); } public void ToggleDebugInfo() { _showDebugInfo = !_showDebugInfo; } public bool ShowDebugInfo => _showDebugInfo; public void HandleResize(uint width, uint height) { // Update FPS text position _fpsText.Position = new Vector2f(10, height - 25); // Update performance text position _performanceText.Position = new Vector2f(width - 200, height - 25); // Update debug panel position _debugPanel.Position = new Vector2f(width - 320, 20); // Update debug text positions for (int i = 0; i < _debugTexts.Count; i++) { _debugTexts[i].Position = new Vector2f(width - 310, 30 + i * 18); } // You might want to reposition the instrument panel too // For now, the instrument panel is fixed, but you could add resize handling } public void DrawCustomMessage(string message, Vector2f position, uint fontSize, Color color) { Text customText = new Text(message, _font, fontSize); customText.FillColor = color; customText.Position = position; _window.Draw(customText); } public void DrawProgressBar(Vector2f position, Vector2f size, float progress, Color fillColor, Color bgColor) { // Draw background RectangleShape background = new RectangleShape(size); background.Position = position; background.FillColor = bgColor; background.OutlineThickness = 1; background.OutlineColor = Color.White; _window.Draw(background); // Draw fill if (progress > 0) { RectangleShape fill = new RectangleShape(new Vector2f(size.X * Math.Clamp(progress, 0, 1), size.Y)); fill.Position = position; fill.FillColor = fillColor; _window.Draw(fill); } // Draw percentage text Text progressText = new Text($"{progress:P0}", _font, 12); progressText.FillColor = Color.White; progressText.Position = new Vector2f( position.X + size.X / 2 - progressText.GetLocalBounds().Width / 2, position.Y + size.Y / 2 - progressText.CharacterSize / 2 ); _window.Draw(progressText); } public void DrawGraph(string title, List data, Vector2f position, Vector2f size, Color lineColor) { if (data.Count < 2) return; // Draw graph background RectangleShape graphBackground = new RectangleShape(size); graphBackground.Position = position; graphBackground.FillColor = new Color(0, 0, 0, 128); graphBackground.OutlineThickness = 1; graphBackground.OutlineColor = Color.White; _window.Draw(graphBackground); // Draw title Text titleText = new Text(title, _font, 12); titleText.FillColor = Color.White; titleText.Position = new Vector2f(position.X + 5, position.Y + 5); _window.Draw(titleText); // Find min and max values float minValue = data.Min(); float maxValue = data.Max(); float valueRange = Math.Max(maxValue - minValue, 0.001f); // Draw data points as line VertexArray line = new VertexArray(PrimitiveType.LineStrip); for (int i = 0; i < data.Count; i++) { float x = position.X + (i / (float)(data.Count - 1)) * size.X; float normalizedValue = (data[i] - minValue) / valueRange; float y = position.Y + size.Y - normalizedValue * size.Y; line.Append(new Vertex(new Vector2f(x, y), lineColor)); } _window.Draw(line); // Draw min/max labels Text minText = new Text($"{minValue:F0}", _font, 10); minText.FillColor = Color.White; minText.Position = new Vector2f(position.X + size.X - 30, position.Y + size.Y - 15); _window.Draw(minText); Text maxText = new Text($"{maxValue:F0}", _font, 10); maxText.FillColor = Color.White; maxText.Position = new Vector2f(position.X + size.X - 30, position.Y + 5); _window.Draw(maxText); } } }