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 parents = new List(); private readonly List transformFuncs = new List(); - public Track() { } + public Track(Gdk.Device device) + { + this.device = device; + } public Track(Track parent, Geometry.TransformFunc transformFunc): - this() + this(parent.device) { parents.AddRange(parent.parents); parents.Add(parent); @@ -36,7 +41,7 @@ namespace Assistance { public Track createChildAndBuild(Geometry.TransformFunc transformFunc, double precision = 1.0) { return new Track(this, transformFunc, precision); } - + public Rectangle getBounds() { if (points.Count == 0) return new Rectangle(); @@ -62,7 +67,7 @@ namespace Assistance { points.Add(tp1); } else { double l = 0.5*(l0 + l1); - TrackPoint p = new TrackPoint( + TrackPoint p = p0.spawn( Geometry.splinePoint(p0.point, p1.point, t0.point, t1.point, l), p0.time + l*(p1.time - p0.time), Geometry.splinePoint(p0.pressure, p1.pressure, t0.pressure, t1.pressure, l), @@ -122,7 +127,10 @@ namespace Assistance { context.Save(); pen.apply(context); foreach(TrackPoint p in points) { - context.Arc(p.point.x, p.point.y, 2.0*p.pressure*pen.width, 0.0, 2.0*Math.PI); + double t = p.keyState.howLongPressed(Gdk.Key.m) + + p.buttonState.howLongPressed(3); + double w = p.pressure*pen.width + 5.0*t; + context.Arc(p.point.x, p.point.y, 2.0*w, 0.0, 2.0*Math.PI); context.Fill(); } context.Restore(); diff --git a/mono/Assistance/TrackPoint.cs b/mono/Assistance/TrackPoint.cs index f8a5c59..adb1b7e 100644 --- a/mono/Assistance/TrackPoint.cs +++ b/mono/Assistance/TrackPoint.cs @@ -6,24 +6,31 @@ namespace Assistance { public double time; public double pressure; public Point tilt; - - public TrackPoint(Point point, double time, double pressure = 0.5, Point tilt = new Point()) { - this.point = point; - this.time = time; - this.pressure = pressure; - this.tilt = tilt; + + public KeyState.Holder keyState; + public KeyState.Holder buttonState; + + public TrackPoint spawn(Point point, double time, double pressure, Point tilt) { + TrackPoint p = this; + p.point = point; + p.time = time; + p.pressure = pressure; + p.tilt = tilt; + p.keyState.timeOffset += time - this.time; + p.buttonState.timeOffset += time - this.time; + return p; } public static TrackPoint operator+ (TrackPoint a, TrackPoint b) - { return new TrackPoint(a.point + b.point, a.time + b.time, a.pressure + b.pressure, a.tilt + b.tilt); } + { return a.spawn(a.point + b.point, a.time + b.time, a.pressure + b.pressure, a.tilt + b.tilt); } public static TrackPoint operator- (TrackPoint a, TrackPoint b) - { return new TrackPoint(a.point - b.point, a.time - b.time, a.pressure - b.pressure, a.tilt - b.tilt); } + { return a.spawn(a.point - b.point, a.time - b.time, a.pressure - b.pressure, a.tilt - b.tilt); } public static TrackPoint operator* (TrackPoint a, double b) - { return new TrackPoint(a.point*b, a.time*b, a.pressure*b, a.tilt*b); } + { return a.spawn(a.point*b, a.time*b, a.pressure*b, a.tilt*b); } public static TrackPoint operator* (double b, TrackPoint a) { return a*b; } public static TrackPoint operator/ (TrackPoint a, double b) - { return new TrackPoint(a.point/b, a.time/b, a.pressure/b, a.tilt/b); } + { return a.spawn(a.point/b, a.time/b, a.pressure/b, a.tilt/b); } } }