mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
UI - Added debugger log tab (#753)
This commit is contained in:
parent
425485b059
commit
b493e43c6e
@ -241,7 +241,16 @@ public class ColorGradient : IList<ColorGradientStop>, IList, INotifyCollectionC
|
|||||||
return _stops[^1].Color;
|
return _stops[^1].Color;
|
||||||
|
|
||||||
//find the first stop after the position
|
//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 the position is before the first stop, return that color
|
||||||
if (stop2Index == 0)
|
if (stop2Index == 0)
|
||||||
return _stops[0].Color;
|
return _stops[0].Color;
|
||||||
|
|||||||
@ -33,8 +33,6 @@ internal class WindowService : IWindowService
|
|||||||
|
|
||||||
public Window ShowWindow(object viewModel)
|
public Window ShowWindow(object viewModel)
|
||||||
{
|
{
|
||||||
Window? parent = GetCurrentWindow();
|
|
||||||
|
|
||||||
string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View");
|
string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View");
|
||||||
Type? type = viewModel.GetType().Assembly.GetType(name);
|
Type? type = viewModel.GetType().Assembly.GetType(name);
|
||||||
|
|
||||||
@ -46,10 +44,7 @@ internal class WindowService : IWindowService
|
|||||||
|
|
||||||
Window window = (Window) Activator.CreateInstance(type)!;
|
Window window = (Window) Activator.CreateInstance(type)!;
|
||||||
window.DataContext = viewModel;
|
window.DataContext = viewModel;
|
||||||
if (parent != null)
|
window.Show();
|
||||||
window.Show(parent);
|
|
||||||
else
|
|
||||||
window.Show();
|
|
||||||
|
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="0.10.18" />
|
<PackageReference Include="Avalonia" Version="0.10.18" />
|
||||||
|
<PackageReference Include="Avalonia.AvaloniaEdit" Version="0.10.12.2" />
|
||||||
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="10.14.0" />
|
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="10.14.0" />
|
||||||
<PackageReference Include="Avalonia.Controls.Skia" Version="0.10.16" />
|
<PackageReference Include="Avalonia.Controls.Skia" Version="0.10.16" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
|
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
Height="800">
|
Height="800">
|
||||||
|
|
||||||
<Window.Styles>
|
<Window.Styles>
|
||||||
|
<StyleInclude Source="avares://AvaloniaEdit/AvaloniaEdit.xaml" />
|
||||||
<Style Selector="StackPanel.sidebar-stackpanel avalonia|MaterialIcon">
|
<Style Selector="StackPanel.sidebar-stackpanel avalonia|MaterialIcon">
|
||||||
<Setter Property="Margin" Value="-7 0 0 0" />
|
<Setter Property="Margin" Value="-7 0 0 0" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|||||||
@ -2,15 +2,25 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
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"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Debugger.Logs.LogsDebugView">
|
x:Class="Artemis.UI.Screens.Debugger.Logs.LogsDebugView"
|
||||||
<StackPanel>
|
x:DataType="logs:LogsDebugViewModel">
|
||||||
<TextBlock Classes="h3">Logs</TextBlock>
|
<aedit:TextEditor Name="log"
|
||||||
<TextBlock TextWrapping="Wrap">
|
Document="{ CompiledBinding Document }"
|
||||||
On this page you can view Artemis's logs in real-time. Logging can come from Artemis itself, plugins and scripts.
|
IsReadOnly="True"
|
||||||
</TextBlock>
|
FontFamily="Consolas"
|
||||||
|
FontSize="12"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
Padding="0 15 15 15"
|
||||||
|
TextChanged="OnTextChanged" >
|
||||||
|
|
||||||
<TextBlock Margin="0 20 0 0">TODO as there's no FlowDocumentScrollViewer in Avalonia and I'm too lazy to come up with an alternative.</TextBlock>
|
<aedit:TextEditor.Styles>
|
||||||
<TextBlock Classes="subtitle">#feelsbadman</TextBlock>
|
<Style Selector="aedit|TextArea">
|
||||||
</StackPanel>
|
<Setter Property="SelectionBrush" Value="#44ffffff" />
|
||||||
|
</Style>
|
||||||
|
</aedit:TextEditor.Styles>
|
||||||
|
</aedit:TextEditor>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -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.Markup.Xaml;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using AvaloniaEdit;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Debugger.Logs;
|
namespace Artemis.UI.Screens.Debugger.Logs;
|
||||||
|
|
||||||
public class LogsDebugView : ReactiveUserControl<LogsDebugViewModel>
|
public class LogsDebugView : ReactiveUserControl<LogsDebugViewModel>
|
||||||
{
|
{
|
||||||
|
private int _lineCount;
|
||||||
|
private TextEditor _textEditor;
|
||||||
|
|
||||||
public LogsDebugView()
|
public LogsDebugView()
|
||||||
{
|
{
|
||||||
|
_lineCount = 0;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
_textEditor = this.FindControl<TextEditor>("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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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;
|
namespace Artemis.UI.Screens.Debugger.Logs;
|
||||||
|
|
||||||
public class LogsDebugViewModel : ActivatableViewModelBase
|
public class LogsDebugViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
|
private readonly MessageTemplateTextFormatter _formatter;
|
||||||
|
|
||||||
|
public TextDocument Document { get; }
|
||||||
|
|
||||||
|
private const int MAX_ENTRIES = 1000;
|
||||||
|
|
||||||
public LogsDebugViewModel()
|
public LogsDebugViewModel()
|
||||||
{
|
{
|
||||||
DisplayName = "Logs";
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -25,20 +25,13 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Border Classes="card" Padding="10">
|
<Border Classes="card" Padding="10">
|
||||||
<ZoomBorder Name="ZoomBorder"
|
<Image Name="Visualization" Source="{Binding CurrentFrame}">
|
||||||
Stretch="None"
|
<Image.Transitions>
|
||||||
ClipToBounds="True"
|
<Transitions>
|
||||||
Focusable="True"
|
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.2" Easing="CubicEaseOut" />
|
||||||
VerticalAlignment="Stretch"
|
</Transitions>
|
||||||
HorizontalAlignment="Stretch">
|
</Image.Transitions>
|
||||||
<Image Name="Visualization" Source="{Binding CurrentFrame}">
|
</Image>
|
||||||
<Image.Transitions>
|
|
||||||
<Transitions>
|
|
||||||
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.2" Easing="CubicEaseOut" />
|
|
||||||
</Transitions>
|
|
||||||
</Image.Transitions>
|
|
||||||
</Image>
|
|
||||||
</ZoomBorder>
|
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -66,6 +66,9 @@ public class EnumSwitchNode : Node
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
Type enumType = SwitchValue.ConnectedTo[0].Type;
|
Type enumType = SwitchValue.ConnectedTo[0].Type;
|
||||||
|
if (!enumType.IsEnum)
|
||||||
|
return;
|
||||||
|
|
||||||
foreach (Enum enumValue in Enum.GetValues(enumType).Cast<Enum>())
|
foreach (Enum enumValue in Enum.GetValues(enumType).Cast<Enum>())
|
||||||
{
|
{
|
||||||
InputPin pin = CreateOrAddInputPin(typeof(object), enumValue.ToString().Humanize(LetterCasing.Sentence));
|
InputPin pin = CreateOrAddInputPin(typeof(object), enumValue.ToString().Humanize(LetterCasing.Sentence));
|
||||||
|
|||||||
@ -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))]
|
[Node("Sorted Gradient", "Generates a sorted gradient from the given colors", "Color", InputType = typeof(SKColor), OutputType = typeof(ColorGradient))]
|
||||||
public class SortedGradientNode : Node
|
public class SortedGradientNode : Node
|
||||||
{
|
{
|
||||||
|
private int lastComputedColorGroup;
|
||||||
public InputPinCollection<SKColor> Inputs { get; }
|
public InputPinCollection<SKColor> Inputs { get; }
|
||||||
public OutputPin<ColorGradient> Output { get; }
|
public OutputPin<ColorGradient> Output { get; }
|
||||||
|
|
||||||
@ -14,10 +15,15 @@ namespace Artemis.VisualScripting.Nodes.Color
|
|||||||
{
|
{
|
||||||
Inputs = CreateInputPinCollection<SKColor>();
|
Inputs = CreateInputPinCollection<SKColor>();
|
||||||
Output = CreateOutputPin<ColorGradient>();
|
Output = CreateOutputPin<ColorGradient>();
|
||||||
|
lastComputedColorGroup = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Evaluate()
|
public override void Evaluate()
|
||||||
{
|
{
|
||||||
|
int newHash = GetInputColorHash();
|
||||||
|
if (newHash == lastComputedColorGroup)
|
||||||
|
return;
|
||||||
|
|
||||||
SKColor[] colors = Inputs.Values.ToArray();
|
SKColor[] colors = Inputs.Values.ToArray();
|
||||||
|
|
||||||
if (colors.Length == 0)
|
if (colors.Length == 0)
|
||||||
@ -35,6 +41,17 @@ namespace Artemis.VisualScripting.Nodes.Color
|
|||||||
}
|
}
|
||||||
|
|
||||||
Output.Value = gradient;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user