730 lines
22 KiB
C#
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
|
|
}
|
|
}
|