Created
April 25, 2018 01:33
-
-
Save rubyu/4e2f6999f9e21be0da0233befa100a03 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Drawing; | |
using System.Threading; | |
using System.Windows.Forms; | |
using System.Collections.Concurrent; | |
using Crevice.Threading; | |
using Crevice.Logging; | |
public class GestureStrokeOverlayManager : IDisposable | |
{ | |
protected class Message {} | |
protected class ResetMessage : Message {} | |
protected class DrawMessage : Message | |
{ | |
public readonly IEnumerable<Crevice.Core.Stroke.Stroke> Strokes; | |
public readonly IEnumerable<Point> BufferedPoints; | |
public DrawMessage(IEnumerable<Crevice.Core.Stroke.Stroke> strokes, IEnumerable<Point> bufferedPoints) | |
{ | |
this.Strokes = strokes; | |
this.BufferedPoints = bufferedPoints; | |
} | |
} | |
class OverlayForm : Form | |
{ | |
class OverlayBuffer : IDisposable | |
{ | |
public readonly Rectangle Rect; | |
public readonly BufferedGraphics Buffer; | |
public OverlayBuffer(Graphics g, Rectangle rect) | |
{ | |
this.Rect = rect; | |
this.Buffer = BufferedGraphicsManager.Current.Allocate(g, Rect); | |
} | |
public void Dispose() => Buffer.Dispose(); | |
} | |
private const int WS_EX_TOOLWINDOW = 0x00000080; | |
private const int WS_EX_TRANSPARENT = 0x00000020; | |
protected override CreateParams CreateParams | |
{ | |
get | |
{ | |
CreateParams cp = base.CreateParams; | |
cp.ExStyle = cp.ExStyle | WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT; | |
return cp; | |
} | |
} | |
public void InvokeProperly(MethodInvoker invoker) | |
{ | |
if (InvokeRequired) | |
{ | |
Invoke(invoker); | |
} | |
else | |
{ | |
invoker.Invoke(); | |
} | |
} | |
private readonly GestureStrokeOverlayManager Manager; | |
private readonly Graphics graphics; | |
private OverlayBuffer bufferA = null; | |
private OverlayBuffer bufferB = null; | |
private readonly object _lockObject = new object(); | |
public OverlayForm(GestureStrokeOverlayManager manager) | |
{ | |
this.Manager = manager; | |
this.graphics = CreateGraphics(); | |
this.graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; | |
this.graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; | |
InitializeComponent(); | |
} | |
public void Reset() => | |
InvokeProperly(() => | |
{ | |
EraseStrokeOverlay(bufferA); | |
EraseStrokeOverlay(bufferB); | |
this.TopMost = true; | |
this.TopLevel = true; | |
}); | |
public void Draw(IEnumerable<Crevice.Core.Stroke.Stroke> strokes, IEnumerable<Point> bufferedPoints, Rectangle rect, bool bufferUpdate = false) => | |
InvokeProperly(() => DrawStrokeOverlay(strokes, bufferedPoints, bufferUpdate)); | |
private void EraseStrokeOverlay(OverlayBuffer buffer) | |
{ | |
lock (_lockObject) | |
{ | |
buffer?.Buffer.Dispose(); | |
buffer = null; | |
buffer.Buffer.Graphics. | |
} | |
Buffer = BufferedGraphicsManager.Current.Allocate(CreateGraphics(), DisplayRectangle); | |
Buffer.Render(); | |
maxRenderedStrokeId = 0; | |
IsEmpty = true; | |
} | |
private void DrawStrokeOverlay(IEnumerable<Crevice.Core.Stroke.Stroke> strokes, IEnumerable<Point> bufferedPoints, bool bufferUpdate) | |
{ | |
if (bufferUpdate) | |
{ | |
using (Verbose.PrintElapsed("Allocating new buffer")) | |
{ | |
var g = CreateGraphics(); | |
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; | |
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; | |
Buffer = BufferedGraphicsManager.Current.Allocate(g, DisplayRectangle); | |
} | |
} | |
foreach (var (v, i) in strokes.Select((v, i) => (v, i))) | |
{ | |
if (Manager.MessageQueue.Count > 0) | |
{ | |
Verbose.Print("DrawStrokeOverlay was canceled."); | |
return; | |
} | |
if (i < maxRenderedStrokeId) | |
{ | |
continue; | |
} | |
var pen = v != strokes.Last() ? Manager.NormalLinePen : Manager.NewLinePen; | |
Verbose.Print($"Drawing Stroke({i + 1}/{strokes.Count()}) of {v.Points.Count} points."); | |
Buffer.Graphics.DrawLines(pen, v.Points.Select(p => new Point(p.X - Location.X, p.Y - Location.Y)).ToArray()); | |
} | |
if (bufferedPoints.Any()) | |
{ | |
var points = (strokes.Any() && strokes.Last().Points.Any() ? new List<Point> { strokes.Last().Points.Last() } : new List<Point>()).Concat(bufferedPoints).Append(Cursor.Position); | |
Verbose.Print($"Drawing undetermined stroke of {points.Count()} points."); | |
Buffer.Graphics.DrawLines(Manager.UndeterminedLinePen, points.Select(p => new Point(p.X - Location.X, p.Y - Location.Y)).ToArray()); | |
} | |
using (Verbose.PrintElapsed("Rendering")) | |
{ | |
Buffer.Render(); | |
maxRenderedStrokeId = strokes.Count() - 1; | |
} | |
IsEmpty = false; | |
} | |
protected override void OnPaint(PaintEventArgs e) | |
{ | |
base.OnPaint(e); | |
bufferA?.Buffer.Render(); | |
bufferB?.Buffer.Render(); | |
} | |
private System.ComponentModel.IContainer components = null; | |
protected override void Dispose(bool disposing) | |
{ | |
if (disposing) | |
{ | |
components?.Dispose(); | |
bufferA?.Dispose(); | |
bufferB?.Dispose(); | |
graphics?.Dispose(); | |
} | |
base.Dispose(disposing); | |
} | |
private void InitializeComponent() | |
{ | |
this.SuspendLayout(); | |
this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 18F); | |
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | |
this.ClientSize = new System.Drawing.Size(1, 1); | |
this.Name = "GestureStrokeOverlayForm"; | |
this.Text = this.Name; | |
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; | |
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; | |
this.ShowIcon = false; | |
this.ShowInTaskbar = false; | |
this.TopMost = true; | |
this.TopLevel = true; | |
this.BackColor = Color.Black; | |
if (!Manager.Debug) | |
{ | |
this.TransparencyKey = Color.Black; | |
} | |
this.DoubleBuffered = true; | |
this.ResumeLayout(false); | |
} | |
} | |
public static Color DefaultNormalLineColor => Color.FromArgb(51, 153, 255); | |
public static Color DefaultNewLineColor => Color.FromArgb(255, 51, 153); | |
public static Color DefaultUndeterminedLineColor => Color.FromArgb(255, 141, 75); | |
public static float DefaultLineWidth => 4.0f; | |
public static int DefaultMinimalOverlaySize => 1200; | |
public readonly Pen NormalLinePen; | |
public readonly Pen NewLinePen; | |
public readonly Pen UndeterminedLinePen; | |
public readonly float LineWidth; | |
public readonly int MinimalOverlaySize; | |
public readonly bool Debug; | |
public void Reset() | |
{ | |
try { | |
MessageQueue.Add(new ResetMessage()); | |
} | |
catch(InvalidOperationException) {} | |
} | |
public void Draw(IEnumerable<Crevice.Core.Stroke.Stroke> strokes, IEnumerable<Point> bufferedPoints) | |
{ | |
try { | |
MessageQueue.Add(new DrawMessage(strokes, bufferedPoints)); | |
} | |
catch(InvalidOperationException) {} | |
} | |
protected readonly BlockingCollection<Message> MessageQueue = new BlockingCollection<Message>(); | |
private readonly OverlayForm smallOverlay; | |
private readonly OverlayForm largeOverlay; | |
public GestureStrokeOverlayManager( | |
Color? normalLineColor = null, | |
Color? newLineColor = null, | |
Color? undeterminedLineColor = null, | |
float? lineWidth = null, | |
int? minimalOverlaySize = null, | |
bool debug = false) | |
{ | |
LineWidth = lineWidth ?? DefaultLineWidth; | |
NormalLinePen = new Pen(normalLineColor ?? DefaultNormalLineColor, LineWidth); | |
NewLinePen = new Pen(newLineColor ?? DefaultNewLineColor, LineWidth); | |
UndeterminedLinePen = new Pen(undeterminedLineColor ?? DefaultUndeterminedLineColor, LineWidth); | |
MinimalOverlaySize = minimalOverlaySize ?? (debug ? 200 : DefaultMinimalOverlaySize); | |
Debug = debug; | |
smallOverlay = new OverlayForm(this); | |
largeOverlay = new OverlayForm(this); | |
RunMessageProcessTask(); | |
} | |
public void Run() | |
{ | |
smallOverlay.Show(); | |
largeOverlay.Show(); | |
} | |
protected Rectangle StrokesToRect(IEnumerable<Crevice.Core.Stroke.Stroke> strokes) | |
{ | |
var points = strokes.SelectMany(x => x.Points); | |
var minX = points.Select(x => x.X).Min(); | |
var minY = points.Select(x => x.Y).Min(); | |
return new Rectangle(minX, minY, points.Select(x => x.X).Max() - minX, points.Select(x => x.Y).Max() - minY); | |
} | |
private bool RectIsContained(Rectangle rect, OverlayForm form) => | |
form.Location.X < rect.X - LineWidth && | |
form.Location.Y < rect.Y - LineWidth && | |
rect.X + rect.Width + LineWidth * 2 < form.Location.X + form.Size.Width && | |
rect.Y + rect.Height + LineWidth * 2 < form.Location.Y + form.Size.Height; | |
private bool RectIsIntersected(Rectangle rect, Screen s) => | |
(s.Bounds.X <= rect.X && rect.X <= s.Bounds.X + s.Bounds.Width || | |
s.Bounds.X <= rect.X + rect.Width && rect.X + rect.Width <= s.Bounds.X + s.Bounds.Width) && | |
(s.Bounds.Y <= rect.Y && rect.Y <= s.Bounds.Y + s.Bounds.Height || | |
s.Bounds.Y <= rect.Y + rect.Height && rect.Y + rect.Height <= s.Bounds.Y + s.Bounds.Height) || | |
(rect.X < s.Bounds.X || s.Bounds.X + s.Bounds.Width < rect.X && | |
rect.Y < s.Bounds.Y || s.Bounds.Y + s.Bounds.Height < rect.Y); | |
private void RunMessageProcessTask() => | |
Task.Factory.StartNew(() => | |
{ | |
try | |
{ | |
foreach (var message in MessageQueue.GetConsumingEnumerable()) | |
{ | |
if (_disposed) break; | |
try { | |
if (message is ResetMessage) | |
{ | |
Verbose.Print("[GestureStrokeOverlayManager.ResetMessage]"); | |
smallOverlay.Reset(); | |
largeOverlay.Minimize(); | |
} | |
else if (message is DrawMessage dm) | |
{ | |
Verbose.Print("[GestureStrokeOverlayManager.DrawMessage]"); | |
var rect = StrokesToRect(dm.Strokes); | |
if (smallOverlay.IsEmpty && | |
MinimalOverlaySize > rect.Width && | |
MinimalOverlaySize > rect.Height) | |
{ | |
Verbose.Print("Mode: 0 (SmallOverlay)"); | |
smallOverlay.InvokeProperly(() => | |
{ | |
Verbose.Print($"Initialzing SmallOverlay ..."); | |
smallOverlay.Location = new Point(rect.X - MinimalOverlaySize / 2, rect.Y - MinimalOverlaySize / 2); | |
smallOverlay.Size = new Size(MinimalOverlaySize, MinimalOverlaySize); | |
Verbose.Print($"Location: {smallOverlay.Location}"); | |
Verbose.Print($"Size: {smallOverlay.Size}"); | |
}); | |
smallOverlay.Draw(dm.Strokes, dm.BufferedPoints, rect, bufferUpdate: true); | |
} | |
else if (!smallOverlay.IsEmpty && RectIsContained(rect, smallOverlay)) | |
{ | |
Verbose.Print("Mode: 0 (SmallOverlay)"); | |
smallOverlay.Draw(dm.Strokes, dm.BufferedPoints, rect, bufferUpdate: false); | |
} | |
else | |
{ | |
Verbose.Print("Mode: 1 (LargeOverlay)"); | |
var screens = Screen.AllScreens.Where(s => RectIsIntersected(rect, s)); | |
foreach (var (s, i) in screens.Select((s, i) => (s, i))) | |
{ | |
Verbose.Print($"Screen({i + 1}/{screens.Count()}) | {s}"); | |
} | |
var overlayLocation = new Point(screens.Select(s => s.Bounds.X).Min(), screens.Select(s => s.Bounds.Y).Min()); | |
var overlaySize = new Size(screens.Select(s => s.Bounds.X + s.Bounds.Width).Max() - overlayLocation.X, screens.Select(s => s.Bounds.Y + s.Bounds.Height).Max() - overlayLocation.Y); | |
if (largeOverlay.Location != overlayLocation || | |
largeOverlay.Size != overlaySize) | |
{ | |
largeOverlay.Reset(); | |
largeOverlay.InvokeProperly(() => | |
{ | |
Verbose.Print($"Initialzing LargeOverlay ..."); | |
largeOverlay.Location = overlayLocation; | |
largeOverlay.Size = overlaySize; | |
Verbose.Print($"Location: {largeOverlay.Location}"); | |
Verbose.Print($"Size: {largeOverlay.Size}"); | |
}); | |
} | |
if (largeOverlay.IsEmpty) | |
{ | |
largeOverlay.Draw(dm.Strokes, dm.BufferedPoints, rect, bufferUpdate: true); | |
} | |
else | |
{ | |
largeOverlay.Draw(dm.Strokes, dm.BufferedPoints, rect, bufferUpdate: false); | |
smallOverlay.Reset(); | |
} | |
} | |
} | |
} | |
catch(Exception ex) | |
{ | |
Verbose.Print($"Error on MessageProcessTask: {ex.ToString()}"); | |
} | |
} | |
smallOverlay.InvokeProperly(() => | |
{ | |
smallOverlay.Close(); | |
smallOverlay.Dispose(); | |
}); | |
smallOverlay.InvokeProperly(() => | |
{ | |
largeOverlay.Close(); | |
largeOverlay.Dispose(); | |
}); | |
NormalLinePen.Dispose(); | |
NewLinePen.Dispose(); | |
UndeterminedLinePen.Dispose(); | |
} | |
catch (OperationCanceledException) {} | |
}); | |
internal bool _disposed { get; private set; } = false; | |
public void Dispose() | |
{ | |
if (_disposed) return; | |
Dispose(true); | |
GC.SuppressFinalize(this); | |
} | |
protected virtual void Dispose(bool disposing) | |
{ | |
_disposed = true; | |
if (disposing) | |
{ | |
MessageQueue.CompleteAdding(); | |
} | |
} | |
~GestureStrokeOverlayManager() => Dispose(false); | |
} | |
GestureStrokeOverlayManager gestureStrokeOverlayManager = null; | |
Config.Callback.MachineStart += (sender, e) => { | |
MainForm.BeginInvoke((MethodInvoker)(() => { | |
gestureStrokeOverlayManager = new GestureStrokeOverlayManager(debug: false); | |
gestureStrokeOverlayManager.Run(); | |
})); | |
}; | |
Config.Callback.MachineStop += (sender, e) => { | |
MainForm.BeginInvoke((MethodInvoker)(() => { | |
gestureStrokeOverlayManager?.Dispose(); | |
})); | |
}; | |
Config.Callback.StrokeUpdated += (sender, e) => { | |
gestureStrokeOverlayManager?.Draw(e.Strokes, (sender as Crevice.Core.Stroke.StrokeWatcher).GetBufferedPoints()); | |
}; | |
Config.Callback.StrokeReset += (sender, e) => { | |
gestureStrokeOverlayManager?.Reset(); | |
}; | |
Config.Callback.StateChanged += (sender, e) => { | |
gestureStrokeOverlayManager?.Reset(); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment