From fb9db5bef3a32583cf4f79bb5450627a4f6f92ef Mon Sep 17 00:00:00 2001 From: mcar <85767699+r00tens@users.noreply.github.com> Date: Sat, 12 Jul 2025 18:56:19 +0200 Subject: [PATCH] feat: blink animation on invalid search field input --- BestiaryUI.cs | 7 +++++- ItemCatalogueUI.cs | 31 +++++++++++++++++++++++- RecipeCatalogueUI.cs | 48 +++++++++++++++++++++++--------------- UIElements/NewUITextBox.cs | 43 +++++++++++++++++++++++++++++++++- 4 files changed, 107 insertions(+), 22 deletions(-) diff --git a/BestiaryUI.cs b/BestiaryUI.cs index db22e47..974bcb3 100644 --- a/BestiaryUI.cs +++ b/BestiaryUI.cs @@ -213,6 +213,10 @@ private int CustomSort(UIElement x, UIElement y) { return x.CompareTo(y); } + /// + /// Validates the NPC-name filter against all loaded NPC names. + /// If no match is found, removes the last character and triggers a blink to indicate invalid input. + /// private void ValidateNPCFilter() { if (npcNameFilter.currentString.Length > 0) @@ -231,6 +235,7 @@ private void ValidateNPCFilter() if (!found) { npcNameFilter.SetText(npcNameFilter.currentString.Substring(0, npcNameFilter.currentString.Length - 1)); + npcNameFilter.TriggerInvalidBlink(); } } updateNeeded = true; @@ -383,4 +388,4 @@ private void ItemChecklistNewLootOnlyFilter_SelectedChanged(object sender, Event updateNeeded = true; } } -} \ No newline at end of file +} diff --git a/ItemCatalogueUI.cs b/ItemCatalogueUI.cs index a366fa5..8fe9d0f 100644 --- a/ItemCatalogueUI.cs +++ b/ItemCatalogueUI.cs @@ -217,6 +217,10 @@ private void UnobtainedRadioButton_OnSelectedChanged(object sender, EventArgs e) updateNeeded = true; } + /// + /// Validates the item-name filter against all item slots. + /// If no match is found, removes the last character and triggers a blink to indicate invalid input. + /// private void ValidateItemFilter() { if (itemNameFilter.currentString.Length > 0) @@ -233,13 +237,38 @@ private void ValidateItemFilter() if (!found) { itemNameFilter.SetText(itemNameFilter.currentString.Substring(0, itemNameFilter.currentString.Length - 1)); + itemNameFilter.TriggerInvalidBlink(); } } updateNeeded = true; } + /// + /// Validates the item-description filter against all item slot tooltips. + /// If no match is found, removes the last character and triggers a blink to indicate invalid input. + /// private void ValidateItemDescription() { + if (itemDescriptionFilter.currentString.Length > 0) + { + bool found = false; + + foreach (var itemSlot in itemSlots) + { + if (itemSlot.item.ToolTip != null && GetTooltipsAsString(itemSlot.item.ToolTip).IndexOf(itemDescriptionFilter.currentString, StringComparison.OrdinalIgnoreCase) != -1) + { + found = true; + break; + } + } + + if (!found) + { + itemDescriptionFilter.SetText(itemDescriptionFilter.currentString.Substring(0, itemDescriptionFilter.currentString.Length - 1)); + itemDescriptionFilter.TriggerInvalidBlink(); + } + } + updateNeeded = true; } @@ -519,4 +548,4 @@ public override void RecalculateChildren() base.RecalculateChildren(); } } -} \ No newline at end of file +} diff --git a/RecipeCatalogueUI.cs b/RecipeCatalogueUI.cs index c11ebae..dad0ccd 100644 --- a/RecipeCatalogueUI.cs +++ b/RecipeCatalogueUI.cs @@ -878,7 +878,8 @@ private void ItemChecklistFilter_SelectedChanged(object sender, EventArgs e) } /// - /// Checks text to verify input is in + /// Validates the item-name filter against recipe outputs. + /// If no match is found, removes the last character and triggers a blink to indicate invalid input. /// private void ValidateItemFilter() { @@ -897,30 +898,39 @@ private void ValidateItemFilter() if (!found) { itemNameFilter.SetText(itemNameFilter.currentString.Substring(0, itemNameFilter.currentString.Length - 1)); + itemNameFilter.TriggerInvalidBlink(); } } updateNeeded = true; } + /// + /// Validates the item-description filter against recipe tooltips. + /// If no match is found, removes the last character and triggers a blink to indicate invalid input. + /// private void ValidateItemDescription() { - //if (itemNameFilter.Text.Length > 0) - //{ - // bool found = false; - // for (int i = 0; i < Recipe.numRecipes; i++) - // { - // Recipe recipe = Main.recipe[i]; - // if (recipe.createItem.name.ToLower().IndexOf(itemNameFilter.Text, StringComparison.OrdinalIgnoreCase) == -1) - // { - // found = true; - // break; - // } - // } - // if (!found) - // { - // itemNameFilter.SetText(itemNameFilter.Text.Substring(0, itemNameFilter.Text.Length - 1)); - // } - //} + if (itemDescriptionFilter.currentString.Length > 0) + { + bool found = false; + + for (int i = 0; i < Recipe.numRecipes; i++) + { + Recipe recipe = Main.recipe[i]; + if (recipe.createItem.ToolTip != null && GetTooltipsAsString(recipe.createItem.ToolTip).IndexOf(itemDescriptionFilter.currentString, StringComparison.OrdinalIgnoreCase) != -1) + { + found = true; + break; + } + } + + if (!found) + { + itemDescriptionFilter.SetText(itemDescriptionFilter.currentString.Substring(0, itemDescriptionFilter.currentString.Length - 1)); + itemDescriptionFilter.TriggerInvalidBlink(); + } + } + updateNeeded = true; } @@ -948,4 +958,4 @@ internal void InvalidateExtendedCraft() SharedUI.instance.ObtainableFilter.button.LeftClick(new UIMouseEvent(null, Vector2.Zero)); } } -} \ No newline at end of file +} diff --git a/UIElements/NewUITextBox.cs b/UIElements/NewUITextBox.cs index 205e67f..e4df24d 100644 --- a/UIElements/NewUITextBox.cs +++ b/UIElements/NewUITextBox.cs @@ -25,6 +25,15 @@ internal class NewUITextBox : UIPanel//UITextPanel private int textBlinkerCount; private int textBlinkerState; + private const int BlinkIntervalFrames = 20; // frames between blink state changes + private const int TotalBlinks = 4; // 4 state changes = 2 full blinks + private int _remainingBlinkSteps; // remaining blink state changes + private int _blinkTimer; // frame countdown until next state change + private bool _blinkState; // true when currently in blink state + + private readonly Color _defaultBorderColor; + private readonly Color _defaultBackgroundColor; + public event Action OnFocus; public event Action OnUnfocus; @@ -48,6 +57,8 @@ public NewUITextBox(string hintText, string text = "") SetPadding(0); BackgroundColor = Color.White; BorderColor = Color.White; + _defaultBackgroundColor = BackgroundColor; + _defaultBorderColor = BorderColor; // keyBoardInput.newKeyEvent += KeyboardInput_newKeyEvent; var closeButton = new UIHoverImageButton(CloseButtonTexture, ""); @@ -206,10 +217,40 @@ private static bool JustPressed(Keys key) return Main.inputText.IsKeyDown(key) && !Main.oldInputText.IsKeyDown(key); } + /// + /// Triggers the blink animation when the filter yields no matches. + /// + public void TriggerInvalidBlink() + { + _remainingBlinkSteps = TotalBlinks; + _blinkTimer = BlinkIntervalFrames; + _blinkState = true; + } + protected override void DrawSelf(SpriteBatch spriteBatch) { Rectangle hitbox = GetInnerDimensions().ToRectangle(); + // handle blink timing and state + if (_remainingBlinkSteps > 0) + { + _blinkTimer--; + if (_blinkTimer <= 0) + { + _blinkTimer = BlinkIntervalFrames; + _blinkState = !_blinkState; + _remainingBlinkSteps--; + } + } + + // apply blink colors if active, otherwise use defaults + BorderColor = (_remainingBlinkSteps > 0 && _blinkState) + ? new Color(255, 0, 0) + : _defaultBorderColor; + BackgroundColor = (_remainingBlinkSteps > 0 && _blinkState) + ? new Color(255, 107, 107) + : _defaultBackgroundColor; + // Draw panel base.DrawSelf(spriteBatch); // Main.spriteBatch.Draw(Main.magicPixel, hitbox, Color.Yellow); @@ -331,4 +372,4 @@ protected override void DrawSelf(SpriteBatch spriteBatch) // Utils.DrawBorderString(spriteBatch, "|", pos, base.TextColor, base.TextScale, 0f, 0f, -1); } } -} \ No newline at end of file +}