// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; using FlaxEngine; using FlaxEngine.GUI; namespace FlaxEditor.Windows.Profiler { /// /// Draws simple chart. /// /// internal class SingleChart : Control { internal const float DefaultHeight = TitleHeight + 60; private const float TitleHeight = 20; private const float PointsOffset = 4; private readonly SamplesBuffer _samples; private string _sample; private int _selectedSampleIndex = -1; private bool _isSelecting; /// /// Gets or sets the chart title. /// public string Title { get; set; } /// /// Gets the index of the selected sample. Value -1 is used to indicate no selection (using the latest sample). /// public int SelectedSampleIndex { get => _selectedSampleIndex; set { value = Mathf.Clamp(value, -1, _samples.Count - 1); if (_selectedSampleIndex != value) { _selectedSampleIndex = value; _sample = _samples.Count == 0 ? string.Empty : FormatSample(_samples.Get(_selectedSampleIndex)); SelectedSampleChanged?.Invoke(_selectedSampleIndex); } } } /// /// Gets the selected sample value. /// public float SelectedSample => _samples.Get(_selectedSampleIndex); /// /// Occurs when selected sample gets changed. /// public event Action SelectedSampleChanged; /// /// The handler function to format sample value for label text. /// public Func FormatSample = (v) => v.ToString(); /// /// Initializes a new instance of the class. /// /// The maximum samples to collect. #if USE_PROFILER public SingleChart(int maxSamples = ProfilerMode.MaxSamples) #else public SingleChart(int maxSamples = 600) #endif : base(0, 0, 100, DefaultHeight) { _samples = new SamplesBuffer(maxSamples); _sample = string.Empty; } /// /// Clears all the samples. /// public void Clear() { _samples.Clear(); _sample = string.Empty; } /// /// Adds the sample value. /// /// The value. public void AddSample(float value) { _samples.Add(value); _sample = FormatSample(value); } /// public override void Draw() { base.Draw(); var style = Style.Current; float chartHeight = Height - TitleHeight; // Draw chart if (_samples.Count > 0) { var chartRect = new Rectangle(0, 0, Width, chartHeight); Render2D.PushClip(ref chartRect); if (_selectedSampleIndex != -1) { float selectedX = Width - (_samples.Count - _selectedSampleIndex - 1) * PointsOffset; Render2D.DrawLine(new Float2(selectedX, 0), new Float2(selectedX, chartHeight), style.Foreground, 1.5f); } int samplesInViewCount = Math.Min((int)(Width / PointsOffset), _samples.Count) - 1; float maxValue = _samples[_samples.Count - 1]; for (int i = 2; i < samplesInViewCount; i++) { maxValue = Mathf.Max(maxValue, _samples[_samples.Count - i]); } Color chartColor = style.BackgroundSelected; var chartRoot = chartRect.BottomRight; float samplesRange = maxValue * 1.1f; float samplesCoeff = -chartHeight / samplesRange; var posPrev = chartRoot + new Float2(0, _samples.Last * samplesCoeff); float posX = 0; for (int i = _samples.Count - 1; i >= 0; i--) { float sample = _samples[i]; var pos = chartRoot + new Float2(posX, sample * samplesCoeff); Render2D.DrawLine(posPrev, pos, chartColor); posPrev = pos; posX -= PointsOffset; } Render2D.PopClip(); } // Draw title var headerRect = new Rectangle(0, chartHeight, Width, TitleHeight); var headerTextRect = new Rectangle(2, chartHeight, Width - 4, TitleHeight); Render2D.FillRectangle(headerRect, style.BackgroundNormal); Render2D.DrawText(style.FontMedium, Title, headerTextRect, style.ForegroundGrey, TextAlignment.Near, TextAlignment.Center); Render2D.DrawText(style.FontMedium, _sample, headerTextRect, style.Foreground, TextAlignment.Far, TextAlignment.Center); } private void OnClick(ref Float2 location) { float samplesWidth = _samples.Count * PointsOffset; SelectedSampleIndex = (int)((samplesWidth - Width + location.X) / PointsOffset); } /// public override bool OnMouseDown(Float2 location, MouseButton button) { if (button == MouseButton.Left && location.Y < (Height - TitleHeight)) { _isSelecting = true; OnClick(ref location); StartMouseCapture(); return true; } return base.OnMouseDown(location, button); } /// public override void OnMouseMove(Float2 location) { if (_isSelecting) { OnClick(ref location); } base.OnMouseMove(location); } /// public override bool OnMouseUp(Float2 location, MouseButton button) { if (button == MouseButton.Left && _isSelecting) { _isSelecting = false; EndMouseCapture(); return true; } return base.OnMouseUp(location, button); } /// public override void OnEndMouseCapture() { _isSelecting = false; } } }