diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index 375d315..7c72f15 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -321,6 +321,8 @@ namespace UVtools.Core.FileFormats set => _haveModifiedLayers = value; } // => LayerManager.IsModified; + public Size Resolution => new Size((int) ResolutionX, (int) ResolutionY); + public abstract uint ResolutionX { get; set; } public abstract uint ResolutionY { get; set; } @@ -363,6 +365,7 @@ namespace UVtools.Core.FileFormats public StringBuilder GCode { get; set; } + public bool HaveGCode => !(GCode is null); public abstract object[] Configs { get; } public bool IsValid => !ReferenceEquals(FileFullPath, null); diff --git a/UVtools.Core/FileFormats/IFileFormat.cs b/UVtools.Core/FileFormats/IFileFormat.cs index 0ca1b84..e166b55 100644 --- a/UVtools.Core/FileFormats/IFileFormat.cs +++ b/UVtools.Core/FileFormats/IFileFormat.cs @@ -7,6 +7,7 @@ */ using System; +using System.Drawing; using System.Text; using Emgu.CV; using UVtools.Core.Operations; @@ -82,6 +83,8 @@ namespace UVtools.Core.FileFormats /// LayerManager LayerManager { get; set; } + Size Resolution { get; } + /// /// Gets the image width resolution /// @@ -207,6 +210,11 @@ namespace UVtools.Core.FileFormats /// StringBuilder GCode { get; set; } + /// + /// Gets if this file have available gcode + /// + bool HaveGCode { get; } + /// /// Get all configuration objects with properties and values /// diff --git a/UVtools.Core/FileFormats/PHZFile.cs b/UVtools.Core/FileFormats/PHZFile.cs index b331ace..8a68196 100644 --- a/UVtools.Core/FileFormats/PHZFile.cs +++ b/UVtools.Core/FileFormats/PHZFile.cs @@ -978,6 +978,7 @@ namespace UVtools.Core.FileFormats public override void Decode(string fileFullPath, OperationProgress progress = null) { base.Decode(fileFullPath, progress); + if(progress is null) progress = new OperationProgress(OperationProgress.StatusGatherLayers, LayerCount); using (var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read)) { diff --git a/UVtools.Core/Operations/OperationProgress.cs b/UVtools.Core/Operations/OperationProgress.cs index f34d339..3ffce1f 100644 --- a/UVtools.Core/Operations/OperationProgress.cs +++ b/UVtools.Core/Operations/OperationProgress.cs @@ -6,11 +6,14 @@ * of this license document, but changing it is not allowed. */ using System; +using System.Diagnostics; using System.Threading; +using UVtools.Core.Extensions; +using UVtools.Core.Objects; namespace UVtools.Core.Operations { - public sealed class OperationProgress + public sealed class OperationProgress : BindableBase { public const string StatusDecodeThumbnails = "Decoded Thumbnails"; public const string StatusGatherLayers = "Gathered Layers"; @@ -34,6 +37,11 @@ namespace UVtools.Core.Operations public CancellationToken Token => TokenSource.Token; private bool _canCancel = true; + private string _title = "Operation"; + private string _itemName = StatusDecodeLayers; + private uint _processedItems; + private uint _itemCount; + public OperationProgress() @@ -50,6 +58,8 @@ namespace UVtools.Core.Operations _canCancel = canCancel; } + public Stopwatch StopWatch { get; } = new Stopwatch(); + /// /// Gets or sets if operation can be cancelled /// @@ -60,35 +70,74 @@ namespace UVtools.Core.Operations if (!_canCancel) return _canCancel; return !Token.IsCancellationRequested && Token.CanBeCanceled && _canCancel; } - set => _canCancel = value; + set => SetProperty(ref _canCancel, value); } /// /// Gets or sets the item name for the operation /// - public string ItemName { get; set; } = StatusDecodeLayers; + public string Title + { + get => _title; + set => SetProperty(ref _title, value); + } + + public string ElapsedTimeStr => $"{StopWatch.Elapsed.Minutes}m {StopWatch.Elapsed.Seconds}s {StopWatch.Elapsed.Milliseconds}ms"; + + /// + /// Gets or sets the item name for the operation + /// + public string ItemName + { + get => _itemName; + set => SetProperty(ref _itemName, value); + } /// /// Gets or sets the number of processed items /// - public uint ProcessedItems { get; set; } + public uint ProcessedItems + { + get => _processedItems; + set + { + //_processedItems = value; + SetProperty(ref _processedItems, value); + OnPropertyChanged(nameof(ProgressPercent)); + OnPropertyChanged(nameof(Description)); + } + } /// /// Gets or sets the total of item count on this operation /// - public uint ItemCount { get; set; } + public uint ItemCount + { + get => _itemCount; + set + { + SetProperty(ref _itemCount, value); + OnPropertyChanged(nameof(IsIndeterminate)); + OnPropertyChanged(nameof(ProgressPercent)); + OnPropertyChanged(nameof(Description)); + } + } /// /// Gets the remaining items to be processed /// public uint RemainingItems => ItemCount - ProcessedItems; - public int ProgressStep => (int) Math.Min(ProcessedItems * 100 / ItemCount, 100); + public int ProgressStep => (int)ProgressPercent; + + public string Description => ToString(); + + public bool IsIndeterminate => ItemCount == 0; /// /// Gets the progress from 0 to 100% /// - public double ProgressPercent => Math.Round(ProcessedItems * 100.0 / ItemCount, 2); + public double ProgressPercent => ItemCount == 0 ? 0 : Math.Round(ProcessedItems * 100.0 / ItemCount, 2).Clamp(0, 100); public static OperationProgress operator +(OperationProgress progress, uint value) { @@ -122,5 +171,13 @@ namespace UVtools.Core.Operations $"{ProcessedItems}/? {ItemName}" : $"{ProcessedItems}/{ItemCount} {ItemName} | {ProgressPercent:0.00}%"; } + + public void TriggerRefresh() + { + OnPropertyChanged(nameof(ElapsedTimeStr)); + OnPropertyChanged(nameof(CanCancel)); + //OnPropertyChanged(nameof(ProgressPercent)); + //OnPropertyChanged(nameof(Description)); + } } } diff --git a/UVtools.GUI/Forms/FrmLoading.cs b/UVtools.GUI/Forms/FrmLoading.cs index 61cd510..8806c57 100644 --- a/UVtools.GUI/Forms/FrmLoading.cs +++ b/UVtools.GUI/Forms/FrmLoading.cs @@ -11,7 +11,7 @@ namespace UVtools.GUI.Forms public partial class FrmLoading : Form { public Stopwatch StopWatch { get; } = new Stopwatch(); - public OperationProgress Progress { get; set; } + public OperationProgress Progress { get; private set; } private LogItem OperationLog; diff --git a/UVtools.WPF/App.axaml b/UVtools.WPF/App.axaml index 9b64e5d..3eb218e 100644 --- a/UVtools.WPF/App.axaml +++ b/UVtools.WPF/App.axaml @@ -4,6 +4,7 @@ + diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs index 9e0b688..f22ddb1 100644 --- a/UVtools.WPF/App.axaml.cs +++ b/UVtools.WPF/App.axaml.cs @@ -6,6 +6,8 @@ * of this license document, but changing it is not allowed. */ +using System; +using System.Diagnostics; using System.Globalization; using System.Threading; using Avalonia; @@ -49,7 +51,19 @@ namespace UVtools.WPF } #region Utilities - + public static void NewInstance(string filePath) + { + try + { + var info = new ProcessStartInfo("UVtools.exe", $"\"{filePath}\""); + Process.Start(info)?.Dispose(); + } + catch (Exception e) + { + Debug.WriteLine(e); + } + + } #endregion } diff --git a/UVtools.WPF/AppSettings.cs b/UVtools.WPF/AppSettings.cs index 9b35e1f..162d76c 100644 --- a/UVtools.WPF/AppSettings.cs +++ b/UVtools.WPF/AppSettings.cs @@ -27,7 +27,7 @@ namespace UVtools.WPF /// /// Returns the zoom level that will be used for autozoom actions /// - public const ushort LockedZoomLevel = 1200; + public static int LockedZoomLevel => ZoomLevels[UserSettings.Instance.LayerPreview.ZoomLockLevelIndex + ZoomLevelSkipCount]; /// diff --git a/UVtools.WPF/Controls/AdvancedImageBox.cs b/UVtools.WPF/Controls/AdvancedImageBox.cs index 513e11d..0922927 100644 --- a/UVtools.WPF/Controls/AdvancedImageBox.cs +++ b/UVtools.WPF/Controls/AdvancedImageBox.cs @@ -408,8 +408,17 @@ namespace UVtools.WPF.Controls set { _image = value; - SizedContainer.Width = _image.Size.Width; - SizedContainer.Height = _image.Size.Height; + if (_image is null) + { + SizedContainer.Width = 0; + SizedContainer.Height = 0; + } + else + { + SizedContainer.Width = _image.Size.Width; + SizedContainer.Height = _image.Size.Height; + } + InvalidateVisual(); } } diff --git a/UVtools.WPF/LayerCache.cs b/UVtools.WPF/LayerCache.cs index 53660fc..e46d4e7 100644 --- a/UVtools.WPF/LayerCache.cs +++ b/UVtools.WPF/LayerCache.cs @@ -10,28 +10,35 @@ using System; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Util; +using UVtools.Core; namespace UVtools.WPF { public sealed class LayerCache { + private Layer _layer; private Mat _image; private Array _layerHierarchyJagged; private VectorOfVectorOfPoint _layerContours; private Mat _layerHierarchy; - public Mat Image + public bool IsCached => !ReferenceEquals(_layer, null); + + public Layer Layer { - get => _image; + get => _layer; set { Clear(); - _image = value; + _layer = value; + _image = _layer.LayerMat; ImageBgr = new Mat(); - CvInvoke.CvtColor(value, ImageBgr, ColorConversion.Gray2Bgr); + CvInvoke.CvtColor(_image, ImageBgr, ColorConversion.Gray2Bgr); } } + public Mat Image => _image; + public Mat ImageBgr { get; set; } public VectorOfVectorOfPoint LayerContours @@ -81,6 +88,7 @@ namespace UVtools.WPF /// public void Clear() { + _layer = null; _image?.Dispose(); ImageBgr?.Dispose(); _layerContours?.Dispose(); diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml index 35058e9..76f60c1 100644 --- a/UVtools.WPF/MainWindow.axaml +++ b/UVtools.WPF/MainWindow.axaml @@ -11,27 +11,42 @@ Icon="/Assets/Icons/UVtools.ico" MinWidth="1024" MinHeight="600" + DragDrop.AllowDrop="True" > - + - + - + - + @@ -41,7 +56,12 @@ - + @@ -102,35 +122,106 @@ - - - - + - Issues + + + Information + - - - - + + + + + + Gcode + + + + + - GCode + + + Issues + - - - - + + + - Log + + + Log + - - - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -287,7 +378,7 @@ @@ -349,8 +440,69 @@ Name="Layer.Image" /> - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index b61abb5..39c9101 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -6,22 +6,27 @@ * of this license document, but changing it is not allowed. */ using System; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; using Avalonia; using Avalonia.Collections; using Avalonia.Controls; +using Avalonia.Input; using Avalonia.Markup.Xaml; +using Avalonia.Threading; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; -using Emgu.CV.Util; using UVtools.Core; using UVtools.Core.Extensions; using UVtools.Core.FileFormats; +using UVtools.Core.PixelEditor; using UVtools.WPF.Controls; using UVtools.WPF.Extensions; using UVtools.WPF.Structures; @@ -87,15 +92,28 @@ namespace UVtools.WPF #endregion #region Controls + + public ProgressWindow ProgressWindow = new ProgressWindow(); + + //private ProgressWindow _progressWindow; public AdvancedImageBox LayerImageBox; public SliderEx LayerSlider; public Panel LayerNavigationTooltipPanel; public Border LayerNavigationTooltipBorder; + + #region DataSets + public ObservableCollection Logs { get; } = new ObservableCollection(); + public ObservableCollection Issues { get; } = new ObservableCollection(); + public ObservableCollection Drawings { get; } = new ObservableCollection(); + #endregion + #endregion #region Members private uint _actualLayer; private bool _isGUIEnabled = true; + private bool _canSave; + private bool _isVerbose; private bool _showLayerImageRotated; private bool _showLayerImageDifference; private bool _showLayerImageIssues = true; @@ -113,7 +131,22 @@ namespace UVtools.WPF public bool IsGUIEnabled { get => _isGUIEnabled; - set => SetProperty(ref _isGUIEnabled, value); + set + { + if (!SetProperty(ref _isGUIEnabled, value)) return; + if (!_isGUIEnabled) return; + if (ProgressWindow.IsActive) + { + Dispatcher.UIThread.InvokeAsync(() => + { + ProgressWindow.Close(); + ProgressWindow.Dispose(); + ProgressWindow = new ProgressWindow(); + }); + } + + + } } public bool IsFileLoaded @@ -122,6 +155,18 @@ namespace UVtools.WPF set => OnPropertyChanged(); } + public bool CanSave + { + get => _canSave; + set => SetProperty(ref _canSave, value); + } + + public bool IsVerbose + { + get => _isVerbose; + set => SetProperty(ref _isVerbose, value); + } + public bool ShowLayerImageRotated { get => _showLayerImageRotated; @@ -233,6 +278,21 @@ namespace UVtools.WPF public bool CanGoUp => _actualLayer < SliderMaximumValue; public bool CanGoDown => _actualLayer > 0; + public string LayerPixelCountStr + { + get + { + if (!LayerCache.IsCached) return "0"; + var pixelPercent = + Math.Round( + LayerCache.Layer.NonZeroPixelCount * 100.0 / (SlicerFile.ResolutionX * SlicerFile.ResolutionY), 2); + return $"{LayerCache.Layer.NonZeroPixelCount} ({pixelPercent}%)"; + } + } + + public string LayerBoundsStr => LayerCache.Layer is null ? "NS" : LayerCache.Layer.BoundingRectangle.ToString(); + public string LayerROIStr => LayerCache.Layer is null ? "NS" : "TODO"; + public long ShowLayerRenderMs { get => _showLayerRenderMs; @@ -240,7 +300,13 @@ namespace UVtools.WPF } #endregion - private uint ActualLayer + public PixelPicker LayerPixelPicker { get; } = new PixelPicker(); + + public string LayerZoomStr => $"{LayerImageBox.Zoom / 100}x"+ + (AppSettings.LockedZoomLevel == LayerImageBox.Zoom ? "🔒" : string.Empty); + public string LayerResolutionStr => SlicerFile?.Resolution.ToString() ?? "Unloaded"; + + public uint ActualLayer { get => _actualLayer; set @@ -250,6 +316,8 @@ namespace UVtools.WPF OnPropertyChanged(nameof(CanGoUp)); OnPropertyChanged(nameof(ActualLayerTooltip)); OnPropertyChanged(nameof(LayerNavigationTooltipMargin)); + OnPropertyChanged(nameof(LayerPixelCountStr)); + OnPropertyChanged(nameof(LayerBoundsStr)); ShowLayer(); } } @@ -259,7 +327,7 @@ namespace UVtools.WPF get { double top = 0; - if (LayerSlider != null) + if (LayerSlider?.Track != null) { double trackerPos = LayerSlider.Track.Thumb.Bounds.Height / 2 + LayerSlider.Track.Thumb.Bounds.Top; double halfTooltipHeight = LayerNavigationTooltipBorder.Bounds.Height / 2; @@ -279,19 +347,12 @@ namespace UVtools.WPF public LayerCache LayerCache = new LayerCache(); - #endregion #region Constructors public MainWindow() { - _showLayerImageDifference = Settings.LayerPreview.ShowLayerDifference; - _showLayerOutlinePrintVolumeBoundary = Settings.LayerPreview.VolumeBoundsOutline; - _showLayerOutlineLayerBoundary = Settings.LayerPreview.LayerBoundsOutline; - _showLayerOutlineHollowAreas = Settings.LayerPreview.HollowOutline; - - DataContext = this; InitializeComponent(); #if DEBUG //this.AttachDevTools(); @@ -301,6 +362,12 @@ namespace UVtools.WPF LayerSlider = this.FindControl("Layer.Navigation.Slider"); LayerNavigationTooltipPanel = this.FindControl("Layer.Navigation.Tooltip.Panel"); LayerNavigationTooltipBorder = this.FindControl("Layer.Navigation.Tooltip.Border"); + + _showLayerImageDifference = Settings.LayerPreview.ShowLayerDifference; + _showLayerOutlinePrintVolumeBoundary = Settings.LayerPreview.VolumeBoundsOutline; + _showLayerOutlineLayerBoundary = Settings.LayerPreview.LayerBoundsOutline; + _showLayerOutlineHollowAreas = Settings.LayerPreview.HollowOutline; + /*LayerSlider.PropertyChanged += (sender, args) => { Debug.WriteLine(args.Property.Name); @@ -311,8 +378,22 @@ namespace UVtools.WPF } };*/ PropertyChanged += OnPropertyChanged; + DataContext = this; + AddHandler(DragDrop.DropEvent, (sender, e) => + { + ProcessFiles(e.Data.GetFileNames().ToArray()); + }); + + AddLog($"{About.Software} start"); } + public override void Show() + { + base.Show(); + ProcessFiles(Program.Args); + } + + private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { Debug.WriteLine(e.PropertyName); @@ -332,14 +413,28 @@ namespace UVtools.WPF #region Events - public async void MenuFileOpenClicked() + public void MenuFileOpenClicked() => OpenFile(); + public void MenuFileOpenNewWindowClicked() => OpenFile(true); + + public async void OpenFile(bool newWindow = false) { var dialog = new OpenFileDialog { - AllowMultiple = false, + AllowMultiple = true, + }; var files = await dialog.ShowAsync(this); - ProcessFiles(files); + ProcessFiles(files, newWindow); + } + + public void CloseFile() + { + if (SlicerFile is null) return; + SlicerFile?.Dispose(); + App.SlicerFile = null; + LayerCache.Clear(); + LayerImageBox.Image = null; + ResetDataContext(); } public async void MenuFileSettingsClicked() @@ -347,6 +442,7 @@ namespace UVtools.WPF SettingsWindow settingsWindow = new SettingsWindow(); await settingsWindow.ShowDialog(this); } + #endregion #region Methods @@ -361,58 +457,63 @@ namespace UVtools.WPF ProcessFile(files[i]); continue; } + + App.NewInstance(files[i]); + } } - void ReloadFile(uint actualLayer = 0) + void ReloadFile() => ReloadFile(_actualLayer); + + void ReloadFile(uint actualLayer) { if (App.SlicerFile is null) return; - ProcessFile(App.SlicerFile.FileFullPath, actualLayer); + ProcessFile(SlicerFile.FileFullPath, _actualLayer); } - void ProcessFile(string fileName, uint actualLayer = 0) + async void ProcessFile(string fileName, uint actualLayer = 0) { + if (!File.Exists(fileName)) return; + CloseFile(); var fileNameOnly = Path.GetFileName(fileName); App.SlicerFile = FileFormat.FindByExtension(fileName, true, true); - if (App.SlicerFile is null) return; + if (SlicerFile is null) return; + ProgressWindow.SetTitle($"Opening: {fileNameOnly}"); IsGUIEnabled = false; - var task = Task.Factory.StartNew(() => { try { - App.SlicerFile.Decode(fileName); + SlicerFile.Decode(fileName, ProgressWindow.RestartProgress()); } catch (OperationCanceledException) { - App.SlicerFile.Clear(); + SlicerFile.Clear(); } finally { - /* Invoke((MethodInvoker)delegate - { - // Running on the UI thread - EnableGUI(true); - });*/ + IsGUIEnabled = true; } }); //ProgressWindow progressWindow = new ProgressWindow(); //progressWindow.ShowDialog(this); - task.Wait(); + await ProgressWindow.ShowDialog(this); - var mat = App.SlicerFile[0].LayerMat; + /*var mat = App.SlicerFile[0].LayerMat; var matRgb = App.SlicerFile[0].BrgMat; Debug.WriteLine("4K grayscale - BGRA convertion:"); var bitmap = mat.ToBitmap(); Debug.WriteLine("4K BGR - BGRA convertion:"); var bitmapRgb = matRgb.ToBitmap(); - LayerImageBox.Image = bitmapRgb; + LayerImageBox.Image = bitmapRgb;*/ + + _actualLayer = actualLayer; + ShowLayer(); - IsGUIEnabled = true; ResetDataContext(); } @@ -444,6 +545,11 @@ namespace UVtools.WPF ActualLayer = SliderMaximumValue; } + public void ZoomToFit() + { + LayerImageBox.ZoomToFit(); + } + /// /// Shows a layer number /// @@ -453,17 +559,11 @@ namespace UVtools.WPF Debug.WriteLine($"Showing layer: {_actualLayer}"); - - //AddLogVerbose($"Show Layer: {layerNum}"); - - Stopwatch watch = Stopwatch.StartNew(); - var layer = SlicerFile[_actualLayer]; + LayerCache.Layer = SlicerFile[_actualLayer]; try { - LayerCache.Image = layer.LayerMat; - //var imageSpan = LayerCache.Image.GetPixelSpan(); //var imageBgrSpan = LayerCache.ImageBgr.GetPixelSpan(); @@ -805,6 +905,7 @@ namespace UVtools.WPF watch.Stop(); ShowLayerRenderMs = watch.ElapsedMilliseconds; + AddLogVerbose($"Show Layer: {_actualLayer}", watch.Elapsed.TotalSeconds); } catch (Exception e) { @@ -812,6 +913,25 @@ namespace UVtools.WPF } } + + public void AddLog(LogItem log) + { + log.Index = Logs.Count; + Logs.Insert(0, log); + //lbLogOperations.Text = $"Operations: {count}"; + } + + public void AddLog(string description, double elapsedTime = 0) => + AddLog(new LogItem(Logs.Count, description, elapsedTime)); + + + public void AddLogVerbose(string description, double elapsedTime = 0) + { + if (!_isVerbose) return; + AddLog(description, elapsedTime); + Debug.WriteLine(description); + } + #endregion } } diff --git a/UVtools.WPF/Program.cs b/UVtools.WPF/Program.cs index ea81577..b3f91bb 100644 --- a/UVtools.WPF/Program.cs +++ b/UVtools.WPF/Program.cs @@ -1,14 +1,21 @@ -using Avalonia; +using System; +using Avalonia; namespace UVtools.WPF { - class Program + public static class Program { + public static string[] Args; // Initialization code. Don't use any Avalonia, third-party APIs or any // SynchronizationContext-reliant code before AppMain is called: things aren't initialized // yet and stuff might break. - public static void Main(string[] args) => BuildAvaloniaApp() - .StartWithClassicDesktopLifetime(args); + [STAThread] + public static void Main(string[] args) + { + Args = args; + BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + } // Avalonia configuration, don't remove; also used by visual designer. public static AppBuilder BuildAvaloniaApp() diff --git a/UVtools.WPF/Structures/LogItem.cs b/UVtools.WPF/Structures/LogItem.cs new file mode 100644 index 0000000..4dc6872 --- /dev/null +++ b/UVtools.WPF/Structures/LogItem.cs @@ -0,0 +1,61 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using UVtools.Core.Objects; + +namespace UVtools.WPF.Structures +{ + public sealed class LogItem : BindableBase + { + private int _index; + private string _startTime; + private double _elapsedTime; + private string _description; + + public int Index + { + get => _index; + set => SetProperty(ref _index, value); + } + + public string StartTime + { + get => _startTime; + set => SetProperty(ref _startTime, value); + } + + public double ElapsedTime + { + get => _elapsedTime; + set => SetProperty(ref _elapsedTime, Math.Round(value, 2)); + } + + public string Description + { + get => _description; + set => SetProperty(ref _description, value); + } + + public LogItem(int index, string description, double elapsedTime = 0) + { + _index = index; + _description = description; + ElapsedTime = elapsedTime; + _startTime = DateTime.Now.ToString("HH:mm:ss"); + } + + public LogItem(string description = null, uint elapsedTime = 0) : this(0, description, elapsedTime) + { } + + public override string ToString() + { + return Description; + } + } +} diff --git a/UVtools.WPF/Structures/PixelPicker.cs b/UVtools.WPF/Structures/PixelPicker.cs new file mode 100644 index 0000000..f011954 --- /dev/null +++ b/UVtools.WPF/Structures/PixelPicker.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using Avalonia; +using ReactiveUI; + +namespace UVtools.WPF.Structures +{ + public class PixelPicker : ReactiveObject + { + private bool _isSet; + private Point _location = new Point(0,0); + private byte _brightness; + + public bool IsSet + { + get => _isSet; + private set => this.RaiseAndSetIfChanged(ref _isSet, value); + } + + public Point Location + { + get => _location; + private set => this.RaiseAndSetIfChanged(ref _location, value); + } + + public byte Brightness + { + get => _brightness; + private set => this.RaiseAndSetIfChanged(ref _brightness, value); + } + + public void Set(Point location, byte brightness) + { + Location = location; + Brightness = brightness; + IsSet = true; + } + + public override string ToString() + { + return $"{{X={_location.X}, Y={_location.Y}, B={_brightness}}}"; + } + } +} diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index 6d7eac4..8e47665 100644 --- a/UVtools.WPF/UVtools.WPF.csproj +++ b/UVtools.WPF/UVtools.WPF.csproj @@ -22,6 +22,7 @@ + diff --git a/UVtools.WPF/Windows/ProgressWindow.axaml b/UVtools.WPF/Windows/ProgressWindow.axaml index fbcc4a2..435bd20 100644 --- a/UVtools.WPF/Windows/ProgressWindow.axaml +++ b/UVtools.WPF/Windows/ProgressWindow.axaml @@ -14,12 +14,21 @@ Icon="/Assets/Icons/UVtools.ico" > - - - + + + - - + + diff --git a/UVtools.WPF/Windows/ProgressWindow.axaml.cs b/UVtools.WPF/Windows/ProgressWindow.axaml.cs index b9744e6..953340a 100644 --- a/UVtools.WPF/Windows/ProgressWindow.axaml.cs +++ b/UVtools.WPF/Windows/ProgressWindow.axaml.cs @@ -5,22 +5,79 @@ * Everyone is permitted to copy and distribute verbatim copies * of this license document, but changing it is not allowed. */ + +using System; +using System.Diagnostics; +using System.Timers; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.Threading; +using UVtools.Core.Operations; +using UVtools.WPF.Structures; namespace UVtools.WPF.Windows { - public class ProgressWindow : Window + public class ProgressWindow : Window, IDisposable { + public Stopwatch StopWatch { get; } = new Stopwatch(); + public OperationProgress Progress { get; } = new OperationProgress(); + + private LogItem _logItem = new LogItem(); + + private Timer _timer; + + public bool CanCancel => Progress?.CanCancel ?? false; + public ProgressWindow() { InitializeComponent(); + DataContext = Progress; + _timer = new Timer(100) {AutoReset = true}; + _timer.Elapsed += (sender, args) => + { + Progress.TriggerRefresh(); + }; } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); - + } + + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + _timer.Stop(); + Progress.StopWatch.Stop(); + _logItem.ElapsedTime = Math.Round(Progress.StopWatch.Elapsed.TotalSeconds, 2); + App.MainWindow.AddLog(_logItem); + } + + public void SetTitle(string title) + { + Progress.Title = title; + _logItem.Description = title; + } + + public OperationProgress RestartProgress(bool canCancel = true) + { + Progress.CanCancel = canCancel; + Progress.StopWatch.Restart(); + + if (!_timer.Enabled) + { + _timer.Enabled = true; + _timer.Start(); + } + + Dispatcher.UIThread.InvokeAsync(() => DataContext = Progress); + return Progress; + } + + public void Dispose() + { + _timer.Close(); + _timer.Dispose(); } } }