Voting (#3185)
* Basic voting * Rewrite lobby in XAML. Working lobby voting. * Escape menu is now XAML. * Vote menu works, custom votes, gamemode votes. * Vote timeouts & administration. Basically done now. * I will now pretend I was never planning to code voting hotkeys. * Make vote call UI a bit... funny. * Fix exception on round restart. * Fix some vote command definitions.
This commit is contained in:
committed by
GitHub
parent
db290fd91e
commit
cea87d6985
@@ -12,6 +12,7 @@ using Content.Client.UserInterface;
|
||||
using Content.Client.UserInterface.AdminMenu;
|
||||
using Content.Client.UserInterface.Stylesheets;
|
||||
using Content.Client.Utility;
|
||||
using Content.Client.Voting;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Alert;
|
||||
@@ -43,6 +44,7 @@ namespace Content.Client
|
||||
IoCManager.Register<ActionManager, ActionManager>();
|
||||
IoCManager.Register<IClientAdminManager, ClientAdminManager>();
|
||||
IoCManager.Register<EuiManager, EuiManager>();
|
||||
IoCManager.Register<IVoteManager, VoteManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using Content.Client.UserInterface;
|
||||
using Content.Client.UserInterface.AdminMenu;
|
||||
using Content.Client.UserInterface.Stylesheets;
|
||||
using Content.Client.Graphics.Overlays;
|
||||
using Content.Client.Voting;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.Components.Cargo;
|
||||
@@ -161,6 +162,7 @@ namespace Content.Client
|
||||
IoCManager.Resolve<EuiManager>().Initialize();
|
||||
IoCManager.Resolve<AlertManager>().Initialize();
|
||||
IoCManager.Resolve<ActionManager>().Initialize();
|
||||
IoCManager.Resolve<IVoteManager>().Initialize();
|
||||
|
||||
_baseClient.RunLevelChanged += (sender, args) =>
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Client.Chat;
|
||||
using Content.Client.Interfaces.Chat;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Client.Voting;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -18,6 +19,7 @@ namespace Content.Client.State
|
||||
[Dependency] private readonly IGameHud _gameHud = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IVoteManager _voteManager = default!;
|
||||
|
||||
[ViewVariables] private ChatBox _gameChat;
|
||||
|
||||
@@ -35,6 +37,7 @@ namespace Content.Client.State
|
||||
|
||||
_userInterfaceManager.StateRoot.AddChild(_gameHud.RootControl);
|
||||
_chatManager.SetChatBox(_gameChat);
|
||||
_voteManager.SetPopupContainer(_gameHud.VoteContainer);
|
||||
_gameChat.DefaultChatFormat = "say \"{0}\"";
|
||||
_gameChat.Input.PlaceHolder = Loc.GetString("Say something! [ for OOC");
|
||||
|
||||
|
||||
@@ -31,14 +31,6 @@ namespace Content.Client.State
|
||||
|
||||
public override void Startup()
|
||||
{
|
||||
var panelTex = ResC.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
|
||||
var back = new StyleBoxTexture
|
||||
{
|
||||
Texture = panelTex,
|
||||
Modulate = new Color(32, 32, 48),
|
||||
};
|
||||
back.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||
|
||||
Button exitButton;
|
||||
Button reconnectButton;
|
||||
Button retryButton;
|
||||
@@ -50,10 +42,7 @@ namespace Content.Client.State
|
||||
Stylesheet = _stylesheetManager.SheetSpace,
|
||||
Children =
|
||||
{
|
||||
new PanelContainer
|
||||
{
|
||||
PanelOverride = back
|
||||
},
|
||||
new PanelContainer {StyleClasses = {StyleBase.ClassAngleRect}},
|
||||
new VBoxContainer
|
||||
{
|
||||
SeparationOverride = 0,
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Linq;
|
||||
using Content.Client.Interfaces;
|
||||
using Content.Client.Interfaces.Chat;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Client.Voting;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client;
|
||||
using Robust.Client.Console;
|
||||
@@ -36,6 +37,7 @@ namespace Content.Client.State
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IVoteManager _voteManager = default!;
|
||||
|
||||
[ViewVariables] private CharacterSetupGui _characterSetup;
|
||||
[ViewVariables] private LobbyGui _lobby;
|
||||
@@ -58,12 +60,14 @@ namespace Content.Client.State
|
||||
_lobby.CharacterPreview.UpdateUI();
|
||||
};
|
||||
|
||||
_lobby = new LobbyGui(_entityManager, _resourceCache, _preferencesManager);
|
||||
_lobby = new LobbyGui(_entityManager, _preferencesManager);
|
||||
_userInterfaceManager.StateRoot.AddChild(_lobby);
|
||||
|
||||
LayoutContainer.SetAnchorPreset(_lobby, LayoutContainer.LayoutPreset.Wide);
|
||||
|
||||
_chatManager.SetChatBox(_lobby.Chat);
|
||||
_voteManager.SetPopupContainer(_lobby.VoteContainer);
|
||||
|
||||
_lobby.Chat.DefaultChatFormat = "ooc \"{0}\"";
|
||||
|
||||
_lobby.ServerName.Text = _baseClient.GameInfo.ServerName;
|
||||
|
||||
12
Content.Client/UserInterface/EscapeMenu.xaml
Normal file
12
Content.Client/UserInterface/EscapeMenu.xaml
Normal file
@@ -0,0 +1,12 @@
|
||||
<SS14Window xmlns="https://spacestation14.io"
|
||||
xmlns:voting="clr-namespace:Content.Client.Voting"
|
||||
Title="{Loc 'Esc Menu'}"
|
||||
Resizable="False">
|
||||
|
||||
<VBoxContainer SeparationOverride="4">
|
||||
<voting:VoteCallMenuButton />
|
||||
<Button Name="OptionsButton" Text="{Loc 'Options'}" />
|
||||
<Button Name="DisconnectButton" Text="{Loc 'Disconnect'}" />
|
||||
<Button Name="QuitButton" Text="{Loc 'Quit game'}" />
|
||||
</VBoxContainer>
|
||||
</SS14Window>
|
||||
@@ -1,51 +1,29 @@
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.UserInterface
|
||||
{
|
||||
internal sealed class EscapeMenu : SS14Window
|
||||
[GenerateTypedNameReferences]
|
||||
internal partial class EscapeMenu : SS14Window
|
||||
{
|
||||
private readonly IClientConsoleHost _consoleHost;
|
||||
|
||||
private BaseButton DisconnectButton;
|
||||
private BaseButton QuitButton;
|
||||
private BaseButton OptionsButton;
|
||||
private OptionsMenu optionsMenu;
|
||||
|
||||
public EscapeMenu(IClientConsoleHost consoleHost)
|
||||
{
|
||||
_consoleHost = consoleHost;
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
private void PerformLayout()
|
||||
{
|
||||
optionsMenu = new OptionsMenu();
|
||||
|
||||
Resizable = false;
|
||||
|
||||
Title = "Esc Menu";
|
||||
|
||||
var vBox = new VBoxContainer {SeparationOverride = 4};
|
||||
Contents.AddChild(vBox);
|
||||
|
||||
OptionsButton = new Button {Text = Loc.GetString("Options")};
|
||||
OptionsButton.OnPressed += OnOptionsButtonClicked;
|
||||
vBox.AddChild(OptionsButton);
|
||||
|
||||
DisconnectButton = new Button {Text = Loc.GetString("Disconnect")};
|
||||
DisconnectButton.OnPressed += OnDisconnectButtonClicked;
|
||||
vBox.AddChild(DisconnectButton);
|
||||
|
||||
QuitButton = new Button {Text = Loc.GetString("Quit Game")};
|
||||
QuitButton.OnPressed += OnQuitButtonClicked;
|
||||
vBox.AddChild(QuitButton);
|
||||
DisconnectButton.OnPressed += OnDisconnectButtonClicked;
|
||||
}
|
||||
|
||||
private void OnQuitButtonClicked(BaseButton.ButtonEventArgs args)
|
||||
@@ -15,6 +15,7 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.Input.Keyboard.Key;
|
||||
using Control = Robust.Client.UserInterface.Control;
|
||||
using LC = Robust.Client.UserInterface.Controls.LayoutContainer;
|
||||
|
||||
namespace Content.Client.UserInterface
|
||||
{
|
||||
@@ -70,6 +71,9 @@ namespace Content.Client.UserInterface
|
||||
Action<bool> OnCombatModeChanged { get; set; }
|
||||
Action<TargetingZone> OnTargetingZoneChanged { get; set; }
|
||||
|
||||
Control VoteContainer { get; }
|
||||
|
||||
void AddTopNotification(TopNotification notification);
|
||||
|
||||
// Init logic.
|
||||
void Initialize();
|
||||
@@ -90,6 +94,7 @@ namespace Content.Client.UserInterface
|
||||
private TargetingDoll _targetingDoll;
|
||||
private Button _combatModeButton;
|
||||
private VBoxContainer _combatPanelContainer;
|
||||
private VBoxContainer _topNotificationContainer;
|
||||
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
@@ -120,10 +125,15 @@ namespace Content.Client.UserInterface
|
||||
public Action<bool> OnCombatModeChanged { get; set; }
|
||||
public Action<TargetingZone> OnTargetingZoneChanged { get; set; }
|
||||
|
||||
public void AddTopNotification(TopNotification notification)
|
||||
{
|
||||
_topNotificationContainer.AddChild(notification);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
RootControl = new LayoutContainer();
|
||||
LayoutContainer.SetAnchorPreset(RootControl, LayoutContainer.LayoutPreset.Wide);
|
||||
RootControl = new LC();
|
||||
LC.SetAnchorPreset(RootControl, LC.LayoutPreset.Wide);
|
||||
|
||||
var escapeTexture = _resourceCache.GetTexture("/Textures/Interface/hamburger.svg.192dpi.png");
|
||||
var characterTexture = _resourceCache.GetTexture("/Textures/Interface/character.svg.192dpi.png");
|
||||
@@ -141,7 +151,7 @@ namespace Content.Client.UserInterface
|
||||
|
||||
RootControl.AddChild(_topButtonsContainer);
|
||||
|
||||
LayoutContainer.SetAnchorAndMarginPreset(_topButtonsContainer, LayoutContainer.LayoutPreset.TopLeft,
|
||||
LC.SetAnchorAndMarginPreset(_topButtonsContainer, LC.LayoutPreset.TopLeft,
|
||||
margin: 10);
|
||||
|
||||
// the icon textures here should all have the same image height (32) but different widths, so in order to ensure
|
||||
@@ -271,10 +281,10 @@ namespace Content.Client.UserInterface
|
||||
}
|
||||
};
|
||||
|
||||
LayoutContainer.SetGrowHorizontal(_combatPanelContainer, LayoutContainer.GrowDirection.Begin);
|
||||
LayoutContainer.SetGrowVertical(_combatPanelContainer, LayoutContainer.GrowDirection.Begin);
|
||||
LayoutContainer.SetAnchorAndMarginPreset(_combatPanelContainer, LayoutContainer.LayoutPreset.BottomRight);
|
||||
LayoutContainer.SetMarginBottom(_combatPanelContainer, -10f);
|
||||
LC.SetGrowHorizontal(_combatPanelContainer, LC.GrowDirection.Begin);
|
||||
LC.SetGrowVertical(_combatPanelContainer, LC.GrowDirection.Begin);
|
||||
LC.SetAnchorAndMarginPreset(_combatPanelContainer, LC.LayoutPreset.BottomRight);
|
||||
LC.SetMarginBottom(_combatPanelContainer, -10f);
|
||||
RootControl.AddChild(_combatPanelContainer);
|
||||
|
||||
_combatModeButton.OnToggled += args => OnCombatModeChanged?.Invoke(args.Pressed);
|
||||
@@ -284,10 +294,10 @@ namespace Content.Client.UserInterface
|
||||
{
|
||||
SeparationOverride = 5
|
||||
};
|
||||
LayoutContainer.SetAnchorAndMarginPreset(centerBottomContainer, LayoutContainer.LayoutPreset.CenterBottom);
|
||||
LayoutContainer.SetGrowHorizontal(centerBottomContainer, LayoutContainer.GrowDirection.Both);
|
||||
LayoutContainer.SetGrowVertical(centerBottomContainer, LayoutContainer.GrowDirection.Begin);
|
||||
LayoutContainer.SetMarginBottom(centerBottomContainer, -10f);
|
||||
LC.SetAnchorAndMarginPreset(centerBottomContainer, LC.LayoutPreset.CenterBottom);
|
||||
LC.SetGrowHorizontal(centerBottomContainer, LC.GrowDirection.Both);
|
||||
LC.SetGrowVertical(centerBottomContainer, LC.GrowDirection.Begin);
|
||||
LC.SetMarginBottom(centerBottomContainer, -10f);
|
||||
RootControl.AddChild(centerBottomContainer);
|
||||
|
||||
HandsContainer = new MarginContainer
|
||||
@@ -313,10 +323,27 @@ namespace Content.Client.UserInterface
|
||||
|
||||
RootControl.AddChild(SuspicionContainer);
|
||||
|
||||
LayoutContainer.SetAnchorAndMarginPreset(SuspicionContainer, LayoutContainer.LayoutPreset.BottomLeft,
|
||||
LC.SetAnchorAndMarginPreset(SuspicionContainer, LC.LayoutPreset.BottomLeft,
|
||||
margin: 10);
|
||||
LayoutContainer.SetGrowHorizontal(SuspicionContainer, LayoutContainer.GrowDirection.End);
|
||||
LayoutContainer.SetGrowVertical(SuspicionContainer, LayoutContainer.GrowDirection.Begin);
|
||||
LC.SetGrowHorizontal(SuspicionContainer, LC.GrowDirection.End);
|
||||
LC.SetGrowVertical(SuspicionContainer, LC.GrowDirection.Begin);
|
||||
|
||||
_topNotificationContainer = new VBoxContainer
|
||||
{
|
||||
CustomMinimumSize = (600, 0)
|
||||
};
|
||||
RootControl.AddChild(_topNotificationContainer);
|
||||
LC.SetAnchorPreset(_topNotificationContainer, LC.LayoutPreset.CenterTop);
|
||||
LC.SetGrowHorizontal(_topNotificationContainer, LC.GrowDirection.Both);
|
||||
LC.SetGrowVertical(_topNotificationContainer, LC.GrowDirection.End);
|
||||
|
||||
VoteContainer = new VBoxContainer();
|
||||
RootControl.AddChild(VoteContainer);
|
||||
LC.SetAnchorPreset(VoteContainer, LC.LayoutPreset.TopLeft);
|
||||
LC.SetMarginLeft(VoteContainer, 180);
|
||||
LC.SetMarginTop(VoteContainer, 100);
|
||||
LC.SetGrowHorizontal(VoteContainer, LC.GrowDirection.End);
|
||||
LC.SetGrowVertical(VoteContainer, LC.GrowDirection.End);
|
||||
}
|
||||
|
||||
private void ButtonTutorialOnOnToggled()
|
||||
@@ -436,6 +463,8 @@ namespace Content.Client.UserInterface
|
||||
|
||||
public Action<bool> SandboxButtonToggled { get; set; }
|
||||
|
||||
public Control VoteContainer { get; private set; }
|
||||
|
||||
public sealed class TopButton : ContainerButton
|
||||
{
|
||||
public const string StyleClassLabelTopButton = "topButtonLabel";
|
||||
|
||||
@@ -1,330 +0,0 @@
|
||||
using Content.Client.Chat;
|
||||
using Content.Client.Interfaces;
|
||||
using Content.Client.UserInterface.Stylesheets;
|
||||
using Content.Client.Utility;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.UserInterface
|
||||
{
|
||||
internal sealed class LobbyGui : Control
|
||||
{
|
||||
public Label ServerName { get; }
|
||||
public Label StartTime { get; }
|
||||
public Button ReadyButton { get; }
|
||||
public Button ObserveButton { get; }
|
||||
public Button OptionsButton { get; }
|
||||
public Button LeaveButton { get; }
|
||||
public ChatBox Chat { get; }
|
||||
public LobbyPlayerList OnlinePlayerList { get; }
|
||||
public ServerInfo ServerInfo { get; }
|
||||
public LobbyCharacterPreviewPanel CharacterPreview { get; }
|
||||
|
||||
public LobbyGui(IEntityManager entityManager,
|
||||
IResourceCache resourceCache,
|
||||
IClientPreferencesManager preferencesManager)
|
||||
{
|
||||
var margin = new MarginContainer
|
||||
{
|
||||
MarginBottomOverride = 20,
|
||||
MarginLeftOverride = 20,
|
||||
MarginRightOverride = 20,
|
||||
MarginTopOverride = 20,
|
||||
};
|
||||
|
||||
AddChild(margin);
|
||||
|
||||
var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
|
||||
var back = new StyleBoxTexture
|
||||
{
|
||||
Texture = panelTex,
|
||||
Modulate = new Color(37, 37, 42),
|
||||
};
|
||||
back.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||
|
||||
var panel = new PanelContainer
|
||||
{
|
||||
PanelOverride = back
|
||||
};
|
||||
|
||||
margin.AddChild(panel);
|
||||
|
||||
var vBox = new VBoxContainer {SeparationOverride = 0};
|
||||
|
||||
margin.AddChild(vBox);
|
||||
|
||||
var topHBox = new HBoxContainer
|
||||
{
|
||||
CustomMinimumSize = (0, 40),
|
||||
Children =
|
||||
{
|
||||
new MarginContainer
|
||||
{
|
||||
MarginLeftOverride = 8,
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
Text = Loc.GetString("Lobby"),
|
||||
StyleClasses = {StyleNano.StyleClassLabelHeadingBigger},
|
||||
VAlign = Label.VAlignMode.Center
|
||||
}
|
||||
}
|
||||
},
|
||||
(ServerName = new Label
|
||||
{
|
||||
StyleClasses = {StyleNano.StyleClassLabelHeadingBigger},
|
||||
VAlign = Label.VAlignMode.Center,
|
||||
SizeFlagsHorizontal = SizeFlags.Expand | SizeFlags.ShrinkCenter
|
||||
}),
|
||||
(OptionsButton = new Button
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkEnd,
|
||||
Text = Loc.GetString("Options"),
|
||||
StyleClasses = {StyleNano.StyleClassButtonBig},
|
||||
}),
|
||||
(LeaveButton = new Button
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkEnd,
|
||||
Text = Loc.GetString("Leave"),
|
||||
StyleClasses = {StyleNano.StyleClassButtonBig},
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
vBox.AddChild(topHBox);
|
||||
|
||||
vBox.AddChild(new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = StyleNano.NanoGold,
|
||||
ContentMarginTopOverride = 2
|
||||
},
|
||||
});
|
||||
|
||||
var hBox = new HBoxContainer
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||
SeparationOverride = 0
|
||||
};
|
||||
vBox.AddChild(hBox);
|
||||
|
||||
CharacterPreview = new LobbyCharacterPreviewPanel(
|
||||
entityManager,
|
||||
preferencesManager)
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.None
|
||||
};
|
||||
hBox.AddChild(new VBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SeparationOverride = 0,
|
||||
Children =
|
||||
{
|
||||
CharacterPreview,
|
||||
|
||||
new StripeBack
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new MarginContainer
|
||||
{
|
||||
MarginRightOverride = 3,
|
||||
MarginLeftOverride = 3,
|
||||
MarginTopOverride = 3,
|
||||
MarginBottomOverride = 3,
|
||||
Children =
|
||||
{
|
||||
new HBoxContainer
|
||||
{
|
||||
SeparationOverride = 6,
|
||||
Children =
|
||||
{
|
||||
(ObserveButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("Observe"),
|
||||
StyleClasses = {StyleNano.StyleClassButtonBig}
|
||||
}),
|
||||
(StartTime = new Label
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Align = Label.AlignMode.Right,
|
||||
FontColorOverride = Color.DarkGray,
|
||||
StyleClasses = {StyleNano.StyleClassLabelBig}
|
||||
}),
|
||||
(ReadyButton = new Button
|
||||
{
|
||||
ToggleMode = true,
|
||||
Text = Loc.GetString("Ready Up"),
|
||||
StyleClasses = {StyleNano.StyleClassButtonBig}
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
new MarginContainer
|
||||
{
|
||||
MarginRightOverride = 3,
|
||||
MarginLeftOverride = 3,
|
||||
MarginTopOverride = 3,
|
||||
MarginBottomOverride = 3,
|
||||
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
(Chat = new ChatBox
|
||||
{
|
||||
Input = {PlaceHolder = Loc.GetString("Say something!")}
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
hBox.AddChild(new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = StyleNano.NanoGold}, CustomMinimumSize = (2, 0)
|
||||
});
|
||||
|
||||
{
|
||||
hBox.AddChild(new VBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
new NanoHeading
|
||||
{
|
||||
Text = Loc.GetString("Online Players"),
|
||||
},
|
||||
new MarginContainer
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||
MarginRightOverride = 3,
|
||||
MarginLeftOverride = 3,
|
||||
MarginTopOverride = 3,
|
||||
MarginBottomOverride = 3,
|
||||
Children =
|
||||
{
|
||||
new HBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
CustomMinimumSize = (50,50),
|
||||
Children =
|
||||
{
|
||||
(OnlinePlayerList = new LobbyPlayerList
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new NanoHeading
|
||||
{
|
||||
Text = Loc.GetString("Server Info"),
|
||||
},
|
||||
new MarginContainer
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||
MarginRightOverride = 3,
|
||||
MarginLeftOverride = 3,
|
||||
MarginTopOverride = 3,
|
||||
MarginBottomOverride = 2,
|
||||
Children =
|
||||
{
|
||||
(ServerInfo = new ServerInfo())
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LobbyPlayerList : Control
|
||||
{
|
||||
private readonly ScrollContainer _scroll;
|
||||
private readonly VBoxContainer _vBox;
|
||||
|
||||
public LobbyPlayerList()
|
||||
{
|
||||
var panel = new PanelContainer()
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#202028") },
|
||||
};
|
||||
_vBox = new VBoxContainer();
|
||||
_scroll = new ScrollContainer();
|
||||
_scroll.AddChild(_vBox);
|
||||
panel.AddChild(_scroll);
|
||||
AddChild(panel);
|
||||
}
|
||||
|
||||
// Adds a row
|
||||
public void AddItem(string name, string status)
|
||||
{
|
||||
var hbox = new HBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
};
|
||||
|
||||
// Player Name
|
||||
hbox.AddChild(new PanelContainer()
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = Color.FromHex("#373744"),
|
||||
ContentMarginBottomOverride = 2,
|
||||
ContentMarginLeftOverride = 4,
|
||||
ContentMarginRightOverride = 4,
|
||||
ContentMarginTopOverride = 2
|
||||
},
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
Text = name
|
||||
}
|
||||
},
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||
});
|
||||
// Status
|
||||
hbox.AddChild(new PanelContainer()
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = Color.FromHex("#373744"),
|
||||
ContentMarginBottomOverride = 2,
|
||||
ContentMarginLeftOverride = 4,
|
||||
ContentMarginRightOverride = 4,
|
||||
ContentMarginTopOverride = 2
|
||||
},
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
Text = status
|
||||
}
|
||||
},
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SizeFlagsStretchRatio = 0.2f,
|
||||
});
|
||||
|
||||
_vBox.AddChild(hbox);
|
||||
}
|
||||
|
||||
// Deletes all rows
|
||||
public void Clear()
|
||||
{
|
||||
_vBox.RemoveAllChildren();
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Content.Client/UserInterface/LobbyGui.xaml
Normal file
88
Content.Client/UserInterface/LobbyGui.xaml
Normal file
@@ -0,0 +1,88 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:style="clr-namespace:Content.Client.UserInterface.Stylesheets"
|
||||
xmlns:cui="clr-namespace:Content.Client.UserInterface"
|
||||
xmlns:chat="clr-namespace:Content.Client.Chat"
|
||||
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 CustomMinimumSize="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 SizeFlagsVertical="FillExpand">
|
||||
<!-- Left panel -->
|
||||
<VBoxContainer Name="CLeftPanelContainer" SizeFlagsHorizontal="FillExpand">
|
||||
<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" SizeFlagsHorizontal="FillExpand" />
|
||||
<Button Name="CReadyButton" ToggleMode="True" Text="{Loc 'Ready Up'}"
|
||||
StyleClasses="ButtonBig" />
|
||||
</HBoxContainer>
|
||||
</MarginContainer>
|
||||
</cui:StripeBack>
|
||||
<MarginContainer SizeFlagsVertical="FillExpand" MarginLeftOverride="3" MarginRightOverride="3"
|
||||
MarginBottomOverride="3"
|
||||
MarginTopOverride="3">
|
||||
<chat:ChatBox Name="CChat" />
|
||||
</MarginContainer>
|
||||
</VBoxContainer>
|
||||
<!-- Gold line -->
|
||||
<PanelContainer CustomMinimumSize="2 0">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="{x:Static style:StyleNano.NanoGold}" />
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<!-- Right panel -->
|
||||
<Control SizeFlagsHorizontal="FillExpand">
|
||||
<VBoxContainer>
|
||||
<!-- Player list -->
|
||||
<cui:NanoHeading Text="{Loc 'Online Players'}" />
|
||||
<MarginContainer SizeFlagsVertical="FillExpand"
|
||||
MarginRightOverride="3" MarginLeftOverride="3"
|
||||
MarginBottomOverride="3" MarginTopOverride="3">
|
||||
<cui:LobbyPlayerList Name="COnlinePlayerList"
|
||||
SizeFlagsHorizontal="FillExpand"
|
||||
SizeFlagsVertical="FillExpand" />
|
||||
</MarginContainer>
|
||||
<!-- Server info -->
|
||||
<cui:NanoHeading Text="{Loc 'Server Info'}" />
|
||||
<MarginContainer SizeFlagsVertical="FillExpand"
|
||||
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>
|
||||
124
Content.Client/UserInterface/LobbyGui.xaml.cs
Normal file
124
Content.Client/UserInterface/LobbyGui.xaml.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using Content.Client.Chat;
|
||||
using Content.Client.Interfaces;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.UserInterface
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
internal sealed partial class LobbyGui : Control
|
||||
{
|
||||
public Label ServerName => CServerName;
|
||||
public Label StartTime => CStartTime;
|
||||
public Button ReadyButton => CReadyButton;
|
||||
public Button ObserveButton => CObserveButton;
|
||||
public Button OptionsButton => COptionsButton;
|
||||
public Button LeaveButton => CLeaveButton;
|
||||
public ChatBox Chat => CChat;
|
||||
public VBoxContainer VoteContainer => CVoteContainer;
|
||||
public LobbyPlayerList OnlinePlayerList => COnlinePlayerList;
|
||||
public ServerInfo ServerInfo => CServerInfo;
|
||||
public LobbyCharacterPreviewPanel CharacterPreview { get; }
|
||||
|
||||
public LobbyGui(IEntityManager entityManager,
|
||||
IClientPreferencesManager preferencesManager)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ServerName.SizeFlagsHorizontal = SizeFlags.Expand | SizeFlags.ShrinkCenter;
|
||||
|
||||
CharacterPreview = new LobbyCharacterPreviewPanel(
|
||||
entityManager,
|
||||
preferencesManager)
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.None
|
||||
};
|
||||
|
||||
CLeftPanelContainer.AddChild(CharacterPreview);
|
||||
CharacterPreview.SetPositionFirst();
|
||||
}
|
||||
}
|
||||
|
||||
public class LobbyPlayerList : Control
|
||||
{
|
||||
private readonly ScrollContainer _scroll;
|
||||
private readonly VBoxContainer _vBox;
|
||||
|
||||
public LobbyPlayerList()
|
||||
{
|
||||
var panel = new PanelContainer()
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#202028")},
|
||||
};
|
||||
_vBox = new VBoxContainer();
|
||||
_scroll = new ScrollContainer();
|
||||
_scroll.AddChild(_vBox);
|
||||
panel.AddChild(_scroll);
|
||||
AddChild(panel);
|
||||
}
|
||||
|
||||
// Adds a row
|
||||
public void AddItem(string name, string status)
|
||||
{
|
||||
var hbox = new HBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
};
|
||||
|
||||
// Player Name
|
||||
hbox.AddChild(new PanelContainer()
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = Color.FromHex("#373744"),
|
||||
ContentMarginBottomOverride = 2,
|
||||
ContentMarginLeftOverride = 4,
|
||||
ContentMarginRightOverride = 4,
|
||||
ContentMarginTopOverride = 2
|
||||
},
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
Text = name
|
||||
}
|
||||
},
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||
});
|
||||
// Status
|
||||
hbox.AddChild(new PanelContainer()
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = Color.FromHex("#373744"),
|
||||
ContentMarginBottomOverride = 2,
|
||||
ContentMarginLeftOverride = 4,
|
||||
ContentMarginRightOverride = 4,
|
||||
ContentMarginTopOverride = 2
|
||||
},
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
Text = status
|
||||
}
|
||||
},
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SizeFlagsStretchRatio = 0.2f,
|
||||
});
|
||||
|
||||
_vBox.AddChild(hbox);
|
||||
}
|
||||
|
||||
// Deletes all rows
|
||||
public void Clear()
|
||||
{
|
||||
_vBox.RemoveAllChildren();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ using Content.Client.Utility;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.UserInterface.Stylesheets
|
||||
@@ -9,10 +11,13 @@ namespace Content.Client.UserInterface.Stylesheets
|
||||
public abstract class StyleBase
|
||||
{
|
||||
public const string ClassHighDivider = "HighDivider";
|
||||
public const string ClassLowDivider = "LowDivider";
|
||||
public const string StyleClassLabelHeading = "LabelHeading";
|
||||
public const string StyleClassLabelSubText = "LabelSubText";
|
||||
public const string StyleClassItalic = "Italic";
|
||||
|
||||
public const string ClassAngleRect = "AngleRect";
|
||||
|
||||
public const string ButtonOpenRight = "OpenRight";
|
||||
public const string ButtonOpenLeft = "OpenLeft";
|
||||
public const string ButtonOpenBoth = "OpenBoth";
|
||||
@@ -30,10 +35,13 @@ namespace Content.Client.UserInterface.Stylesheets
|
||||
protected StyleBoxTexture BaseButtonOpenBoth { get; }
|
||||
protected StyleBoxTexture BaseButtonSquare { get; }
|
||||
|
||||
protected StyleBoxTexture BaseAngleRect { get; }
|
||||
|
||||
protected StyleBase(IResourceCache resCache)
|
||||
{
|
||||
var notoSans12 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 12);
|
||||
var notoSans12Italic = resCache.GetFont("/Fonts/NotoSans/NotoSans-Italic.ttf", 12);
|
||||
var textureCloseButton = resCache.GetTexture("/Textures/Interface/Nano/cross.svg.png");
|
||||
|
||||
// Button styles.
|
||||
var buttonTex = resCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
|
||||
@@ -80,6 +88,12 @@ namespace Content.Client.UserInterface.Stylesheets
|
||||
BaseButtonSquare.SetPadding(StyleBox.Margin.Right, 2);
|
||||
BaseButtonSquare.SetPadding(StyleBox.Margin.Left, 1);
|
||||
|
||||
BaseAngleRect = new StyleBoxTexture
|
||||
{
|
||||
Texture = buttonTex,
|
||||
};
|
||||
BaseAngleRect.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||
|
||||
BaseRules = new[]
|
||||
{
|
||||
// Default font.
|
||||
@@ -97,6 +111,33 @@ namespace Content.Client.UserInterface.Stylesheets
|
||||
{
|
||||
new StyleProperty("font", notoSans12Italic),
|
||||
}),
|
||||
|
||||
// Window close button base texture.
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(TextureButton), new[] {SS14Window.StyleClassWindowCloseButton}, null,
|
||||
null),
|
||||
new[]
|
||||
{
|
||||
new StyleProperty(TextureButton.StylePropertyTexture, textureCloseButton),
|
||||
new StyleProperty(Control.StylePropertyModulateSelf, Color.FromHex("#4B596A")),
|
||||
}),
|
||||
// Window close button hover.
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(TextureButton), new[] {SS14Window.StyleClassWindowCloseButton}, null,
|
||||
new[] {TextureButton.StylePseudoClassHover}),
|
||||
new[]
|
||||
{
|
||||
new StyleProperty(Control.StylePropertyModulateSelf, Color.FromHex("#7F3636")),
|
||||
}),
|
||||
// Window close button pressed.
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(TextureButton), new[] {SS14Window.StyleClassWindowCloseButton}, null,
|
||||
new[] {TextureButton.StylePseudoClassPressed}),
|
||||
new[]
|
||||
{
|
||||
new StyleProperty(Control.StylePropertyModulateSelf, Color.FromHex("#753131")),
|
||||
}),
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,6 @@ namespace Content.Client.UserInterface.Stylesheets
|
||||
var notoSansBold16 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Bold.ttf", 16);
|
||||
var notoSansBold18 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Bold.ttf", 18);
|
||||
var notoSansBold20 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Bold.ttf", 20);
|
||||
var textureCloseButton = resCache.GetTexture("/Textures/Interface/Nano/cross.svg.png");
|
||||
var windowHeaderTex = resCache.GetTexture("/Textures/Interface/Nano/window_header.png");
|
||||
var windowHeader = new StyleBoxTexture
|
||||
{
|
||||
@@ -428,31 +427,6 @@ namespace Content.Client.UserInterface.Stylesheets
|
||||
{
|
||||
new StyleProperty(PanelContainer.StylePropertyPanel, windowHeader),
|
||||
}),
|
||||
// Window close button base texture.
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(TextureButton), new[] {SS14Window.StyleClassWindowCloseButton}, null,
|
||||
null),
|
||||
new[]
|
||||
{
|
||||
new StyleProperty(TextureButton.StylePropertyTexture, textureCloseButton),
|
||||
new StyleProperty(Control.StylePropertyModulateSelf, Color.FromHex("#4B596A")),
|
||||
}),
|
||||
// Window close button hover.
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(TextureButton), new[] {SS14Window.StyleClassWindowCloseButton}, null,
|
||||
new[] {TextureButton.StylePseudoClassHover}),
|
||||
new[]
|
||||
{
|
||||
new StyleProperty(Control.StylePropertyModulateSelf, Color.FromHex("#7F3636")),
|
||||
}),
|
||||
// Window close button pressed.
|
||||
new StyleRule(
|
||||
new SelectorElement(typeof(TextureButton), new[] {SS14Window.StyleClassWindowCloseButton}, null,
|
||||
new[] {TextureButton.StylePseudoClassPressed}),
|
||||
new[]
|
||||
{
|
||||
new StyleProperty(Control.StylePropertyModulateSelf, Color.FromHex("#753131")),
|
||||
}),
|
||||
|
||||
// Shapes for the buttons.
|
||||
Element<ContainerButton>().Class(ContainerButton.StyleClassButton)
|
||||
@@ -1061,7 +1035,12 @@ namespace Content.Client.UserInterface.Stylesheets
|
||||
new StyleRule(new SelectorElement(typeof(PanelContainer), new []{ ClassHighDivider}, null, null), new []
|
||||
{
|
||||
new StyleProperty(PanelContainer.StylePropertyPanel, new StyleBoxFlat { BackgroundColor = NanoGold, ContentMarginBottomOverride = 2, ContentMarginLeftOverride = 2}),
|
||||
})
|
||||
}),
|
||||
|
||||
Element<PanelContainer>().Class(ClassAngleRect)
|
||||
.Prop(PanelContainer.StylePropertyPanel, BaseAngleRect)
|
||||
.Prop(Control.StylePropertyModulateSelf, Color.FromHex("#25252A")),
|
||||
|
||||
}).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,20 @@ namespace Content.Client.UserInterface.Stylesheets
|
||||
var notoSans10 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 10);
|
||||
var notoSansBold16 = resCache.GetFont("/Fonts/NotoSans/NotoSans-Bold.ttf", 16);
|
||||
|
||||
var progressBarBackground = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = new Color(0.25f, 0.25f, 0.25f)
|
||||
};
|
||||
progressBarBackground.SetContentMarginOverride(StyleBox.Margin.Vertical, 5);
|
||||
|
||||
var progressBarForeground = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = new Color(0.25f, 0.50f, 0.25f)
|
||||
};
|
||||
progressBarForeground.SetContentMarginOverride(StyleBox.Margin.Vertical, 5);
|
||||
|
||||
var textureInvertedTriangle = resCache.GetTexture("/Textures/Interface/Nano/inverted_triangle.svg.png");
|
||||
|
||||
Stylesheet = new Stylesheet(BaseRules.Concat(new StyleRule[]
|
||||
{
|
||||
Element<Label>().Class(StyleClassLabelHeading)
|
||||
@@ -45,6 +59,15 @@ namespace Content.Client.UserInterface.Stylesheets
|
||||
{
|
||||
BackgroundColor = SpaceRed, ContentMarginBottomOverride = 2, ContentMarginLeftOverride = 2
|
||||
}),
|
||||
|
||||
Element<PanelContainer>().Class(ClassLowDivider)
|
||||
.Prop(PanelContainer.StylePropertyPanel, new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = Color.FromHex("#444"),
|
||||
ContentMarginLeftOverride = 2,
|
||||
ContentMarginBottomOverride = 2
|
||||
}),
|
||||
|
||||
// Shapes for the buttons.
|
||||
Element<ContainerButton>().Class(ContainerButton.StyleClassButton)
|
||||
.Prop(ContainerButton.StylePropertyStyleBox, BaseButton),
|
||||
@@ -103,11 +126,42 @@ namespace Content.Client.UserInterface.Stylesheets
|
||||
Element<Label>().Class(ContainerButton.StyleClassButton)
|
||||
.Prop(Label.StylePropertyAlignMode, Label.AlignMode.Center),
|
||||
|
||||
Element<PanelContainer>().Class(ClassAngleRect)
|
||||
.Prop(PanelContainer.StylePropertyPanel, BaseAngleRect)
|
||||
.Prop(Control.StylePropertyModulateSelf, Color.FromHex("#202030")),
|
||||
|
||||
Child()
|
||||
.Parent(Element<Button>().Class(ContainerButton.StylePseudoClassDisabled))
|
||||
.Child(Element<Label>())
|
||||
.Prop("font-color", Color.FromHex("#E5E5E581")),
|
||||
|
||||
Element<ProgressBar>()
|
||||
.Prop(ProgressBar.StylePropertyBackground, progressBarBackground)
|
||||
.Prop(ProgressBar.StylePropertyForeground, progressBarForeground),
|
||||
|
||||
// OptionButton
|
||||
Element<OptionButton>()
|
||||
.Prop(ContainerButton.StylePropertyStyleBox, BaseButton),
|
||||
|
||||
Element<OptionButton>().Pseudo(ContainerButton.StylePseudoClassNormal)
|
||||
.Prop(Control.StylePropertyModulateSelf, ButtonColorDefault),
|
||||
|
||||
Element<OptionButton>().Pseudo(ContainerButton.StylePseudoClassHover)
|
||||
.Prop(Control.StylePropertyModulateSelf, ButtonColorHovered),
|
||||
|
||||
Element<OptionButton>().Pseudo(ContainerButton.StylePseudoClassPressed)
|
||||
.Prop(Control.StylePropertyModulateSelf, ButtonColorPressed),
|
||||
|
||||
Element<OptionButton>().Pseudo(ContainerButton.StylePseudoClassDisabled)
|
||||
.Prop(Control.StylePropertyModulateSelf, ButtonColorDisabled),
|
||||
|
||||
Element<TextureRect>().Class(OptionButton.StyleClassOptionTriangle)
|
||||
.Prop(TextureRect.StylePropertyTexture, textureInvertedTriangle),
|
||||
|
||||
Element<Label>().Class(OptionButton.StyleClassOptionButton)
|
||||
.Prop(Label.StylePropertyAlignMode, Label.AlignMode.Center),
|
||||
|
||||
|
||||
}).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
9
Content.Client/UserInterface/TopNotification.cs
Normal file
9
Content.Client/UserInterface/TopNotification.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.UserInterface
|
||||
{
|
||||
public class TopNotification : Control
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
39
Content.Client/Voting/VoteCallMenu.xaml
Normal file
39
Content.Client/Voting/VoteCallMenu.xaml
Normal file
@@ -0,0 +1,39 @@
|
||||
<v:VoteCallMenu xmlns="https://spacestation14.io"
|
||||
xmlns:cuic="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:v="clr-namespace:Content.Client.Voting"
|
||||
MouseFilter="Stop" CustomMinimumSize="350 150">
|
||||
<PanelContainer StyleClasses="AngleRect" />
|
||||
<VBoxContainer>
|
||||
<HBoxContainer>
|
||||
<MarginContainer MarginLeftOverride="8" SizeFlagsHorizontal="FillExpand">
|
||||
<Label Text="{Loc 'Call Vote'}" VAlign="Center" StyleClasses="LabelHeading" />
|
||||
</MarginContainer>
|
||||
<MarginContainer MarginRightOverride="8">
|
||||
<TextureButton Name="CloseButton" StyleClasses="windowCloseButton"
|
||||
SizeFlagsVertical="ShrinkCenter" />
|
||||
</MarginContainer>
|
||||
</HBoxContainer>
|
||||
<cuic:HighDivider />
|
||||
|
||||
<MarginContainer SizeFlagsHorizontal="Fill" SizeFlagsVertical="Expand"
|
||||
MarginLeftOverride="8" MarginRightOverride="8" MarginTopOverride="2">
|
||||
<HBoxContainer>
|
||||
<OptionButton Name="VoteTypeButton" SizeFlagsHorizontal="FillExpand" />
|
||||
<Control SizeFlagsHorizontal="FillExpand">
|
||||
<OptionButton Name="VoteSecondButton" Visible="False" />
|
||||
</Control>
|
||||
</HBoxContainer>
|
||||
</MarginContainer>
|
||||
|
||||
<MarginContainer SizeFlagsHorizontal="Fill"
|
||||
MarginLeftOverride="8" MarginRightOverride="8" MarginBottomOverride="2">
|
||||
<Button Name="CreateButton" Text="{Loc 'Call Vote'}" />
|
||||
</MarginContainer>
|
||||
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
<MarginContainer MarginLeftOverride="12">
|
||||
<Label StyleClasses="LabelSubText" Text="{Loc 'Powered by Robust™ Anti-Tamper Technology'}" />
|
||||
</MarginContainer>
|
||||
|
||||
</VBoxContainer>
|
||||
</v:VoteCallMenu>
|
||||
111
Content.Client/Voting/VoteCallMenu.xaml.cs
Normal file
111
Content.Client/Voting/VoteCallMenu.xaml.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
#nullable enable
|
||||
using Content.Client.UserInterface.Stylesheets;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.Voting
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public partial class VoteCallMenu : BaseWindow
|
||||
{
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
|
||||
public static readonly (string name, string id, (string name, string id)[]? secondaries)[] AvailableVoteTypes =
|
||||
{
|
||||
("Restart round", "restart", null),
|
||||
("Next gamemode", "preset", null)
|
||||
};
|
||||
|
||||
public VoteCallMenu()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
|
||||
CloseButton.OnPressed += _ => Close();
|
||||
|
||||
for (var i = 0; i < AvailableVoteTypes.Length; i++)
|
||||
{
|
||||
var (text, _, _) = AvailableVoteTypes[i];
|
||||
VoteTypeButton.AddItem(Loc.GetString(text), i);
|
||||
}
|
||||
|
||||
VoteTypeButton.OnItemSelected += VoteTypeSelected;
|
||||
VoteSecondButton.OnItemSelected += VoteSecondSelected;
|
||||
CreateButton.OnPressed += CreatePressed;
|
||||
}
|
||||
|
||||
private void CreatePressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
var typeId = VoteTypeButton.SelectedId;
|
||||
var (_, typeKey, secondaries) = AvailableVoteTypes[typeId];
|
||||
|
||||
if (secondaries != null)
|
||||
{
|
||||
var secondaryId = VoteSecondButton.SelectedId;
|
||||
var (_, secondKey) = secondaries[secondaryId];
|
||||
|
||||
_consoleHost.LocalShell.RemoteExecuteCommand($"createvote {typeKey} {secondKey}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_consoleHost.LocalShell.RemoteExecuteCommand($"createvote {typeKey}");
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
private static void VoteSecondSelected(OptionButton.ItemSelectedEventArgs obj)
|
||||
{
|
||||
obj.Button.SelectId(obj.Id);
|
||||
}
|
||||
|
||||
private void VoteTypeSelected(OptionButton.ItemSelectedEventArgs obj)
|
||||
{
|
||||
VoteTypeButton.SelectId(obj.Id);
|
||||
|
||||
var (_, _, options) = AvailableVoteTypes[obj.Id];
|
||||
if (options == null)
|
||||
{
|
||||
VoteSecondButton.Visible = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
VoteSecondButton.Visible = true;
|
||||
VoteSecondButton.Clear();
|
||||
|
||||
for (var i = 0; i < options.Length; i++)
|
||||
{
|
||||
var (text, _) = options[i];
|
||||
VoteSecondButton.AddItem(Loc.GetString(text), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
|
||||
{
|
||||
return DragMode.Move;
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class VoteMenuCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "votemenu";
|
||||
public string Description => "Opens the voting menu";
|
||||
public string Help => "Usage: votemenu";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
new VoteCallMenu().OpenCentered();
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Content.Client/Voting/VoteCallMenuButton.cs
Normal file
49
Content.Client/Voting/VoteCallMenuButton.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Client.Voting
|
||||
{
|
||||
/// <summary>
|
||||
/// LITERALLY just a button that opens the vote call menu.
|
||||
/// Automatically disables itself if the client cannot call votes.
|
||||
/// </summary>
|
||||
public sealed class VoteCallMenuButton : Button
|
||||
{
|
||||
[Dependency] private readonly IVoteManager _voteManager = default!;
|
||||
|
||||
public VoteCallMenuButton()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
Text = Loc.GetString("Call vote");
|
||||
OnPressed += OnOnPressed;
|
||||
}
|
||||
|
||||
private void OnOnPressed(ButtonEventArgs obj)
|
||||
{
|
||||
var menu = new VoteCallMenu();
|
||||
menu.OpenCentered();
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
{
|
||||
base.EnteredTree();
|
||||
|
||||
UpdateCanCall(_voteManager.CanCallVote);
|
||||
_voteManager.CanCallVoteChanged += UpdateCanCall;
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
|
||||
_voteManager.CanCallVoteChanged += UpdateCanCall;
|
||||
}
|
||||
|
||||
private void UpdateCanCall(bool canCall)
|
||||
{
|
||||
Disabled = !canCall;
|
||||
}
|
||||
}
|
||||
}
|
||||
196
Content.Client/Voting/VoteManager.cs
Normal file
196
Content.Client/Voting/VoteManager.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Network.NetMessages;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Content.Client.Voting
|
||||
{
|
||||
public interface IVoteManager
|
||||
{
|
||||
void Initialize();
|
||||
void SendCastVote(int voteId, int option);
|
||||
void ClearPopupContainer();
|
||||
void SetPopupContainer(Control container);
|
||||
bool CanCallVote { get; }
|
||||
event Action<bool> CanCallVoteChanged;
|
||||
}
|
||||
|
||||
public sealed class VoteManager : IVoteManager
|
||||
{
|
||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _console = default!;
|
||||
|
||||
private readonly Dictionary<int, ActiveVote> _votes = new();
|
||||
private readonly Dictionary<int, VotePopup> _votePopups = new();
|
||||
private Control? _popupContainer;
|
||||
|
||||
public bool CanCallVote { get; private set; }
|
||||
public event Action<bool>? CanCallVoteChanged;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_netManager.RegisterNetMessage<MsgVoteData>(MsgVoteData.NAME, ReceiveVoteData);
|
||||
_netManager.RegisterNetMessage<MsgVoteCanCall>(MsgVoteCanCall.NAME, ReceiveVoteCanCall);
|
||||
}
|
||||
|
||||
public void ClearPopupContainer()
|
||||
{
|
||||
if (_popupContainer == null)
|
||||
return;
|
||||
|
||||
if (!_popupContainer.Disposed)
|
||||
{
|
||||
foreach (var popup in _votePopups.Values)
|
||||
{
|
||||
popup.Orphan();
|
||||
}
|
||||
}
|
||||
|
||||
_votePopups.Clear();
|
||||
_popupContainer = null;
|
||||
}
|
||||
|
||||
public void SetPopupContainer(Control container)
|
||||
{
|
||||
if (_popupContainer != null)
|
||||
{
|
||||
ClearPopupContainer();
|
||||
}
|
||||
|
||||
_popupContainer = container;
|
||||
|
||||
foreach (var (vId, vote) in _votes)
|
||||
{
|
||||
var popup = new VotePopup(vote);
|
||||
|
||||
_votePopups.Add(vId, popup);
|
||||
_popupContainer.AddChild(popup);
|
||||
popup.UpdateData();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReceiveVoteData(MsgVoteData message)
|
||||
{
|
||||
var @new = false;
|
||||
var voteId = message.VoteId;
|
||||
if (!_votes.TryGetValue(voteId, out var existingVote))
|
||||
{
|
||||
if (!message.VoteActive)
|
||||
{
|
||||
// Got "vote inactive" for nonexistent vote???
|
||||
return;
|
||||
}
|
||||
|
||||
@new = true;
|
||||
|
||||
// New vote from the server.
|
||||
var vote = new ActiveVote(voteId)
|
||||
{
|
||||
Entries = message.Options
|
||||
.Select(c => new VoteEntry(c.name))
|
||||
.ToArray()
|
||||
};
|
||||
|
||||
existingVote = vote;
|
||||
_votes.Add(voteId, vote);
|
||||
}
|
||||
else if (!message.VoteActive)
|
||||
{
|
||||
// Remove gone vote.
|
||||
_votes.Remove(voteId);
|
||||
if (_votePopups.TryGetValue(voteId, out var toRemove))
|
||||
{
|
||||
toRemove.Orphan();
|
||||
_votePopups.Remove(voteId);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Update vote data from incoming.
|
||||
if (message.IsYourVoteDirty)
|
||||
existingVote.OurVote = message.YourVote;
|
||||
// On the server, most of these params can't change.
|
||||
// It can't hurt to just re-set this stuff since I'm lazy and the server is sending it anyways, so...
|
||||
existingVote.Initiator = message.VoteInitiator;
|
||||
existingVote.Title = message.VoteTitle;
|
||||
existingVote.StartTime = _gameTiming.RealServerToLocal(message.StartTime);
|
||||
existingVote.EndTime = _gameTiming.RealServerToLocal(message.EndTime);
|
||||
|
||||
// Logger.Debug($"{existingVote.StartTime}, {existingVote.EndTime}, {_gameTiming.RealTime}");
|
||||
|
||||
for (var i = 0; i < message.Options.Length; i++)
|
||||
{
|
||||
existingVote.Entries[i].Votes = message.Options[i].votes;
|
||||
}
|
||||
|
||||
if (@new && _popupContainer != null)
|
||||
{
|
||||
var popup = new VotePopup(existingVote);
|
||||
|
||||
_votePopups.Add(voteId, popup);
|
||||
_popupContainer.AddChild(popup);
|
||||
}
|
||||
|
||||
if (_votePopups.TryGetValue(voteId, out var ePopup))
|
||||
{
|
||||
ePopup.UpdateData();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReceiveVoteCanCall(MsgVoteCanCall message)
|
||||
{
|
||||
if (CanCallVote == message.CanCall)
|
||||
return;
|
||||
|
||||
CanCallVote = message.CanCall;
|
||||
CanCallVoteChanged?.Invoke(CanCallVote);
|
||||
}
|
||||
|
||||
public void SendCastVote(int voteId, int option)
|
||||
{
|
||||
var data = _votes[voteId];
|
||||
// Update immediately to avoid any funny reconciliation bugs.
|
||||
// See also code in server side to avoid bulldozing this.
|
||||
data.OurVote = option;
|
||||
_console.LocalShell.RemoteExecuteCommand($"vote {voteId} {option}");
|
||||
}
|
||||
|
||||
public sealed class ActiveVote
|
||||
{
|
||||
public VoteEntry[] Entries = default!;
|
||||
|
||||
// Both of these are local RealTime (converted at NetMsg receive).
|
||||
public TimeSpan StartTime;
|
||||
public TimeSpan EndTime;
|
||||
public string Title = "";
|
||||
public string Initiator = "";
|
||||
public int? OurVote;
|
||||
public int Id;
|
||||
|
||||
public ActiveVote(int voteId)
|
||||
{
|
||||
Id = voteId;
|
||||
}
|
||||
}
|
||||
|
||||
public class VoteEntry
|
||||
{
|
||||
public string Text { get; }
|
||||
public int Votes { get; set; }
|
||||
|
||||
public VoteEntry(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Content.Client/Voting/VotePopup.xaml
Normal file
19
Content.Client/Voting/VotePopup.xaml
Normal file
@@ -0,0 +1,19 @@
|
||||
<ui:Control xmlns:ui="clr-namespace:Robust.Client.UserInterface;assembly=Robust.Client"
|
||||
xmlns:uic="clr-namespace:Robust.Client.UserInterface.Controls;assembly=Robust.Client">
|
||||
<uic:PanelContainer StyleClasses="AngleRect" />
|
||||
<uic:MarginContainer MarginLeftOverride="4" MarginRightOverride="4" MarginTopOverride="4" MarginBottomOverride="4">
|
||||
<uic:VBoxContainer>
|
||||
<uic:Label Name="VoteCaller" />
|
||||
<uic:Label Name="VoteTitle" />
|
||||
|
||||
<uic:GridContainer Columns="3" Name="VoteOptionsContainer" />
|
||||
<uic:HBoxContainer>
|
||||
<uic:MarginContainer SizeFlagsHorizontal="FillExpand" MarginLeftOverride="2" MarginRightOverride="2"
|
||||
MarginTopOverride="2" MarginBottomOverride="2">
|
||||
<uic:ProgressBar Name="TimeLeftBar" MinValue="0" MaxValue="1" />
|
||||
</uic:MarginContainer>
|
||||
<uic:Label Name="TimeLeftText" />
|
||||
</uic:HBoxContainer>
|
||||
</uic:VBoxContainer>
|
||||
</uic:MarginContainer>
|
||||
</ui:Control>
|
||||
83
Content.Client/Voting/VotePopup.xaml.cs
Normal file
83
Content.Client/Voting/VotePopup.xaml.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
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.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Voting
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public partial class VotePopup : Control
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IVoteManager _voteManager = default!;
|
||||
|
||||
private readonly VoteManager.ActiveVote _vote;
|
||||
private readonly Button[] _voteButtons;
|
||||
|
||||
public VotePopup(VoteManager.ActiveVote vote)
|
||||
{
|
||||
_vote = vote;
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
|
||||
|
||||
Modulate = Color.White.WithAlpha(0.75f);
|
||||
_voteButtons = new Button[vote.Entries.Length];
|
||||
var group = new ButtonGroup();
|
||||
|
||||
for (var i = 0; i < _voteButtons.Length; i++)
|
||||
{
|
||||
var button = new Button
|
||||
{
|
||||
ToggleMode = true,
|
||||
Group = group
|
||||
};
|
||||
_voteButtons[i] = button;
|
||||
VoteOptionsContainer.AddChild(button);
|
||||
var i1 = i;
|
||||
button.OnPressed += _ => _voteManager.SendCastVote(vote.Id, i1);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateData()
|
||||
{
|
||||
VoteTitle.Text = _vote.Title;
|
||||
VoteCaller.Text = Loc.GetString("{0} called a vote:", _vote.Initiator);
|
||||
|
||||
for (var i = 0; i < _voteButtons.Length; i++)
|
||||
{
|
||||
var entry = _vote.Entries[i];
|
||||
_voteButtons[i].Text = Loc.GetString("{0} ({1})", entry.Text, entry.Votes);
|
||||
|
||||
if (_vote.OurVote == i)
|
||||
_voteButtons[i].Pressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
// Logger.Debug($"{_gameTiming.ServerTime}, {_vote.StartTime}, {_vote.EndTime}");
|
||||
|
||||
var curTime = _gameTiming.RealTime;
|
||||
var timeLeft = _vote.EndTime - curTime;
|
||||
if (timeLeft < TimeSpan.Zero)
|
||||
timeLeft = TimeSpan.Zero;
|
||||
|
||||
// Round up a second.
|
||||
timeLeft = TimeSpan.FromSeconds(Math.Ceiling(timeLeft.TotalSeconds));
|
||||
|
||||
TimeLeftBar.Value = Math.Min(1, (float) ((curTime.TotalSeconds - _vote.StartTime.TotalSeconds) /
|
||||
(_vote.EndTime.TotalSeconds - _vote.StartTime.TotalSeconds)));
|
||||
|
||||
TimeLeftText.Text = $"{timeLeft:m\\:ss}";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user