JD2022-TU1/main/tools/framework/JD.Sequencers/Windowlesses/WindowlessRootControl.cs

730 lines
22 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using JD.Collections.Extensions;
using JD.Kernel.StaticHelpers;
using JD.Sequencers.Controls;
namespace JD.Sequencers.Windowlesses
{
/// <summary>
/// This control is the bridge between the normal Win32 world and the "windowless" world.
/// </summary>
public class WindowlessRootControl : Control, IPhysicalControl
{
#region Variables
private readonly WindowlessContainer m_rootControl;
private WindowlessContainer m_currentContainer; // The container we are currently interacting with
private WindowlessControl m_focusedControl; // Control that has the focus.
private WindowlessControl m_hoveredControl; // Control over which we are hovering.
private WindowlessControl m_dragControl; // Control over which we are dragging.
private readonly WindowlessTooltip m_tooltip;
private readonly MouseEventModerator m_mouseModerator;
private InvalidateProfiler m_invalidateProfiler;
#endregion
#region Constructor
/// <summary>
/// Constructor.
/// </summary>
public WindowlessRootControl()
{
m_rootControl = new WindowlessContainer(this);
m_mouseModerator = new MouseEventModerator(this);
SetStyle(ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.DoubleBuffer,
true);
m_tooltip = new WindowlessTooltip(this);
m_mouseModerator.MouseUp += OnMouseUp;
m_mouseModerator.MouseDown += OnMouseDown;
m_mouseModerator.MouseMove += OnMouseMove;
m_mouseModerator.MouseWheel += OnMouseWheel;
// Uncomment to Profile which calls are responsible of the painting performance issues.
// ProfilePainting();
InitializeComponent();
}
private void ProfilePainting()
{
m_invalidateProfiler = new InvalidateProfiler(this);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
m_mouseModerator.Dispose();
m_rootControl.Dispose();
m_tooltip.Dispose();
}
base.Dispose(disposing);
}
#endregion
#region Properties
/// <summary>
/// Root container.
/// </summary>
public WindowlessContainer RootContainer
{
get { return m_rootControl; }
}
private WindowlessContainer CurrentContainer
{
get { return m_currentContainer; }
set { m_currentContainer = value; }
}
private WindowlessControl FocusedControl
{
get { return m_focusedControl; }
set { m_focusedControl = value; }
}
private WindowlessControl HoveredControl
{
get { return m_hoveredControl; }
set { m_hoveredControl = value; }
}
private WindowlessControl DragControl
{
get { return m_dragControl; }
set { m_dragControl = value; }
}
#region Captions
[Category("Appearance")]
public System.Drawing.Color TooltipTextColor
{
get { return m_tooltip.TextColor; }
set
{
if (m_tooltip.TextColor != value)
{
m_tooltip.TextColor = value;
Invalidate();
}
}
}
[Category("Appearance")]
public System.Drawing.Color TooltipBackgroundColor
{
get { return m_tooltip.BackgroundColor; }
set
{
if (m_tooltip.BackgroundColor != value)
{
m_tooltip.BackgroundColor = value;
Invalidate();
}
}
}
[Category("Appearance")]
public System.Drawing.Font TooltipFont
{
get { return m_tooltip.Font; }
set
{
if (m_tooltip.Font != value)
{
m_tooltip.Font = value;
Invalidate();
}
}
}
[Category("Appearance")]
public bool TooltipOnBottom
{
get { return m_tooltip.ShowOnBottom; }
set
{
m_tooltip.ShowOnBottom = value;
Invalidate();
}
}
[Category("Appearance")]
public StringFormat TooltipTextFormat
{
get { return m_tooltip.TextFormat; }
set
{
if (m_tooltip.TextFormat != value)
{
m_tooltip.TextFormat = value;
Invalidate();
}
}
}
[Category("Behavior")]
public int TooltipIntervalDelay
{
get { return m_tooltip.IntervalDelay; }
set
{
if (m_tooltip.IntervalDelay != value)
{
m_tooltip.IntervalDelay = value;
Invalidate();
}
}
}
#endregion
#endregion
#region Methods
private void InitializeComponent()
{
this.SuspendLayout();
//
// WindowlessRootControl
//
this.AllowDrop = true;
this.ResumeLayout(false);
}
private TControl FindControl<TControl>(Point location)
where TControl : WindowlessControl
{
return RootContainer.FindControl<TControl>(location);
}
/// <summary>
/// Overriden.
/// </summary>
/// <param name="e"></param>
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
RootContainer.Bounds = ClientRectangle;
}
/// <summary>
/// Overriden.
/// </summary>
/// <param name="e"></param>
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
RootContainer.OnPaint(e);
AfterPaint?.Invoke(this, e);
}
private event EventHandler AfterPaint;
#region Key events
///// <summary>
///// Overriden.
///// </summary>
///// <param name="e"></param>
//protected override void OnKeyUp(KeyEventArgs e)
//{
// base.OnKeyUp(e);
// RootContainer.OnToolKeyUp(e);
// if (FocusedControl != null)
// {
// FocusedControl.OnKeyUp(e);
// }
//}
///// <summary>
///// Overriden.
///// </summary>
///// <param name="e"></param>
//protected override void OnKeyDown(KeyEventArgs e)
//{
// base.OnKeyDown(e);
// RootContainer.OnToolKeyDown(e);
// if (FocusedControl != null)
// {
// FocusedControl.OnKeyDown(e);
// }
//}
#endregion
#region Mouse events
public void SimulateOnMouseDown(object sender, MouseEventArgs e)
{
OnMouseDown(sender, e);
}
protected virtual void OnMouseDown(object sender, MouseEventArgs e)
{
CurrentContainer = FindControl<WindowlessContainer>(e.Location);
if (CurrentContainer == null)
{
return;
}
CurrentContainer.LastMouseDownEventArgs = e;
// Do MouseDown Operation on container
CurrentContainer.ToolManager.OnMouseDown(e);
// Change FocusControl
FocusedControl = FindControl<WindowlessControl>(e.Location);
// Raise MouseDown event on container
CurrentContainer.OnMouseDown(e);
// Raise MouseDown event on focus control
if (FocusedControl != CurrentContainer)
{
FocusedControl.OnMouseDown(e);
}
}
protected virtual void OnMouseUp(object sender, MouseEventArgs e)
{
if (CurrentContainer == null)
{
return;
}
// Do MouseUp Operation on container
CurrentContainer.ToolManager.OnMouseUp(e);
// Raise MouseUp event on container
CurrentContainer.OnMouseUp(e);
// Raise MouseUp event on focus control
if (FocusedControl != CurrentContainer)
{
FocusedControl.OnMouseUp(e);
}
}
protected virtual void OnMouseWheel(object sender, MouseEventArgs e)
{
WindowlessContainer container = CurrentContainer ?? FindControl<WindowlessContainer>(e.Location);
if (container == null)
{
return;
}
// Do MouseWheel Operation on container
bool focused = Focused;
Focus();
if (focused)
{
container.ToolManager.OnMouseWheel(e);
}
// Raise MouseWheel event on container
container.OnMouseWheel(e);
// Raise MouseMove event on hover control
if (FocusedControl != null && e.Button != MouseButtons.None)
{
HoveredControl = FocusedControl;
}
if (HoveredControl != null && HoveredControl != container)
{
HoveredControl.OnMouseWheel(e);
}
}
protected virtual void OnMouseMove(object sender, MouseEventArgs e)
{
WindowlessContainer container = CurrentContainer;
if (e.Button == MouseButtons.None || container == null)
{
container = FindControl<WindowlessContainer>(e.Location);
}
if (container == null)
{
return;
}
// Do MouseMove Operation on container
container.ToolManager.OnMouseMove(e);
// Raise MouseMove event on container
container.OnMouseMove(e);
// Raise MouseMove event on hover control
if (FocusedControl != null && e.Button != MouseButtons.None)
{
HoveredControl = FocusedControl;
}
else
{
WindowlessControl control = FindControl<WindowlessControl>(e.Location);
if (HoveredControl != control)
{
OnToolMouseLeave(e);
HoveredControl = control;
OnToolMouseEnter(e);
}
}
m_tooltip.SetToolTip(HoveredControl);
HoveredControl.OnMouseMove(e);
}
/// <summary>
/// Overriden.
/// </summary>
/// <param name="e"></param>
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
OnToolMouseLeave(e);
m_tooltip.SetToolTip(null);
}
private void OnToolMouseEnter(EventArgs e)
{
if (HoveredControl != null)
{
/*TODO//Start Timer for Hover
_HoverTimer.Stop();
_HoverTimer.Interval = _HoverControl.HoverInterval;
_HoverTimer.Start();*/
//Forward OnMouseEnter
HoveredControl.OnMouseEnter(e);
}
}
private void OnToolMouseLeave(EventArgs e)
{
if (HoveredControl != null)
{
/*TODO_HoverTimer.Stop();*/
HoveredControl.OnMouseLeave(e);
}
HoveredControl = null;
}
#endregion
#region Drag events
/// <summary>
/// Overriden.
/// </summary>
/// <param name="drgevent"></param>
protected override void OnDragEnter(DragEventArgs drgevent)
{
base.OnDragEnter(drgevent);
OnDragEnterImpl(drgevent);
}
private void OnDragEnterImpl(DragEventArgs drgevent)
{
DragControl = FindControl<WindowlessControl>(PointToClient(new Point(drgevent.X, drgevent.Y)));
DragControl.OnDragEnter(drgevent);
}
/// <summary>
/// Overriden.
/// </summary>
/// <param name="e"></param>
protected override void OnDragLeave(EventArgs e)
{
base.OnDragLeave(e);
OnDragLeaveImpl(e);
}
private void OnDragLeaveImpl(EventArgs e)
{
if (DragControl != null)
{
DragControl.OnDragLeave(e);
}
DragControl = null;
}
/// <summary>
/// Overriden.
/// </summary>
/// <param name="drgevent"></param>
protected override void OnDragOver(DragEventArgs drgevent)
{
base.OnDragOver(drgevent);
WindowlessControl control = FindControl<WindowlessControl>(PointToClient(new Point(drgevent.X, drgevent.Y)));
if (control != DragControl)
{
OnDragLeaveImpl(drgevent);
DragControl = control;
OnDragEnterImpl(drgevent);
}
DragControl.OnDragOver(drgevent);
}
/// <summary>
/// Overriden.
/// </summary>
/// <param name="drgevent"></param>
protected override void OnDragDrop(DragEventArgs drgevent)
{
base.OnDragDrop(drgevent);
DragControl.OnDragDrop(drgevent);
}
#endregion
#endregion
#region Implementation of IPhysicalControl
/// <summary>
/// Gets the currently pressed modifier keys (Ctrl, Alt, Shift)
/// </summary>
Keys IPhysicalControl.ModifierKeys
{
get { return ModifierKeys; }
}
#endregion
#region Nested Types
private class WindowlessTooltip : IDisposable
{
#region Constants
/// <summary>
/// When the tooltip is docked, it will float 25 pixel away from the side.
/// </summary>
private const int DockMargin = 25;
#endregion
#region Variables
private readonly WindowlessRootControl m_rootControl;
private readonly ToolTip m_toolTip = new ToolTip();
#endregion
#region Constructors
public WindowlessTooltip(WindowlessRootControl rootControl)
{
Throw.IfNull(rootControl, "rootControl");
m_rootControl = rootControl;
m_toolTip = new ToolTip { ShowAlways = true, InitialDelay = 0, AutomaticDelay = 0, BackColor = System.Drawing.Color.FromArgb(100, System.Drawing.Color.WhiteSmoke) };
TextColor = System.Drawing.Color.Black;
BackgroundColor = System.Drawing.Color.White;
Font = SystemFonts.CaptionFont;
TextFormat = new StringFormat { LineAlignment = StringAlignment.Center, Alignment = StringAlignment.Center };
}
public void Dispose()
{
m_toolTip.SetToolTip(m_rootControl, "");
m_toolTip.Dispose();
}
#endregion
#region Properties
public System.Drawing.Color TextColor
{
get { return m_toolTip.ForeColor; }
set { m_toolTip.ForeColor = value; }
}
public System.Drawing.Color BackgroundColor
{
get { return m_toolTip.BackColor; }
set { m_toolTip.BackColor = value; }
}
public System.Drawing.Font Font { get; set; }
public StringFormat TextFormat { get; set; }
public int IntervalDelay
{
get { return m_toolTip.InitialDelay; }
set { m_toolTip.InitialDelay = value; }
}
/// <summary>
/// When true, the tooltip always shows on the bottom left/right corner of the screen.
/// </summary>
public bool ShowOnBottom { get; set; }
#endregion
#region Methods
public void SetToolTip(WindowlessControl hoveredControl)
{
string text = hoveredControl == null ? null : hoveredControl.ToolTipText;
if (text != m_toolTip.GetToolTip(m_rootControl))
{
if (!string.IsNullOrEmpty(text))
{
var position = ComputePosition(text, hoveredControl);
m_toolTip.Show(text, m_rootControl, (int)position.X, (int)position.Y);
}
else
{
m_toolTip.SetToolTip(m_rootControl, text);
}
}
}
private PointF ComputePosition(string text, WindowlessControl hoveredControl)
{
using (var graphics = m_rootControl.CreateGraphics())
{
PointF hoveredControlCenter = new PointF(hoveredControl.Bounds.Left + hoveredControl.Bounds.Width / 2, hoveredControl.Bounds.Bottom);
SizeF tooltipSize = ComputeFinalSize(graphics, text);
hoveredControlCenter.X -= tooltipSize.Width / 2;
PointF location = hoveredControlCenter;
if (ShowOnBottom)
{
var mouseLocation = m_rootControl.PointToClient(MousePosition);
location = new PointF(DockMargin, m_rootControl.ClientSize.Height - tooltipSize.Height - DockMargin);
if (mouseLocation.Y > location.Y - DockMargin && mouseLocation.X < DockMargin + tooltipSize.Width)
{
location.X = m_rootControl.ClientSize.Height - DockMargin - tooltipSize.Width;
}
}
return location;
}
}
private SizeF ComputeFinalSize(Graphics graphics, string text)
{
SizeF finalSize = SizeF.Empty;
finalSize = graphics.MeasureString(text, Font);
return finalSize;
}
#endregion
}
/// <summary>
/// Helps to diagnose which controls asked for an invalidation
/// - To have the better fluidity, full invalidation should be avoided.
/// </summary>
private class InvalidateProfiler
{
private readonly WindowlessRootControl _profiledControlControl;
private readonly List<Frame> _history = new List<Frame>();
private Frame _currentFrame = new Frame();
public InvalidateProfiler(WindowlessRootControl profiledControlControl)
{
_profiledControlControl = profiledControlControl;
_profiledControlControl.Invalidated += ProfiledControl_Invalidated;
_profiledControlControl.Paint += ProfiledControl_Paint;
_profiledControlControl.AfterPaint += ProfiledControl_OnAfterPaint;
}
void ProfiledControl_Invalidated(object sender, InvalidateEventArgs e)
{
_currentFrame.Log(e.InvalidRect);
}
void ProfiledControl_Paint(object sender, PaintEventArgs e)
{
_currentFrame.PaintStart = DateTime.Now;
}
private void ProfiledControl_OnAfterPaint(object sender, EventArgs e)
{
_currentFrame.PaintEnd = DateTime.Now;
_history.Add(_currentFrame);
_currentFrame = new Frame();
}
private class Frame
{
private readonly List<InvalidationRequest> _requests = new List<InvalidationRequest>();
public DateTime PaintStart { get; set; }
public DateTime PaintEnd { get; set; }
public TimeSpan PaintDuration { get { return PaintEnd - PaintStart; } }
public InvalidationRequest[] LargerRequests { get { return _requests.OrderByDescending(request => request.InvalidatedRegion.Width * request.InvalidatedRegion.Height).ToArray(); } }
public void Log(Rectangle invalidatedRegion)
{
// Do not log while painting.
if (PaintStart == DateTime.MinValue &&
// Do not log if the request is a redundancy.
!_requests.Any(request => request.InvalidatedRegion.Contains(invalidatedRegion)))
{
// The first 7 frames are always the same.
_requests.Add(new InvalidationRequest { CallStack = new StackTrace(skipFrames: 7, fNeedFileInfo:false).ToString().Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries), InvalidatedRegion = invalidatedRegion });
}
}
public override string ToString()
{
if (!_requests.Any())
{
return "No Invalidation for frame";
}
return $"{PaintDuration.TotalMilliseconds} ms - Main invalidation:{LargerRequests[0]}";
}
}
private class InvalidationRequest
{
public Rectangle InvalidatedRegion { get; set; }
public string[] CallStack { get; set; }
public override string ToString()
{
return $"{InvalidatedRegion.Size} - {CallStack.Join("; ")}";
}
}
}
#endregion
}
}