| using System; |
| using System.Collections.Generic; |
| |
| namespace Assistance { |
| public class InputManager: Track.IOwner { |
| public static readonly Drawing.Pen penPreview = new Drawing.Pen("Dark Green", 3.0, 0.25); |
| public static readonly double levelAlpha = 0.8; |
| |
| public class TrackHandler: Track.Handler { |
| public readonly List<int> keys = new List<int>(); |
| public TrackHandler(InputManager owner, Track original, int keysCount = 0): |
| base(owner, original) |
| { for(int i = 0; i < keysCount; ++i) keys.Add(0); } |
| } |
| |
| public class KeyPoint { |
| public class Holder: IDisposable { |
| public readonly KeyPoint keyPoint; |
| private bool holded = false; |
| |
| public Holder(KeyPoint keyPoint) |
| { this.keyPoint = keyPoint; reuse(); } |
| |
| public bool available |
| { get { return keyPoint.available; } } |
| public bool isHolded |
| { get { return holded; } } |
| public bool reuse() { |
| if (!holded) ++keyPoint.refCount; |
| holded = true; |
| return keyPoint.available; |
| } |
| public void release() { |
| if (holded) --keyPoint.refCount; |
| holded = false; |
| } |
| |
| public void Dispose() |
| { Dispose(true); GC.SuppressFinalize(this); } |
| protected virtual void Dispose(bool disposing) |
| { release(); } |
| ~Holder() |
| { Dispose(false); } |
| } |
| |
| private int refCount = 0; |
| public bool available = true; |
| |
| public Holder hold() |
| { return new Holder(this); } |
| public bool isFree |
| { get { return refCount <= 0; } } |
| } |
| |
| public interface IModifier: Track.IOwner { |
| void activate(); |
| void modify(List<Track> tracks, KeyPoint keyPoint, List<Track> outTracks); |
| void draw(Cairo.Context context, List<Track> tracks, List<Point> hovers); |
| void deactivate(); |
| } |
| |
| public class Modifier: IModifier { |
| public virtual void activate() { } |
| public virtual void modify(Track track, KeyPoint keyPoint, List<Track> outTracks) { } |
| public virtual void modify(List<Track> tracks, KeyPoint keyPoint, List<Track> outTracks) |
| { foreach(Track track in tracks) modify(track, keyPoint, outTracks); } |
| public virtual void drawHover(Cairo.Context context, Point hover) { } |
| public virtual void drawTrack(Cairo.Context context, Track track) { } |
| public virtual void draw(Cairo.Context context, List<Track> tracks, List<Point> hovers) { |
| foreach(Track track in tracks) drawTrack(context, track); |
| foreach(Point hover in hovers) drawHover(context, hover); |
| } |
| public virtual void deactivate() { } |
| } |
| |
| |
| public readonly Workarea workarea; |
| |
| private readonly InputState state = new InputState(); |
| |
| private bool wantActive; |
| private bool actualActive; |
| private Tool tool; |
| private readonly List<IModifier> modifiers = new List<IModifier>(); |
| |
| private readonly List<List<Track>> tracks = new List<List<Track>>() { new List<Track>() }; |
| private readonly List<KeyPoint> keyPoints = new List<KeyPoint>(); |
| private int keyPointsSent; |
| |
| |
| public InputManager(Workarea workarea) |
| { this.workarea = workarea; } |
| |
| private void paintRollbackTo(int keyIndex, List<Track> 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 cnt = handler.keys[keyIndex]; |
| track.resetRemoved(); |
| track.forceAdded(track.count - cnt); |
| } |
| for(int i = level; i < keyPoints.Count; ++i) keyPoints[i].available = false; |
| keyPoints.RemoveRange(level, keyPoints.Count - level); |
| } |
| |
| private void paintApply(int count, List<Track> subTracks) { |
| if (count <= 0) |
| return; |
| |
| int level = keyPoints.Count - count; |
| bool resend = true; |
| |
| if (level < keyPointsSent) { |
| |
| int applied = tool.paintApply(keyPointsSent - level); |
| applied = Math.Max(0, Math.Min(keyPointsSent - level, applied)); |
| keyPointsSent -= applied; |
| if (keyPointsSent == level) resend = false; |
| } |
| |
| if (level < keyPointsSent) { |
| |
| tool.paintPop(keyPointsSent - level); |
| keyPointsSent = level; |
| } |
| |
| |
| foreach(Track track in subTracks) { |
| TrackHandler handler = (TrackHandler)track.handler; |
| if (resend) { |
| track.resetRemoved(); |
| track.forceAdded(track.count - handler.keys[keyPointsSent]); |
| } |
| handler.keys.RemoveRange(level, handler.keys.Count - level); |
| } |
| for(int i = level; i < keyPoints.Count; ++i) keyPoints[i].available = false; |
| keyPoints.RemoveRange(level, keyPoints.Count - level); |
| } |
| |
| private void paintTracks() { |
| bool allFinished = true; |
| foreach(Track track in tracks[0]) |
| if (!track.isFinished()) |
| { allFinished = false; break; } |
| |
| while(true) { |
| |
| KeyPoint newKeyPoint = new KeyPoint(); |
| for(int i = 0; i < modifiers.Count; ++i) { |
| tracks[i+1].Clear(); |
| modifiers[i].modify(tracks[i], newKeyPoint, tracks[i+1]); |
| } |
| List<Track> subTracks = tracks[modifiers.Count]; |
| |
| |
| foreach(Track track in subTracks) |
| if (track.handler == null) |
| track.handler = new TrackHandler(this, track, keyPoints.Count); |
| |
| |
| |
| |
| |
| |
| |
| |
| if (keyPoints.Count > 0) { |
| |
| int rollbackIndex = keyPoints.Count; |
| foreach(Track track in subTracks) { |
| if (track.wasRemoved) { |
| int count = track.count - track.pointsAdded; |
| TrackHandler handler = (TrackHandler)track.handler; |
| while(rollbackIndex > 0 && (rollbackIndex >= keyPoints.Count || handler.keys[rollbackIndex] > count)) |
| --rollbackIndex; |
| } |
| } |
| paintRollbackTo(rollbackIndex, subTracks); |
| |
| |
| int applyCount = 0; |
| while(applyCount < keyPoints.Count && keyPoints[keyPoints.Count - applyCount - 1].isFree) |
| ++applyCount; |
| paintApply(applyCount, subTracks); |
| } |
| |
| |
| if (keyPointsSent == keyPoints.Count && subTracks.Count > 0) |
| tool.paintTracks(subTracks); |
| foreach(Track track in subTracks) |
| track.resetCounters(); |
| |
| |
| if (newKeyPoint.isFree) { |
| if (allFinished) { |
| paintApply(keyPoints.Count, subTracks); |
| foreach(List<Track> ts in tracks) |
| ts.Clear(); |
| } |
| break; |
| } |
| |
| |
| if (tool.paintPush()) ++keyPointsSent; |
| keyPoints.Add(newKeyPoint); |
| foreach(Track track in subTracks) |
| ((TrackHandler)track.handler).keys.Add(track.count); |
| } |
| |
| } |
| |
| private long getDeviceId(Gdk.Device device) |
| { return device == null ? 0 : device.Handle.ToInt64(); } |
| |
| private int trackCompare(Track track, Gdk.Device device, long touchId) { |
| if (getDeviceId(track.device) < getDeviceId(device)) return -1; |
| if (getDeviceId(track.device) > getDeviceId(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[0].Insert(index, track); |
| return track; |
| } |
| |
| private Track getTrack(Gdk.Device device, long touchId, long ticks) { |
| if (tracks[0].Count == 0) |
| return createTrack(0, device, touchId, ticks); |
| int cmp; |
| |
| int a = 0; |
| cmp = trackCompare(tracks[0][a], device, touchId); |
| if (cmp == 0) return tracks[0][a]; |
| if (cmp < 0) return createTrack(a, device, touchId, ticks); |
| |
| int b = tracks[0].Count - 1; |
| cmp = trackCompare(tracks[0][b], device, touchId); |
| if (cmp == 0) return tracks[0][b]; |
| if (cmp > 0) return createTrack(b+1, device, touchId, ticks); |
| |
| |
| while(true) { |
| int c = (a + b)/2; |
| if (a == c) break; |
| cmp = trackCompare(tracks[0][c], device, touchId); |
| if (cmp < 0) b = c; else |
| if (cmp > 0) a = c; else |
| return tracks[0][c]; |
| } |
| return createTrack(b, device, touchId, ticks); |
| } |
| |
| private void addTrackPoint(Track track, Point position, double pressure, Point tilt, double time, bool final) { |
| |
| track.add( new Track.Point( |
| position, |
| pressure, |
| tilt, |
| (double)track.count, |
| time, |
| 0.0, |
| final )); |
| } |
| |
| private void touchTracks(bool finish = false) { |
| foreach(Track track in tracks[0]) { |
| if (!track.isFinished() && !track.isEmpty) { |
| Track.Point p = track.getLast(); |
| addTrackPoint(track, p.position, p.pressure, p.tilt, p.time, finish); |
| } |
| } |
| } |
| |
| private void actualActivate() { |
| bool wasActive = actualActive; |
| actualActive = wantActive && tool != null; |
| if (wasActive == actualActive) return; |
| |
| if (actualActive) { |
| foreach(IModifier modifier in modifiers) |
| modifier.activate(); |
| tool.activate(); |
| } else { |
| touchTracks(true); |
| tool.deactivate(); |
| foreach(IModifier modifier in modifiers) |
| modifier.deactivate(); |
| } |
| } |
| |
| public bool isActive() |
| { return actualActive; } |
| public void activate() |
| { wantActive = true; actualActivate(); } |
| public void deactivate() |
| { wantActive = false; actualActivate(); } |
| public void processTracks() |
| { if (isActive()) paintTracks(); } |
| public void finishTracks() |
| { if (isActive()) { touchTracks(true); processTracks(); } } |
| |
| public List<Track> getInputTracks() |
| { return tracks[0]; } |
| public List<Track> getOutputTracks() |
| { return tracks[modifiers.Count]; } |
| |
| |
| public void trackEvent(Gdk.Device device, long touchId, Point position, double pressure, Point tilt, bool final, long ticks) { |
| if (isActive()) { |
| Track track = getTrack(device, touchId, ticks); |
| if (!track.isFinished()) { |
| double time = (double)(ticks - track.keyHistory.ticks)*Timer.step - track.keyHistory.timeOffset; |
| addTrackPoint(track, position, pressure, tilt, time, final); |
| } |
| } |
| } |
| |
| public void keyEvent(bool press, Gdk.Key key, long ticks) { |
| state.keyEvent(press, key, ticks); |
| if (isActive()) { |
| processTracks(); |
| tool.keyEvent(press, key, state); |
| touchTracks(); |
| processTracks(); |
| } |
| } |
| |
| public void buttonEvent(bool press, Gdk.Device device, uint button, long ticks) { |
| state.buttonEvent(press, device, button, ticks); |
| if (isActive()) { |
| processTracks(); |
| tool.buttonEvent(press, device, button, state); |
| touchTracks(); |
| processTracks(); |
| } |
| } |
| |
| public Tool getTool() |
| { return tool; } |
| |
| public void setTool(Tool tool) { |
| if (this.tool != tool) { |
| if (actualActive) { |
| finishTracks(); |
| this.tool.deactivate(); |
| } |
| |
| this.tool = tool; |
| |
| if (actualActive) { |
| if (this.tool != null) |
| this.tool.activate(); |
| else |
| actualActivate(); |
| } |
| } |
| } |
| |
| public int getModifiersCount() |
| { return modifiers.Count; } |
| public IModifier getModifier(int index) |
| { return modifiers[index]; } |
| |
| public void insertModifier(int index, IModifier modifier) { |
| if (actualActive) |
| finishTracks(); |
| modifiers.Insert(index, modifier); |
| tracks.Insert(index+1, new List<Track>()); |
| if (actualActive) |
| modifier.activate(); |
| } |
| public void addModifier(IModifier modifier) |
| { insertModifier(getModifiersCount(), modifier); } |
| |
| public void removeModifier(int index) { |
| if (actualActive) { |
| finishTracks(); |
| modifiers[index].deactivate(); |
| } |
| modifiers.RemoveAt(index); |
| tracks.RemoveAt(index+1); |
| } |
| public void removeModifier(IModifier modifier) { |
| for(int i = 0; i < getModifiersCount(); ++i) |
| if (getModifier(i) == modifier) |
| { removeModifier(i); break; } |
| } |
| public void clearModifiers() { |
| while(getModifiersCount() > 0) |
| removeModifier(getModifiersCount() - 1); |
| } |
| |
| |
| public void draw(Cairo.Context context, List<Point> hovers) { |
| if (!isActive()) return; |
| |
| |
| tool.draw(context); |
| |
| |
| if (keyPointsSent < keyPoints.Count) { |
| context.Save(); |
| penPreview.apply(context); |
| foreach(Track track in getOutputTracks()) { |
| TrackHandler handler = (TrackHandler)track.handler; |
| int start = handler.keys[keyPointsSent] - 1; |
| if (start < 0) start = 0; |
| if (start < track.count) { |
| Drawing.Color color = penPreview.color; |
| int level = keyPointsSent; |
| |
| color.apply(context); |
| context.MoveTo(track[start].position.x, track[start].position.y); |
| for(int i = start + 1; i < track.count; ++i) { |
| while(level < handler.keys.Count && handler.keys[level] <= i) { |
| context.Stroke(); |
| context.MoveTo(track[i-1].position.x, track[i-1].position.y); |
| color.a *= levelAlpha; |
| color.apply(context); |
| ++level; |
| } |
| context.LineTo(track[i].position.x, track[i].position.y); |
| } |
| } |
| } |
| context.Stroke(); |
| context.Restore(); |
| } |
| |
| |
| for(int i = 0; i < modifiers.Count; ++i) |
| modifiers[i].draw(context, tracks[i], hovers); |
| } |
| } |
| } |
| |