Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Food Recipe Guidebook #783

Merged
merged 8 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Content.Client/Guidebook/Controls/GuideFoodComposition.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<BoxContainer xmlns="https://spacestation14.io"
Orientation="Horizontal"
HorizontalAlignment="Stretch"
HorizontalExpand="True"
Margin="0 0 0 5">
<BoxContainer Name="ReactantsContainer" Orientation="Vertical" HorizontalExpand="True" VerticalAlignment="Center">
<Label Name="ReagentLabel"
HorizontalExpand="True"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Access="Public"
Margin="2 0 0 0"/>
</BoxContainer>
<BoxContainer Orientation="Vertical" VerticalAlignment="Center">
<Label Name="AmountLabel"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Access="Public"
Margin="2 0 0 0"/>
</BoxContainer>
</BoxContainer>
31 changes: 31 additions & 0 deletions Content.Client/Guidebook/Controls/GuideFoodComposition.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Content.Client.UserInterface.ControlExtensions;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;

namespace Content.Client.Guidebook.Controls;

[UsedImplicitly, GenerateTypedNameReferences]
public sealed partial class GuideFoodComposition : BoxContainer, ISearchableControl
{
public GuideFoodComposition(ReagentPrototype proto, FixedPoint2 quantity)
{
RobustXamlLoader.Load(this);

ReagentLabel.Text = proto.LocalizedName;
AmountLabel.Text = quantity.ToString();
}

public bool CheckMatchesSearch(string query)
{
return this.ChildrenContainText(query);
}

public void SetHiddenState(bool state, string query)
{
Visible = CheckMatchesSearch(query) ? state : !state;
}
}
43 changes: 43 additions & 0 deletions Content.Client/Guidebook/Controls/GuideFoodEmbed.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<BoxContainer xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Orientation="Vertical"
Margin="5 5 5 5">
<PanelContainer HorizontalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BorderThickness="1" BorderColor="#777777"/>
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical">
<PanelContainer Name="NameBackground" HorizontalExpand="True" VerticalExpand="False">
<RichTextLabel Name="FoodName" HorizontalAlignment="Center"/>
</PanelContainer>
<BoxContainer Name="SourcesContainer" HorizontalExpand="True">
<Collapsible HorizontalExpand="True">
<CollapsibleHeading Title="{Loc 'guidebook-food-sources-header'}"/>
<CollapsibleBody>
<GridContainer Name="SourcesDescriptionContainer"
Margin="10 0 10 0"
Columns="1"
HSeparationOverride="5"
HorizontalAlignment="Stretch"
HorizontalExpand="True"/>
</CollapsibleBody>
</Collapsible>
</BoxContainer>
<BoxContainer Name="CompositionContainer" HorizontalExpand="True">
<Collapsible>
<CollapsibleHeading Title="{Loc 'guidebook-food-reagents-header'}"/>
<CollapsibleBody>
<BoxContainer Name="CompositionDescriptionContainer"
Orientation="Vertical"
Margin="10 0 10 0"
HorizontalExpand="True"/>
</CollapsibleBody>
</Collapsible>
</BoxContainer>
<BoxContainer Margin="10 5 10 10" HorizontalExpand="True">
<!-- The troublemaker !-->
<RichTextLabel Name="FoodDescription" HorizontalAlignment="Left"/>
</BoxContainer>
</BoxContainer>
</PanelContainer>
</BoxContainer>
161 changes: 161 additions & 0 deletions Content.Client/Guidebook/Controls/GuideFoodEmbed.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.Chemistry.EntitySystems;
using Content.Client.Guidebook.Richtext;
using Content.Client.Message;
using Content.Client.Nutrition.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
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;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;

namespace Content.Client.Guidebook.Controls;

/// <summary>
/// Control for embedding a food recipe into a guidebook.
/// </summary>
[UsedImplicitly, GenerateTypedNameReferences]
public sealed partial class GuideFoodEmbed : BoxContainer, IDocumentTag, ISearchableControl
{
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;

private readonly FoodGuideDataSystem _foodGuideData;
private readonly ISawmill _logger = default!;

public GuideFoodEmbed()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_foodGuideData = _systemManager.GetEntitySystem<FoodGuideDataSystem>();
_logger = Logger.GetSawmill("food guide");
MouseFilter = MouseFilterMode.Stop;
}

public GuideFoodEmbed(FoodGuideEntry entry) : this()
{
GenerateControl(entry);
}

public bool CheckMatchesSearch(string query)
{
return FoodName.GetMessage()?.Contains(query, StringComparison.InvariantCultureIgnoreCase) == true
|| FoodDescription.GetMessage()?.Contains(query, StringComparison.InvariantCultureIgnoreCase) == true;
}

public void SetHiddenState(bool state, string query)
{
Visible = CheckMatchesSearch(query) ? state : !state;
}

public bool TryParseTag(Dictionary<string, string> args, [NotNullWhen(true)] out Control? control)
{
control = null;
if (!args.TryGetValue("Food", out var id))
{
_logger.Error("Food embed tag is missing food prototype argument.");
return false;
}

if (!_foodGuideData.TryGetData(id, out var data))
{
_logger.Warning($"Specified food prototype \"{id}\" does not have any known sources.");
return false;
}

GenerateControl(data);

control = this;
return true;
}

private void GenerateControl(FoodGuideEntry data)
{
_prototype.TryIndex(data.Result, out var proto);
if (proto == null)
{
FoodName.SetMarkup(Loc.GetString("guidebook-food-unknown-proto", ("id", data.Result)));
return;
}

var composition = data.Composition
.Select(it => _prototype.TryIndex<ReagentPrototype>(it.Reagent.Prototype, out var reagent) ? (reagent, it.Quantity) : (null, 0))
.Where(it => it.reagent is not null)
.Cast<(ReagentPrototype, FixedPoint2)>()
.ToList();

#region Colors

CalculateColors(composition, out var textColor, out var backgroundColor);

NameBackground.PanelOverride = new StyleBoxFlat
{
BackgroundColor = backgroundColor
};
FoodName.SetMarkup(Loc.GetString("guidebook-food-name", ("color", textColor), ("name", proto.Name)));

#endregion

#region Sources

foreach (var source in data.Sources.OrderBy(it => it.OutputCount))
{
var control = new GuideFoodSource(proto, source, _prototype);
SourcesDescriptionContainer.AddChild(control);
}

#endregion

#region Composition

foreach (var (reagent, quantity) in composition)
{
var control = new GuideFoodComposition(reagent, quantity);
CompositionDescriptionContainer.AddChild(control);
}

#endregion

FormattedMessage description = new();
description.AddText(proto?.Description ?? string.Empty);
// Cannot describe food flavor or smth beause food is entirely server-side

FoodDescription.SetMessage(description);
}

private void CalculateColors(List<(ReagentPrototype, FixedPoint2)> composition, out Color text, out Color background)
{
// Background color is calculated as the weighted average of the colors of the composition.
// Text color is determined based on background luminosity.
float r = 0, g = 0, b = 0;
FixedPoint2 weight = 0;

foreach (var (proto, quantity) in composition)
{
var tcolor = proto.SubstanceColor;
var prevalence =
quantity <= 0 ? 0f
: weight == 0f ? 1f
: (quantity / (weight + quantity)).Float();

r = r * (1 - prevalence) + tcolor.R * prevalence;
g = g * (1 - prevalence) + tcolor.G * prevalence;
b = b * (1 - prevalence) + tcolor.B * prevalence;

if (quantity > 0)
weight += quantity;
}

// Copied from GuideReagentEmbed which was probably copied from stackoverflow. This is the formula for color luminosity.
var lum = 0.2126f * r + 0.7152f * g + 0.0722f;

background = new Color(r, g, b);
text = lum > 0.5f ? Color.Black : Color.White;
}
}
4 changes: 4 additions & 0 deletions Content.Client/Guidebook/Controls/GuideFoodGroupEmbed.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<BoxContainer xmlns="https://spacestation14.io"
Orientation="Vertical">
<BoxContainer Name="GroupContainer" Orientation="Vertical"/>
</BoxContainer>
38 changes: 38 additions & 0 deletions Content.Client/Guidebook/Controls/GuideFoodGroupEmbed.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.Guidebook.Richtext;
using Content.Client.Nutrition.EntitySystems;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;

namespace Content.Client.Guidebook.Controls;

[UsedImplicitly, GenerateTypedNameReferences]
public sealed partial class GuideFoodGroupEmbed : BoxContainer, IDocumentTag
{
[Dependency] private readonly IEntitySystemManager _sysMan = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;

public GuideFoodGroupEmbed()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
MouseFilter = MouseFilterMode.Stop;

foreach (var data in _sysMan.GetEntitySystem<FoodGuideDataSystem>().Registry.OrderBy(it => it.Identifier))
{
var embed = new GuideFoodEmbed(data);
GroupContainer.AddChild(embed);
}
}

public bool TryParseTag(Dictionary<string, string> args, [NotNullWhen(true)] out Control? control)
{
control = this;
return true;
}
}
37 changes: 37 additions & 0 deletions Content.Client/Guidebook/Controls/GuideFoodSource.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<BoxContainer xmlns="https://spacestation14.io"
Orientation="Horizontal"
HorizontalAlignment="Stretch"
HorizontalExpand="True"
Margin="0 0 0 5">
<BoxContainer Name="ReactantsContainer" Orientation="Vertical" VerticalAlignment="Center">
<TextureRect HorizontalAlignment="Center"
Name="SourceTexture"
Access="Public"/>
<!-- Using rich label here because apparently normal labels do not support soft wrap -->
<RichTextLabel Name="SourceLabel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Access="Public"
Margin="2 0 0 0"/>
</BoxContainer>
<BoxContainer Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center" HorizontalExpand="True">
<TextureRect HorizontalAlignment="Center"
Name="ProcessingTexture"
Access="Public"/>
<Label Name="ProcessingLabel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Access="Public"
Margin="2 0 0 0"/>
</BoxContainer>
<BoxContainer Orientation="Vertical" VerticalAlignment="Center">
<TextureRect HorizontalAlignment="Center"
Name="OutputsTexture"
Access="Public"/>
<Label Name="OutputsLabel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Access="Public"
Margin="2 0 0 0"/>
</BoxContainer>
</BoxContainer>
Loading
Loading