using System; using System.IO; using System.Windows.Documents; using System.Windows.Media; using System.Windows.Threading; using MPF.Core.Data; namespace MPF.UI.Core.ViewModels { public class LogOutputViewModel { /// /// Document representing the text /// internal FlowDocument Document { get; private set; } /// /// Queue of items that need to be logged /// internal ProcessingQueue LogQueue { get; private set; } /// /// Dispatcher from the parent view for invocation /// internal Dispatcher Dispatcher { get; private set; } /// /// Paragraph backing the log /// private readonly Paragraph _paragraph; /// /// Cached value of the last line written /// private Run lastLine = null; public LogOutputViewModel(Dispatcher dispatcher) { // Update the internal state Document = new FlowDocument() { Background = new SolidColorBrush(Color.FromArgb(0xFF, 0x20, 0x20, 0x20)) }; _paragraph = new Paragraph(); Document.Blocks.Add(_paragraph); // Setup the processing queue LogQueue = new ProcessingQueue(ProcessLogLine); Dispatcher = dispatcher; } /// /// Clear all inlines of the paragraph /// public void ClearInlines() => _paragraph.Inlines.Clear(); /// /// Save all inlines to console.log /// public void SaveInlines() { using (StreamWriter tw = new StreamWriter(File.OpenWrite("console.log"))) { foreach (var inline in _paragraph.Inlines) { if (inline is Run run) tw.Write(run.Text); } } } /// /// Log line wrapper /// internal struct LogLine { public readonly string Text; public readonly LogLevel LogLevel; public LogLine(string text, LogLevel logLevel) { this.Text = text; this.LogLevel = logLevel; } /// /// Get the foreground Brush for the current LogLevel /// /// Brush representing the color public Brush GetForegroundColor() { switch (this.LogLevel) { case LogLevel.SECRET: return Brushes.Blue; case LogLevel.ERROR: return Brushes.Red; case LogLevel.VERBOSE: return Brushes.Yellow; case LogLevel.USER: default: return Brushes.White; } } /// /// Generate a Run object from the current LogLine /// /// Run object based on internal values public Run GenerateRun() { return new Run { Text = this.Text, Foreground = GetForegroundColor() }; } } /// /// Enqueue text to the log with formatting /// /// Text to write to the log /// LogLevel for the log internal void LogInternal(string text, LogLevel logLevel) { // Null text gets ignored if (text == null) return; // Enqueue the text LogQueue.Enqueue(new LogLine(text, logLevel)); } /// /// Process the log lines in the queue /// /// LogLine item to process internal void ProcessLogLine(LogLine nextLogLine) { // Null text gets ignored string nextText = nextLogLine.Text; if (nextText == null) return; try { if (nextText.StartsWith("\r")) ReplaceLastLine(nextLogLine); else AppendToTextBox(nextLogLine); } catch (Exception ex) { // In the event that something fails horribly, we want to log AppendToTextBox(new LogLine(ex.ToString(), LogLevel.ERROR)); } } /// /// Append log line to the log text box /// /// LogLine value to append private void AppendToTextBox(LogLine logLine) { Dispatcher.Invoke(() => { var run = logLine.GenerateRun(); _paragraph.Inlines.Add(run); lastLine = run; }); } /// /// Replace the last line written to the log text box /// /// LogLine value to append private void ReplaceLastLine(LogLine logLine) { Dispatcher.Invoke(() => { lastLine.Text = logLine.Text; lastLine.Foreground = logLine.GetForegroundColor(); }); } } }