mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-06 21:52:11 -04:00
Repo-wide sweep with the pinned CSharpier 1.3.0 tool. Whitespace and line-wrapping only; OpenNest.Engine.Tests (109) and OpenNest.IO.Tests pass after reformat, full solution builds 0 errors. Added .csharpierignore so csproj/config XML keeps its existing layout (CSharpier's XML wrapping churns attributes with zero benefit). Formatting is now enforceable: dotnet csharpier check . passes.
645 lines
23 KiB
C#
645 lines
23 KiB
C#
using System;
|
|
using System.Drawing;
|
|
using System.Runtime.InteropServices;
|
|
using System.Windows.Forms;
|
|
using System.Windows.Forms.VisualStyles;
|
|
|
|
namespace OpenNest
|
|
{
|
|
public enum ToolbarTheme
|
|
{
|
|
Toolbar,
|
|
MediaToolbar,
|
|
CommunicationsToolbar,
|
|
BrowserTabBar,
|
|
HelpBar,
|
|
}
|
|
|
|
/// <summary>Renders a toolstrip using the UxTheme API via VisualStyleRenderer and a specific style.</summary>
|
|
/// <remarks>Perhaps surprisingly, this does not need to be disposable.</remarks>
|
|
public class ToolStripRenderer : ToolStripSystemRenderer
|
|
{
|
|
VisualStyleRenderer renderer;
|
|
|
|
public ToolStripRenderer(ToolbarTheme theme)
|
|
{
|
|
Theme = theme;
|
|
}
|
|
|
|
/// <summary>
|
|
/// It shouldn't be necessary to P/Invoke like this, however VisualStyleRenderer.GetMargins
|
|
/// misses out a parameter in its own P/Invoke.
|
|
/// </summary>
|
|
internal static class NativeMethods
|
|
{
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct MARGINS
|
|
{
|
|
public int cxLeftWidth;
|
|
public int cxRightWidth;
|
|
public int cyTopHeight;
|
|
public int cyBottomHeight;
|
|
}
|
|
|
|
[DllImport("uxtheme.dll")]
|
|
public static extern int GetThemeMargins(
|
|
IntPtr hTheme,
|
|
IntPtr hdc,
|
|
int iPartId,
|
|
int iStateId,
|
|
int iPropId,
|
|
IntPtr rect,
|
|
out MARGINS pMargins
|
|
);
|
|
}
|
|
|
|
// See http://msdn2.microsoft.com/en-us/library/bb773210.aspx - "Parts and States"
|
|
// Only menu-related parts/states are needed here, VisualStyleRenderer handles most of the rest.
|
|
enum MenuParts : int
|
|
{
|
|
ItemTMSchema = 1,
|
|
DropDownTMSchema = 2,
|
|
BarItemTMSchema = 3,
|
|
BarDropDownTMSchema = 4,
|
|
ChevronTMSchema = 5,
|
|
SeparatorTMSchema = 6,
|
|
BarBackground = 7,
|
|
BarItem = 8,
|
|
PopupBackground = 9,
|
|
PopupBorders = 10,
|
|
PopupCheck = 11,
|
|
PopupCheckBackground = 12,
|
|
PopupGutter = 13,
|
|
PopupItem = 14,
|
|
PopupSeparator = 15,
|
|
PopupSubmenu = 16,
|
|
SystemClose = 17,
|
|
SystemMaximize = 18,
|
|
SystemMinimize = 19,
|
|
SystemRestore = 20,
|
|
}
|
|
|
|
enum MenuBarStates : int
|
|
{
|
|
Active = 1,
|
|
Inactive = 2,
|
|
}
|
|
|
|
enum MenuBarItemStates : int
|
|
{
|
|
Normal = 1,
|
|
Hover = 2,
|
|
Pushed = 3,
|
|
Disabled = 4,
|
|
DisabledHover = 5,
|
|
DisabledPushed = 6,
|
|
}
|
|
|
|
enum MenuPopupItemStates : int
|
|
{
|
|
Normal = 1,
|
|
Hover = 2,
|
|
Disabled = 3,
|
|
DisabledHover = 4,
|
|
}
|
|
|
|
enum MenuPopupCheckStates : int
|
|
{
|
|
CheckmarkNormal = 1,
|
|
CheckmarkDisabled = 2,
|
|
BulletNormal = 3,
|
|
BulletDisabled = 4,
|
|
}
|
|
|
|
enum MenuPopupCheckBackgroundStates : int
|
|
{
|
|
Disabled = 1,
|
|
Normal = 2,
|
|
Bitmap = 3,
|
|
}
|
|
|
|
enum MenuPopupSubMenuStates : int
|
|
{
|
|
Normal = 1,
|
|
Disabled = 2,
|
|
}
|
|
|
|
enum MarginTypes : int
|
|
{
|
|
Sizing = 3601,
|
|
Content = 3602,
|
|
Caption = 3603,
|
|
}
|
|
|
|
static readonly int RebarBackground = 6;
|
|
|
|
Padding GetThemeMargins(IDeviceContext dc, MarginTypes marginType)
|
|
{
|
|
NativeMethods.MARGINS margins;
|
|
try
|
|
{
|
|
IntPtr hDC = dc.GetHdc();
|
|
if (
|
|
0
|
|
== NativeMethods.GetThemeMargins(
|
|
renderer.Handle,
|
|
hDC,
|
|
renderer.Part,
|
|
renderer.State,
|
|
(int)marginType,
|
|
IntPtr.Zero,
|
|
out margins
|
|
)
|
|
)
|
|
return new Padding(
|
|
margins.cxLeftWidth,
|
|
margins.cyTopHeight,
|
|
margins.cxRightWidth,
|
|
margins.cyBottomHeight
|
|
);
|
|
return new Padding(0);
|
|
}
|
|
finally
|
|
{
|
|
dc.ReleaseHdc();
|
|
}
|
|
}
|
|
|
|
private static int GetItemState(ToolStripItem item)
|
|
{
|
|
bool hot = item.Selected;
|
|
|
|
if (item.IsOnDropDown)
|
|
{
|
|
if (item.Enabled)
|
|
return hot ? (int)MenuPopupItemStates.Hover : (int)MenuPopupItemStates.Normal;
|
|
return hot
|
|
? (int)MenuPopupItemStates.DisabledHover
|
|
: (int)MenuPopupItemStates.Disabled;
|
|
}
|
|
else
|
|
{
|
|
if (item.Pressed)
|
|
return item.Enabled
|
|
? (int)MenuBarItemStates.Pushed
|
|
: (int)MenuBarItemStates.DisabledPushed;
|
|
if (item.Enabled)
|
|
return hot ? (int)MenuBarItemStates.Hover : (int)MenuBarItemStates.Normal;
|
|
return hot ? (int)MenuBarItemStates.DisabledHover : (int)MenuBarItemStates.Disabled;
|
|
}
|
|
}
|
|
|
|
public ToolbarTheme Theme { get; set; }
|
|
|
|
private string RebarClass
|
|
{
|
|
get { return SubclassPrefix + "Rebar"; }
|
|
}
|
|
|
|
private string ToolbarClass
|
|
{
|
|
get { return SubclassPrefix + "ToolBar"; }
|
|
}
|
|
|
|
private string MenuClass
|
|
{
|
|
get { return SubclassPrefix + "Menu"; }
|
|
}
|
|
|
|
private string SubclassPrefix
|
|
{
|
|
get
|
|
{
|
|
switch (Theme)
|
|
{
|
|
case ToolbarTheme.MediaToolbar:
|
|
return "Media::";
|
|
case ToolbarTheme.CommunicationsToolbar:
|
|
return "Communications::";
|
|
case ToolbarTheme.BrowserTabBar:
|
|
return "BrowserTabBar::";
|
|
case ToolbarTheme.HelpBar:
|
|
return "Help::";
|
|
default:
|
|
return string.Empty;
|
|
}
|
|
}
|
|
}
|
|
|
|
private VisualStyleElement Subclass(VisualStyleElement element)
|
|
{
|
|
return VisualStyleElement.CreateElement(
|
|
SubclassPrefix + element.ClassName,
|
|
element.Part,
|
|
element.State
|
|
);
|
|
}
|
|
|
|
private bool EnsureRenderer()
|
|
{
|
|
if (!IsSupported)
|
|
return false;
|
|
|
|
if (renderer == null)
|
|
renderer = new VisualStyleRenderer(VisualStyleElement.Button.PushButton.Normal);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Gives parented ToolStrips a transparent background.
|
|
protected override void Initialize(ToolStrip toolStrip)
|
|
{
|
|
if (toolStrip.Parent is ToolStripPanel)
|
|
toolStrip.BackColor = Color.Transparent;
|
|
|
|
base.Initialize(toolStrip);
|
|
}
|
|
|
|
// Using just ToolStripManager.Renderer without setting the Renderer individually per ToolStrip means
|
|
// that the ToolStrip is not passed to the Initialize method. ToolStripPanels, however, are. So we can
|
|
// simply initialize it here too, and this should guarantee that the ToolStrip is initialized at least
|
|
// once. Hopefully it isn't any more complicated than this.
|
|
protected override void InitializePanel(ToolStripPanel toolStripPanel)
|
|
{
|
|
foreach (Control control in toolStripPanel.Controls)
|
|
if (control is ToolStrip)
|
|
Initialize((ToolStrip)control);
|
|
|
|
base.InitializePanel(toolStripPanel);
|
|
}
|
|
|
|
protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e)
|
|
{
|
|
if (EnsureRenderer())
|
|
{
|
|
renderer.SetParameters(MenuClass, (int)MenuParts.PopupBorders, 0);
|
|
if (e.ToolStrip.IsDropDown)
|
|
{
|
|
Region oldClip = e.Graphics.Clip;
|
|
|
|
// Tool strip borders are rendered *after* the content, for some reason.
|
|
// So we have to exclude the inside of the popup otherwise we'll draw over it.
|
|
Rectangle insideRect = e.ToolStrip.ClientRectangle;
|
|
insideRect.Inflate(-1, -1);
|
|
e.Graphics.ExcludeClip(insideRect);
|
|
|
|
renderer.DrawBackground(
|
|
e.Graphics,
|
|
e.ToolStrip.ClientRectangle,
|
|
e.AffectedBounds
|
|
);
|
|
|
|
// Restore the old clip in case the Graphics is used again (does that ever happen?)
|
|
e.Graphics.Clip = oldClip;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
base.OnRenderToolStripBorder(e);
|
|
}
|
|
}
|
|
|
|
Rectangle GetBackgroundRectangle(ToolStripItem item)
|
|
{
|
|
if (!item.IsOnDropDown)
|
|
return new Rectangle(new Point(), item.Bounds.Size);
|
|
|
|
// For a drop-down menu item, the background rectangles of the items should be touching vertically.
|
|
// This ensures that's the case.
|
|
Rectangle rect = item.Bounds;
|
|
|
|
// The background rectangle should be inset two pixels horizontally (on both sides), but we have
|
|
// to take into account the border.
|
|
rect.X = item.ContentRectangle.X + 1;
|
|
rect.Width = item.ContentRectangle.Width - 1;
|
|
|
|
// Make sure we're using all of the vertical space, so that the edges touch.
|
|
rect.Y = 0;
|
|
return rect;
|
|
}
|
|
|
|
protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e)
|
|
{
|
|
if (EnsureRenderer())
|
|
{
|
|
int partID = e.Item.IsOnDropDown
|
|
? (int)MenuParts.PopupItem
|
|
: (int)MenuParts.BarItem;
|
|
renderer.SetParameters(MenuClass, partID, GetItemState(e.Item));
|
|
|
|
Rectangle bgRect = GetBackgroundRectangle(e.Item);
|
|
renderer.DrawBackground(e.Graphics, bgRect, bgRect);
|
|
}
|
|
else
|
|
{
|
|
base.OnRenderMenuItemBackground(e);
|
|
}
|
|
}
|
|
|
|
protected override void OnRenderToolStripPanelBackground(ToolStripPanelRenderEventArgs e)
|
|
{
|
|
if (EnsureRenderer())
|
|
{
|
|
// Draw the background using Rebar & RP_BACKGROUND (or, if that is not available, fall back to
|
|
// Rebar.Band.Normal)
|
|
if (
|
|
VisualStyleRenderer.IsElementDefined(
|
|
VisualStyleElement.CreateElement(RebarClass, RebarBackground, 0)
|
|
)
|
|
)
|
|
{
|
|
renderer.SetParameters(RebarClass, RebarBackground, 0);
|
|
}
|
|
else
|
|
{
|
|
renderer.SetParameters(RebarClass, 0, 0);
|
|
}
|
|
|
|
if (renderer.IsBackgroundPartiallyTransparent())
|
|
renderer.DrawParentBackground(
|
|
e.Graphics,
|
|
e.ToolStripPanel.ClientRectangle,
|
|
e.ToolStripPanel
|
|
);
|
|
|
|
renderer.DrawBackground(e.Graphics, e.ToolStripPanel.ClientRectangle);
|
|
|
|
e.Handled = true;
|
|
}
|
|
else
|
|
{
|
|
base.OnRenderToolStripPanelBackground(e);
|
|
}
|
|
}
|
|
|
|
// Render the background of an actual menu bar, dropdown menu or toolbar.
|
|
protected override void OnRenderToolStripBackground(
|
|
System.Windows.Forms.ToolStripRenderEventArgs e
|
|
)
|
|
{
|
|
if (EnsureRenderer())
|
|
{
|
|
if (e.ToolStrip.IsDropDown)
|
|
{
|
|
renderer.SetParameters(MenuClass, (int)MenuParts.PopupBackground, 0);
|
|
}
|
|
else
|
|
{
|
|
// It's a MenuStrip or a ToolStrip. If it's contained inside a larger panel, it should have a
|
|
// transparent background, showing the panel's background.
|
|
|
|
if (e.ToolStrip.Parent is ToolStripPanel)
|
|
{
|
|
// The background should be transparent, because the ToolStripPanel's background will be visible.
|
|
// (Of course, we assume the ToolStripPanel is drawn using the same theme, but it's not my fault
|
|
// if someone does that.)
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// A lone toolbar/menubar should act like it's inside a toolbox, I guess.
|
|
// Maybe I should use the MenuClass in the case of a MenuStrip, although that would break
|
|
// the other themes...
|
|
if (
|
|
VisualStyleRenderer.IsElementDefined(
|
|
VisualStyleElement.CreateElement(RebarClass, RebarBackground, 0)
|
|
)
|
|
)
|
|
renderer.SetParameters(RebarClass, RebarBackground, 0);
|
|
else
|
|
renderer.SetParameters(RebarClass, 0, 0);
|
|
}
|
|
}
|
|
|
|
if (renderer.IsBackgroundPartiallyTransparent())
|
|
renderer.DrawParentBackground(
|
|
e.Graphics,
|
|
e.ToolStrip.ClientRectangle,
|
|
e.ToolStrip
|
|
);
|
|
|
|
renderer.DrawBackground(e.Graphics, e.ToolStrip.ClientRectangle, e.AffectedBounds);
|
|
}
|
|
else
|
|
{
|
|
base.OnRenderToolStripBackground(e);
|
|
}
|
|
}
|
|
|
|
// The only purpose of this override is to change the arrow colour.
|
|
// It's OK to just draw over the default arrow since we also pass down arrow drawing to the system renderer.
|
|
protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventArgs e)
|
|
{
|
|
if (EnsureRenderer())
|
|
{
|
|
ToolStripSplitButton sb = (ToolStripSplitButton)e.Item;
|
|
base.OnRenderSplitButtonBackground(e);
|
|
|
|
// It doesn't matter what colour of arrow we tell it to draw. OnRenderArrow will compute it from the item anyway.
|
|
OnRenderArrow(
|
|
new ToolStripArrowRenderEventArgs(
|
|
e.Graphics,
|
|
sb,
|
|
sb.DropDownButtonBounds,
|
|
Color.Red,
|
|
ArrowDirection.Down
|
|
)
|
|
);
|
|
}
|
|
else
|
|
{
|
|
base.OnRenderSplitButtonBackground(e);
|
|
}
|
|
}
|
|
|
|
Color GetItemTextColor(ToolStripItem item)
|
|
{
|
|
int partId = item.IsOnDropDown ? (int)MenuParts.PopupItem : (int)MenuParts.BarItem;
|
|
renderer.SetParameters(MenuClass, partId, GetItemState(item));
|
|
return renderer.GetColor(ColorProperty.TextColor);
|
|
}
|
|
|
|
protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
|
|
{
|
|
if (EnsureRenderer())
|
|
e.TextColor = GetItemTextColor(e.Item);
|
|
|
|
base.OnRenderItemText(e);
|
|
}
|
|
|
|
protected override void OnRenderImageMargin(ToolStripRenderEventArgs e)
|
|
{
|
|
if (EnsureRenderer())
|
|
{
|
|
if (e.ToolStrip.IsDropDown)
|
|
{
|
|
renderer.SetParameters(MenuClass, (int)MenuParts.PopupGutter, 0);
|
|
// The AffectedBounds is usually too small, way too small to look right. Instead of using that,
|
|
// use the AffectedBounds but with the right width. Then narrow the rectangle to the correct edge
|
|
// based on whether or not it's RTL. (It doesn't need to be narrowed to an edge in LTR mode, but let's
|
|
// do that anyway.)
|
|
// Using the DisplayRectangle gets roughly the right size so that the separator is closer to the text.
|
|
Padding margins = GetThemeMargins(e.Graphics, MarginTypes.Sizing);
|
|
int extraWidth =
|
|
(
|
|
e.ToolStrip.Width
|
|
- e.ToolStrip.DisplayRectangle.Width
|
|
- margins.Left
|
|
- margins.Right
|
|
- 1
|
|
) - e.AffectedBounds.Width;
|
|
Rectangle rect = e.AffectedBounds;
|
|
rect.Y += 2;
|
|
rect.Height -= 4;
|
|
int sepWidth = renderer.GetPartSize(e.Graphics, ThemeSizeType.True).Width;
|
|
if (e.ToolStrip.RightToLeft == RightToLeft.Yes)
|
|
{
|
|
rect = new Rectangle(rect.X - extraWidth, rect.Y, sepWidth, rect.Height);
|
|
rect.X += sepWidth;
|
|
}
|
|
else
|
|
{
|
|
rect = new Rectangle(
|
|
rect.Width + extraWidth - sepWidth,
|
|
rect.Y,
|
|
sepWidth,
|
|
rect.Height
|
|
);
|
|
}
|
|
renderer.DrawBackground(e.Graphics, rect);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
base.OnRenderImageMargin(e);
|
|
}
|
|
}
|
|
|
|
protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e)
|
|
{
|
|
if (e.ToolStrip.IsDropDown && EnsureRenderer())
|
|
{
|
|
renderer.SetParameters(MenuClass, (int)MenuParts.PopupSeparator, 0);
|
|
Rectangle rect = new Rectangle(
|
|
e.ToolStrip.DisplayRectangle.Left,
|
|
0,
|
|
e.ToolStrip.DisplayRectangle.Width,
|
|
e.Item.Height
|
|
);
|
|
renderer.DrawBackground(e.Graphics, rect, rect);
|
|
}
|
|
else
|
|
{
|
|
e.Graphics.DrawLine(
|
|
Pens.LightGray,
|
|
e.Item.ContentRectangle.X,
|
|
e.Item.ContentRectangle.Y,
|
|
e.Item.ContentRectangle.X,
|
|
e.Item.ContentRectangle.Y + e.Item.Height - 6
|
|
);
|
|
}
|
|
}
|
|
|
|
protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e)
|
|
{
|
|
if (EnsureRenderer())
|
|
{
|
|
Rectangle bgRect = GetBackgroundRectangle(e.Item);
|
|
bgRect.Width = bgRect.Height;
|
|
|
|
// Now, mirror its position if the menu item is RTL.
|
|
if (e.Item.RightToLeft == RightToLeft.Yes)
|
|
bgRect = new Rectangle(
|
|
e.ToolStrip.ClientSize.Width - bgRect.X - bgRect.Width,
|
|
bgRect.Y,
|
|
bgRect.Width,
|
|
bgRect.Height
|
|
);
|
|
|
|
renderer.SetParameters(
|
|
MenuClass,
|
|
(int)MenuParts.PopupCheckBackground,
|
|
e.Item.Enabled
|
|
? (int)MenuPopupCheckBackgroundStates.Normal
|
|
: (int)MenuPopupCheckBackgroundStates.Disabled
|
|
);
|
|
renderer.DrawBackground(e.Graphics, bgRect);
|
|
|
|
Rectangle checkRect = e.ImageRectangle;
|
|
checkRect.X = bgRect.X + bgRect.Width / 2 - checkRect.Width / 2;
|
|
checkRect.Y = bgRect.Y + bgRect.Height / 2 - checkRect.Height / 2;
|
|
|
|
// I don't think ToolStrip even supports radio box items, so no need to render them.
|
|
renderer.SetParameters(
|
|
MenuClass,
|
|
(int)MenuParts.PopupCheck,
|
|
e.Item.Enabled
|
|
? (int)MenuPopupCheckStates.CheckmarkNormal
|
|
: (int)MenuPopupCheckStates.CheckmarkDisabled
|
|
);
|
|
|
|
renderer.DrawBackground(e.Graphics, checkRect);
|
|
}
|
|
else
|
|
{
|
|
base.OnRenderItemCheck(e);
|
|
}
|
|
}
|
|
|
|
protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e)
|
|
{
|
|
// The default renderer will draw an arrow for us (the UXTheme API seems not to have one for all directions),
|
|
// but it will get the colour wrong in many cases. The text colour is probably the best colour to use.
|
|
if (EnsureRenderer())
|
|
e.ArrowColor = GetItemTextColor(e.Item);
|
|
base.OnRenderArrow(e);
|
|
}
|
|
|
|
protected override void OnRenderOverflowButtonBackground(ToolStripItemRenderEventArgs e)
|
|
{
|
|
if (EnsureRenderer())
|
|
{
|
|
// BrowserTabBar::Rebar draws the chevron using the default background. Odd.
|
|
string rebarClass = RebarClass;
|
|
if (Theme == ToolbarTheme.BrowserTabBar)
|
|
rebarClass = "Rebar";
|
|
|
|
int state = VisualStyleElement.Rebar.Chevron.Normal.State;
|
|
if (e.Item.Pressed)
|
|
state = VisualStyleElement.Rebar.Chevron.Pressed.State;
|
|
else if (e.Item.Selected)
|
|
state = VisualStyleElement.Rebar.Chevron.Hot.State;
|
|
|
|
renderer.SetParameters(
|
|
rebarClass,
|
|
VisualStyleElement.Rebar.Chevron.Normal.Part,
|
|
state
|
|
);
|
|
renderer.DrawBackground(e.Graphics, new Rectangle(Point.Empty, e.Item.Size));
|
|
}
|
|
else
|
|
{
|
|
base.OnRenderOverflowButtonBackground(e);
|
|
}
|
|
}
|
|
|
|
public bool IsSupported
|
|
{
|
|
get
|
|
{
|
|
if (!VisualStyleRenderer.IsSupported)
|
|
return false;
|
|
|
|
// Needs a more robust check. It seems mono supports very different style sets.
|
|
return VisualStyleRenderer.IsElementDefined(
|
|
VisualStyleElement.CreateElement(
|
|
"Menu",
|
|
(int)MenuParts.BarBackground,
|
|
(int)MenuBarStates.Active
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|