diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs index e36a2cd174..4542d967ee 100644 --- a/Content.Client/Lobby/LobbyUIController.cs +++ b/Content.Client/Lobby/LobbyUIController.cs @@ -123,10 +123,10 @@ public sealed class LobbyUIController : UIController, IOnStateEntered()) - { - _profileEditor.RefreshTraits(); - } + // if (obj.WasModified()) // DeltaV - Refreshed in TraitsTab + // { + // _profileEditor.RefreshTraits(); + // } } } diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml index a73b46330e..c5cc2f9c69 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml @@ -3,6 +3,7 @@ xmlns:humanoid="clr-namespace:Content.Client.Humanoid" xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls" xmlns:ui="clr-namespace:Content.Client.Lobby.UI" + xmlns:traits="clr-namespace:Content.Client._DV.Traits.UI" HorizontalExpand="True"> @@ -133,12 +134,15 @@ - - - - - - + + + + + + + + + diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index f967964e18..df537509fe 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs @@ -39,6 +39,7 @@ using System.Globalization; using Content.Client._CD.Records.UI; using Content.Shared._CD.Records; // End CD - Character Records +using Content.Shared._DV.Traits; // DV - Traits namespace Content.Client.Lobby.UI { @@ -180,6 +181,8 @@ namespace Content.Client.Lobby.UI Save?.Invoke(); }; + Traits.OnTraitsChanged += OnTraitsSelectionChanged; // DeltaV + #region Left #region Name @@ -239,7 +242,6 @@ namespace Content.Client.Lobby.UI { SpeciesButton.SelectId(args.Id); SetSpecies(_species[args.Id].ID); - RefreshTraits(); // DeltaV - Allows for hiding traits UpdateHairPickers(); OnSkinColorOnValueChanged(); }; @@ -462,7 +464,7 @@ namespace Content.Client.Lobby.UI TabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab")); - RefreshTraits(); + // RefreshTraits(); // DeltaV #region Markings @@ -515,6 +517,57 @@ namespace Content.Client.Lobby.UI IsDirty = false; } + // Begin DeltaV - Traits Integration + /// + /// Called when trait selection changes in the TraitsTab. + /// Updates the profile with the new trait selection. + /// + private void OnTraitsSelectionChanged(HashSet> traits) + { + if (Profile is null) + return; + + // Remove all existing traits - iterate directly over readonly collection + foreach (var existingTrait in Profile.TraitPreferences) + { + Profile = Profile.WithoutTraitPreference(existingTrait, _prototypeManager); + } + + // Add newly selected traits + foreach (var trait in traits) + { + Profile = Profile.WithTraitPreference(trait.Id, _prototypeManager); + } + + SetDirty(); + } + + /// + /// Updates the traits tab with the current profile's selected traits. + /// + private void UpdateTraitsSelection() + { + if (Profile is null) + { + Traits.SetSelectedTraits(new HashSet>()); + return; + } + + // Convert profile's trait preferences (strings) to ProtoId + var selectedTraits = new HashSet>(Profile.TraitPreferences.Count); + foreach (var traitId in Profile.TraitPreferences) + { + // Validate that the trait still exists in prototypes + if (_prototypeManager.HasIndex(traitId)) + { + selectedTraits.Add(new ProtoId(traitId)); + } + } + + Traits.SetSelectedTraits(selectedTraits); + } + // End DeltaV - Traits Integration + /// /// Refreshes the flavor text editor status. /// @@ -549,122 +602,122 @@ namespace Content.Client.Lobby.UI /// /// Refreshes traits selector /// - public void RefreshTraits() - { - TraitsList.RemoveAllChildren(); - - var traits = _prototypeManager.EnumeratePrototypes().OrderBy(t => Loc.GetString(t.Name)).ToList(); - TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab")); - - if (traits.Count < 1) - { - TraitsList.AddChild(new Label - { - Text = Loc.GetString("humanoid-profile-editor-no-traits"), - FontColorOverride = Color.Gray, - }); - return; - } - - // Setup model - Dictionary> traitGroups = new(); - List defaultTraits = new(); - traitGroups.Add(TraitCategoryPrototype.Default, defaultTraits); - - foreach (var trait in traits) - { - // Begin DeltaV Additions - Species trait exclusion - if (Profile?.Species is { } selectedSpecies && trait.ExcludedSpecies.Contains(selectedSpecies)) - { - Profile = Profile?.WithoutTraitPreference(trait.ID, _prototypeManager); - continue; - } - // End DeltaV Additions - - if (trait.Category == null) - { - defaultTraits.Add(trait.ID); - continue; - } - - if (!_prototypeManager.HasIndex(trait.Category)) - continue; - - var group = traitGroups.GetOrNew(trait.Category); - group.Add(trait.ID); - } - - // Create UI view from model - foreach (var (categoryId, categoryTraits) in traitGroups) - { - TraitCategoryPrototype? category = null; - - if (categoryId != TraitCategoryPrototype.Default) - { - category = _prototypeManager.Index(categoryId); - // Label - TraitsList.AddChild(new Label - { - Text = Loc.GetString(category.Name), - Margin = new Thickness(0, 10, 0, 0), - StyleClasses = { StyleClass.LabelHeading }, - }); - } - - List selectors = new(); - var selectionCount = 0; - - foreach (var traitProto in categoryTraits) - { - var trait = _prototypeManager.Index(traitProto); - var selector = new TraitPreferenceSelector(trait); - - selector.Preference = Profile?.TraitPreferences.Contains(trait.ID) == true; - if (selector.Preference) - selectionCount += trait.Cost; - - selector.PreferenceChanged += preference => - { - if (preference) - { - Profile = Profile?.WithTraitPreference(trait.ID, _prototypeManager); - } - else - { - Profile = Profile?.WithoutTraitPreference(trait.ID, _prototypeManager); - } - - SetDirty(); - RefreshTraits(); // If too many traits are selected, they will be reset to the real value. - }; - selectors.Add(selector); - } - - // Selection counter - if (category is { MaxTraitPoints: >= 0 }) - { - TraitsList.AddChild(new Label - { - Text = Loc.GetString("humanoid-profile-editor-trait-count-hint", ("current", selectionCount) ,("max", category.MaxTraitPoints)), - FontColorOverride = Color.Gray - }); - } - - foreach (var selector in selectors) - { - if (selector == null) - continue; - - if (category is { MaxTraitPoints: >= 0 } && - selector.Cost + selectionCount > category.MaxTraitPoints) - { - selector.Checkbox.Label.FontColorOverride = Color.Red; - } - - TraitsList.AddChild(selector); - } - } - } + // public void RefreshTraits() + // { + // TraitsList.RemoveAllChildren(); + // + // var traits = _prototypeManager.EnumeratePrototypes().OrderBy(t => Loc.GetString(t.Name)).ToList(); + // TabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab")); + // + // if (traits.Count < 1) + // { + // TraitsList.AddChild(new Label + // { + // Text = Loc.GetString("humanoid-profile-editor-no-traits"), + // FontColorOverride = Color.Gray, + // }); + // return; + // } + // + // // Setup model + // Dictionary> traitGroups = new(); + // List defaultTraits = new(); + // traitGroups.Add(TraitCategoryPrototype.Default, defaultTraits); + // + // foreach (var trait in traits) + // { + // // Begin DeltaV Additions - Species trait exclusion + // if (Profile?.Species is { } selectedSpecies && trait.ExcludedSpecies.Contains(selectedSpecies)) + // { + // Profile = Profile?.WithoutTraitPreference(trait.ID, _prototypeManager); + // continue; + // } + // // End DeltaV Additions + // + // if (trait.Category == null) + // { + // defaultTraits.Add(trait.ID); + // continue; + // } + // + // if (!_prototypeManager.HasIndex(trait.Category)) + // continue; + // + // var group = traitGroups.GetOrNew(trait.Category); + // group.Add(trait.ID); + // } + // + // // Create UI view from model + // foreach (var (categoryId, categoryTraits) in traitGroups) + // { + // TraitCategoryPrototype? category = null; + // + // if (categoryId != TraitCategoryPrototype.Default) + // { + // category = _prototypeManager.Index(categoryId); + // // Label + // TraitsList.AddChild(new Label + // { + // Text = Loc.GetString(category.Name), + // Margin = new Thickness(0, 10, 0, 0), + // StyleClasses = { StyleClass.LabelHeading }, + // }); + // } + // + // List selectors = new(); + // var selectionCount = 0; + // + // foreach (var traitProto in categoryTraits) + // { + // var trait = _prototypeManager.Index(traitProto); + // var selector = new TraitPreferenceSelector(trait); + // + // selector.Preference = Profile?.TraitPreferences.Contains(trait.ID) == true; + // if (selector.Preference) + // selectionCount += trait.Cost; + // + // selector.PreferenceChanged += preference => + // { + // if (preference) + // { + // Profile = Profile?.WithTraitPreference(trait.ID, _prototypeManager); + // } + // else + // { + // Profile = Profile?.WithoutTraitPreference(trait.ID, _prototypeManager); + // } + // + // SetDirty(); + // RefreshTraits(); // If too many traits are selected, they will be reset to the real value. + // }; + // selectors.Add(selector); + // } + // + // // Selection counter + // if (category is { MaxTraitPoints: >= 0 }) + // { + // TraitsList.AddChild(new Label + // { + // Text = Loc.GetString("humanoid-profile-editor-trait-count-hint", ("current", selectionCount) ,("max", category.MaxTraitPoints)), + // FontColorOverride = Color.Gray + // }); + // } + // + // foreach (var selector in selectors) + // { + // if (selector == null) + // continue; + // + // if (category is { MaxTraitPoints: >= 0 } && + // selector.Cost + selectionCount > category.MaxTraitPoints) + // { + // selector.Checkbox.Label.FontColorOverride = Color.Red; + // } + // + // TraitsList.AddChild(selector); + // } + // } + // } /// /// Refreshes the species selector. @@ -845,11 +898,13 @@ namespace Content.Client.Lobby.UI _recordsTab.Update(profile); // End CD - Character Records + UpdateTraitsSelection(); // DeltaV - Traits + RefreshAntags(); RefreshJobs(); RefreshLoadouts(); RefreshSpecies(); - RefreshTraits(); + // RefreshTraits(); // DeltaV RefreshFlavorText(); ReloadPreview(); diff --git a/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs b/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs index a52a3fa2db..b7e66f0a21 100644 --- a/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs +++ b/Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs @@ -19,22 +19,24 @@ public sealed partial class TraitPreferenceSelector : Control public event Action? PreferenceChanged; - public TraitPreferenceSelector(TraitPrototype trait) - { - RobustXamlLoader.Load(this); - - var text = trait.Cost != 0 ? $"[{trait.Cost}] " : ""; - text += Loc.GetString(trait.Name); - - Cost = trait.Cost; - Checkbox.Text = text; - Checkbox.OnToggled += OnCheckBoxToggled; - - if (trait.Description is { } desc) - { - Checkbox.ToolTip = Loc.GetString(desc); - } - } + // DeltaV - This whole control is generally unused but the compiler wouldn't compile if I removed it + // So I'm just gonna comment this part out + // public TraitPreferenceSelector(TraitPrototype trait) + // { + // RobustXamlLoader.Load(this); + // + // var text = trait.Cost != 0 ? $"[{trait.Cost}] " : ""; + // text += Loc.GetString(trait.Name); + // + // Cost = trait.Cost; + // Checkbox.Text = text; + // Checkbox.OnToggled += OnCheckBoxToggled; + // + // if (trait.Description is { } desc) + // { + // Checkbox.ToolTip = Loc.GetString(desc); + // } + // } private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args) { diff --git a/Content.Client/_DV/Traits/UI/TraitCategory.xaml b/Content.Client/_DV/Traits/UI/TraitCategory.xaml new file mode 100644 index 0000000000..c1f3145218 --- /dev/null +++ b/Content.Client/_DV/Traits/UI/TraitCategory.xaml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + diff --git a/Content.Client/_DV/Traits/UI/TraitCategory.xaml.cs b/Content.Client/_DV/Traits/UI/TraitCategory.xaml.cs new file mode 100644 index 0000000000..3aad0a5086 --- /dev/null +++ b/Content.Client/_DV/Traits/UI/TraitCategory.xaml.cs @@ -0,0 +1,190 @@ +using System.Linq; +using Content.Shared._DV.Traits; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; + +namespace Content.Client._DV.Traits.UI; + +[GenerateTypedNameReferences] +public sealed partial class TraitCategory : BoxContainer +{ + public event Action, bool>? OnTraitToggled; + + private readonly TraitCategoryPrototype _category; + private readonly List _allTraits; + private readonly Dictionary, TraitEntry> _traitEntries = new(); + + private bool _isExpanded; + + public int SelectedCount; + public int PointsSpent; + + public TraitCategory(TraitCategoryPrototype category, List traits) + { + RobustXamlLoader.Load(this); + + _category = category; + _allTraits = traits; + _isExpanded = category.DefaultExpanded; + + CategoryNameLabel.Text = Loc.GetString(category.Name); + SetAccentColor(category.AccentColor); + + HeaderButton.OnPressed += _ => ToggleExpanded(); + + PopulateTraits(); + UpdateExpandedState(); + UpdateStats(); + } + + private void SetAccentColor(Color color) + { + AccentBar.PanelOverride = new StyleBoxFlat { BackgroundColor = color }; // dumb stylesheet modulation workaround + } + + private void PopulateTraits() + { + TraitsContainer.RemoveAllChildren(); + _traitEntries.Clear(); + + foreach (var trait in _allTraits) + { + var entry = new TraitEntry(trait); + entry.OnToggled += selected => OnTraitEntryToggled(trait.ID, selected); + _traitEntries[trait.ID] = entry; + TraitsContainer.AddChild(entry); + } + } + + private void OnTraitEntryToggled(ProtoId traitId, bool selected) + { + OnTraitToggled?.Invoke(traitId, selected); + } + + private void ToggleExpanded() + { + _isExpanded = !_isExpanded; + UpdateExpandedState(); + } + + private void UpdateExpandedState() + { + ContentPanel.Visible = _isExpanded; + ExpandIcon.Text = _isExpanded ? "▼" : "▶"; + } + + public void UpdateStats() + { + SelectedCount = _traitEntries.Values.Count(e => e.IsSelected); + PointsSpent = _traitEntries.Values + .Where(e => e.IsSelected) + .Sum(e => e.TraitCost); + + if (_category.MaxTraits.HasValue) + { + CategoryStatsLabel.Text = Loc.GetString("trait-category-traits", + ("selected", SelectedCount), + ("max", _category.MaxTraits.Value)); + } + else + { + CategoryStatsLabel.Text = Loc.GetString("trait-category-traits-unlimited", + ("selected", SelectedCount)); + } + + if (_category.MaxPoints.HasValue) + { + CategoryPointsLabel.Visible = true; + CategoryPointsLabel.Text = Loc.GetString("trait-category-points", + ("selected", PointsSpent), + ("max", _category.MaxPoints.Value)); + } + else + { + CategoryPointsLabel.Visible = false; + } + } + + public void SetTraitSelected(ProtoId traitId, bool selected) + { + if (_traitEntries.TryGetValue(traitId, out var entry)) + { + entry.SetSelected(selected); + } + } + + public void ClearSelection() + { + foreach (var (_, entry) in _traitEntries) + { + entry.SetSelected(false); + } + + SelectedCount = 0; + PointsSpent = 0; + UpdateStats(); + } + + /// + /// Gets the IDs of all currently selected traits in this category. + /// + public IEnumerable> GetSelectedTraitIds() + { + return _traitEntries + .Where(kvp => kvp.Value.IsSelected) + .Select(kvp => kvp.Key); + } + + /// + /// Filters traits based on search text only + /// + public void FilterTraits(string searchText) + { + var hasVisibleTraits = false; + + foreach (var (traitId, entry) in _traitEntries) + { + var trait = _allTraits.First(t => t.ID == traitId); + var name = Loc.GetString(trait.Name); + var description = Loc.GetString(trait.Description); + + var matchesSearch = string.IsNullOrEmpty(searchText) || + name.Contains(searchText, StringComparison.OrdinalIgnoreCase) || + description.Contains(searchText, StringComparison.OrdinalIgnoreCase); + + entry.Visible = matchesSearch; + + if (entry.Visible) + hasVisibleTraits = true; + } + + // Hide entire category if no traits match search + Visible = hasVisibleTraits; + } + + /// + /// Updates condition states for all trait entries based on current job/species. + /// Traits that don't meet conditions are disabled but still visible. + /// + public void UpdateConditions(string? jobId, string? speciesId) + { + foreach (var (_, entry) in _traitEntries) + { + entry.UpdateConditionsMet(jobId, speciesId); + } + + // Update stats since some traits may have been deselected + UpdateStats(); + } + + /// + /// Checks if a trait in this category meets its conditions. + /// + public bool TraitMeetsConditions(ProtoId traitId) + { + return _traitEntries.TryGetValue(traitId, out var entry) && entry.MeetsConditions; + } +} diff --git a/Content.Client/_DV/Traits/UI/TraitEntry.xaml b/Content.Client/_DV/Traits/UI/TraitEntry.xaml new file mode 100644 index 0000000000..821e195daf --- /dev/null +++ b/Content.Client/_DV/Traits/UI/TraitEntry.xaml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/_DV/Traits/UI/TraitEntry.xaml.cs b/Content.Client/_DV/Traits/UI/TraitEntry.xaml.cs new file mode 100644 index 0000000000..d9ec9a7076 --- /dev/null +++ b/Content.Client/_DV/Traits/UI/TraitEntry.xaml.cs @@ -0,0 +1,218 @@ +using Content.Shared._DV.Traits; +using Content.Shared._DV.Traits.Conditions; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Client._DV.Traits.UI; + +[GenerateTypedNameReferences] +public sealed partial class TraitEntry : PanelContainer +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly ILocalizationManager _loc = default!; + + public event Action? OnToggled; + + public bool IsSelected => TraitCheckbox.Pressed; + public int TraitCost { get; } + + private readonly TraitPrototype _trait; + private bool _isUpdating; + private readonly List _failedConditionTooltips = new(); + + public TraitEntry(TraitPrototype trait) + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + _trait = trait; + TraitCost = trait.Cost; + + // Enable mouse events so tooltips work + MouseFilter = MouseFilterMode.Pass; + + TraitNameLabel.Text = Loc.GetString(trait.Name); + TraitDescriptionLabel.SetMessage(Loc.GetString(trait.Description)); + + // Format cost display + var costPrefix = trait.Cost > 0 ? "+" : ""; + var costColor = trait.Cost > 0 ? "#ff6b6b" : trait.Cost < 0 ? "#6bff6b" : "#888888"; + TraitCostLabel.Text = $"{costPrefix}{trait.Cost}"; + TraitCostLabel.ModulateSelfOverride = Color.FromHex(costColor); + + TraitCheckbox.OnToggled += OnCheckboxToggled; + + // Build condition tooltips + UpdateConditionTooltips(); + } + + private void UpdateConditionTooltips() + { + var tooltips = new List(); + + foreach (var condition in _trait.Conditions) + { + var tooltip = condition.GetTooltip(_prototype, _loc); + if (!string.IsNullOrEmpty(tooltip)) + tooltips.Add(tooltip); + } + + if (tooltips.Count > 0) + { + var tooltipText = Loc.GetString("trait-conditions-tooltip", + ("requirements", string.Join("\n", tooltips))); + + TooltipSupplier = _ => CreateMarkupTooltip(tooltipText); + } + else + TooltipSupplier = null; + } + + /// + /// Creates a tooltip control that properly parses markup. + /// + private static Tooltip CreateMarkupTooltip(string markupText) + { + var tooltip = new Tooltip(); + + // Parse the markup into a FormattedMessage + tooltip.SetMessage(FormattedMessage.FromMarkupOrThrow(markupText)); + + return tooltip; + } + + /// + /// Updates whether conditions are met based on current job/species. + /// + public void UpdateConditionsMet(string? jobId, string? speciesId) + { + _failedConditionTooltips.Clear(); + MeetsConditions = true; + + foreach (var condition in _trait.Conditions) + { + var result = condition switch + { + IsSpeciesCondition speciesCond => CheckSpeciesCondition(speciesCond, speciesId), + HasJobCondition jobCond => CheckJobCondition(jobCond, jobId), + InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId), + _ => true, + }; + + // Apply inversion + result ^= condition.Invert; + + if (!result) + { + MeetsConditions = false; + var tooltip = condition.GetTooltip(_prototype, _loc); + if (!string.IsNullOrEmpty(tooltip)) + _failedConditionTooltips.Add(tooltip); + } + } + + UpdateDisabledState(); + } + + private bool CheckSpeciesCondition(IsSpeciesCondition condition, string? speciesId) + { + if (string.IsNullOrEmpty(speciesId)) + return false; + + return speciesId == condition.Species.Id; + } + + private bool CheckJobCondition(HasJobCondition condition, string? jobId) + { + if (string.IsNullOrEmpty(jobId)) + return false; + + return jobId == condition.Job; + } + + private bool CheckDepartmentCondition(InDepartmentCondition condition, string? jobId) + { + if (string.IsNullOrEmpty(jobId)) + return false; + + if (!_prototype.TryIndex(condition.Department, out var department)) + return false; + + return department.Roles.Contains(jobId); + } + + private void UpdateDisabledState() + { + TraitCheckbox.Disabled = !MeetsConditions; + + if (!MeetsConditions) + { + // Deselect if conditions no longer met + if (TraitCheckbox.Pressed) + { + _isUpdating = true; + TraitCheckbox.Pressed = false; + UpdateSelectedStyle(); + _isUpdating = false; + OnToggled?.Invoke(false); + } + + // Show why it's disabled + AddStyleClass("TraitsEntryDisabled"); + + // Update tooltip to show failed conditions + if (_failedConditionTooltips.Count > 0) + { + var tooltipText = Loc.GetString("trait-conditions-not-met-tooltip", + ("requirements", string.Join("\n", _failedConditionTooltips))); + + TooltipSupplier = _ => CreateMarkupTooltip(tooltipText); + } + } + else + { + RemoveStyleClass("TraitsEntryDisabled"); + UpdateConditionTooltips(); // Reset to normal tooltips + } + } + + private void OnCheckboxToggled(BaseButton.ButtonToggledEventArgs args) + { + if (_isUpdating) + return; + + if (!MeetsConditions) + { + // Prevent selection if conditions not met + _isUpdating = true; + TraitCheckbox.Pressed = false; + _isUpdating = false; + return; + } + + UpdateSelectedStyle(); + OnToggled?.Invoke(args.Pressed); + } + + public void SetSelected(bool selected) + { + _isUpdating = true; + TraitCheckbox.Pressed = selected && MeetsConditions; + UpdateSelectedStyle(); + _isUpdating = false; + } + + public bool MeetsConditions { get; private set; } = true; + + private void UpdateSelectedStyle() + { + if (TraitCheckbox.Pressed) + AddStyleClass("TraitsEntrySelected"); + else + RemoveStyleClass("TraitsEntrySelected"); + } +} diff --git a/Content.Client/_DV/Traits/UI/TraitsSheetlet.cs b/Content.Client/_DV/Traits/UI/TraitsSheetlet.cs new file mode 100644 index 0000000000..63c98dc814 --- /dev/null +++ b/Content.Client/_DV/Traits/UI/TraitsSheetlet.cs @@ -0,0 +1,224 @@ +using Content.Client.Stylesheets; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using static Content.Client.Stylesheets.StylesheetHelpers; + +namespace Content.Client._DV.Traits.UI; + +[CommonSheetlet] +public sealed class TraitsSheetlet : Sheetlet where T : PalettedStylesheet +{ + public override StyleRule[] GetRules(T sheet, object config) + { + // Color palette + // sorry but the default ColorPalette just sucks in terms of ligher/darker colors + var bgDark = Color.FromHex("#1a1a22"); + var bgMedium = Color.FromHex("#22222a"); + var bgLight = Color.FromHex("#2a2a35"); + var bgLighter = Color.FromHex("#32323e"); + var textPrimary = Color.FromHex("#e0e0e0"); + var textSecondary = Color.FromHex("#a0a0a0"); + var textMuted = Color.FromHex("#707070"); + var accentGreen = Color.FromHex("#4ade80"); + var accentYellow = Color.FromHex("#fbbf24"); + var accentRed = Color.FromHex("#f87171"); + var accentBlue = Color.FromHex("#60a5fa"); + + // StyleBoxes + var headerPanelBox = new StyleBoxFlat + { + BackgroundColor = bgLight, + BorderColor = bgLighter, + BorderThickness = new Thickness(0, 0, 0, 1) + }; + headerPanelBox.SetContentMarginOverride(StyleBox.Margin.All, 0); + + var searchBarBox = new StyleBoxFlat { BackgroundColor = bgMedium }; + searchBarBox.SetContentMarginOverride(StyleBox.Margin.All, 0); + + var searchInputBox = new StyleBoxFlat + { + BackgroundColor = bgDark, + ContentMarginLeftOverride = 8, + ContentMarginRightOverride = 8 + }; + + var footerPanelBox = new StyleBoxFlat + { + BackgroundColor = bgMedium, + BorderColor = bgLighter, + BorderThickness = new Thickness(0, 1, 0, 0) + }; + + var categoryHeaderBox = new StyleBoxFlat { BackgroundColor = bgLight }; + categoryHeaderBox.SetContentMarginOverride(StyleBox.Margin.All, 0); + + var categoryHeaderButtonBox = new StyleBoxFlat { BackgroundColor = Color.Transparent }; + categoryHeaderButtonBox.SetContentMarginOverride(StyleBox.Margin.All, 0); + + var categoryContentBox = new StyleBoxFlat { BackgroundColor = bgMedium }; + + var categoryAccentBox = new StyleBoxFlat { BackgroundColor = accentBlue }; + + var entryPanelBox = new StyleBoxFlat + { + BackgroundColor = bgLight, + BorderColor = bgLighter, + BorderThickness = new Thickness(1) + }; + entryPanelBox.SetContentMarginOverride(StyleBox.Margin.All, 0); + + var entrySelectedBox = new StyleBoxFlat + { + BackgroundColor = Color.FromHex("#2a3a4a"), + BorderColor = accentBlue, + BorderThickness = new Thickness(1, 1, 1, 1) + }; + entrySelectedBox.SetContentMarginOverride(StyleBox.Margin.All, 0); + + var progressBarBgBox = new StyleBoxFlat + { + BackgroundColor = bgDark, + BorderColor = bgLighter, + BorderThickness = new Thickness(1) + }; + + var progressBarFillFull = new StyleBoxFlat { BackgroundColor = accentGreen }; + var progressBarFillPartial = new StyleBoxFlat { BackgroundColor = accentYellow }; + var progressBarFillLow = new StyleBoxFlat { BackgroundColor = accentRed }; + var progressBarFillEmpty = new StyleBoxFlat { BackgroundColor = bgDark }; + + var rules = new List + { + // ===== HEADER PANEL ===== + E() + .Class("TraitsHeaderPanel") + .Panel(headerPanelBox), + + E