sources/Google.Solutions.IapDesktop.Application/Theme/VSThemeExtensions.cs (207 lines of code) (raw):

// // Copyright 2023 Google LLC // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. using Google.Solutions.Common.Util; using Google.Solutions.IapDesktop.Application.Host; using Google.Solutions.IapDesktop.Application.Windows; using Google.Solutions.Mvvm.Interop; using Google.Solutions.Mvvm.Theme; using System; using System.Diagnostics; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; using WeifenLuo.WinFormsUI.Docking; namespace Google.Solutions.IapDesktop.Application.Theme { internal class VSThemeExtensions { //--------------------------------------------------------------------- // Tool strips. //--------------------------------------------------------------------- internal class ToolStripRenderer : VisualStudioToolStripRenderer { private readonly DockPanelColorPalette palette; public ToolStripRenderer(DockPanelColorPalette palette) : base(palette) { this.palette = palette; base.UseGlassOnMenuStrip = false; // // This logic causes the border to be clipped in high-dpi // mode, and we don't need it anyway. // this.UseCustomMenuItemBackground = false; } protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) { // // The base class doesn't adjust the arrow color. // That's okay in light mode, but makes arrows almost invisible // in dark mode. // if (e.Item is ToolStripMenuItem item && item != null) { // // Sub-menu. // e.ArrowColor = this.palette.CommandBarMenuPopupDefault.Arrow; } else if (e.Item is ToolStripDropDownButton && e.Item.Owner is ToolStrip toolStrip) { if (toolStrip is StatusStrip) { // // DropDownButton main window status strip. // e.ArrowColor = this.palette.MainWindowStatusBarDefault.Text; } else { // // DropDownButton in tool strip. // e.ArrowColor = this.palette.CommandBarToolbarButtonDefault.Arrow; } } base.OnRenderArrow(e); } } //--------------------------------------------------------------------- // Float window. //--------------------------------------------------------------------- private class MinimizableFloatWindow : FloatWindow { private readonly IControlTheme? theme; public MinimizableFloatWindow( DockPanel dockPanel, DockPane pane, IControlTheme? theme) : base(dockPanel, pane) { this.AutoScaleMode = AutoScaleMode.Dpi; this.AutoScaleDimensions = DpiAwareness.DefaultDpi; this.theme = theme; } public MinimizableFloatWindow( DockPanel dockPanel, DockPane pane, Rectangle bounds, IControlTheme? theme) : base(dockPanel, pane, bounds) { this.AutoScaleMode = AutoScaleMode.Dpi; this.AutoScaleDimensions = DpiAwareness.DefaultDpi; this.theme = theme; } public void ApplyTheme() { this.theme?.ApplyTo(this); } protected override void WndProc(ref Message m) { if (base.IsDisposed) { return; } // // The base classes implementation doesn't handle clicks on the // minimize button properly, see // https://github.com/dockpanelsuite/dockpanelsuite/issues/526.. // if (m.Msg == (int)WindowMessage.WM_NCLBUTTONDOWN && m.WParam.ToInt32() == NativeMethods.HTREDUCE) { // // Eat this message so that the base class can't misinterpret it // as a click on the title bar. // } else if (m.Msg == (int)WindowMessage.WM_NCLBUTTONUP && m.WParam.ToInt32() == NativeMethods.HTREDUCE) { // // Minimize window. // NativeMethods.SendMessage( this.Handle, (int)WindowMessage.WM_SYSCOMMAND, new IntPtr(NativeMethods.SC_MINIMIZE), IntPtr.Zero); } else { base.WndProc(ref m); } } protected override void OnLayout(LayoutEventArgs levent) { base.OnLayout(levent); // // When a float window is split, the base class resets // the icon and sets the title to " ". // // When that happens, apply the standard title and // icon again so that we avoid showing a windows with a // standard icon and empty title. // if (string.IsNullOrWhiteSpace(this.Text)) { this.Text = Install.ProductName; this.Icon = Install.ProductIcon; } } protected override void Dispose(bool disposing) { try { base.Dispose(disposing); } catch (InvalidOperationException) { // // b/262842025: When the parent window is closed, it requests float // windows to dispose by sending it a WM_USER+1 message (see FloatWindow // in DockPanelSuite). // // This WM_USER+1 message is handled asynchronously. Thus, the parent // window's handle might have already been destroyed when this window is // dispatching the message. However, the base class, under some // circumstances, touches the main window handle, triggering // an exception. The exception is benign as we're disposing anyway, // so ignore it. // Debug.Assert(false, "Disposing float window failed"); } } } internal class FloatWindowFactory : DockPanelExtender.IFloatWindowFactory { public IControlTheme? Theme { get; set; } public FloatWindow CreateFloatWindow(DockPanel dockPanel, DockPane pane, Rectangle bounds) { var window = new MinimizableFloatWindow(dockPanel, pane, bounds, this.Theme); window.ApplyTheme(); return window; } public FloatWindow CreateFloatWindow(DockPanel dockPanel, DockPane pane) { var window = new MinimizableFloatWindow(dockPanel, pane, this.Theme); window.ApplyTheme(); return window; } } //--------------------------------------------------------------------- // DockPaneFactory. //--------------------------------------------------------------------- internal class DockPaneFactory : DockPanelExtender.IDockPaneFactory { private readonly DockPanelExtender.IDockPaneFactory factory; public DockPaneFactory(DockPanelExtender.IDockPaneFactory factory) { Debug.Assert(factory != null); this.factory = factory.ExpectNotNull(nameof(factory)); } public DockPane CreateDockPane( IDockContent content, DockState visibleState, bool show) { return this.factory.CreateDockPane(content, visibleState, show); } public DockPane CreateDockPane( IDockContent content, FloatWindow floatWindow, bool show) { return this.factory.CreateDockPane(content, floatWindow, show); } public DockPane CreateDockPane( IDockContent content, DockPane prevPane, DockAlignment alignment, double proportion, bool show) { return this.factory.CreateDockPane(content, prevPane, alignment, proportion, show); } public DockPane CreateDockPane( IDockContent content, Rectangle floatWindowBounds, bool show) { if (content is DocumentWindow docWindow) { // // Maintain the original client size. That's particularly // important for RDP window as resizing is slow and expensive. // var form = docWindow.DockHandler.DockPanel.FindForm(); var nonClientOverhead = new Size { Width = form.Width - form.ClientRectangle.Width, Height = form.Height - form.ClientRectangle.Height }; var pane = this.factory.CreateDockPane( content, new Rectangle( docWindow.Bounds.Location, docWindow.Bounds.Size + nonClientOverhead), show); Debug.Assert(pane.FloatWindow != null); // // Make this a first-class window. // pane.FloatWindow!.FormBorderStyle = FormBorderStyle.Sizable; pane.FloatWindow.ShowInTaskbar = true; pane.FloatWindow.Owner = null; // // Setting the properties above makes Windows forget about // whether the window was supposed to use light or dark mode. // Reapply theme to restore theming consistency. // ((MinimizableFloatWindow)pane.FloatWindow).ApplyTheme(); return pane; } else { return this.factory.CreateDockPane(content, floatWindowBounds, show); } } } //--------------------------------------------------------------------- // P/Invoke. //--------------------------------------------------------------------- private static class NativeMethods { internal const int HTREDUCE = 8; internal const int SC_MINIMIZE = 0xF020; [DllImport("user32.dll")] internal static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp); } } }