diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..d9c4c72 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,26 @@ +name: Build SDUI + +on: + pull_request: + +jobs: + build: + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v1 + + - name: Restore NuGet packages + uses: nuget/setup-nuget@v1 + with: + nuget-version: "5.x" + + - name: Restore solution + run: nuget restore SDUI.sln + + - name: Build solution + run: msbuild SDUI.sln /p:Configuration=Release diff --git a/.github/workflows/dotnet-desktop.yml b/.github/workflows/dotnet-desktop.yml deleted file mode 100644 index f767f79..0000000 --- a/.github/workflows/dotnet-desktop.yml +++ /dev/null @@ -1,115 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# This workflow will build, test, sign and package a WPF or Windows Forms desktop application -# built on .NET Core. -# To learn how to migrate your existing application to .NET Core, -# refer to https://docs.microsoft.com/en-us/dotnet/desktop-wpf/migration/convert-project-from-net-framework -# -# To configure this workflow: -# -# 1. Configure environment variables -# GitHub sets default environment variables for every workflow run. -# Replace the variables relative to your project in the "env" section below. -# -# 2. Signing -# Generate a signing certificate in the Windows Application -# Packaging Project or add an existing signing certificate to the project. -# Next, use PowerShell to encode the .pfx file using Base64 encoding -# by running the following Powershell script to generate the output string: -# -# $pfx_cert = Get-Content '.\SigningCertificate.pfx' -Encoding Byte -# [System.Convert]::ToBase64String($pfx_cert) | Out-File 'SigningCertificate_Encoded.txt' -# -# Open the output file, SigningCertificate_Encoded.txt, and copy the -# string inside. Then, add the string to the repo as a GitHub secret -# and name it "Base64_Encoded_Pfx." -# For more information on how to configure your signing certificate for -# this workflow, refer to https://github.com/microsoft/github-actions-for-desktop-apps#signing -# -# Finally, add the signing certificate password to the repo as a secret and name it "Pfx_Key". -# See "Build the Windows Application Packaging project" below to see how the secret is used. -# -# For more information on GitHub Actions, refer to https://github.com/features/actions -# For a complete CI/CD sample to get started with GitHub Action workflows for Desktop Applications, -# refer to https://github.com/microsoft/github-actions-for-desktop-apps - -name: .NET Core Desktop - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -jobs: - - build: - - strategy: - matrix: - configuration: [Debug, Release] - - runs-on: windows-latest # For a list of available runner types, refer to - # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on - - env: - Solution_Name: your-solution-name # Replace with your solution name, i.e. MyWpfApp.sln. - Test_Project_Path: your-test-project-path # Replace with the path to your test project, i.e. MyWpfApp.Tests\MyWpfApp.Tests.csproj. - Wap_Project_Directory: your-wap-project-directory-name # Replace with the Wap project directory relative to the solution, i.e. MyWpfApp.Package. - Wap_Project_Path: your-wap-project-path # Replace with the path to your Wap project, i.e. MyWpf.App.Package\MyWpfApp.Package.wapproj. - - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - # Install the .NET Core workload - - name: Install .NET Core - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 6.0.x - - # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild - - name: Setup MSBuild.exe - uses: microsoft/setup-msbuild@v1.0.2 - - # Execute all unit tests in the solution - - name: Execute unit tests - run: dotnet test - - # Restore the application to populate the obj folder with RuntimeIdentifiers - - name: Restore the application - run: msbuild $env:Solution_Name /t:Restore /p:Configuration=$env:Configuration - env: - Configuration: ${{ matrix.configuration }} - - # Decode the base 64 encoded pfx and save the Signing_Certificate - - name: Decode the pfx - run: | - $pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}") - $certificatePath = Join-Path -Path $env:Wap_Project_Directory -ChildPath GitHubActionsWorkflow.pfx - [IO.File]::WriteAllBytes("$certificatePath", $pfx_cert_byte) - - # Create the app package by building and packaging the Windows Application Packaging project - - name: Create the app package - run: msbuild $env:Wap_Project_Path /p:Configuration=$env:Configuration /p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode /p:AppxBundle=$env:Appx_Bundle /p:PackageCertificateKeyFile=GitHubActionsWorkflow.pfx /p:PackageCertificatePassword=${{ secrets.Pfx_Key }} - env: - Appx_Bundle: Always - Appx_Bundle_Platforms: x86|x64 - Appx_Package_Build_Mode: StoreUpload - Configuration: ${{ matrix.configuration }} - - # Remove the pfx - - name: Remove the pfx - run: Remove-Item -path $env:Wap_Project_Directory\GitHubActionsWorkflow.pfx - - # Upload the MSIX package: https://github.com/marketplace/actions/upload-a-build-artifact - - name: Upload build artifacts - uses: actions/upload-artifact@v3 - with: - name: MSIX Package - path: ${{ env.Wap_Project_Directory }}\AppPackages diff --git a/SDUI/AnimationEngine/AnimationManager.cs b/SDUI/AnimationEngine/AnimationManager.cs index 7ed2606..4406d20 100644 --- a/SDUI/AnimationEngine/AnimationManager.cs +++ b/SDUI/AnimationEngine/AnimationManager.cs @@ -1,16 +1,17 @@ using System; using System.Windows.Forms; using SDUI.AnimationEngine; +using SDUI.Helpers; namespace SDUI.Animation { /// - /// Modern, optimize edilmiþ animation manager - ValueProvider tabanlý + /// Modern, optimize edilmiÅŸ animation manager - ValueProvider tabanlı /// public class AnimationEngine : IDisposable { private readonly ValueProvider _valueProvider; - private Timer _timer; // Lazy initialization için readonly kaldýrýldý + private Timer _timer; // Lazy initialization için readonly kaldırıldı private bool _isRunning; private bool _disposed; private System.Drawing.Point _animationSource; @@ -35,19 +36,19 @@ public AnimationEngine(bool singular = true) AnimationType = AnimationType.EaseInOut; _valueProvider = new ValueProvider(0, ValueFactories.DoubleFactory, EasingMethods.DefaultEase); - - // Timer'ý lazy initialization ile oluþtur - handle sorununu çözer + + // Timer'ı lazy initialization ile oluÅŸtur - handle sorununu çözer // _timer = new Timer { Interval = 16 }; // BU SATIR KALDIRILDI // _timer.Tick += OnTick; // BU SATIR KALDIRILDI } public bool Running => _isRunning; - - // Lazy initialization - Timer sadece gerektiðinde oluþturulur + + // Lazy initialization - Timer sadece gerektiÄŸinde oluturulur private void EnsureTimer() { if (_timer != null) return; - + try { _timer = new Timer { Interval = 16 }; // ~60 FPS @@ -55,8 +56,8 @@ private void EnsureTimer() } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"AnimationManager: Timer oluþturulamadý - {ex.Message}"); - // Timer oluþturulamazsa animasyon devre dýþý býrakýlýr + System.Diagnostics.Debug.WriteLine($"AnimationManager: Timer oluturulamad - {ex.Message}"); + // Timer oluÅŸturulamazsa animasyon devre dışı bırakılır _timer = null; } } @@ -78,6 +79,15 @@ public void StartNewAnimation(AnimationDirection direction, object[] data) public void StartNewAnimation(AnimationDirection direction, System.Drawing.Point source, object[] data) { + if (!SystemAnimations.AreAnimationsEnabled) + { + var instantTarget = direction == AnimationDirection.In || direction == AnimationDirection.InOutIn ? 1.0 : 0.0; + SetProgress(instantTarget); + OnAnimationProgress?.Invoke(this); + OnAnimationFinished?.Invoke(this); + return; + } + if (_isRunning && !InterruptAnimation) return; @@ -91,12 +101,12 @@ public void StartNewAnimation(AnimationDirection direction, System.Drawing.Point double duration = Math.Abs(target - _valueProvider.CurrentValue) / currentIncrement * 16; // milliseconds _valueProvider.StartTransition(_valueProvider.CurrentValue, target, TimeSpan.FromMilliseconds(Math.Max(16, duration))); - + _isRunning = true; - - // Timer'ý lazy initialization ile oluþtur + + // Timer'ı lazy initialization ile oluÅŸtur EnsureTimer(); - + if (_timer != null && !_timer.Enabled) _timer.Start(); } @@ -176,7 +186,7 @@ private void OnTick(object sender, EventArgs e) _timer.Stop(); OnAnimationFinished?.Invoke(this); } - + OnAnimationProgress?.Invoke(this); } @@ -201,16 +211,16 @@ private void UpdateEasingMethod() public void Dispose() { if (_disposed) return; - + if (_timer != null) { _timer.Stop(); - _timer.Tick -= OnTick; // Event handler'ý kaldýr + _timer.Tick -= OnTick; // Event handler'ı kaldır _timer.Dispose(); _timer = null; } - + _disposed = true; } } -} +} \ No newline at end of file diff --git a/SDUI/Controls/UIWindow.cs b/SDUI/Controls/UIWindow.cs index 536b94f..8253a99 100644 --- a/SDUI/Controls/UIWindow.cs +++ b/SDUI/Controls/UIWindow.cs @@ -49,6 +49,16 @@ public enum TabDesingMode /// private bool _formMoveMouseDown; + /// + /// Determines whether the user is dragging the form + /// + private bool _isDragging; + + /// + /// The starting point of the drag + /// + private Point _dragStartPoint; + /// /// The position of the form when the left mouse button is pressed /// @@ -699,6 +709,9 @@ protected override void OnMouseClick(MouseEventArgs e) if (!ShowTitle) return; + if (_isDragging) + return; + if (_inCloseBox) { _inCloseBox = false; @@ -760,9 +773,6 @@ protected override void OnMouseClick(MouseEventArgs e) if (pageRect == null) UpdateTabRects(); - if (_formMoveMouseDown && !MousePosition.Equals(_mouseOffset)) - return; - for (int i = 0; i < pageRect.Count; i++) { if (pageRect[i].Contains(e.Location)) @@ -793,6 +803,7 @@ protected override void OnMouseDown(MouseEventArgs e) if (e.Button == MouseButtons.Left && Movable) { _formMoveMouseDown = true; + _dragStartPoint = e.Location; _location = Location; _mouseOffset = MousePosition; } @@ -852,46 +863,25 @@ protected override void OnMouseUp(MouseEventArgs e) IsStayAtTopBorder = false; Cursor.Clip = new Rectangle(); _formMoveMouseDown = false; + _isDragging = false; animationSource = e.Location; } protected override void OnMouseMove(MouseEventArgs e) { - if (_formMoveMouseDown && !MousePosition.Equals(_mouseOffset)) + if (_formMoveMouseDown) { - if (WindowState == FormWindowState.Maximized) - { - int maximizedWidth = Width; - int locationX = Left; - ShowMaximize(); - - float offsetXRatio = 1 - (float)Width / maximizedWidth; - _mouseOffset.X -= (int)((_mouseOffset.X - locationX) * offsetXRatio); - } - - var offsetX = _mouseOffset.X - MousePosition.X; - var offsetY = _mouseOffset.Y - MousePosition.Y; - var _workingArea = Screen.GetWorkingArea(this); - - // If the current mouse stays on the upper edge of the container, it will trigger an edge wait for MaximumBorderInterval(ms), - // If the movement ends at this time, the window will be automatically maximized, this function is provided for multiple monitors arranged up and down - // The advantage of setting the judgment to a specific value here is that it is difficult to trigger the stay event if the form is quickly moved across the monitor - if (MousePosition.Y - _workingArea.Top == 0) + if (!_isDragging) { - if (!IsStayAtTopBorder) + var dragRect = new Rectangle(_dragStartPoint, Size.Empty); + dragRect.Inflate(SystemInformation.DragSize); + if (!dragRect.Contains(e.Location)) { - Cursor.Clip = _workingArea; - TopBorderStayTicks = DateTime.Now.Ticks; - IsStayAtTopBorder = true; - } - else if (DateTime.Now.Ticks - TopBorderStayTicks > _stickyBorderTime) - { - Cursor.Clip = new Rectangle(); + _isDragging = true; + PostDragForm(Handle); } } - - Location = new Point(_location.X - offsetX, _location.Y - offsetY); } else { @@ -985,6 +975,17 @@ protected override void OnMouseMove(MouseEventArgs e) base.OnMouseMove(e); } + protected override void WndProc(ref Message m) + { + base.WndProc(ref m); + + if (m.Msg == NativeMethods.WM_EXITSIZEMOVE) + { + _formMoveMouseDown = false; + _isDragging = false; + } + } + protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); @@ -1401,7 +1402,7 @@ private void DrawControlBoxButtons(Graphics g, Color foreColor) { // Cache hover colors var closeHoverColor = Color.FromArgb(222, 179, 30, 30); - + if (_inCloseBox) { var alpha = (int)(closeBoxHoverAnimationManager.GetProgress() * closeHoverColor.A); diff --git a/SDUI/Controls/UIWindowBase.cs b/SDUI/Controls/UIWindowBase.cs index 629137c..1f10e53 100644 --- a/SDUI/Controls/UIWindowBase.cs +++ b/SDUI/Controls/UIWindowBase.cs @@ -83,6 +83,12 @@ protected void DragForm(IntPtr handle) SendMessage(handle, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0); } + protected void PostDragForm(IntPtr handle) + { + ReleaseCapture(); + PostMessage(handle, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0); + } + protected override CreateParams CreateParams { get diff --git a/SDUI/Helpers/SystemAnimations.cs b/SDUI/Helpers/SystemAnimations.cs new file mode 100644 index 0000000..7c62eac --- /dev/null +++ b/SDUI/Helpers/SystemAnimations.cs @@ -0,0 +1,45 @@ +using System; +using System.Runtime.InteropServices; + +namespace SDUI.Helpers +{ + public static class SystemAnimations + { + private const uint SPI_GETANIMATION = 0x0048; + + [StructLayout(LayoutKind.Sequential)] + public struct ANIMATIONINFO + { + public uint cbSize; + public int iMinAnimate; + } + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool SystemParametersInfo(uint uiAction, uint uiParam, ref ANIMATIONINFO pvParam, uint fWinIni); + + private static readonly bool _areAnimationsEnabled = GetAreAnimationsEnabled(); + + public static bool AreAnimationsEnabled => _areAnimationsEnabled; + + private static bool GetAreAnimationsEnabled() + { + try + { + ANIMATIONINFO ai = new ANIMATIONINFO(); + ai.cbSize = (uint)Marshal.SizeOf(ai); + + if (SystemParametersInfo(SPI_GETANIMATION, ai.cbSize, ref ai, 0)) + { + return ai.iMinAnimate != 0; + } + + return true; // Default to true if the call fails + } + catch + { + return true; // Default to true in case of an exception + } + } + } +} diff --git a/SDUI/NativeMethods.cs b/SDUI/NativeMethods.cs index 5251b23..4214b53 100644 --- a/SDUI/NativeMethods.cs +++ b/SDUI/NativeMethods.cs @@ -52,6 +52,7 @@ public class NativeMethods public const int LVM_GETGROUPINFOBYINDEX = LVM_FIRST + 153; public const int LVM_MOVEITEMTOGROUP = LVM_FIRST + 154; public const int WM_LBUTTONUP = 0x202; + public const int WM_EXITSIZEMOVE = 0x0232; public const int LVGF_NONE = 0x0; public const int LVGF_HEADER = 0x1; @@ -141,7 +142,7 @@ public struct SubclassInfo [DllImport("user32.dll")] public static extern IntPtr GetDCEx(IntPtr hwnd, IntPtr hrgnclip, uint fdwOptions); - + [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetRect(Rect rect, int w, int h, int x, int y); @@ -211,6 +212,9 @@ public struct SubclassInfo [DllImport(user32, EntryPoint = "SendMessageW", SetLastError = true)] public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, ref Rect lParam); + [DllImport(user32, SetLastError = true)] + public static extern bool PostMessage(IntPtr hWnd, int msg, int wParam, int lParam); + [DllImport(user32, EntryPoint = "PostMessageW", SetLastError = true)] public static extern int PostMessage(IntPtr hWnd, int Msg, int wParam, ref IntPtr lParam); @@ -452,12 +456,12 @@ public static void EnableAcrylic(IWin32Window window, Color blurColor) window.Handle, ref data); } - + /// - /// Fills an area for glass rendering - /// - /// - /// + /// Fills an area for glass rendering + /// + /// + /// public static void FillForGlass(Graphics g, Rectangle r) { var rc = new Rect