From ae071a9a1ca73ddb63063dd2682efa325a34db35 Mon Sep 17 00:00:00 2001 From: Anton Kasyanov Date: Fri, 12 Aug 2016 22:06:38 +0300 Subject: [PATCH] Add movement and resize capabilities to the Thumbnail windows in borderless mode --- ...thods.cs => WindowManagerNativeMethods.cs} | 2 +- Eve-O-Preview/Eve-O-Preview.csproj | 2 +- .../Presentation/ThumbnailManager.cs | 18 +-- .../UI/Implementation/ThumbnailOverlay.cs | 2 +- .../Implementation/ThumbnailView.Designer.cs | 8 +- .../UI/Implementation/ThumbnailView.cs | 141 +++++++++++++----- 6 files changed, 124 insertions(+), 49 deletions(-) rename Eve-O-Preview/DwmAPI/{DwmApiNativeMethods.cs => WindowManagerNativeMethods.cs} (97%) diff --git a/Eve-O-Preview/DwmAPI/DwmApiNativeMethods.cs b/Eve-O-Preview/DwmAPI/WindowManagerNativeMethods.cs similarity index 97% rename from Eve-O-Preview/DwmAPI/DwmApiNativeMethods.cs rename to Eve-O-Preview/DwmAPI/WindowManagerNativeMethods.cs index e3c663f..66899eb 100644 --- a/Eve-O-Preview/DwmAPI/DwmApiNativeMethods.cs +++ b/Eve-O-Preview/DwmAPI/WindowManagerNativeMethods.cs @@ -5,7 +5,7 @@ using System.Drawing; namespace EveOPreview { // Desktop Windows Manager APIs - static class DwmApiNativeMethods + static class WindowManagerNativeMethods { [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); diff --git a/Eve-O-Preview/Eve-O-Preview.csproj b/Eve-O-Preview/Eve-O-Preview.csproj index 82a281f..e0e78ce 100644 --- a/Eve-O-Preview/Eve-O-Preview.csproj +++ b/Eve-O-Preview/Eve-O-Preview.csproj @@ -167,7 +167,7 @@ ThumbnailView.cs - + diff --git a/Eve-O-Preview/Presentation/ThumbnailManager.cs b/Eve-O-Preview/Presentation/ThumbnailManager.cs index 414bb03..16388e7 100644 --- a/Eve-O-Preview/Presentation/ThumbnailManager.cs +++ b/Eve-O-Preview/Presentation/ThumbnailManager.cs @@ -95,8 +95,8 @@ namespace EveOPreview.UI public void RefreshThumbnails() { - IntPtr foregroundWindowHandle = DwmApiNativeMethods.GetForegroundWindow(); - Boolean hideAllThumbnails = (this._configuration.HideThumbnailsOnLostFocus && this.IsNonClientWindowActive(foregroundWindowHandle)) || !DwmApiNativeMethods.DwmIsCompositionEnabled(); + IntPtr foregroundWindowHandle = WindowManagerNativeMethods.GetForegroundWindow(); + Boolean hideAllThumbnails = (this._configuration.HideThumbnailsOnLostFocus && this.IsNonClientWindowActive(foregroundWindowHandle)) || !WindowManagerNativeMethods.DwmIsCompositionEnabled(); this.DisableViewEvents(); @@ -184,7 +184,7 @@ namespace EveOPreview.UI Process[] clientProcesses = ThumbnailManager.GetClientProcesses(); List processHandles = new List(clientProcesses.Length); - IntPtr foregroundWindowHandle = DwmApiNativeMethods.GetForegroundWindow(); + IntPtr foregroundWindowHandle = WindowManagerNativeMethods.GetForegroundWindow(); List viewsAdded = new List(); List viewsUpdated = new List(); @@ -316,13 +316,13 @@ namespace EveOPreview.UI private void ThumbnailActivated(IntPtr id) { - DwmApiNativeMethods.SetForegroundWindow(id); + WindowManagerNativeMethods.SetForegroundWindow(id); - int style = DwmApiNativeMethods.GetWindowLong(id, DwmApiNativeMethods.GWL_STYLE); + int style = WindowManagerNativeMethods.GetWindowLong(id, WindowManagerNativeMethods.GWL_STYLE); // If the window was minimized then its thumbnail should be reset - if ((style & DwmApiNativeMethods.WS_MINIMIZE) == DwmApiNativeMethods.WS_MINIMIZE) + if ((style & WindowManagerNativeMethods.WS_MINIMIZE) == WindowManagerNativeMethods.WS_MINIMIZE) { - DwmApiNativeMethods.ShowWindowAsync(id, DwmApiNativeMethods.SW_SHOWNORMAL); + WindowManagerNativeMethods.ShowWindowAsync(id, WindowManagerNativeMethods.SW_SHOWNORMAL); IThumbnailView view; if (this._thumbnailViews.TryGetValue(id, out view)) { @@ -418,7 +418,7 @@ namespace EveOPreview.UI return; } - DwmApiNativeMethods.MoveWindow(clientHandle, clientLayout.X, clientLayout.Y, clientLayout.Width, clientLayout.Height, true); + WindowManagerNativeMethods.MoveWindow(clientHandle, clientLayout.X, clientLayout.Y, clientLayout.Width, clientLayout.Height, true); } private void UpdateClientLayouts() @@ -428,7 +428,7 @@ namespace EveOPreview.UI foreach (Process process in clientProcesses) { RECT rect; - DwmApiNativeMethods.GetWindowRect(process.MainWindowHandle, out rect); + WindowManagerNativeMethods.GetWindowRect(process.MainWindowHandle, out rect); int left = Math.Abs(rect.Left); int right = Math.Abs(rect.Right); diff --git a/Eve-O-Preview/UI/Implementation/ThumbnailOverlay.cs b/Eve-O-Preview/UI/Implementation/ThumbnailOverlay.cs index d02a926..986dfa7 100644 --- a/Eve-O-Preview/UI/Implementation/ThumbnailOverlay.cs +++ b/Eve-O-Preview/UI/Implementation/ThumbnailOverlay.cs @@ -32,7 +32,7 @@ namespace EveOPreview.UI get { var Params = base.CreateParams; - Params.ExStyle |= (int)DwmApiNativeMethods.WS_EX_TOOLWINDOW; + Params.ExStyle |= (int)WindowManagerNativeMethods.WS_EX_TOOLWINDOW; return Params; } } diff --git a/Eve-O-Preview/UI/Implementation/ThumbnailView.Designer.cs b/Eve-O-Preview/UI/Implementation/ThumbnailView.Designer.cs index c8680f3..9ba73e6 100644 --- a/Eve-O-Preview/UI/Implementation/ThumbnailView.Designer.cs +++ b/Eve-O-Preview/UI/Implementation/ThumbnailView.Designer.cs @@ -33,9 +33,11 @@ namespace EveOPreview.UI this.ShowInTaskbar = false; this.Text = "Preview"; this.TopMost = true; - this.MouseLeave += new System.EventHandler(this.LostFocus_Handler); - this.MouseHover += new System.EventHandler(this.Focused_Handler); - this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.ThumbnailActivated_Handler); + this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.MouseDown_Handler); + this.MouseLeave += new System.EventHandler(this.MouseLeave_Handler); + this.MouseEnter += new System.EventHandler(this.MouseEnter_Handler); + this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.MouseMove_Handler); + this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.MouseUp_Handler); this.Move += new System.EventHandler(this.Move_Handler); this.Resize += new System.EventHandler(this.Resize_Handler); this.ResumeLayout(false); diff --git a/Eve-O-Preview/UI/Implementation/ThumbnailView.cs b/Eve-O-Preview/UI/Implementation/ThumbnailView.cs index 2e252f2..59be9b1 100644 --- a/Eve-O-Preview/UI/Implementation/ThumbnailView.cs +++ b/Eve-O-Preview/UI/Implementation/ThumbnailView.cs @@ -8,22 +8,24 @@ namespace EveOPreview.UI public partial class ThumbnailView : Form, IThumbnailView { #region Private fields - - //private readonly IThumbnailManager _manager; private readonly ThumbnailOverlay _overlay; - // This is pure brainless View - // Just somewhat more complex than usual + // Part of the logic (namely current size / position management) + // was moved to the view due to the performance reasons private bool _isThumbnailSetUp; private bool _isOverlayVisible; private bool _isTopMost; private bool _isPositionChanged; private bool _isSizeChanged; + private bool _isCustomMouseModeActive; private DateTime _suppressResizeEventsTimestamp; private DWM_THUMBNAIL_PROPERTIES _thumbnail; private IntPtr _thumbnailHandle; - private Size _baseSize; - private Point _baseLocation; + private Size _baseZoomSize; + private Point _baseZoomLocation; + private Point _baseMousePosition; + private Size _baseZoomMaximumSize; + private HotkeyHandler _hotkeyHandler; #endregion @@ -39,12 +41,13 @@ namespace EveOPreview.UI this._isPositionChanged = true; this._isSizeChanged = true; + this._isCustomMouseModeActive = false; this._suppressResizeEventsTimestamp = DateTime.UtcNow; InitializeComponent(); - this._overlay = new ThumbnailOverlay(this, this.ThumbnailActivated_Handler); + this._overlay = new ThumbnailOverlay(this, this.MouseDown_Handler); } public IntPtr Id { get; set; } @@ -153,10 +156,18 @@ namespace EveOPreview.UI public void SetFrames(bool enable) { + FormBorderStyle style = enable ? FormBorderStyle.SizableToolWindow : FormBorderStyle.None; + + // No need to change the borders style if it is ALREADY correct + if (this.FormBorderStyle == style) + { + return; + } + // Fix for WinForms issue with the Resize event being fired with inconsistent ClientSize value // Any Resize events fired before this timestamp will be ignored this._suppressResizeEventsTimestamp = DateTime.UtcNow.AddMilliseconds(450); - this.FormBorderStyle = enable ? FormBorderStyle.SizableToolWindow : FormBorderStyle.None; + this.FormBorderStyle = style; // Notify about possible contents position change this._isSizeChanged = true; @@ -178,11 +189,8 @@ namespace EveOPreview.UI public void ZoomIn(ViewZoomAnchor anchor, int zoomFactor) { - this._baseSize = this.Size; - this._baseLocation = this.Location; - - int oldWidth = this._baseSize.Width; - int oldHeight = this._baseSize.Height; + int oldWidth = this._baseZoomSize.Width; + int oldHeight = this._baseZoomSize.Height; int locationX = this.Location.X; int locationY = this.Location.Y; @@ -193,6 +201,7 @@ namespace EveOPreview.UI // First change size, THEN move the window // Otherwise there is a chance to fail in a loop // Zoom requied -> Moved the windows 1st -> Focus is lost -> Window is moved back -> Focus is back on -> Zoom required -> ... + this.MaximumSize = new Size(0, 0); this.Size = new Size(newWidth, newHeight); switch (anchor) @@ -217,7 +226,7 @@ namespace EveOPreview.UI break; case ViewZoomAnchor.SW: - this.Location = new Point(locationX, locationY - newHeight + this._baseSize.Height); + this.Location = new Point(locationX, locationY - newHeight + this._baseZoomSize.Height); break; case ViewZoomAnchor.S: this.Location = new Point(locationX - newWidth / 2 + oldWidth / 2, locationY - newHeight + oldHeight); @@ -230,8 +239,7 @@ namespace EveOPreview.UI public void ZoomOut() { - this.Size = this._baseSize; - this.Location = this._baseLocation; + this.RestoreWindowSizeAndLocation(); } public void RegisterHotkey(Keys hotkey) @@ -257,7 +265,6 @@ namespace EveOPreview.UI // There can be a lot of possible exception reasons here // In case of any of them the hotkey setting is silently ignored } - } public void UnregisterHotkey() @@ -291,7 +298,7 @@ namespace EveOPreview.UI this._thumbnail.rcDestination = new RECT(0, 0, this.ClientSize.Width, this.ClientSize.Height); try { - DwmApiNativeMethods.DwmUpdateThumbnailProperties(this._thumbnailHandle, this._thumbnail); + WindowManagerNativeMethods.DwmUpdateThumbnailProperties(this._thumbnailHandle, this._thumbnail); } catch (ArgumentException) { @@ -347,7 +354,7 @@ namespace EveOPreview.UI get { var Params = base.CreateParams; - Params.ExStyle |= (int)DwmApiNativeMethods.WS_EX_TOOLWINDOW; + Params.ExStyle |= (int)WindowManagerNativeMethods.WS_EX_TOOLWINDOW; return Params; } } @@ -370,34 +377,46 @@ namespace EveOPreview.UI this.ThumbnailResized?.Invoke(this.Id); } - private void Focused_Handler(object sender, EventArgs e) + private void MouseEnter_Handler(object sender, EventArgs e) { + this.ExitCustomMouseMode(); + this.SaveWindowSizeAndLocation(); + this.ThumbnailFocused?.Invoke(this.Id); } - private void LostFocus_Handler(object sender, EventArgs e) + private void MouseLeave_Handler(object sender, EventArgs e) { this.ThumbnailLostFocus?.Invoke(this.Id); } - private void ThumbnailActivated_Handler(object sender, MouseEventArgs e) + private void MouseDown_Handler(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { this.ThumbnailActivated?.Invoke(this.Id); } - //if (e.Button == MouseButtons.Right) - //{ - // // do smth cool? - //} + if ((e.Button == MouseButtons.Right) || (e.Button == (MouseButtons.Left | MouseButtons.Right))) + { + this.EnterCustomMouseMode(); + } + } - //if (e.Button == MouseButtons.Middle) - //{ - //// Trigger full thumbnail refresh - //this.UnregisterThumbnail(); - //this.Refresh(); - //} + private void MouseMove_Handler(object sender, MouseEventArgs e) + { + if (this._isCustomMouseModeActive) + { + this.ProcessCustomMouseMode(e.Button.HasFlag(MouseButtons.Left), e.Button.HasFlag(MouseButtons.Right)); + } + } + + private void MouseUp_Handler(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Right) + { + this.ExitCustomMouseMode(); + } } private void HotkeyPressed_Handler(object sender, HandledEventArgs e) @@ -408,9 +427,29 @@ namespace EveOPreview.UI } #endregion + // This pair of methods saves/restores certain window propeties + // Methods are used to remove the 'Zoom' effect (if any) when the + // custom resize/move mode is activated + // Methods are kept on this level because moving to the presenter + // the code that responds to the mouse events like movement + // seems like a huge overkill + private void SaveWindowSizeAndLocation() + { + this._baseZoomSize = this.Size; + this._baseZoomLocation = this.Location; + this._baseZoomMaximumSize = this.MaximumSize; + } + + private void RestoreWindowSizeAndLocation() + { + this.Size = this._baseZoomSize; + this.MaximumSize = this._baseZoomMaximumSize; + this.Location = this._baseZoomLocation; + } + private void RegisterThumbnail() { - this._thumbnailHandle = DwmApiNativeMethods.DwmRegisterThumbnail(this.Handle, this.Id); + this._thumbnailHandle = WindowManagerNativeMethods.DwmRegisterThumbnail(this.Handle, this.Id); this._thumbnail = new DWM_THUMBNAIL_PROPERTIES(); this._thumbnail.dwFlags = DWM_TNP_CONSTANTS.DWM_TNP_VISIBLE @@ -428,11 +467,45 @@ namespace EveOPreview.UI { try { - DwmApiNativeMethods.DwmUnregisterThumbnail(thumbnailHandle); + WindowManagerNativeMethods.DwmUnregisterThumbnail(thumbnailHandle); } catch (ArgumentException) { } } + + private void EnterCustomMouseMode() + { + this.RestoreWindowSizeAndLocation(); + + this._isCustomMouseModeActive = true; + this._baseMousePosition = Control.MousePosition; + } + + private void ProcessCustomMouseMode(bool leftButton, bool rightButton) + { + Point mousePosition = Control.MousePosition; + int offsetX = mousePosition.X - this._baseMousePosition.X; + int offsetY = mousePosition.Y - this._baseMousePosition.Y; + this._baseMousePosition = mousePosition; + + // Left + Right buttons trigger thumbnail resize + // Right button only trigger thumbnail movement + if (leftButton && rightButton) + { + this.Size = new Size(this.Size.Width + offsetX, this.Size.Height + offsetY); + this._baseZoomSize = this.Size; + } + else + { + this.Location = new Point(this.Location.X + offsetX, this.Location.Y + offsetY); + this._baseZoomLocation = this.Location; + } + } + + private void ExitCustomMouseMode() + { + this._isCustomMouseModeActive = false; + } } } \ No newline at end of file