Update AIImages mod to improve image size presets by removing redundant labels and enhancing character information display. Add collapsible sections for positive and negative prompts in the UI, along with localized strings in English and Russian. Update AIImages.dll to reflect these changes.

This commit is contained in:
Leonid Pershin
2025-10-27 00:45:55 +03:00
parent ce98638e55
commit e3a90d6186
6 changed files with 324 additions and 176 deletions

Binary file not shown.

View File

@@ -3,7 +3,7 @@
<!-- Square Sizes -->
<AIImages.ImageSizePresetDef>
<defName>Size_512x512</defName>
<label>512x512 (Square)</label>
<label>512x512</label>
<width>512</width>
<height>512</height>
<category>Square</category>
@@ -11,7 +11,7 @@
</AIImages.ImageSizePresetDef>
<AIImages.ImageSizePresetDef>
<defName>Size_768x768</defName>
<label>768x768 (Square)</label>
<label>768x768</label>
<width>768</width>
<height>768</height>
<category>Square</category>
@@ -19,7 +19,7 @@
</AIImages.ImageSizePresetDef>
<AIImages.ImageSizePresetDef>
<defName>Size_1024x1024</defName>
<label>1024x1024 (Square)</label>
<label>1024x1024</label>
<width>1024</width>
<height>1024</height>
<category>Square</category>
@@ -28,7 +28,7 @@
<!-- Portrait Sizes -->
<AIImages.ImageSizePresetDef>
<defName>Size_512x768</defName>
<label>512x768 (Portrait)</label>
<label>512x768</label>
<width>512</width>
<height>768</height>
<category>Portrait</category>
@@ -36,7 +36,7 @@
</AIImages.ImageSizePresetDef>
<AIImages.ImageSizePresetDef>
<defName>Size_768x1024</defName>
<label>768x1024 (Portrait)</label>
<label>768x1024</label>
<width>768</width>
<height>1024</height>
<category>Portrait</category>
@@ -44,7 +44,7 @@
</AIImages.ImageSizePresetDef>
<AIImages.ImageSizePresetDef>
<defName>Size_896x1152</defName>
<label>896x1152 (Portrait)</label>
<label>896x1152</label>
<width>896</width>
<height>1152</height>
<category>Portrait</category>
@@ -53,7 +53,7 @@
<!-- Landscape Sizes -->
<AIImages.ImageSizePresetDef>
<defName>Size_768x512</defName>
<label>768x512 (Landscape)</label>
<label>768x512</label>
<width>768</width>
<height>512</height>
<category>Landscape</category>
@@ -61,7 +61,7 @@
</AIImages.ImageSizePresetDef>
<AIImages.ImageSizePresetDef>
<defName>Size_1024x768</defName>
<label>1024x768 (Landscape)</label>
<label>1024x768</label>
<width>1024</width>
<height>768</height>
<category>Landscape</category>
@@ -69,9 +69,9 @@
</AIImages.ImageSizePresetDef>
<AIImages.ImageSizePresetDef>
<defName>Size_1152x896</defName>
<label>1152x896 (Landscape)</label>
<label>1152x896</label>
<width>1152</width>
<height>896</height>
<height>1152</height>
<category>Landscape</category>
<sortOrder>90</sortOrder>
</AIImages.ImageSizePresetDef>

View File

@@ -7,6 +7,17 @@
<AIImages.Window.Title>AI Image Generator</AIImages.Window.Title>
<AIImages.Window.PawnLabel>Character: {0}</AIImages.Window.PawnLabel>
<AIImages.Window.Refresh>Refresh</AIImages.Window.Refresh>
<AIImages.Copy>Copy</AIImages.Copy>
<!-- Character Info -->
<AIImages.CharacterInfo.Title>Character Information</AIImages.CharacterInfo.Title>
<AIImages.Info.Gender>Gender</AIImages.Info.Gender>
<AIImages.Info.Age>Age</AIImages.Info.Age>
<AIImages.Info.BodyType>Body Type</AIImages.Info.BodyType>
<AIImages.Info.SkinTone>Skin Tone</AIImages.Info.SkinTone>
<AIImages.Info.Hair>Hairstyle</AIImages.Info.Hair>
<AIImages.Info.HairColor>Hair Color</AIImages.Info.HairColor>
<AIImages.Info.Traits>Traits</AIImages.Info.Traits>
<AIImages.Info.Apparel>Apparel</AIImages.Info.Apparel>
<!-- Appearance -->
<AIImages.Appearance.SectionTitle>Appearance</AIImages.Appearance.SectionTitle>
<AIImages.Appearance.NoInfo>Appearance information unavailable</AIImages.Appearance.NoInfo>

View File

@@ -7,6 +7,17 @@
<AIImages.Window.Title>Генератор AI Изображений</AIImages.Window.Title>
<AIImages.Window.PawnLabel>Персонаж: {0}</AIImages.Window.PawnLabel>
<AIImages.Window.Refresh>Обновить</AIImages.Window.Refresh>
<AIImages.Copy>Копировать</AIImages.Copy>
<!-- Character Info -->
<AIImages.CharacterInfo.Title>Информация о персонаже</AIImages.CharacterInfo.Title>
<AIImages.Info.Gender>Пол</AIImages.Info.Gender>
<AIImages.Info.Age>Возраст</AIImages.Info.Age>
<AIImages.Info.BodyType>Тип тела</AIImages.Info.BodyType>
<AIImages.Info.SkinTone>Тон кожи</AIImages.Info.SkinTone>
<AIImages.Info.Hair>Прическа</AIImages.Info.Hair>
<AIImages.Info.HairColor>Цвет волос</AIImages.Info.HairColor>
<AIImages.Info.Traits>Черты характера</AIImages.Info.Traits>
<AIImages.Info.Apparel>Одежда</AIImages.Info.Apparel>
<!-- Appearance -->
<AIImages.Appearance.SectionTitle>Внешность</AIImages.Appearance.SectionTitle>
<AIImages.Appearance.NoInfo>Информация о внешности недоступна</AIImages.Appearance.NoInfo>

View File

@@ -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;
/// <summary>
/// Обновляет данные персонажа
/// </summary>
@@ -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();
}
/// <summary>
/// Отрисовывает информацию о персонаже в компактном виде
/// </summary>
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 = DrawPromptSection(
scrollViewRect,
curY,
scrollViewRect.width / 2f - 5f,
30f
),
"AIImages.Prompt.CopyNegative".Translate()
)
)
{
string negativeForCopy = promptGeneratorService.GenerateNegativePrompt(
"AIImages.Prompt.PositiveTitle".Translate(),
ref showPositivePrompt,
() =>
promptGeneratorService.GeneratePositivePrompt(
appearanceData,
generationSettings
),
new Color(0.1f, 0.3f, 0.1f, 0.5f),
ref promptScrollPosition
);
GUIUtility.systemCopyBuffer = negativeForCopy;
copiedMessageTime = 2f;
}
curY += 35f;
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; // Дополнительный отступ
}
/// <summary>
/// Отрисовывает сворачиваемую секцию с заголовком и содержимым
/// </summary>
private void DrawCollapsibleSection(
Rect parentRect,
float startY,
string title,
ref bool expanded,
System.Func<float> 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();
}
}
/// <summary>
/// Отрисовывает секцию с промптом
/// </summary>
private float DrawPromptSection(
Rect parentRect,
float startY,
string title,
ref bool expanded,
System.Func<string> 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)

Binary file not shown.