diff --git a/Eve-O-Preview/Configuration/Implementation/ThumbnailConfiguration.cs b/Eve-O-Preview/Configuration/Implementation/ThumbnailConfiguration.cs index eac17e9..779f509 100644 --- a/Eve-O-Preview/Configuration/Implementation/ThumbnailConfiguration.cs +++ b/Eve-O-Preview/Configuration/Implementation/ThumbnailConfiguration.cs @@ -24,6 +24,8 @@ namespace EveOPreview.Configuration.Implementation this.MinimizeToTray = false; this.ThumbnailRefreshPeriod = 500; + this.EnableCompatibilityMode = false; + this.ThumbnailOpacity = 0.5; this.EnableClientLayoutTracking = false; @@ -54,6 +56,9 @@ namespace EveOPreview.Configuration.Implementation public bool MinimizeToTray { get; set; } public int ThumbnailRefreshPeriod { get; set; } + [JsonProperty("CompatibilityMode")] + public bool EnableCompatibilityMode { get; set; } + [JsonProperty("ThumbnailsOpacity")] public double ThumbnailOpacity { get; set; } diff --git a/Eve-O-Preview/Configuration/Interface/IThumbnailConfiguration.cs b/Eve-O-Preview/Configuration/Interface/IThumbnailConfiguration.cs index 565e395..3cb1158 100644 --- a/Eve-O-Preview/Configuration/Interface/IThumbnailConfiguration.cs +++ b/Eve-O-Preview/Configuration/Interface/IThumbnailConfiguration.cs @@ -8,6 +8,8 @@ namespace EveOPreview.Configuration bool MinimizeToTray { get; set; } int ThumbnailRefreshPeriod { get; set; } + bool EnableCompatibilityMode { get; set; } + double ThumbnailOpacity { get; set; } bool EnableClientLayoutTracking { get; set; } diff --git a/Eve-O-Preview/Eve-O-Preview.csproj b/Eve-O-Preview/Eve-O-Preview.csproj index c469a1f..5e9d4aa 100644 --- a/Eve-O-Preview/Eve-O-Preview.csproj +++ b/Eve-O-Preview/Eve-O-Preview.csproj @@ -147,6 +147,7 @@ + @@ -157,6 +158,15 @@ + + Form + + + Component + + + Form + @@ -212,7 +222,7 @@ ThumbnailView.cs - + diff --git a/Eve-O-Preview/Program.cs b/Eve-O-Preview/Program.cs index 430e490..9ab309b 100644 --- a/Eve-O-Preview/Program.cs +++ b/Eve-O-Preview/Program.cs @@ -11,14 +11,16 @@ namespace EveOPreview { static class Program { - private static string MutexName = "EVE-O Preview Single Instance Mutex"; + private static string MUTEX_NAME = "EVE-O Preview Single Instance Mutex"; + + private static Mutex _singleInstanceMutex; /// The main entry point for the application. [STAThread] static void Main() { #if DEBUG - var expirationDate = new DateTime(2019, 5, 1); + var expirationDate = new DateTime(2019, 5, 15); if (DateTime.Today >= expirationDate) { MessageBox.Show(@"This Beta version is expired. Please download a new build at https://github.com/Phrynohyas/eve-o-preview/releases", @"EVE-O Preview", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); @@ -28,11 +30,11 @@ namespace EveOPreview // The very usual Mutex-based single-instance screening // 'token' variable is used to store reference to the instance Mutex // during the app lifetime - object token = Program.GetInstanceToken(); + Program._singleInstanceMutex = Program.GetInstanceToken(); // If it was not possible to acquire the app token then another app instance is already running // Nothing to do here - if (token == null) + if (Program._singleInstanceMutex == null) { return; } @@ -46,7 +48,7 @@ namespace EveOPreview controller.Run(); } - private static object GetInstanceToken() + private static Mutex GetInstanceToken() { // The code might look overcomplicated here for a single Mutex operation // Yet we had already experienced a Windows-level issue @@ -55,7 +57,7 @@ namespace EveOPreview // exceptions later try { - Mutex.OpenExisting(Program.MutexName); + Mutex.OpenExisting(Program.MUTEX_NAME); // if that didn't fail then another instance is already running return null; } @@ -65,7 +67,7 @@ namespace EveOPreview } catch (Exception) { - Mutex token = new Mutex(true, Program.MutexName, out var result); + Mutex token = new Mutex(true, Program.MUTEX_NAME, out var result); return result ? token : null; } } @@ -105,9 +107,11 @@ namespace EveOPreview IApplicationController controller = new ApplicationController(container); // UI classes - controller.RegisterView() - .RegisterView() - .RegisterInstance(new ApplicationContext()); + controller.RegisterView(); + controller.RegisterView(); + + controller.RegisterView(); + controller.RegisterInstance(new ApplicationContext()); return controller; } diff --git a/Eve-O-Preview/Services/Implementation/DwmThumbnail.cs b/Eve-O-Preview/Services/Implementation/DwmThumbnail.cs index 76b9388..873ded8 100644 --- a/Eve-O-Preview/Services/Implementation/DwmThumbnail.cs +++ b/Eve-O-Preview/Services/Implementation/DwmThumbnail.cs @@ -36,7 +36,7 @@ namespace EveOPreview.Services.Implementation try { - this._handle = DwmApiNativeMethods.DwmRegisterThumbnail(destination, source); + this._handle = DwmNativeMethods.DwmRegisterThumbnail(destination, source); } catch (ArgumentException) { @@ -63,7 +63,7 @@ namespace EveOPreview.Services.Implementation try { - DwmApiNativeMethods.DwmUnregisterThumbnail(this._handle); + DwmNativeMethods.DwmUnregisterThumbnail(this._handle); } catch (ArgumentException) { @@ -88,7 +88,7 @@ namespace EveOPreview.Services.Implementation try { - DwmApiNativeMethods.DwmUpdateThumbnailProperties(this._handle, this._properties); + DwmNativeMethods.DwmUpdateThumbnailProperties(this._handle, this._properties); } catch (ArgumentException) { diff --git a/Eve-O-Preview/Services/Implementation/ThumbnailManager.cs b/Eve-O-Preview/Services/Implementation/ThumbnailManager.cs index 005927c..9f55ac2 100644 --- a/Eve-O-Preview/Services/Implementation/ThumbnailManager.cs +++ b/Eve-O-Preview/Services/Implementation/ThumbnailManager.cs @@ -10,7 +10,7 @@ using MediatR; namespace EveOPreview.Services { - class ThumbnailManager : IThumbnailManager + sealed class ThumbnailManager : IThumbnailManager { #region Private constants private const int WINDOW_POSITION_THRESHOLD_LOW = -10_000; diff --git a/Eve-O-Preview/Services/Implementation/WindowManager.cs b/Eve-O-Preview/Services/Implementation/WindowManager.cs index a2096f1..b7e7135 100644 --- a/Eve-O-Preview/Services/Implementation/WindowManager.cs +++ b/Eve-O-Preview/Services/Implementation/WindowManager.cs @@ -1,14 +1,19 @@ using System; +using System.Drawing; using System.Runtime.InteropServices; using EveOPreview.Services.Interop; namespace EveOPreview.Services.Implementation { - class WindowManager : IWindowManager + sealed class WindowManager : IWindowManager { + #region Private constants + private const int WINDOW_SIZE_THRESHOLD = 300; + #endregion + public WindowManager() { - this.IsCompositionEnabled = DwmApiNativeMethods.DwmIsCompositionEnabled(); + this.IsCompositionEnabled = DwmNativeMethods.DwmIsCompositionEnabled(); } public bool IsCompositionEnabled { get; } @@ -63,12 +68,42 @@ namespace EveOPreview.Services.Implementation return User32NativeMethods.IsIconic(handle); } - public IDwmThumbnail RegisterThumbnail(IntPtr destination, IntPtr source) + public IDwmThumbnail GetLiveThumbnail(IntPtr destination, IntPtr source) { IDwmThumbnail thumbnail = new DwmThumbnail(this); thumbnail.Register(destination, source); return thumbnail; } + + public Image GetStaticThumbnail(IntPtr source) + { + var sourceContext = User32NativeMethods.GetDC(source); + + User32NativeMethods.GetClientRect(source, out RECT windowRect); + + var width = windowRect.Right - windowRect.Left; + var height = windowRect.Bottom - windowRect.Top; + + // Check if there is anything to make thumbnail of + if ((width < WINDOW_SIZE_THRESHOLD) || (height < WINDOW_SIZE_THRESHOLD)) + { + return null; + } + + var destContext = Gdi32NativeMethods.CreateCompatibleDC(sourceContext); + var bitmap = Gdi32NativeMethods.CreateCompatibleBitmap(sourceContext, width, height); + + var oldBitmap = Gdi32NativeMethods.SelectObject(destContext, bitmap); + Gdi32NativeMethods.BitBlt(destContext, 0, 0, width, height, sourceContext, 0, 0, Gdi32NativeMethods.SRCCOPY); + Gdi32NativeMethods.SelectObject(destContext, oldBitmap); + Gdi32NativeMethods.DeleteDC(destContext); + User32NativeMethods.ReleaseDC(source, sourceContext); + + Image image = Image.FromHbitmap(bitmap); + Gdi32NativeMethods.DeleteObject(bitmap); + + return image; + } } } \ No newline at end of file diff --git a/Eve-O-Preview/Services/Interface/IWindowManager.cs b/Eve-O-Preview/Services/Interface/IWindowManager.cs index e482a16..50a01b6 100644 --- a/Eve-O-Preview/Services/Interface/IWindowManager.cs +++ b/Eve-O-Preview/Services/Interface/IWindowManager.cs @@ -1,4 +1,5 @@ using System; +using System.Drawing; namespace EveOPreview.Services { @@ -15,6 +16,7 @@ namespace EveOPreview.Services (int Left, int Top, int Right, int Bottom) GetWindowPosition(IntPtr handle); bool IsWindowMinimized(IntPtr handle); - IDwmThumbnail RegisterThumbnail(IntPtr destination, IntPtr source); + IDwmThumbnail GetLiveThumbnail(IntPtr destination, IntPtr source); + Image GetStaticThumbnail(IntPtr source); } } \ No newline at end of file diff --git a/Eve-O-Preview/Services/Interop/DwmApiNativeMethods.cs b/Eve-O-Preview/Services/Interop/DwmNativeMethods.cs similarity index 95% rename from Eve-O-Preview/Services/Interop/DwmApiNativeMethods.cs rename to Eve-O-Preview/Services/Interop/DwmNativeMethods.cs index 90794e0..5cb7325 100644 --- a/Eve-O-Preview/Services/Interop/DwmApiNativeMethods.cs +++ b/Eve-O-Preview/Services/Interop/DwmNativeMethods.cs @@ -4,7 +4,7 @@ using System.Drawing; namespace EveOPreview.Services.Interop { - static class DwmApiNativeMethods + static class DwmNativeMethods { [DllImport("dwmapi.dll", PreserveSig = false)] public static extern void DwmEnableBlurBehindWindow(IntPtr hWnd, DWM_BLURBEHIND pBlurBehind); diff --git a/Eve-O-Preview/Services/Interop/Gdi32NativeMethods.cs b/Eve-O-Preview/Services/Interop/Gdi32NativeMethods.cs new file mode 100644 index 0000000..f966511 --- /dev/null +++ b/Eve-O-Preview/Services/Interop/Gdi32NativeMethods.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.InteropServices; + +namespace EveOPreview.Services.Interop +{ + static class Gdi32NativeMethods + { + public const int SRCCOPY = 13369376; + + [DllImport("gdi32.dll")] + public static extern IntPtr CreateCompatibleDC(IntPtr hdc); + + [DllImport("gdi32.dll")] + public static extern bool DeleteDC(IntPtr hdc); + + [DllImport("gdi32.dll")] + public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int width, int height); + + [DllImport("gdi32.dll")] + public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject); + + [DllImport("gdi32.dll")] + public static extern bool DeleteObject(IntPtr hObject); + + [DllImport("gdi32.dll")] + public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hObjectSource, int nXSrc, int nYSrc, int dwRop); + } +} diff --git a/Eve-O-Preview/Services/Interop/User32NativeMethods.cs b/Eve-O-Preview/Services/Interop/User32NativeMethods.cs index fbde187..6adc97a 100644 --- a/Eve-O-Preview/Services/Interop/User32NativeMethods.cs +++ b/Eve-O-Preview/Services/Interop/User32NativeMethods.cs @@ -24,7 +24,10 @@ namespace EveOPreview.Services.Interop public static extern int GetWindowLong(IntPtr hWnd, int nIndex); [DllImport("user32.dll")] - public static extern int GetWindowRect(IntPtr hwnd, out RECT rect); + public static extern int GetWindowRect(IntPtr hWnd, out RECT rect); + + [DllImport("user32.dll")] + public static extern bool GetClientRect(IntPtr hWnd, out RECT rect); [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] @@ -42,5 +45,14 @@ namespace EveOPreview.Services.Interop [DllImport("user32.dll")] public static extern bool IsZoomed(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern IntPtr GetWindowDC(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern IntPtr GetDC(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hdc); } } \ No newline at end of file diff --git a/Eve-O-Preview/View/Implementation/LiveThumbnailView.cs b/Eve-O-Preview/View/Implementation/LiveThumbnailView.cs new file mode 100644 index 0000000..543c94d --- /dev/null +++ b/Eve-O-Preview/View/Implementation/LiveThumbnailView.cs @@ -0,0 +1,48 @@ +using System; +using EveOPreview.Services; + +namespace EveOPreview.View +{ + sealed class LiveThumbnailView : ThumbnailView + { + #region Private fields + private IDwmThumbnail _thumbnail; + #endregion + + public LiveThumbnailView(IWindowManager windowManager) + : base(windowManager) + { + } + + public override void Close() + { + this._thumbnail?.Unregister(); + base.Close(); + } + + protected override void RefreshThumbnail(bool forceRefresh) + { + // To prevent flickering the old broken thumbnail is removed AFTER the new shiny one is created + IDwmThumbnail obsoleteThumbnail = forceRefresh ? this._thumbnail : null; + + if ((this._thumbnail == null) || forceRefresh) + { + this.RegisterThumbnail(); + } + + obsoleteThumbnail?.Unregister(); + } + + protected override void ResizeThumbnail(int baseWidth, int baseHeight, int highlightWidthTop, int highlightWidthRight, int highlightWidthBottom, int highlightWidthLeft) + { + this._thumbnail.Move(0 + highlightWidthLeft, 0 + highlightWidthTop, baseWidth - highlightWidthRight, baseHeight - highlightWidthBottom); + this._thumbnail.Update(); + } + + private void RegisterThumbnail() + { + this._thumbnail = this.WindowManager.GetLiveThumbnail(this.Handle, this.Id); + this._thumbnail.Update(); + } + } +} diff --git a/Eve-O-Preview/View/Implementation/MainForm.resx b/Eve-O-Preview/View/Implementation/MainForm.resx index 426d527..0537524 100644 --- a/Eve-O-Preview/View/Implementation/MainForm.resx +++ b/Eve-O-Preview/View/Implementation/MainForm.resx @@ -141,12 +141,72 @@ True + + False + + + True + + + True + + + False + + + True + + + False + + + True + + + False + + + True + + + False + + + True + False True + + False + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + True @@ -180,6 +240,39 @@ True + + False + + + True + + + False + + + True + + + False + + + True + + + True + + + True + + + True + + + False + + + True + False @@ -216,6 +309,12 @@ True + + False + + + True + False @@ -225,6 +324,54 @@ True + + False + + + True + + + True + + + True + + + False + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + True @@ -276,6 +423,27 @@ True + + False + + + True + + + True + + + True + + + True + + + True + + + True + True @@ -303,6 +471,21 @@ True + + False + + + True + + + True + + + False + + + True + True @@ -324,6 +507,36 @@ True + + False + + + True + + + False + + + True + + + False + + + True + + + True + + + False + + + True + + + True + False diff --git a/Eve-O-Preview/View/Implementation/StaticThumbnailImage.cs b/Eve-O-Preview/View/Implementation/StaticThumbnailImage.cs new file mode 100644 index 0000000..f5ec500 --- /dev/null +++ b/Eve-O-Preview/View/Implementation/StaticThumbnailImage.cs @@ -0,0 +1,23 @@ +using System; +using System.Windows.Forms; + +namespace EveOPreview.View +{ + sealed class StaticThumbnailImage : PictureBox + { + protected override void WndProc(ref Message m) + { + const int WM_NCHITTEST = 0x0084; + const int HTTRANSPARENT = (-1); + + if (m.Msg == WM_NCHITTEST) + { + m.Result = (IntPtr)HTTRANSPARENT; + } + else + { + base.WndProc(ref m); + } + } + } +} diff --git a/Eve-O-Preview/View/Implementation/StaticThumbnailView.cs b/Eve-O-Preview/View/Implementation/StaticThumbnailView.cs new file mode 100644 index 0000000..cabe743 --- /dev/null +++ b/Eve-O-Preview/View/Implementation/StaticThumbnailView.cs @@ -0,0 +1,49 @@ +using System; +using System.Drawing; +using System.Windows.Forms; +using EveOPreview.Services; + +namespace EveOPreview.View +{ + sealed class StaticThumbnailView : ThumbnailView + { + #region Private fields + private readonly PictureBox _thumbnail; + #endregion + + public StaticThumbnailView(IWindowManager windowManager) + : base(windowManager) + { + this._thumbnail = new StaticThumbnailImage + { + TabStop = false, + SizeMode = PictureBoxSizeMode.StretchImage, + Location = new Point(0, 0), + Size = new Size(this.ClientSize.Width, this.ClientSize.Height) + }; + this.Controls.Add(this._thumbnail); + } + + protected override void RefreshThumbnail(bool forceRefresh) + { + if (!forceRefresh) + { + return; + } + + var thumbnail = this.WindowManager.GetStaticThumbnail(this.Id); + if (thumbnail != null) + { + var oldImage = this._thumbnail.Image; + this._thumbnail.Image = thumbnail; + oldImage?.Dispose(); + } + } + + protected override void ResizeThumbnail(int baseWidth, int baseHeight, int highlightWidthTop, int highlightWidthRight, int highlightWidthBottom, int highlightWidthLeft) + { + this._thumbnail.Location = new Point(0 + highlightWidthLeft, 0 + highlightWidthTop); + this._thumbnail.Size = new Size(baseWidth - highlightWidthLeft - highlightWidthRight, baseHeight - highlightWidthTop - highlightWidthBottom); + } + } +} \ No newline at end of file diff --git a/Eve-O-Preview/View/Implementation/ThumbnailView.Designer.cs b/Eve-O-Preview/View/Implementation/ThumbnailView.Designer.cs index c0540fd..697d424 100644 --- a/Eve-O-Preview/View/Implementation/ThumbnailView.Designer.cs +++ b/Eve-O-Preview/View/Implementation/ThumbnailView.Designer.cs @@ -19,11 +19,12 @@ namespace EveOPreview.View // ThumbnailView // this.AccessibleRole = System.Windows.Forms.AccessibleRole.None; - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; this.BackColor = System.Drawing.Color.Black; + this.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch; this.ClientSize = new System.Drawing.Size(153, 89); this.ControlBox = false; + this.DoubleBuffered = true; this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; this.MaximizeBox = false; this.MinimizeBox = false; diff --git a/Eve-O-Preview/View/Implementation/ThumbnailView.cs b/Eve-O-Preview/View/Implementation/ThumbnailView.cs index bc5cf0f..65e1e7d 100644 --- a/Eve-O-Preview/View/Implementation/ThumbnailView.cs +++ b/Eve-O-Preview/View/Implementation/ThumbnailView.cs @@ -7,26 +7,32 @@ using EveOPreview.UI.Hotkeys; namespace EveOPreview.View { - public partial class ThumbnailView : Form, IThumbnailView + public abstract partial class ThumbnailView : Form, IThumbnailView { #region Private constants private const int RESIZE_EVENT_TIMEOUT = 500; +f private const double OPACITY_THRESHOLD = 0.9; + private const double OPACITY_EPSILON = 0.1; #endregion #region Private fields - private readonly IWindowManager _windowManager; private readonly ThumbnailOverlay _overlay; - private IDwmThumbnail _thumbnail; // Part of the logic (namely current size / position management) // was moved to the view due to the performance reasons private bool _isOverlayVisible; private bool _isTopMost; + private bool _isHighlightEnabled; + private bool _isHighlightRequested; + private int _highlightWidth; + private bool _isLocationChanged; private bool _isSizeChanged; + private bool _isCustomMouseModeActive; - private bool _isHighlightEnabled; - private int _highlightWidth; + + private double _opacity; + private DateTime _suppressResizeEventsTimestamp; private Size _baseZoomSize; private Point _baseZoomLocation; @@ -36,29 +42,34 @@ namespace EveOPreview.View private HotkeyHandler _hotkeyHandler; #endregion - public ThumbnailView(IWindowManager windowManager) + protected ThumbnailView(IWindowManager windowManager) { this.SuppressResizeEvent(); - this._windowManager = windowManager; + this.WindowManager = windowManager; this.IsActive = false; this.IsOverlayEnabled = false; this._isOverlayVisible = false; this._isTopMost = false; + this._isHighlightEnabled = false; + this._isHighlightRequested = false; this._isLocationChanged = true; this._isSizeChanged = true; + this._isCustomMouseModeActive = false; - this._isHighlightEnabled = false; + this._opacity = 1.0; InitializeComponent(); this._overlay = new ThumbnailOverlay(this, this.MouseDown_Handler); } + protected IWindowManager WindowManager { get; } + public IntPtr Id { get; set; } public string Title @@ -131,12 +142,11 @@ namespace EveOPreview.View base.Hide(); } - public new void Close() + public new virtual void Close() { this.SuppressResizeEvent(); this.IsActive = false; - this._thumbnail?.Unregister(); this._overlay.Close(); base.Close(); } @@ -155,13 +165,33 @@ namespace EveOPreview.View public void SetOpacity(double opacity) { - this.Opacity = opacity; + if (opacity >= OPACITY_THRESHOLD) + { + opacity = 1.0; + } - // Overlay opacity settings - // Of the thumbnail's opacity is almost full then set the overlay's one to - // full. Otherwise set it to half of the thumbnail opacity - // Opacity value is stored even if the overlay is not displayed atm - this._overlay.Opacity = this.Opacity > 0.9 ? 1.0 : 1.0 - (1.0 - this.Opacity) / 2; + if (Math.Abs(opacity - this._opacity) < OPACITY_EPSILON) + { + return; + } + + try + { + this.Opacity = opacity; + + // Overlay opacity settings + // Of the thumbnail's opacity is almost full then set the overlay's one to + // full. Otherwise set it to half of the thumbnail opacity + // Opacity value is stored even if the overlay is not displayed atm + this._overlay.Opacity = opacity > 0.8 ? 1.0 : 1.0 - (1.0 - opacity) / 2; + + this._opacity = opacity; + } + catch (Win32Exception) + { + // Something went wrong in WinForms internals + // Opacity will be updated in the next cycle + } } public void SetFrames(bool enable) @@ -177,9 +207,6 @@ namespace EveOPreview.View this.SuppressResizeEvent(); this.FormBorderStyle = style; - - // Notify about possible contents position change - this._isSizeChanged = true; } public void SetTopMost(bool enableTopmost) @@ -198,20 +225,20 @@ namespace EveOPreview.View public void SetHighlight(bool enabled, Color color, int width) { - if (this._isHighlightEnabled == enabled) + if (this._isHighlightRequested == enabled) { return; } if (enabled) { - this._isHighlightEnabled = true; + this._isHighlightRequested = true; this._highlightWidth = width; this.BackColor = color; } else { - this._isHighlightEnabled = false; + this._isHighlightRequested = false; this.BackColor = SystemColors.Control; } @@ -305,69 +332,31 @@ namespace EveOPreview.View public void Refresh(bool forceRefresh) { - // To prevent flickering the old broken thumbnail is removed AFTER the new shiny one is created - IDwmThumbnail obsoleteThumbnail = forceRefresh ? this._thumbnail : null; + this.RefreshThumbnail(forceRefresh); + this.HighlightThumbnail(forceRefresh || this._isSizeChanged); + this.RefreshOverlay(forceRefresh || this._isSizeChanged || this._isLocationChanged); - if ((this._thumbnail == null) || forceRefresh) - { - this.RegisterThumbnail(); - } - - bool sizeChanged = this._isSizeChanged || forceRefresh; - bool locationChanged = this._isLocationChanged || forceRefresh; - - if (sizeChanged) - { - this.RecalculateThumbnailSize(); - - this.UpdateThumbnail(); - - this._isSizeChanged = false; - } - - obsoleteThumbnail?.Unregister(); - - this._overlay.EnableOverlayLabel(this.IsOverlayEnabled); - - if (!this._isOverlayVisible) - { - // One-time action to show the Overlay before it is set up - // Otherwise its position won't be set - this._overlay.Show(); - this._isOverlayVisible = true; - } - else - { - if (!(sizeChanged || locationChanged)) - { - // No need to adjust in the overlay location if it is already visible and properly set - return; - } - } - - Size overlaySize = this.ClientSize; - Point overlayLocation = this.Location; - - int borderWidth = (this.Size.Width - this.ClientSize.Width) / 2; - overlayLocation.X += borderWidth; - overlayLocation.Y += (this.Size.Height - this.ClientSize.Height) - borderWidth; - - this._isLocationChanged = false; - this._overlay.Size = overlaySize; - this._overlay.Location = overlayLocation; - this._overlay.Refresh(); + this._isSizeChanged = false; } - private void RecalculateThumbnailSize() + protected abstract void RefreshThumbnail(bool forceRefresh); + + protected abstract void ResizeThumbnail(int baseWidth, int baseHeight, int highlightWidthTop, int highlightWidthRight, int highlightWidthBottom, int highlightWidthLeft); + + private void HighlightThumbnail(bool forceRefresh) { - // This approach would work only for square-shaped thumbnail window - // To get PROPER results we have to do some crazy math - //int delta = this._isHighlightEnabled ? this._highlightWidth : 0; - //this._thumbnail.rcDestination = new RECT(0 + delta, 0 + delta, this.ClientSize.Width - delta, this.ClientSize.Height - delta); - if (!this._isHighlightEnabled) + if (!forceRefresh && (this._isHighlightRequested == this._isHighlightEnabled)) { - //No highlighting enabled, so no odd math required - this._thumbnail.Move(0, 0, this.ClientSize.Width, this.ClientSize.Height); + // Nothing to do here + return; + } + + this._isHighlightEnabled = this._isHighlightRequested; + + if (!this._isHighlightRequested) + { + //No highlighting enabled, so no math required + this.ResizeThumbnail(this.ClientSize.Width, this.ClientSize.Height, 0, 0, 0, 0); return; } @@ -381,7 +370,38 @@ namespace EveOPreview.View int highlightWidthLeft = (baseWidth - actualWidth) / 2; int highlightWidthRight = baseWidth - actualWidth - highlightWidthLeft; - this._thumbnail.Move(0 + highlightWidthLeft, 0 + this._highlightWidth, baseWidth - highlightWidthRight, baseHeight - this._highlightWidth); + this.ResizeThumbnail(this.ClientSize.Width, this.ClientSize.Height, this._highlightWidth, highlightWidthRight, this._highlightWidth, highlightWidthLeft); + } + + private void RefreshOverlay(bool forceRefresh) + { + if (this._isOverlayVisible && !forceRefresh) + { + // No need to update anything. Everything is already set up + return; + } + + this._overlay.EnableOverlayLabel(this.IsOverlayEnabled); + + if (!this._isOverlayVisible) + { + // One-time action to show the Overlay before it is set up + // Otherwise its position won't be set + this._overlay.Show(); + this._isOverlayVisible = true; + } + + Size overlaySize = this.ClientSize; + Point overlayLocation = this.Location; + + int borderWidth = (this.Size.Width - this.ClientSize.Width) / 2; + overlayLocation.X += borderWidth; + overlayLocation.Y += (this.Size.Height - this.ClientSize.Height) - borderWidth; + + this._isLocationChanged = false; + this._overlay.Size = overlaySize; + this._overlay.Location = overlayLocation; + this._overlay.Refresh(); } private void SuppressResizeEvent() @@ -482,18 +502,6 @@ namespace EveOPreview.View } #endregion - #region Thumbnail management - private void RegisterThumbnail() - { - this._thumbnail = this._windowManager.RegisterThumbnail(this.Handle, this.Id); - } - - private void UpdateThumbnail() - { - this._thumbnail.Update(); - } - #endregion - #region Custom Mouse mode // This pair of methods saves/restores certain window properties // Methods are used to remove the 'Zoom' effect (if any) when the diff --git a/Eve-O-Preview/View/Implementation/ThumbnailViewFactory.cs b/Eve-O-Preview/View/Implementation/ThumbnailViewFactory.cs index 22c8715..83d271e 100644 --- a/Eve-O-Preview/View/Implementation/ThumbnailViewFactory.cs +++ b/Eve-O-Preview/View/Implementation/ThumbnailViewFactory.cs @@ -1,20 +1,25 @@ using System; using System.Drawing; +using EveOPreview.Configuration; namespace EveOPreview.View { sealed class ThumbnailViewFactory : IThumbnailViewFactory { private readonly IApplicationController _controller; + private readonly bool _isCompatibilityModeEnabled; - public ThumbnailViewFactory(IApplicationController controller) + public ThumbnailViewFactory(IApplicationController controller, IThumbnailConfiguration configuration) { this._controller = controller; + this._isCompatibilityModeEnabled = configuration.EnableCompatibilityMode; } public IThumbnailView Create(IntPtr id, string title, Size size) { - IThumbnailView view = this._controller.Create(); + IThumbnailView view = this._isCompatibilityModeEnabled + ? (IThumbnailView)this._controller.Create() + : (IThumbnailView)this._controller.Create(); view.Id = id; view.Title = title; diff --git a/Eve-O-Preview/app.manifest b/Eve-O-Preview/app.manifest index 5fc7044..cc914b0 100644 --- a/Eve-O-Preview/app.manifest +++ b/Eve-O-Preview/app.manifest @@ -74,8 +74,8 @@ --> - True/PM - PerMonitorV2 + True + PerMonitor diff --git a/README.md b/README.md index 03ac55d..5d5997b 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ Some of the application options are not exposed in the GUI. They can be adjusted | Option | Description | | --- | --- | | **ActiveClientHighlightThickness** | Thickness of the border used to highlight the active client's thumbnail.
Allowed values are **1**...**6**.
The default value is **3**
For example: **"ActiveClientHighlightThickness": 3** | +| **CompatibilityMode** | Enables the alternative render mode (see below)
The default value is **false**
For example: **"CompatibilityMode": true** | | **EnableThumbnailSnap** | Allows to disable thumbnails snap feature by setting its value to **false**
The default value is **true**
For example: **"EnableThumbnailSnap": true** | | **PriorityClients** | Allows to set a list of clients that are not auto-minimized on inactivity even if the **Minimize inactive EVE clients** option is enabled. Listed clients still can be minimized using Windows hotkeys or via _Ctrl+Click_ on the corresponding thumbnail
The default value is empty list **[]**
For example: **"PriorityClients": [ "EVE - Phrynohyas Tig-Rah", "EVE - Ondatra Patrouette" ]** | | **ThumbnailMinimumSize** | Minimum thumbnail size that can be set either via GUI or by resizing a thumbnail window. Value is written in the form "width, height"
The default value is **"100, 80"**.
For example: **"ThumbnailMinimumSize": "100, 80"** | @@ -124,6 +125,14 @@ The following hotkey is described as `modifier+key` where `modifier` can be **Co **Note:** Do not set hotkeys to use the key combinations already used by EVE. It won't work as "_I set hotkey for my DPS char to F1 and when I'll press F1 it will automatically open the DPS char's window and activate guns_". Key combination will be swallowed by EVE-O Preview and NOT retranslated to EVE window. So it will be only "_it will automatically open the DPS char's window_". +## Compatibility Mode + +This setting allows to enable an alternate thumbnail render. This render doesn't use advanced DWM API to create live previews. Instead it is a screenshot-based render with the following pros and cons: +* `+` Doesn't require Aero to work +* `+` Should work even in remote desktop environments +* `-` Consumes significantly more memory. In the testing environment EVE-O Preview did consume around 180 MB to manage 3 thumbnails using this render. At the same time the primary render did consume around 50 MB when run in the same environment. +* `-` Thumbnail images are refreshed at 1 FPS rate +* `-` Possible short mouse cursor freezes ---