1
0
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:
Diogo Trindade 2023-02-01 18:46:37 +00:00 committed by GitHub
parent 425485b059
commit b493e43c6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 174 additions and 31 deletions

View File

@ -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;

View File

@ -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;
} }

View File

@ -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" />

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
} }
} }

View File

@ -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);
} }
} }

View File

@ -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>

View File

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

View File

@ -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;
} }
} }
} }