update assets to 1.03
This commit is contained in:
154
Assets/WordConnectGameToolkit/Scripts/GUI/FlexibleGridLayout.cs
Normal file
154
Assets/WordConnectGameToolkit/Scripts/GUI/FlexibleGridLayout.cs
Normal file
@ -0,0 +1,154 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WordsToolkit.Scripts.GUI
|
||||
{
|
||||
[AddComponentMenu("Layout/Flexible Grid Layout")]
|
||||
public class FlexibleGridLayout : LayoutGroup
|
||||
{
|
||||
[Header("Grid Settings")]
|
||||
[SerializeField] private bool fitX = true;
|
||||
[SerializeField] private bool fitY = true;
|
||||
[SerializeField] private int rows = 1;
|
||||
[SerializeField] private int columns = 1;
|
||||
|
||||
[Header("Cell Settings")]
|
||||
[SerializeField] private Vector2 cellSize = new Vector2(100, 100);
|
||||
[SerializeField] private Vector2 spacing = Vector2.zero;
|
||||
[SerializeField] private bool overrideHeight = false;
|
||||
[SerializeField] private float fixedCellHeight = 100f;
|
||||
|
||||
[Header("Flexible Width Options")]
|
||||
[SerializeField] private bool enableFlexibleWidth = true;
|
||||
[SerializeField] private bool enableFlexibleHeight = false;
|
||||
[SerializeField] private float minCellWidth = 50f;
|
||||
[SerializeField] private float maxCellWidth = 200f;
|
||||
[SerializeField] private bool keepAspectRatio = false;
|
||||
[SerializeField] private bool stretchLastRow = true;
|
||||
|
||||
public override void CalculateLayoutInputHorizontal()
|
||||
{
|
||||
base.CalculateLayoutInputHorizontal();
|
||||
|
||||
if (fitX || fitY)
|
||||
{
|
||||
float sqrRt = Mathf.Sqrt(transform.childCount);
|
||||
rows = Mathf.CeilToInt(sqrRt);
|
||||
columns = Mathf.CeilToInt(sqrRt);
|
||||
}
|
||||
|
||||
if (fitX)
|
||||
{
|
||||
rows = Mathf.CeilToInt(transform.childCount / (float)columns);
|
||||
}
|
||||
if (fitY)
|
||||
{
|
||||
columns = Mathf.CeilToInt(transform.childCount / (float)rows);
|
||||
}
|
||||
|
||||
CalculateAndApplyLayout();
|
||||
}
|
||||
|
||||
private void CalculateAndApplyLayout()
|
||||
{
|
||||
float parentWidth = rectTransform.rect.width;
|
||||
float parentHeight = rectTransform.rect.height;
|
||||
|
||||
float availableWidth = parentWidth - padding.left - padding.right;
|
||||
float availableHeight = parentHeight - padding.top - padding.bottom;
|
||||
|
||||
float totalSpacingWidth = spacing.x * (columns - 1);
|
||||
float totalSpacingHeight = spacing.y * (rows - 1);
|
||||
|
||||
float cellWidth = (availableWidth - totalSpacingWidth) / columns;
|
||||
float cellHeight = (availableHeight - totalSpacingHeight) / rows;
|
||||
|
||||
if (enableFlexibleWidth)
|
||||
{
|
||||
cellWidth = Mathf.Clamp(cellWidth, minCellWidth, maxCellWidth);
|
||||
if (keepAspectRatio)
|
||||
{
|
||||
cellHeight = cellWidth * (cellSize.y / cellSize.x);
|
||||
}
|
||||
}
|
||||
|
||||
if (enableFlexibleHeight && !keepAspectRatio)
|
||||
{
|
||||
cellHeight = enableFlexibleHeight ? cellHeight : cellSize.y;
|
||||
}
|
||||
|
||||
cellSize.x = enableFlexibleWidth ? cellWidth : cellSize.x;
|
||||
cellSize.y = enableFlexibleHeight || keepAspectRatio ? cellHeight : cellSize.y;
|
||||
|
||||
// Override height if specified
|
||||
if (overrideHeight)
|
||||
{
|
||||
cellSize.y = fixedCellHeight;
|
||||
}
|
||||
|
||||
SetChildrenPositions();
|
||||
}
|
||||
|
||||
private void SetChildrenPositions()
|
||||
{
|
||||
int totalChildren = rectChildren.Count;
|
||||
int lastRowStart = ((totalChildren - 1) / columns) * columns;
|
||||
int elementsInLastRow = totalChildren - lastRowStart;
|
||||
bool isLastRowIncomplete = elementsInLastRow < columns && elementsInLastRow > 0;
|
||||
|
||||
for (int i = 0; i < rectChildren.Count; i++)
|
||||
{
|
||||
int rowIndex = i / columns;
|
||||
int columnIndex = i % columns;
|
||||
bool isInLastRow = i >= lastRowStart;
|
||||
|
||||
var item = rectChildren[i];
|
||||
|
||||
float cellWidth = cellSize.x;
|
||||
float xPos;
|
||||
|
||||
if (stretchLastRow && isLastRowIncomplete && isInLastRow)
|
||||
{
|
||||
// Calculate stretched width for last row elements
|
||||
float availableWidth = rectTransform.rect.width - padding.left - padding.right;
|
||||
float totalSpacing = spacing.x * (elementsInLastRow - 1);
|
||||
float stretchedCellWidth = (availableWidth - totalSpacing) / elementsInLastRow;
|
||||
|
||||
if (enableFlexibleWidth)
|
||||
{
|
||||
stretchedCellWidth = Mathf.Clamp(stretchedCellWidth, minCellWidth, maxCellWidth);
|
||||
}
|
||||
|
||||
cellWidth = stretchedCellWidth;
|
||||
|
||||
// Calculate position for stretched elements
|
||||
int positionInLastRow = i - lastRowStart;
|
||||
xPos = padding.left + (cellWidth * positionInLastRow) + (spacing.x * positionInLastRow);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal positioning
|
||||
xPos = padding.left + (cellSize.x * columnIndex) + (spacing.x * columnIndex);
|
||||
}
|
||||
|
||||
float yPos = padding.top + (cellSize.y * rowIndex) + (spacing.y * rowIndex);
|
||||
|
||||
SetChildAlongAxis(item, 0, xPos, cellWidth);
|
||||
SetChildAlongAxis(item, 1, yPos, cellSize.y);
|
||||
}
|
||||
}
|
||||
|
||||
public override void CalculateLayoutInputVertical()
|
||||
{
|
||||
}
|
||||
|
||||
public override void SetLayoutHorizontal()
|
||||
{
|
||||
}
|
||||
|
||||
public override void SetLayoutVertical()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -10,13 +10,17 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
public static class EditorScope
|
||||
{
|
||||
private static IObjectResolver editorContainer;
|
||||
private static bool isDisposed = false;
|
||||
|
||||
public static IObjectResolver Container
|
||||
{
|
||||
get
|
||||
{
|
||||
if (editorContainer == null)
|
||||
if (isDisposed || editorContainer == null)
|
||||
{
|
||||
if (isDisposed)
|
||||
return null;
|
||||
|
||||
var builder = new ContainerBuilder();
|
||||
Configure(builder);
|
||||
editorContainer = builder.Build();
|
||||
@ -48,7 +52,31 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
|
||||
public static T Resolve<T>() where T : class
|
||||
{
|
||||
return Container.Resolve<T>();
|
||||
var container = Container;
|
||||
if (container == null || isDisposed)
|
||||
return null;
|
||||
return container.Resolve<T>();
|
||||
}
|
||||
|
||||
public static void Dispose()
|
||||
{
|
||||
isDisposed = true;
|
||||
if (editorContainer != null)
|
||||
{
|
||||
if (editorContainer is global::System.IDisposable disposableContainer)
|
||||
{
|
||||
disposableContainer.Dispose();
|
||||
}
|
||||
editorContainer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Called by Unity when domain is reloading
|
||||
[UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void Reset()
|
||||
{
|
||||
isDisposed = false;
|
||||
editorContainer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,9 @@ namespace WordsToolkit.Scripts.Levels.Editor.EditorWindows
|
||||
// Static field to track all windows and handle script recompilation
|
||||
private static List<LevelManagerWindow> activeWindows = new List<LevelManagerWindow>();
|
||||
|
||||
// Static flag to track if Unity is quitting
|
||||
private static bool isQuitting = false;
|
||||
|
||||
// Static event for when hierarchy selection changes - other windows can subscribe to this
|
||||
public static event Action<LevelHierarchyItem> OnHierarchySelectionChanged;
|
||||
|
||||
@ -64,6 +67,9 @@ namespace WordsToolkit.Scripts.Levels.Editor.EditorWindows
|
||||
activeWindows.Add(this);
|
||||
}
|
||||
|
||||
// Subscribe to Unity quitting event
|
||||
EditorApplication.quitting += OnUnityQuitting;
|
||||
|
||||
// Initialize UI
|
||||
LevelManagerWindowUI.InitializeUI(this);
|
||||
|
||||
@ -82,10 +88,23 @@ namespace WordsToolkit.Scripts.Levels.Editor.EditorWindows
|
||||
if (!string.IsNullOrEmpty(m_SelectedLanguage))
|
||||
EditorPrefs.SetString(SELECTED_LANGUAGE_KEY, m_SelectedLanguage);
|
||||
|
||||
// Unsubscribe from Unity quitting event
|
||||
EditorApplication.quitting -= OnUnityQuitting;
|
||||
|
||||
// Unregister this window instance
|
||||
activeWindows.Remove(this);
|
||||
}
|
||||
|
||||
private static void OnUnityQuitting()
|
||||
{
|
||||
isQuitting = true;
|
||||
// Dispose the editor scope container to prevent crashes
|
||||
EditorScope.Dispose();
|
||||
}
|
||||
|
||||
// Public static property to check if Unity is quitting
|
||||
public static bool IsQuitting => isQuitting;
|
||||
|
||||
private void InitializeTreeView()
|
||||
{
|
||||
// Create tree view state if needed
|
||||
|
||||
@ -845,20 +845,20 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
var wordsListContainer = new VisualElement();
|
||||
wordsListContainer.style.width = new StyleLength(new Length(100, LengthUnit.Percent));
|
||||
wordsListContainer.style.flexGrow = 1;
|
||||
|
||||
|
||||
// Store reference to the words list container for this language
|
||||
wordsListElements[langCode] = wordsListContainer;
|
||||
|
||||
|
||||
// Fill words list from the SerializedProperty using ListView
|
||||
FillWordsList(wordsListContainer, wordsProp, langCode);
|
||||
|
||||
|
||||
wordsContent.Add(wordsListContainer);
|
||||
|
||||
// Add Clear Words button
|
||||
var clearWordsButton = new Button(() =>
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("Clear Words",
|
||||
"Are you sure you want to clear all words and crossword? This action cannot be undone.",
|
||||
if (EditorUtility.DisplayDialog("Clear Words",
|
||||
"Are you sure you want to clear all words and crossword? This action cannot be undone.",
|
||||
"Clear", "Cancel"))
|
||||
{
|
||||
serializedObject.Update();
|
||||
@ -871,7 +871,7 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
{
|
||||
previewDataDict[langCode] = null;
|
||||
}
|
||||
|
||||
|
||||
// Clear the saved crossword data in the level
|
||||
var languageData = level.GetLanguageData(langCode);
|
||||
if (languageData != null)
|
||||
@ -886,10 +886,10 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
|
||||
EditorUtility.SetDirty(serializedObject.targetObject);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
|
||||
// Notify that the level needs update to refresh the crossword
|
||||
NotifyLevelNeedsUpdate(level);
|
||||
|
||||
|
||||
// Refresh the UI using LevelManagerWindow
|
||||
EditorWindows.LevelManagerWindow.RefreshInspectorForLevel(level);
|
||||
}
|
||||
@ -912,13 +912,13 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
{
|
||||
// Clear existing content first
|
||||
wordsListContainer.Clear();
|
||||
|
||||
|
||||
// Create ListView for words
|
||||
var wordsListView = new ListView();
|
||||
wordsListView.name = $"words-listview-{langCode}";
|
||||
wordsListView.style.flexGrow = 1;
|
||||
wordsListView.style.height = StyleKeyword.Auto; // Auto height based on content
|
||||
|
||||
|
||||
// Schedule setting the title after the ListView is fully constructed
|
||||
wordsListView.schedule.Execute(() =>
|
||||
{
|
||||
@ -928,7 +928,7 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
{
|
||||
var label = sizeField.Q<Label>();
|
||||
label.text = "Words";
|
||||
|
||||
|
||||
// Set the text input value directly on the TextField
|
||||
sizeField.value = wordsProp.arraySize.ToString();
|
||||
}
|
||||
@ -949,10 +949,10 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
}
|
||||
}
|
||||
}).ExecuteLater(1); // Execute after UI is built
|
||||
|
||||
|
||||
// Bind the ListView to the words property
|
||||
wordsListView.BindProperty(wordsProp);
|
||||
|
||||
|
||||
// Setup ListView properties
|
||||
wordsListView.showBorder = true;
|
||||
wordsListView.showAlternatingRowBackgrounds = AlternatingRowBackground.None;
|
||||
@ -961,16 +961,16 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
wordsListView.reorderMode = ListViewReorderMode.Animated;
|
||||
wordsListView.virtualizationMethod = CollectionVirtualizationMethod.FixedHeight;
|
||||
wordsListView.fixedItemHeight = 25; // Set a fixed height per item to help with layout
|
||||
|
||||
|
||||
// Set up the make item callback
|
||||
wordsListView.makeItem = () => CreateWordListItem(langCode);
|
||||
|
||||
|
||||
// Set up the bind item callback
|
||||
wordsListView.bindItem = (element, index) => BindWordListItem(element, index, wordsProp, langCode);
|
||||
|
||||
|
||||
// Set up the unbind item callback to clean up event handlers
|
||||
wordsListView.unbindItem = (element, index) => UnbindWordListItem(element, index);
|
||||
|
||||
|
||||
// Add callback for when items are added/removed
|
||||
wordsListView.itemsAdded += (items) =>
|
||||
{
|
||||
@ -981,7 +981,7 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
// Refresh available words UI when words are added
|
||||
EditorWindows.LevelManagerWindow.RefreshInspectorForLevel(level);
|
||||
};
|
||||
|
||||
|
||||
wordsListView.itemsRemoved += (items) =>
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
@ -1000,7 +1000,7 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
// Refresh available words UI when words are removed
|
||||
EditorWindows.LevelManagerWindow.RefreshInspectorForLevel(level);
|
||||
};
|
||||
|
||||
|
||||
wordsListContainer.Add(wordsListView);
|
||||
}
|
||||
|
||||
@ -1219,20 +1219,21 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
private void UpdateWordIcon(VisualElement iconContainer, string wordValue, string langCode)
|
||||
{
|
||||
iconContainer.Clear();
|
||||
|
||||
|
||||
// Check if word is used in other levels
|
||||
var usedInLevels = LevelEditorServices.GetUsedInLevels(wordValue, langCode, level);
|
||||
bool hasWarning = !string.IsNullOrEmpty(wordValue) && usedInLevels.Length > 0;
|
||||
|
||||
|
||||
// Get banned words service for banned status check
|
||||
var bannedWordsService = EditorScope.Resolve<IBannedWordsService>();
|
||||
bool isWordBanned = !string.IsNullOrEmpty(wordValue) && bannedWordsService != null && bannedWordsService.IsWordBanned(wordValue, langCode);
|
||||
|
||||
|
||||
// Check if word is duplicate within current level
|
||||
bool isDuplicate = IsWordDuplicateInLevel(wordValue, langCode);
|
||||
|
||||
|
||||
// Check if word is not known in model controller
|
||||
bool isWordUnknown = !string.IsNullOrEmpty(wordValue) && Controller != null &&
|
||||
bool isWordUnknown = !string.IsNullOrEmpty(wordValue)
|
||||
&& Controller != null &&
|
||||
!Controller.IsWordKnown(wordValue, langCode);
|
||||
|
||||
if (hasWarning || isWordBanned || isDuplicate || isWordUnknown)
|
||||
@ -1241,7 +1242,7 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
warningImage.image = warningIcon;
|
||||
warningImage.style.width = 16;
|
||||
warningImage.style.height = 16;
|
||||
|
||||
|
||||
// Priority: Banned > Duplicate > Used in other levels
|
||||
if (isWordBanned && isDuplicate && hasWarning)
|
||||
{
|
||||
@ -1264,7 +1265,7 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
else if (isDuplicate && hasWarning)
|
||||
{
|
||||
// Duplicate + used in other levels
|
||||
string tooltipText = usedInLevels.Length == 1
|
||||
string tooltipText = usedInLevels.Length == 1
|
||||
? $"WARNING: This word is DUPLICATED in this level AND already used in level {usedInLevels[0].number}"
|
||||
: $"WARNING: This word is DUPLICATED in this level AND already used in levels: {string.Join(", ", usedInLevels.OrderBy(l => l.number).Select(l => l.number.ToString()))}";
|
||||
iconContainer.tooltip = tooltipText;
|
||||
@ -1285,7 +1286,7 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
else if (hasWarning)
|
||||
{
|
||||
// Only used in other levels
|
||||
string tooltipText = usedInLevels.Length == 1
|
||||
string tooltipText = usedInLevels.Length == 1
|
||||
? $"This word has already been used in level {usedInLevels[0].number}"
|
||||
: $"This word has already been used in levels: {string.Join(", ", usedInLevels.OrderBy(l => l.number).Select(l => l.number.ToString()))}";
|
||||
iconContainer.tooltip = tooltipText;
|
||||
@ -1297,7 +1298,7 @@ namespace WordsToolkit.Scripts.Levels.Editor
|
||||
iconContainer.tooltip = "This word is not known in the model controller";
|
||||
warningImage.tintColor = Color.green;
|
||||
}
|
||||
|
||||
|
||||
iconContainer.Add(warningImage);
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,9 +53,9 @@ namespace WordsToolkit.Scripts.Services.IAP
|
||||
|
||||
public void UnsubscribeFromPurchaseEvent(Action<string> purchaseHandler)
|
||||
{
|
||||
#if UNITY_PURCHASING
|
||||
#if UNITY_PURCHASING
|
||||
IAPController.OnSuccessfulPurchase -= purchaseHandler;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
public void BuyProduct(string productId)
|
||||
|
||||
Reference in New Issue
Block a user