diff --git a/mono/Assistance/Assistance.csproj b/mono/Assistance/Assistance.csproj
index f036d14..e4e56d3 100644
--- a/mono/Assistance/Assistance.csproj
+++ b/mono/Assistance/Assistance.csproj
@@ -56,6 +56,8 @@
+
+
diff --git a/mono/Assistance/KeyState.cs b/mono/Assistance/KeyState.cs
new file mode 100644
index 0000000..d231d05
--- /dev/null
+++ b/mono/Assistance/KeyState.cs
@@ -0,0 +1,87 @@
+using System;
+
+namespace Assistance {
+ public class KeyState where T: IComparable, new() {
+ public struct Holder {
+ public KeyState state;
+ public long ticks;
+ public double timeOffset;
+
+ public Holder(KeyState state, long ticks = 0, double timeOffset = 0.0) {
+ this.state = state;
+ this.ticks = ticks;
+ this.timeOffset = timeOffset;
+ }
+
+ public KeyState find(T value)
+ { return state == null ? null : state.find(value); }
+ public bool isEmpty
+ { get { return state == null || state.isEmpty; } }
+ public bool isPressed(T value)
+ { return find(value) != null; }
+ public double howLongPressed(T value) {
+ KeyState state = find(value);
+ return state == null ? 0.0
+ : Math.Max(Timer.step, (ticks - state.ticks)*Timer.step + timeOffset);
+ }
+ }
+
+ public static readonly T none = new T();
+ public static readonly KeyState empty = new KeyState();
+
+ public readonly KeyState previous;
+ public readonly long ticks;
+ public readonly T value;
+
+ public KeyState(): this(null, 0, none) { }
+
+ private KeyState(KeyState previous, long ticks, T value) {
+ this.previous = previous;
+ this.ticks = ticks;
+ this.value = value;
+ }
+
+ public KeyState find(T value) {
+ if (value.CompareTo(none) == 0)
+ return null;
+ if (value.CompareTo(this.value) == 0)
+ return this;
+ if (previous == null)
+ return null;
+ return previous.find(value);
+ }
+
+ private KeyState makeChainWithout(KeyState ks) {
+ if (this == ks || previous == null) return previous;
+ return new KeyState(previous.makeChainWithout(ks), ticks, value);
+ }
+
+ public KeyState change(bool press, T value, long ticks) {
+ if (value.CompareTo(none) == 0)
+ return this;
+ if (ticks <= this.ticks)
+ ticks = this.ticks + 1;
+
+ KeyState p = find(value);
+ if (press) {
+ if (p != null) return this;
+ return new KeyState(isEmpty ? null : this, ticks, value);
+ }
+
+ if (p == null) return this;
+ KeyState chain = makeChainWithout(p);
+ return chain == null ? new KeyState() : chain;
+ }
+
+ public bool isEmpty
+ { get { return value.CompareTo(none) == 0 && (previous == null || previous.isEmpty); } }
+ public bool isPressed(T value)
+ { return find(value) != null; }
+
+ public KeyState press(T value, long ticks)
+ { return change(true, value, ticks); }
+ public KeyState release(T value, long ticks)
+ { return change(false, value, ticks); }
+ }
+}
+
diff --git a/mono/Assistance/MainWindow.cs b/mono/Assistance/MainWindow.cs
index 899bafc..4c71d61 100644
--- a/mono/Assistance/MainWindow.cs
+++ b/mono/Assistance/MainWindow.cs
@@ -18,9 +18,14 @@ namespace Assistance {
ActivePoint activePoint;
uint timeStart = 0;
+ long ticksStart = 0;
+ long lastTicks = 0;
Point offset;
Point cursor;
+ KeyState keyState = new KeyState();
+ KeyState buttonState = new KeyState();
+
Track track = null;
public MainWindow(): base(Gtk.WindowType.Toplevel) {
@@ -91,9 +96,10 @@ namespace Assistance {
activePoint.bringToFront();
}
- private void beginTrack() {
+ private void beginTrack(Gdk.Device device) {
endDragAndTrack();
- track = new Track();
+ track = new Track(device);
+ ticksStart = Timer.ticks();
}
private void endDragAndTrack() {
@@ -109,13 +115,16 @@ namespace Assistance {
switch(e.Key) {
case Gdk.Key.Key_1:
new AssistantVanishingPoint(workarea.document, cursor);
+ endDragAndTrack();
break;
case Gdk.Key.Key_2:
new AssistantGrid(workarea.document, cursor);
+ endDragAndTrack();
break;
case Gdk.Key.Q:
case Gdk.Key.q:
new ModifierSnowflake(workarea.document, cursor);
+ endDragAndTrack();
break;
case Gdk.Key.I:
case Gdk.Key.i:
@@ -128,46 +137,104 @@ namespace Assistance {
activePoint.owner.remove();
endDragAndTrack();
break;
+ default:
+ keyState = keyState.press(e.Key, Timer.ticks());
+ retryTrackPoint();
+ break;
}
- endDragAndTrack();
QueueDraw();
return base.OnKeyPressEvent(e);
}
- TrackPoint makeTrackPoint(double x, double y, uint time, Gdk.Device device, double[] axes) {
- TrackPoint point = new TrackPoint(
- windowToWorkarea(new Point(x, y)),
- (double)(time - timeStart)*0.001 );
+ protected override bool OnKeyReleaseEvent(Gdk.EventKey e) {
+ keyState = keyState.release(e.Key, Timer.ticks());
+ retryTrackPoint();
+ return base.OnKeyReleaseEvent(e);
+ }
+
+ void addTrackPoint(Point p, double time, double pressure, Point tilt) {
+ if (track == null)
+ return;
+
+ TrackPoint point = new TrackPoint();
+ point.point = p;
+ point.time = time;
+ point.pressure = pressure;
+ point.tilt = tilt;
+
+ long ticks = Timer.ticks();
+ if (track.points.Count > 0) {
+ double t = track.points[ track.points.Count-1 ].time;
+ if (point.time - t < Geometry.precision)
+ point.time = t + (ticks - lastTicks)*Timer.step;
+ }
+ lastTicks = ticks;
+
+ point.keyState.state = keyState;
+ point.keyState.ticks = ticksStart;
+ point.keyState.timeOffset = point.time;
+
+ point.buttonState.state = buttonState;
+ point.buttonState.ticks = ticksStart;
+ point.buttonState.timeOffset = point.time;
+
+ track.points.Add(point);
+ }
+
+ void addTrackPoint(double x, double y, uint t, Gdk.Device device, double[] axes) {
+ Point point = windowToWorkarea(new Point(x, y));
+ double time = (double)(t - timeStart)*0.001;
+ double pressure = 0.5;
+ Point tilt = new Point(0.0, 0.0);
+
if (device != null && axes != null) {
double v;
if (device.GetAxis(axes, Gdk.AxisUse.Pressure, out v))
- point.pressure = v;
+ pressure = v;
if (device.GetAxis(axes, Gdk.AxisUse.Xtilt, out v))
- point.tilt.x = v;
+ tilt.x = v;
if (device.GetAxis(axes, Gdk.AxisUse.Ytilt, out v))
- point.tilt.y = v;
+ tilt.y = v;
}
- return point;
+
+ long ticks = Timer.ticks();
+ long dticks = Math.Max(1, ticks - lastTicks);
+ lastTicks = ticks;
+ if (track.points.Count > 0) {
+ double prevTime = track.points[ track.points.Count-1 ].time;
+ if (time - prevTime < Geometry.precision)
+ time = prevTime + dticks*Timer.step;
+ }
+
+ addTrackPoint(point, time, pressure, tilt);
+ }
+
+ void addTrackPoint(Gdk.EventButton e) {
+ addTrackPoint(e.X, e.Y, e.Time, e.Device, e.Axes);
}
- TrackPoint makeTrackPoint(Gdk.EventButton e) {
- return makeTrackPoint(e.X, e.Y, e.Time, e.Device, e.Axes);
+ void addTrackPoint(Gdk.EventMotion e) {
+ addTrackPoint(e.X, e.Y, e.Time, e.Device, e.Axes);
}
- TrackPoint makeTrackPoint(Gdk.EventMotion e) {
- return makeTrackPoint(e.X, e.Y, e.Time, e.Device, e.Axes);
+ void retryTrackPoint() {
+ if (track == null || track.points.Count < 1) return;
+ TrackPoint last = track.points[track.points.Count-1];
+ addTrackPoint(last.point, last.time, last.pressure, last.tilt);
}
protected override bool OnButtonPressEvent(Gdk.EventButton e) {
cursor = windowToWorkarea(new Point(e.X, e.Y));
+ buttonState = buttonState.press(e.Button, Timer.ticks());
+ retryTrackPoint();
if (e.Button == 1) {
timeStart = e.Time;
activePoint = workarea.findPoint(cursor);
if (activePoint != null) {
beginDrag();
} else {
- beginTrack();
- track.points.Add(makeTrackPoint(e));
+ beginTrack(e.Device);
+ addTrackPoint(e);
}
}
QueueDraw();
@@ -176,9 +243,11 @@ namespace Assistance {
protected override bool OnButtonReleaseEvent(Gdk.EventButton e) {
cursor = windowToWorkarea(new Point(e.X, e.Y));
+ buttonState = buttonState.release(e.Button, Timer.ticks());
+ retryTrackPoint();
if (e.Button == 1) {
if (!dragging && track != null)
- track.points.Add(makeTrackPoint(e));
+ addTrackPoint(e);
endDragAndTrack();
if (!dragging && track == null)
activePoint = workarea.findPoint(cursor);
@@ -194,7 +263,7 @@ namespace Assistance {
} else
if (track != null) {
if (e.IsHint) Gdk.Display.Default.Beep();
- track.points.Add(makeTrackPoint(e));
+ addTrackPoint(e);
} else {
activePoint = workarea.findPoint(cursor);
}
diff --git a/mono/Assistance/Timer.cs b/mono/Assistance/Timer.cs
new file mode 100644
index 0000000..97780fd
--- /dev/null
+++ b/mono/Assistance/Timer.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Assistance {
+ public class Timer {
+ public static readonly long frequency = System.Diagnostics.Stopwatch.Frequency;
+ public static readonly double step = 1.0/(double)frequency;
+
+ private static readonly System.Diagnostics.Stopwatch instance = new System.Diagnostics.Stopwatch();
+
+ static Timer() { instance.Start(); }
+
+ public static long ticks()
+ { return instance.ElapsedTicks; }
+ }
+}
+
diff --git a/mono/Assistance/Tool.cs b/mono/Assistance/Tool.cs
index fe9ffaf..f592f3d 100644
--- a/mono/Assistance/Tool.cs
+++ b/mono/Assistance/Tool.cs
@@ -1,9 +1,30 @@
using System;
namespace Assistance {
+ [Flags]
+ public enum Modifiers {
+ None = 0,
+ Interpolation = 1,
+ Guideline = 2,
+ Multiline = 4
+ };
+
public class Tool {
- public Tool() {
- }
+ public void activate() { }
+
+ public int getAvailableStackSize()
+ { return 1; }
+ public bool getIsCancellable()
+ { return false; }
+ public Modifiers getAvailableModifiers()
+ { return Modifiers.None; }
+
+ public bool paint_begin(Track track) { return false; }
+ public void paint_point(TrackPoint point, Track track) { }
+ public bool paint_apply() { return false; }
+ public void paint_cancel() { }
+
+ public void disactivate() { }
}
}
diff --git a/mono/Assistance/Track.cs b/mono/Assistance/Track.cs
index 242fe6c..0454046 100644
--- a/mono/Assistance/Track.cs
+++ b/mono/Assistance/Track.cs
@@ -5,17 +5,22 @@ using Assistance.Drawing;
namespace Assistance {
public class Track {
public static readonly Pen pen = new Pen("Dark Green", 3.0);
+ public static readonly Pen penSpecial = new Pen("Blue", 3.0);
public static readonly Pen penPreview = new Pen("Dark Green", 1.0, 0.25);
+ public readonly Gdk.Device device;
public readonly List points = new List();
private readonly List