CLICK_BLOCKING_REFACTOR_TEMPLATE.md 4.2 KB

Click Blocking Refactoring Template

Problem

The click blocking functionality was duplicated across multiple UI components (ActiveQuestUI, TeamOverviewController, etc.), leading to code duplication and maintenance overhead.

Solution

Instead of inheritance (which caused compilation issues with Unity's build order), we use composition with a helper class pattern.

Refactoring Template

Step 1: Create the Helper Class

Create Assets/Scripts/UI/Common/ClickBlockingHelper.cs:

using UnityEngine;
using System.Collections;

public static class ClickBlockingHelper
{
    public static void RegisterWithClickManager(IClickBlocker blocker)
    {
        ClickManager.Instance?.RegisterClickBlocker(blocker);
    }

    public static void UnregisterWithClickManager(IClickBlocker blocker)
    {
        ClickManager.Instance?.UnregisterClickBlocker(blocker);
    }

    public static void SetClickFlag(string componentName, MonoBehaviour coroutineStarter, System.Action<bool> flagSetter)
    {
        flagSetter(true);
        Debug.Log($"🚫 {componentName}: Click flag SET - blocking map clicks");
        coroutineStarter.StopAllCoroutines();
        coroutineStarter.StartCoroutine(ResetClickFlagCoroutine(componentName, flagSetter));
    }

    private static IEnumerator ResetClickFlagCoroutine(string componentName, System.Action<bool> flagSetter)
    {
        yield return new WaitForEndOfFrame();
        yield return new WaitForEndOfFrame();
        yield return new WaitForSeconds(0.1f);
        flagSetter(false);
        Debug.Log($"🔓 {componentName}: Click flag reset - map clicks allowed again");
    }
}

Step 2: Update UI Components

For each UI component that implements IClickBlocker, replace the duplicated methods:

REPLACE THIS PATTERN:

// OLD - Duplicated code in each component
private bool recentlyHandledClick = false;

void OnEnable() => ClickManager.Instance?.RegisterClickBlocker(this);
void OnDisable() => ClickManager.Instance?.UnregisterClickBlocker(this);

private void SetClickFlag()
{
    recentlyHandledClick = true;
    Debug.Log("🚫 ComponentName: Click flag SET - blocking map clicks");
    StopAllCoroutines();
    StartCoroutine(ResetClickFlag());
}

private IEnumerator ResetClickFlag()
{
    yield return new WaitForEndOfFrame();
    yield return new WaitForEndOfFrame();
    yield return new WaitForSeconds(0.1f);
    recentlyHandledClick = false;
    Debug.Log("🔓 ComponentName: Click flag reset - map clicks allowed again");
}

public bool IsBlockingClick(Vector2 screenPosition)
{
    if (recentlyHandledClick)
    {
        Debug.Log("🚫 ComponentName: Blocking click due to recent UI interaction");
        return true;
    }
    // ... rest of method
}

WITH THIS PATTERN:

// NEW - Using helper for common functionality
private bool recentlyHandledClick = false;

void OnEnable() => ClickBlockingHelper.RegisterWithClickManager(this);
void OnDisable() => ClickBlockingHelper.UnregisterWithClickManager(this);

private void SetClickFlag()
{
    ClickBlockingHelper.SetClickFlag(GetType().Name, this, (flag) => recentlyHandledClick = flag);
}

public bool IsBlockingClick(Vector2 screenPosition)
{
    if (recentlyHandledClick)
    {
        Debug.Log($"🚫 {GetType().Name}: Blocking click due to recent UI interaction");
        return true;
    }
    // ... rest of method unchanged
}

Step 3: Components to Refactor

Apply this pattern to:

  • ActiveQuestUI.cs (already done)
  • TeamOverviewController.cs (needs refactoring)
  • TravelUI.cs (if it has the same pattern)
  • CombatEventPopupUXML.cs (if it has the same pattern)

Benefits

  1. Eliminates Code Duplication: Common logic is centralized
  2. Easier Maintenance: Changes only need to be made in one place
  3. Consistent Behavior: All components behave identically
  4. Unity-Compatible: Uses composition instead of problematic inheritance
  5. Type-Safe: Maintains compile-time safety

Usage in Event Handlers

In your UI event handlers, simply call:

someElement.RegisterCallback<MouseDownEvent>(evt =>
{
    SetClickFlag(); // Now uses the helper internally
    evt.StopPropagation();
});

The helper handles all the coroutine management and timing automatically.