Implement AI Images gallery feature in RimWorld mod, allowing users to view, delete, and manage generated images. Update UI components to include a gallery button and enhance image management functionality. Add localization strings for gallery features in English and Russian. Update AIImages.dll to reflect these changes.
This commit is contained in:
413
Source/AIImages/Window_AIGallery.cs
Normal file
413
Source/AIImages/Window_AIGallery.cs
Normal file
@@ -0,0 +1,413 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Окно галереи AI-сгенерированных изображений персонажа
|
||||
/// </summary>
|
||||
[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<string> portraitPaths = new List<string>();
|
||||
private List<Texture2D> portraitTextures = new List<Texture2D>();
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Загружает галерею изображений персонажа
|
||||
/// </summary>
|
||||
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}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Освобождает ресурсы текстур
|
||||
/// </summary>
|
||||
private void UnloadTextures()
|
||||
{
|
||||
foreach (var texture in portraitTextures)
|
||||
{
|
||||
if (texture != null)
|
||||
{
|
||||
Object.Destroy(texture);
|
||||
}
|
||||
}
|
||||
portraitTextures.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Освобождает ресурсы при закрытии окна
|
||||
/// </summary>
|
||||
public override void PreClose()
|
||||
{
|
||||
base.PreClose();
|
||||
UnloadTextures();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отрисовка окна
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отрисовка галереи изображений
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отрисовка одного элемента галереи
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
// Игнорируем ошибки чтения даты
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет выбранное изображение
|
||||
/// </summary>
|
||||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удаляет все изображения
|
||||
/// </summary>
|
||||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user