Viewport improvements (#3765)
This commit is contained in:
committed by
GitHub
parent
8e2fc49357
commit
147a54c642
@@ -29,11 +29,14 @@ namespace Content.Client.UserInterface
|
||||
public readonly Button CloseButton;
|
||||
public readonly Button SaveButton;
|
||||
|
||||
public CharacterSetupGui(IEntityManager entityManager,
|
||||
public CharacterSetupGui(
|
||||
IEntityManager entityManager,
|
||||
IResourceCache resourceCache,
|
||||
IClientPreferencesManager preferencesManager,
|
||||
IPrototypeManager prototypeManager)
|
||||
{
|
||||
AddChild(new ParallaxControl());
|
||||
|
||||
_entityManager = entityManager;
|
||||
_preferencesManager = preferencesManager;
|
||||
var margin = new Control
|
||||
|
||||
53
Content.Client/UserInterface/LauncherConnectingGui.xaml
Normal file
53
Content.Client/UserInterface/LauncherConnectingGui.xaml
Normal file
@@ -0,0 +1,53 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:cuic="clr-namespace:Content.Client.UserInterface">
|
||||
<cuic:ParallaxControl />
|
||||
<Control HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<PanelContainer StyleClasses="AngleRect" />
|
||||
<VBoxContainer MinSize="300 200">
|
||||
<HBoxContainer>
|
||||
<Label Margin="8 0 0 0" Text="{Loc 'connecting-title'}"
|
||||
StyleClasses="LabelHeading" VAlign="Center" />
|
||||
<Button Name="ExitButton" Text="{Loc 'connecting-exit'}"
|
||||
HorizontalAlignment="Right" HorizontalExpand="True" />
|
||||
</HBoxContainer>
|
||||
<cui:HighDivider />
|
||||
<VBoxContainer VerticalExpand="True" Margin="4 4 4 0">
|
||||
<Control VerticalExpand="True" Margin="0 0 0 8">
|
||||
<VBoxContainer Name="ConnectingStatus">
|
||||
<Label Text="{Loc 'connecting-in-progress'}" Align="Center" />
|
||||
<!-- Who the fuck named these cont- oh wait I did -->
|
||||
<Label Name="ConnectStatus" StyleClasses="LabelSubText" Align="Center" />
|
||||
</VBoxContainer>
|
||||
<VBoxContainer Name="ConnectFail" Visible="False">
|
||||
<Label Name="ConnectFailReason" Align="Center" />
|
||||
<Button Name="RetryButton" Text="{Loc 'connecting-retry'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalExpand="True" VerticalAlignment="Bottom" />
|
||||
</VBoxContainer>
|
||||
<VBoxContainer Name="Disconnected">
|
||||
<Label Text="{Loc 'connecting-disconnected'}" Align="Center" />
|
||||
<Label Name="DisconnectReason" Align="Center" />
|
||||
<Button Name="ReconnectButton" Text="{Loc 'connecting-reconnect'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalExpand="True" VerticalAlignment="Bottom" />
|
||||
</VBoxContainer>
|
||||
</Control>
|
||||
<Label Name="ConnectingAddress" StyleClasses="LabelSubText" HorizontalAlignment="Center" />
|
||||
</VBoxContainer>
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#444" ContentMarginTopOverride="2" />
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
|
||||
<HBoxContainer Margin="12 0 4 0" VerticalAlignment="Bottom">
|
||||
<Label Text="{Loc 'connecting-tip'}" StyleClasses="LabelSubText" />
|
||||
<Label Text="{Loc 'connecting-version'}" StyleClasses="LabelSubText"
|
||||
HorizontalAlignment="Right" HorizontalExpand="True" />
|
||||
</HBoxContainer>
|
||||
</VBoxContainer>
|
||||
</Control>
|
||||
</Control>
|
||||
62
Content.Client/UserInterface/LauncherConnectingGui.xaml.cs
Normal file
62
Content.Client/UserInterface/LauncherConnectingGui.xaml.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using Content.Client.State;
|
||||
using Content.Client.UserInterface.Stylesheets;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Client.UserInterface
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class LauncherConnectingGui : Control
|
||||
{
|
||||
private readonly LauncherConnecting _state;
|
||||
|
||||
public LauncherConnectingGui(LauncherConnecting state)
|
||||
{
|
||||
_state = state;
|
||||
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide);
|
||||
|
||||
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
|
||||
|
||||
ReconnectButton.OnPressed += _ => _state.RetryConnect();
|
||||
RetryButton.OnPressed += _ => _state.RetryConnect();
|
||||
ExitButton.OnPressed += _ => _state.Exit();
|
||||
|
||||
var addr = state.Address;
|
||||
if (addr != null)
|
||||
ConnectingAddress.Text = addr;
|
||||
|
||||
state.PageChanged += OnPageChanged;
|
||||
state.ConnectFailReasonChanged += ConnectFailReasonChanged;
|
||||
state.ConnectionStateChanged += ConnectionStateChanged;
|
||||
|
||||
ConnectionStateChanged(state.ConnectionState);
|
||||
}
|
||||
|
||||
private void ConnectFailReasonChanged(string? reason)
|
||||
{
|
||||
ConnectFailReason.Text = reason == null
|
||||
? null
|
||||
: Loc.GetString("connecting-fail-reason", ("reason", reason));
|
||||
}
|
||||
|
||||
private void OnPageChanged(LauncherConnecting.Page page)
|
||||
{
|
||||
ConnectingStatus.Visible = page == LauncherConnecting.Page.Connecting;
|
||||
ConnectFail.Visible = page == LauncherConnecting.Page.ConnectFailed;
|
||||
Disconnected.Visible = page == LauncherConnecting.Page.Disconnected;
|
||||
}
|
||||
|
||||
private void ConnectionStateChanged(ClientConnectionState state)
|
||||
{
|
||||
ConnectStatus.Text = Loc.GetString($"connecting-state-{state}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,82 +7,88 @@
|
||||
xmlns:maths="clr-namespace:Robust.Shared.Maths;assembly=Robust.Shared.Maths"
|
||||
xmlns:voting="clr-namespace:Content.Client.Voting">
|
||||
|
||||
<!-- One day I'll code a Margin property for controls. -->
|
||||
<MarginContainer MarginBottomOverride="20" MarginLeftOverride="20" MarginRightOverride="20"
|
||||
MarginTopOverride="20">
|
||||
<PanelContainer StyleClasses="AngleRect" />
|
||||
<VBoxContainer>
|
||||
<!-- Top row -->
|
||||
<HBoxContainer MinSize="0 40">
|
||||
<MarginContainer MarginLeftOverride="8">
|
||||
<Label StyleClasses="LabelHeadingBigger" VAlign="Center" Text="{Loc 'Lobby'}" />
|
||||
</MarginContainer>
|
||||
<Label Name="CServerName" StyleClasses="LabelHeadingBigger" VAlign="Center" />
|
||||
<voting:VoteCallMenuButton Name="CCallVoteButton" StyleClasses="ButtonBig" />
|
||||
<Button Name="COptionsButton" StyleClasses="ButtonBig" Text="{Loc 'Options'}" />
|
||||
<Button Name="CLeaveButton" StyleClasses="ButtonBig" Text="{Loc 'Leave'}" />
|
||||
</HBoxContainer>
|
||||
<!-- Gold line -->
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="{x:Static style:StyleNano.NanoGold}"
|
||||
ContentMarginTopOverride="2" />
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<!-- Middle section with the two vertical panels -->
|
||||
<HBoxContainer VerticalExpand="True">
|
||||
<!-- Left panel -->
|
||||
<VBoxContainer Name="CLeftPanelContainer" HorizontalExpand="True">
|
||||
<cui:StripeBack>
|
||||
<MarginContainer MarginLeftOverride="3" MarginRightOverride="3" MarginBottomOverride="3"
|
||||
MarginTopOverride="3">
|
||||
<HBoxContainer SeparationOverride="6">
|
||||
<Button Name="CObserveButton" Text="{Loc 'Observe'}" StyleClasses="ButtonBig" />
|
||||
<Label Name="CStartTime" Align="Right"
|
||||
FontColorOverride="{x:Static maths:Color.DarkGray}"
|
||||
StyleClasses="LabelBig" HorizontalExpand="True" />
|
||||
<Button Name="CReadyButton" ToggleMode="True" Text="{Loc 'Ready Up'}"
|
||||
StyleClasses="ButtonBig" />
|
||||
</HBoxContainer>
|
||||
</MarginContainer>
|
||||
</cui:StripeBack>
|
||||
<MarginContainer VerticalExpand="True" MarginLeftOverride="3" MarginRightOverride="3"
|
||||
MarginBottomOverride="3"
|
||||
MarginTopOverride="3">
|
||||
<chat:ChatBox Name="CChat" />
|
||||
<Control>
|
||||
<!-- Parallax background -->
|
||||
<cui:ParallaxControl />
|
||||
|
||||
<!-- One day I'll code a Margin property for controls. -->
|
||||
<MarginContainer MarginBottomOverride="20" MarginLeftOverride="20" MarginRightOverride="20"
|
||||
MarginTopOverride="20">
|
||||
<PanelContainer StyleClasses="AngleRect" />
|
||||
<VBoxContainer>
|
||||
<!-- Top row -->
|
||||
<HBoxContainer MinSize="0 40">
|
||||
<MarginContainer MarginLeftOverride="8">
|
||||
<Label StyleClasses="LabelHeadingBigger" VAlign="Center" Text="{Loc 'Lobby'}" />
|
||||
</MarginContainer>
|
||||
</VBoxContainer>
|
||||
<Label Name="CServerName" StyleClasses="LabelHeadingBigger" VAlign="Center" />
|
||||
<voting:VoteCallMenuButton Name="CCallVoteButton" StyleClasses="ButtonBig" />
|
||||
<Button Name="COptionsButton" StyleClasses="ButtonBig" Text="{Loc 'Options'}" />
|
||||
<Button Name="CLeaveButton" StyleClasses="ButtonBig" Text="{Loc 'Leave'}" />
|
||||
</HBoxContainer>
|
||||
<!-- Gold line -->
|
||||
<PanelContainer MinSize="2 0">
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="{x:Static style:StyleNano.NanoGold}" />
|
||||
<gfx:StyleBoxFlat BackgroundColor="{x:Static style:StyleNano.NanoGold}"
|
||||
ContentMarginTopOverride="2" />
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<!-- Right panel -->
|
||||
<Control HorizontalExpand="True">
|
||||
<VBoxContainer>
|
||||
<!-- Player list -->
|
||||
<cui:NanoHeading Text="{Loc 'Online Players'}" />
|
||||
<MarginContainer VerticalExpand="True"
|
||||
MarginRightOverride="3" MarginLeftOverride="3"
|
||||
MarginBottomOverride="3" MarginTopOverride="3">
|
||||
<cui:LobbyPlayerList Name="COnlinePlayerList"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True" />
|
||||
</MarginContainer>
|
||||
<!-- Server info -->
|
||||
<cui:NanoHeading Text="{Loc 'Server Info'}" />
|
||||
<MarginContainer VerticalExpand="True"
|
||||
MarginRightOverride="3" MarginLeftOverride="3"
|
||||
MarginBottomOverride="2" MarginTopOverride="3">
|
||||
<cui:ServerInfo Name="CServerInfo" />
|
||||
<!-- Middle section with the two vertical panels -->
|
||||
<HBoxContainer VerticalExpand="True">
|
||||
<!-- Left panel -->
|
||||
<VBoxContainer Name="CLeftPanelContainer" HorizontalExpand="True">
|
||||
<cui:StripeBack>
|
||||
<MarginContainer MarginLeftOverride="3" MarginRightOverride="3" MarginBottomOverride="3"
|
||||
MarginTopOverride="3">
|
||||
<HBoxContainer SeparationOverride="6">
|
||||
<Button Name="CObserveButton" Text="{Loc 'Observe'}" StyleClasses="ButtonBig" />
|
||||
<Label Name="CStartTime" Align="Right"
|
||||
FontColorOverride="{x:Static maths:Color.DarkGray}"
|
||||
StyleClasses="LabelBig" HorizontalExpand="True" />
|
||||
<Button Name="CReadyButton" ToggleMode="True" Text="{Loc 'Ready Up'}"
|
||||
StyleClasses="ButtonBig" />
|
||||
</HBoxContainer>
|
||||
</MarginContainer>
|
||||
</cui:StripeBack>
|
||||
<MarginContainer VerticalExpand="True" MarginLeftOverride="3" MarginRightOverride="3"
|
||||
MarginBottomOverride="3"
|
||||
MarginTopOverride="3">
|
||||
<chat:ChatBox Name="CChat" />
|
||||
</MarginContainer>
|
||||
</VBoxContainer>
|
||||
<MarginContainer SizeFlagsHorizontal="ShrinkEnd" MarginTopOverride="8" MarginRightOverride="8">
|
||||
<VBoxContainer Name="CVoteContainer" />
|
||||
</MarginContainer>
|
||||
</Control>
|
||||
</HBoxContainer>
|
||||
</VBoxContainer>
|
||||
</MarginContainer>
|
||||
<!-- Gold line -->
|
||||
<PanelContainer MinSize="2 0">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="{x:Static style:StyleNano.NanoGold}" />
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<!-- Right panel -->
|
||||
<Control HorizontalExpand="True">
|
||||
<VBoxContainer>
|
||||
<!-- Player list -->
|
||||
<cui:NanoHeading Text="{Loc 'Online Players'}" />
|
||||
<MarginContainer VerticalExpand="True"
|
||||
MarginRightOverride="3" MarginLeftOverride="3"
|
||||
MarginBottomOverride="3" MarginTopOverride="3">
|
||||
<cui:LobbyPlayerList Name="COnlinePlayerList"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True" />
|
||||
</MarginContainer>
|
||||
<!-- Server info -->
|
||||
<cui:NanoHeading Text="{Loc 'Server Info'}" />
|
||||
<MarginContainer VerticalExpand="True"
|
||||
MarginRightOverride="3" MarginLeftOverride="3"
|
||||
MarginBottomOverride="2" MarginTopOverride="3">
|
||||
<cui:ServerInfo Name="CServerInfo" />
|
||||
</MarginContainer>
|
||||
</VBoxContainer>
|
||||
<MarginContainer SizeFlagsHorizontal="ShrinkEnd" MarginTopOverride="8" MarginRightOverride="8">
|
||||
<VBoxContainer Name="CVoteContainer" />
|
||||
</MarginContainer>
|
||||
</Control>
|
||||
</HBoxContainer>
|
||||
</VBoxContainer>
|
||||
</MarginContainer>
|
||||
|
||||
</Control>
|
||||
</Control>
|
||||
|
||||
153
Content.Client/UserInterface/MainViewport.cs
Normal file
153
Content.Client/UserInterface/MainViewport.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using Content.Shared;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.UserInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper for <see cref="ScalingViewport"/> that listens to configuration variables.
|
||||
/// Also does NN-snapping within tolerances.
|
||||
/// </summary>
|
||||
public sealed class MainViewport : Control
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly ViewportManager _vpManager = default!;
|
||||
|
||||
public ScalingViewport Viewport { get; }
|
||||
|
||||
public MainViewport()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
Viewport = new ScalingViewport
|
||||
{
|
||||
AlwaysRender = true,
|
||||
RenderScaleMode = ScalingViewportRenderScaleMode.CeilInt,
|
||||
MouseFilter = MouseFilterMode.Stop
|
||||
};
|
||||
|
||||
AddChild(Viewport);
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
{
|
||||
base.EnteredTree();
|
||||
|
||||
_vpManager.AddViewport(this);
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
|
||||
_vpManager.RemoveViewport(this);
|
||||
}
|
||||
|
||||
public void UpdateCfg()
|
||||
{
|
||||
var stretch = _cfg.GetCVar(CCVars.ViewportStretch);
|
||||
var renderScaleUp = _cfg.GetCVar(CCVars.ViewportScaleRender);
|
||||
var fixedFactor = _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
|
||||
|
||||
if (stretch)
|
||||
{
|
||||
var snapFactor = CalcSnappingFactor();
|
||||
if (snapFactor == null)
|
||||
{
|
||||
// Did not find a snap, enable stretching.
|
||||
Viewport.FixedStretchSize = null;
|
||||
Viewport.StretchMode = ScalingViewportStretchMode.Bilinear;
|
||||
|
||||
if (renderScaleUp)
|
||||
{
|
||||
Viewport.RenderScaleMode = ScalingViewportRenderScaleMode.CeilInt;
|
||||
}
|
||||
else
|
||||
{
|
||||
Viewport.RenderScaleMode = ScalingViewportRenderScaleMode.Fixed;
|
||||
Viewport.FixedRenderScale = 1;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Found snap, set fixed factor and run non-stretching code.
|
||||
fixedFactor = snapFactor.Value;
|
||||
}
|
||||
|
||||
Viewport.FixedStretchSize = Viewport.ViewportSize * fixedFactor;
|
||||
Viewport.StretchMode = ScalingViewportStretchMode.Nearest;
|
||||
|
||||
if (renderScaleUp)
|
||||
{
|
||||
Viewport.RenderScaleMode = ScalingViewportRenderScaleMode.Fixed;
|
||||
Viewport.FixedRenderScale = fixedFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Snapping but forced to render scale at scale 1 so...
|
||||
// At least we can NN.
|
||||
Viewport.RenderScaleMode = ScalingViewportRenderScaleMode.Fixed;
|
||||
Viewport.FixedRenderScale = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private int? CalcSnappingFactor()
|
||||
{
|
||||
// Margin tolerance is tolerance of "the window is too big"
|
||||
// where we add a margin to the viewport to make it fit.
|
||||
var cfgToleranceMargin = _cfg.GetCVar(CCVars.ViewportSnapToleranceMargin);
|
||||
// Clip tolerance is tolerance of "the window is too small"
|
||||
// where we are clipping the viewport to make it fit.
|
||||
var cfgToleranceClip = _cfg.GetCVar(CCVars.ViewportSnapToleranceClip);
|
||||
|
||||
// Calculate if the viewport, when rendered at an integer scale,
|
||||
// is close enough to the control size to enable "snapping" to NN,
|
||||
// potentially cutting a tiny bit off/leaving a margin.
|
||||
//
|
||||
// Idea here is that if you maximize the window at 1080p or 1440p
|
||||
// we are close enough to an integer scale (2x and 3x resp) that we should "snap" to it.
|
||||
|
||||
// Just do it iteratively.
|
||||
// I'm sure there's a smarter approach that needs one try with math but I'm dumb.
|
||||
for (var i = 1; i <= 10; i++)
|
||||
{
|
||||
var toleranceMargin = i * cfgToleranceMargin;
|
||||
var toleranceClip = i * cfgToleranceClip;
|
||||
var scaled = (Vector2) Viewport.ViewportSize * i;
|
||||
var (dx, dy) = PixelSize - scaled;
|
||||
|
||||
// The rule for which snap fits is that at LEAST one axis needs to be in the tolerance size wise.
|
||||
// One axis MAY be larger but not smaller than tolerance.
|
||||
// Obviously if it's too small it's bad, and if it's too big on both axis we should stretch up.
|
||||
if (Fits(dx) && Fits(dy) || Fits(dx) && Larger(dy) || Larger(dx) && Fits(dy))
|
||||
{
|
||||
// Found snap that fits.
|
||||
return i;
|
||||
}
|
||||
|
||||
bool Larger(float a)
|
||||
{
|
||||
return a > toleranceMargin;
|
||||
}
|
||||
|
||||
bool Fits(float a)
|
||||
{
|
||||
return a <= toleranceMargin && a >= -toleranceClip;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override void Resized()
|
||||
{
|
||||
base.Resized();
|
||||
|
||||
UpdateCfg();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,10 @@
|
||||
using System;
|
||||
using Content.Client.GameObjects.Components.HUD.Inventory;
|
||||
using Content.Shared;
|
||||
using Content.Shared.Prototypes.HUD;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -39,6 +35,11 @@ namespace Content.Client.UserInterface
|
||||
private readonly OptionButton LightingPresetOption;
|
||||
private readonly OptionButton _uiScaleOption;
|
||||
private readonly OptionButton _hudThemeOption;
|
||||
private readonly CheckBox _viewportStretchCheckBox;
|
||||
private readonly CheckBox _viewportLowResCheckBox;
|
||||
private readonly Slider _viewportScaleSlider;
|
||||
private readonly Control _viewportScaleBox;
|
||||
private readonly Label _viewportScaleText;
|
||||
|
||||
public GraphicsControl(IConfigurationManager cfg, IPrototypeManager proMan)
|
||||
{
|
||||
@@ -121,12 +122,55 @@ namespace Content.Client.UserInterface
|
||||
}
|
||||
});
|
||||
|
||||
contents.AddChild(new Placeholder()
|
||||
_viewportStretchCheckBox = new CheckBox
|
||||
{
|
||||
VerticalExpand = true,
|
||||
PlaceholderText = Loc.GetString("ui-options-placeholder-viewport")
|
||||
Text = Loc.GetString("ui-options-vp-stretch")
|
||||
};
|
||||
|
||||
_viewportStretchCheckBox.OnToggled += _ =>
|
||||
{
|
||||
UpdateViewportScale();
|
||||
UpdateApplyButton();
|
||||
};
|
||||
|
||||
_viewportScaleSlider = new Slider
|
||||
{
|
||||
MinValue = 1,
|
||||
MaxValue = 5,
|
||||
Rounded = true,
|
||||
MinWidth = 200
|
||||
};
|
||||
|
||||
_viewportScaleSlider.OnValueChanged += _ =>
|
||||
{
|
||||
UpdateApplyButton();
|
||||
UpdateViewportScale();
|
||||
};
|
||||
|
||||
_viewportLowResCheckBox = new CheckBox { Text = Loc.GetString("ui-options-vp-low-res")};
|
||||
_viewportLowResCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
|
||||
contents.AddChild(new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
_viewportStretchCheckBox,
|
||||
(_viewportScaleBox = new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(_viewportScaleText = new Label
|
||||
{
|
||||
Margin = new Thickness(8, 0)
|
||||
}),
|
||||
_viewportScaleSlider,
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
contents.AddChild(_viewportLowResCheckBox);
|
||||
|
||||
vBox.AddChild(contents);
|
||||
|
||||
vBox.AddChild(new StripeBack
|
||||
@@ -145,6 +189,12 @@ namespace Content.Client.UserInterface
|
||||
LightingPresetOption.SelectId(GetConfigLightingQuality());
|
||||
_uiScaleOption.SelectId(GetConfigUIScalePreset(ConfigUIScale));
|
||||
_hudThemeOption.SelectId(_cfg.GetCVar(CCVars.HudTheme));
|
||||
_viewportScaleSlider.Value = _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
|
||||
_viewportStretchCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportStretch);
|
||||
_viewportLowResCheckBox.Pressed = !_cfg.GetCVar(CCVars.ViewportScaleRender);
|
||||
|
||||
UpdateViewportScale();
|
||||
UpdateApplyButton();
|
||||
|
||||
AddChild(vBox);
|
||||
}
|
||||
@@ -173,6 +223,9 @@ namespace Content.Client.UserInterface
|
||||
_cfg.SetCVar(CVars.DisplayWindowMode,
|
||||
(int) (FullscreenCheckBox.Pressed ? WindowMode.Fullscreen : WindowMode.Windowed));
|
||||
_cfg.SetCVar(CVars.DisplayUIScale, UIScaleOptions[_uiScaleOption.SelectedId]);
|
||||
_cfg.SetCVar(CCVars.ViewportStretch, _viewportStretchCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ViewportFixedScaleFactor, (int) _viewportScaleSlider.Value);
|
||||
_cfg.SetCVar(CCVars.ViewportScaleRender, !_viewportLowResCheckBox.Pressed);
|
||||
_cfg.SaveToFile();
|
||||
UpdateApplyButton();
|
||||
}
|
||||
@@ -195,8 +248,18 @@ namespace Content.Client.UserInterface
|
||||
var isLightingQualitySame = LightingPresetOption.SelectedId == GetConfigLightingQuality();
|
||||
var isHudThemeSame = _hudThemeOption.SelectedId == _cfg.GetCVar(CCVars.HudTheme);
|
||||
var isUIScaleSame = MathHelper.CloseTo(UIScaleOptions[_uiScaleOption.SelectedId], ConfigUIScale);
|
||||
ApplyButton.Disabled = isVSyncSame && isFullscreenSame && isLightingQualitySame && isHudThemeSame &&
|
||||
isUIScaleSame;
|
||||
var isVPStretchSame = _viewportStretchCheckBox.Pressed == _cfg.GetCVar(CCVars.ViewportStretch);
|
||||
var isVPScaleSame = (int) _viewportScaleSlider.Value == _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
|
||||
var isVPResSame = _viewportLowResCheckBox.Pressed == !_cfg.GetCVar(CCVars.ViewportScaleRender);
|
||||
|
||||
ApplyButton.Disabled = isVSyncSame &&
|
||||
isFullscreenSame &&
|
||||
isLightingQualitySame &&
|
||||
isUIScaleSame &&
|
||||
isVPStretchSame &&
|
||||
isVPScaleSame &&
|
||||
isVPResSame &&
|
||||
isHudThemeSame;
|
||||
}
|
||||
|
||||
private bool ConfigIsFullscreen =>
|
||||
@@ -261,6 +324,12 @@ namespace Content.Client.UserInterface
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void UpdateViewportScale()
|
||||
{
|
||||
_viewportScaleBox.Visible = !_viewportStretchCheckBox.Pressed;
|
||||
_viewportScaleText.Text = Loc.GetString("ui-options-vp-scale", ("scale", _viewportScaleSlider.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
47
Content.Client/UserInterface/ParallaxControl.cs
Normal file
47
Content.Client/UserInterface/ParallaxControl.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Content.Client.Interfaces.Parallax;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Client.UserInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders the parallax background as a UI control.
|
||||
/// </summary>
|
||||
public sealed class ParallaxControl : Control
|
||||
{
|
||||
[Dependency] private readonly IParallaxManager _parallaxManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] public Vector2i Offset { get; set; }
|
||||
|
||||
public ParallaxControl()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
Offset = (_random.Next(0, 1000), _random.Next(0, 1000));
|
||||
RectClipContent = true;
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
var tex = _parallaxManager.ParallaxTexture;
|
||||
if (tex == null)
|
||||
return;
|
||||
|
||||
var size = tex.Size;
|
||||
var ourSize = PixelSize;
|
||||
|
||||
for (var x = -size.X + Offset.X; x < ourSize.X; x += size.X)
|
||||
{
|
||||
for (var y = -size.Y + Offset.Y; y < ourSize.Y; y += size.Y)
|
||||
{
|
||||
handle.DrawTexture(tex, (x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
340
Content.Client/UserInterface/ScalingViewport.cs
Normal file
340
Content.Client/UserInterface/ScalingViewport.cs
Normal file
@@ -0,0 +1,340 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace Content.Client.UserInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// Viewport control that has a fixed viewport size and scales it appropriately.
|
||||
/// </summary>
|
||||
public sealed class ScalingViewport : Control, IViewportControl
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
|
||||
// Internal viewport creation is deferred.
|
||||
private IClydeViewport? _viewport;
|
||||
private IEye? _eye;
|
||||
private Vector2i _viewportSize;
|
||||
private int _curRenderScale;
|
||||
private ScalingViewportStretchMode _stretchMode = ScalingViewportStretchMode.Bilinear;
|
||||
private ScalingViewportRenderScaleMode _renderScaleMode = ScalingViewportRenderScaleMode.Fixed;
|
||||
private int _fixedRenderScale = 1;
|
||||
|
||||
private readonly List<CopyPixelsDelegate<Rgba32>> _queuedScreenshots = new();
|
||||
|
||||
public int CurrentRenderScale => _curRenderScale;
|
||||
|
||||
/// <summary>
|
||||
/// The eye to render.
|
||||
/// </summary>
|
||||
public IEye? Eye
|
||||
{
|
||||
get => _eye;
|
||||
set
|
||||
{
|
||||
_eye = value;
|
||||
|
||||
if (_viewport != null)
|
||||
_viewport.Eye = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The size, in unscaled pixels, of the internal viewport.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The actual viewport may have render scaling applied based on parameters.
|
||||
/// </remarks>
|
||||
public Vector2i ViewportSize
|
||||
{
|
||||
get => _viewportSize;
|
||||
set
|
||||
{
|
||||
_viewportSize = value;
|
||||
InvalidateViewport();
|
||||
}
|
||||
}
|
||||
|
||||
// Do not need to InvalidateViewport() since it doesn't affect viewport creation.
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] public Vector2i? FixedStretchSize { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ScalingViewportStretchMode StretchMode
|
||||
{
|
||||
get => _stretchMode;
|
||||
set
|
||||
{
|
||||
_stretchMode = value;
|
||||
InvalidateViewport();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ScalingViewportRenderScaleMode RenderScaleMode
|
||||
{
|
||||
get => _renderScaleMode;
|
||||
set
|
||||
{
|
||||
_renderScaleMode = value;
|
||||
InvalidateViewport();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int FixedRenderScale
|
||||
{
|
||||
get => _fixedRenderScale;
|
||||
set
|
||||
{
|
||||
_fixedRenderScale = value;
|
||||
InvalidateViewport();
|
||||
}
|
||||
}
|
||||
|
||||
public ScalingViewport()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RectClipContent = true;
|
||||
}
|
||||
|
||||
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
base.KeyBindDown(args);
|
||||
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
_inputManager.ViewportKeyEvent(this, args);
|
||||
}
|
||||
|
||||
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
base.KeyBindUp(args);
|
||||
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
_inputManager.ViewportKeyEvent(this, args);
|
||||
}
|
||||
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
EnsureViewportCreated();
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
DebugTools.AssertNotNull(_viewport);
|
||||
|
||||
_viewport!.Render();
|
||||
|
||||
if (_queuedScreenshots.Count != 0)
|
||||
{
|
||||
var callbacks = _queuedScreenshots.ToArray();
|
||||
|
||||
_viewport.RenderTarget.CopyPixelsToMemory<Rgba32>(image =>
|
||||
{
|
||||
foreach (var callback in callbacks)
|
||||
{
|
||||
callback(image);
|
||||
}
|
||||
});
|
||||
|
||||
_queuedScreenshots.Clear();
|
||||
}
|
||||
|
||||
var drawBox = GetDrawBox();
|
||||
var drawBoxGlobal = drawBox.Translated(GlobalPixelPosition);
|
||||
_viewport.RenderScreenOverlaysBelow(handle, this, drawBoxGlobal);
|
||||
handle.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
|
||||
_viewport.RenderScreenOverlaysAbove(handle, this, drawBoxGlobal);
|
||||
}
|
||||
|
||||
public void Screenshot(CopyPixelsDelegate<Rgba32> callback)
|
||||
{
|
||||
_queuedScreenshots.Add(callback);
|
||||
}
|
||||
|
||||
// Draw box in pixel coords to draw the viewport at.
|
||||
private UIBox2i GetDrawBox()
|
||||
{
|
||||
DebugTools.AssertNotNull(_viewport);
|
||||
|
||||
var vpSize = _viewport!.Size;
|
||||
var ourSize = (Vector2) PixelSize;
|
||||
|
||||
if (FixedStretchSize == null)
|
||||
{
|
||||
var (ratioX, ratioY) = ourSize / vpSize;
|
||||
var ratio = Math.Min(ratioX, ratioY);
|
||||
|
||||
var size = vpSize * ratio;
|
||||
// Size
|
||||
var pos = (ourSize - size) / 2;
|
||||
|
||||
return (UIBox2i) UIBox2.FromDimensions(pos, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Center only, no scaling.
|
||||
var pos = (ourSize - FixedStretchSize.Value) / 2;
|
||||
return (UIBox2i) UIBox2.FromDimensions(pos, FixedStretchSize.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void RegenerateViewport()
|
||||
{
|
||||
DebugTools.AssertNull(_viewport);
|
||||
|
||||
var vpSizeBase = ViewportSize;
|
||||
var ourSize = PixelSize;
|
||||
var (ratioX, ratioY) = ourSize / (Vector2) vpSizeBase;
|
||||
var ratio = Math.Min(ratioX, ratioY);
|
||||
var renderScale = 1;
|
||||
switch (_renderScaleMode)
|
||||
{
|
||||
case ScalingViewportRenderScaleMode.CeilInt:
|
||||
renderScale = (int) Math.Ceiling(ratio);
|
||||
break;
|
||||
case ScalingViewportRenderScaleMode.FloorInt:
|
||||
renderScale = (int) Math.Floor(ratio);
|
||||
break;
|
||||
case ScalingViewportRenderScaleMode.Fixed:
|
||||
renderScale = _fixedRenderScale;
|
||||
break;
|
||||
}
|
||||
|
||||
// Always has to be at least one to avoid passing 0,0 to the viewport constructor
|
||||
renderScale = Math.Max(1, renderScale);
|
||||
|
||||
_curRenderScale = renderScale;
|
||||
|
||||
_viewport = _clyde.CreateViewport(
|
||||
ViewportSize * renderScale,
|
||||
new TextureSampleParameters
|
||||
{
|
||||
Filter = StretchMode == ScalingViewportStretchMode.Bilinear,
|
||||
});
|
||||
|
||||
_viewport.RenderScale = (renderScale, renderScale);
|
||||
|
||||
_viewport.Eye = _eye;
|
||||
}
|
||||
|
||||
protected override void Resized()
|
||||
{
|
||||
base.Resized();
|
||||
|
||||
InvalidateViewport();
|
||||
}
|
||||
|
||||
private void InvalidateViewport()
|
||||
{
|
||||
_viewport?.Dispose();
|
||||
_viewport = null;
|
||||
}
|
||||
|
||||
public MapCoordinates ScreenToMap(Vector2 coords)
|
||||
{
|
||||
if (_eye == null)
|
||||
return default;
|
||||
|
||||
EnsureViewportCreated();
|
||||
|
||||
var matrix = Matrix3.Invert(LocalToScreenMatrix());
|
||||
|
||||
return _viewport!.LocalToWorld(matrix.Transform(coords));
|
||||
}
|
||||
|
||||
public Vector2 WorldToScreen(Vector2 map)
|
||||
{
|
||||
if (_eye == null)
|
||||
return default;
|
||||
|
||||
EnsureViewportCreated();
|
||||
|
||||
var vpLocal = _viewport!.WorldToLocal(map);
|
||||
|
||||
var matrix = LocalToScreenMatrix();
|
||||
|
||||
return matrix.Transform(vpLocal);
|
||||
}
|
||||
|
||||
private Matrix3 LocalToScreenMatrix()
|
||||
{
|
||||
DebugTools.AssertNotNull(_viewport);
|
||||
|
||||
var drawBox = GetDrawBox();
|
||||
var scaleFactor = drawBox.Size / (Vector2) _viewport!.Size;
|
||||
|
||||
if (scaleFactor == (0, 0))
|
||||
// Basically a nonsense scenario, at least make sure to return something that can be inverted.
|
||||
return Matrix3.Identity;
|
||||
|
||||
var scale = Matrix3.CreateScale(scaleFactor);
|
||||
var translate = Matrix3.CreateTranslation(GlobalPixelPosition + drawBox.TopLeft);
|
||||
|
||||
return scale * translate;
|
||||
}
|
||||
|
||||
private void EnsureViewportCreated()
|
||||
{
|
||||
if (_viewport == null)
|
||||
{
|
||||
RegenerateViewport();
|
||||
}
|
||||
|
||||
DebugTools.AssertNotNull(_viewport);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines how the viewport is stretched if it does not match the size of the control perfectly.
|
||||
/// </summary>
|
||||
public enum ScalingViewportStretchMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Bilinear sampling is used.
|
||||
/// </summary>
|
||||
Bilinear = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Nearest neighbor sampling is used.
|
||||
/// </summary>
|
||||
Nearest,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines how the base render scale of the viewport is selected.
|
||||
/// </summary>
|
||||
public enum ScalingViewportRenderScaleMode
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ScalingViewport.FixedRenderScale"/> is used.
|
||||
/// </summary>
|
||||
Fixed = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Floor to the closest integer scale possible.
|
||||
/// </summary>
|
||||
FloorInt,
|
||||
|
||||
/// <summary>
|
||||
/// Ceiling to the closest integer scale possible.
|
||||
/// </summary>
|
||||
CeilInt
|
||||
}
|
||||
}
|
||||
15
Content.Client/UserInterface/ViewportExt.cs
Normal file
15
Content.Client/UserInterface/ViewportExt.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
|
||||
namespace Content.Client.UserInterface
|
||||
{
|
||||
public static class ViewportExt
|
||||
{
|
||||
public static int GetRenderScale(this IViewportControl viewport)
|
||||
{
|
||||
if (viewport is ScalingViewport svp)
|
||||
return svp.CurrentRenderScale;
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Content.Client/UserInterface/ViewportManager.cs
Normal file
42
Content.Client/UserInterface/ViewportManager.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Client.UserInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// Event proxy for <see cref="MainViewport"/> to listen to config events.
|
||||
/// </summary>
|
||||
// ReSharper disable once ClassNeverInstantiated.Global
|
||||
public sealed class ViewportManager
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private readonly List<MainViewport> _viewports = new();
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_cfg.OnValueChanged(CCVars.ViewportStretch, _ => UpdateCfg());
|
||||
_cfg.OnValueChanged(CCVars.ViewportSnapToleranceClip, _ => UpdateCfg());
|
||||
_cfg.OnValueChanged(CCVars.ViewportSnapToleranceMargin, _ => UpdateCfg());
|
||||
_cfg.OnValueChanged(CCVars.ViewportScaleRender, _ => UpdateCfg());
|
||||
_cfg.OnValueChanged(CCVars.ViewportFixedScaleFactor, _ => UpdateCfg());
|
||||
}
|
||||
|
||||
private void UpdateCfg()
|
||||
{
|
||||
_viewports.ForEach(v => v.UpdateCfg());
|
||||
}
|
||||
|
||||
public void AddViewport(MainViewport vp)
|
||||
{
|
||||
_viewports.Add(vp);
|
||||
}
|
||||
|
||||
public void RemoveViewport(MainViewport vp)
|
||||
{
|
||||
_viewports.Remove(vp);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user