public override VerbResult OtherVerb(EditableView.ClickPosition position, SAW.Functions.Codes code) { switch (code) { case SAW.Functions.Codes.Increment: case SAW.Functions.Codes.Decrement: int delta = code == SAW.Functions.Codes.Increment ? 1 : -1; float step = position.ScalarSnapStep(0); if (step <= 0) { step = delta * Globals.Root.CurrentConfig.ReadSingle(Config.Radius_Step, 1); } else { step *= delta; } SizeF vector = Vertices[0].VectorTo(Vertices[1]); float length = vector.Length(); if (length + step < 5 || length < Geometry.NEGLIGIBLESMALL) { return(VerbResult.Rejected); } Vertices[1] = Vertices[0] + vector.ChangeLength(length + step); m_Bounds = RectangleF.Empty; return(VerbResult.Continuing); default: return(VerbResult.Rejected); } }
/// <summary> /// Returns the type of manipulation that will happen when interacting with the element at the specified point. /// </summary> /// <param name="point">The point to start manipulating.</param> /// <param name="altDown">Whether any alt key is pressed.</param> /// <param name="preview">whether to set the preview manipulation, or the real one.</param> /// <param name="translateOnly">Whether to ignore any special manipulations and only use translate.</param> /// <returns>The manipulation type for the specified point. <c>null</c> if no manipulation would happen /// at this point.</returns> /// <remarks>Manipulation preview is used to show what would be modified on a selected element. We cannot /// keep updating the element manipulation as the mouse moves, but do want to provide a visual indicator.</remarks> public override bool StartManipulating(Point point, bool altDown, bool preview = false, bool translateOnly = false) { SizeF d = point - this.Location; if (d.Length() > this.Radius + 2) { this.PreviewManipulation = null; return(false); } // Scale if mouse over the outter edge. if (Math.Sqrt(Math.Abs(Math.Pow(d.Width, 2) + Math.Pow(d.Height, 2) - Math.Pow(this.Radius, 2))) < 16 && !translateOnly) { this.SetManipulation( new ElementManipulation { Type = ElementManipulationType.Scale, Index = 0, Direction = d.GetUnitVector() }, preview); return(true); } this.SetManipulation( new ElementManipulation { Type = ElementManipulationType.Translate, Index = 0 }, preview); return(true); }
/// <summary> /// Returns the unit vector of the specified speed. /// </summary> /// <param name="speed">The speed to get the unit vector of.</param> /// <returns>The unit vector.</returns> public static SizeF GetUnitVector(this SizeF speed) { /* The unit vector is calculated as * * v * ||v|| */ return(speed.Multiply(1 / speed.Length())); }
/// <summary> /// Renders the key in the specified surface. /// </summary> /// <param name="g">The GDI+ surface to render on.</param> /// <param name="speed">The speed of the mouse.</param> public void Render(Graphics g, SizeF speed) { var subStyle = GlobalSettings.CurrentStyle.TryGetElementStyle <MouseSpeedIndicatorStyle>(this.Id) ?? GlobalSettings.CurrentStyle.DefaultMouseSpeedIndicatorStyle; // Small circles have a fifth of the radius of the full control. var smallRadius = (float)this.Radius / 5; // The sensitivity is a factor over the mouse speed. var sensitivity = GlobalSettings.Settings.MouseSensitivity / (float)100; // The total length is determined by the sensitivity, speed and radius. But never more than the radius. var pointerLength = (int)Math.Min(this.Radius, sensitivity * speed.Length() * this.Radius); var colorMultiplier = Math.Max(0, Math.Min(1, (float)pointerLength / this.Radius)); Color color1 = subStyle.InnerColor; Color outerColor = subStyle.OuterColor; // The second color should be averaged over the two specified colours, based upon how far out the thingymabob is. var color2 = Color.FromArgb( (int)(color1.R * (1 - colorMultiplier) + outerColor.R * colorMultiplier), (int)(color1.G * (1 - colorMultiplier) + outerColor.G * colorMultiplier), (int)(color1.B * (1 - colorMultiplier) + outerColor.B * colorMultiplier)); // Draw the edge. g.DrawEllipse( new Pen(color1, subStyle.OutlineWidth), Geom.CircleToRectangle(this.Location, this.Radius)); // Only calculate the pointer data if it has some length. if (pointerLength > 0) { // Determine the angle of the pointer. var angle = speed.GetAngle(); // Determine the location of the pointer end. var pointerEnd = this.Location.CircularTranslate(pointerLength, angle); // If the pointer doesn't end where it starts, draw it. if (pointerEnd.X != this.Location.X || pointerEnd.Y != this.Location.Y) { // Draw the pointer, as a pie. g.FillPie( new LinearGradientBrush(this.Location, pointerEnd, color1, color2), Geom.CircleToRectangle(this.Location, pointerLength), Geom.RadToDeg(angle) - 10, 20); // Draw a circle on the outter edge in the direction of the pointer. var pointerEdge = this.Location.CircularTranslate(this.Radius, angle); g.FillEllipse(new SolidBrush(color2), Geom.CircleToRectangle(pointerEdge, (int)smallRadius)); } } // Draw the circle in the center. g.FillEllipse(new SolidBrush(color1), Geom.CircleToRectangle(this.Location, (int)smallRadius)); }
protected internal override void DoGrabMove(GrabMovement move) { switch (move.GrabType) { case GrabTypes.SingleVertex: { int index = move.ShapeIndex; int previous = (index + 3) % 4; int next = (index + 1) % 4; int opposite = (index + 2) % 4; // keep orientation unchanged. Opposite point is only invariant. Calc vectors from this to the 2 semi-moving points SizeF previousVector = Vertices[opposite].VectorTo(Vertices[previous]); SizeF nextVector = Vertices[opposite].VectorTo(Vertices[next]); SizeF movingVector = Vertices[opposite].VectorTo(move.Current.Snapped); previousVector = Geometry.ProjectionVector(movingVector, previousVector); nextVector = Geometry.ProjectionVector(movingVector, nextVector); if (previousVector.Length() < Geometry.THINLINE || nextVector.Length() < Geometry.THINLINE) { return; // degenerate } Vertices[next] = Vertices[opposite] + nextVector; Vertices[previous] = Vertices[opposite] + previousVector; Vertices[index] = Vertices[previous] + nextVector; m_Bounds = CalculateBounds(); } break; case GrabTypes.Radius: { int vertex = move.ShapeIndex; // vertex at start of line int end = (move.ShapeIndex + 1) % 4; // and end of line int previous = (move.ShapeIndex + 3) % 4; // vertex at start of previous line leading to intVertex int opposite = (move.ShapeIndex + 2) % 4; // and at opposite corner; end of line leading from intEnd PointF newPoint = Geometry.ClosestPointOnLine(Vertices[previous], Vertices[vertex], move.Current.Snapped); SizeF newVector = Vertices[previous].VectorTo(newPoint); // new length of the sides which are being stretched if (newVector.Length() < Geometry.THINLINE) { return; } Vertices[vertex] = newPoint; Vertices[end] = Vertices[opposite] + newVector; m_Bounds = CalculateBounds(); } break; default: base.DoGrabMove(move); break; } DiscardPath(); }
internal static SizeF AngleSnapVector(SizeF sz, float stepAngle = -1) { if (stepAngle <= 0) { stepAngle = AngleStep(); } if (sz.IsEmpty) { Debug.Fail("Cannot AngleSnapVector empty vector"); return(sz); } float angle = sz.VectorAngle(); angle = (float)(Math.Round(angle / stepAngle) * stepAngle); return(ScalarToVector(sz.Length(), angle)); }
private bool AdjustPoint(int index, EditableView.ClickPosition position) { // Used by both Float and DoGrabMove. Returns true if the point is valid PointF pt = position.Snapped; // the main task is to restrict the line to the correct directions float angularStep = GetAngularStep(position.Page); SizeF vector = Vertices[1 - index].VectorTo(pt); if (vector.IsEmpty) { return(false); } // the following calculations will fail if the two points are on top of each other // but there is no need to do any of this - the position of point(0) is a valid position (ish) // calculate the angle in radians of this line counting clockwise from vertically up (must count from vertically in order to match the isometric paper) float angle = vector.VectorAngle(); float length = vector.Length(); angle = (float)(Math.Round(angle / angularStep) * angularStep); // angle changed to the closest of the allowed angles // now snapped the length so that it will hit a grid point // the complication is if we are going diagonally on squared paper, in which case the incremental length is sqrt(2) * spacing. // the following function should take care of this float step = position.ScalarSnapStep(angle); if (step > 0) { length = (float)Math.Round(length / step) * step; // make it a multiple of a grid unit } vector = Geometry.ScalarToVector(length, angle); pt = PointF.Add(Vertices[1 - index], vector); // and finally we snap the resulting point back to the page grid - it should already be on this, but just in case // of any rounding errors if (position.RequestedSnap == SnapModes.Grid) { position.Snapped = position.Page.Paper.SnapPoint2(pt); } else { position.Snapped = pt; } // in order to avoid flickering it is worth checking if the target has actually moved - because this line is very constrained often it does not move at all, or and repainting a completely unchanged line is the most flickery situation return(true); }
internal override SizeF SocketExitVector(int index) { EnsureSocketList(); if (index < 0 || index >= m_Sockets.Count) { Debug.Fail("Invalid socket index"); return(new SizeF(1, 0)); } SizeF exit = m_Sockets[index].ExitVector; // we cannot return SizeF.Empty; if the user has not specified then exit vector we will deduce it based on the group if (exit.IsEmpty) { exit = Middle().VectorTo(m_Sockets[index].Centre); // and just in case the user has managed to place the socket right in the middle if (exit.Length() < Geometry.NEGLIGIBLE) { exit = new SizeF(1, 0); } } return(exit); }
private const float TRANSVERSECLEARANCE = 5; // clearance to leave around shape when stepping around it private void PositionLine() { // This builds up a list of points required at each of the beginning and end List <PointF> start = new List <PointF> { StartPoint }; List <PointF> end = new List <PointF> { FinishPoint }; SizeF vector = StartPoint.VectorTo(FinishPoint); if (vector.Length() < 1) { return; } float primary = PrimaryVector(vector).VectorAngle(); ProcessExitVector(start, 0, primary); ProcessExitVector(end, 1, primary + Geometry.ANGLE180); // The remaining required vector after adding on the exit vectors... bool[] conflict = new bool[2]; int index = 0; int step = 0; bool done = false; do { if (index == 0) { vector = start[start.Count - 1].VectorTo(end[end.Count - 1]); } else // opposite direction reverses the vector... { vector = end[end.Count - 1].VectorTo(start[start.Count - 1]); } SizeF primaryVector = PrimaryVector(vector); SizeF transverseGuaranteed = TransverseVector(vector, true); SizeF transverseNull = TransverseVector(vector, false); List <PointF> points = index == 0 ? start : end; List <PointF> other = index == 1 ? start : end; conflict[index] = PrimaryVectorConflicts(points, primaryVector, m_Links[index].Shape); conflict[1 - index] = PrimaryVectorConflicts(other, new SizeF(0, 0) - primaryVector, m_Links[1 - index].Shape); if (conflict[index]) { // If the other shape is not conflicted, the best strategy can be to move the entire transverse distance now (assuming that is far enough to clear this shape) PointF pt = points[points.Count - 1] + transverseGuaranteed; if (!conflict[1 - index] && !PrimaryVectorConflicts(pt, primaryVector, m_Links[index].Shape)) { // prime vector won't conflict once entire distance away points.Add(pt); done = true; } else { // can't go correct distance away - probably not far enough to clear this shape // therefore go far enough to clear this one pt = points[points.Count - 1] + GetRequiredTransverseToClearShape(points[points.Count - 1], m_Links[index].Shape, primaryVector, transverseGuaranteed); points.Add(pt); } } else if (!conflict[1 - index]) { // neither now conflict - place the line as necessary // first we check if extending one of the previous vectors, which is in the transverse direction // will work. This saves an extra wiggle (which does look quite silly) when the exits were, say, vertical and the current primary // is horizontal, but there is nothing constraining us from just extending the exit vectors with a single horizontal line SizeF last = LastVector(points); SizeF otherLast = LastVector(other); if (VectorsMatch(last, transverseGuaranteed)) { if (VectorsMatch(new SizeF(0, 0) - otherLast, transverseGuaranteed)) { // both match, ideal option is to move half long each points.Add(points[points.Count - 1] + transverseGuaranteed.MultiplyBy(0.5f)); other.Add(other[other.Count - 1] + transverseGuaranteed.MultiplyBy(-0.5f)); } else { points.Add(points[points.Count - 1] + transverseGuaranteed); } done = true; } else if (VectorsMatch(new SizeF(0, 0) - otherLast, transverseGuaranteed)) { other.Add(other[other.Count - 1] + transverseGuaranteed.MultiplyBy(-1)); done = true; } else { SizeF first = ChooseCrossStepPosition(index, points, other, primaryVector); if (!first.IsEmpty) { PointF pt = points[points.Count - 1] + first; points.Add(pt); if (!transverseNull.IsEmpty) { pt = pt + transverseNull; points.Add(pt); } } done = true; } } index = (index + 1) % 2; step += 1; } while (!(step >= 8 || done)); // And now merge these lists to create the new list of vertices Vertices.Clear(); Vertices.AddRange(start); end.Reverse(); Vertices.AddRange(end); DecacheArrowheads(); DiscardPath(); }
internal static float ProjectionScalar(SizeF project, SizeF ontoDirection) { // calculates the projection of the first parameter in the direction given by the second parameter. (i.e. length of first parameter in the direction of the second) //The length of the second parameter does not affect the result return(project.DotProduct(ontoDirection) / ontoDirection.Length()); }
internal static SizeF ChangeLength(this SizeF vector, float length) { float multiply = length / vector.Length(); return(new SizeF(vector.Width * multiply, vector.Height * multiply)); }