using System.Collections.Generic; using System.IO; using System.Linq; using AIImages.Helpers; using RimWorld; using UnityEngine; using Verse; #pragma warning disable IDE1006 // Naming Styles namespace AIImages { /// /// Окно галереи AI-сгенерированных изображений персонажа /// [System.Diagnostics.CodeAnalysis.SuppressMessage( "Style", "IDE1006:Naming Styles", Justification = "RimWorld Window naming convention" )] [System.Diagnostics.CodeAnalysis.SuppressMessage( "Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "RimWorld Window naming convention" )] public class Window_AIGallery : Window { private Pawn pawn; private List portraitPaths = new List(); private List portraitTextures = new List(); private Vector2 mainScrollPosition = Vector2.zero; private int selectedIndex = 0; public Window_AIGallery(Pawn pawn) { this.pawn = pawn; this.doCloseX = true; this.doCloseButton = true; this.forcePause = false; this.absorbInputAroundWindow = false; this.draggable = true; this.preventCameraMotion = false; LoadGallery(); } public override Vector2 InitialSize => new Vector2(900f, 700f); /// /// Загружает галерею изображений персонажа /// private void LoadGallery() { DebugLogger.Log($"[AI Gallery] Loading gallery for {pawn?.Name}"); // Очищаем старые текстуры UnloadTextures(); // Загружаем пути к портретам portraitPaths = PawnPortraitHelper.GetAllPortraits(pawn); // Загружаем текстуры foreach (var path in portraitPaths) { if (File.Exists(path)) { try { byte[] imageData = File.ReadAllBytes(path); Texture2D texture = new Texture2D(2, 2); texture.LoadImage(imageData); portraitTextures.Add(texture); DebugLogger.Log($"[AI Gallery] Loaded texture from: {path}"); } catch (System.Exception ex) { DebugLogger.Warning($"[AI Gallery] Failed to load texture: {path}, Error: {ex.Message}"); portraitTextures.Add(null); } } else { DebugLogger.Warning($"[AI Gallery] File not found: {path}"); portraitTextures.Add(null); } } DebugLogger.Log($"[AI Gallery] Loaded {portraitTextures.Count} textures for {pawn?.Name}"); } /// /// Освобождает ресурсы текстур /// private void UnloadTextures() { foreach (var texture in portraitTextures) { if (texture != null) { Object.Destroy(texture); } } portraitTextures.Clear(); } /// /// Освобождает ресурсы при закрытии окна /// public override void PreClose() { base.PreClose(); UnloadTextures(); } /// /// Отрисовка окна /// public override void DoWindowContents(Rect inRect) { // Заголовок Text.Font = GameFont.Medium; Widgets.Label( new Rect(0f, 0f, inRect.width, 40f), "AIImages.Gallery.Title".Translate() ); // Имя персонажа Text.Font = GameFont.Small; Widgets.Label( new Rect(0f, 40f, inRect.width, 30f), "AIImages.Window.PawnLabel".Translate(pawn.NameShortColored.Resolve()) ); // Количество изображений Widgets.Label( new Rect(0f, 70f, inRect.width, 25f), "AIImages.Gallery.Count".Translate(portraitTextures.Count) ); // Разделитель Widgets.DrawLineHorizontal(0f, 100f, inRect.width); // Область для галереи Rect galleryRect = new Rect(0f, 110f, inRect.width, inRect.height - 180f); DrawGallery(galleryRect); // Кнопка обновления if ( Widgets.ButtonText( new Rect(0f, inRect.height - 70f, inRect.width * 0.3f, 35f), "AIImages.Window.Refresh".Translate() ) ) { LoadGallery(); } // Кнопка удаления выбранного изображения if ( portraitTextures.Count > 0 && selectedIndex >= 0 && selectedIndex < portraitTextures.Count && Widgets.ButtonText( new Rect(inRect.width * 0.32f, inRect.height - 70f, inRect.width * 0.3f, 35f), "AIImages.Gallery.DeleteSelected".Translate() ) ) { DeleteSelectedImage(); } // Кнопка удаления всех изображений if ( portraitTextures.Count > 0 && Widgets.ButtonText( new Rect(inRect.width * 0.64f, inRect.height - 70f, inRect.width * 0.36f, 35f), "AIImages.Gallery.DeleteAll".Translate(portraitTextures.Count) ) ) { DeleteAllImages(); } } /// /// Отрисовка галереи изображений /// private void DrawGallery(Rect rect) { if (portraitTextures.Count == 0) { Text.Anchor = TextAnchor.MiddleCenter; GUI.color = new Color(1f, 1f, 1f, 0.5f); Widgets.Label(rect, "AIImages.Gallery.Empty".Translate()); GUI.color = Color.white; Text.Anchor = TextAnchor.UpperLeft; return; } // Параметры сетки int columns = 3; int rows = Mathf.CeilToInt((float)portraitTextures.Count / columns); float cellWidth = (rect.width - 40f) / columns; float cellHeight = cellWidth + 40f; // Высота ячейки (изображение + кнопки) float spacing = 10f; float viewHeight = rows * cellHeight + spacing; Rect viewRect = new Rect(0f, 0f, rect.width - 20f, viewHeight); Widgets.BeginScrollView(rect, ref mainScrollPosition, viewRect); for (int i = 0; i < portraitTextures.Count; i++) { int row = i / columns; int col = i % columns; float x = col * (cellWidth + spacing); float y = row * cellHeight; Rect cellRect = new Rect(x, y, cellWidth, cellHeight); DrawGalleryItem(cellRect, i); } Widgets.EndScrollView(); } /// /// Отрисовка одного элемента галереи /// private void DrawGalleryItem(Rect rect, int index) { // Подсветка выбранного элемента if (index == selectedIndex) { Widgets.DrawBoxSolid(rect, new Color(0.3f, 0.5f, 0.8f, 0.2f)); Widgets.DrawBox(rect, 2); } else { Widgets.DrawBox(rect); } // Изображение Texture2D texture = portraitTextures[index]; if (texture != null) { Rect imageRect = new Rect( rect.x + 5f, rect.y + 5f, rect.width - 10f, rect.width - 10f ); // Клик по изображению для выбора if (Widgets.ButtonInvisible(imageRect)) { selectedIndex = index; } GUI.DrawTexture(imageRect, texture); } else { Rect errorRect = new Rect(rect.x + 5f, rect.y + 5f, rect.width - 10f, rect.width - 10f); Widgets.DrawBoxSolid(errorRect, new Color(0.5f, 0f, 0f, 0.5f)); Text.Anchor = TextAnchor.MiddleCenter; GUI.color = Color.white; Widgets.Label(errorRect, "AIImages.Gallery.LoadError".Translate()); GUI.color = Color.white; Text.Anchor = TextAnchor.UpperLeft; } // Информация под изображением Text.Font = GameFont.Tiny; Text.Anchor = TextAnchor.UpperLeft; float infoY = rect.y + rect.width - 5f; // Имя файла string fileName = Path.GetFileName(portraitPaths[index]); if (fileName.Length > 30) { fileName = fileName.Substring(0, 27) + "..."; } Widgets.Label( new Rect(rect.x + 5f, infoY, rect.width - 10f, 20f), fileName ); // Дата создания if (File.Exists(portraitPaths[index])) { try { var fileInfo = new FileInfo(portraitPaths[index]); string dateStr = fileInfo.CreationTime.ToString("dd.MM.yyyy HH:mm"); Widgets.Label( new Rect(rect.x + 5f, infoY + 15f, rect.width - 10f, 20f), dateStr ); } catch { // Игнорируем ошибки чтения даты } } } /// /// Удаляет выбранное изображение /// private void DeleteSelectedImage() { if (selectedIndex < 0 || selectedIndex >= portraitPaths.Count) return; string path = portraitPaths[selectedIndex]; // Подтверждение Find.WindowStack.Add( Dialog_MessageBox.CreateConfirmation( "AIImages.Gallery.ConfirmDelete".Translate(), delegate { // Удаляем из файловой системы if (File.Exists(path)) { try { File.Delete(path); DebugLogger.Log($"[AI Gallery] Deleted file: {path}"); } catch (System.Exception ex) { Messages.Message( "AIImages.Gallery.DeleteError".Translate(ex.Message), MessageTypeDefOf.RejectInput ); return; } } // Удаляем из компонента пешки PawnPortraitHelper.RemovePortrait(pawn, path); // Обновляем галерею LoadGallery(); // Сбрасываем выбор if (selectedIndex >= portraitTextures.Count) { selectedIndex = portraitTextures.Count - 1; } Messages.Message( "AIImages.Gallery.Deleted".Translate(), MessageTypeDefOf.PositiveEvent ); }, destructive: true ) ); } /// /// Удаляет все изображения /// private void DeleteAllImages() { // Подтверждение Find.WindowStack.Add( Dialog_MessageBox.CreateConfirmation( "AIImages.Gallery.ConfirmDeleteAll".Translate(portraitPaths.Count), delegate { // Удаляем все файлы int deletedCount = 0; foreach (var path in portraitPaths) { if (File.Exists(path)) { try { File.Delete(path); deletedCount++; } catch (System.Exception ex) { DebugLogger.Warning( $"[AI Gallery] Failed to delete file: {path}, Error: {ex.Message}" ); } } } // Очищаем компонент пешки PawnPortraitHelper.ClearPortrait(pawn); // Обновляем галерею LoadGallery(); selectedIndex = 0; Messages.Message( "AIImages.Gallery.AllDeleted".Translate(deletedCount), MessageTypeDefOf.PositiveEvent ); }, destructive: true ) ); } } }