Skip to content

Instantly share code, notes, and snippets.

@rubyu
Last active April 25, 2018 12:54
Show Gist options
  • Save rubyu/62b54f0255d938c929e71dff235a36ea to your computer and use it in GitHub Desktop.
Save rubyu/62b54f0255d938c929e71dff235a36ea to your computer and use it in GitHub Desktop.
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
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 Desktop : IDisposable
{
private enum RDW : int
{
INVALIDATE = 0x1,
INTERNALPAINT = 0x2,
ERASE = 0x4,
VALIDATE = 0x8,
NOINTERNALPAINT = 0x10,
NOERASE = 0x20,
NOCHILDREN = 0x40,
ALLCHILDREN = 0x80,
UPDATENOW = 0x100,
ERASENOW = 0x200,
FRAME = 0x400,
NOFRAME = 0x800
}
[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("user32.dll")]
private static extern IntPtr ReleaseDC(IntPtr hwnd, IntPtr hdc);
[DllImport("user32.dll")]
private static extern bool UpdateWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, RDW flags);
private const int WS_EX_TOOLWINDOW = 0x00000080;
private const int WS_EX_TRANSPARENT = 0x00000020;
private readonly GestureStrokeOverlayManager manager;
private readonly IntPtr dc;
private readonly System.Timers.Timer timer;
private DrawMessage lastDrawMessage = null;
public Desktop(GestureStrokeOverlayManager manager)
{
this.manager = manager;
this.dc = GetDC(IntPtr.Zero);
this.timer = new System.Timers.Timer(15);
this.timer.AutoReset = false;
this.timer.Elapsed += (sender, e) => {
Draw();
};
}
public void Clear() {
lastDrawMessage = null;
RedrawWindow(IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, RDW.ALLCHILDREN | RDW.UPDATENOW | RDW.ERASE | RDW.INVALIDATE | RDW.INTERNALPAINT);
UpdateWindow(IntPtr.Zero);
}
public void Draw(DrawMessage dm)
{
lastDrawMessage = dm;
timer.Start();
Draw();
}
private readonly object _lockObject = new object();
private void Draw()
{
lock (_lockObject)
{
if (lastDrawMessage == null)
{
return;
}
var strokes = lastDrawMessage.Strokes;
var bufferedPoints = lastDrawMessage.BufferedPoints;
using (var g = Graphics.FromHdc(dc))
{
if (manager.MessageQueue.Any())
{
Verbose.Print("DrawStrokeOverlay was canceled.");
return;
}
var points = (strokes.SelectMany(s => s.Points)).Concat(bufferedPoints).Append(Cursor.Position);
g.DrawLines(manager.NormalLinePen, points.ToArray());
/*
foreach (var (v, i) in strokes.Select((v, i) => (v, i)))
{
if (manager.MessageQueue.Count > 0)
{
Verbose.Print("DrawStrokeOverlay was canceled.");
return;
}
var pen = v != strokes.Last() ? manager.NormalLinePen : manager.NewLinePen;
Verbose.Print($"Drawing Stroke({i + 1}/{strokes.Count()}) of {v.Points.Count} points.");
g.DrawLines(pen, v.Points.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.");
g.DrawLines(manager.UndeterminedLinePen, points.ToArray());
}
*/
}
timer.Start();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
}
ReleaseDC(IntPtr.Zero, dc);
}
~Desktop()
{
Dispose(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 readonly Pen NormalLinePen;
public readonly Pen NewLinePen;
public readonly Pen UndeterminedLinePen;
public readonly float LineWidth;
public readonly bool Debug;
public void Clear()
{
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>();
public GestureStrokeOverlayManager(
Color? normalLineColor = null,
Color? newLineColor = null,
Color? undeterminedLineColor = null,
float? lineWidth = 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);
Debug = debug;
RunMessageProcessTask();
}
private void RunMessageProcessTask() =>
Task.Factory.StartNew(() =>
{
try
{
Desktop desktop = null;
foreach (var message in MessageQueue.GetConsumingEnumerable())
{
if (_disposed) break;
try {
if (message is ResetMessage)
{
Verbose.Print("[GestureStrokeOverlayManager.Clear]");
desktop?.Clear();
desktop?.Dispose();
desktop = null;
}
else if (message is DrawMessage dm)
{
Verbose.Print("[GestureStrokeOverlayManager.DrawMessage]");
if (desktop == null)
{
desktop = new Desktop(this);
}
desktop.Draw(dm);
}
}
catch(Exception ex)
{
Verbose.Print($"Error on MessageProcessTask: {ex.ToString()}");
}
}
desktop?.Clear();
desktop?.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);
}));
};
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?.Clear();
};
Config.Callback.StateChanged += (sender, e) => {
gestureStrokeOverlayManager?.Clear();
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment