diff --git a/Content.Client/Administration/Managers/ClientAdminManager.cs b/Content.Client/Administration/Managers/ClientAdminManager.cs
index 66c8b8a063..8978e2fd6d 100644
--- a/Content.Client/Administration/Managers/ClientAdminManager.cs
+++ b/Content.Client/Administration/Managers/ClientAdminManager.cs
@@ -130,5 +130,13 @@ namespace Content.Client.Administration.Managers
return null;
}
+
+ public AdminData? GetAdminData(bool includeDeAdmin = false)
+ {
+ if (_player.LocalPlayer is { Session: { } session })
+ return GetAdminData(session, includeDeAdmin);
+
+ return null;
+ }
}
}
diff --git a/Content.Client/Administration/Managers/IClientAdminManager.cs b/Content.Client/Administration/Managers/IClientAdminManager.cs
index 46e3a01537..b4b5b48b81 100644
--- a/Content.Client/Administration/Managers/IClientAdminManager.cs
+++ b/Content.Client/Administration/Managers/IClientAdminManager.cs
@@ -1,5 +1,4 @@
-using System;
-using Content.Shared.Administration;
+using Content.Shared.Administration;
namespace Content.Client.Administration.Managers
{
@@ -13,6 +12,15 @@ namespace Content.Client.Administration.Managers
///
event Action AdminStatusUpdated;
+ ///
+ /// Gets the admin data for the client, if they are an admin.
+ ///
+ ///
+ /// Whether to return admin data for admins that are current de-adminned.
+ ///
+ /// if the player is not an admin.
+ AdminData? GetAdminData(bool includeDeAdmin = false);
+
///
/// Checks whether the local player is an admin.
///
@@ -52,5 +60,17 @@ namespace Content.Client.Administration.Managers
bool CanAdminMenu();
void Initialize();
+
+ ///
+ /// Checks if the client is an admin.
+ ///
+ ///
+ /// Whether to return admin data for admins that are current de-adminned.
+ ///
+ /// true if the player is an admin, false otherwise.
+ bool IsAdmin(bool includeDeAdmin = false)
+ {
+ return GetAdminData(includeDeAdmin) != null;
+ }
}
}
diff --git a/Content.Client/Changelog/ChangelogManager.cs b/Content.Client/Changelog/ChangelogManager.cs
index 249332c337..396af99d2c 100644
--- a/Content.Client/Changelog/ChangelogManager.cs
+++ b/Content.Client/Changelog/ChangelogManager.cs
@@ -1,29 +1,29 @@
-using System;
-using System.Collections.Generic;
using System.Globalization;
-using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Content.Shared.CCVar;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
-using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
-using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Utility;
-
namespace Content.Client.Changelog
{
- public sealed partial class ChangelogManager
+ public sealed partial class ChangelogManager : IPostInjectInit
{
+ [Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IResourceManager _resource = default!;
[Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
+ private const string SawmillName = "changelog";
+ public const string MainChangelogName = "Changelog";
+
+ private ISawmill _sawmill = default!;
+
public bool NewChangelogEntries { get; private set; }
public int LastReadId { get; private set; }
public int MaxId { get; private set; }
@@ -51,17 +51,39 @@ namespace Content.Client.Changelog
public async void Initialize()
{
// Open changelog purely to compare to the last viewed date.
- var changelog = await LoadChangelog();
+ var changelogs = await LoadChangelog();
+ UpdateChangelogs(changelogs);
+ }
- if (changelog.Count == 0)
+ private void UpdateChangelogs(List changelogs)
+ {
+ if (changelogs.Count == 0)
{
return;
}
- MaxId = changelog.Max(c => c.Id);
+ var mainChangelogs = changelogs.Where(c => c.Name == MainChangelogName).ToArray();
+ if (mainChangelogs.Length == 0)
+ {
+ _sawmill.Error($"No changelog file found in Resources/Changelog with name {MainChangelogName}");
+ return;
+ }
+
+ var changelog = changelogs[0];
+ if (mainChangelogs.Length > 1)
+ {
+ _sawmill.Error($"More than one file found in Resource/Changelog with name {MainChangelogName}");
+ }
+
+ if (changelog.Entries.Count == 0)
+ {
+ return;
+ }
+
+ MaxId = changelog.Entries.Max(c => c.Id);
var path = new ResPath($"/changelog_last_seen_{_configManager.GetCVar(CCVars.ServerId)}");
- if(_resource.UserData.TryReadAllText(path, out var lastReadIdText))
+ if (_resource.UserData.TryReadAllText(path, out var lastReadIdText))
{
LastReadId = int.Parse(lastReadIdText);
}
@@ -71,20 +93,74 @@ namespace Content.Client.Changelog
NewChangelogEntriesChanged?.Invoke();
}
- public Task> LoadChangelog()
+ public Task> LoadChangelog()
{
return Task.Run(() =>
{
- var yamlData = _resource.ContentFileReadYaml(new ("/Changelog/Changelog.yml"));
+ var changelogs = new List();
+ var directory = new ResPath("/Changelog");
+ foreach (var file in _resource.ContentFindFiles(new ResPath("/Changelog/")))
+ {
+ if (file.Directory != directory || file.Extension != "yml")
+ continue;
- if (yamlData.Documents.Count == 0)
- return new List();
+ var yamlData = _resource.ContentFileReadYaml(file);
- var node = (MappingDataNode)yamlData.Documents[0].RootNode.ToDataNode();
- return _serialization.Read>(node["Entries"], notNullableOverride: true);
+ if (yamlData.Documents.Count == 0)
+ continue;
+
+ var node = yamlData.Documents[0].RootNode.ToDataNodeCast();
+ var changelog = _serialization.Read(node, notNullableOverride: true);
+ if (string.IsNullOrWhiteSpace(changelog.Name))
+ changelog.Name = file.FilenameWithoutExtension;
+
+ changelogs.Add(changelog);
+ }
+
+ changelogs.Sort((a, b) => a.Order.CompareTo(b.Order));
+ return changelogs;
});
}
+ public void PostInject()
+ {
+ _sawmill = _logManager.GetSawmill(SawmillName);
+ }
+
+ [DataDefinition]
+ public sealed partial class Changelog
+ {
+ ///
+ /// The name to use for this changelog.
+ /// If left unspecified, the name of the file is used instead.
+ /// Used during localization to find the user-displayed name of this changelog.
+ ///
+ [DataField("Name")]
+ public string Name = string.Empty;
+
+ ///
+ /// The individual entries in this changelog.
+ /// These are not kept around in memory in the changelog manager.
+ ///
+ [DataField("Entries")]
+ public List Entries = new();
+
+ ///
+ /// Whether or not this changelog will be displayed as a tab to non-admins.
+ /// These are still loaded by all clients, but not shown if they aren't an admin,
+ /// as they do not contain sensitive data and are publicly visible on GitHub.
+ ///
+ [DataField("AdminOnly")]
+ public bool AdminOnly;
+
+ ///
+ /// Used when ordering the changelog tabs for the user to see.
+ /// Larger numbers are displayed later, smaller numbers are displayed earlier.
+ ///
+ [DataField("Order")]
+ public int Order;
+ }
+
[DataDefinition]
public sealed partial class ChangelogEntry : ISerializationHooks
{
@@ -108,7 +184,7 @@ namespace Content.Client.Changelog
}
[DataDefinition]
- public sealed partial class ChangelogChange : ISerializationHooks
+ public sealed partial class ChangelogChange
{
[DataField("type")]
public ChangelogLineType Type { get; private set; }
diff --git a/Content.Client/Changelog/ChangelogTab.xaml b/Content.Client/Changelog/ChangelogTab.xaml
new file mode 100644
index 0000000000..7c049efacc
--- /dev/null
+++ b/Content.Client/Changelog/ChangelogTab.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/Content.Client/Changelog/ChangelogTab.xaml.cs b/Content.Client/Changelog/ChangelogTab.xaml.cs
new file mode 100644
index 0000000000..d1e2bc7533
--- /dev/null
+++ b/Content.Client/Changelog/ChangelogTab.xaml.cs
@@ -0,0 +1,175 @@
+using System.Linq;
+using System.Numerics;
+using Content.Client.Resources;
+using Content.Client.Stylesheets;
+using Robust.Client.AutoGenerated;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Utility;
+using static Content.Client.Changelog.ChangelogManager;
+using static Robust.Client.UserInterface.Controls.BoxContainer;
+
+namespace Content.Client.Changelog;
+
+[GenerateTypedNameReferences]
+public sealed partial class ChangelogTab : Control
+{
+ [Dependency] private readonly ChangelogManager _changelog = default!;
+ [Dependency] private readonly IResourceCache _resourceCache = default!;
+
+ public bool AdminOnly;
+
+ public ChangelogTab()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ }
+
+ public void PopulateChangelog(ChangelogManager.Changelog changelog)
+ {
+ var byDay = changelog.Entries
+ .GroupBy(e => e.Time.ToLocalTime().Date)
+ .OrderByDescending(c => c.Key);
+
+ var hasRead = changelog.Name != MainChangelogName ||
+ _changelog.MaxId <= _changelog.LastReadId;
+
+ foreach (var dayEntries in byDay)
+ {
+ var day = dayEntries.Key;
+
+ var groupedEntries = dayEntries
+ .GroupBy(c => (c.Author, Read: c.Id <= _changelog.LastReadId))
+ .OrderBy(c => c.Key.Read)
+ .ThenBy(c => c.Key.Author);
+
+ string dayNice;
+ var today = DateTime.Today;
+ if (day == today)
+ dayNice = Loc.GetString("changelog-today");
+ else if (day == today.AddDays(-1))
+ dayNice = Loc.GetString("changelog-yesterday");
+ else
+ dayNice = day.ToShortDateString();
+
+ ChangelogBody.AddChild(new Label
+ {
+ Text = dayNice,
+ StyleClasses = { StyleBase.StyleClassLabelHeading },
+ Margin = new Thickness(4, 6, 0, 0)
+ });
+
+ var first = true;
+
+ foreach (var groupedEntry in groupedEntries)
+ {
+ var (author, read) = groupedEntry.Key;
+
+ if (!first)
+ {
+ ChangelogBody.AddChild(new Control { Margin = new Thickness(4) });
+ }
+
+ if (read && !hasRead)
+ {
+ hasRead = true;
+
+ var upArrow =
+ _resourceCache.GetTexture("/Textures/Interface/Changelog/up_arrow.svg.192dpi.png");
+
+ var readDivider = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical
+ };
+
+ var hBox = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ HorizontalAlignment = HAlignment.Center,
+ Children =
+ {
+ new TextureRect
+ {
+ Texture = upArrow,
+ ModulateSelfOverride = Color.FromHex("#888"),
+ TextureScale = new Vector2(0.5f, 0.5f),
+ Margin = new Thickness(4, 3),
+ VerticalAlignment = VAlignment.Bottom
+ },
+ new Label
+ {
+ Align = Label.AlignMode.Center,
+ Text = Loc.GetString("changelog-new-changes"),
+ FontColorOverride = Color.FromHex("#888"),
+ },
+ new TextureRect
+ {
+ Texture = upArrow,
+ ModulateSelfOverride = Color.FromHex("#888"),
+ TextureScale = new Vector2(0.5f, 0.5f),
+ Margin = new Thickness(4, 3),
+ VerticalAlignment = VAlignment.Bottom
+ }
+ }
+ };
+
+ readDivider.AddChild(hBox);
+ readDivider.AddChild(new PanelContainer { StyleClasses = { StyleBase.ClassLowDivider } });
+ ChangelogBody.AddChild(readDivider);
+
+ if (first)
+ readDivider.SetPositionInParent(ChangelogBody.ChildCount - 2);
+ }
+
+ first = false;
+
+ var authorLabel = new RichTextLabel
+ {
+ Margin = new Thickness(6, 0, 0, 0),
+ };
+ authorLabel.SetMessage(
+ FormattedMessage.FromMarkup(Loc.GetString("changelog-author-changed", ("author", author))));
+ ChangelogBody.AddChild(authorLabel);
+
+ foreach (var change in groupedEntry.SelectMany(c => c.Changes))
+ {
+ var text = new RichTextLabel();
+ text.SetMessage(FormattedMessage.FromMarkup(change.Message));
+ ChangelogBody.AddChild(new BoxContainer
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ Margin = new Thickness(14, 1, 10, 2),
+ Children =
+ {
+ GetIcon(change.Type),
+ text
+ }
+ });
+ }
+ }
+ }
+ }
+
+ private TextureRect GetIcon(ChangelogLineType type)
+ {
+ var (file, color) = type switch
+ {
+ ChangelogLineType.Add => ("plus.svg.192dpi.png", "#6ED18D"),
+ ChangelogLineType.Remove => ("minus.svg.192dpi.png", "#D16E6E"),
+ ChangelogLineType.Fix => ("bug.svg.192dpi.png", "#D1BA6E"),
+ ChangelogLineType.Tweak => ("wrench.svg.192dpi.png", "#6E96D1"),
+ _ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
+ };
+
+ return new TextureRect
+ {
+ Texture = _resourceCache.GetTexture(new ResPath($"/Textures/Interface/Changelog/{file}")),
+ VerticalAlignment = VAlignment.Top,
+ TextureScale = new Vector2(0.5f, 0.5f),
+ Margin = new Thickness(2, 4, 6, 2),
+ ModulateSelfOverride = Color.FromHex(color)
+ };
+ }
+}
diff --git a/Content.Client/Changelog/ChangelogWindow.xaml b/Content.Client/Changelog/ChangelogWindow.xaml
index 888a8528d9..355452dbfa 100644
--- a/Content.Client/Changelog/ChangelogWindow.xaml
+++ b/Content.Client/Changelog/ChangelogWindow.xaml
@@ -3,15 +3,10 @@
Title="{Loc 'changelog-window-title'}"
MinSize="500 400"
SetSize="500 400">
-
-
-
-
-
-
+
-
+
diff --git a/Content.Client/Changelog/ChangelogWindow.xaml.cs b/Content.Client/Changelog/ChangelogWindow.xaml.cs
index cea5bd9e7c..e5f492900c 100644
--- a/Content.Client/Changelog/ChangelogWindow.xaml.cs
+++ b/Content.Client/Changelog/ChangelogWindow.xaml.cs
@@ -1,28 +1,22 @@
using System.Linq;
-using System.Numerics;
-using Content.Client.Resources;
+using Content.Client.Administration.Managers;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Systems.EscapeMenu;
using Content.Shared.Administration;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
-using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Console;
-using Robust.Shared.Utility;
-using static Content.Client.Changelog.ChangelogManager;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Changelog
{
[GenerateTypedNameReferences]
public sealed partial class ChangelogWindow : FancyWindow
{
+ [Dependency] private readonly IClientAdminManager _adminManager = default!;
[Dependency] private readonly ChangelogManager _changelog = default!;
- [Dependency] private readonly IResourceCache _resourceCache = default!;
public ChangelogWindow()
{
@@ -39,154 +33,84 @@ namespace Content.Client.Changelog
PopulateChangelog();
}
+ protected override void EnteredTree()
+ {
+ base.EnteredTree();
+ _adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
+ }
+
+ protected override void ExitedTree()
+ {
+ base.ExitedTree();
+ _adminManager.AdminStatusUpdated -= OnAdminStatusUpdated;
+ }
+
+ private void OnAdminStatusUpdated()
+ {
+ TabsUpdated();
+ }
+
private async void PopulateChangelog()
{
// Changelog is not kept in memory so load it again.
- var changelog = await _changelog.LoadChangelog();
+ var changelogs = await _changelog.LoadChangelog();
- var byDay = changelog
- .GroupBy(e => e.Time.ToLocalTime().Date)
- .OrderByDescending(c => c.Key);
+ Tabs.DisposeAllChildren();
- var hasRead = _changelog.MaxId <= _changelog.LastReadId;
- foreach (var dayEntries in byDay)
+ var i = 0;
+ foreach (var changelog in changelogs)
{
- var day = dayEntries.Key;
+ var tab = new ChangelogTab { AdminOnly = changelog.AdminOnly };
+ tab.PopulateChangelog(changelog);
- var groupedEntries = dayEntries
- .GroupBy(c => (c.Author, Read: c.Id <= _changelog.LastReadId))
- .OrderBy(c => c.Key.Read)
- .ThenBy(c => c.Key.Author);
-
- string dayNice;
- var today = DateTime.Today;
- if (day == today)
- dayNice = Loc.GetString("changelog-today");
- else if (day == today.AddDays(-1))
- dayNice = Loc.GetString("changelog-yesterday");
- else
- dayNice = day.ToShortDateString();
-
- ChangelogBody.AddChild(new Label
- {
- Text = dayNice,
- StyleClasses = { StyleBase.StyleClassLabelHeading },
- Margin = new Thickness(4, 6, 0, 0)
- });
-
- var first = true;
-
- foreach (var groupedEntry in groupedEntries)
- {
- var (author, read) = groupedEntry.Key;
-
- if (!first)
- {
- ChangelogBody.AddChild(new Control { Margin = new Thickness(4) });
- }
-
- if (read && !hasRead)
- {
- hasRead = true;
-
- var upArrow =
- _resourceCache.GetTexture("/Textures/Interface/Changelog/up_arrow.svg.192dpi.png");
-
- var readDivider = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical
- };
-
- var hBox = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- HorizontalAlignment = HAlignment.Center,
- Children =
- {
- new TextureRect
- {
- Texture = upArrow,
- ModulateSelfOverride = Color.FromHex("#888"),
- TextureScale = new Vector2(0.5f, 0.5f),
- Margin = new Thickness(4, 3),
- VerticalAlignment = VAlignment.Bottom
- },
- new Label
- {
- Align = Label.AlignMode.Center,
- Text = Loc.GetString("changelog-new-changes"),
- FontColorOverride = Color.FromHex("#888"),
- },
- new TextureRect
- {
- Texture = upArrow,
- ModulateSelfOverride = Color.FromHex("#888"),
- TextureScale = new Vector2(0.5f, 0.5f),
- Margin = new Thickness(4, 3),
- VerticalAlignment = VAlignment.Bottom
- }
- }
- };
-
- readDivider.AddChild(hBox);
- readDivider.AddChild(new PanelContainer { StyleClasses = { StyleBase.ClassLowDivider } });
- ChangelogBody.AddChild(readDivider);
-
- if (first)
- readDivider.SetPositionInParent(ChangelogBody.ChildCount - 2);
- }
-
- first = false;
-
- var authorLabel = new RichTextLabel
- {
- Margin = new Thickness(6, 0, 0, 0),
- };
- authorLabel.SetMessage(
- FormattedMessage.FromMarkup(Loc.GetString("changelog-author-changed", ("author", author))));
- ChangelogBody.AddChild(authorLabel);
-
- foreach (var change in groupedEntry.SelectMany(c => c.Changes))
- {
- var text = new RichTextLabel();
- text.SetMessage(FormattedMessage.FromMarkup(change.Message));
- ChangelogBody.AddChild(new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- Margin = new Thickness(14, 1, 10, 2),
- Children =
- {
- GetIcon(change.Type),
- text
- }
- });
- }
- }
+ Tabs.AddChild(tab);
+ Tabs.SetTabTitle(i++, Loc.GetString($"changelog-tab-title-{changelog.Name}"));
}
var version = typeof(ChangelogWindow).Assembly.GetName().Version ?? new Version(1, 0);
VersionLabel.Text = Loc.GetString("changelog-version-tag", ("version", version.ToString()));
+
+ TabsUpdated();
}
- private TextureRect GetIcon(ChangelogLineType type)
+ private void TabsUpdated()
{
- var (file, color) = type switch
- {
- ChangelogLineType.Add => ("plus.svg.192dpi.png", "#6ED18D"),
- ChangelogLineType.Remove => ("minus.svg.192dpi.png", "#D16E6E"),
- ChangelogLineType.Fix => ("bug.svg.192dpi.png", "#D1BA6E"),
- ChangelogLineType.Tweak => ("wrench.svg.192dpi.png", "#6E96D1"),
- _ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
- };
+ var tabs = Tabs.Children.OfType().ToArray();
+ var isAdmin = _adminManager.IsAdmin(true);
- return new TextureRect
+ var visibleTabs = 0;
+ int? firstVisible = null;
+ for (var i = 0; i < tabs.Length; i++)
{
- Texture = _resourceCache.GetTexture(new ResPath($"/Textures/Interface/Changelog/{file}")),
- VerticalAlignment = VAlignment.Top,
- TextureScale = new Vector2(0.5f, 0.5f),
- Margin = new Thickness(2, 4, 6, 2),
- ModulateSelfOverride = Color.FromHex(color)
- };
+ var tab = tabs[i];
+
+ if (!tab.AdminOnly || isAdmin)
+ {
+ Tabs.SetTabVisible(i, true);
+ tab.Visible = true;
+ visibleTabs++;
+ firstVisible ??= i;
+ }
+ else
+ {
+ Tabs.SetTabVisible(i, false);
+ tab.Visible = false;
+ }
+ }
+
+ Tabs.TabsVisible = visibleTabs > 1;
+
+ // Current tab became invisible, select the first one that is visible
+ if (!Tabs.GetTabVisible(Tabs.CurrentTab) && firstVisible != null)
+ {
+ Tabs.CurrentTab = firstVisible.Value;
+ }
+
+ // We are only displaying one tab, hide its header
+ if (!Tabs.TabsVisible && firstVisible != null)
+ {
+ Tabs.SetTabVisible(firstVisible.Value, false);
+ }
}
}
diff --git a/Content.Client/Stylesheets/StyleSpace.cs b/Content.Client/Stylesheets/StyleSpace.cs
index a82dba65bc..3bb4e986af 100644
--- a/Content.Client/Stylesheets/StyleSpace.cs
+++ b/Content.Client/Stylesheets/StyleSpace.cs
@@ -4,7 +4,6 @@ using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Maths;
using static Robust.Client.UserInterface.StylesheetHelpers;
namespace Content.Client.Stylesheets
@@ -62,6 +61,14 @@ namespace Content.Client.Stylesheets
var textureInvertedTriangle = resCache.GetTexture("/Textures/Interface/Nano/inverted_triangle.svg.png");
+ var tabContainerPanel = new StyleBoxTexture();
+ tabContainerPanel.SetPatchMargin(StyleBox.Margin.All, 2);
+
+ var tabContainerBoxActive = new StyleBoxFlat {BackgroundColor = new Color(64, 64, 64)};
+ tabContainerBoxActive.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5);
+ var tabContainerBoxInactive = new StyleBoxFlat {BackgroundColor = new Color(32, 32, 32)};
+ tabContainerBoxInactive.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5);
+
Stylesheet = new Stylesheet(BaseRules.Concat(new StyleRule[]
{
Element