diff --git a/mono/Assistance/InputManager.cs b/mono/Assistance/InputManager.cs new file mode 100644 index 0000000..059f543 --- /dev/null +++ b/mono/Assistance/InputManager.cs @@ -0,0 +1,327 @@ +using System; +using System.Collections.Generic; + +namespace Assistance { + public class InputManager: Track.Owner { + public static readonly Drawing.Pen penPreview = new Drawing.Pen("Dark Green", 1.0, 0.25); + + public class TrackHandler: Track.Handler { + public readonly List keys = new List(); + public TrackHandler(InputManager owner, Track original): + base(owner, track) { } + } + + public class KeyPoint { + public class Holder: IDisposable { + public readonly KeyPoint keyPoint; + private bool disposed = false; + public Holder(KeyPoint keyPoint) + { this.keyPoint = keyPoint; ++keyPoint.refCount; } + public void Dispose() + { Dispose(true); GC.SuppressFinalize(this); } + protected virtual void Dispose(bool disposing) + { if (!disposed) --keyPoint.refCount; disposed = true; } + ~Holder() + { Dispose(false); } + } + private int refCount = 0; + public Holder hold() + { return new Holder(this); } + public bool isFree + { get { return refCount <= 0; } } + } + + + public readonly Workarea workarea; + + private readonly InputState state = new InputState(); + + private Tool tool; + private readonly List modifiers = new List(); + + private readonly List tracks = new List(); + private readonly List subTracks = null; + private readonly List keyPoints = new List(); + private int keyPointsSent; + + + InputManager(Workarea workarea) + { this.workarea = workarea; } + + + private void paintRollbackTo(int keyIndex, List subTracks) { + if (keyIndex >= keyPoints.Count) + return; + + int level = keyIndex + 1; + if (level <= keyPointsSent) { + if (level <= keyPointsSent) + tool.paintPop(keyPointsSent - level); + tool.paintCancel(); + keyPointsSent = level; + } + + foreach(Track track in subTracks) { + TrackHandler handler = (TrackHandler)track.handler; + handler.keys.RemoveRange(level, keyPoints.Count - level); + int index = handler.keys[keyIndex]; + track.wayPointsRemoved = 0; + track.wayPointsAdded = track.points.Count - index - 1; + } + keyPoints.RemoveRange(level, keyPoints.Count - level); + } + + private void paintApply(int count, Dictionary subTracks) { + if (count <= 0) + return; + + level = keyPoints.Count - count; + if (level >= keyPointsSent || tool.paintApply(keyPointsSent - level)) { + // apply + foreach(Track track in subTracks) + ((TrackHandler)track.handler).keys.RemoveRange(level, keyPoints.Count - level); + } else { + // rollback + tool.paintPop(keyPointsSent - level); + foreach(Track track in subTracks) { + TrackHandler handler = (TrackHandler)track.handler; + int index = handler.keys[level]; + handler.keys.RemoveRange(level, keyPoints.Count - level); + track.wayPointsRemoved = 0; + track.wayPointsAdded = track.points.Count - index - 1; + } + } + + keyPoints.RemoveRange(level, keyPoints.Count - level); + if (level < keyPointsSent) + keyPointsSent = level; + } + + private void paintTracks() { + bool allFinished = true; + foreach(Track track in tracks) + if (!track.isFinished) + { allFinished = false; break; } + + while(true) { + // run modifiers + KeyPoint newKeyPoint = new KeyPoint(); + List subTracks = tracks; + foreach(Modifier modifier in modifiers) + subTracks = modifier.modify(subTracks); + + if (keyPoints.Count > 0) { + // rollback + int rollbackIndex = keyPoints.Count; + foreach(Track track in subTracks) { + if (track.wayPointsRemoved > 0) { + int count = track.points.Count - track.wayPointsAdded + track.wayPointsRemoved; + TrackHandler handler = (TrackHandler)track.handler; + while(rollbackIndex > 0 && (rollbackIndex >= keyPoints.Count || handler.keys[rollbackIndex] > count)) + --rollbackIndex; + } + } + paintRollbackTo(rollbackIndex, subTracks); + + // apply + int applyCount = 0; + while(applyCount < keyPoints.Count && keyPoints[keyPoints.Count - applyCount - 1].isFree) + ++applyCount; + paintApply(applyCount, subTracks); + } + + // send to tool + if (keyPointsSent == keyPoints.Count) + tool.paintTracks(subTracks); + + // is paint finished? + if (newKeyPoint.isFree) { + if (allFinished) { + paintApply(keyPoints.Count, subTracks); + tracks.Clear(); + this.subTracks = null; + } else { + this.subTracks = subTracks; + } + break; + } + + // create key point + if (tool.paintPush()) ++keyPointsSent; + keyPoints.Add(newKeyPoint); + foreach(Track track in subTracks) + ((TrackHandler)track.handler).keys.Add(track.points.Count); + } + } + + private int trackCompare(Track track, Gdk.Device device, long touchId) { + if (track.device < device) return -1; + if (track.device > device) return 1; + if (track.touchId < touchId) return -1; + if (track.touchId > touchId) return 1; + return 0; + } + + private Track createTrack(int index, Gdk.Device device, long touchId, long ticks) { + Track track = new Track( + device, + touchId, + state.keyHistoryHolder(ticks), + state.buttonHistoryHolder(device, ticks) ); + tracks.Insert(index, track); + return track; + } + + private Track getTrack(Gdk.Device device, long touchId, long ticks) { + int cmp; + + int a = 0; + cmp = trackCompare(tracks[a], device, touchId); + if (cmp == 0) return tracks[a]; + if (cmp < 0) return createTrack(a, device, touchId, ticks); + + int b = tracks.Count - 1; + cmp = trackCompare(tracks[b], device, touchId); + if (cmp == 0) return tracks[b]; + if (cmp > 0) return createTrack(b+1, device, touchId, ticks); + + // binary search: tracks[a] < tracks[c] < tracks[b] + while(true) { + int c = (a + b)/2; + if (a == c) break; + cmp = trackCompare(tracks[c], device, touchId); + if (cmp < 0) b = c; else + if (cmp > 0) a = c; else + return tracks[c]; + } + return createTrack(b, device, touchId, ticks); + } + + private void addTrackPoint(Track track, Track.Point point, double time, bool final) { + // fix time + if (track.points.Count > 0) + time = Math.Max(time, track.getLast().time + Timer.step); + + // calc length + double length = track.points.Count > 0 + ? (point.position - track.getLast().point.position).lenSqr() + track.getLast().length + : 0.0; + + // add + track.points.Add( new Track.WayPoint( + point, + new Track.Point(), + (double)track.points.Count, + time, + length, + track.points.Count, + final )); + ++track.wayPointsAdded; + } + + private void touchTracks(bool finish = false) { + foreach(Track track in tracks) + if (!track.isFinished() && track.points.Count > 0) + addTrackPoint(track, track.getLast().point, track.getLast().time, finish); + paintTracks(); + } + + private void finishTracks() + { touchTracks(true); } + + + public void trackEvent(long touchId, Gdk.Device device, Track.Point point, bool final, long ticks) { + if (tool != null) { + Track track = getTrack(touchId, device, ticks); + if (!track.isFinished) { + double time = (double)(ticks - track.keyHistory.ticks)*Timer.step - track.keyHistory.timeOffset; + addTrackPoint(track, point, time, final); + paintTracks(); + } + } + } + + public void keyEvent(bool press, Gdk.Key key, long ticks) { + state.keyEvent(press, key, ticks); + if (tool != null) { + tool.keyEvent(press, key, state); + touchTracks(); + } + } + + public void buttonEvent(bool press, Gdk.Device device, uint button, long ticks) { + state.buttonEvent(press, device, button, ticks); + if (tool != null) { + tool.buttonEvent(press, device, button, state); + touchTracks(); + } + } + + + public Tool getTool() + { return tool; } + + public void setTool(Tool tool) { + if (this.tool == tool) { + if (this.tool != null) { + finishTracks(); + this.tool.deactivate(); + } + + this.tool = tool; + + if (this.tool != null) + this.tool.activate(); + } + } + + public int getModifiersCount() + { return modifiers.Count; } + public InputModifier getModifier(int index) + { return modifiers[index]; } + + public void insertModifier(int index, InputModifier modifier) { + if (this.tool != null) finishTracks(); + modifiers.Insert(index, modifier); + modifier.activate(); + } + public void addModifier(InputModifier modifier) + { insertModifier(getModifiersCount(), modifier); } + + public void removeModifier(int index) { + if (this.tool != null) finishTracks(); + modifiers[i].deactivate(); + modifiers.RemoveAt(index); + } + public void removeModifier(InputModifier modifier) { + for(int i = 0; i < getModifiersCount(); ++i) + if (getModifier(i) == modifier) + { removeModifier(i); break; } + } + public void clearModifiers() { + while(getModifiersCount() > 0) + removeModifier(0); + } + + + public void draw(Cairo.Context context) { + // paint not sent sub-tracks + if (subTracks != null && keyPointsSent < keyPoints.Count) { + context.Save(); + penPreview.apply(context); + foreach(Track track in subTracks) { + TrackHandler handler = (TrackHandler)track.handler; + int start = handler.keys[keyPointsSent]; + if (start < track.points.Count) { + context.MoveTo(track.points[start].point.position.x, track.points[start].point.position.y); + for(int i = start + 1; i < track.points.Count; ++i) + context.MoveTo(track.points[i].point.position.x, track.points[i].point.position.y); + } + } + context.Stroke(); + context.Restore(); + } + } + } +} + diff --git a/mono/Assistance/InputModifier.cs b/mono/Assistance/InputModifier.cs new file mode 100644 index 0000000..23b1f56 --- /dev/null +++ b/mono/Assistance/InputModifier.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; + +namespace Assistance { + public interface InputModifier { + void activate(); + List modify(List tracks); + void deactivate(); + } +} + diff --git a/mono/Assistance/InputState.cs b/mono/Assistance/InputState.cs index 81560cd..2ba3adc 100644 --- a/mono/Assistance/InputState.cs +++ b/mono/Assistance/InputState.cs @@ -101,10 +101,10 @@ namespace Assistance { { return new KeyState.Holder(keyState, ticks, timeOffset); } public KeyState.Holder keyStateHolder() { return keyStateHolder(ticks); } - public KeyHistory.Holder keyStateHolder(long ticks, double timeOffset = 0.0) + public KeyHistory.Holder keyHistoryHolder(long ticks, double timeOffset = 0.0) { return new KeyHistory.Holder(keyHistory, ticks, timeOffset); } - public KeyHistory.Holder keyStateHolder() - { return keyStateHolder(ticks); } + public KeyHistory.Holder keyHistoryHolder() + { return keyHistoryHolder(ticks); } public KeyState.Holder buttonStateHolder(Gdk.Device device, long ticks, double timeOffset = 0.0) { return new KeyState.Holder(buttonState(device), ticks, timeOffset); } diff --git a/mono/Assistance/Tool.cs b/mono/Assistance/Tool.cs index d0cc2ca..fc2f74e 100644 --- a/mono/Assistance/Tool.cs +++ b/mono/Assistance/Tool.cs @@ -1,79 +1,52 @@ using System; namespace Assistance { - [Flags] - public enum Modifiers { - None = 0, - Interpolation = 1, - Guideline = 2, - Multiline = 4 - }; - public class Tool { - public Modifiers getAvailableModifiers() - { return Modifiers.None; } - - public void activate() { } - - public void keyPress(Gdk.Key key, InputState state) { } - public void keyRelease(Gdk.Key key, InputState state) { } - public void buttonPress(Gdk.Device device, uint button, InputState state) { } - public void buttonRelease(Gdk.Device device, uint button, InputState state) { } + [Flags] + public enum ModifierTypes { + None = 0, + Tangents = 1, + Interpolation = 2, + Guideline = 4, + Multiline = 8 + }; + + public virtual ModifierTypes getAvailableModifierTypes() + { return ModifierTypes.None; } + + public virtual void activate() { } + + public virtual void keyEvent(bool press, Gdk.Key key, InputState state) { } + public virtual void buttonEvent(bool press, Gdk.Device device, uint button, InputState state) { } - public bool paintBegin() { return false; } - public void paintTrackBegin(Track track) { } - public void paintTrackPoint(Track track) { } - public void paintTrackEnd(Track track) { } - public bool paintApply() { return false; } - public void paintCancel() { } + // create new painting level and return true, or do nothing and return false + // was: ------O-------O------ + // become: ------O-------O------O + public virtual bool paintPush() { return false; } + + // paint several track-points at the top painting level + // was: ------O-------O------ + // become: ------O-------O------------ + public virtual void paintTracks(List tracks) { } + + // try to merge N top painting levels and return true, or do nothing and return false + // was: ------O-------O------O------ + // become (N = 2): ------O--------------------- + public virtual bool paintApply(int count) { return 0; } + + // reset top level to initial state + // was: ------O-------O------O------ + // become: ------O-------O------O + public virtual void paintCancel() { } + + // cancel and pop N painting levels + // was: ------O-------O------O------ + // become (N = 2): ------O------- + public virtual void paintPop(int count) { } + - public void draw(Cairo.Context context) { } + public virtual void draw(Cairo.Context context) { } - public void deactivate() { } + public virtual void deactivate() { } } } - -/* -TODO: - ////////////////////////////////////////// - // deprecated - ////////////////////////////////////////// - - 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 void draw(Cairo.Context context, bool preview = false) { - if (preview) { - if (points.Count < 2) - return; - context.Save(); - penPreview.apply(context); - context.MoveTo(points[0].point.x, points[0].point.y); - for(int i = 1; i < points.Count; ++i) - context.LineTo(points[i].point.x, points[i].point.y); - context.Stroke(); - context.Restore(); - } else { - context.Save(); - pen.apply(context); - foreach(TrackPoint p in points) { - 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(); - } - } - - public Rectangle getBounds() { - if (points.Count == 0) - return new Rectangle(); - Rectangle bounds = new Rectangle(points[0].point); - foreach(TrackPoint p in points) - bounds = bounds.expand(p.point); - return bounds.inflate(Math.Max(pen.width, penPreview.width) + 2.0); - } -*/ \ No newline at end of file diff --git a/mono/Assistance/Track.cs b/mono/Assistance/Track.cs index 66cf7b0..ef36955 100644 --- a/mono/Assistance/Track.cs +++ b/mono/Assistance/Track.cs @@ -5,7 +5,7 @@ using Assistance.Drawing; namespace Assistance { public class Track { public class Owner { } - + public class Handler { public readonly Owner owner; public readonly Track original; @@ -76,14 +76,16 @@ namespace Assistance { public double time; public double length; + public int depRootIndex; public bool final; public WayPoint( Point point, - Point tangent, + Point tangent = new Point(), double originalIndex = 0.0, double time = 0.0, double length = 0.0, + int depRootIndex = 0, bool final = false ) { this.point = point; @@ -91,15 +93,17 @@ namespace Assistance { this.originalIndex = originalIndex; this.time = time; this.length = length; + this.depRootIndex = depRootIndex; this.final = final; } } - private static long lastTouchId; + private static long lastId; - public readonly long touchId; + public readonly long id; public readonly Gdk.Device device; + public readonly long touchId; public readonly List points = new List(); public readonly KeyHistory.Holder keyHistory; public readonly KeyHistory.Holder buttonHistory; @@ -110,37 +114,25 @@ namespace Assistance { public int wayPointsRemoved; public int wayPointsAdded; - public static long genTouchId() { return ++lastTouchId; } - public Track( - Gdk.Device device, - KeyHistory.Holder keyHistory, - KeyHistory.Holder buttonHistory, - long touchId + Gdk.Device device = null, + long touchId = 0, + KeyHistory.Holder keyHistory = null, + KeyHistory.Holder buttonHistory = null ) { + this.id = ++lastId; this.device = device; + this.touchId = touchId; this.keyHistory = keyHistory; this.buttonHistory = buttonHistory; - this.touchId = touchId; } - public Track( - Gdk.Device device = null, - KeyHistory.Holder keyHistory = null, - KeyHistory.Holder buttonHistory = null - ): - this(device, genTouchId()) { } - - public Track(Modifier modifier, long touchId): + public Track(Modifier modifier): this( modifier.original.device, modifier.original.keyHistory.offset( modifier.timeOffset ), - modifier.original.buttonHistory.offset( modifier.timeOffset ), - touchId ) + modifier.original.buttonHistory.offset( modifier.timeOffset ) ) { this.modifier = modifier; } - public Track(Modifier modifier): - this(modifier, genTouchId()) { } - public Track original { get { return modifier != null ? modifier.original : null; } } public double timeOffset @@ -253,7 +245,8 @@ namespace Assistance { Geometry.Interpolation.splineTangent(p0.point, p1.point, p0.tangent, p1.tangent, l), Geometry.interpolationLinear(p0.originalIndex, p1.originalIndex, l), Geometry.interpolationLinear(p0.time, p1.time, l), - Geometry.interpolationLinear(p0.length, p1.length, l) ); + Geometry.interpolationLinear(p0.length, p1.length, l), + p1.depRootIndex ); } } }