ResearchConsoleMenu to XAML UI (#4640)

* Create/rename files

* ResearchClientServerSelectionMenu to XAML

* ResearchConsoleMenu to XAML

* Localization
This commit is contained in:
Visne
2021-09-19 19:23:32 +02:00
committed by GitHub
parent 8e802b4305
commit 4f125f9c4a
7 changed files with 301 additions and 331 deletions

View File

@@ -0,0 +1,9 @@
<SS14Window xmlns="https://spacestation14.io"
Title="{Loc 'research-client-server-selection-menu-title'}"
MinSize="300 300"
SetSize="300 300">
<ItemList Name="Servers"
SelectMode="Single">
<!-- Servers are added here by code -->
</ItemList>
</SS14Window>

View File

@@ -1,34 +1,31 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
namespace Content.Client.Research.UI namespace Content.Client.Research.UI
{ {
public class ResearchClientServerSelectionMenu : SS14Window [GenerateTypedNameReferences]
public partial class ResearchClientServerSelectionMenu : SS14Window
{ {
private readonly ItemList _servers;
private int _serverCount; private int _serverCount;
private string[] _serverNames = System.Array.Empty<string>(); private string[] _serverNames = System.Array.Empty<string>();
private int[] _serverIds = System.Array.Empty<int>(); private int[] _serverIds = System.Array.Empty<int>();
private int _selectedServerId = -1; private int _selectedServerId = -1;
public ResearchClientBoundUserInterface Owner { get; } private ResearchClientBoundUserInterface Owner { get; }
public ResearchClientServerSelectionMenu(ResearchClientBoundUserInterface owner) public ResearchClientServerSelectionMenu(ResearchClientBoundUserInterface owner)
{ {
MinSize = SetSize = (300, 300); RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
Owner = owner; Owner = owner;
Title = Loc.GetString("research-client-server-selection-menu-title");
_servers = new ItemList() {SelectMode = ItemList.ItemListSelectMode.Single}; Servers.OnItemSelected += OnItemSelected;
Servers.OnItemDeselected += OnItemDeselected;
_servers.OnItemSelected += OnItemSelected;
_servers.OnItemDeselected += OnItemDeselected;
Contents.AddChild(_servers);
} }
public void OnItemSelected(ItemList.ItemListSelectedEventArgs itemListSelectedEventArgs) public void OnItemSelected(ItemList.ItemListSelectedEventArgs itemListSelectedEventArgs)
@@ -49,22 +46,22 @@ namespace Content.Client.Research.UI
_selectedServerId = selectedServerId; _selectedServerId = selectedServerId;
// Disable so we can select the new selected server without triggering a new sync request. // Disable so we can select the new selected server without triggering a new sync request.
_servers.OnItemSelected -= OnItemSelected; Servers.OnItemSelected -= OnItemSelected;
_servers.OnItemDeselected -= OnItemDeselected; Servers.OnItemDeselected -= OnItemDeselected;
_servers.Clear(); Servers.Clear();
for (var i = 0; i < _serverCount; i++) for (var i = 0; i < _serverCount; i++)
{ {
var id = _serverIds[i]; var id = _serverIds[i];
_servers.AddItem(Loc.GetString("research-client-server-selection-menu-server-entry-text", ("id", id), ("serverName", _serverNames[i]))); Servers.AddItem(Loc.GetString("research-client-server-selection-menu-server-entry-text", ("id", id), ("serverName", _serverNames[i])));
if (id == _selectedServerId) if (id == _selectedServerId)
{ {
_servers[id].Selected = true; Servers[id].Selected = true;
} }
} }
_servers.OnItemSelected += OnItemSelected; Servers.OnItemSelected += OnItemSelected;
_servers.OnItemDeselected += OnItemDeselected; Servers.OnItemDeselected += OnItemDeselected;
} }
} }
} }

View File

@@ -1,309 +0,0 @@
using System.Collections.Generic;
using Content.Shared.Research.Prototypes;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.Utility;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Research.UI
{
public class ResearchConsoleMenu : SS14Window
{
public ResearchConsoleBoundUserInterface Owner { get; }
private readonly List<TechnologyPrototype> _unlockedTechnologyPrototypes = new();
private readonly List<TechnologyPrototype> _unlockableTechnologyPrototypes = new();
private readonly List<TechnologyPrototype> _futureTechnologyPrototypes = new();
private readonly Label _pointLabel;
private readonly Label _pointsPerSecondLabel;
private readonly Label _technologyName;
private readonly Label _technologyDescription;
private readonly Label _technologyRequirements;
private readonly TextureRect _technologyIcon;
private readonly ItemList _unlockedTechnologies;
private readonly ItemList _unlockableTechnologies;
private readonly ItemList _futureTechnologies;
public Button UnlockButton { get; private set; }
public Button ServerSelectionButton { get; private set; }
public Button ServerSyncButton { get; private set; }
public TechnologyPrototype? TechnologySelected;
public ResearchConsoleMenu(ResearchConsoleBoundUserInterface owner)
{
SetSize = MinSize = (800, 400);
IoCManager.InjectDependencies(this);
Title = Loc.GetString("research-console-menu-title");
Owner = owner;
_unlockedTechnologies = new ItemList()
{
SelectMode = ItemList.ItemListSelectMode.Button,
HorizontalExpand = true,
VerticalExpand = true,
};
_unlockedTechnologies.OnItemSelected += UnlockedTechnologySelected;
_unlockableTechnologies = new ItemList()
{
SelectMode = ItemList.ItemListSelectMode.Button,
HorizontalExpand = true,
VerticalExpand = true,
};
_unlockableTechnologies.OnItemSelected += UnlockableTechnologySelected;
_futureTechnologies = new ItemList()
{
SelectMode = ItemList.ItemListSelectMode.Button,
HorizontalExpand = true,
VerticalExpand = true,
};
_futureTechnologies.OnItemSelected += FutureTechnologySelected;
var vbox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
HorizontalExpand = true,
VerticalExpand = true,
};
var hboxTechnologies = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
HorizontalExpand = true,
VerticalExpand = true,
SizeFlagsStretchRatio = 2,
SeparationOverride = 10,
};
var hboxSelected = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
HorizontalExpand = true,
VerticalExpand = true,
SizeFlagsStretchRatio = 1
};
var vboxPoints = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
HorizontalExpand = true,
VerticalExpand = true,
SizeFlagsStretchRatio = 1,
};
var vboxTechInfo = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
HorizontalExpand = true,
VerticalExpand = true,
SizeFlagsStretchRatio = 3,
};
_pointLabel = new Label() { Text = Loc.GetString("research-console-menu-research-points-text", ("points", 0)) };
_pointsPerSecondLabel = new Label() { Text = Loc.GetString("research-console-menu-points-per-second-text", ("pointsPerSecond", 0)) };
var vboxPointsButtons = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Align = AlignMode.End,
HorizontalExpand = true,
VerticalExpand = true,
};
ServerSelectionButton = new Button() { Text = Loc.GetString("research-console-menu-server-selection-button") };
ServerSyncButton = new Button() { Text = Loc.GetString("research-console-menu-server-sync-button") };
UnlockButton = new Button() { Text = Loc.GetString("research-console-menu-server-unlock-button"), Disabled = true };
vboxPointsButtons.AddChild(ServerSelectionButton);
vboxPointsButtons.AddChild(ServerSyncButton);
vboxPointsButtons.AddChild(UnlockButton);
vboxPoints.AddChild(_pointLabel);
vboxPoints.AddChild(_pointsPerSecondLabel);
vboxPoints.AddChild(vboxPointsButtons);
_technologyIcon = new TextureRect()
{
HorizontalExpand = true,
VerticalExpand = true,
SizeFlagsStretchRatio = 1,
Stretch = TextureRect.StretchMode.KeepAspectCentered,
};
_technologyName = new Label();
_technologyDescription = new Label();
_technologyRequirements = new Label();
vboxTechInfo.AddChild(_technologyName);
vboxTechInfo.AddChild(_technologyDescription);
vboxTechInfo.AddChild(_technologyRequirements);
hboxSelected.AddChild(_technologyIcon);
hboxSelected.AddChild(vboxTechInfo);
hboxSelected.AddChild(vboxPoints);
hboxTechnologies.AddChild(_unlockedTechnologies);
hboxTechnologies.AddChild(_unlockableTechnologies);
hboxTechnologies.AddChild(_futureTechnologies);
vbox.AddChild(hboxTechnologies);
vbox.AddChild(hboxSelected);
Contents.AddChild(vbox);
UnlockButton.OnPressed += (args) =>
{
CleanSelectedTechnology();
};
Populate();
}
/// <summary>
/// Cleans the selected technology controls to blank.
/// </summary>
private void CleanSelectedTechnology()
{
UnlockButton.Disabled = true;
_technologyIcon.Texture = Texture.Transparent;
_technologyName.Text = string.Empty;
_technologyDescription.Text = string.Empty;
_technologyRequirements.Text = string.Empty;
}
/// <summary>
/// Called when an unlocked technology is selected.
/// </summary>
private void UnlockedTechnologySelected(ItemList.ItemListSelectedEventArgs obj)
{
TechnologySelected = _unlockedTechnologyPrototypes[obj.ItemIndex];
UnlockButton.Disabled = true;
PopulateSelectedTechnology();
}
/// <summary>
/// Called when an unlockable technology is selected.
/// </summary>
private void UnlockableTechnologySelected(ItemList.ItemListSelectedEventArgs obj)
{
TechnologySelected = _unlockableTechnologyPrototypes[obj.ItemIndex];
UnlockButton.Disabled = Owner.Points < TechnologySelected.RequiredPoints;
PopulateSelectedTechnology();
}
/// <summary>
/// Called when a future technology is selected
/// </summary>
private void FutureTechnologySelected(ItemList.ItemListSelectedEventArgs obj)
{
TechnologySelected = _futureTechnologyPrototypes[obj.ItemIndex];
UnlockButton.Disabled = true;
PopulateSelectedTechnology();
}
/// <summary>
/// Populate all technologies in the ItemLists.
/// </summary>
public void PopulateItemLists()
{
_unlockedTechnologies.Clear();
_unlockableTechnologies.Clear();
_futureTechnologies.Clear();
_unlockedTechnologyPrototypes.Clear();
_unlockableTechnologyPrototypes.Clear();
_futureTechnologyPrototypes.Clear();
var prototypeMan = IoCManager.Resolve<IPrototypeManager>();
// For now, we retrieve all technologies. In the future, this should be changed.
foreach (var tech in prototypeMan.EnumeratePrototypes<TechnologyPrototype>())
{
if (Owner.IsTechnologyUnlocked(tech))
{
_unlockedTechnologies.AddItem(tech.Name, tech.Icon.Frame0());
_unlockedTechnologyPrototypes.Add(tech);
}
else if (Owner.CanUnlockTechnology(tech))
{
_unlockableTechnologies.AddItem(tech.Name, tech.Icon.Frame0());
_unlockableTechnologyPrototypes.Add(tech);
}
else
{
_futureTechnologies.AddItem(tech.Name, tech.Icon.Frame0());
_futureTechnologyPrototypes.Add(tech);
}
}
}
/// <summary>
/// Fills the selected technology controls with details.
/// </summary>
public void PopulateSelectedTechnology()
{
if (TechnologySelected == null)
{
_technologyName.Text = string.Empty;
_technologyDescription.Text = string.Empty;
_technologyRequirements.Text = string.Empty;
return;
}
_technologyIcon.Texture = TechnologySelected.Icon.Frame0();
_technologyName.Text = TechnologySelected.Name;
_technologyDescription.Text = TechnologySelected.Description + $"\n{TechnologySelected.RequiredPoints} " + Loc.GetString("research-console-menu-research-points-text" ,("points", Owner.Points)).ToLowerInvariant();
_technologyRequirements.Text = Loc.GetString("research-console-tech-requirements-none");
var prototypeMan = IoCManager.Resolve<IPrototypeManager>();
for (var i = 0; i < TechnologySelected.RequiredTechnologies.Count; i++)
{
var requiredId = TechnologySelected.RequiredTechnologies[i];
if (!prototypeMan.TryIndex(requiredId, out TechnologyPrototype? prototype)) continue;
if (i == 0)
_technologyRequirements.Text = Loc.GetString("research-console-tech-requirements-prototype-name", ("prototypeName", prototype.Name));
else
_technologyRequirements.Text += $", {prototype.Name}";
}
}
/// <summary>
/// Updates the research point labels.
/// </summary>
public void PopulatePoints()
{
_pointLabel.Text = Loc.GetString("research-console-menu-research-points-text", ("points", Owner.Points));
_pointsPerSecondLabel.Text = Loc.GetString("research-console-menu-points-per-second-text", ("pointsPerSecond", Owner.PointsPerSecond));
}
/// <summary>
/// Updates the whole user interface.
/// </summary>
public void Populate()
{
PopulatePoints();
PopulateSelectedTechnology();
PopulateItemLists();
}
}
}

View File

@@ -0,0 +1,84 @@
<SS14Window xmlns="https://spacestation14.io"
Title="{Loc 'research-console-menu-title'}"
MinSize="800 400"
SetSize="800 400">
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True">
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
VerticalExpand="True"
SizeFlagsStretchRatio="2"
SeparationOverride="10">
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True">
<Label Text="{Loc 'research-console-menu-unlocked-technologies-label'}" />
<ItemList Name="UnlockedTechnologies"
SelectMode="Button"
HorizontalExpand="True"
VerticalExpand="True">
<!-- Unlocked technologies are added here by code -->
</ItemList>
</BoxContainer>
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True">
<Label Text="{Loc 'research-console-menu-unlockable-technologies-label'}" />
<ItemList Name="UnlockableTechnologies"
SelectMode="Button"
HorizontalExpand="True"
VerticalExpand="True">
<!-- Unlockable technologies are added here by code -->
</ItemList>
</BoxContainer>
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True">
<Label Text="{Loc 'research-console-menu-future-technologies-label'}" />
<ItemList Name="FutureTechnologies"
SelectMode="Button"
HorizontalExpand="True"
VerticalExpand="True">
<!-- Future technologies are added here by code -->
</ItemList>
</BoxContainer>
</BoxContainer>
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
VerticalExpand="True"
SizeFlagsStretchRatio="1">
<TextureRect Name="TechnologyIcon"
HorizontalExpand="True"
VerticalExpand="True"
SizeFlagsStretchRatio="1"
Stretch="KeepAspectCentered" />
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True"
SizeFlagsStretchRatio="3">
<Label Name="TechnologyName" />
<Label Name="TechnologyDescription" />
<Label Name="TechnologyRequirements" />
</BoxContainer>
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True"
SizeFlagsStretchRatio="1">
<Label Name="PointLabel" />
<Label Name="PointsPerSecondLabel" />
<BoxContainer Orientation="Vertical"
Align="End"
HorizontalExpand="True"
VerticalExpand="True">
<Button Name="ServerSelectionButtonProtected"
Text="{Loc 'research-console-menu-server-selection-button'}" />
<Button Name="ServerSyncButtonProtected"
Text="{Loc 'research-console-menu-server-sync-button'}" />
<Button Name="UnlockButtonProtected"
Disabled="True" />
</BoxContainer>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</SS14Window>

View File

@@ -0,0 +1,188 @@
using System.Collections.Generic;
using Content.Shared.Research.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Prototypes;
namespace Content.Client.Research.UI
{
[GenerateTypedNameReferences]
public partial class ResearchConsoleMenu : SS14Window
{
public ResearchConsoleBoundUserInterface Owner { get; }
private readonly List<TechnologyPrototype> _unlockedTechnologyPrototypes = new();
private readonly List<TechnologyPrototype> _unlockableTechnologyPrototypes = new();
private readonly List<TechnologyPrototype> _futureTechnologyPrototypes = new();
public Button UnlockButton => UnlockButtonProtected;
public Button ServerSelectionButton => ServerSelectionButtonProtected;
public Button ServerSyncButton => ServerSyncButtonProtected;
public TechnologyPrototype? TechnologySelected;
public ResearchConsoleMenu(ResearchConsoleBoundUserInterface owner)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
Owner = owner;
UnlockedTechnologies.OnItemSelected += UnlockedTechnologySelected;
UnlockableTechnologies.OnItemSelected += UnlockableTechnologySelected;
FutureTechnologies.OnItemSelected += FutureTechnologySelected;
PointLabel.Text = Loc.GetString("research-console-menu-research-points-text", ("points", 0));
PointsPerSecondLabel.Text = Loc.GetString("research-console-menu-points-per-second-text", ("pointsPerSecond", 0));
UnlockButton.Text = Loc.GetString("research-console-menu-server-unlock-button");
UnlockButton.OnPressed += _ =>
{
CleanSelectedTechnology();
};
Populate();
}
/// <summary>
/// Cleans the selected technology controls to blank.
/// </summary>
private void CleanSelectedTechnology()
{
UnlockButton.Disabled = true;
TechnologyIcon.Texture = Texture.Transparent;
TechnologyName.Text = string.Empty;
TechnologyDescription.Text = string.Empty;
TechnologyRequirements.Text = string.Empty;
}
/// <summary>
/// Called when an unlocked technology is selected.
/// </summary>
private void UnlockedTechnologySelected(ItemList.ItemListSelectedEventArgs obj)
{
TechnologySelected = _unlockedTechnologyPrototypes[obj.ItemIndex];
UnlockButton.Disabled = true;
PopulateSelectedTechnology();
}
/// <summary>
/// Called when an unlockable technology is selected.
/// </summary>
private void UnlockableTechnologySelected(ItemList.ItemListSelectedEventArgs obj)
{
TechnologySelected = _unlockableTechnologyPrototypes[obj.ItemIndex];
UnlockButton.Disabled = Owner.Points < TechnologySelected.RequiredPoints;
PopulateSelectedTechnology();
}
/// <summary>
/// Called when a future technology is selected
/// </summary>
private void FutureTechnologySelected(ItemList.ItemListSelectedEventArgs obj)
{
TechnologySelected = _futureTechnologyPrototypes[obj.ItemIndex];
UnlockButton.Disabled = true;
PopulateSelectedTechnology();
}
/// <summary>
/// Populate all technologies in the ItemLists.
/// </summary>
public void PopulateItemLists()
{
UnlockedTechnologies.Clear();
UnlockableTechnologies.Clear();
FutureTechnologies.Clear();
_unlockedTechnologyPrototypes.Clear();
_unlockableTechnologyPrototypes.Clear();
_futureTechnologyPrototypes.Clear();
var prototypeMan = IoCManager.Resolve<IPrototypeManager>();
// For now, we retrieve all technologies. In the future, this should be changed.
foreach (var tech in prototypeMan.EnumeratePrototypes<TechnologyPrototype>())
{
if (Owner.IsTechnologyUnlocked(tech))
{
UnlockedTechnologies.AddItem(tech.Name, tech.Icon.Frame0());
_unlockedTechnologyPrototypes.Add(tech);
}
else if (Owner.CanUnlockTechnology(tech))
{
UnlockableTechnologies.AddItem(tech.Name, tech.Icon.Frame0());
_unlockableTechnologyPrototypes.Add(tech);
}
else
{
FutureTechnologies.AddItem(tech.Name, tech.Icon.Frame0());
_futureTechnologyPrototypes.Add(tech);
}
}
}
/// <summary>
/// Fills the selected technology controls with details.
/// </summary>
public void PopulateSelectedTechnology()
{
if (TechnologySelected == null)
{
TechnologyName.Text = string.Empty;
TechnologyDescription.Text = string.Empty;
TechnologyRequirements.Text = string.Empty;
return;
}
TechnologyIcon.Texture = TechnologySelected.Icon.Frame0();
TechnologyName.Text = TechnologySelected.Name;
TechnologyDescription.Text = TechnologySelected.Description + $"\n{TechnologySelected.RequiredPoints} " + Loc.GetString("research-console-menu-research-points-text" ,("points", Owner.Points)).ToLowerInvariant();
TechnologyRequirements.Text = Loc.GetString("research-console-tech-requirements-none");
var prototypeMan = IoCManager.Resolve<IPrototypeManager>();
for (var i = 0; i < TechnologySelected.RequiredTechnologies.Count; i++)
{
var requiredId = TechnologySelected.RequiredTechnologies[i];
if (!prototypeMan.TryIndex(requiredId, out TechnologyPrototype? prototype)) continue;
if (i == 0)
TechnologyRequirements.Text = Loc.GetString("research-console-tech-requirements-prototype-name", ("prototypeName", prototype.Name));
else
TechnologyRequirements.Text += $", {prototype.Name}";
}
}
/// <summary>
/// Updates the research point labels.
/// </summary>
public void PopulatePoints()
{
PointLabel.Text = Loc.GetString("research-console-menu-research-points-text", ("points", Owner.Points));
PointsPerSecondLabel.Text = Loc.GetString("research-console-menu-points-per-second-text", ("pointsPerSecond", Owner.PointsPerSecond));
}
/// <summary>
/// Updates the whole user interface.
/// </summary>
public void Populate()
{
PopulatePoints();
PopulateSelectedTechnology();
PopulateItemLists();
}
}
}

View File

@@ -1,12 +1,13 @@
## UI ## UI
research-console-menu-title = R&D Console research-console-menu-title = R&D Console
research-console-menu-unlocked-technologies-label = Unlocked technologies
research-console-menu-unlockable-technologies-label = Unlockable technologies
research-console-menu-future-technologies-label = Future technologies
research-console-menu-research-points-text = Research Points: {$points} research-console-menu-research-points-text = Research Points: {$points}
research-console-menu-points-per-second-text = Points per Second {$pointsPerSecond} research-console-menu-points-per-second-text = Points per Second {$pointsPerSecond}
research-console-menu-server-selection-button = Server list research-console-menu-server-selection-button = Server list
research-console-menu-server-sync-button = Sync research-console-menu-server-sync-button = Sync
research-console-menu-server-unlock-button = Unlock research-console-menu-server-unlock-button = Unlock
research-console-menu-title = R&D Console
research-console-menu-title = R&D Console
research-console-tech-requirements-none = No technology requirements. research-console-tech-requirements-none = No technology requirements.
research-console-tech-requirements-prototype-name = Requires: {$prototypeName} research-console-tech-requirements-prototype-name = Requires: {$prototypeName}

View File

@@ -247,7 +247,7 @@
# Basic Parts Technology Tree # Basic Parts Technology Tree
- type: technology - type: technology
name: "Basic Parts Technology" name: "basic parts technology"
id: BasicPartsTechnology id: BasicPartsTechnology
description: They aren't great, but at least they're something. description: They aren't great, but at least they're something.
icon: icon: