--- /dev/null
+<Control xmlns="https://spacestation14.io"
+ xmlns:fancyTree="clr-namespace:Content.Client.UserInterface.Controls.FancyTree"
+ xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
+ xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
+ SetSize="750 700"
+ MinSize="100 200">
+ <PanelContainer StyleClasses="BackgroundDark">
+ <SplitContainer Orientation="Horizontal" HorizontalExpand="True" Name="Split">
+ <!-- Guide select -->
+ <BoxContainer Orientation="Vertical" Name="TreeBox">
+ <fancyTree:FancyTree Name="Tree" VerticalExpand="True" HorizontalExpand="True" Access="Public"/>
+ <Button Name="PopOutButton" HorizontalAlignment="Left" VerticalAlignment="Bottom" Access="Public" Text="{Loc admin-logs-pop-out}"/>
+ <customControls:VSeparator StyleClasses="LowDivider" Margin="0 -2"/>
+ </BoxContainer>
+ <BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
+ <BoxContainer Name="SearchContainer" Visible="False" HorizontalExpand="True">
+ <LineEdit
+ Name="SearchBar"
+ PlaceHolder="{Loc 'guidebook-filter-placeholder-text'}"
+ HorizontalExpand="True"
+ Margin="0 5 10 5">
+ </LineEdit>
+ </BoxContainer>
+ <ScrollContainer Name="Scroll" HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
+ <Control>
+ <BoxContainer Orientation="Vertical" Name="EntryContainer" Margin="5 5 5 5" Visible="False"/>
+ <BoxContainer Orientation="Vertical" Name="Placeholder" Margin="5 5 5 5">
+ <Label HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Loc 'guidebook-placeholder-text'}"/>
+ <Label HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Loc 'guidebook-placeholder-text-2'}"/>
+ </BoxContainer>
+ </Control>
+ </ScrollContainer>
+ </BoxContainer>
+ </SplitContainer>
+ </PanelContainer>
+</Control>
--- /dev/null
+using System.Linq;
+using Content.Client.Guidebook.RichText;
+using Content.Client.UserInterface.ControlExtensions;
+using Content.Client.UserInterface.Controls.FancyTree;
+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.ContentPack;
+
+namespace Content.Client.Guidebook.Controls;
+[GenerateTypedNameReferences]
+public sealed partial class GuidebookControl : Control, ILinkClickHandler
+{
+ [Dependency] private readonly IResourceManager _resourceManager = default!;
+ [Dependency] private readonly DocumentParsingManager _parsingMan = default!;
+
+ private Dictionary<string, GuideEntry> _entries = new();
+
+ public GuidebookControl()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ Tree.OnSelectedItemChanged += OnSelectionChanged;
+
+ SearchBar.OnTextChanged += _ =>
+ {
+ HandleFilter();
+ };
+ }
+
+ private IClydeWindow? ClydeWindow { get; set; }
+
+ private void OnSelectionChanged(TreeItem? item)
+ {
+ if (item != null && item.Metadata is GuideEntry entry)
+ ShowGuide(entry);
+ else
+ ClearSelectedGuide();
+ }
+
+ public void ClearSelectedGuide()
+ {
+ Placeholder.Visible = true;
+ EntryContainer.Visible = false;
+ SearchContainer.Visible = false;
+ EntryContainer.RemoveAllChildren();
+ }
+
+ private void ShowGuide(GuideEntry entry)
+ {
+ Scroll.SetScrollValue(default);
+ Placeholder.Visible = false;
+ EntryContainer.Visible = true;
+ SearchBar.Text = "";
+ EntryContainer.RemoveAllChildren();
+ using var file = _resourceManager.ContentFileReadText(entry.Text);
+
+ SearchContainer.Visible = entry.FilterEnabled;
+
+ if (!_parsingMan.TryAddMarkup(EntryContainer, file.ReadToEnd()))
+ {
+ EntryContainer.AddChild(new Label() { Text = "ERROR: Failed to parse document." });
+ Logger.Error($"Failed to parse contents of guide document {entry.Id}.");
+ }
+ }
+
+ public void UpdateGuides(
+ Dictionary<string, GuideEntry> entries,
+ List<string>? rootEntries = null,
+ string? forceRoot = null,
+ string? selected = null)
+ {
+ _entries = entries;
+ RepopulateTree(rootEntries, forceRoot);
+ ClearSelectedGuide();
+
+ Split.State = SplitContainer.SplitState.Auto;
+ if (entries.Count == 1)
+ {
+ TreeBox.Visible = false;
+ Split.ResizeMode = SplitContainer.SplitResizeMode.NotResizable;
+ selected = entries.Keys.First();
+ }
+ else
+ {
+ TreeBox.Visible = true;
+ Split.ResizeMode = SplitContainer.SplitResizeMode.RespectChildrenMinSize;
+ }
+
+ if (selected != null)
+ {
+ var item = Tree.Items.FirstOrDefault(x => x.Metadata is GuideEntry entry && entry.Id == selected);
+ Tree.SetSelectedIndex(item?.Index);
+ }
+ }
+
+ private IEnumerable<GuideEntry> GetSortedRootEntries(List<string>? rootEntries)
+ {
+ if (rootEntries == null)
+ {
+ HashSet<string> entries = new(_entries.Keys);
+ foreach (var entry in _entries.Values)
+ {
+ entries.ExceptWith(entry.Children);
+ }
+ rootEntries = entries.ToList();
+ }
+
+ return rootEntries
+ .Select(x => _entries[x])
+ .OrderBy(x => x.Priority)
+ .ThenBy(x => Loc.GetString(x.Name));
+ }
+
+ private void RepopulateTree(List<string>? roots = null, string? forcedRoot = null)
+ {
+ Tree.Clear();
+
+ HashSet<string> addedEntries = new();
+
+ TreeItem? parent = forcedRoot == null ? null : AddEntry(forcedRoot, null, addedEntries);
+ foreach (var entry in GetSortedRootEntries(roots))
+ {
+ AddEntry(entry.Id, parent, addedEntries);
+ }
+ Tree.SetAllExpanded(true);
+ }
+
+ private TreeItem? AddEntry(string id, TreeItem? parent, HashSet<string> addedEntries)
+ {
+ if (!_entries.TryGetValue(id, out var entry))
+ return null;
+
+ if (!addedEntries.Add(id))
+ {
+ Logger.Error($"Adding duplicate guide entry: {id}");
+ return null;
+ }
+
+ var item = Tree.AddItem(parent);
+ item.Metadata = entry;
+ var name = Loc.GetString(entry.Name);
+ item.Label.Text = name;
+
+ foreach (var child in entry.Children)
+ {
+ AddEntry(child, item, addedEntries);
+ }
+
+ return item;
+ }
+
+ public void HandleClick(string link)
+ {
+ if (!_entries.TryGetValue(link, out var entry))
+ return;
+
+ if (Tree.TryGetIndexFromMetadata(entry, out var index))
+ {
+ Tree.ExpandParentEntries(index.Value);
+ Tree.SetSelectedIndex(index);
+ }
+ else
+ {
+ ShowGuide(entry);
+ }
+ }
+
+ private void HandleFilter()
+ {
+ var emptySearch = SearchBar.Text.Trim().Length == 0;
+
+ if (Tree.SelectedItem != null && Tree.SelectedItem.Metadata is GuideEntry entry && entry.FilterEnabled)
+ {
+ var foundElements = EntryContainer.GetSearchableControls();
+
+ foreach (var element in foundElements)
+ {
+ element.SetHiddenState(true, SearchBar.Text.Trim());
+ }
+ }
+
+ }
+}
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:fancyTree="clr-namespace:Content.Client.UserInterface.Controls.FancyTree"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
- SetSize="750 700"
- MinSize="100 200"
+ xmlns:controls1="clr-namespace:Content.Client.Guidebook.Controls"
Resizable="True"
Title="{Loc 'guidebook-window-title'}">
- <SplitContainer Orientation="Horizontal" HorizontalExpand="True" Name="Split">
- <!-- Guide select -->
- <BoxContainer Orientation="Horizontal" Name="TreeBox">
- <fancyTree:FancyTree Name="Tree" VerticalExpand="True" HorizontalExpand="True" Access="Public"/>
- <cc:VSeparator StyleClasses="LowDivider" Margin="0 -2"/>
- </BoxContainer>
- <BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
- <BoxContainer Name="SearchContainer" Visible="False" HorizontalExpand="True">
- <LineEdit
- Name="SearchBar"
- PlaceHolder="{Loc 'guidebook-filter-placeholder-text'}"
- HorizontalExpand="True"
- Margin="0 5 10 5">
- </LineEdit>
- </BoxContainer>
- <ScrollContainer Name="Scroll" HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
- <Control>
- <BoxContainer Orientation="Vertical" Name="EntryContainer" Margin="5 5 5 5" Visible="False"/>
- <BoxContainer Orientation="Vertical" Name="Placeholder" Margin="5 5 5 5">
- <Label HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Loc 'guidebook-placeholder-text'}"/>
- <Label HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Loc 'guidebook-placeholder-text-2'}"/>
- </BoxContainer>
- </Control>
- </ScrollContainer>
- </BoxContainer>
- </SplitContainer>
+ <controls1:GuidebookControl Name="Guidebook" Access="Public"/>
</controls:FancyWindow>
using Content.Client.UserInterface.Controls.FancyTree;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Guidebook.Controls;
[GenerateTypedNameReferences]
-public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
+public sealed partial class GuidebookWindow : FancyWindow
{
- [Dependency] private readonly IResourceManager _resourceManager = default!;
- [Dependency] private readonly DocumentParsingManager _parsingMan = default!;
-
- private Dictionary<string, GuideEntry> _entries = new();
-
- public GuidebookWindow()
- {
- RobustXamlLoader.Load(this);
- IoCManager.InjectDependencies(this);
-
- Tree.OnSelectedItemChanged += OnSelectionChanged;
-
- SearchBar.OnTextChanged += _ =>
- {
- HandleFilter();
- };
- }
-
- private void OnSelectionChanged(TreeItem? item)
- {
- if (item != null && item.Metadata is GuideEntry entry)
- ShowGuide(entry);
- else
- ClearSelectedGuide();
- }
-
- public void ClearSelectedGuide()
- {
- Placeholder.Visible = true;
- EntryContainer.Visible = false;
- SearchContainer.Visible = false;
- EntryContainer.RemoveAllChildren();
- }
-
- private void ShowGuide(GuideEntry entry)
- {
- Scroll.SetScrollValue(default);
- Placeholder.Visible = false;
- EntryContainer.Visible = true;
- SearchBar.Text = "";
- EntryContainer.RemoveAllChildren();
- using var file = _resourceManager.ContentFileReadText(entry.Text);
-
- SearchContainer.Visible = entry.FilterEnabled;
-
- if (!_parsingMan.TryAddMarkup(EntryContainer, file.ReadToEnd()))
- {
- EntryContainer.AddChild(new Label() { Text = "ERROR: Failed to parse document." });
- Logger.Error($"Failed to parse contents of guide document {entry.Id}.");
- }
- }
-
- public void UpdateGuides(
- Dictionary<string, GuideEntry> entries,
- List<string>? rootEntries = null,
- string? forceRoot = null,
- string? selected = null)
- {
- _entries = entries;
- RepopulateTree(rootEntries, forceRoot);
- ClearSelectedGuide();
-
- Split.State = SplitContainer.SplitState.Auto;
- if (entries.Count == 1)
- {
- TreeBox.Visible = false;
- Split.ResizeMode = SplitContainer.SplitResizeMode.NotResizable;
- selected = entries.Keys.First();
- }
- else
- {
- TreeBox.Visible = true;
- Split.ResizeMode = SplitContainer.SplitResizeMode.RespectChildrenMinSize;
- }
-
- if (selected != null)
- {
- var item = Tree.Items.FirstOrDefault(x => x.Metadata is GuideEntry entry && entry.Id == selected);
- Tree.SetSelectedIndex(item?.Index);
- }
- }
-
- private IEnumerable<GuideEntry> GetSortedRootEntries(List<string>? rootEntries)
- {
- if (rootEntries == null)
- {
- HashSet<string> entries = new(_entries.Keys);
- foreach (var entry in _entries.Values)
- {
- entries.ExceptWith(entry.Children);
- }
- rootEntries = entries.ToList();
- }
-
- return rootEntries
- .Select(x => _entries[x])
- .OrderBy(x => x.Priority)
- .ThenBy(x => Loc.GetString(x.Name));
- }
-
- private void RepopulateTree(List<string>? roots = null, string? forcedRoot = null)
- {
- Tree.Clear();
-
- HashSet<string> addedEntries = new();
-
- TreeItem? parent = forcedRoot == null ? null : AddEntry(forcedRoot, null, addedEntries);
- foreach (var entry in GetSortedRootEntries(roots))
- {
- AddEntry(entry.Id, parent, addedEntries);
- }
- Tree.SetAllExpanded(true);
- }
-
- private TreeItem? AddEntry(string id, TreeItem? parent, HashSet<string> addedEntries)
- {
- if (!_entries.TryGetValue(id, out var entry))
- return null;
-
- if (!addedEntries.Add(id))
- {
- Logger.Error($"Adding duplicate guide entry: {id}");
- return null;
- }
-
- var item = Tree.AddItem(parent);
- item.Metadata = entry;
- var name = Loc.GetString(entry.Name);
- item.Label.Text = name;
-
- foreach (var child in entry.Children)
- {
- AddEntry(child, item, addedEntries);
- }
-
- return item;
- }
-
- public void HandleClick(string link)
- {
- if (!_entries.TryGetValue(link, out var entry))
- return;
-
- if (Tree.TryGetIndexFromMetadata(entry, out var index))
- {
- Tree.ExpandParentEntries(index.Value);
- Tree.SetSelectedIndex(index);
- }
- else
- {
- ShowGuide(entry);
- }
- }
-
- private void HandleFilter()
- {
- var emptySearch = SearchBar.Text.Trim().Length == 0;
-
- if (Tree.SelectedItem != null && Tree.SelectedItem.Metadata is GuideEntry entry && entry.FilterEnabled)
- {
- var foundElements = EntryContainer.GetSearchableControls();
-
- foreach (var element in foundElements)
- {
- element.SetHiddenState(true, SearchBar.Text.Trim());
- }
- }
-
- }
}
using Content.Client.Lobby;
using Content.Client.UserInterface.Controls;
using Content.Shared.Input;
+using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using static Robust.Client.UserInterface.Controls.BaseButton;
public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyState>, IOnStateEntered<GameplayState>, IOnStateExited<LobbyState>, IOnStateExited<GameplayState>, IOnSystemChanged<GuidebookSystem>
{
[UISystemDependency] private readonly GuidebookSystem _guidebookSystem = default!;
+ [Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IUserInterfaceManager _uiManager = default!;
+
+ private IClydeWindow? ClydeWindow { get; set; }
private GuidebookWindow? _guideWindow;
private MenuButton? GuidebookButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.GuidebookButton;
_guideWindow = UIManager.CreateWindow<GuidebookWindow>();
_guideWindow.OnClose += OnWindowClosed;
_guideWindow.OnOpen += OnWindowOpen;
+ _guideWindow.Guidebook.PopOutButton.OnPressed += _ => PopOut();
// setup keybinding
CommandBinds.Builder
string? selected = null)
{
if (_guideWindow == null)
- return;
+ {
+ _guideWindow = UIManager.CreateWindow<GuidebookWindow>();
+ _guideWindow.OnClose += OnWindowClosed;
+ _guideWindow.OnOpen += OnWindowOpen;
+ _guideWindow.Guidebook.PopOutButton.OnPressed += _ => PopOut();
+ }
if (_guideWindow.IsOpen)
{
}
}
- _guideWindow.UpdateGuides(guides, rootEntries, forceRoot, selected);
+ _guideWindow.Guidebook.UpdateGuides(guides, rootEntries, forceRoot, selected);
// Expand up to depth-2.
- _guideWindow.Tree.SetAllExpanded(false);
- _guideWindow.Tree.SetAllExpanded(true, 1);
+ _guideWindow.Guidebook.Tree.SetAllExpanded(false);
+ _guideWindow.Guidebook.Tree.SetAllExpanded(true, 1);
_guideWindow.OpenCenteredRight();
}
RecursivelyAddChildren(child, guides);
}
}
+ private void PopOut()
+ {
+ if (_guideWindow == null)
+ {
+ return;
+ }
+
+ var monitor = _clyde.EnumerateMonitors().First();
+
+ ClydeWindow = _clyde.CreateWindow(new WindowCreateParameters
+ {
+ Maximized = false,
+ Title = "Guidebook",
+ Monitor = monitor,
+ Width = 750,
+ Height = 700
+ });
+ var control = _guideWindow.Guidebook;
+ control.Orphan();
+ _guideWindow.Dispose();
+ _guideWindow = null;
+
+ ClydeWindow.RequestClosed += OnRequestClosed;
+
+ ClydeWindow.DisposeOnClose = true;
+ var Root = _uiManager.CreateWindowRoot(ClydeWindow);
+ Root.AddChild(control);
+
+ control.PopOutButton.Disabled = true;
+ control.PopOutButton.Visible = false;
+ }
+
+ private void OnRequestClosed(WindowRequestClosedEventArgs obj)
+ {
+ ClydeWindow = null;
+ _guideWindow = null;
+ if (GuidebookButton != null)
+ GuidebookButton.Pressed = false;
+ }
}
defaultWindowTitle: Space Station 14
windowIconSet: /Textures/Logo/icon
splashLogo: /Textures/Logo/logo.png
+multiWindow: true
# PJB PLEASE