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

View File

@@ -7,6 +7,17 @@
<AIImages.Window.Title>AI Image Generator</AIImages.Window.Title> <AIImages.Window.Title>AI Image Generator</AIImages.Window.Title>
<AIImages.Window.PawnLabel>Character: {0}</AIImages.Window.PawnLabel> <AIImages.Window.PawnLabel>Character: {0}</AIImages.Window.PawnLabel>
<AIImages.Window.Refresh>Refresh</AIImages.Window.Refresh> <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 --> <!-- Appearance -->
<AIImages.Appearance.SectionTitle>Appearance</AIImages.Appearance.SectionTitle> <AIImages.Appearance.SectionTitle>Appearance</AIImages.Appearance.SectionTitle>
<AIImages.Appearance.NoInfo>Appearance information unavailable</AIImages.Appearance.NoInfo> <AIImages.Appearance.NoInfo>Appearance information unavailable</AIImages.Appearance.NoInfo>

View File

@@ -7,6 +7,17 @@
<AIImages.Window.Title>Генератор AI Изображений</AIImages.Window.Title> <AIImages.Window.Title>Генератор AI Изображений</AIImages.Window.Title>
<AIImages.Window.PawnLabel>Персонаж: {0}</AIImages.Window.PawnLabel> <AIImages.Window.PawnLabel>Персонаж: {0}</AIImages.Window.PawnLabel>
<AIImages.Window.Refresh>Обновить</AIImages.Window.Refresh> <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 --> <!-- Appearance -->
<AIImages.Appearance.SectionTitle>Внешность</AIImages.Appearance.SectionTitle> <AIImages.Appearance.SectionTitle>Внешность</AIImages.Appearance.SectionTitle>
<AIImages.Appearance.NoInfo>Информация о внешности недоступна</AIImages.Appearance.NoInfo> <AIImages.Appearance.NoInfo>Информация о внешности недоступна</AIImages.Appearance.NoInfo>

View File

@@ -51,7 +51,7 @@ namespace AIImages
{ {
this.pawn = pawn; this.pawn = pawn;
this.doCloseX = true; this.doCloseX = true;
this.doCloseButton = true; this.doCloseButton = false; // Убираем дублирующую кнопку "Закрыть"
this.forcePause = false; this.forcePause = false;
this.absorbInputAroundWindow = false; this.absorbInputAroundWindow = false;
this.draggable = true; this.draggable = true;
@@ -75,6 +75,11 @@ namespace AIImages
private Vector2 negativePromptScrollPosition = Vector2.zero; private Vector2 negativePromptScrollPosition = Vector2.zero;
private float copiedMessageTime = 0f; private float copiedMessageTime = 0f;
// Состояние сворачиваемых секций
private bool showPositivePrompt = false;
private bool showNegativePrompt = false;
private bool showCharacterInfo = true;
/// <summary> /// <summary>
/// Обновляет данные персонажа /// Обновляет данные персонажа
/// </summary> /// </summary>
@@ -395,54 +400,142 @@ namespace AIImages
portraitSize, portraitSize,
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; DrawCollapsibleSection(
Widgets.Label( scrollViewRect,
new Rect(10f, contentY, scrollViewRect.width - 20f, 30f), contentY,
"AIImages.Appearance.SectionTitle".Translate() "AIImages.CharacterInfo.Title".Translate(),
); ref showCharacterInfo,
contentY += 35f; () => DrawCharacterInfoContent(scrollViewRect)
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
); );
Widgets.EndScrollView(); 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) private void DrawRightColumn(Rect rect)
{ {
// Рассчитываем высоту контента для скролла // Рассчитываем высоту контента для скролла
float contentHeight = CalculateRightColumnHeight(rect); float contentHeight = CalculateRightColumnHeight();
Rect scrollViewRect = new Rect(0f, 0f, rect.width - 20f, contentHeight); Rect scrollViewRect = new Rect(0f, 0f, rect.width - 20f, contentHeight);
Widgets.BeginScrollView(rect, ref rightColumnScrollPosition, scrollViewRect); Widgets.BeginScrollView(rect, ref rightColumnScrollPosition, scrollViewRect);
@@ -454,132 +547,35 @@ namespace AIImages
curY = DrawProgressBar(scrollViewRect, curY); curY = DrawProgressBar(scrollViewRect, curY);
curY = DrawGenerationButton(scrollViewRect, curY); curY = DrawGenerationButton(scrollViewRect, curY);
// Позитивный промпт секция // Сворачиваемая секция с позитивным промптом
Text.Font = GameFont.Medium; curY = DrawPromptSection(
Widgets.Label( scrollViewRect,
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, curY,
scrollViewRect.width / 2f - 5f, "AIImages.Prompt.PositiveTitle".Translate(),
30f ref showPositivePrompt,
), () =>
"AIImages.Prompt.CopyNegative".Translate() promptGeneratorService.GeneratePositivePrompt(
) appearanceData,
)
{
string negativeForCopy = promptGeneratorService.GenerateNegativePrompt(
generationSettings generationSettings
),
new Color(0.1f, 0.3f, 0.1f, 0.5f),
ref promptScrollPosition
); );
GUIUtility.systemCopyBuffer = negativeForCopy;
copiedMessageTime = 2f; curY += 5f;
}
curY += 35f; // Сворачиваемая секция с негативным промптом
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 ( if (
@@ -641,10 +637,9 @@ namespace AIImages
return height; return height;
} }
private float CalculateRightColumnHeight(Rect rect) private float CalculateRightColumnHeight()
{ {
float height = 0f; float height = 0f;
float contentWidth = rect.width - 20f;
// Превью изображения // Превью изображения
if (generatedImage != null) if (generatedImage != null)
@@ -694,6 +689,137 @@ namespace AIImages
return height + 50f; // Дополнительный отступ 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) private float DrawImagePreview(Rect rect, float curY)
{ {
if (generatedImage != null) if (generatedImage != null)

Binary file not shown.