News UI overhaul and PDA notifications (#19610)

This commit is contained in:
Julian Giebel
2024-02-27 02:38:00 +01:00
committed by GitHub
parent f284b43ff6
commit 0752acdc2c
54 changed files with 1381 additions and 708 deletions

View File

@@ -1,50 +0,0 @@
using Content.Client.UserInterface.Fragments;
using Content.Shared.CartridgeLoader.Cartridges;
using Content.Shared.CartridgeLoader;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
namespace Content.Client.CartridgeLoader.Cartridges;
public sealed partial class NewsReadUi : UIFragment
{
private NewsReadUiFragment? _fragment;
public override Control GetUIFragmentRoot()
{
return _fragment!;
}
public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
{
_fragment = new NewsReadUiFragment();
_fragment.OnNextButtonPressed += () =>
{
SendNewsReadMessage(NewsReadUiAction.Next, userInterface);
};
_fragment.OnPrevButtonPressed += () =>
{
SendNewsReadMessage(NewsReadUiAction.Prev, userInterface);
};
_fragment.OnNotificationSwithPressed += () =>
{
SendNewsReadMessage(NewsReadUiAction.NotificationSwith, userInterface);
};
}
public override void UpdateState(BoundUserInterfaceState state)
{
if (state is NewsReadBoundUserInterfaceState cast)
_fragment?.UpdateState(cast.Article, cast.TargetNum, cast.TotalNum, cast.NotificationOn);
else if (state is NewsReadEmptyBoundUserInterfaceState empty)
_fragment?.UpdateEmptyState(empty.NotificationOn);
}
private void SendNewsReadMessage(NewsReadUiAction action, BoundUserInterface userInterface)
{
var newsMessage = new NewsReadUiMessageEvent(action);
var message = new CartridgeUiMessage(newsMessage);
userInterface.SendMessage(message);
}
}

View File

@@ -0,0 +1,54 @@
using Content.Client.UserInterface.Fragments;
using Content.Shared.CartridgeLoader.Cartridges;
using Content.Shared.CartridgeLoader;
using Robust.Client.UserInterface;
namespace Content.Client.CartridgeLoader.Cartridges;
public sealed partial class NewsReaderUi : UIFragment
{
private NewsReaderUiFragment? _fragment;
public override Control GetUIFragmentRoot()
{
return _fragment!;
}
public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
{
_fragment = new NewsReaderUiFragment();
_fragment.OnNextButtonPressed += () =>
{
SendNewsReaderMessage(NewsReaderUiAction.Next, userInterface);
};
_fragment.OnPrevButtonPressed += () =>
{
SendNewsReaderMessage(NewsReaderUiAction.Prev, userInterface);
};
_fragment.OnNotificationSwithPressed += () =>
{
SendNewsReaderMessage(NewsReaderUiAction.NotificationSwitch, userInterface);
};
}
public override void UpdateState(BoundUserInterfaceState state)
{
switch (state)
{
case NewsReaderBoundUserInterfaceState cast:
_fragment?.UpdateState(cast.Article, cast.TargetNum, cast.TotalNum, cast.NotificationOn);
break;
case NewsReaderEmptyBoundUserInterfaceState empty:
_fragment?.UpdateEmptyState(empty.NotificationOn);
break;
}
}
private void SendNewsReaderMessage(NewsReaderUiAction action, BoundUserInterface userInterface)
{
var newsMessage = new NewsReaderUiMessageEvent(action);
var message = new CartridgeUiMessage(newsMessage);
userInterface.SendMessage(message);
}
}

View File

@@ -1,21 +1,30 @@
<cartridges:NewsReadUiFragment xmlns:cartridges="clr-namespace:Content.Client.CartridgeLoader.Cartridges"
<cartridges:NewsReaderUiFragment xmlns:cartridges="clr-namespace:Content.Client.CartridgeLoader.Cartridges"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns="https://spacestation14.io" Margin="1 0 2 0">
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns="https://spacestation14.io"
Margin="1 0 2 0"
Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True">
<PanelContainer StyleClasses="BackgroundDark"></PanelContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="5,5,5,5">
<Button
Name="Prev"
MinWidth="64"
Disabled="True"
HorizontalAlignment="Left"
Text="{Loc 'news-read-ui-past-text'}"
Text="{Loc 'news-read-ui-prev-text'}"
ToolTip="{Loc 'news-read-ui-prev-tooltip'}"
Access="Public"
HorizontalExpand="True"/>
<Button
Name="Next"
MinWidth="64"
Disabled="True"
HorizontalAlignment="Right"
Text="{Loc 'news-read-ui-next-text'}" />
Text="{Loc 'news-read-ui-next-text'}"
ToolTip="{Loc 'news-read-ui-next-tooltip'}"/>
</BoxContainer>
<controls:StripeBack Name="АrticleNameContainer">
<PanelContainer>
@@ -46,9 +55,11 @@
</PanelContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="5,5,5,5">
<Button
Name="NotificationSwith"
Name="NotificationSwitch"
ToolTip="{Loc news-reader-ui-mute-tooltip}"
MinWidth="20"/>
<RichTextLabel Margin="5,2,2,2" Name="ShareTime" VerticalAlignment="Top"/>
<customControls:VSeparator Margin="2 0"/>
<RichTextLabel Margin="5,2,2,2" Name="Author" VerticalAlignment="Top" HorizontalAlignment="Right"/>
</BoxContainer>
</cartridges:NewsReadUiFragment>
</cartridges:NewsReaderUiFragment>

View File

@@ -7,23 +7,20 @@ using Robust.Client.UserInterface.XAML;
namespace Content.Client.CartridgeLoader.Cartridges;
[GenerateTypedNameReferences]
public sealed partial class NewsReadUiFragment : BoxContainer
public sealed partial class NewsReaderUiFragment : BoxContainer
{
public event Action? OnNextButtonPressed;
public event Action? OnPrevButtonPressed;
public event Action? OnNotificationSwithPressed;
public NewsReadUiFragment()
public NewsReaderUiFragment()
{
RobustXamlLoader.Load(this);
Orientation = LayoutOrientation.Vertical;
HorizontalExpand = true;
VerticalExpand = true;
Next.OnPressed += _ => OnNextButtonPressed?.Invoke();
Prev.OnPressed += _ => OnPrevButtonPressed?.Invoke();
NotificationSwith.OnPressed += _ => OnNotificationSwithPressed?.Invoke();
NotificationSwitch.OnPressed += _ => OnNotificationSwithPressed?.Invoke();
}
public void UpdateState(NewsArticle article, int targetNum, int totalNum, bool notificationOn)
@@ -33,17 +30,20 @@ public sealed partial class NewsReadUiFragment : BoxContainer
ShareTime.Visible = true;
Author.Visible = true;
PageName.Text = article.Name;
PageName.Text = article.Title;
PageText.SetMarkup(article.Content);
PageNum.Text = $"{targetNum}/{totalNum}";
NotificationSwith.Text = Loc.GetString(notificationOn ? "news-read-ui-notification-on" : "news-read-ui-notification-off");
NotificationSwitch.Text = Loc.GetString(notificationOn ? "news-read-ui-notification-on" : "news-read-ui-notification-off");
string shareTime = article.ShareTime.ToString("hh\\:mm\\:ss");
string shareTime = article.ShareTime.ToString(@"hh\:mm\:ss");
ShareTime.SetMarkup(Loc.GetString("news-read-ui-time-prefix-text") + " " + shareTime);
Author.SetMarkup(Loc.GetString("news-read-ui-author-prefix") + " " + (article.Author != null ? article.Author : Loc.GetString("news-read-ui-no-author")));
Prev.Disabled = targetNum <= 1;
Next.Disabled = targetNum >= totalNum;
}
public void UpdateEmptyState(bool notificationOn)
@@ -55,6 +55,6 @@ public sealed partial class NewsReadUiFragment : BoxContainer
PageName.Text = Loc.GetString("news-read-ui-not-found-text");
NotificationSwith.Text = Loc.GetString(notificationOn ? "news-read-ui-notification-on" : "news-read-ui-notification-off");
NotificationSwitch.Text = Loc.GetString(notificationOn ? "news-read-ui-notification-on" : "news-read-ui-notification-off");
}
}

View File

@@ -0,0 +1,71 @@
<Control xmlns="https://spacestation14.io"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
MouseFilter="Stop">
<PanelContainer StyleClasses="BackgroundOpenLeft"/>
<PanelContainer>
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BorderColor="#25252A" BorderThickness="0 0 0 3"/>
</PanelContainer.PanelOverride>
</PanelContainer>
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<Control Margin="0 0 1 0">
<PanelContainer StyleClasses="WindowHeadingBackground" />
<BoxContainer Margin="4 2 8 0" Orientation="Horizontal">
<Label Name="Title" Text="{Loc news-write-ui-new-article}"
HorizontalExpand="True" VAlign="Center" StyleClasses="FancyWindowTitle" />
</BoxContainer>
</Control>
<PanelContainer StyleClasses="LowDivider" Margin="0 0 1 0"/>
<BoxContainer Orientation="Horizontal">
<Label Text="Title:" Margin="17 10 0 9" VerticalAlignment="Center"/>
<LineEdit Name="TitleField" Margin="6 10 0 9" MinWidth="260" MinHeight="23" Access="Public"/>
<Control HorizontalExpand="True" />
<Label Name="RichTextInfoLabel" Text="?" MouseFilter="Pass" Margin="14 0" StyleClasses="LabelSecondaryColor"/>
</BoxContainer>
<Control Name="TextEditPanel" VerticalExpand="True" Margin="11 0 11 0">
<PanelContainer>
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#202023" BorderThickness="1" BorderColor="#3B3E56"/>
</PanelContainer.PanelOverride>
</PanelContainer>
<TextEdit Name="ContentField" Margin="0 1" Access="Public"/>
</Control>
<Control Name="PreviewPanel" Visible="False" VerticalExpand="True" Margin="11 0 11 0">
<PanelContainer>
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BorderThickness="1" BorderColor="#3B3E56"/>
</PanelContainer.PanelOverride>
</PanelContainer>
<ScrollContainer HScrollEnabled="True">
<RichTextLabel Name="PreviewLabel" VerticalAlignment="Top" Margin="9 3" MaxWidth="360"/>
</ScrollContainer>
</Control>
<BoxContainer Orientation="Horizontal" Margin="12 5 12 8">
<Control>
<Button Name="ButtonCancel" SetHeight="32" SetWidth="85"
StyleClasses="ButtonColorRed" Text="{Loc news-write-ui-cancel-text}"/>
</Control>
<Control HorizontalExpand="True"/>
<BoxContainer Orientation="Horizontal">
<Button Name="ButtonPreview" SetHeight="32" SetWidth="85"
StyleClasses="OpenRight" Text="{Loc news-write-ui-preview-text}"/>
<Button Name="ButtonPublish" SetHeight="32" SetWidth="85" Text="{Loc news-write-ui-publish-text}" Access="Public"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
<PanelContainer>
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BorderThickness="2 0 0 0" BorderColor="#1d1d22"/>
</PanelContainer.PanelOverride>
</PanelContainer>
<PanelContainer HorizontalAlignment="Left" VerticalAlignment="Top" SetHeight="27" SetWidth="2">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BorderColor="#2a2a2d" BorderThickness="0 0 0 2"/>
</PanelContainer.PanelOverride>
</PanelContainer>
<PanelContainer HorizontalAlignment="Left" VerticalAlignment="Top" SetHeight="25" SetWidth="2">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#1b1b1f"/>
</PanelContainer.PanelOverride>
</PanelContainer>
</Control>

View File

@@ -0,0 +1,109 @@
using Content.Client.Message;
using Content.Client.Stylesheets;
using Content.Shared.MassMedia.Systems;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
namespace Content.Client.MassMedia.Ui;
[GenerateTypedNameReferences]
public sealed partial class ArticleEditorPanel : Control
{
public event Action? PublishButtonPressed;
private bool _preview;
public ArticleEditorPanel()
{
RobustXamlLoader.Load(this);
ButtonPublish.StyleClasses.Add(StyleBase.ButtonOpenLeft);
ButtonPublish.StyleClasses.Add(StyleNano.StyleClassButtonColorGreen);
ContentField.GetChild(0).Margin = new Thickness(9, 3);
// Customize scrollbar width and margin. This is not possible in xaml
var scrollbar = ContentField.GetChild(1);
scrollbar.SetWidth = 6f;
scrollbar.Margin = new Thickness(9, 0, 2 , 0);
RichTextInfoLabel.TooltipSupplier = sender =>
{
var label = new RichTextLabel();
label.SetMarkup(Loc.GetString("news-write-ui-richtext-tooltip"));
var tooltip = new Tooltip();
tooltip.GetChild(0).Children.Clear();
tooltip.GetChild(0).Children.Add(label);
return tooltip;
};
ButtonPreview.OnPressed += OnPreview;
ButtonCancel.OnPressed += OnCancel;
ButtonPublish.OnPressed += OnPublish;
TitleField.OnTextChanged += args => OnTextChanged(args.Text.Length, args.Control, SharedNewsSystem.MaxTitleLength);
ContentField.OnTextChanged += args => OnTextChanged(Rope.CalcTotalLength(args.TextRope), args.Control, SharedNewsSystem.MaxContentLength);
}
private void OnTextChanged(long length, Control control, long maxLength)
{
if (length > maxLength)
{
control.ModulateSelfOverride = Color.Red;
control.ToolTip = Loc.GetString("news-writer-text-length-exceeded");
}
else
{
control.ModulateSelfOverride = null;
control.ToolTip = string.Empty;
}
}
private void OnPreview(BaseButton.ButtonEventArgs eventArgs)
{
_preview = !_preview;
TextEditPanel.Visible = !_preview;
PreviewPanel.Visible = _preview;
PreviewLabel.SetMarkup(Rope.Collapse(ContentField.TextRope));
}
private void OnCancel(BaseButton.ButtonEventArgs eventArgs)
{
Reset();
Visible = false;
}
private void OnPublish(BaseButton.ButtonEventArgs eventArgs)
{
PublishButtonPressed?.Invoke();
Reset();
Visible = false;
}
private void Reset()
{
_preview = false;
TextEditPanel.Visible = true;
PreviewPanel.Visible = false;
PreviewLabel.SetMarkup("");
TitleField.Text = "";
ContentField.TextRope = Rope.Leaf.Empty;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
ButtonPreview.OnPressed -= OnPreview;
ButtonCancel.OnPressed -= OnCancel;
ButtonPublish.OnPressed -= OnPublish;
}
}

View File

@@ -1,26 +0,0 @@
<Control xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="0 0 0 12">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
<PanelContainer HorizontalExpand="True" VerticalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#4c6530"/>
</PanelContainer.PanelOverride>
<Label Name="NameLabel" Margin="6 6 6 6" HorizontalAlignment="Center"/>
</PanelContainer>
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<PanelContainer HorizontalExpand="True" VerticalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#33333f"/>
</PanelContainer.PanelOverride>
<RichTextLabel Name="Author" HorizontalExpand="True" VerticalAlignment="Bottom" Margin="6 6 6 6"/>
<Button Name="Delete"
Text="{Loc 'news-write-ui-delete-text'}"
HorizontalAlignment="Right"
Margin="8 6 6 6"
Access="Public"/>
</PanelContainer>
</BoxContainer>
</BoxContainer>
</Control>

View File

@@ -1,27 +0,0 @@
using Content.Client.Message;
using Content.Shared.Research.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.MassMedia.Ui;
[GenerateTypedNameReferences]
public sealed partial class MiniArticleCardControl : Control
{
public Action? OnDeletePressed;
public int ArticleNum;
public MiniArticleCardControl(string name, string author)
{
RobustXamlLoader.Load(this);
NameLabel.Text = name;
Author.SetMarkup(author);
Delete.OnPressed += _ => OnDeletePressed?.Invoke();
}
}

View File

@@ -0,0 +1,29 @@
<Control xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Margin="0 0 0 8">
<PanelContainer StyleClasses="AngleRect" ModulateSelfOverride="#2b2b31"/>
<BoxContainer Orientation="Vertical" SetHeight="60">
<Control HorizontalExpand="True" SetHeight="27">
<PanelContainer>
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderColor="#3B3E56" BorderThickness="0 0 0 1"/>
</PanelContainer.PanelOverride>
</PanelContainer>
<Label Name="TitleLabel" Margin="12 0 6 0" HorizontalAlignment="Left"/>
</Control>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label FontColorOverride="#b1b1b2" StyleClasses="LabelSmall" Name="AuthorLabel" Margin="14 6 6 6"/>
<Control HorizontalExpand="True"/>
<Label FontColorOverride="#b1b1b2" StyleClasses="LabelSmall" Name="PublishTimeLabel" Margin="6 6 6 6"/>
<controls:ConfirmButton Name="DeleteButton" Text="{Loc news-write-ui-delete-text}"
HorizontalAlignment="Right" Margin="8 6 6 6" SetHeight="19" SetWidth="52" Access="Public">
<Button.StyleClasses>
<system:String>ButtonSmall</system:String>
<system:String>ButtonColorRed</system:String>
</Button.StyleClasses>
</controls:ConfirmButton>
</BoxContainer>
</BoxContainer>
</Control>

View File

@@ -0,0 +1,47 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.MassMedia.Ui;
[GenerateTypedNameReferences]
public sealed partial class NewsArticleCard : Control
{
private string? _authorMarkup;
private TimeSpan? _publicationTime;
public Action? OnDeletePressed;
public int ArtcileNumber;
public string? Title
{
get => TitleLabel.Text;
set => TitleLabel.Text = value?.Length <= 30 ? value : $"{value?[..30]}...";
}
public string? Author
{
get => _authorMarkup;
set
{
_authorMarkup = value;
AuthorLabel.Text = _authorMarkup ?? "";
}
}
public TimeSpan? PublicationTime
{
get => _publicationTime;
set
{
_publicationTime = value;
PublishTimeLabel.Text = value?.ToString(@"hh\:mm\:ss") ?? "";
}
}
public NewsArticleCard()
{
RobustXamlLoader.Load(this);
DeleteButton.OnPressed += _ => OnDeletePressed?.Invoke();
}
}

View File

@@ -1,79 +0,0 @@
using JetBrains.Annotations;
using Content.Shared.MassMedia.Components;
using Content.Shared.MassMedia.Systems;
using Robust.Shared.Utility;
namespace Content.Client.MassMedia.Ui
{
[UsedImplicitly]
public sealed class NewsWriteBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private NewsWriteMenu? _menu;
[ViewVariables]
private string _windowName = Loc.GetString("news-read-ui-default-title");
public NewsWriteBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
_menu = new NewsWriteMenu(_windowName);
_menu.OpenCentered();
_menu.OnClose += Close;
_menu.ShareButtonPressed += OnShareButtonPressed;
_menu.DeleteButtonPressed += OnDeleteButtonPressed;
SendMessage(new NewsWriteArticlesRequestMessage());
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_menu?.Close();
_menu?.Dispose();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (_menu == null || state is not NewsWriteBoundUserInterfaceState cast)
return;
_menu.UpdateUI(cast.Articles, cast.ShareAvalible);
}
private void OnShareButtonPressed()
{
if (_menu == null || _menu.NameInput.Text.Length == 0)
return;
var stringContent = Rope.Collapse(_menu.ContentInput.TextRope);
if (stringContent.Length == 0)
return;
var stringName = _menu.NameInput.Text.Trim();
var name = stringName[..Math.Min(stringName.Length, (SharedNewsSystem.MaxNameLength))];
var content = stringContent[..Math.Min(stringContent.Length, (SharedNewsSystem.MaxArticleLength))];
_menu.ContentInput.TextRope = new Rope.Leaf(string.Empty);
_menu.NameInput.Text = string.Empty;
SendMessage(new NewsWriteShareMessage(name, content));
}
private void OnDeleteButtonPressed(int articleNum)
{
if (_menu == null)
return;
SendMessage(new NewsWriteDeleteMessage(articleNum));
}
}
}

View File

@@ -1,68 +0,0 @@
<DefaultWindow
xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
Title="{Loc 'news-write-ui-default-title'}"
MinSize="680 512"
SetSize="680 512">
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
VerticalExpand="True">
<BoxContainer Orientation="Vertical"
VerticalExpand="True"
SizeFlagsStretchRatio="2"
Margin="10 0 10 10"
MinWidth="350">
<Label Text="{Loc 'news-write-ui-articles-label'}" HorizontalAlignment="Center"/>
<customControls:HSeparator StyleClasses="LowDivider" Margin="0 0 0 10"/>
<PanelContainer VerticalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<ScrollContainer
HScrollEnabled="False"
HorizontalExpand="True"
VerticalExpand="True">
<BoxContainer
Name="ArticleCardsContainer"
Orientation="Vertical"
VerticalExpand="True">
</BoxContainer>
</ScrollContainer>
</PanelContainer>
</BoxContainer>
<BoxContainer Orientation="Vertical"
VerticalExpand="True"
HorizontalExpand="True"
Margin="15 0 0 0">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Text="{Loc 'news-write-ui-article-name-label'}"/>
<LineEdit Name="NameInput"
MinSize="60 0"
VerticalAlignment="Top"
Margin="4 0 0 0"
Access="Public"
HorizontalExpand="True"/>
</BoxContainer>
<customControls:HSeparator StyleClasses="LowDivider" Margin="0 5 0 5"/>
<Label Text="{Loc 'news-write-ui-article-content-label'}" Margin="0 0 0 5"/>
<PanelContainer Name="InputContainer"
VerticalAlignment="Stretch"
VerticalExpand="True"
HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#333237"/>
</PanelContainer.PanelOverride>
<TextEdit Name="ContentInput" Access="Public" />
</PanelContainer>
<Button Name="Share"
MinWidth="30"
HorizontalAlignment="Left"
Text="{Loc 'news-write-ui-share-text'}"
Access="Public"
Margin="0 4 4 4">
</Button>
</BoxContainer>
</BoxContainer>
</DefaultWindow>

View File

@@ -1,42 +0,0 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Content.Shared.MassMedia.Systems;
namespace Content.Client.MassMedia.Ui;
[GenerateTypedNameReferences]
public sealed partial class NewsWriteMenu : DefaultWindow
{
public event Action? ShareButtonPressed;
public event Action<int>? DeleteButtonPressed;
public NewsWriteMenu(string name)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
if (Window != null)
Window.Title = name;
Share.OnPressed += _ => ShareButtonPressed?.Invoke();
}
public void UpdateUI(NewsArticle[] articles, bool shareAvalible)
{
ArticleCardsContainer.Children.Clear();
for (int i = 0; i < articles.Length; i++)
{
var article = articles[i];
var mini = new MiniArticleCardControl(article.Name, (article.Author != null ? article.Author : Loc.GetString("news-read-ui-no-author")));
mini.ArticleNum = i;
mini.OnDeletePressed += () => DeleteButtonPressed?.Invoke(mini.ArticleNum);
ArticleCardsContainer.AddChild(mini);
}
Share.Disabled = !shareAvalible;
}
}

View File

@@ -0,0 +1,84 @@
using JetBrains.Annotations;
using Content.Shared.MassMedia.Systems;
using Content.Shared.MassMedia.Components;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.MassMedia.Ui;
[UsedImplicitly]
public sealed class NewsWriterBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[ViewVariables]
private NewsWriterMenu? _menu;
public NewsWriterBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
_menu = new NewsWriterMenu(_gameTiming);
_menu.OpenCentered();
_menu.OnClose += Close;
_menu.ArticleEditorPanel.PublishButtonPressed += OnPublishButtonPressed;
_menu.DeleteButtonPressed += OnDeleteButtonPressed;
SendMessage(new NewsWriterArticlesRequestMessage());
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_menu?.Close();
_menu?.Dispose();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is not NewsWriterBoundUserInterfaceState cast)
return;
_menu?.UpdateUI(cast.Articles, cast.PublishEnabled, cast.NextPublish);
}
private void OnPublishButtonPressed()
{
var title = _menu?.ArticleEditorPanel.TitleField.Text.Trim() ?? "";
if (_menu == null || title.Length == 0)
return;
var stringContent = Rope.Collapse(_menu.ArticleEditorPanel.ContentField.TextRope).Trim();
if (stringContent.Length == 0)
return;
var name = title.Length <= SharedNewsSystem.MaxTitleLength
? title
: $"{title[..(SharedNewsSystem.MaxTitleLength - 3)]}...";
var content = stringContent.Length <= SharedNewsSystem.MaxContentLength
? stringContent
: $"{stringContent[..(SharedNewsSystem.MaxContentLength - 3)]}...";
SendMessage(new NewsWriterPublishMessage(name, content));
}
private void OnDeleteButtonPressed(int articleNum)
{
if (_menu == null)
return;
SendMessage(new NewsWriterDeleteMessage(articleNum));
}
}

View File

@@ -0,0 +1,45 @@
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:ui="clr-namespace:Content.Client.MassMedia.Ui"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Title="{Loc 'news-write-ui-default-title'}"
MinSize="348 443"
SetSize="348 443">
<ui:ArticleEditorPanel Name="ArticleEditorPanel" HorizontalAlignment="Left" VerticalExpand="True"
MinWidth="410" MinHeight="370" Margin="0 0 0 30" Access="Public" Visible="False"/>
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<Control VerticalExpand="True" HorizontalExpand="True" Margin="10 10 10 0">
<PanelContainer Name="MainPanel" HorizontalExpand="False" VerticalExpand="True">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#202023" />
</PanelContainer.PanelOverride>
</PanelContainer>
<ScrollContainer Name="ArticleListScrollbar" HorizontalExpand="True" VerticalExpand="True" HScrollEnabled="True">
<BoxContainer Name="ArticlesContainer" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="6 6 6 6">
</BoxContainer>
</ScrollContainer>
</Control>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="12 7 12 9">
<BoxContainer Orientation="Horizontal">
<Label Name="ArticleCount" Text="{Loc news-write-ui-article-count-0}"/>
</BoxContainer>
<Control HorizontalExpand="True"/>
<Control>
<Button Name="ButtonCreate" SetHeight="26" MinWidth="83" Text="{Loc news-write-ui-create-text}"/>
</Control>
</BoxContainer>
<Control SetHeight="30" Margin="2 0 0 0">
<PanelContainer Name="FooterPanel">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BorderColor="#5A5A5A" BorderThickness="0 2 0 0" />
</PanelContainer.PanelOverride>
</PanelContainer>
<BoxContainer Name="ContentFooter" HorizontalExpand="True" SetHeight="28">
<Label Text="{Loc news-write-ui-footer-text}" VerticalAlignment="Center" Margin="6 0" StyleClasses="PdaContentFooterText"/>
</BoxContainer>
</Control>
</BoxContainer>
</controls:FancyWindow>

View File

@@ -0,0 +1,97 @@
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Content.Shared.MassMedia.Systems;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
namespace Content.Client.MassMedia.Ui;
[GenerateTypedNameReferences]
public sealed partial class NewsWriterMenu : FancyWindow
{
private readonly IGameTiming _gameTiming;
private TimeSpan? _nextPublish;
public event Action<int>? DeleteButtonPressed;
public NewsWriterMenu(IGameTiming gameTiming)
{
RobustXamlLoader.Load(this);
_gameTiming = gameTiming;
ContentsContainer.RectClipContent = false;
// Customize scrollbar width and margin. This is not possible in xaml
var scrollbar = ArticleListScrollbar.GetChild(1);
scrollbar.SetWidth = 6f;
scrollbar.Margin = new Thickness(0, 0, 2 , 0);
ButtonCreate.OnPressed += OnCreate;
}
public void UpdateUI(NewsArticle[] articles, bool publishEnabled, TimeSpan nextPublish)
{
ArticlesContainer.Children.Clear();
ArticleCount.Text = Loc.GetString("news-write-ui-article-count-text", ("count", articles.Length));
//Iterate backwards to have the newest article at the top
for (var i = articles.Length - 1; i >= 0 ; i--)
{
var article = articles[i];
var control = new NewsArticleCard
{
Title = article.Title,
Author = article.Author ?? Loc.GetString("news-read-ui-no-author"),
PublicationTime = article.ShareTime,
ArtcileNumber = i
};
control.OnDeletePressed += () => DeleteButtonPressed?.Invoke(control.ArtcileNumber);
ArticlesContainer.AddChild(control);
}
ButtonCreate.Disabled = !publishEnabled;
_nextPublish = nextPublish;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (!_nextPublish.HasValue)
return;
var remainingTime = _nextPublish.Value.Subtract(_gameTiming.CurTime);
if (remainingTime.TotalSeconds <= 0)
{
_nextPublish = null;
ButtonCreate.Text = Loc.GetString("news-write-ui-create-text");
return;
}
ButtonCreate.Text = remainingTime.Seconds.ToString("D2");
}
protected override void Resized()
{
base.Resized();
var margin = ArticleEditorPanel.Margin;
// Bandaid for the funny 1 pixel margin differences
ArticleEditorPanel.Margin = new Thickness(Width - 1, margin.Top, margin.Right, margin.Bottom);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
ButtonCreate.OnPressed -= OnCreate;
}
private void OnCreate(BaseButton.ButtonEventArgs buttonEventArgs)
{
ArticleEditorPanel.Visible = true;
}
}

View File

@@ -24,7 +24,7 @@ public sealed class PaperBoundUserInterface : BoundUserInterface
_window.OnClose += Close;
_window.Input.OnKeyBindDown += args => // Solution while TextEdit don't have events
{
if (args.Function == EngineKeyFunctions.TextSubmit)
if (args.Function == EngineKeyFunctions.MultilineTextSubmit)
{
var text = Rope.Collapse(_window.Input.TextRope);
Input_OnTextEntered(text);

View File

@@ -74,6 +74,7 @@ namespace Content.Client.Stylesheets
public const string StyleClassLabelKeyText = "LabelKeyText";
public const string StyleClassLabelSecondaryColor = "LabelSecondaryColor";
public const string StyleClassLabelBig = "LabelBig";
public const string StyleClassLabelSmall = "LabelSmall";
public const string StyleClassButtonBig = "ButtonBig";
public const string StyleClassPopupMessageSmall = "PopupMessageSmall";
@@ -329,6 +330,12 @@ namespace Content.Client.Stylesheets
chatFilterButton.SetPatchMargin(StyleBox.Margin.All, 5);
chatFilterButton.SetPadding(StyleBox.Margin.All, 2);
var smallButtonTex = resCache.GetTexture("/Textures/Interface/Nano/button_small.svg.96dpi.png");
var smallButtonBase = new StyleBoxTexture
{
Texture = smallButtonTex,
};
var textureInvertedTriangle = resCache.GetTexture("/Textures/Interface/Nano/inverted_triangle.svg.png");
var lineEditTex = resCache.GetTexture("/Textures/Interface/Nano/lineedit.png");
@@ -646,6 +653,23 @@ namespace Content.Client.Stylesheets
.Pseudo(ContainerButton.StylePseudoClassDisabled)
.Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDisabled),
// Colors for confirm buttons confirm states.
Element<ConfirmButton>()
.Pseudo(ConfirmButton.ConfirmPrefix + ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDefault),
Element<ConfirmButton>()
.Pseudo(ConfirmButton.ConfirmPrefix + ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, ButtonColorCautionHovered),
Element<ConfirmButton>()
.Pseudo(ConfirmButton.ConfirmPrefix + ContainerButton.StylePseudoClassPressed)
.Prop(Control.StylePropertyModulateSelf, ButtonColorCautionPressed),
Element<ConfirmButton>()
.Pseudo(ConfirmButton.ConfirmPrefix + ContainerButton.StylePseudoClassDisabled)
.Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDisabled),
new StyleRule(new SelectorChild(
new SelectorElement(typeof(Button), null, null, new[] {ContainerButton.StylePseudoClassDisabled}),
new SelectorElement(typeof(Label), null, null, null)),
@@ -1189,14 +1213,6 @@ namespace Content.Client.Stylesheets
new StyleProperty(StripeBack.StylePropertyBackground, stripeBack),
}),
// StyleClassLabelBig
new StyleRule(
SelectorElement.Class(StyleClassLabelBig),
new[]
{
new StyleProperty("font", notoSans16),
}),
// StyleClassItemStatus
new StyleRule(SelectorElement.Class(StyleClassItemStatus), new[]
{
@@ -1303,10 +1319,29 @@ namespace Content.Client.Stylesheets
new StyleProperty(PanelContainer.StylePropertyPanel, new StyleBoxFlat { BackgroundColor = NanoGold, ContentMarginBottomOverride = 2, ContentMarginLeftOverride = 2}),
}),
// Labels ---
Element<Label>().Class(StyleClassLabelBig)
.Prop(Label.StylePropertyFont, notoSans16),
Element<Label>().Class(StyleClassLabelSmall)
.Prop(Label.StylePropertyFont, notoSans10),
// ---
// Different Background shapes ---
Element<PanelContainer>().Class(ClassAngleRect)
.Prop(PanelContainer.StylePropertyPanel, BaseAngleRect)
.Prop(Control.StylePropertyModulateSelf, Color.FromHex("#25252A")),
Element<PanelContainer>().Class("BackgroundOpenRight")
.Prop(PanelContainer.StylePropertyPanel, BaseButtonOpenRight)
.Prop(Control.StylePropertyModulateSelf, Color.FromHex("#25252A")),
Element<PanelContainer>().Class("BackgroundOpenLeft")
.Prop(PanelContainer.StylePropertyPanel, BaseButtonOpenLeft)
.Prop(Control.StylePropertyModulateSelf, Color.FromHex("#25252A")),
// ---
// Dividers
Element<PanelContainer>().Class(ClassLowDivider)
.Prop(PanelContainer.StylePropertyPanel, new StyleBoxFlat
{
@@ -1393,6 +1428,15 @@ namespace Content.Client.Stylesheets
.Prop(Control.StylePropertyModulateSelf, ButtonColorGoodHovered),
// ---
// Small Button ---
Element<Button>().Class("ButtonSmall")
.Prop(ContainerButton.StylePropertyStyleBox, smallButtonBase),
Child().Parent(Element<Button>().Class("ButtonSmall"))
.Child(Element<Label>())
.Prop(Label.StylePropertyFont, notoSans8),
// ---
Element<Label>().Class("StatusFieldTitle")
.Prop("font-color", NanoGold),
@@ -1490,7 +1534,6 @@ namespace Content.Client.Stylesheets
{
BackgroundColor = FancyTreeSelectedRowColor,
}),
}).ToList());
}
}

View File

@@ -0,0 +1,142 @@
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
namespace Content.Client.UserInterface.Controls;
/// <summary>
/// A Button that requires a second click to actually invoke its OnPressed action. <br/>
/// When clicked once it will change rendering modes to be prefixed by <see cref="ConfirmPrefix"/>
/// and displays <see cref="ConfirmationText"/> on the button instead of <see cref="Text"/>.<br/>
/// <br/>
/// After the first click <see cref="CooldownTime"/> needs to elapse before it can be clicked again to confirm.<br/>
/// When the button doesn't get clicked a second time before <see cref="ResetTime"/> passes it changes back to its normal state.<br/>
/// </summary>
/// <remarks>
/// Colors for the different states need to be set in the stylesheet
/// </remarks>
public sealed class ConfirmButton : Button
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
public const string ConfirmPrefix = "confirm-";
private TimeSpan? _nextReset;
private TimeSpan? _nextCooldown;
private string? _confirmationText;
private string? _text;
/// <summary>
/// Fired when the button was pressed and confirmed
/// </summary>
public new event Action<ButtonEventArgs>? OnPressed;
/// <inheritdoc cref="Button.Text"/>
/// <remarks>
/// Hides the buttons text property to be able to sanely replace the button text with
/// <see cref="_confirmationText"/> when asking for confirmation
/// </remarks>
public new string? Text
{
get => _text;
set
{
_text = value;
base.Text = IsConfirming ? _confirmationText : value;
}
}
/// <summary>
/// The text displayed on the button when waiting for a second click
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public string ConfirmationText
{
get => _confirmationText ?? Loc.GetString("generic-confirm");
set => _confirmationText = value;
}
/// <summary>
/// The time until the button reverts to normal
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public TimeSpan ResetTime { get; set; } = TimeSpan.FromSeconds(2);
/// <summary>
/// The time until the button accepts a second click. This is to prevent accidentally confirming the button
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public TimeSpan CooldownTime { get; set; } = TimeSpan.FromSeconds(.5);
[ViewVariables]
public bool IsConfirming = false;
public ConfirmButton()
{
IoCManager.InjectDependencies(this);
base.OnPressed += HandleOnPressed;
}
protected override void FrameUpdate(FrameEventArgs args)
{
if (IsConfirming && _gameTiming.CurTime > _nextReset)
{
IsConfirming = false;
base.Text = Text;
DrawModeChanged();
}
if (Disabled && _gameTiming.CurTime > _nextCooldown)
Disabled = false;
}
protected override void DrawModeChanged()
{
if (IsConfirming)
{
switch (DrawMode)
{
case DrawModeEnum.Normal:
SetOnlyStylePseudoClass(ConfirmPrefix + StylePseudoClassNormal);
break;
case DrawModeEnum.Pressed:
SetOnlyStylePseudoClass(ConfirmPrefix + StylePseudoClassPressed);
break;
case DrawModeEnum.Hover:
SetOnlyStylePseudoClass(ConfirmPrefix + StylePseudoClassHover);
break;
case DrawModeEnum.Disabled:
SetOnlyStylePseudoClass(ConfirmPrefix + StylePseudoClassDisabled);
break;
default:
throw new ArgumentOutOfRangeException();
}
return;
}
base.DrawModeChanged();
}
private void HandleOnPressed(ButtonEventArgs buttonEvent)
{
//Prevent accidental confirmations from double clicking
if (IsConfirming && _nextCooldown > _gameTiming.CurTime)
return;
switch (IsConfirming)
{
case false:
_nextCooldown = _gameTiming.CurTime + CooldownTime;
_nextReset = _gameTiming.CurTime + ResetTime;
Disabled = true;
break;
case true:
OnPressed?.Invoke(buttonEvent);
break;
}
base.Text = IsConfirming ? Text : ConfirmationText;
IsConfirming = !IsConfirming;
}
}

View File

@@ -488,11 +488,12 @@ public sealed class ChatUIController : UIController
if (_state.CurrentState is GameplayStateBase)
{
// can always hear local / radio / emote when in the game
// can always hear local / radio / emote / notifications when in the game
FilterableChannels |= ChatChannel.Local;
FilterableChannels |= ChatChannel.Whisper;
FilterableChannels |= ChatChannel.Radio;
FilterableChannels |= ChatChannel.Emotes;
FilterableChannels |= ChatChannel.Notifications;
// Can only send local / radio / emote when attached to a non-ghost entity.
// TODO: this logic is iffy (checking if controlling something that's NOT a ghost), is there a better way to check this?

View File

@@ -16,6 +16,7 @@ public sealed partial class ChannelFilterPopup : Popup
ChatChannel.Whisper,
ChatChannel.Emotes,
ChatChannel.Radio,
ChatChannel.Notifications,
ChatChannel.LOOC,
ChatChannel.OOC,
ChatChannel.Dead,