diff --git a/CHANGELOG.md b/CHANGELOG.md index 68d99b47..80964873 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,23 @@ # Changelog +## 26/06/2023 - v3.15.0 + +- **File formats:** + - (Add) CXDLPv4 format + - (Improvement) Add missing keys for SL1 format from PrusaSlicer 2.6.0 +- **UI**: + - (Add) About - Terms of Use + - (Add) Outline: Enclosing circles + - (Improvement) Minor alteration on how current layer preview cache the contours + - (Improvement) Revamp tool Edit print parameters + - (Improvement) Revamp message boxes + - (Remove) Avalonia.MessageBox dependency in favor of use our own message box +- (Change) PrusaSlicer printers - Creality Halot Mage/Pro: Use CXDLPV4 format instead ENCRYPTED.CTB + ## 21/06/2023 - v3.14.4 - (Add) File - Rename: Allow to rename the current file with a new name (Ctrl + F2) +- (Improvement) Settings - Issues: Removed the "Compute issues on load" in favor of a new setting which allow to have three types (No not compute issues, Compute time inexpensive issues, Compute the enabled issues). The default option remain unchanged (The second option), if you had the old setting enabled you need to select the last option. - (Improvement) Tool - Edit print parameters: It now apply settings without close the window, allowing user to do continuous work. After all editing is done the user must manually close the window (#731) - (Improvement) Resin traps and suction cups: Optimization of contour grouping will now make the detection faster if it contains a large number of contours - (Change) Lower the default setting for binary threshold for resin traps, from 127 to 100 diff --git a/PrusaSlicer/printer/Creality Halot Mage CL-103L.ini b/PrusaSlicer/printer/Creality Halot Mage CL-103L.ini index 6c9e04cf..d539bca2 100644 --- a/PrusaSlicer/printer/Creality Halot Mage CL-103L.ini +++ b/PrusaSlicer/printer/Creality Halot Mage CL-103L.ini @@ -1,4 +1,4 @@ -# generated by PrusaSlicer 2.5.2+win64 on 2023-05-05 at 21:41:15 UTC +# generated by PrusaSlicer 2.6.0+win64 on 2023-06-24 at 15:21:33 UTC absolute_correction = 0 area_fill = 50 bed_custom_model = @@ -27,7 +27,7 @@ min_exposure_time = 1 min_initial_exposure_time = 1 print_host = printer_model = SL1 -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_SL1\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_HALOT-MAGE\nFILEFORMAT_ENCRYPTED.CTB\n\nSTART_CUSTOM_VALUES\nWaitTimeBeforeCure_2\nBottomLiftHeight_7\nLiftHeight_6\nBottomLiftSpeed_80\nLiftSpeed_80\nRetractSpeed_150\nBottomLightPWM_255\nLightPWM_255\nEND_CUSTOM_VALUES +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_SL1\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_HALOT-MAGE\nFILEFORMAT_CXDLPV4\n\nSTART_CUSTOM_VALUES\nWaitTimeBeforeCure_2\nBottomLiftHeight_7\nLiftHeight_6\nBottomLiftSpeed_80\nLiftSpeed_80\nRetractSpeed_150\nBottomLightPWM_255\nLightPWM_255\nEND_CUSTOM_VALUES printer_settings_id = printer_technology = SLA printer_variant = default @@ -38,5 +38,7 @@ relative_correction = 1,1 relative_correction_x = 1 relative_correction_y = 1 relative_correction_z = 1 +sla_archive_format = SL1 +sla_output_precision = 0.001 slow_tilt_time = 8 -thumbnails = 116x116,90x290,90x290 +thumbnails = 120x120,300x300 diff --git a/PrusaSlicer/printer/Creality Halot Mage Pro CL-103.ini b/PrusaSlicer/printer/Creality Halot Mage Pro CL-103.ini index a6b5ca30..37b6bda3 100644 --- a/PrusaSlicer/printer/Creality Halot Mage Pro CL-103.ini +++ b/PrusaSlicer/printer/Creality Halot Mage Pro CL-103.ini @@ -1,4 +1,4 @@ -# generated by PrusaSlicer 2.5.2+win64 on 2023-05-05 at 21:41:31 UTC +# generated by PrusaSlicer 2.6.0+win64 on 2023-06-24 at 15:21:41 UTC absolute_correction = 0 area_fill = 50 bed_custom_model = @@ -27,7 +27,7 @@ min_exposure_time = 1 min_initial_exposure_time = 1 print_host = printer_model = SL1 -printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_SL1\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_HALOT-MAGE-PRO\nFILEFORMAT_ENCRYPTED.CTB\n\nSTART_CUSTOM_VALUES\nWaitTimeBeforeCure_2\nBottomLiftHeight_7\nLiftHeight_6\nBottomLiftSpeed_80\nLiftSpeed_80\nRetractSpeed_150\nBottomLightPWM_255\nLightPWM_255\nEND_CUSTOM_VALUES +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_SL1\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_HALOT-MAGE-PRO\nFILEFORMAT_CXDLPV4\n\nSTART_CUSTOM_VALUES\nWaitTimeBeforeCure_2\nBottomLiftHeight_7\nLiftHeight_6\nBottomLiftSpeed_80\nLiftSpeed_80\nRetractSpeed_150\nBottomLightPWM_255\nLightPWM_255\nEND_CUSTOM_VALUES printer_settings_id = printer_technology = SLA printer_variant = default @@ -38,5 +38,7 @@ relative_correction = 1,1 relative_correction_x = 1 relative_correction_y = 1 relative_correction_z = 1 +sla_archive_format = SL1 +sla_output_precision = 0.001 slow_tilt_time = 8 -thumbnails = 116x116,90x290,90x290 +thumbnails = 120x120,300x300 diff --git a/PrusaSlicer/sla_print/Universal 0.10 - Light Supports.ini b/PrusaSlicer/sla_print/Universal 0.10 - Light Supports.ini index a7d2e159..06e15b55 100644 --- a/PrusaSlicer/sla_print/Universal 0.10 - Light Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.10 - Light Supports.ini @@ -1,4 +1,21 @@ -# generated by PrusaSlicer 2.3.0+win64 on 2021-03-24 at 03:00:47 UTC +# generated by PrusaSlicer 2.6.0+win64 on 2023-06-24 at 15:32:46 UTC +branchingsupport_base_diameter = 4 +branchingsupport_base_height = 1 +branchingsupport_base_safety_distance = 1 +branchingsupport_buildplate_only = 0 +branchingsupport_critical_angle = 45 +branchingsupport_head_front_diameter = 0.4 +branchingsupport_head_penetration = 0.2 +branchingsupport_head_width = 1 +branchingsupport_max_bridge_length = 5 +branchingsupport_max_bridges_on_pillar = 2 +branchingsupport_max_pillar_link_distance = 10 +branchingsupport_max_weight_on_model = 10 +branchingsupport_object_elevation = 5 +branchingsupport_pillar_connection_mode = dynamic +branchingsupport_pillar_diameter = 1 +branchingsupport_pillar_widening_factor = 0.5 +branchingsupport_small_pillar_diameter_percent = 50% compatible_printers = compatible_printers_condition = default_sla_print_profile = @@ -24,17 +41,20 @@ pad_wall_slope = 90 pad_wall_thickness = 0.2 sla_print_settings_id = slice_closing_radius = 0.005 +slicing_mode = regular support_base_diameter = 3 support_base_height = 1 support_base_safety_distance = 1 support_buildplate_only = 0 support_critical_angle = 45 +support_enforcers_only = 0 support_head_front_diameter = 0.6 support_head_penetration = 0.4 support_head_width = 3 support_max_bridge_length = 10 support_max_bridges_on_pillar = 3 support_max_pillar_link_distance = 10 +support_max_weight_on_model = 10 support_object_elevation = 5 support_pillar_connection_mode = zigzag support_pillar_diameter = 1.3 @@ -42,4 +62,5 @@ support_pillar_widening_factor = 0 support_points_density_relative = 100 support_points_minimal_distance = 1 support_small_pillar_diameter_percent = 60% +support_tree_type = default supports_enable = 1 diff --git a/README.md b/README.md index fcce00ea..075e1321 100644 --- a/README.md +++ b/README.md @@ -99,58 +99,26 @@ But also, I need victims for test subject. Proceed at your own risk! # Known File Formats -- SL1 (PrusaSlicer) -- SL1S (PrusaSlicer) -- Zip (Chitubox) -- Photon (Chitubox) -- Photons (Chitubox) -- CBDDLP (Chitubox) -- CTB (Chitubox) -- PHZ (Chitubox) -- FDG (Voxelab) -- PWS (Photon Workshop) -- PW0 (Photon Workshop) -- PWX (Photon Workshop) -- DLP (Photon Workshop) -- DL2P (Photon Workshop) -- PWMO (Photon Workshop) -- PWMA (Photon Workshop) -- PWMS (Photon Workshop) -- PWMX (Photon Workshop) -- PMX2 (Photon Workshop) -- PWMB (Photon Workshop) -- PWSQ (Photon Workshop) -- PX6S (Photon Workshop) -- PM3 (Photon Workshop) -- PM3N (Photon Workshop) -- PM3M (Photon Workshop) -- PM3R (Photon Workshop) -- PM5 (Photon Workshop) -- PM5S (Photon Workshop) -- PWC (Photon Workshop) +- SL1, SL1S (PrusaSlicer) +- Photon, Photons, CBDDLP, CTB, PHZ, FDG, ZIP (Chitubox) +- PWS, PW0, PWX, DLP, DL2P, PWMO, PWMA, PWMS, PWMX, PMX2, PWMB, PWSQ, PX6S, PM3, PM3N, PM3M, PM3R, PM5, PM5S, PWC (Photon Workshop) - JXS (GKone Slicer) - ZCode (UnizMaker) - ZCodex (Z-Suite) -- CWS (NovaMaker) -- RGB.CWS (Nova Bene4 Mono / Elfin2 Mono SE) +- CWS (NovaMaker), RGB.CWS (Nova Bene4 Mono / Elfin2 Mono SE) - XML.CWS (Wanhao Workshop) - MDLP (Makerbase MKS-DLP v1) - GR1 (GR1 Workshop) -- CXDLP (Creality Box) +- CXDLP, CXDLPV4 (Creality Box) - GOO (Elegoo) -- LGS (Longer Orange 10) -- LGS30 (Longer Orange 30) -- LGS120 (Longer Orange 120) -- LGS4K (Longer Orange 4K & mono) +- LGS (Longer Orange 10), LGS30 (Longer Orange 30), LGS120 (Longer Orange 120), LGS4K (Longer Orange 4K & mono) - Flashforge SVGX -- Anet N4 -- Anet N7 -- ZIP (Generic / Phrozen Zip) -- VDA.ZIP (Voxeldance Additive) +- Anet N4, Anet N7 - OSLA (Open SLA universal binary file) -- VDT (Voxeldance Tango) - OSF (Vlare Open File Format) - UVJ (Vendor-neutral format for manual manipulation) +- VDT (Voxeldance Tango), VDA.ZIP (Voxeldance Additive) +- ZIP (Generic / Phrozen Zip) - Image files (png, jpg, jpeg, jp2, tif, bmp, pbm, pgm, ras, sr) # PrusaSlicer diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7f671e2e..a193921d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,9 +1,12 @@ -- (Add) File - Rename: Allow to rename the current file with a new name (Ctrl + F2) -- (Improvement) Tool - Edit print parameters: It now apply settings without close the window, allowing user to do continuous work. After all editing is done the user must manually close the window (#731) -- (Improvement) Resin traps and suction cups: Optimization of contour grouping will now make the detection faster if it contain a large number of contours -- (Change) Lower the default setting for binary threshold for resin traps, from 127 to 100 -- (Fix) macOS: Unable to have settings on Monterey or above due the settings folder no longer exists on recent systems. (#728) - Your current settings will not be automatically transferred to the new location, to do such please copy them over or use the following command before upgrade: `mv "$HOME/.local/share/UVtools" "$HOME/Library/Application Support"` - If you already ran UVtools and would like to transfer old settings, use: `cp -Rf "$HOME/.local/share/UVtools/" "$HOME/Library/Application Support/UVtools/"` -- (Upgrade) .NET from 6.0.16 to 6.0.18 +- **File formats:** + - (Add) CXDLPv4 format + - (Improvement) Add missing keys for SL1 format from PrusaSlicer 2.6.0 +- **UI**: + - (Add) About - Terms of Use + - (Add) Outline: Enclosing circles + - (Improvement) Minor alteration on how current layer preview cache the contours + - (Improvement) Revamp tool Edit print parameters + - (Improvement) Revamp message boxes + - (Remove) Avalonia.MessageBox dependency in favor of use our own message box +- (Change) PrusaSlicer printers - Creality Halot Mage/Pro: Use CXDLPV4 format instead ENCRYPTED.CTB diff --git a/Scripts/010 Editor/cxdlpv4.bt b/Scripts/010 Editor/cxdlpv4.bt index 64501d9c..d8f4e545 100644 --- a/Scripts/010 Editor/cxdlpv4.bt +++ b/Scripts/010 Editor/cxdlpv4.bt @@ -7,6 +7,8 @@ LittleEndian(); +enum BOOL { False, True }; + struct HEADER { BigEndian(); uint32 MagicSize ; @@ -24,8 +26,8 @@ struct HEADER { float BedSizeY ; float BedSizeZ ; - float TotalHeightMilimeter ; - float LayerHeightMilimeter ; + float PrintHeight ; + float LayerHeight ; uint BottomLayersCount ; uint PreviewSmallOffsetAddress ; @@ -45,14 +47,14 @@ struct HEADER { } header; struct PREVIEW { - uint ResolutionX ; - uint ResolutionY ; - uint ImageOffset ; - uint ImageLength ; - uint Unknown1 ; - uint Unknown2 ; - uint Unknown3 ; - uint Unknown4 ; + uint ResolutionX ; + uint ResolutionY ; + uint ImageOffset ; + uint ImageLength ; + uint Unknown1 ; + uint Unknown2 ; + uint Unknown3 ; + uint Unknown4 ; ubyte Data[ImageLength] ; }; @@ -79,26 +81,26 @@ struct PRINT_PARAMETERS { }; struct SLICER_INFO { - float BottomLiftHeight2 ; - float BottomLiftSpeed2 ; - float LiftHeight2 ; - float LiftSpeed2 ; - float RetractHeight2 ; - float RetractSpeed2 ; - float RestTimeAfterLift ; - - uint PerLayerSettings ; // 0 to not support, 1 to support - uint TimestampMinutes ; - uint AntiAliasLevel ; - uint SoftwareVersion ; // 0 - float RestTimeAfterRetract ; - float RestTimeBeforeLift ; - float BottomExposureTime ; - float ExposureTime ; - float RestTimeAfterLift ; - uint TransitionLayerCount ; - uint Padding ; - uint Padding ; + float BottomLiftHeight2 ; + float BottomLiftSpeed2 ; + float LiftHeight2 ; + float LiftSpeed2 ; + float RetractHeight2 ; + float RetractSpeed2 ; + float RestTimeAfterLift ; + + BOOL PerLayerSettings ; // 0 to not support, 1 to support + uint TimestampMinutes ; + uint AntiAliasLevel ; + uint SoftwareVersion ; // 0 + float RestTimeAfterRetract ; + float RestTimeBeforeLift ; + float BottomExposureTime ; + float ExposureTime ; + float RestTimeAfterLift ; + uint TransitionLayerCount ; + uint Padding ; + uint Padding ; }; if(header.PreviewSmallOffsetAddress > 0) @@ -123,31 +125,31 @@ if(header.SlicerOffset > 0){ SLICER_INFO SlicerInfo ; } -struct LAYER_DATA { - float LayerPositionZ ; - float LayerExposure ; +struct LAYER_DEF { + float PositionZ ; + float ExposureTime ; float LightOffSeconds ; uint DataAddress ; uint DataSize ; - uint DataType ; + uint DataType ; // pixel=0,line=1, segment=2 uint CentroidDistance ; - uint TotalArea ; + uint LargestArea ; uint Unknown ; uint Unknown ; }; -struct LAYER_DATAEX { - float LiftHeight ; - float LiftSpeed ; - float LiftHeight2 ; - float LiftSpeed2 ; - float RetractSpeed ; - float RetractHeight2 ; - float RetractSpeed2 ; - float RestTimeBeforeLift ; - float RestTimeAfterLift ; - float RestTimeAfterRetract ; - float LightPWM ; +struct LAYER_DEFEX { + float LiftHeight ; + float LiftSpeed ; + float LiftHeight2 ; + float LiftSpeed2 ; + float RetractSpeed ; + float RetractHeight2 ; + float RetractSpeed2 ; + float RestTimeBeforeLift ; + float RestTimeAfterLift ; + float RestTimeAfterRetract ; + float LightPWM ; }; @@ -159,7 +161,7 @@ FSeek(header.LayersDefinitionOffsetAddress); struct LAYERS { local uint i; for( i = 0; i < header.LayerCount; i++ ){ - LAYER_DATA layerData ; + LAYER_DEF layerData ; } } layers; @@ -167,7 +169,7 @@ struct LAYERSEX { local uint i; for( i = 0; i < header.LayerCount; i++ ){ - LAYER_DATAEX layerDataEx; + LAYER_DEFEX layerDataEx; LAYER_RLE lD(layers.layerData[i].DataSize - 44); } @@ -175,5 +177,6 @@ struct LAYERSEX { BigEndian(); +FSeek(FileSize()-4); uint CheckSum ; LittleEndian(); \ No newline at end of file diff --git a/Scripts/UVtools.ScriptSample/ScriptAdvancedDialogSample.cs b/Scripts/UVtools.ScriptSample/ScriptAdvancedDialogSample.cs index 37c25012..ff7b9ae2 100644 --- a/Scripts/UVtools.ScriptSample/ScriptAdvancedDialogSample.cs +++ b/Scripts/UVtools.ScriptSample/ScriptAdvancedDialogSample.cs @@ -64,10 +64,10 @@ public bool ScriptExecute() // Trigger an message box to user, will also show in console runs but in text form var result = MessageBoxManager.Standard.ShowDialog("This is my script", "Script is about to start, are you sure you want to continue?\n" + - "This will destroy your file!", AbstractMessageBoxStandard.MessageButtons.YesNo).Result; + "This will destroy your file!", MessageButtons.YesNo).Result; // throw error without stack trace - if (result != AbstractMessageBoxStandard.MessageButtonResult.Yes) throw new MessageException("User wanted to abort the script :("); + if (result != MessageButtonResult.Yes) throw new MessageException("User wanted to abort the script :("); // Write some text to show after the operation has completed with success Operation.AfterCompleteReport = "My operation has performed the following changes:\n"; diff --git a/UVtools.AvaloniaControls/AdvancedImageBox.axaml b/UVtools.AvaloniaControls/AdvancedImageBox.axaml index ad3c8ea7..0001c38e 100644 --- a/UVtools.AvaloniaControls/AdvancedImageBox.axaml +++ b/UVtools.AvaloniaControls/AdvancedImageBox.axaml @@ -12,23 +12,21 @@ Name="ViewPort" Background="Transparent"/> - + - + - - - + + + + diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs index 191bc80d..2e3c26ea 100644 --- a/UVtools.WPF/App.axaml.cs +++ b/UVtools.WPF/App.axaml.cs @@ -22,6 +22,7 @@ using System.Linq; using System.Reflection; using System.Web; +using Avalonia.Media; using UVtools.Core; using UVtools.Core.FileFormats; using UVtools.Core.Managers; @@ -67,12 +68,12 @@ public enum ApplicationTheme public static readonly StyleInclude AppStyleLight = new(CreateAssemblyUri("/Assets/Styles")) { - Source = CreateAssemblyUri("/Assets/Styles/StylesLight.xaml") + Source = CreateAssemblyUri("/Assets/Styles/StylesLight.axaml") }; public static readonly StyleInclude AppStyleDark = new(CreateAssemblyUri("/Assets/Styles")) { - Source = CreateAssemblyUri("/Assets/Styles/StylesDark.xaml") + Source = CreateAssemblyUri("/Assets/Styles/StylesDark.axaml") }; public static FluentTheme Fluent = new(CreateAssemblyUri("/Styles")); @@ -244,13 +245,17 @@ public override void OnFrameworkInitializationCompleted() "fa-regular fa-frown", $"{About.Software} crashed due an unexpected {category.ToLowerInvariant()} error.\nYou can report this error if you find necessary.\nFind more details below:\n", bugDescription, + TextWrapping.NoWrap, new[] { MessageWindow.CreateLinkButtonAction("Report", "fa-solid fa-bug", $"https://github.com/sn4k3/UVtools/issues/new?template=bug_report_form.yml&title={HttpUtility.UrlEncode($"[Crash] {reader.ReadLine()}")}&system={HttpUtility.UrlEncode(system)}&bug_description={HttpUtility.UrlEncode($"```\n{bugDescription}\n```")}", () => Current?.Clipboard?.SetTextAsync($"```\n{bugDescription}\n```")), MessageWindow.CreateLinkButtonAction("Help", "fa-solid fa-question", "https://github.com/sn4k3/UVtools/discussions/categories/q-a", () => Current?.Clipboard?.SetTextAsync($"```\n{bugDescription}\n```")), MessageWindow.CreateButtonAction("Restart", "fa-solid fa-redo-alt", () => SystemAware.StartThisApplication()), MessageWindow.CreateCloseButton("fa-solid fa-sign-out-alt") - }); + }) + { + AboutButtonIsVisible = true + }; } else { @@ -344,12 +349,16 @@ private MessageWindow MissingOpenCVDependenciesWindow() "fa-regular fa-frown", $"{About.SoftwareWithVersionArch} [{SystemAware.OperatingSystemName}]\nUnable to run due one or more missing dependencies.\nTriggered by: libcvextern (OpenCV)", message, + TextWrapping.NoWrap, new[] { MessageWindow.CreateLinkButton("Open manual", "fa-brands fa-edge", "https://github.com/sn4k3/UVtools#requirements"), MessageWindow.CreateLinkButton("Ask for help", "fa-solid fa-question", "https://github.com/sn4k3/UVtools/discussions/categories/q-a"), MessageWindow.CreateCloseButton("fa-solid fa-sign-out-alt") - }); + }) + { + AboutButtonIsVisible = true + }; } #region Utilities diff --git a/UVtools.WPF/Assets/Styles/Styles.xaml b/UVtools.WPF/Assets/Styles/Styles.axaml similarity index 93% rename from UVtools.WPF/Assets/Styles/Styles.xaml rename to UVtools.WPF/Assets/Styles/Styles.axaml index e679be22..396bb4cf 100644 --- a/UVtools.WPF/Assets/Styles/Styles.xaml +++ b/UVtools.WPF/Assets/Styles/Styles.axaml @@ -1,5 +1,7 @@  + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:i="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia" + xmlns:uac="clr-namespace:UVtools.AvaloniaControls;assembly=UVtools.AvaloniaControls"> @@ -105,11 +107,6 @@ - --> - - + + + + @@ -343,5 +350,13 @@ + + + + \ No newline at end of file diff --git a/UVtools.WPF/Assets/Styles/StylesDark.xaml b/UVtools.WPF/Assets/Styles/StylesDark.axaml similarity index 100% rename from UVtools.WPF/Assets/Styles/StylesDark.xaml rename to UVtools.WPF/Assets/Styles/StylesDark.axaml diff --git a/UVtools.WPF/Assets/Styles/StylesLight.xaml b/UVtools.WPF/Assets/Styles/StylesLight.axaml similarity index 100% rename from UVtools.WPF/Assets/Styles/StylesLight.xaml rename to UVtools.WPF/Assets/Styles/StylesLight.axaml diff --git a/UVtools.WPF/Controls/ButtonWithIcon.cs b/UVtools.WPF/Controls/ButtonWithIcon.cs index c720fe1c..3c30c46d 100644 --- a/UVtools.WPF/Controls/ButtonWithIcon.cs +++ b/UVtools.WPF/Controls/ButtonWithIcon.cs @@ -10,7 +10,6 @@ using Avalonia.Layout; using Avalonia.Styling; using Avalonia.Threading; -using JetBrains.Annotations; using System; namespace UVtools.WPF.Controls; @@ -48,8 +47,7 @@ public IconPlacementType IconPlacement public static readonly StyledProperty IconProperty = AvaloniaProperty.Register(nameof(Icon)); - [CanBeNull] - public string Icon + public string? Icon { get => GetValue(IconProperty); set => SetValue(IconProperty, value); @@ -81,7 +79,7 @@ protected override void OnInitialized() }, DispatcherPriority.Loaded); } - public IControl MakeIcon() + public Projektanker.Icons.Avalonia.Icon MakeIcon() { return new Projektanker.Icons.Avalonia.Icon { Value = Icon }; } diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs b/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs index 02aa9aa7..75a02003 100644 --- a/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs +++ b/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs @@ -2,10 +2,10 @@ using Avalonia.Markup.Xaml; using Avalonia.Media.Imaging; using Avalonia.Threading; -using MessageBox.Avalonia.Enums; using System; using System.Linq; using System.Timers; +using UVtools.Core.Dialogs; using UVtools.Core.FileFormats; using UVtools.Core.Objects; using UVtools.Core.Operations; @@ -104,7 +104,7 @@ public async void GenerateExposureTable() if (await ParentWindow.MessageBoxQuestion( "This automatic exposure table generation will clear the current table data!\n" + "Do you want to continue?" - ) != ButtonResult.Yes) return; + ) != MessageButtonResult.Yes) return; } Operation.GenerateExposureTable(); @@ -136,7 +136,7 @@ public async void ExposureTableClearEntries() if (Operation.ExposureTable.Count <= 0) return; if (await ParentWindow.MessageBoxQuestion( $"Are you sure you want to all the {Operation.ExposureTable.Count} entries?" - ) != ButtonResult.Yes) return; + ) != MessageButtonResult.Yes) return; Operation.ExposureTable.Clear(); } @@ -146,7 +146,7 @@ public async void ExposureTableRemoveSelectedEntries() if (_exposureTable.SelectedItems.Count <= 0) return; if (await ParentWindow.MessageBoxQuestion( $"Are you sure you want to remove the {_exposureTable.SelectedItems.Count} selected entries?" - ) != ButtonResult.Yes) return; + ) != MessageButtonResult.Yes) return; Operation.ExposureTable.RemoveRange(_exposureTable.SelectedItems.Cast()); } diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateToleranceControl.axaml.cs b/UVtools.WPF/Controls/Calibrators/CalibrateToleranceControl.axaml.cs index 18bc91a3..a2470c3b 100644 --- a/UVtools.WPF/Controls/Calibrators/CalibrateToleranceControl.axaml.cs +++ b/UVtools.WPF/Controls/Calibrators/CalibrateToleranceControl.axaml.cs @@ -100,7 +100,7 @@ public void UpdatePreview() if (find is not null) { if (await ParentWindow.MessageBoxQuestion( - $"A profile with same name and/or values already exists, do you want to overwrite:\n{find}\nwith:\n{resize}\n?") != ButtonResult.Yes) return; + $"A profile with same name and/or values already exists, do you want to overwrite:\n{find}\nwith:\n{resize}\n?") != MessageButtonResult.Yes) return; OperationProfiles.RemoveProfile(resize, false); } diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml.cs b/UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml.cs index 4fc1cb6c..85f3a6bd 100644 --- a/UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml.cs +++ b/UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml.cs @@ -1,8 +1,8 @@ using Avalonia.Markup.Xaml; using Avalonia.Media.Imaging; using Avalonia.Threading; -using MessageBox.Avalonia.Enums; using System.Timers; +using UVtools.Core.Dialogs; using UVtools.Core.Operations; using UVtools.WPF.Controls.Tools; using UVtools.WPF.Extensions; @@ -99,7 +99,7 @@ public async void AddProfile() if (find is not null) { if (await ParentWindow.MessageBoxQuestion( - $"A profile with same name and/or values already exists, do you want to overwrite:\n{find}\nwith:\n{resize}\n?") != ButtonResult.Yes) return; + $"A profile with same name and/or values already exists, do you want to overwrite:\n{find}\nwith:\n{resize}\n?") != MessageButtonResult.Yes) return; OperationProfiles.RemoveProfile(resize, false); } diff --git a/UVtools.WPF/Controls/RenameFileControl.axaml.cs b/UVtools.WPF/Controls/RenameFileControl.axaml.cs index e778b9c7..656f32c9 100644 --- a/UVtools.WPF/Controls/RenameFileControl.axaml.cs +++ b/UVtools.WPF/Controls/RenameFileControl.axaml.cs @@ -3,7 +3,7 @@ using System.Text; using System.Threading.Tasks; using Avalonia.Markup.Xaml; -using MessageBox.Avalonia.Enums; +using UVtools.Core.Dialogs; using UVtools.Core.FileFormats; using UVtools.WPF.Extensions; @@ -99,7 +99,7 @@ public override async Task OnBeforeProcess() if (!File.Exists(NewFilePath)) return true; if (await ParentWindow.MessageBoxQuestion( $"The file \"{_newFileNameNoExt}\" already exists, do you want to overwrite?", - "File already exists") == ButtonResult.Yes) + "File already exists") == MessageButtonResult.Yes) { Overwrite = true; return true; diff --git a/UVtools.WPF/Controls/Tools/ToolDoubleExposureControl.axaml b/UVtools.WPF/Controls/Tools/ToolDoubleExposureControl.axaml index 5173853d..af0d4e7e 100644 --- a/UVtools.WPF/Controls/Tools/ToolDoubleExposureControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolDoubleExposureControl.axaml @@ -25,7 +25,7 @@ VerticalAlignment="Center" Text="1º exposure:"/> - - + - + Content="Propagate modifications to layers"/> - + - + - + - + - + - + - + + - - - - - - + + + + + + + diff --git a/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml.cs index ebbce927..2b924855 100644 --- a/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml.cs @@ -1,12 +1,15 @@ using Avalonia; using Avalonia.Controls; -using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Markup.Xaml; using System; +using System.Collections.Generic; using System.ComponentModel; -using System.Globalization; +using System.Linq; using System.Threading.Tasks; +using Projektanker.Icons.Avalonia; +using UVtools.AvaloniaControls; +using UVtools.Core.Extensions; using UVtools.Core.FileFormats; using UVtools.Core.Operations; using UVtools.WPF.Windows; @@ -18,160 +21,281 @@ public class ToolEditParametersControl : ToolControl public OperationEditParameters Operation => BaseOperation as OperationEditParameters; public RowControl[] RowControls; - private Grid grid; + private Grid globalGrid; + private Grid perLayerGrid; public sealed class RowControl { public FileFormat.PrintParameterModifier Modifier { get; } + public Control FirstColumn; + public TextBlock Name { get; } - public TextBlock OldValue { get; } - public NumericUpDown NewValue { get; } - //public TextBlock Unit { get; } - public Button ResetButton { get; } + public ExtendedNumericUpDown NumericUpDown { get; } public RowControl(FileFormat.PrintParameterModifier modifier) { Modifier = modifier; modifier.NewValue = Math.Clamp(modifier.OldValue, modifier.Minimum, modifier.Maximum); - + var label = ReferenceEquals(modifier, FileFormat.PrintParameterModifier.BottomLayerCount) + ? modifier.Name: + modifier.Name.Replace("Bottom ", string.Empty, StringComparison.InvariantCultureIgnoreCase).FirstCharToUpper(); + + var stackPanel = new StackPanel{ Orientation = Orientation.Horizontal, Spacing = 5 }; + + if (ReferenceEquals(modifier, FileFormat.PrintParameterModifier.PositionZ)) + { + stackPanel.Children.Add(new Icon { Value = "fa-solid fa-stairs" }); + } + else if (ReferenceEquals(modifier, FileFormat.PrintParameterModifier.BottomLayerCount)) + { + stackPanel.Children.Add(new Icon { Value = "fa-solid fa-fire-burner" }); + } + else if (ReferenceEquals(modifier, FileFormat.PrintParameterModifier.TransitionLayerCount)) + { + stackPanel.Children.Add(new Icon { Value = "fa-solid fa-layer-group" }); + } + else if (label.Contains("delay", StringComparison.InvariantCultureIgnoreCase) || label.Contains("wait", StringComparison.InvariantCultureIgnoreCase)) + { + stackPanel.Children.Add(new Icon { Value = "fa-solid fa-stopwatch" }); + } + else if (label.Contains("exposure", StringComparison.InvariantCultureIgnoreCase)) + { + stackPanel.Children.Add(new Icon { Value = "fa-regular fa-eye" }); + } + else if (label.Contains("lift", StringComparison.InvariantCultureIgnoreCase)) + { + stackPanel.Children.Add(new Icon { Value = "fa-solid fa-arrow-up-from-bracket" }); + } + else if (label.Contains("retract", StringComparison.InvariantCultureIgnoreCase)) + { + stackPanel.Children.Add(new Icon { Value = "fa-solid fa-arrows-down-to-line" }); + } + else if (label.Contains("pwm", StringComparison.InvariantCultureIgnoreCase)) + { + stackPanel.Children.Add(new Icon { Value = "fa-regular fa-sun" }); + } + Name = new TextBlock { - Text = $"{modifier.Name}:", + Text = $"{label}:", VerticalAlignment = VerticalAlignment.Center, - Padding = new Thickness(15, 0), + Padding = new Thickness(0,0,10, 0), Tag = this, }; - if(!string.IsNullOrWhiteSpace(modifier.Description)) ToolTip.SetTip(Name, modifier.Description); + stackPanel.Children.Add(Name); - OldValue = new TextBlock - { - Text = modifier.OldValue.ToString(CultureInfo.InvariantCulture), - VerticalAlignment = VerticalAlignment.Center, - HorizontalAlignment = HorizontalAlignment.Center, - //Padding = new Thickness(15, 0), - Tag = this - }; + FirstColumn = stackPanel; - NewValue = new NumericUpDown + + + if (!string.IsNullOrWhiteSpace(modifier.Description)) ToolTip.SetTip(Name, modifier.Description); + + NumericUpDown = new ExtendedNumericUpDown { //DecimalPlaces = modifier.DecimalPlates, VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Stretch, + Margin = new Thickness(0,2,0,0), Minimum = (double) modifier.Minimum, Maximum = (double) modifier.Maximum, Increment = modifier.Increment, + FormatString = modifier.DecimalPlates > 0 ? $"F{modifier.DecimalPlates}" : string.Empty, Value = (double)modifier.NewValue, + ValueUnit = modifier.ValueUnit, + IsInitialValueVisible = true, + ResetAutoVisibility = true, Tag = this, //Width = 100, + MinWidth = 320, ClipValueToMinMax = true }; - if (modifier.DecimalPlates > 0) - { - NewValue.FormatString = $"F{modifier.DecimalPlates}"; - } - - if (!string.IsNullOrWhiteSpace(modifier.ValueUnit)) - { - var valueLabel = modifier.ValueUnit == "☀" ? "sun" : modifier.ValueUnit.Replace("/", string.Empty); - NewValue.Classes = new Classes("ValueLabel", $"ValueLabel_{valueLabel}"); - } - - /*Unit = new TextBlock - { - Text = modifier.ValueUnit, - VerticalAlignment = VerticalAlignment.Center, - Padding = new Thickness(10, 0, 15, 0), - Tag = this - };*/ - ResetButton = new Button - { - IsVisible = false, - IsEnabled = false, - VerticalAlignment = VerticalAlignment.Center, - Tag = this, - Padding = new Thickness(5), - Content = new Projektanker.Icons.Avalonia.Icon{Value = "fa-solid fa-undo-alt"}, - HorizontalAlignment = HorizontalAlignment.Stretch - }; - ResetButton.Click += ResetButtonOnClick; - NewValue.ValueChanged += NewValueOnValueChanged; + NumericUpDown.ValueChanged += NewValueOnValueChanged; } private void NewValueOnValueChanged(object? sender, NumericUpDownValueChangedEventArgs e) { - Modifier.NewValue = (decimal) NewValue.Value; - ResetButton.IsVisible = ResetButton.IsEnabled = Modifier.HasChanged; + Modifier.NewValue = (decimal) NumericUpDown.Value; } + } - private void ResetButtonOnClick(object? sender, RoutedEventArgs e) + private static (FileFormat.PrintParameterModifier modifierLeft, FileFormat.PrintParameterModifier modifierRight)[] TSMCModifierPairs = { - NewValue.Value = (double) Modifier.OldValue; - NewValue.Focus(); - } - } + new (FileFormat.PrintParameterModifier.BottomLiftHeight, FileFormat.PrintParameterModifier.BottomLiftHeight2), + new (FileFormat.PrintParameterModifier.BottomLiftSpeed, FileFormat.PrintParameterModifier.BottomLiftSpeed2), + //new (FileFormat.PrintParameterModifier.BottomRetractHeight, FileFormat.PrintParameterModifier.BottomRetractHeight2), + new (FileFormat.PrintParameterModifier.BottomRetractSpeed, FileFormat.PrintParameterModifier.BottomRetractSpeed2), + + new (FileFormat.PrintParameterModifier.LiftHeight, FileFormat.PrintParameterModifier.LiftHeight2), + new (FileFormat.PrintParameterModifier.LiftSpeed, FileFormat.PrintParameterModifier.LiftSpeed2), + //new (FileFormat.PrintParameterModifier.RetractHeight, FileFormat.PrintParameterModifier.RetractHeight2), + new (FileFormat.PrintParameterModifier.RetractSpeed, FileFormat.PrintParameterModifier.RetractSpeed2), + }; public ToolEditParametersControl() { BaseOperation = new OperationEditParameters(SlicerFile); if (!ValidateSpawn()) return; InitializeComponent(); - - grid = this.FindControl("grid"); + + globalGrid = this.FindControl("GlobalGrid"); + perLayerGrid = this.FindControl("PerLayerGrid"); } public void PopulateGrid() { - const byte cols = 4; - if (grid.Children.Count > cols) + var grid = Operation.PerLayerOverride ? perLayerGrid : globalGrid; + + var gridCols = 2 - Convert.ToInt32(Operation.PerLayerOverride); + if (grid.Children.Count > gridCols) { - grid.Children.RemoveRange(cols, grid.Children.Count - cols); + grid.Children.RemoveRange(gridCols, grid.Children.Count - gridCols); } if (grid.RowDefinitions.Count > 1) { - grid.RowDefinitions.RemoveRange(1, grid.RowDefinitions.Count-1); + grid.RowDefinitions.RemoveRange(1, grid.RowDefinitions.Count - 1); } - int rowIndex = 1; RowControls = new RowControl[Operation.Modifiers.Length]; - //table.RowCount = Operation.Modifiers.Length+1; + var addedModifiers = new List(); + + var controlIndex = 0; + var rowDict = new Dictionary + { + { true, 1 }, + { false, 1 }, + }; + + Control CreateTSMCfields(RowControl left, RowControl right) + { + var grid = new Grid + { + ColumnDefinitions = Operation.PerLayerOverride + ? new ColumnDefinitions("*,Auto,*") + : new ColumnDefinitions("Auto,Auto,*") + }; + + var textBox = new TextBlock + { + Text = ">", + Margin = new Thickness(5, 0), + VerticalAlignment = VerticalAlignment.Center + }; + + left.NumericUpDown.MinWidth /= 2; + left.NumericUpDown.ShowButtonSpinner = false; + right.NumericUpDown.MinWidth /= 2; + right.NumericUpDown.ShowButtonSpinner = false; + + if (!Operation.PerLayerOverride) + { + left.NumericUpDown.ValueUnit = null; + left.NumericUpDown.IsInitialValueVisible = false; + left.NumericUpDown.Width = 110; + + right.NumericUpDown.IsInitialValueVisible = false; + right.NumericUpDown.Width = 180; + } + + grid.Children.Add(left.NumericUpDown); + grid.Children.Add(textBox); + grid.Children.Add(right.NumericUpDown); + + Grid.SetColumn(left.NumericUpDown, 0); + Grid.SetColumn(textBox, 1); + Grid.SetColumn(right.NumericUpDown, 2); + + return grid; + } + foreach (var modifier in Operation.Modifiers) { + if(addedModifiers.Contains(modifier)) continue; + grid.RowDefinitions.Add(new RowDefinition()); - byte column = 0; - - var rowControl = new RowControl(modifier); - grid.Children.Add(rowControl.Name); - grid.Children.Add(rowControl.OldValue); - grid.Children.Add(rowControl.NewValue); - //grid.Children.Add(rowControl.Unit); - grid.Children.Add(rowControl.ResetButton); - Grid.SetRow(rowControl.Name, rowIndex); - Grid.SetColumn(rowControl.Name, column++); - - Grid.SetRow(rowControl.OldValue, rowIndex); - Grid.SetColumn(rowControl.OldValue, column++); - - Grid.SetRow(rowControl.NewValue, rowIndex); - Grid.SetColumn(rowControl.NewValue, column++); - - //Grid.SetRow(rowControl.Unit, rowIndex); - //Grid.SetColumn(rowControl.Unit, column++); - - Grid.SetRow(rowControl.ResetButton, rowIndex); - Grid.SetColumn(rowControl.ResetButton, column++); - /*table.Controls.Add(rowControl.Name, column++, rowIndex); - table.Controls.Add(rowControl.OldValue, column++, rowIndex); - table.Controls.Add(rowControl.NewValue, column++, rowIndex); - table.Controls.Add(rowControl.Unit, column++, rowIndex); - table.Controls.Add(rowControl.ResetButton, column++, rowIndex); - */ - RowControls[rowIndex - 1] = rowControl; - - rowIndex++; + + bool isBottomLayer = !Operation.PerLayerOverride && modifier.Name.Contains("Bottom"); + int column = isBottomLayer || Operation.PerLayerOverride ? 0 : 3; + + var rowControl1 = new RowControl(modifier); + RowControl? rowControl2 = null; + grid.Children.Add(rowControl1.FirstColumn); + + Control valueContainer = rowControl1.NumericUpDown; + + Grid.SetRow(rowControl1.FirstColumn, rowDict[isBottomLayer]); + Grid.SetColumn(rowControl1.FirstColumn, column++); + + foreach (var modifierPair in TSMCModifierPairs) + { + if (!ReferenceEquals(modifierPair.modifierLeft, modifier)) continue; + if(!Operation.Modifiers.Contains(modifierPair.modifierRight)) break; + + rowControl2 = new RowControl(modifierPair.modifierRight); + valueContainer = CreateTSMCfields(rowControl1, rowControl2); + + break; + } + + if (rowControl2 is null && ReferenceEquals(modifier, FileFormat.PrintParameterModifier.BottomRetractHeight2)) + { + rowControl1.Name.Text = rowControl1.Name.Text.Replace("2) ", string.Empty).FirstCharToUpper(); + var rowControlVirtual = new RowControl(FileFormat.PrintParameterModifier.BottomRetractHeight2.Clone()) + { + NumericUpDown = + { + Value = Operation.PerLayerOverride + ? SlicerFile[Operation.LayerIndexStart].RetractHeight + : SlicerFile.BottomRetractHeight, + IsReadOnly = true, + IsEnabled = false + } + }; + if (Operation.PerLayerOverride) + { + rowControlVirtual.NumericUpDown.IsInitialValueVisible = false; + } + rowControlVirtual.NumericUpDown.RedefineOldValue(); + valueContainer = CreateTSMCfields(rowControlVirtual, rowControl1); + } + else if (rowControl2 is null && ReferenceEquals(modifier, FileFormat.PrintParameterModifier.RetractHeight2)) + { + rowControl1.Name.Text = rowControl1.Name.Text.Replace("2) ", string.Empty).FirstCharToUpper(); + var rowControlVirtual = new RowControl(FileFormat.PrintParameterModifier.RetractHeight2.Clone()) + { + NumericUpDown = + { + Value = Operation.PerLayerOverride + ? SlicerFile[Operation.LayerIndexStart].RetractHeight + : SlicerFile.RetractHeight, + IsReadOnly = true, + IsEnabled = false + } + }; + if (Operation.PerLayerOverride) + { + rowControlVirtual.NumericUpDown.IsInitialValueVisible = false; + } + rowControlVirtual.NumericUpDown.RedefineOldValue(); + valueContainer = CreateTSMCfields(rowControlVirtual, rowControl1); + } + + + grid.Children.Add(valueContainer); + Grid.SetRow(valueContainer, rowDict[isBottomLayer]); + Grid.SetColumn(valueContainer, column); + + RowControls[controlIndex++] = rowControl1; + addedModifiers.Add(modifier); + if (rowControl2 is not null) + { + RowControls[controlIndex++] = rowControl2; + addedModifiers.Add(rowControl2.Modifier); + } + rowDict[isBottomLayer]++; } } diff --git a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs index 70a73ba0..e67f2fba 100644 --- a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs @@ -126,7 +126,7 @@ private void InitializeComponent() message.Content += "\nDo you want to remove all invalid files from list?"; - if (await ParentWindow.MessageBoxQuestion(message.ToString()) == ButtonResult.Yes) + if (await ParentWindow.MessageBoxQuestion(message.ToString()) == MessageButtonResult.Yes) { ConcurrentBag result = (ConcurrentBag)message.Tag; foreach (var file in result) diff --git a/UVtools.WPF/Extensions/WindowExtensions.cs b/UVtools.WPF/Extensions/WindowExtensions.cs index 782c7c4b..feab569d 100644 --- a/UVtools.WPF/Extensions/WindowExtensions.cs +++ b/UVtools.WPF/Extensions/WindowExtensions.cs @@ -6,19 +6,21 @@ * of this license document, but changing it is not allowed. */ +using System; using Avalonia.Controls; using Avalonia.Platform; using Avalonia.Threading; -using MessageBox.Avalonia.DTO; -using MessageBox.Avalonia.Enums; using System.Threading; using System.Threading.Tasks; +using UVtools.Core.Dialogs; +using UVtools.WPF.Controls; +using UVtools.WPF.Windows; namespace UVtools.WPF.Extensions; public static class WindowExtensions { - public static async Task MessageBoxGeneric(this Window window, string message, string title = null, string header = null, + /*public static async Task MessageBoxGeneric(this Window window, string message, string title = null, string header = null, ButtonEnum buttons = ButtonEnum.Ok, Icon icon = Icon.None, bool markdown = false, bool topMost = false, WindowStartupLocation location = WindowStartupLocation.CenterOwner) { var messageBoxStandardWindow = MessageBox.Avalonia.MessageBoxManager.GetMessageBoxStandardWindow( @@ -40,31 +42,99 @@ public static async Task MessageBoxGeneric(this Window window, str Topmost = topMost }); return await messageBoxStandardWindow.ShowDialog(window); + }*/ + + public static async Task MessageBoxGeneric(this Window window, string message, string title = null, string header = null, + MessageButtons buttons = MessageButtons.Ok, string? headerIcon = null, bool markdown = false, bool topMost = false, WindowStartupLocation location = WindowStartupLocation.CenterOwner) + { + ButtonWithIcon[] msgButtons = null; + + switch (buttons) + { + case MessageButtons.Ok: + msgButtons = new[] + { + MessageWindow.CreateOkButton(isCancel: true) + }; + break; + case MessageButtons.YesNo: + msgButtons = new[] + { + MessageWindow.CreateYesButton(), + MessageWindow.CreateNoButton(isCancel: true) + }; + break; + case MessageButtons.OkCancel: + msgButtons = new[] + { + MessageWindow.CreateOkButton(), + MessageWindow.CreateCancelButton() + }; + break; + case MessageButtons.OkAbort: + msgButtons = new[] + { + MessageWindow.CreateOkButton(), + MessageWindow.CreateAbortButton() + }; + break; + case MessageButtons.YesNoCancel: + msgButtons = new[] + { + MessageWindow.CreateYesButton(), + MessageWindow.CreateNoButton(), + MessageWindow.CreateCancelButton() + }; + break; + case MessageButtons.YesNoAbort: + msgButtons = new[] + { + MessageWindow.CreateYesButton(), + MessageWindow.CreateNoButton(), + MessageWindow.CreateAbortButton() + }; + break; + default: + throw new ArgumentOutOfRangeException(nameof(buttons), buttons, null); + } + + var messageWindow = new MessageWindow(title ?? window.Title, headerIcon, header, message, msgButtons, markdown) + { + WindowStartupLocation = location, + Topmost = topMost + }; + + var result = (await messageWindow.ShowDialog(window))?.Tag; + if (result is null) return MessageButtonResult.Cancel; + if (result is not MessageButtonResult resultButton) throw new NotImplementedException($"Message box interface is not correctly implemented, expecting a button result but got {result}."); + + return resultButton; } - public static async Task MessageBoxInfo(this Window window, string message, string title = null, ButtonEnum buttons = ButtonEnum.Ok, bool markdown = false, bool topMost = false) - => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Information", null, buttons, Icon.Info, markdown, topMost, WindowStartupLocation.CenterOwner); - public static async Task MessageBoxError(this Window window, string message, string title = null, ButtonEnum buttons = ButtonEnum.Ok, bool markdown = false, bool topMost = false) - => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Error", null, buttons, Icon.Error, markdown, topMost, WindowStartupLocation.CenterOwner); + public static async Task MessageBoxInfo(this Window window, string message, string title = null, MessageButtons buttons = MessageButtons.Ok, bool markdown = false, bool topMost = false) + => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Information", null, buttons, MessageWindow.IconHeaderInformation, markdown, topMost, WindowStartupLocation.CenterOwner); + + public static async Task MessageBoxError(this Window window, string message, string title = null, MessageButtons buttons = MessageButtons.Ok, bool markdown = false, bool topMost = false) + => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Error", null, buttons, MessageWindow.IconHeaderError, markdown, topMost, WindowStartupLocation.CenterOwner); - public static async Task MessageBoxQuestion(this Window window, string message, string title = null, ButtonEnum buttons = ButtonEnum.YesNo, bool markdown = false, bool topMost = false) - => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Question", null, buttons, Icon.Question, markdown, topMost, WindowStartupLocation.CenterOwner); + public static async Task MessageBoxQuestion(this Window window, string message, string title = null, MessageButtons buttons = MessageButtons.YesNo, bool markdown = false, bool topMost = false) + => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Question", null, buttons, MessageWindow.IconHeaderQuestion, markdown, topMost, WindowStartupLocation.CenterOwner); - public static async Task MessageBoxWaring(this Window window, string message, string title = null, ButtonEnum buttons = ButtonEnum.Ok, bool markdown = false, bool topMost = false) - => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Question", null, buttons, Icon.Warning, markdown, topMost, WindowStartupLocation.CenterOwner); + public static async Task MessageBoxWaring(this Window window, string message, string title = null, MessageButtons buttons = MessageButtons.Ok, bool markdown = false, bool topMost = false) + => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Question", null, buttons, MessageWindow.IconHeaderWarning, markdown, topMost, WindowStartupLocation.CenterOwner); - public static async Task MessageBoxWithHeaderInfo(this Window window, string header, string message, string title = null, ButtonEnum buttons = ButtonEnum.Ok, bool markdown = false, bool topMost = false) - => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Information", header, buttons, Icon.Info, markdown, topMost, WindowStartupLocation.CenterOwner); + public static async Task MessageBoxWithHeaderInfo(this Window window, string header, string message, string title = null, MessageButtons buttons = MessageButtons.Ok, bool markdown = false, bool topMost = false) + => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Information", header, buttons, MessageWindow.IconHeaderInformation, markdown, topMost, WindowStartupLocation.CenterOwner); - public static async Task MessageBoxWithHeaderError(this Window window, string header, string message, string title = null, ButtonEnum buttons = ButtonEnum.Ok, bool markdown = false, bool topMost = false) - => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Error", header, buttons, Icon.Error, markdown, topMost, WindowStartupLocation.CenterOwner); + public static async Task MessageBoxWithHeaderError(this Window window, string header, string message, string title = null, MessageButtons buttons = MessageButtons.Ok, bool markdown = false, bool topMost = false) + => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Error", header, buttons, MessageWindow.IconHeaderError, markdown, topMost, WindowStartupLocation.CenterOwner); - public static async Task MessageBoxWithHeaderQuestion(this Window window, string header, string message, string title = null, ButtonEnum buttons = ButtonEnum.YesNo, bool markdown = false, bool topMost = false) - => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Question", header, buttons, Icon.Question, markdown, topMost, WindowStartupLocation.CenterOwner); + public static async Task MessageBoxWithHeaderQuestion(this Window window, string header, string message, string title = null, MessageButtons buttons = MessageButtons.YesNo, bool markdown = false, bool topMost = false) + => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Question", header, buttons, MessageWindow.IconHeaderQuestion, markdown, topMost, WindowStartupLocation.CenterOwner); - public static async Task MessageBoxWithHeaderWaring(this Window window, string header, string message, string title = null, ButtonEnum buttons = ButtonEnum.Ok, bool markdown = false, bool topMost = false) - => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Question", header, buttons, Icon.Warning, markdown, topMost, WindowStartupLocation.CenterOwner); + public static async Task MessageBoxWithHeaderWaring(this Window window, string header, string message, string title = null, MessageButtons buttons = MessageButtons.Ok, bool markdown = false, bool topMost = false) + => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Question", header, buttons, MessageWindow.IconHeaderWarning, markdown, topMost, WindowStartupLocation.CenterOwner); public static void ShowDialogSync(this Window window, Window parent = null) diff --git a/UVtools.WPF/LayerCache.cs b/UVtools.WPF/LayerCache.cs index 5b99fc6b..e4af9e48 100644 --- a/UVtools.WPF/LayerCache.cs +++ b/UVtools.WPF/LayerCache.cs @@ -10,7 +10,6 @@ using Avalonia.Skia; using Emgu.CV; using Emgu.CV.CvEnum; -using Emgu.CV.Util; using SkiaSharp; using UVtools.Core.Extensions; using UVtools.Core.Layers; @@ -20,8 +19,6 @@ namespace UVtools.WPF; public sealed class LayerCache { private Layer _layer; - private VectorOfVectorOfPoint _layerContours; - private int[,] _layerContourHierarchy; //private SKCanvas _canvas; private WriteableBitmap _bitmap; @@ -73,35 +70,7 @@ public SKCanvas Canvas return SKSurface.Create(info, framebuffer.Address, framebuffer.RowBytes).Canvas; } } - - public VectorOfVectorOfPoint LayerContours - { - get - { - if (_layerContours is null) CacheContours(); - return _layerContours; - } - private set => _layerContours = value; - } - - public int[,] LayerContourHierarchy - { - get - { - if (_layerContourHierarchy is null) CacheContours(); - return _layerContourHierarchy; - } - private set => _layerContourHierarchy = value; - } - - public void CacheContours(bool refresh = false) - { - if(refresh) Clear(); - if (_layerContours is not null) return; - _layerContours = Image.FindContours(out _layerContourHierarchy, RetrType.Tree); - } - - + /// /// Clears the cache /// @@ -110,8 +79,5 @@ public void Clear() _layer = null; Image?.Dispose(); ImageBgr?.Dispose(); - _layerContours?.Dispose(); - _layerContours = null; - _layerContourHierarchy = null; } } \ No newline at end of file diff --git a/UVtools.WPF/MainWindow.Clipboard.cs b/UVtools.WPF/MainWindow.Clipboard.cs index 47f2b461..8e15748b 100644 --- a/UVtools.WPF/MainWindow.Clipboard.cs +++ b/UVtools.WPF/MainWindow.Clipboard.cs @@ -10,9 +10,9 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Threading; -using MessageBox.Avalonia.Enums; using System; using System.ComponentModel; +using UVtools.Core.Dialogs; using UVtools.WPF.Extensions; namespace UVtools.WPF; @@ -98,7 +98,7 @@ public async void ClipboardClear() { if (await this.MessageBoxQuestion("Are you sure you want to clear the clipboard?\n" + "Current layers will be placed as original layers\n" + - "This action is permanent!", "Clear clipboard?") != ButtonResult.Yes) return; + "This action is permanent!", "Clear clipboard?") != MessageButtonResult.Yes) return; Clipboard.Clear(true); } } \ No newline at end of file diff --git a/UVtools.WPF/MainWindow.GCode.cs b/UVtools.WPF/MainWindow.GCode.cs index 985af66f..ef5d61f7 100644 --- a/UVtools.WPF/MainWindow.GCode.cs +++ b/UVtools.WPF/MainWindow.GCode.cs @@ -7,9 +7,9 @@ */ using Avalonia; using Avalonia.Controls; -using MessageBox.Avalonia.Enums; using System; using System.IO; +using UVtools.Core.Dialogs; using UVtools.Core.SystemOS; using UVtools.WPF.Extensions; using Helpers = UVtools.WPF.Controls.Helpers; @@ -63,7 +63,7 @@ public async void OnClickGCodeSaveFile() var result = await this.MessageBoxQuestion( "GCode save was successful. Do you want open the file in the default editor?", "GCode save complete"); - if (result != ButtonResult.Yes) return; + if (result != MessageButtonResult.Yes) return; SystemAware.StartProcess(file); } diff --git a/UVtools.WPF/MainWindow.Information.cs b/UVtools.WPF/MainWindow.Information.cs index 22b1b4ed..d7f76fe0 100644 --- a/UVtools.WPF/MainWindow.Information.cs +++ b/UVtools.WPF/MainWindow.Information.cs @@ -10,7 +10,6 @@ using Avalonia.Input; using Emgu.CV; using Emgu.CV.CvEnum; -using MessageBox.Avalonia.Enums; using System; using System.Collections; using System.Collections.ObjectModel; @@ -18,6 +17,7 @@ using System.IO; using System.Reflection; using System.Text; +using UVtools.Core.Dialogs; using UVtools.Core.FileFormats; using UVtools.Core.Layers; using UVtools.Core.Objects; @@ -416,7 +416,7 @@ public async void OnClickPropertiesSaveFile() var result = await this.MessageBoxQuestion( "Properties save was successful. Do you want open the file in the default editor?", "Properties save complete"); - if (result != ButtonResult.Yes) return; + if (result != MessageButtonResult.Yes) return; SystemAware.StartProcess(file); } diff --git a/UVtools.WPF/MainWindow.Issues.cs b/UVtools.WPF/MainWindow.Issues.cs index 55be5b63..f7a9b4b3 100644 --- a/UVtools.WPF/MainWindow.Issues.cs +++ b/UVtools.WPF/MainWindow.Issues.cs @@ -14,7 +14,6 @@ using Avalonia.Threading; using Emgu.CV; using Emgu.CV.Util; -using MessageBox.Avalonia.Enums; using System; using System.Collections; using System.Collections.Generic; @@ -23,6 +22,7 @@ using System.Linq; using System.Threading.Tasks; using UVtools.Core; +using UVtools.Core.Dialogs; using UVtools.Core.Extensions; using UVtools.Core.FileFormats; using UVtools.Core.Layers; @@ -169,7 +169,7 @@ public async void RemoveRepairIssues(IEnumerable issues, bool promptC (resinTraps > 0 ? $"- Fill/solidify {resinTraps} resin trap(s)\n" : string.Empty) + (suctionCups > 0 ? $"- Drill {suctionCups} suction cup(s) at it's center\n" : string.Empty) + "\nWarning: Removing an island can cause other issues to appear if there is material present in the layers above it.\n" + - "Always check previous and next layers before performing an island removal.", $"Remove {issues.Count()} Issues?") != ButtonResult.Yes) return; + "Always check previous and next layers before performing an island removal.", $"Remove {issues.Count()} Issues?") != MessageButtonResult.Yes) return; var processParallelIssues = new Dictionary>(); var processSuctionCups = new List(); @@ -403,7 +403,7 @@ public async void OnClickIssueIgnore() if (await this.MessageBoxQuestion( $"Are you sure you want to re-enable {SlicerFile.IssueManager.IgnoredIssues.Count} ignored issues?\n" + "A full re-detect will be required to get the ignored issues.\n", $"Re-enable {SlicerFile.IssueManager.IgnoredIssues.Count} Issues?") != - ButtonResult.Yes) return; + MessageButtonResult.Yes) return; SlicerFile.IssueManager.IgnoredIssues.Clear(); @@ -415,7 +415,7 @@ public async void OnClickIssueIgnore() if (await this.MessageBoxQuestion( $"Are you sure you want to hide and ignore all selected {IssuesGrid.SelectedItems.Count} issues?\n" + "The ignored issues won't be re-detected.\n", $"Ignore {IssuesGrid.SelectedItems.Count} Issues?") != - ButtonResult.Yes) return; + MessageButtonResult.Yes) return; var list = IssuesGrid.SelectedItems.Cast().ToArray(); SlicerFile.IssueManager.IgnoredIssues.AddRange(list); diff --git a/UVtools.WPF/MainWindow.LayerPreview.cs b/UVtools.WPF/MainWindow.LayerPreview.cs index 3bbd402f..384ef7e0 100644 --- a/UVtools.WPF/MainWindow.LayerPreview.cs +++ b/UVtools.WPF/MainWindow.LayerPreview.cs @@ -16,7 +16,6 @@ using Emgu.CV.CvEnum; using Emgu.CV.Structure; using Emgu.CV.Util; -using MessageBox.Avalonia.Enums; using System; using System.Collections.Generic; using System.Diagnostics; @@ -27,6 +26,7 @@ using System.Timers; using UVtools.AvaloniaControls; using UVtools.Core; +using UVtools.Core.Dialogs; using UVtools.Core.EmguCV; using UVtools.Core.Extensions; using UVtools.Core.Layers; @@ -81,6 +81,7 @@ public enum ZoomToFitType : byte private bool _showLayerOutlinePrintVolumeBoundary; private bool _showLayerOutlineLayerBoundary; private bool _showLayerOutlineContourBoundary; + private bool _showLayerOutlineEnclosingCircles; private bool _showLayerOutlineHollowAreas; private bool _showLayerOutlineCentroids; private bool _showLayerOutlineTriangulate; @@ -116,6 +117,7 @@ public void InitLayerPreview() _showLayerOutlinePrintVolumeBoundary = Settings.LayerPreview.VolumeBoundsOutline; _showLayerOutlineLayerBoundary = Settings.LayerPreview.LayerBoundsOutline; _showLayerOutlineContourBoundary = Settings.LayerPreview.ContourBoundsOutline; + _showLayerOutlineEnclosingCircles = Settings.LayerPreview.EnclosingCirclesOutline; _showLayerOutlineHollowAreas = Settings.LayerPreview.HollowOutline; _showLayerOutlineCentroids = Settings.LayerPreview.CentroidOutline; @@ -397,6 +399,16 @@ public bool ShowLayerOutlineContourBoundary } } + public bool ShowLayerOutlineEnclosingCircles + { + get => _showLayerOutlineEnclosingCircles; + set + { + if (!RaiseAndSetIfChanged(ref _showLayerOutlineEnclosingCircles, value)) return; + ShowLayer(); + } + } + public bool ShowLayerOutlineHollowAreas { get => _showLayerOutlineHollowAreas; @@ -756,13 +768,13 @@ public void AddMaskPoints(IEnumerable pointsOfPoints, bool clear = true public void SelectLayerPositiveAreasMask() { - AddMaskPoints(LayerCache.LayerContours.ToArrayOfArray()); + AddMaskPoints(LayerCache.Layer.Contours.VectorOfContours.ToArrayOfArray()); if (_maskPoints.Count > 0 && Settings.LayerPreview.MaskClearROIAfterSet) ClearROI(); } public void SelectLayerHollowAreasMask() { - var contours = EmguContours.GetNegativeContours(LayerCache.LayerContours, LayerCache.LayerContourHierarchy); + var contours = EmguContours.GetNegativeContours(LayerCache.Layer.Contours.VectorOfContours, LayerCache.Layer.Contours.Hierarchy); AddMaskPoints(contours.ToArrayOfArray()); if (_maskPoints.Count > 0 && Settings.LayerPreview.MaskClearROIAfterSet) ClearROI(); } @@ -1189,9 +1201,9 @@ is not MainIssue.IssueType.PrintHeight { int lastParent = -1; uint reps = 0; - for (int i = 0; i < LayerCache.LayerContours.Size; i++) + for (int i = 0; i < LayerCache.Layer.Contours.Count; i++) { - var parent = LayerCache.LayerContourHierarchy[i, EmguContour.HierarchyParent]; + var parent = LayerCache.Layer.Contours[i, EmguContour.HierarchyParent]; if (parent == -1) { reps = 0; @@ -1203,7 +1215,7 @@ is not MainIssue.IssueType.PrintHeight continue; } - CvInvoke.Rectangle(LayerCache.ImageBgr, CvInvoke.BoundingRectangle(LayerCache.LayerContours[i]), + CvInvoke.Rectangle(LayerCache.ImageBgr, LayerCache.Layer.Contours[i].BoundingRectangle, new MCvScalar( Settings.LayerPreview.ContourBoundsOutlineColor.B, Settings.LayerPreview.ContourBoundsOutlineColor.G, @@ -1214,6 +1226,18 @@ is not MainIssue.IssueType.PrintHeight } } + if (_showLayerOutlineEnclosingCircles) + { + for (int i = 0; i < LayerCache.Layer.Contours.Count; i++) + { + LayerCache.Layer.Contours[i].FitCircle(LayerCache.ImageBgr, + new MCvScalar( + Settings.LayerPreview.EnclosingCirclesOutlineColor.B, + Settings.LayerPreview.EnclosingCirclesOutlineColor.G, + Settings.LayerPreview.EnclosingCirclesOutlineColor.R), Settings.LayerPreview.EnclosingCirclesOutlineThickness, LineType.AntiAlias); + } + } + if (_showLayerOutlineHollowAreas) { //CvInvoke.Threshold(ActualLayerImage, grayscale, 1, 255, ThresholdType.Binary); @@ -1224,7 +1248,7 @@ is not MainIssue.IssueType.PrintHeight * hierarchy[i][2]: the index of the first child * hierarchy[i][3]: the index of the parent */ - using var vec = EmguContours.GetNegativeContours(LayerCache.LayerContours, LayerCache.LayerContourHierarchy); + using var vec = EmguContours.GetNegativeContours(LayerCache.Layer.Contours.VectorOfContours, LayerCache.Layer.Contours.Hierarchy); if (vec.Size > 0) { CvInvoke.DrawContours(LayerCache.ImageBgr, vec, -1, @@ -1239,10 +1263,9 @@ is not MainIssue.IssueType.PrintHeight { int lastParent = -1; uint reps = 0; - var size = LayerCache.LayerContours.Size; - for (int i = 0; i < size; i++) + for (int i = 0; i < LayerCache.Layer.Contours.Count; i++) { - var parent = LayerCache.LayerContourHierarchy[i, EmguContour.HierarchyParent]; + var parent = LayerCache.Layer.Contours[i, EmguContour.HierarchyParent]; if (parent == -1) { reps = 0; @@ -1252,7 +1275,7 @@ is not MainIssue.IssueType.PrintHeight { if (Settings.LayerPreview.CentroidOutlineHollow) { - CvInvoke.Circle(LayerCache.ImageBgr, EmguContour.GetCentroid(LayerCache.LayerContours[i]), + CvInvoke.Circle(LayerCache.ImageBgr, LayerCache.Layer.Contours[i].Centroid, Settings.LayerPreview.CentroidOutlineDiameter / 2, new MCvScalar( Settings.LayerPreview.HollowOutlineColor.B, Settings.LayerPreview.HollowOutlineColor.G, @@ -1261,7 +1284,7 @@ is not MainIssue.IssueType.PrintHeight } else { - CvInvoke.Circle(LayerCache.ImageBgr, EmguContour.GetCentroid(LayerCache.LayerContours[i]), + CvInvoke.Circle(LayerCache.ImageBgr, LayerCache.Layer.Contours[i].Centroid, Settings.LayerPreview.CentroidOutlineDiameter / 2, new MCvScalar( Settings.LayerPreview.CentroidOutlineColor.B, Settings.LayerPreview.CentroidOutlineColor.G, @@ -1276,7 +1299,7 @@ is not MainIssue.IssueType.PrintHeight if (_showLayerOutlineTriangulate) { - var groups = EmguContours.GetPositiveContoursInGroups(LayerCache.LayerContours, LayerCache.LayerContourHierarchy); + var groups = EmguContours.GetPositiveContoursInGroups(LayerCache.Layer.Contours.VectorOfContours, LayerCache.Layer.Contours.Hierarchy); var lineColor = new MCvScalar( Settings.LayerPreview.TriangulateOutlineColor.B, Settings.LayerPreview.TriangulateOutlineColor.G, @@ -1406,7 +1429,7 @@ is not MainIssue.IssueType.PrintHeight ? Settings.PixelEditor.RemovePixelHighlightColor : Settings.PixelEditor.RemovePixelColor; - using var vec = EmguContours.GetContoursInside(LayerCache.LayerContours, LayerCache.LayerContourHierarchy, operation.Location); + using var vec = EmguContours.GetContoursInside(LayerCache.Layer.Contours.VectorOfContours, LayerCache.Layer.Contours.Hierarchy, operation.Location); if (vec.Size > 0) CvInvoke.DrawContours(LayerCache.ImageBgr, vec, -1, new MCvScalar(color.B, color.G, color.R), -1); /*var hollowGroups = EmguContours.GetPositiveContoursInGroups(LayerCache.LayerContours, LayerCache.LayerContourHierarchy); @@ -2025,7 +2048,7 @@ private async void LayerImageBox_KeyUp(object? sender, KeyEventArgs e) if (e.KeyModifiers == KeyModifiers.Control) { if (await this.MessageBoxQuestion($"Are you sure you want to clone the current layer {_actualLayer}?", - "Clone the current layer?") != ButtonResult.Yes) return; + "Clone the current layer?") != MessageButtonResult.Yes) return; var operationLayerClone = new OperationLayerClone(SlicerFile); operationLayerClone.SelectCurrentLayer(_actualLayer); @@ -2056,7 +2079,7 @@ private async void LayerImageBox_KeyUp(object? sender, KeyEventArgs e) } if (await this.MessageBoxQuestion($"Are you sure you want to keep only the selected region/mask(s) {layerRange}?", - "Keep only selected region/mask(s)?") != ButtonResult.Yes) return; + "Keep only selected region/mask(s)?") != MessageButtonResult.Yes) return; await RunOperation(operation); e.Handled = true; return; @@ -2066,7 +2089,7 @@ private async void LayerImageBox_KeyUp(object? sender, KeyEventArgs e) if (e.KeyModifiers == KeyModifiers.Control) { if (await this.MessageBoxQuestion($"Are you sure you want to remove the current layer {_actualLayer}?", - "Remove the current layer?") != ButtonResult.Yes) return; + "Remove the current layer?") != MessageButtonResult.Yes) return; var operationLayerRemove = new OperationLayerRemove(SlicerFile); operationLayerRemove.SelectCurrentLayer(_actualLayer); @@ -2097,7 +2120,7 @@ private async void LayerImageBox_KeyUp(object? sender, KeyEventArgs e) } if (await this.MessageBoxQuestion($"Are you sure you want to discard the selected region/mask(s) {layerRange}?", - "Discard selected region/mask(s)?") != ButtonResult.Yes) return; + "Discard selected region/mask(s)?") != MessageButtonResult.Yes) return; await RunOperation(operation); e.Handled = true; return; @@ -2241,11 +2264,10 @@ public bool SelectObjectRoi(Point location) { var point = GetTransposedPoint(location); - for (int i = LayerCache.LayerContours.Size-1; i >= 0; i--) + for (int i = LayerCache.Layer.Contours.Count-1; i >= 0; i--) { - if (CvInvoke.PointPolygonTest(LayerCache.LayerContours[i], point, false) < 0) continue; - var rectangle = CvInvoke.BoundingRectangle(LayerCache.LayerContours[i]); - ROI = rectangle; + if (!LayerCache.Layer.Contours[i].IsInside(point)) continue; + ROI = LayerCache.Layer.Contours[i].BoundingRectangle; return true; } @@ -2256,15 +2278,14 @@ public uint SelectObjectRoi(Rectangle roiRectangle) { if (roiRectangle.IsEmpty) return 0; List rectangles = new(); - for (int i = 0; i < LayerCache.LayerContours.Size; i++) + for (int i = 0; i < LayerCache.Layer.Contours.Count; i++) { - var rectangle = CvInvoke.BoundingRectangle(LayerCache.LayerContours[i]); + var rectangle = LayerCache.Layer.Contours[i].BoundingRectangle; //roi.Intersect(rectangle); if (roiRectangle.IntersectsWith(rectangle)) { rectangles.Add(rectangle); } - } roiRectangle = rectangles.Count == 0 ? Rectangle.Empty : rectangles[0]; for (var i = 1; i < rectangles.Count; i++) @@ -2272,7 +2293,7 @@ public uint SelectObjectRoi(Rectangle roiRectangle) var rectangle = rectangles[i]; roiRectangle = Rectangle.Union(roiRectangle, rectangle); } - + ROI = roiRectangle; return (uint)rectangles.Count; @@ -2282,7 +2303,7 @@ public bool SelectObjectMask(Point location) { var point = GetTransposedPoint(location); - using var vec = EmguContours.GetContoursInside(LayerCache.LayerContours, LayerCache.LayerContourHierarchy, point, (_globalModifiers & KeyModifiers.Control) != 0); + using var vec = EmguContours.GetContoursInside(LayerCache.Layer.Contours.VectorOfContours, LayerCache.Layer.Contours.Hierarchy, point, (_globalModifiers & KeyModifiers.Control) != 0); AddMaskPoints(vec.ToArrayOfArray(), false); return vec.Size > 0; } diff --git a/UVtools.WPF/MainWindow.PixelEditor.cs b/UVtools.WPF/MainWindow.PixelEditor.cs index 4b500c3d..93915701 100644 --- a/UVtools.WPF/MainWindow.PixelEditor.cs +++ b/UVtools.WPF/MainWindow.PixelEditor.cs @@ -13,7 +13,6 @@ using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; -using MessageBox.Avalonia.Enums; using SkiaSharp; using System; using System.Collections.Generic; @@ -21,6 +20,7 @@ using System.Drawing; using System.Linq; using System.Threading.Tasks; +using UVtools.Core.Dialogs; using UVtools.Core.Extensions; using UVtools.Core.Layers; using UVtools.Core.PixelEditor; @@ -131,7 +131,7 @@ public async void OnClickDrawingClear() { if (Drawings.Count == 0) return; if (await this.MessageBoxQuestion($"Are you sure you want to clear {Drawings.Count} operations?", - "Clear pixel editor operations?") != ButtonResult.Yes) return; + "Clear pixel editor operations?") != MessageButtonResult.Yes) return; Drawings.Clear(); ShowLayer(); } @@ -523,14 +523,14 @@ public async void DrawModifications(bool exitEditor) return; } - ButtonResult result; + MessageButtonResult result; if (exitEditor) { result = await this.MessageBoxQuestion( "There are edit operations that have not been applied. " + "Would you like to apply all operations before closing the editor?", - "Closing image editor?", ButtonEnum.YesNoCancel); + "Closing image editor?", MessageButtons.YesNoCancel); } else { @@ -541,16 +541,16 @@ public async void DrawModifications(bool exitEditor) // For the "apply" case, We aren't exiting the editor, so map "No" to "Cancel" here // in order to prevent pixel history from being cleared. - result = result == ButtonResult.No ? ButtonResult.Cancel : ButtonResult.Yes; + result = result == MessageButtonResult.No ? MessageButtonResult.Cancel : MessageButtonResult.Yes; } - if (result == ButtonResult.Cancel) + if (result == MessageButtonResult.Cancel) { IsPixelEditorActive = true; return; } - if (result == ButtonResult.Yes) + if (result == MessageButtonResult.Yes) { IsGUIEnabled = false; ShowProgressWindow("Drawing pixels"); @@ -617,7 +617,7 @@ public async void DrawModifications(bool exitEditor) Drawings.Clear(); ShowLayer(); - if (exitEditor || (Settings.PixelEditor.CloseEditorOnApply && result == ButtonResult.Yes)) + if (exitEditor || (Settings.PixelEditor.CloseEditorOnApply && result == MessageButtonResult.Yes)) { IsPixelEditorActive = false; if (!ReferenceEquals(LastSelectedTabItem, TabPixelEditor)) diff --git a/UVtools.WPF/MainWindow.Suggestions.cs b/UVtools.WPF/MainWindow.Suggestions.cs index 60b7313a..d7974d57 100644 --- a/UVtools.WPF/MainWindow.Suggestions.cs +++ b/UVtools.WPF/MainWindow.Suggestions.cs @@ -8,13 +8,13 @@ using Avalonia.Controls; using Avalonia.Threading; -using MessageBox.Avalonia.Enums; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; +using UVtools.Core.Dialogs; using UVtools.Core.FileFormats; using UVtools.Core.Managers; using UVtools.Core.Suggestions; @@ -91,7 +91,7 @@ public async void ApplySuggestionsClicked() { sb.AppendLine(suggestion.ConfirmationMessage); } - if (await this.MessageBoxQuestion(sb.ToString(), "Apply suggestions?") != ButtonResult.Yes) return; + if (await this.MessageBoxQuestion(sb.ToString(), "Apply suggestions?") != MessageButtonResult.Yes) return; IsGUIEnabled = false; ShowProgressWindow($"Applying {suggestions.Length} suggestions", false); @@ -138,7 +138,7 @@ public async void ApplySuggestionClicked(Suggestion suggestion) { if (!IsFileLoaded || suggestion is null || suggestion.IsInformativeOnly) return; - if (await this.MessageBoxQuestion($"Are you sure you want to apply the following suggestion?:\n\n{suggestion.ConfirmationMessage}", "Apply the suggestion?") != ButtonResult.Yes) return; + if (await this.MessageBoxQuestion($"Are you sure you want to apply the following suggestion?:\n\n{suggestion.ConfirmationMessage}", "Apply the suggestion?") != MessageButtonResult.Yes) return; IsGUIEnabled = false; diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml index f98df384..dfc988d5 100644 --- a/UVtools.WPF/MainWindow.axaml +++ b/UVtools.WPF/MainWindow.axaml @@ -2003,24 +2003,20 @@ - - - - - - + + + + + + + diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index 23536052..e44ee8d1 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -11,7 +11,6 @@ using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Avalonia.Threading; -using MessageBox.Avalonia.Enums; using System; using System.Collections.Generic; using System.ComponentModel; @@ -27,6 +26,7 @@ using System.Web; using UVtools.AvaloniaControls; using UVtools.Core; +using UVtools.Core.Dialogs; using UVtools.Core.Exceptions; using UVtools.Core.Extensions; using UVtools.Core.FileFormats; @@ -502,12 +502,12 @@ await this.MessageBoxError($"The device {drive.Name} is not ready/available at t switch (await this.MessageBoxQuestion("There are unsaved changes. Do you want to save the current file before copy it over?\n\n" + "Yes: Save the current file and copy it over.\n" + "No: Copy the file without current modifications.\n" + - "Cancel: Abort the operation.", "Send to - Unsaved changes", ButtonEnum.YesNoCancel)) + "Cancel: Abort the operation.", "Send to - Unsaved changes", MessageButtons.YesNoCancel)) { - case ButtonResult.Yes: + case MessageButtonResult.Yes: await SaveFile(true); break; - case ButtonResult.No: + case MessageButtonResult.No: break; default: return; @@ -528,7 +528,7 @@ await this.MessageBoxError($"The device {drive.Name} is not ready/available at t "Keep in mind there is no guarantee that the file will start to print.\n" + "Are you sure you want to continue?\n\n" + "Yes: Print this file name.\n" + - "No: Cancel file print.", "Print the filename?") != ButtonResult.Yes) return; + "No: Cancel file print.", "Print the filename?") != MessageButtonResult.Yes) return; } else { @@ -537,7 +537,7 @@ await this.MessageBoxError($"The device {drive.Name} is not ready/available at t "Keep in mind there is no guarantee that the file will start to print.\n" + "Are you sure you want to continue?\n\n" + "Yes: Send file and print it.\n" + - "No: Cancel file sending and print.", "Send and print the file?") != ButtonResult.Yes) return; + "No: Cancel file sending and print.", "Send and print the file?") != MessageButtonResult.Yes) return; } } @@ -643,7 +643,7 @@ await this.MessageBoxError($"The device {drive.Name} is not ready/available at t { if (await this.MessageBoxQuestion( $"File '{SlicerFile.Filename}' has copied successfully into {removableDrive.Name}\n" + - $"Do you want to eject the {removableDrive.Name} drive now?", "Copied ok, eject the drive?") == ButtonResult.Yes) + $"Do you want to eject the {removableDrive.Name} drive now?", "Copied ok, eject the drive?") == MessageButtonResult.Yes) { Progress.ResetAll($"Ejecting {removableDrive.Name}"); @@ -1057,7 +1057,7 @@ public async void OpenFile(bool newWindow = false, FileFormat.FileDecodeType fil public async void OnMenuFileCloseFile() { if (CanSave && await this.MessageBoxQuestion("There are unsaved changes. Do you want close this file without saving?") != - ButtonResult.Yes) + MessageButtonResult.Yes) { return; } @@ -1218,7 +1218,7 @@ public async void MenuHelpInstallProfilesClicked() $"Was looking on: {PSFolder} and {SSFolder}\n\n" + "Click 'Yes' to open the PrusaSlicer webpage for download\n" + "Click 'No' to dismiss", - "Unable to detect PrusaSlicer") == ButtonResult.Yes) + "Unable to detect PrusaSlicer") == MessageButtonResult.Yes) SystemAware.OpenBrowser("https://www.prusa3d.com/prusaslicer/"); return; } @@ -1248,47 +1248,40 @@ public void MenuHelpDebugTriggerNewUpdateClicked() public async void MenuNewVersionClicked() { - if (string.IsNullOrWhiteSpace(VersionChecker.DownloadLink)) - { - var result = await this.MessageBoxWithHeaderQuestion( - $"Do you like to manually download and update {About.Software} v{About.VersionStr} to v{VersionChecker.Version}?", - "## Changelog: \n\n" + - $"{VersionChecker.Changelog}", - $"Update UVtools to v{VersionChecker.Version}?", + var autoUpdateButton = MessageWindow.CreateButton("Auto update", MessageWindow.IconButtonDownload); + var manualUpdateButton = MessageWindow.CreateButton("Manual update", MessageWindow.IconButtonOpenBrowser); + + var messageBox = new MessageWindow($"Update UVtools to v{VersionChecker.Version}?", + MessageWindow.IconHeaderQuestion, + $"Do you like to update {About.Software} from v{About.VersionStr} to v{VersionChecker.Version}?", + "## Changelog:\n\n" + + $"{VersionChecker.Changelog}", + string.IsNullOrWhiteSpace(VersionChecker.DownloadLink) ? + new[] + { + manualUpdateButton, + MessageWindow.CreateCancelButton() + } + : new[] + { + autoUpdateButton, + manualUpdateButton, + MessageWindow.CreateCancelButton() + }, + true); - ButtonEnum.YesNoCancel, true); + var result = await messageBox.ShowDialog(this); - if (result == ButtonResult.Yes) - { - SystemAware.OpenBrowser(VersionChecker.UrlLatestRelease); - } + if (ReferenceEquals(result, autoUpdateButton)) + { + IsGUIEnabled = false; + ShowProgressWindow($"Downloading: {VersionChecker.Filename}"); + await VersionChecker.AutoUpgrade(Progress); + IsGUIEnabled = true; } - else + else if (ReferenceEquals(result, manualUpdateButton)) { - var result = await this.MessageBoxWithHeaderQuestion( - $"Do you like to auto-update {About.Software} v{About.VersionStr} to v{VersionChecker.Version}?", - "Yes: Auto update \n" + - "No: Manual download and update \n" + - "Cancel: No action \n\n" + - "## Changelog: \n\n" + - $"{VersionChecker.Changelog}", - $"Update UVtools to v{VersionChecker.Version}?", - - ButtonEnum.YesNoCancel, true); - - - switch (result) - { - case ButtonResult.No: - SystemAware.OpenBrowser(VersionChecker.UrlLatestRelease); - break; - case ButtonResult.Yes: - IsGUIEnabled = false; - ShowProgressWindow($"Downloading: {VersionChecker.Filename}"); - await VersionChecker.AutoUpgrade(Progress); - IsGUIEnabled = true; - break; - } + SystemAware.OpenBrowser(VersionChecker.UrlLatestRelease); } } @@ -1505,13 +1498,13 @@ await this.MessageBoxError("It seems this file has no layers. Possible causes c "Cancel: Do not auto-convert the file.", $"File '{SlicerFile.Filename}' already exists", - ButtonEnum.YesNoCancel); + MessageButtons.YesNoCancel); - if (result is ButtonResult.Cancel or ButtonResult.Abort) + if (result is MessageButtonResult.Cancel or MessageButtonResult.Abort) { canConvert = false; } - else if (result == ButtonResult.No) + else if (result == MessageButtonResult.No) { var dialog = new SaveFileDialog { @@ -1575,7 +1568,7 @@ await this.MessageBoxError("It seems this file has no layers. Possible causes c break; case RemoveSourceFileAction.Prompt: if (await this.MessageBoxQuestion($"File was successfully converted to: {targetFilename}\n" + - $"Do you want to remove the source file: {oldFileName}", $"Remove source file: {oldFileName}") == ButtonResult.Yes) removeSourceFile = true; + $"Do you want to remove the source file: {oldFileName}", $"Remove source file: {oldFileName}") == MessageButtonResult.Yes) removeSourceFile = true; break; } @@ -1714,8 +1707,8 @@ await this.MessageBoxError("It seems this file has no layers. Possible causes c "3) If you used PrusaSlicer to slice this file, you must use it with compatible UVtools printer profiles (Help - Install profiles into PrusaSlicer).\n\n" + "Click 'Yes' to auto fix and set the file resolution with the layer resolution, but only use this option if you are sure it's ok to!\n" + "Click 'No' to continue as it is and ignore this warning, you can still repair issues and use some of the tools.", - "File and layer resolution mismatch!", ButtonEnum.YesNo); - if (result == ButtonResult.Yes) + "File and layer resolution mismatch!", MessageButtons.YesNo); + if (result == MessageButtonResult.Yes) { SlicerFile.Resolution = mat.Size; RaisePropertyChanged(nameof(LayerResolutionStr)); @@ -1752,7 +1745,7 @@ await this.MessageBoxWaring( $"Yes: Change to version {lastVersion}. (Highly recommended!)\n" + $"No: Keep the version {SlicerFile.Version} while editing, but a latter full encode of the file will force the version {lastVersion}.", $"File version {SlicerFile.Version} is outside the supported range for your printer"); - if (result == ButtonResult.Yes) + if (result == MessageButtonResult.Yes) { SlicerFile.Version = lastVersion; CanSave = true; @@ -1978,14 +1971,14 @@ private async void ConvertToOnTapped(object? sender, RoutedEventArgs e) "Yes: Open in a new window.\n" + "No: Open in this window.\n" + "Cancel: Do not perform any action.\n", - "Conversion complete", ButtonEnum.YesNoCancel); + "Conversion complete", MessageButtons.YesNoCancel); switch (question) { - case ButtonResult.No: + case MessageButtonResult.No: ProcessFile(newFilePath, _actualLayer); break; - case ButtonResult.Yes: + case MessageButtonResult.Yes: App.NewInstance(newFilePath); break; } @@ -1998,7 +1991,7 @@ private async void ConvertToOnTapped(object? sender, RoutedEventArgs e) break; case RemoveSourceFileAction.Prompt: if (await this.MessageBoxQuestion($"File was successfully converted to: {Path.GetFileName(newFilePath)}\n" + - $"Do you want to remove the source file: {oldFileName}", $"Remove source file: {oldFileName}") == ButtonResult.Yes) removeSourceFile = true; + $"Do you want to remove the source file: {oldFileName}", $"Remove source file: {oldFileName}") == MessageButtonResult.Yes) removeSourceFile = true; break; } @@ -2029,7 +2022,7 @@ public async Task SaveFile(string filepath = null, bool ignoreOverwriteWar var result = await this.MessageBoxQuestion( "Original input file will be overwritten. Do you wish to proceed?", "Overwrite file?"); - if(result != ButtonResult.Yes) return false; + if(result != MessageButtonResult.Yes) return false; } } @@ -2129,7 +2122,7 @@ await Task.Factory.StartNew(() => if (await this.MessageBoxQuestion( $"Extraction to {finalPath} completed in ({LastStopWatch.ElapsedMilliseconds / 1000}s)\n\n" + "'Yes' to open target folder, 'No' to continue.", - "Extraction complete") == ButtonResult.Yes) + "Extraction complete") == MessageButtonResult.Yes) { SystemAware.StartProcess(finalPath); } @@ -2365,7 +2358,7 @@ private async void MenuFileOpenRecentItemOnClick(object? sender, RoutedEventArgs if (_globalModifiers == KeyModifiers.Control) { if (await this.MessageBoxQuestion("Are you sure you want to purge the non-existing files from the recent list?", - "Purge the non-existing files?") == ButtonResult.Yes) + "Purge the non-existing files?") == MessageButtonResult.Yes) { /*if (_globalModifiers == KeyModifiers.Shift) { @@ -2383,7 +2376,7 @@ private async void MenuFileOpenRecentItemOnClick(object? sender, RoutedEventArgs (_globalModifiers & KeyModifiers.Shift) != 0) { if (await this.MessageBoxQuestion($"Are you sure you want to remove the selected file from the recent list?\n{file}", - "Remove the file from recent list?") == ButtonResult.Yes) + "Remove the file from recent list?") == MessageButtonResult.Yes) { RemoveRecentFile(file); } @@ -2395,7 +2388,7 @@ private async void MenuFileOpenRecentItemOnClick(object? sender, RoutedEventArgs { if (await this.MessageBoxQuestion($"The file: {file} does not exists anymore.\n" + "Do you want to remove this file from recent list?", - "The file does not exists") == ButtonResult.Yes) + "The file does not exists") == MessageButtonResult.Yes) { RecentFiles.Load(); RecentFiles.Instance.Remove(file); diff --git a/UVtools.WPF/Structures/UiMessageBoxStandard.cs b/UVtools.WPF/Structures/UiMessageBoxStandard.cs index 78e409dd..3d1f322a 100644 --- a/UVtools.WPF/Structures/UiMessageBoxStandard.cs +++ b/UVtools.WPF/Structures/UiMessageBoxStandard.cs @@ -7,7 +7,6 @@ */ using Avalonia.Threading; -using MessageBox.Avalonia.Enums; using System; using System.Threading.Tasks; using UVtools.Core.Dialogs; @@ -33,8 +32,8 @@ public override Task ShowDialog(string? title, string messa { return Dispatcher.UIThread.InvokeAsync(async () => { - var result = await App.MainWindow.MessageBoxQuestion(message, string.IsNullOrWhiteSpace(title) ? "Question" : title, (ButtonEnum) buttons, false, true); - return (MessageButtonResult) result; + var result = await App.MainWindow.MessageBoxQuestion(message, string.IsNullOrWhiteSpace(title) ? "Question" : title, buttons, false, true); + return result; }); } #endregion diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index e286cc84..bc265670 100644 --- a/UVtools.WPF/UVtools.WPF.csproj +++ b/UVtools.WPF/UVtools.WPF.csproj @@ -12,7 +12,7 @@ LICENSE https://github.com/sn4k3/UVtools Git - 3.14.4 + 3.15.0 AnyCPU;x64 UVtools.png README.md @@ -47,7 +47,7 @@ - + diff --git a/UVtools.WPF/UserSettings.cs b/UVtools.WPF/UserSettings.cs index b0981175..a749b117 100644 --- a/UVtools.WPF/UserSettings.cs +++ b/UVtools.WPF/UserSettings.cs @@ -281,6 +281,9 @@ public sealed class LayerPreviewUserSettings : BindableBase private Color _contourBoundsOutlineColor = new(255, 50, 100, 50); private byte _contourBoundsOutlineThickness = 2; private bool _contourBoundsOutline = false; + private Color _enclosingCirclesOutlineColor = new(255, 127, 0, 0); + private byte _enclosingCirclesOutlineThickness = 2; + private bool _enclosingCirclesOutline = false; private Color _hollowOutlineColor = new(255, 255, 165, 0); private sbyte _hollowOutlineLineThickness = 5; private bool _hollowOutline = false; @@ -434,6 +437,35 @@ public bool ContourBoundsOutline set => RaiseAndSetIfChanged(ref _contourBoundsOutline, value); } + public Color EnclosingCirclesOutlineColor + { + get => _enclosingCirclesOutlineColor; + set + { + RaiseAndSetIfChanged(ref _enclosingCirclesOutlineColor, value); + RaisePropertyChanged(nameof(EnclosingCirclesOutlineBrush)); + } + } + + [XmlIgnore] + public SolidColorBrush EnclosingCirclesOutlineBrush + { + get => new(_enclosingCirclesOutlineColor.ToAvalonia()); + set => EnclosingCirclesOutlineColor = new Color(value); + } + + public byte EnclosingCirclesOutlineThickness + { + get => _enclosingCirclesOutlineThickness; + set => RaiseAndSetIfChanged(ref _enclosingCirclesOutlineThickness, value); + } + + public bool EnclosingCirclesOutline + { + get => _enclosingCirclesOutline; + set => RaiseAndSetIfChanged(ref _enclosingCirclesOutline, value); + } + public Color HollowOutlineColor { get => _hollowOutlineColor; diff --git a/UVtools.WPF/Windows/AboutWindow.axaml b/UVtools.WPF/Windows/AboutWindow.axaml index 3511ce17..6dd40abb 100644 --- a/UVtools.WPF/Windows/AboutWindow.axaml +++ b/UVtools.WPF/Windows/AboutWindow.axaml @@ -41,7 +41,7 @@ Padding="10" VerticalContentAlignment="Center" HorizontalAlignment="Right" - Icon="fa-solid fa-sign-out-alt" + Icon="fa-solid fa-right-from-bracket" Text="Close"/> @@ -133,13 +133,21 @@ - + + - + + + + + ()); MaterialManager.Save(); } @@ -89,7 +89,7 @@ public async void RemoveSelectedMaterials() public async void ClearMaterials() { if (Manager.Count == 0) return; - if (await this.MessageBoxQuestion($"Are you sure you want to clear {Manager.Count} materials?") != ButtonResult.Yes) return; + if (await this.MessageBoxQuestion($"Are you sure you want to clear {Manager.Count} materials?") != MessageButtonResult.Yes) return; Manager.Clear(true); } } \ No newline at end of file diff --git a/UVtools.WPF/Windows/MessageWindow.axaml b/UVtools.WPF/Windows/MessageWindow.axaml index 6b392044..5366df83 100644 --- a/UVtools.WPF/Windows/MessageWindow.axaml +++ b/UVtools.WPF/Windows/MessageWindow.axaml @@ -10,46 +10,82 @@ WindowStartupLocation="CenterScreen" SizeToContent="WidthAndHeight" MinWidth="300" - MinHeight="200" + MinHeight="140" WindowConstrainMaxSize="Ratio" WindowsWidthMaxSizeRatio="0.75" WindowsHeightMaxSizeRatio="0.75" Title="UVtools Message"> - + - - + + VerticalAlignment="Center" + HorizontalAlignment="Center" + Margin="0,0,20,0" + IsVisible="{Binding HeaderIcon, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/> - + Padding="0" + TextWrapping="{Binding TextWrap}" + VerticalAlignment="Center" + VerticalContentAlignment="Center" + ScrollViewer.HorizontalScrollBarVisibility="Disabled" + ScrollViewer.VerticalScrollBarVisibility="Auto" + IsVisible="{Binding HeaderText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" + Text="{Binding HeaderText}"/> + + + + + + + + + - + + + + + + Classes="FooterActions" + Padding="10"> - - - - - diff --git a/UVtools.WPF/Windows/MessageWindow.axaml.cs b/UVtools.WPF/Windows/MessageWindow.axaml.cs index 3a9fc50e..2c2d5d49 100644 --- a/UVtools.WPF/Windows/MessageWindow.axaml.cs +++ b/UVtools.WPF/Windows/MessageWindow.axaml.cs @@ -3,6 +3,10 @@ using Avalonia.Layout; using Avalonia.Markup.Xaml; using System; +using Avalonia.Controls.Primitives; +using Avalonia.Media; +using Markdown.Avalonia; +using UVtools.Core.Dialogs; using UVtools.Core.SystemOS; using UVtools.WPF.Controls; @@ -10,14 +14,42 @@ namespace UVtools.WPF.Windows { public partial class MessageWindow : WindowEx { + #region Constants + + public const string IconHeaderInformation = "fa-solid fa-circle-info"; + public const string IconHeaderQuestion = "fa-solid fa-circle-question"; + public const string IconHeaderWarning = "fa-solid fa-triangle-exclamation"; + public const string IconHeaderError = "fa-solid fa-circle-exclamation"; + + public const string IconButtonYes = "fa-solid fa-check"; + public const string IconButtonOk = IconButtonYes; + public const string IconButtonNo = "fa-solid fa-xmark"; + public const string IconButtonNone = IconButtonNo; + public const string IconButtonAbort = "fa-solid fa-ban"; + public const string IconButtonCancel = "fa-solid fa-ban"; + public const string IconButtonExit = "fa-solid fa-right-from-bracket"; + public const string IconButtonClose = IconButtonExit; + + public const string IconButtonDownload = "fa-solid fa-cloud-arrow-down"; + public const string IconButtonOpenBrowser = "fa-brands fa-edge"; + + #endregion + #region Members + + private TextWrapping _textWrap = TextWrapping.Wrap; private string? _headerIcon; - private ushort _headerIconSize = 64; - private string _headerTitle; - private bool _aboutButtonIsVisible = true; - private string _message; + private ushort _headerIconSize; + private string _headerText; + private bool _aboutButtonIsVisible; + private string _messageText; + private bool _renderMarkdown; + private readonly TextBox _headerTextBox; + private readonly TextBox _messageTextBox; + private readonly Border _markdownBorder; private readonly StackPanel _buttonsRightPanel; + #endregion #region Properties @@ -26,6 +58,25 @@ public partial class MessageWindow : WindowEx /// public ButtonWithIcon? PressedButton { get; private set; } + public TextWrapping TextWrap + { + get => _textWrap; + set + { + if(!RaiseAndSetIfChanged(ref _textWrap, value)) return; + if (value == TextWrapping.Wrap) + { + ScrollViewer.SetHorizontalScrollBarVisibility(_headerTextBox, ScrollBarVisibility.Disabled); + ScrollViewer.SetHorizontalScrollBarVisibility(_messageTextBox, ScrollBarVisibility.Disabled); + } + else + { + ScrollViewer.SetHorizontalScrollBarVisibility(_headerTextBox, ScrollBarVisibility.Auto); + ScrollViewer.SetHorizontalScrollBarVisibility(_messageTextBox, ScrollBarVisibility.Auto); + } + } + } + public string? HeaderIcon { get => _headerIcon; @@ -36,28 +87,40 @@ public string? HeaderIcon } } + /// + /// Gets or sets the header icon size, if 0 or negative it will auto calculate the size based on text height within a limit + /// public ushort HeaderIconSize { get => _headerIconSize; set => RaiseAndSetIfChanged(ref _headerIconSize, value); } - public string? HeaderTitle + public string? HeaderText { - get => _headerTitle; + get => _headerText; set { - if(!RaiseAndSetIfChanged(ref _headerTitle, value)) return; + if(!RaiseAndSetIfChanged(ref _headerText, value)) return; RaisePropertyChanged(nameof(HeaderIsVisible)); } } - public bool HeaderIsVisible => !string.IsNullOrWhiteSpace(HeaderIcon) || !string.IsNullOrWhiteSpace(HeaderTitle); + public bool HeaderIsVisible => !string.IsNullOrWhiteSpace(HeaderText); - public string Message + public string MessageText { - get => _message; - set => RaiseAndSetIfChanged(ref _message, value); + get => _messageText; + set => RaiseAndSetIfChanged(ref _messageText, value); + } + + /// + /// Gets or sets to render the as markdown + /// + public bool RenderMarkdown + { + get => _renderMarkdown; + set => RaiseAndSetIfChanged(ref _renderMarkdown, value); } public bool AboutButtonIsVisible @@ -74,16 +137,29 @@ public MessageWindow() InitializeComponent(); + _headerTextBox = this.FindControl("HeaderTextBox"); + _messageTextBox = this.FindControl("MessageTextBox"); + _markdownBorder = this.FindControl("MarkdownBorder"); _buttonsRightPanel = this.FindControl("ButtonsRightPanel"); DataContext = this; } - public MessageWindow(string title, string? headerIcon, string? headerTitle, string message, ButtonWithIcon[]? rightButtons = null) : this() + public MessageWindow(string title, string? headerIcon, string? headerText, string messageText, TextWrapping textWrap, ButtonWithIcon[]? rightButtons = null, bool renderMarkdown = false) : this() { - Title = title; + Title = title.Trim(); + TextWrap = textWrap; HeaderIcon = headerIcon; - HeaderTitle = headerTitle; - Message = message; + HeaderText = headerText?.Trim(); + MessageText = messageText?.Trim(); + RenderMarkdown = renderMarkdown; + + if (renderMarkdown) + { + _markdownBorder.Child = new MarkdownScrollViewer + { + Markdown = messageText + }; + } if (rightButtons is not null && rightButtons.Length > 0) { @@ -103,13 +179,32 @@ public MessageWindow(string title, string? headerIcon, string? headerTitle, stri } } - public MessageWindow(string title, string message, ButtonWithIcon[]? buttons = null) : this(title, null, null, message, buttons) { } + public MessageWindow(string title, string? headerIcon, string? headerText, string messageText, ButtonWithIcon[]? rightButtons = null, bool renderMarkdown = false) : this(title, headerIcon, headerText, messageText, TextWrapping.Wrap, rightButtons, renderMarkdown) { } + public MessageWindow(string title, string message, TextWrapping textWrap, ButtonWithIcon[]? buttons = null, bool renderMarkdown = false) : this(title, null, null, message, textWrap, buttons, renderMarkdown) { } + public MessageWindow(string title, string message, ButtonWithIcon[]? buttons = null, bool renderMarkdown = false) : this(title, null, null, message, buttons, renderMarkdown) { } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } + + protected override void OnOpened(EventArgs e) + { + base.OnOpened(e); + if (_headerIconSize <= 0 && !string.IsNullOrWhiteSpace(_headerIcon)) + { + if (HeaderIsVisible) + { + HeaderIconSize = (ushort)Math.Clamp(_headerTextBox.DesiredSize.Height, 48, 256); + } + else + { + HeaderIconSize = (ushort)Math.Clamp(_messageTextBox.DesiredSize.Height, 32, 128); + } + } + } + #endregion #region Methods @@ -120,68 +215,111 @@ public async void OpenAboutWindow() #endregion #region Static methods - public static ButtonWithIcon CreateButtonFunc(string? text, string? icon, Func customAction, int padding = 10) + public static ButtonWithIcon CreateButtonFunc(string? text, string? icon, Func customAction, int padding = 10, object? tag = null) { var button = CreateButton(text, icon, padding); button.Click += (sender, e) => e.Handled = customAction.Invoke(); return button; } - public static ButtonWithIcon CreateButtonAction(string? text, string? icon, Action customAction, int padding = 10) + public static ButtonWithIcon CreateButtonAction(string? text, string? icon, Action customAction, int padding = 10, object? tag = null) { var button = CreateButton(text, icon, padding); button.Click += (sender, e) => customAction.Invoke(); return button; } - public static ButtonWithIcon CreateButtonFunc(string? text, Func customAction, int padding = 10) => CreateButtonFunc(text, null, customAction, padding); - public static ButtonWithIcon CreateButtonAction(string? text, Action customAction, int padding = 10) => CreateButtonAction(text, null, customAction, padding); + public static ButtonWithIcon CreateButtonFunc(string? text, Func customAction, int padding = 10, object? tag = null) => CreateButtonFunc(text, null, customAction, padding); + public static ButtonWithIcon CreateButtonAction(string? text, Action customAction, int padding = 10, object? tag = null) => CreateButtonAction(text, null, customAction, padding); - public static ButtonWithIcon CreateButton(string? text, string? icon, int padding = 10) => new() + public static ButtonWithIcon CreateButton(string? text, string? icon, int padding = 10, object? tag = null) => new() { Icon = icon, Text = text, VerticalAlignment = VerticalAlignment.Center, - Padding = new Thickness(padding) + Padding = new Thickness(padding), + Tag = tag }; - public static ButtonWithIcon CreateButton(string? text, int padding = 10) => CreateButton(text, null, padding); + public static ButtonWithIcon CreateButton(string? text, int padding = 10, object? tag = null) => CreateButton(text, null, padding, tag); - public static ButtonWithIcon CreateLinkButtonAction(string? text, string? icon, string url, Action customAction, int padding = 10) + public static ButtonWithIcon CreateLinkButtonAction(string? text, string? icon, string url, Action customAction, int padding = 10, object? tag = null) { var button = CreateButtonFunc(text, icon, () => { customAction.Invoke(); SystemAware.OpenBrowser(url); return true; - }, padding); + }, padding, tag); return button; } - public static ButtonWithIcon CreateLinkButton(string? text, string? icon, string url, int padding = 10) + public static ButtonWithIcon CreateLinkButton(string? text, string? icon, string url, int padding = 10, object? tag = null) { var button = CreateButtonFunc(text, icon, () => { SystemAware.OpenBrowser(url); return true; - }, padding); + }, padding, tag); return button; } - public static ButtonWithIcon CreateLinkButtonAction(string? text, string url, Action customAction, int padding = 10) => CreateLinkButtonAction(text, null, url, customAction, padding); - public static ButtonWithIcon CreateLinkButton(string? text, string url, int padding = 10) => CreateLinkButton(text, null, url, padding); + public static ButtonWithIcon CreateLinkButtonAction(string? text, string url, Action customAction, int padding = 10, object? tag = null) => CreateLinkButtonAction(text, null, url, customAction, padding, tag); + public static ButtonWithIcon CreateLinkButton(string? text, string url, int padding = 10, object? tag = null) => CreateLinkButton(text, null, url, padding, tag); + + public static ButtonWithIcon CreateOkButton(string? icon = IconButtonOk, int padding = 10, bool isDefault = true, bool isCancel = false) + { + var btn = CreateButton("Ok", icon, padding, MessageButtonResult.Ok); + btn.IsDefault = isDefault; + btn.IsCancel = isCancel; + return btn; + } + + public static ButtonWithIcon CreateYesButton(string? icon = IconButtonYes, int padding = 10, bool isDefault = true, bool isCancel = false) + { + var btn = CreateButton("Yes", icon, padding, MessageButtonResult.Yes); + btn.IsDefault = isDefault; + btn.IsCancel = isCancel; + return btn; + } + + public static ButtonWithIcon CreateNoButton(string? icon = IconButtonNo, int padding = 10, bool isDefault = false, bool isCancel = false) + { + var btn = CreateButton("No", icon, padding, MessageButtonResult.No); + btn.IsDefault = isDefault; + btn.IsCancel = isCancel; + return btn; + } + + public static ButtonWithIcon CreateNoneButton(string? icon = IconButtonNone, int padding = 10, bool isDefault = false, bool isCancel = false) + { + var btn = CreateButton("None", icon, padding, MessageButtonResult.None); + btn.IsDefault = isDefault; + btn.IsCancel = isCancel; + return btn; + } + + public static ButtonWithIcon CreateAbortButton(string? icon = IconButtonAbort, int padding = 10, bool isDefault = false, bool isCancel = true) + { + var btn = CreateButton("Abort", icon, padding, MessageButtonResult.Abort); + btn.IsDefault = isDefault; + btn.IsCancel = isCancel; + return btn; + } - public static ButtonWithIcon CreateCancelButton(string? icon = null, int padding = 10) + public static ButtonWithIcon CreateCancelButton(string? icon = IconButtonCancel, int padding = 10, bool isDefault = false, bool isCancel = true) { - var btn = CreateButton("Cancel", icon, padding); - btn.IsCancel = true; + var btn = CreateButton("Cancel", icon, padding, MessageButtonResult.Cancel); + btn.IsDefault = isDefault; + btn.IsCancel = isCancel; return btn; } - public static ButtonWithIcon CreateCloseButton(string? icon = null, int padding = 10) + public static ButtonWithIcon CreateCloseButton(string? icon = IconButtonClose, int padding = 10, bool isDefault = false, bool isCancel = true) { - var btn = CreateButton("Close", icon, padding); - btn.IsCancel = true; + var btn = CreateButton("Close", icon, padding, MessageButtonResult.Cancel); + btn.IsDefault = isDefault; + btn.IsCancel = isCancel; return btn; } #endregion diff --git a/UVtools.WPF/Windows/MissingInformationWindow.axaml.cs b/UVtools.WPF/Windows/MissingInformationWindow.axaml.cs index 608cfb04..f9e66dba 100644 --- a/UVtools.WPF/Windows/MissingInformationWindow.axaml.cs +++ b/UVtools.WPF/Windows/MissingInformationWindow.axaml.cs @@ -7,7 +7,7 @@ */ using Avalonia; using Avalonia.Markup.Xaml; -using MessageBox.Avalonia.Enums; +using UVtools.Core.Dialogs; using UVtools.WPF.Controls; using UVtools.WPF.Extensions; @@ -70,7 +70,7 @@ private void InitializeComponent() public async void Apply() { - if (await this.MessageBoxQuestion("Are you sure you want to submit and apply the information?", "Submit and apply the information?") != ButtonResult.Yes) return; + if (await this.MessageBoxQuestion("Are you sure you want to submit and apply the information?", "Submit and apply the information?") != MessageButtonResult.Yes) return; if ((decimal)SlicerFile.DisplayWidth != _displayWidth && _displayWidth > 0) { diff --git a/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml.cs b/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml.cs index 744754c0..330ed51b 100644 --- a/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml.cs +++ b/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml.cs @@ -1,7 +1,7 @@ using Avalonia.Markup.Xaml; -using MessageBox.Avalonia.Enums; using System; using System.IO; +using UVtools.Core.Dialogs; using UVtools.WPF.Controls; using UVtools.WPF.Extensions; using UVtools.WPF.Structures; @@ -85,7 +85,7 @@ public async void InstallProfiles() "---------------\n" + "Click 'Yes' to continue\n" + "Click 'No' to cancel this operation", - $"Install printers into {slicerName}") != ButtonResult.Yes) return; + $"Install printers into {slicerName}") != MessageButtonResult.Yes) return; ushort count = 0; diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml b/UVtools.WPF/Windows/SettingsWindow.axaml index 7cd43829..28dec7b3 100644 --- a/UVtools.WPF/Windows/SettingsWindow.axaml +++ b/UVtools.WPF/Windows/SettingsWindow.axaml @@ -533,7 +533,7 @@ @@ -650,11 +650,41 @@ IsChecked="{Binding Settings.LayerPreview.ContourBoundsOutline}"/> - + + Text="Enclosing circles:"/>