mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 07:33:55 +09:00
Merge branch 'master' into fix-pause-gameplay-action-not-closing
This commit is contained in:
@ -25,8 +25,6 @@ namespace osu.Game.Graphics.Containers
|
||||
protected virtual string PopInSampleName => "UI/overlay-pop-in";
|
||||
protected virtual string PopOutSampleName => "UI/overlay-pop-out";
|
||||
|
||||
protected override bool BlockScrollInput => false;
|
||||
|
||||
protected override bool BlockNonPositionalInput => true;
|
||||
|
||||
/// <summary>
|
||||
@ -90,6 +88,15 @@ namespace osu.Game.Graphics.Containers
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e)
|
||||
{
|
||||
// allow for controlling volume when alt is held.
|
||||
// mostly for compatibility with osu-stable.
|
||||
if (e.AltPressed) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (e.Repeat)
|
||||
|
@ -4,6 +4,7 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
@ -117,11 +118,11 @@ namespace osu.Game.Graphics
|
||||
|
||||
host.GetClipboard()?.SetImage(image);
|
||||
|
||||
string filename = getFilename();
|
||||
(string filename, var stream) = getWritableStream();
|
||||
|
||||
if (filename == null) return;
|
||||
|
||||
using (var stream = storage.CreateFileSafely(filename))
|
||||
using (stream)
|
||||
{
|
||||
switch (screenshotFormat.Value)
|
||||
{
|
||||
@ -142,7 +143,7 @@ namespace osu.Game.Graphics
|
||||
|
||||
notificationOverlay.Post(new SimpleNotification
|
||||
{
|
||||
Text = $"{filename} saved!",
|
||||
Text = $"Screenshot {filename} saved!",
|
||||
Activated = () =>
|
||||
{
|
||||
storage.PresentFileExternally(filename);
|
||||
@ -152,23 +153,28 @@ namespace osu.Game.Graphics
|
||||
}
|
||||
});
|
||||
|
||||
private string getFilename()
|
||||
private static readonly object filename_reservation_lock = new object();
|
||||
|
||||
private (string filename, Stream stream) getWritableStream()
|
||||
{
|
||||
var dt = DateTime.Now;
|
||||
string fileExt = screenshotFormat.ToString().ToLowerInvariant();
|
||||
|
||||
string withoutIndex = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}.{fileExt}";
|
||||
if (!storage.Exists(withoutIndex))
|
||||
return withoutIndex;
|
||||
|
||||
for (ulong i = 1; i < ulong.MaxValue; i++)
|
||||
lock (filename_reservation_lock)
|
||||
{
|
||||
string indexedName = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}-{i}.{fileExt}";
|
||||
if (!storage.Exists(indexedName))
|
||||
return indexedName;
|
||||
}
|
||||
var dt = DateTime.Now;
|
||||
string fileExt = screenshotFormat.ToString().ToLowerInvariant();
|
||||
|
||||
return null;
|
||||
string withoutIndex = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}.{fileExt}";
|
||||
if (!storage.Exists(withoutIndex))
|
||||
return (withoutIndex, storage.GetStream(withoutIndex, FileAccess.Write, FileMode.Create));
|
||||
|
||||
for (ulong i = 1; i < ulong.MaxValue; i++)
|
||||
{
|
||||
string indexedName = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}-{i}.{fileExt}";
|
||||
if (!storage.Exists(indexedName))
|
||||
return (indexedName, storage.GetStream(indexedName, FileAccess.Write, FileMode.Create));
|
||||
}
|
||||
|
||||
return (null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
@ -13,12 +11,12 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public readonly MenuItemType Type;
|
||||
|
||||
public OsuMenuItem(string text, MenuItemType type = MenuItemType.Standard)
|
||||
public OsuMenuItem(LocalisableString text, MenuItemType type = MenuItemType.Standard)
|
||||
: this(text, type, null)
|
||||
{
|
||||
}
|
||||
|
||||
public OsuMenuItem(LocalisableString text, MenuItemType type, Action action)
|
||||
public OsuMenuItem(LocalisableString text, MenuItemType type, Action? action)
|
||||
: base(text, action)
|
||||
{
|
||||
Type = type;
|
||||
|
337
osu.Game/Graphics/UserInterface/SegmentedGraph.cs
Normal file
337
osu.Game/Graphics/UserInterface/SegmentedGraph.cs
Normal file
@ -0,0 +1,337 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Shaders;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public partial class SegmentedGraph<T> : Drawable
|
||||
where T : struct, IComparable<T>, IConvertible, IEquatable<T>
|
||||
{
|
||||
private bool graphNeedsUpdate;
|
||||
|
||||
private T[]? values;
|
||||
private int[] tiers = Array.Empty<int>();
|
||||
private readonly SegmentManager segments;
|
||||
|
||||
private int tierCount;
|
||||
|
||||
public SegmentedGraph(int tierCount = 1)
|
||||
{
|
||||
this.tierCount = tierCount;
|
||||
tierColours = new[]
|
||||
{
|
||||
new Colour4(0, 0, 0, 0)
|
||||
};
|
||||
segments = new SegmentManager(tierCount);
|
||||
}
|
||||
|
||||
public T[] Values
|
||||
{
|
||||
get => values ?? Array.Empty<T>();
|
||||
set
|
||||
{
|
||||
if (value == values) return;
|
||||
|
||||
values = value;
|
||||
graphNeedsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
private Colour4[] tierColours;
|
||||
|
||||
public Colour4[] TierColours
|
||||
{
|
||||
get => tierColours;
|
||||
set
|
||||
{
|
||||
if (value.Length == 0 || value == tierColours)
|
||||
return;
|
||||
|
||||
tierCount = value.Length;
|
||||
tierColours = value;
|
||||
|
||||
graphNeedsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
private Texture texture = null!;
|
||||
private IShader shader = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IRenderer renderer, ShaderManager shaders)
|
||||
{
|
||||
texture = renderer.WhitePixel;
|
||||
shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (graphNeedsUpdate)
|
||||
{
|
||||
recalculateTiers(values);
|
||||
recalculateSegments();
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
graphNeedsUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void recalculateTiers(T[]? arr)
|
||||
{
|
||||
if (arr == null || arr.Length == 0)
|
||||
{
|
||||
tiers = Array.Empty<int>();
|
||||
return;
|
||||
}
|
||||
|
||||
float[] floatValues = arr.Select(v => Convert.ToSingle(v)).ToArray();
|
||||
|
||||
// Shift values to eliminate negative ones
|
||||
float min = floatValues.Min();
|
||||
|
||||
if (min < 0)
|
||||
{
|
||||
for (int i = 0; i < floatValues.Length; i++)
|
||||
floatValues[i] += Math.Abs(min);
|
||||
}
|
||||
|
||||
// Normalize values
|
||||
float max = floatValues.Max();
|
||||
|
||||
for (int i = 0; i < floatValues.Length; i++)
|
||||
floatValues[i] /= max;
|
||||
|
||||
// Deduce tiers from values
|
||||
tiers = floatValues.Select(v => (int)Math.Floor(v * tierCount)).ToArray();
|
||||
}
|
||||
|
||||
private void recalculateSegments()
|
||||
{
|
||||
segments.Clear();
|
||||
|
||||
if (tiers.Length == 0)
|
||||
{
|
||||
segments.Add(0, 0, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < tiers.Length; i++)
|
||||
{
|
||||
for (int tier = 0; tier < tierCount; tier++)
|
||||
{
|
||||
if (tier < 0)
|
||||
continue;
|
||||
|
||||
// One tier covers itself and all tiers above it.
|
||||
// By layering multiple transparent boxes, higher tiers will be brighter.
|
||||
// If using opaque colors, higher tiers will be on front, covering lower tiers.
|
||||
if (tiers[i] >= tier)
|
||||
{
|
||||
if (!segments.IsTierStarted(tier))
|
||||
segments.StartSegment(tier, i * 1f / tiers.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (segments.IsTierStarted(tier))
|
||||
segments.EndSegment(tier, i * 1f / tiers.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
segments.EndAllPendingSegments();
|
||||
segments.Sort();
|
||||
}
|
||||
|
||||
private Colour4 getTierColour(int tier) => tier >= 0 ? tierColours[tier] : new Colour4(0, 0, 0, 0);
|
||||
|
||||
protected override DrawNode CreateDrawNode() => new SegmentedGraphDrawNode(this);
|
||||
|
||||
protected struct SegmentInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The tier this segment is at.
|
||||
/// </summary>
|
||||
public int Tier;
|
||||
|
||||
/// <summary>
|
||||
/// The progress at which this segment starts.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is a normalized float (from 0 to 1).
|
||||
/// </remarks>
|
||||
public float Start;
|
||||
|
||||
/// <summary>
|
||||
/// The progress at which this segment ends.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is a normalized float (from 0 to 1).
|
||||
/// </remarks>
|
||||
public float End;
|
||||
|
||||
/// <summary>
|
||||
/// The length of this segment.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is a normalized float (from 0 to 1).
|
||||
/// </remarks>
|
||||
public float Length => End - Start;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"({Tier}, {Start * 100}%, {End * 100}%)";
|
||||
}
|
||||
}
|
||||
|
||||
private class SegmentedGraphDrawNode : DrawNode
|
||||
{
|
||||
public new SegmentedGraph<T> Source => (SegmentedGraph<T>)base.Source;
|
||||
|
||||
private Texture texture = null!;
|
||||
private IShader shader = null!;
|
||||
private readonly List<SegmentInfo> segments = new List<SegmentInfo>();
|
||||
private Vector2 drawSize;
|
||||
|
||||
public SegmentedGraphDrawNode(SegmentedGraph<T> source)
|
||||
: base(source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ApplyState()
|
||||
{
|
||||
base.ApplyState();
|
||||
|
||||
texture = Source.texture;
|
||||
shader = Source.shader;
|
||||
drawSize = Source.DrawSize;
|
||||
segments.Clear();
|
||||
segments.AddRange(Source.segments.Where(s => s.Length * drawSize.X > 1));
|
||||
}
|
||||
|
||||
public override void Draw(IRenderer renderer)
|
||||
{
|
||||
base.Draw(renderer);
|
||||
|
||||
shader.Bind();
|
||||
|
||||
foreach (SegmentInfo segment in segments)
|
||||
{
|
||||
Vector2 topLeft = new Vector2(segment.Start * drawSize.X, 0);
|
||||
Vector2 topRight = new Vector2(segment.End * drawSize.X, 0);
|
||||
Vector2 bottomLeft = new Vector2(segment.Start * drawSize.X, drawSize.Y);
|
||||
Vector2 bottomRight = new Vector2(segment.End * drawSize.X, drawSize.Y);
|
||||
|
||||
renderer.DrawQuad(
|
||||
texture,
|
||||
new Quad(
|
||||
Vector2Extensions.Transform(topLeft, DrawInfo.Matrix),
|
||||
Vector2Extensions.Transform(topRight, DrawInfo.Matrix),
|
||||
Vector2Extensions.Transform(bottomLeft, DrawInfo.Matrix),
|
||||
Vector2Extensions.Transform(bottomRight, DrawInfo.Matrix)),
|
||||
Source.getTierColour(segment.Tier));
|
||||
}
|
||||
|
||||
shader.Unbind();
|
||||
}
|
||||
}
|
||||
|
||||
protected class SegmentManager : IEnumerable<SegmentInfo>
|
||||
{
|
||||
private readonly List<SegmentInfo> segments = new List<SegmentInfo>();
|
||||
|
||||
private readonly SegmentInfo?[] pendingSegments;
|
||||
|
||||
public SegmentManager(int tierCount)
|
||||
{
|
||||
pendingSegments = new SegmentInfo?[tierCount];
|
||||
}
|
||||
|
||||
public void StartSegment(int tier, float start)
|
||||
{
|
||||
if (pendingSegments[tier] != null)
|
||||
throw new InvalidOperationException($"Another {nameof(SegmentInfo)} of tier {tier.ToString()} has already been started.");
|
||||
|
||||
pendingSegments[tier] = new SegmentInfo
|
||||
{
|
||||
Tier = tier,
|
||||
Start = Math.Clamp(start, 0, 1)
|
||||
};
|
||||
}
|
||||
|
||||
public void EndSegment(int tier, float end)
|
||||
{
|
||||
SegmentInfo? pendingSegment = pendingSegments[tier];
|
||||
if (pendingSegment == null)
|
||||
throw new InvalidOperationException($"Cannot end {nameof(SegmentInfo)} of tier {tier.ToString()} that has not been started.");
|
||||
|
||||
SegmentInfo segment = pendingSegment.Value;
|
||||
segment.End = Math.Clamp(end, 0, 1);
|
||||
segments.Add(segment);
|
||||
pendingSegments[tier] = null;
|
||||
}
|
||||
|
||||
public void EndAllPendingSegments()
|
||||
{
|
||||
foreach (SegmentInfo? pendingSegment in pendingSegments)
|
||||
{
|
||||
if (pendingSegment == null)
|
||||
continue;
|
||||
|
||||
SegmentInfo finalizedSegment = pendingSegment.Value;
|
||||
finalizedSegment.End = 1;
|
||||
segments.Add(finalizedSegment);
|
||||
}
|
||||
}
|
||||
|
||||
public void Sort() =>
|
||||
segments.Sort((a, b) =>
|
||||
a.Tier != b.Tier
|
||||
? a.Tier.CompareTo(b.Tier)
|
||||
: a.Start.CompareTo(b.Start));
|
||||
|
||||
public void Add(SegmentInfo segment) => segments.Add(segment);
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
segments.Clear();
|
||||
|
||||
for (int i = 0; i < pendingSegments.Length; i++)
|
||||
pendingSegments[i] = null;
|
||||
}
|
||||
|
||||
public int Count => segments.Count;
|
||||
|
||||
public void Add(int tier, float start, float end)
|
||||
{
|
||||
SegmentInfo segment = new SegmentInfo
|
||||
{
|
||||
Tier = tier,
|
||||
Start = Math.Clamp(start, 0, 1),
|
||||
End = Math.Clamp(end, 0, 1)
|
||||
};
|
||||
|
||||
if (segment.Start > segment.End)
|
||||
throw new InvalidOperationException("Segment start cannot be after segment end.");
|
||||
|
||||
Add(segment);
|
||||
}
|
||||
|
||||
public bool IsTierStarted(int tier) => tier >= 0 && pendingSegments[tier].HasValue;
|
||||
|
||||
public IEnumerator<SegmentInfo> GetEnumerator() => segments.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
@ -25,7 +24,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// <param name="text">The text to display.</param>
|
||||
/// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param>
|
||||
/// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param>
|
||||
protected StatefulMenuItem(string text, Func<object, object> changeStateFunc, MenuItemType type = MenuItemType.Standard)
|
||||
protected StatefulMenuItem(LocalisableString text, Func<object, object> changeStateFunc, MenuItemType type = MenuItemType.Standard)
|
||||
: this(text, changeStateFunc, type, null)
|
||||
{
|
||||
}
|
||||
@ -37,7 +36,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param>
|
||||
/// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param>
|
||||
/// <param name="action">A delegate to be invoked when this <see cref="StatefulMenuItem"/> is pressed.</param>
|
||||
protected StatefulMenuItem(string text, Func<object, object> changeStateFunc, MenuItemType type, Action<object> action)
|
||||
protected StatefulMenuItem(LocalisableString text, Func<object, object>? changeStateFunc, MenuItemType type, Action<object>? action)
|
||||
: base(text, type)
|
||||
{
|
||||
Action.Value = () =>
|
||||
@ -69,7 +68,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// <param name="text">The text to display.</param>
|
||||
/// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param>
|
||||
/// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param>
|
||||
protected StatefulMenuItem(string text, Func<T, T> changeStateFunc, MenuItemType type = MenuItemType.Standard)
|
||||
protected StatefulMenuItem(LocalisableString text, Func<T, T>? changeStateFunc, MenuItemType type = MenuItemType.Standard)
|
||||
: this(text, changeStateFunc, type, null)
|
||||
{
|
||||
}
|
||||
@ -81,7 +80,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="StatefulMenuItem"/> is pressed.</param>
|
||||
/// <param name="type">The type of action which this <see cref="StatefulMenuItem"/> performs.</param>
|
||||
/// <param name="action">A delegate to be invoked when this <see cref="StatefulMenuItem"/> is pressed.</param>
|
||||
protected StatefulMenuItem(string text, Func<T, T> changeStateFunc, MenuItemType type, Action<T> action)
|
||||
protected StatefulMenuItem(LocalisableString text, Func<T, T>? changeStateFunc, MenuItemType type, Action<T>? action)
|
||||
: base(text, o => changeStateFunc?.Invoke((T)o) ?? o, type, o => action?.Invoke((T)o))
|
||||
{
|
||||
base.State.BindValueChanged(state =>
|
||||
|
@ -1,10 +1,9 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
@ -18,7 +17,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// </summary>
|
||||
/// <param name="text">The text to display.</param>
|
||||
/// <param name="type">The type of action which this <see cref="ToggleMenuItem"/> performs.</param>
|
||||
public ToggleMenuItem(string text, MenuItemType type = MenuItemType.Standard)
|
||||
public ToggleMenuItem(LocalisableString text, MenuItemType type = MenuItemType.Standard)
|
||||
: this(text, type, null)
|
||||
{
|
||||
}
|
||||
@ -29,7 +28,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// <param name="text">The text to display.</param>
|
||||
/// <param name="type">The type of action which this <see cref="ToggleMenuItem"/> performs.</param>
|
||||
/// <param name="action">A delegate to be invoked when this <see cref="ToggleMenuItem"/> is pressed.</param>
|
||||
public ToggleMenuItem(string text, MenuItemType type, Action<bool> action)
|
||||
public ToggleMenuItem(LocalisableString text, MenuItemType type, Action<bool>? action)
|
||||
: base(text, value => !value, type, action)
|
||||
{
|
||||
}
|
||||
|
Reference in New Issue
Block a user