diff --git a/Assemblies/AIImages.dll b/Assemblies/AIImages.dll
index 569545a..e13e64c 100644
Binary files a/Assemblies/AIImages.dll and b/Assemblies/AIImages.dll differ
diff --git a/Defs/ImageSizePresetDefs.xml b/Defs/ImageSizePresetDefs.xml
index 1db4801..db3e967 100644
--- a/Defs/ImageSizePresetDefs.xml
+++ b/Defs/ImageSizePresetDefs.xml
@@ -3,7 +3,7 @@
Size_512x512
-
+
512
512
Square
@@ -11,7 +11,7 @@
Size_768x768
-
+
768
768
Square
@@ -19,7 +19,7 @@
Size_1024x1024
-
+
1024
1024
Square
@@ -28,7 +28,7 @@
Size_512x768
-
+
512
768
Portrait
@@ -36,7 +36,7 @@
Size_768x1024
-
+
768
1024
Portrait
@@ -44,7 +44,7 @@
Size_896x1152
-
+
896
1152
Portrait
@@ -53,7 +53,7 @@
Size_768x512
-
+
768
512
Landscape
@@ -61,7 +61,7 @@
Size_1024x768
-
+
1024
768
Landscape
@@ -69,9 +69,9 @@
Size_1152x896
-
+
1152
- 896
+ 1152
Landscape
90
diff --git a/Languages/English/Keyed/AIImages.xml b/Languages/English/Keyed/AIImages.xml
index 4a99787..202242b 100644
--- a/Languages/English/Keyed/AIImages.xml
+++ b/Languages/English/Keyed/AIImages.xml
@@ -7,6 +7,17 @@
AI Image Generator
Character: {0}
Refresh
+ Copy
+
+ Character Information
+ Gender
+ Age
+ Body Type
+ Skin Tone
+ Hairstyle
+ Hair Color
+ Traits
+ Apparel
Appearance
Appearance information unavailable
diff --git a/Languages/Russian/Keyed/AIImages.xml b/Languages/Russian/Keyed/AIImages.xml
index 5d8467e..7ade9a6 100644
--- a/Languages/Russian/Keyed/AIImages.xml
+++ b/Languages/Russian/Keyed/AIImages.xml
@@ -7,6 +7,17 @@
Генератор AI Изображений
Персонаж: {0}
Обновить
+ Копировать
+
+ Информация о персонаже
+ Пол
+ Возраст
+ Тип тела
+ Тон кожи
+ Прическа
+ Цвет волос
+ Черты характера
+ Одежда
Внешность
Информация о внешности недоступна
diff --git a/Source/AIImages/Window_AIImage.cs b/Source/AIImages/Window_AIImage.cs
index af067fa..b859683 100644
--- a/Source/AIImages/Window_AIImage.cs
+++ b/Source/AIImages/Window_AIImage.cs
@@ -51,7 +51,7 @@ namespace AIImages
{
this.pawn = pawn;
this.doCloseX = true;
- this.doCloseButton = true;
+ this.doCloseButton = false; // Убираем дублирующую кнопку "Закрыть"
this.forcePause = false;
this.absorbInputAroundWindow = false;
this.draggable = true;
@@ -75,6 +75,11 @@ namespace AIImages
private Vector2 negativePromptScrollPosition = Vector2.zero;
private float copiedMessageTime = 0f;
+ // Состояние сворачиваемых секций
+ private bool showPositivePrompt = false;
+ private bool showNegativePrompt = false;
+ private bool showCharacterInfo = true;
+
///
/// Обновляет данные персонажа
///
@@ -395,54 +400,142 @@ namespace AIImages
portraitSize,
portraitSize
);
- GUI.DrawTexture(portraitRect, generatedImage);
- contentY += portraitSize + 15f;
+
+ // Рамка вокруг портрета
+ Widgets.DrawBox(portraitRect);
+ GUI.DrawTexture(portraitRect.ContractedBy(2f), generatedImage);
+ contentY += portraitSize + 20f;
}
- // Секция "Внешность"
- Text.Font = GameFont.Medium;
- Widgets.Label(
- new Rect(10f, contentY, scrollViewRect.width - 20f, 30f),
- "AIImages.Appearance.SectionTitle".Translate()
- );
- contentY += 35f;
-
- Text.Font = GameFont.Small;
- string appearanceText = pawnDescriptionService.GetAppearanceDescription(pawn);
- float appearanceHeight = Text.CalcHeight(appearanceText, scrollViewRect.width - 30f);
- Widgets.Label(
- new Rect(20f, contentY, scrollViewRect.width - 30f, appearanceHeight),
- appearanceText
- );
- contentY += appearanceHeight + 20f;
-
- // Разделитель
- Widgets.DrawLineHorizontal(10f, contentY, scrollViewRect.width - 20f);
- contentY += 15f;
-
- // Секция "Одежда"
- Text.Font = GameFont.Medium;
- Widgets.Label(
- new Rect(10f, contentY, scrollViewRect.width - 20f, 30f),
- "AIImages.Apparel.SectionTitle".Translate()
- );
- contentY += 35f;
-
- Text.Font = GameFont.Small;
- string apparelText = pawnDescriptionService.GetApparelDescription(pawn);
- float apparelHeight = Text.CalcHeight(apparelText, scrollViewRect.width - 30f);
- Widgets.Label(
- new Rect(20f, contentY, scrollViewRect.width - 30f, apparelHeight),
- apparelText
+ // Сворачиваемая секция "Информация о персонаже"
+ DrawCollapsibleSection(
+ scrollViewRect,
+ contentY,
+ "AIImages.CharacterInfo.Title".Translate(),
+ ref showCharacterInfo,
+ () => DrawCharacterInfoContent(scrollViewRect)
);
Widgets.EndScrollView();
}
+ ///
+ /// Отрисовывает информацию о персонаже в компактном виде
+ ///
+ private float DrawCharacterInfoContent(Rect parentRect)
+ {
+ float contentHeight = 0f;
+ float lineHeight = 24f;
+ float indent = 10f;
+
+ Text.Font = GameFont.Tiny;
+
+ // Базовая информация
+ var info = new[]
+ {
+ ("AIImages.Info.Gender".Translate(), appearanceData.Gender.ToString()),
+ ("AIImages.Info.Age".Translate(), appearanceData.Age.ToString()),
+ ("AIImages.Info.BodyType".Translate(), appearanceData.BodyType),
+ (
+ "AIImages.Info.SkinTone".Translate(),
+ ColorDescriptionService.GetSkinToneDescription(appearanceData.SkinColor)
+ ),
+ ("AIImages.Info.Hair".Translate(), appearanceData.HairStyle),
+ (
+ "AIImages.Info.HairColor".Translate(),
+ ColorDescriptionService.GetHairColorDescription(appearanceData.HairColor)
+ ),
+ };
+
+ foreach (var (label, value) in info)
+ {
+ Widgets.Label(
+ new Rect(indent, contentHeight, parentRect.width * 0.4f, lineHeight),
+ label + ":"
+ );
+ Widgets.Label(
+ new Rect(
+ parentRect.width * 0.42f,
+ contentHeight,
+ parentRect.width * 0.55f,
+ lineHeight
+ ),
+ value
+ );
+ contentHeight += lineHeight;
+ }
+
+ // Черты характера
+ if (pawn.story?.traits?.allTraits != null && pawn.story.traits.allTraits.Any())
+ {
+ contentHeight += 10f;
+ Text.Font = GameFont.Small;
+ Widgets.Label(
+ new Rect(indent, contentHeight, parentRect.width - indent * 2, lineHeight),
+ "AIImages.Info.Traits".Translate() + ":"
+ );
+ contentHeight += lineHeight;
+
+ Text.Font = GameFont.Tiny;
+ var traitLabels = pawn.story.traits.allTraits.Select(trait => trait.LabelCap);
+ foreach (var traitLabel in traitLabels)
+ {
+ Widgets.Label(
+ new Rect(
+ indent * 2,
+ contentHeight,
+ parentRect.width - indent * 3,
+ lineHeight
+ ),
+ "• " + traitLabel
+ );
+ contentHeight += lineHeight;
+ }
+ }
+
+ // Одежда
+ var apparel = pawn.apparel?.WornApparel;
+ if (apparel != null && apparel.Any())
+ {
+ contentHeight += 10f;
+ Text.Font = GameFont.Small;
+ Widgets.Label(
+ new Rect(indent, contentHeight, parentRect.width - indent * 2, lineHeight),
+ "AIImages.Info.Apparel".Translate() + ":"
+ );
+ contentHeight += lineHeight;
+
+ Text.Font = GameFont.Tiny;
+ foreach (var item in apparel)
+ {
+ var colorDesc = ColorDescriptionService.GetApparelColorDescription(
+ item.DrawColor
+ );
+ string apparelLabel = $"• {colorDesc} {item.def.label}";
+ float apparelHeight = Text.CalcHeight(
+ apparelLabel,
+ parentRect.width - indent * 3
+ );
+ Widgets.Label(
+ new Rect(
+ indent * 2,
+ contentHeight,
+ parentRect.width - indent * 3,
+ apparelHeight
+ ),
+ apparelLabel
+ );
+ contentHeight += apparelHeight;
+ }
+ }
+
+ return contentHeight + 10f;
+ }
+
private void DrawRightColumn(Rect rect)
{
// Рассчитываем высоту контента для скролла
- float contentHeight = CalculateRightColumnHeight(rect);
+ float contentHeight = CalculateRightColumnHeight();
Rect scrollViewRect = new Rect(0f, 0f, rect.width - 20f, contentHeight);
Widgets.BeginScrollView(rect, ref rightColumnScrollPosition, scrollViewRect);
@@ -454,132 +547,35 @@ namespace AIImages
curY = DrawProgressBar(scrollViewRect, curY);
curY = DrawGenerationButton(scrollViewRect, curY);
- // Позитивный промпт секция
- Text.Font = GameFont.Medium;
- Widgets.Label(
- new Rect(0f, curY, scrollViewRect.width, 30f),
- "AIImages.Prompt.PositiveTitle".Translate()
- );
- curY += 35f;
-
- // Получаем позитивный промпт
- Text.Font = GameFont.Tiny;
- string positivePrompt = promptGeneratorService.GeneratePositivePrompt(
- appearanceData,
- generationSettings
- );
-
- // Фиксированная высота для области промпта
- float promptBoxHeight = 100f;
- float actualPositiveHeight = Text.CalcHeight(
- positivePrompt,
- scrollViewRect.width - 20f
- );
-
- Rect positiveOuterRect = new Rect(0f, curY, scrollViewRect.width, promptBoxHeight);
- Rect positiveViewRect = new Rect(
- 0f,
- 0f,
- scrollViewRect.width - 20f,
- actualPositiveHeight
- );
-
- // Рисуем фон
- Widgets.DrawBoxSolid(positiveOuterRect, new Color(0.1f, 0.3f, 0.1f, 0.5f));
-
- // Рисуем промпт с прокруткой
- Widgets.BeginScrollView(
- positiveOuterRect.ContractedBy(5f),
- ref promptScrollPosition,
- positiveViewRect
- );
- Widgets.Label(
- new Rect(0f, 0f, positiveViewRect.width, positiveViewRect.height),
- positivePrompt
- );
- Widgets.EndScrollView();
-
- curY += promptBoxHeight + 10f;
-
- // Негативный промпт секция
- Text.Font = GameFont.Medium;
- Widgets.Label(
- new Rect(0f, curY, scrollViewRect.width, 30f),
- "AIImages.Prompt.NegativeTitle".Translate()
- );
- curY += 35f;
-
- // Получаем негативный промпт
- Text.Font = GameFont.Tiny;
- string negativePrompt = promptGeneratorService.GenerateNegativePrompt(
- generationSettings
- );
-
- float actualNegativeHeight = Text.CalcHeight(
- negativePrompt,
- scrollViewRect.width - 20f
- );
-
- Rect negativeOuterRect = new Rect(0f, curY, scrollViewRect.width, promptBoxHeight);
- Rect negativeViewRect = new Rect(
- 0f,
- 0f,
- scrollViewRect.width - 20f,
- actualNegativeHeight
- );
-
- // Рисуем фон (красноватый для негативного)
- Widgets.DrawBoxSolid(negativeOuterRect, new Color(0.3f, 0.1f, 0.1f, 0.5f));
-
- // Рисуем промпт с прокруткой
- Widgets.BeginScrollView(
- negativeOuterRect.ContractedBy(5f),
- ref negativePromptScrollPosition,
- negativeViewRect
- );
- Widgets.Label(
- new Rect(0f, 0f, negativeViewRect.width, negativeViewRect.height),
- negativePrompt
- );
- Widgets.EndScrollView();
-
- curY += promptBoxHeight + 10f;
-
- // Кнопки копирования промптов
- if (
- Widgets.ButtonText(
- new Rect(0f, curY, scrollViewRect.width / 2f - 5f, 30f),
- "AIImages.Prompt.CopyPositive".Translate()
- )
- )
- {
- string positiveForCopy = promptGeneratorService.GeneratePositivePrompt(
- appearanceData,
- generationSettings
- );
- GUIUtility.systemCopyBuffer = positiveForCopy;
- copiedMessageTime = 2f;
- }
-
- if (
- Widgets.ButtonText(
- new Rect(
- scrollViewRect.width / 2f + 5f,
- curY,
- scrollViewRect.width / 2f - 5f,
- 30f
+ // Сворачиваемая секция с позитивным промптом
+ curY = DrawPromptSection(
+ scrollViewRect,
+ curY,
+ "AIImages.Prompt.PositiveTitle".Translate(),
+ ref showPositivePrompt,
+ () =>
+ promptGeneratorService.GeneratePositivePrompt(
+ appearanceData,
+ generationSettings
),
- "AIImages.Prompt.CopyNegative".Translate()
- )
- )
- {
- string negativeForCopy = promptGeneratorService.GenerateNegativePrompt(
- generationSettings
- );
- GUIUtility.systemCopyBuffer = negativeForCopy;
- copiedMessageTime = 2f;
- }
- curY += 35f;
+ new Color(0.1f, 0.3f, 0.1f, 0.5f),
+ ref promptScrollPosition
+ );
+
+ curY += 5f;
+
+ // Сворачиваемая секция с негативным промптом
+ curY = DrawPromptSection(
+ scrollViewRect,
+ curY,
+ "AIImages.Prompt.NegativeTitle".Translate(),
+ ref showNegativePrompt,
+ () => promptGeneratorService.GenerateNegativePrompt(generationSettings),
+ new Color(0.3f, 0.1f, 0.1f, 0.5f),
+ ref negativePromptScrollPosition
+ );
+
+ curY += 10f;
// Кнопка обновления данных
if (
@@ -641,10 +637,9 @@ namespace AIImages
return height;
}
- private float CalculateRightColumnHeight(Rect rect)
+ private float CalculateRightColumnHeight()
{
float height = 0f;
- float contentWidth = rect.width - 20f;
// Превью изображения
if (generatedImage != null)
@@ -694,6 +689,137 @@ namespace AIImages
return height + 50f; // Дополнительный отступ
}
+ ///
+ /// Отрисовывает сворачиваемую секцию с заголовком и содержимым
+ ///
+ private void DrawCollapsibleSection(
+ Rect parentRect,
+ float startY,
+ string title,
+ ref bool expanded,
+ System.Func drawContentFunc
+ )
+ {
+ float curY = startY;
+ float headerHeight = 32f;
+
+ // Рисуем заголовок с фоном
+ Rect headerRect = new Rect(0f, curY, parentRect.width, headerHeight);
+ Widgets.DrawBoxSolid(headerRect, new Color(0.25f, 0.25f, 0.25f, 0.8f));
+ Widgets.DrawBox(headerRect);
+
+ // Иконка раскрытия (треугольник)
+ string icon = expanded ? "▼" : "►";
+ Text.Font = GameFont.Small;
+ Widgets.Label(new Rect(8f, curY + 6f, 20f, headerHeight), icon);
+
+ // Заголовок
+ Text.Font = GameFont.Small;
+ Widgets.Label(new Rect(30f, curY + 6f, parentRect.width - 40f, headerHeight), title);
+
+ // Клик для раскрытия/сворачивания
+ if (Widgets.ButtonInvisible(headerRect))
+ {
+ expanded = !expanded;
+ }
+
+ // Рисуем содержимое если развернуто
+ if (expanded)
+ {
+ drawContentFunc();
+ }
+ }
+
+ ///
+ /// Отрисовывает секцию с промптом
+ ///
+ private float DrawPromptSection(
+ Rect parentRect,
+ float startY,
+ string title,
+ ref bool expanded,
+ System.Func getPromptFunc,
+ Color backgroundColor,
+ ref Vector2 scrollPosition
+ )
+ {
+ float curY = startY;
+ float headerHeight = 32f;
+
+ // Получаем промпт
+ string prompt = getPromptFunc();
+
+ // Рисуем заголовок с фоном
+ Rect headerRect = new Rect(0f, curY, parentRect.width, headerHeight);
+ Widgets.DrawBoxSolid(headerRect, new Color(0.25f, 0.25f, 0.25f, 0.8f));
+ Widgets.DrawBox(headerRect);
+
+ // Иконка раскрытия
+ string icon = expanded ? "▼" : "►";
+ Text.Font = GameFont.Small;
+ Widgets.Label(new Rect(8f, curY + 6f, 20f, headerHeight), icon);
+
+ // Заголовок
+ Widgets.Label(new Rect(30f, curY + 6f, parentRect.width - 80f, headerHeight), title);
+
+ // Кнопка копирования в заголовке
+ Rect copyButtonRect = new Rect(parentRect.width - 70f, curY + 4f, 65f, 24f);
+ if (Widgets.ButtonText(copyButtonRect, "📋 " + "AIImages.Copy".Translate()))
+ {
+ GUIUtility.systemCopyBuffer = prompt;
+ copiedMessageTime = 2f;
+ }
+
+ // Клик на остальной области для раскрытия/сворачивания
+ Rect clickableHeaderRect = new Rect(0f, curY, parentRect.width - 75f, headerHeight);
+ if (Widgets.ButtonInvisible(clickableHeaderRect))
+ {
+ expanded = !expanded;
+ }
+
+ curY += headerHeight + 4f;
+
+ // Рисуем содержимое если развернуто
+ if (expanded)
+ {
+ float promptBoxHeight = 120f;
+ Text.Font = GameFont.Tiny;
+ float actualPromptHeight = Text.CalcHeight(prompt, parentRect.width - 20f);
+
+ Rect promptOuterRect = new Rect(0f, curY, parentRect.width, promptBoxHeight);
+ Rect promptViewRect = new Rect(0f, 0f, parentRect.width - 20f, actualPromptHeight);
+
+ // Рисуем фон
+ Widgets.DrawBoxSolid(promptOuterRect, backgroundColor);
+ Widgets.DrawBox(promptOuterRect);
+
+ // Рисуем промпт с прокруткой
+ Widgets.BeginScrollView(
+ promptOuterRect.ContractedBy(5f),
+ ref scrollPosition,
+ promptViewRect
+ );
+
+ var prevAnchor = Text.Anchor;
+ Text.Anchor = TextAnchor.UpperLeft;
+ Text.WordWrap = true;
+
+ Widgets.Label(
+ new Rect(0f, 0f, promptViewRect.width, promptViewRect.height),
+ prompt
+ );
+
+ Text.Anchor = prevAnchor;
+ Text.WordWrap = true;
+
+ Widgets.EndScrollView();
+
+ curY += promptBoxHeight + 10f;
+ }
+
+ return curY;
+ }
+
private float DrawImagePreview(Rect rect, float curY)
{
if (generatedImage != null)
diff --git a/Textures/UI/Commands/AIImage.dds b/Textures/UI/Commands/AIImage.dds
new file mode 100644
index 0000000..573f9ac
Binary files /dev/null and b/Textures/UI/Commands/AIImage.dds differ