private void moveSelectionInBounds() { var hitObjects = selectedMovableObjects; Quad quad = getSurroundingQuad(hitObjects); Vector2 delta = Vector2.Zero; if (quad.TopLeft.X < 0) { delta.X -= quad.TopLeft.X; } if (quad.TopLeft.Y < 0) { delta.Y -= quad.TopLeft.Y; } if (quad.BottomRight.X > DrawWidth) { delta.X -= quad.BottomRight.X - DrawWidth; } if (quad.BottomRight.Y > DrawHeight) { delta.Y -= quad.BottomRight.Y - DrawHeight; } foreach (var h in hitObjects) { h.Position += delta; } }
public CrashScreenCover() { RelativeSizeAxes = Axes.Both; Scale = new osuTK.Vector2(scale); Anchor = Anchor.Centre; Origin = Anchor.Centre; }
private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) { scale = getClampedScale(hitObjects, reference, scale); // move the selection before scaling if dragging from top or left anchors. float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0; float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0; Quad selectionQuad = getSurroundingQuad(hitObjects); foreach (var h in hitObjects) { var newPosition = h.Position; // guard against no-ops and NaN. if (scale.X != 0 && selectionQuad.Width > 0) { newPosition.X = selectionQuad.TopLeft.X + xOffset + (h.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X); } if (scale.Y != 0 && selectionQuad.Height > 0) { newPosition.Y = selectionQuad.TopLeft.Y + yOffset + (h.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y); } h.Position = newPosition; } }
public NewsCoverTest() { Spacing = new osuTK.Vector2(0, 10); var article = new NewsArticleCover.ArticleInfo { Author = "Ephemeral", CoverUrl = "https://assets.ppy.sh/artists/58/header.jpg", Time = new DateTime(2019, 12, 4), Title = "New Featured Artist: Kurokotei" }; Children = new Drawable[] { new NewsArticleCover(article) { Height = 200 }, new NewsArticleCover(article) { Height = 120 }, new NewsArticleCover(article) { RelativeSizeAxes = Axes.None, Size = new osuTK.Vector2(400, 200), } }; }
public static void DrawCircleImage(SKCanvas canvas, osuTK.Vector2 pos, float r, SKBitmap bitmap, byte alpha = 255) { // Scale bitmap (warning: inefficient) var imgPos = new SKPoint(pos.X - r + offsetX, pos.Y - r + offsetY); var scaledBitmap = bitmap.Resize(new SKImageInfo((int)(r * 2), (int)(r * 2)), SKFilterQuality.High); canvas.DrawBitmap(scaledBitmap, imgPos, new SKPaint() { Color = new SKColor(0, 0, 0, alpha) }); }
public override bool HandleReverse() { var hitObjects = EditorBeatmap.SelectedHitObjects; double endTime = hitObjects.Max(h => h.GetEndTime()); double startTime = hitObjects.Min(h => h.StartTime); bool moreThanOneObject = hitObjects.Count > 1; foreach (var h in hitObjects) { if (moreThanOneObject) { h.StartTime = endTime - (h.GetEndTime() - startTime); } if (h is Slider slider) { var points = slider.Path.ControlPoints.ToArray(); Vector2 endPos = points.Last().Position.Value; slider.Path.ControlPoints.Clear(); slider.Position += endPos; PathType?lastType = null; for (var i = 0; i < points.Length; i++) { var p = points[i]; p.Position.Value -= endPos; // propagate types forwards to last null type if (i == points.Length - 1) { p.Type.Value = lastType; } else if (p.Type.Value != null) { var newType = p.Type.Value; p.Type.Value = lastType; lastType = newType; } slider.Path.ControlPoints.Insert(0, p); } } } return(true); }
private void scaleSlider(Slider slider, Vector2 scale) { referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type.Value).ToList(); Quad sliderQuad = GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); // Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0. scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size; Vector2 pathRelativeDeltaScale = new Vector2( sliderQuad.Width == 0 ? 0 : 1 + scale.X / sliderQuad.Width, sliderQuad.Height == 0 ? 0 : 1 + scale.Y / sliderQuad.Height); Queue <Vector2> oldControlPoints = new Queue <Vector2>(); foreach (var point in slider.Path.ControlPoints) { oldControlPoints.Enqueue(point.Position.Value); point.Position.Value *= pathRelativeDeltaScale; } // Maintain the path types in case they were defaulted to bezier at some point during scaling for (int i = 0; i < slider.Path.ControlPoints.Count; ++i) { slider.Path.ControlPoints[i].Type.Value = referencePathTypes[i]; } //if sliderhead or sliderend end up outside playfield, revert scaling. Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider }); (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); if (xInBounds && yInBounds && slider.Path.HasValidLength) { return; } foreach (var point in slider.Path.ControlPoints) { point.Position.Value = oldControlPoints.Dequeue(); } }
public override bool HandleScale(Vector2 scale, Anchor reference) { adjustScaleFromAnchor(ref scale, reference); var hitObjects = selectedMovableObjects; // for the time being, allow resizing of slider paths only if the slider is // the only hit object selected. with a group selection, it's likely the user // is not looking to change the duration of the slider but expand the whole pattern. if (hitObjects.Length == 1 && hitObjects.First() is Slider slider) { scaleSlider(slider, scale); } else { scaleHitObjects(hitObjects, reference, scale); } moveSelectionInBounds(); return(true); }
private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) { // cancel out scale in axes we don't care about (based on which drag handle was used). if ((reference & Anchor.x1) > 0) { scale.X = 0; } if ((reference & Anchor.y1) > 0) { scale.Y = 0; } // reverse the scale direction if dragging from top or left. if ((reference & Anchor.x0) > 0) { scale.X = -scale.X; } if ((reference & Anchor.y0) > 0) { scale.Y = -scale.Y; } }
/// <summary> /// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip. /// </summary> /// <param name="hitObjects">The hitobjects to be scaled</param> /// <param name="reference">The anchor from which the scale operation is performed</param> /// <param name="scale">The scale to be clamped</param> /// <returns>The clamped scale vector</returns> private Vector2 getClampedScale(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) { float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0; float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0; Quad selectionQuad = getSurroundingQuad(hitObjects); //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. Quad scaledQuad = new Quad(selectionQuad.TopLeft.X + xOffset, selectionQuad.TopLeft.Y + yOffset, selectionQuad.Width + scale.X, selectionQuad.Height + scale.Y); //max Size -> playfield bounds if (scaledQuad.TopLeft.X < 0) { scale.X += scaledQuad.TopLeft.X; } if (scaledQuad.TopLeft.Y < 0) { scale.Y += scaledQuad.TopLeft.Y; } if (scaledQuad.BottomRight.X > DrawWidth) { scale.X -= scaledQuad.BottomRight.X - DrawWidth; } if (scaledQuad.BottomRight.Y > DrawHeight) { scale.Y -= scaledQuad.BottomRight.Y - DrawHeight; } //min Size -> almost 0. Less than 0 causes the quad to flip, exactly 0 causes scaling to get stuck at minimum scale. Vector2 scaledSize = selectionQuad.Size + scale; Vector2 minSize = new Vector2(Precision.FLOAT_EPSILON); scale = Vector2.ComponentMax(minSize, scaledSize) - selectionQuad.Size; return(scale); }
public static SNVector2 ToSystemNumerics(this TKVector2 vec) => new SNVector2(vec.X, vec.Y);