Files
osu-framework/osu.Framework.Tests/Visual/Platform/WindowDisplaysPreview.cs
2025-12-06 17:22:05 +08:00

267 lines
10 KiB
C#

// 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.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Platform;
using osuTK;
using osuTK.Graphics;
namespace osu.Framework.Tests.Visual.Platform
{
public partial class WindowDisplaysPreview : Container
{
public const float FONT_SIZE = 120f;
private readonly TextFlowContainer windowCaption;
private readonly Container paddedContainer;
private readonly Container screenContainer;
private readonly Container windowContainer;
private readonly Container? borderContainer;
private Vector2 screenContainerOffset;
private static readonly Color4 active_fill = new Color4(255, 138, 104, 255);
private static readonly Color4 active_stroke = new Color4(244, 74, 25, 255);
private static readonly Color4 screen_fill = new Color4(255, 181, 104, 255);
private static readonly Color4 screen_stroke = new Color4(244, 137, 25, 255);
private static readonly Color4 window_fill = new Color4(95, 113, 197, 255);
private static readonly Color4 window_stroke = new Color4(36, 59, 166, 255);
private static readonly Color4 window_border_fill = new Color4(85, 207, 89, 200);
private static readonly Color4 window_border_stroke = new Color4(50, 122, 53, 255);
private IWindow? window;
private readonly Bindable<WindowMode> windowMode = new Bindable<WindowMode>();
private readonly Bindable<Display> currentDisplay = new Bindable<Display>();
private readonly bool showDisplayUsableBounds;
public WindowDisplaysPreview(bool showDisplayUsableBounds = false, bool showWindowBorder = false)
{
this.showDisplayUsableBounds = showDisplayUsableBounds;
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(70),
Child = paddedContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Child = screenContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Child = windowContainer = new Container
{
BorderColour = window_stroke,
BorderThickness = 20,
Masking = true,
Depth = -10,
Alpha = 0.7f,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = window_fill
},
windowCaption = new TextFlowContainer(sprite =>
{
sprite.Font = sprite.Font.With(size: FONT_SIZE);
sprite.Colour = Color4.White;
})
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(50),
Colour = Color4.White
}
}
}
}
}
};
if (showWindowBorder)
{
screenContainer.Add(borderContainer = new Container
{
BorderColour = window_border_stroke,
BorderThickness = 20,
Masking = true,
Depth = -5,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = window_border_fill
}
});
}
}
[BackgroundDependencyLoader]
private void load(FrameworkConfigManager config, GameHost host)
{
window = host.Window;
config.BindWith(FrameworkSetting.WindowMode, windowMode);
if (window != null)
{
window.DisplaysChanged += onDisplaysChanged;
currentDisplay.BindTo(window.CurrentDisplayBindable);
currentDisplay.BindValueChanged(_ => Scheduler.AddOnce(() => refreshScreens(window.Displays)));
refreshScreens(window.Displays);
}
}
private void onDisplaysChanged(IEnumerable<Display> displays)
{
Scheduler.AddOnce(refreshScreens, displays);
}
private void refreshScreens(IEnumerable<Display> displays)
{
screenContainer.RemoveAll(d => d != windowContainer && d != borderContainer, false);
var bounds = new RectangleI();
foreach (var display in displays)
{
screenContainer.AddRange(createScreen(display, window.AsNonNull().CurrentDisplayBindable.Value.Index));
bounds = RectangleI.Union(bounds, new RectangleI(display.Bounds.X, display.Bounds.Y, display.Bounds.Width, display.Bounds.Height));
}
screenContainerOffset = bounds.Location;
foreach (var box in screenContainer.Children)
{
box.Position -= bounds.Location;
}
screenContainer.Size = bounds.Size;
}
private IEnumerable<Container> createScreen(Display display, int activeDisplayIndex)
{
bool isActive = display.Index == activeDisplayIndex;
yield return new Container
{
X = display.Bounds.X,
Y = display.Bounds.Y,
Width = display.Bounds.Width,
Height = display.Bounds.Height,
BorderColour = isActive ? active_stroke : screen_stroke,
BorderThickness = 20,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = isActive ? active_fill : screen_fill
},
new TextFlowContainer(sprite =>
{
sprite.Font = new FontUsage(size: FONT_SIZE);
sprite.Colour = Color4.Black;
})
{
RelativeSizeAxes = Axes.Both,
Text = $"{display.Name}\n"
+ $"{display.Bounds.Width}x{display.Bounds.Height}\n"
+ $"Mode: {modeName(display.DisplayModes.FirstOrDefault())}",
Padding = new MarginPadding(50),
}
}
};
if (showDisplayUsableBounds)
{
yield return new Container
{
X = display.UsableBounds.X,
Y = display.UsableBounds.Y,
Width = display.UsableBounds.Width,
Height = display.UsableBounds.Height,
BorderColour = isActive ? Color4.MediumPurple : Color4.Green,
BorderThickness = 20,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = (isActive ? Color4.MediumPurple : Color4.Green).Opacity(0.2f)
},
}
};
}
}
private string modeName(DisplayMode mode) => $"{mode.Size.Width}x{mode.Size.Height}@{mode.RefreshRate}";
private void updateWindowContainer()
{
if (window == null) return;
bool fullscreen = window.WindowMode.Value == WindowMode.Fullscreen;
var currentBounds = window.CurrentDisplayBindable.Value.Bounds;
windowContainer.X = window.Position.X;
windowContainer.Y = window.Position.Y;
windowContainer.Width = fullscreen ? currentBounds.Width : window.Size.Width;
windowContainer.Height = fullscreen ? currentBounds.Height : window.Size.Height;
windowContainer.Position -= screenContainerOffset;
windowCaption.Text = $"{windowMode}\nSize: {window.Size.Width}x{window.Size.Height}\nClient: {window.ClientSize.Width}x{window.ClientSize.Height}";
if (borderContainer != null)
{
var borderSize = window.BorderSize.Value;
borderContainer.X = window.Position.X - borderSize.Left;
borderContainer.Y = window.Position.Y - borderSize.Top;
borderContainer.Width = windowContainer.Width + borderSize.TotalHorizontal;
borderContainer.Height = windowContainer.Height + borderSize.TotalVertical;
borderContainer.Position -= screenContainerOffset;
}
}
protected override void Update()
{
base.Update();
if (window == null)
return;
updateWindowContainer();
var scale = Vector2.Divide(paddedContainer.DrawSize, screenContainer.Size);
screenContainer.Scale = new Vector2(Math.Min(scale.X, scale.Y));
}
protected override void Dispose(bool isDisposing)
{
if (window != null)
window.DisplaysChanged -= onDisplaysChanged;
base.Dispose(isDisposing);
}
}
}