diff --git a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs index 3615fbfbc..8550a9b10 100644 --- a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs +++ b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs @@ -241,7 +241,16 @@ public class ColorGradient : IList, IList, INotifyCollectionC return _stops[^1].Color; //find the first stop after the position - int stop2Index = _stops.FindIndex(s => s.Position >= position); + int stop2Index = 0; + + for (int i = 0; i < _stops.Count; i++) + { + if (_stops[i].Position >= position) + { + stop2Index = i; + break; + } + } //if the position is before the first stop, return that color if (stop2Index == 0) return _stops[0].Color; diff --git a/src/Artemis.UI.Shared/Services/Window/WindowService.cs b/src/Artemis.UI.Shared/Services/Window/WindowService.cs index 5678ed75b..c30688eef 100644 --- a/src/Artemis.UI.Shared/Services/Window/WindowService.cs +++ b/src/Artemis.UI.Shared/Services/Window/WindowService.cs @@ -33,8 +33,6 @@ internal class WindowService : IWindowService public Window ShowWindow(object viewModel) { - Window? parent = GetCurrentWindow(); - string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View"); Type? type = viewModel.GetType().Assembly.GetType(name); @@ -46,10 +44,7 @@ internal class WindowService : IWindowService Window window = (Window) Activator.CreateInstance(type)!; window.DataContext = viewModel; - if (parent != null) - window.Show(parent); - else - window.Show(); + window.Show(); return window; } diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 65fd23482..bf4ce5d0c 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Artemis.UI/Screens/Debugger/DebugView.axaml b/src/Artemis.UI/Screens/Debugger/DebugView.axaml index f98fdc13b..6387ce994 100644 --- a/src/Artemis.UI/Screens/Debugger/DebugView.axaml +++ b/src/Artemis.UI/Screens/Debugger/DebugView.axaml @@ -15,6 +15,7 @@ Height="800"> + diff --git a/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugView.axaml b/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugView.axaml index 3a3207c8a..db82a857f 100644 --- a/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugView.axaml +++ b/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugView.axaml @@ -2,15 +2,25 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:aedit="https://github.com/avaloniaui/avaloniaedit" + xmlns:controls="clr-namespace:Artemis.UI.Controls" + xmlns:logs="clr-namespace:Artemis.UI.Screens.Debugger.Logs" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Screens.Debugger.Logs.LogsDebugView"> - - Logs - - On this page you can view Artemis's logs in real-time. Logging can come from Artemis itself, plugins and scripts. - + x:Class="Artemis.UI.Screens.Debugger.Logs.LogsDebugView" + x:DataType="logs:LogsDebugViewModel"> + - TODO as there's no FlowDocumentScrollViewer in Avalonia and I'm too lazy to come up with an alternative. - #feelsbadman - + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugView.axaml.cs b/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugView.axaml.cs index 16cf140d7..20bb00ff2 100644 --- a/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugView.axaml.cs +++ b/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugView.axaml.cs @@ -1,17 +1,68 @@ +using System; +using System.Diagnostics; +using System.Reflection; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; +using Avalonia.Threading; +using AvaloniaEdit; namespace Artemis.UI.Screens.Debugger.Logs; public class LogsDebugView : ReactiveUserControl { + private int _lineCount; + private TextEditor _textEditor; + public LogsDebugView() { + _lineCount = 0; InitializeComponent(); } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); + _textEditor = this.FindControl("log"); + } + + protected override void OnInitialized() + { + base.OnInitialized(); + Dispatcher.UIThread.Post(() => _textEditor.ScrollToEnd(), DispatcherPriority.ApplicationIdle); + } + + private void OnTextChanged(object? sender, EventArgs e) + { + if (_textEditor is null) + return; + if (_textEditor.ExtentHeight == 0) + return; + + int linesAdded = _textEditor.LineCount - _lineCount; + double lineHeight = _textEditor.ExtentHeight / _textEditor.LineCount; + double outOfScreenTextHeight = _textEditor.ExtentHeight - _textEditor.VerticalOffset - _textEditor.ViewportHeight; + double outOfScreenLines = outOfScreenTextHeight / lineHeight; + + //we need this help distance because of rounding. + //if we scroll slightly above the end, we still want it + //to scroll down to the new lines. + const double graceDistance = 1d; + + //if we were at the bottom of the log and + //if the last log event was 5 lines long + //we will be 5 lines out sync. + //if this is the case, scroll down. + + //if we are more than that out of sync, + //the user scrolled up and we should not + //mess with anything. + if (_lineCount == 0 || linesAdded + graceDistance > outOfScreenLines) + { + Dispatcher.UIThread.Post(() => _textEditor.ScrollToEnd(), DispatcherPriority.ApplicationIdle); + _lineCount = _textEditor.LineCount; + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugViewModel.cs b/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugViewModel.cs index 93eaf4075..ca067a392 100644 --- a/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Debugger/Tabs/Logs/LogsDebugViewModel.cs @@ -1,11 +1,74 @@ -using Artemis.UI.Shared; +using Artemis.Core; +using Artemis.UI.Shared; +using Avalonia.Threading; +using AvaloniaEdit.Document; +using ReactiveUI; +using Serilog.Events; +using Serilog.Formatting.Display; +using System.IO; +using System.Reactive.Disposables; namespace Artemis.UI.Screens.Debugger.Logs; public class LogsDebugViewModel : ActivatableViewModelBase { + private readonly MessageTemplateTextFormatter _formatter; + + public TextDocument Document { get; } + + private const int MAX_ENTRIES = 1000; + public LogsDebugViewModel() { DisplayName = "Logs"; + Document = new TextDocument(); + _formatter = new MessageTemplateTextFormatter( + "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}" + ); + + foreach(LogEvent logEvent in LogStore.Events) + AddLogEvent(logEvent); + + this.WhenActivated(disp => + { + LogStore.EventAdded += OnLogEventAdded; + + Disposable.Create(() => + { + LogStore.EventAdded -= OnLogEventAdded; + }).DisposeWith(disp); + }); + } + + private void OnLogEventAdded(object? sender, LogEventEventArgs e) + { + Dispatcher.UIThread.Post(() => + { + AddLogEvent(e.LogEvent); + }); + } + + private void AddLogEvent(LogEvent logEvent) + { + using StringWriter writer = new(); + _formatter.Format(logEvent, writer); + string line = writer.ToString(); + Document.Insert(Document.TextLength, '\n' + line.TrimEnd('\r', '\n')); + while (Document.LineCount > MAX_ENTRIES) + RemoveOldestLine(); + } + + private void RemoveOldestLine() + { + int firstNewLine = Document.Text.IndexOf('\n'); + if (firstNewLine == -1) + { + //this should never happen. + //just in case let's return + //instead of throwing + return; + } + + Document.Remove(0, firstNewLine + 1); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugView.axaml b/src/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugView.axaml index bb50ebe66..c5258f3a3 100644 --- a/src/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugView.axaml +++ b/src/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugView.axaml @@ -25,20 +25,13 @@ - - - - - - - - - + + + + + + + \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Branching/EnumSwitchNode.cs b/src/Artemis.VisualScripting/Nodes/Branching/EnumSwitchNode.cs index e380c3446..5ca915053 100644 --- a/src/Artemis.VisualScripting/Nodes/Branching/EnumSwitchNode.cs +++ b/src/Artemis.VisualScripting/Nodes/Branching/EnumSwitchNode.cs @@ -66,6 +66,9 @@ public class EnumSwitchNode : Node return; Type enumType = SwitchValue.ConnectedTo[0].Type; + if (!enumType.IsEnum) + return; + foreach (Enum enumValue in Enum.GetValues(enumType).Cast()) { InputPin pin = CreateOrAddInputPin(typeof(object), enumValue.ToString().Humanize(LetterCasing.Sentence)); diff --git a/src/Artemis.VisualScripting/Nodes/Color/SortedGradient.cs b/src/Artemis.VisualScripting/Nodes/Color/SortedGradient.cs index f444c0cf0..00cdd61d0 100644 --- a/src/Artemis.VisualScripting/Nodes/Color/SortedGradient.cs +++ b/src/Artemis.VisualScripting/Nodes/Color/SortedGradient.cs @@ -7,6 +7,7 @@ namespace Artemis.VisualScripting.Nodes.Color [Node("Sorted Gradient", "Generates a sorted gradient from the given colors", "Color", InputType = typeof(SKColor), OutputType = typeof(ColorGradient))] public class SortedGradientNode : Node { + private int lastComputedColorGroup; public InputPinCollection Inputs { get; } public OutputPin Output { get; } @@ -14,10 +15,15 @@ namespace Artemis.VisualScripting.Nodes.Color { Inputs = CreateInputPinCollection(); Output = CreateOutputPin(); + lastComputedColorGroup = 0; } public override void Evaluate() { + int newHash = GetInputColorHash(); + if (newHash == lastComputedColorGroup) + return; + SKColor[] colors = Inputs.Values.ToArray(); if (colors.Length == 0) @@ -35,6 +41,17 @@ namespace Artemis.VisualScripting.Nodes.Color } Output.Value = gradient; + lastComputedColorGroup = newHash; + } + + private int GetInputColorHash() + { + int hash = 0; + + foreach (SKColor color in Inputs.Values) + hash = HashCode.Combine(hash, color.GetHashCode()); + + return hash; } } }