// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Layout; using osuTK; namespace osu.Game.Graphics.Containers { /// /// A container that reverts any rotation (and optionally scale) applied by its direct parent. /// public class UprightAspectMaintainingContainer : Container { protected override Container Content { get; } /// /// Controls how much this container scales compared to its parent (default is 1.0f). /// public float ScalingFactor { get; set; } = 1; /// /// Controls the scaling of this container. /// public ScaleMode Scaling { get; set; } = ScaleMode.Vertical; private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); public UprightAspectMaintainingContainer() { AddInternal(Content = new GrowToFitContainer()); AddLayout(layout); } protected override void Update() { base.Update(); if (!layout.IsValid) { keepUprightAndUnstretched(); layout.Validate(); } } /// /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. /// private void keepUprightAndUnstretched() { // Decomposes the inverse of the parent DrawInfo.Matrix into rotation, shear and scale. var parentMatrix = Parent.DrawInfo.Matrix; // Remove Translation. parentMatrix.M31 = 0.0f; parentMatrix.M32 = 0.0f; Matrix3 reversedParrent = parentMatrix.Inverted(); // Extract the rotation. float angle = MathF.Atan2(reversedParrent.M12, reversedParrent.M11); Rotation = MathHelper.RadiansToDegrees(angle); // Remove rotation from the C matrix so that it only contains shear and scale. Matrix3 m = Matrix3.CreateRotationZ(-angle); reversedParrent *= m; // Extract shear. float alpha = reversedParrent.M21 / reversedParrent.M22; Shear = new Vector2(-alpha, 0); // Etract scale. float sx = reversedParrent.M11; float sy = reversedParrent.M22; Vector3 parentScale = parentMatrix.ExtractScale(); float usedScale = 1.0f; switch (Scaling) { case ScaleMode.Horizontal: usedScale = parentScale.X; break; case ScaleMode.Vertical: usedScale = parentScale.Y; break; } usedScale = 1.0f + (usedScale - 1.0f) * ScalingFactor; Scale = new Vector2(sx * usedScale, sy * usedScale); } /// /// A container that grows in size to fit its children and retains its size when its children shrink /// private class GrowToFitContainer : Container { protected override Container Content => content; private readonly Container content; public GrowToFitContainer() { InternalChild = content = new Container { AutoSizeAxes = Axes.Both, }; } protected override void Update() { base.Update(); Height = Math.Max(content.Height, Height); Width = Math.Max(content.Width, Width); } } } public enum ScaleMode { /// /// Prevent this container from scaling. /// NoScaling, /// /// Scale uniformly (maintaining aspect ratio) based on the vertical scale of the parent. /// Vertical, /// /// Scale uniformly (maintaining aspect ratio) based on the horizontal scale of the parent. /// Horizontal, } }