Enhance AIImages mod with settings support and improved UI for image generation. Update localized strings in English and Russian for better clarity. Refactor code for better organization and maintainability.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using AIImages.Models;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
@@ -10,7 +10,7 @@ using Verse;
|
||||
namespace AIImages
|
||||
{
|
||||
/// <summary>
|
||||
/// Empty window that opens when clicking the pawn button
|
||||
/// Окно для просмотра персонажа и генерации AI изображений
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"Style",
|
||||
@@ -25,29 +25,48 @@ namespace AIImages
|
||||
public class Window_AIImage : Window
|
||||
{
|
||||
private Pawn pawn;
|
||||
private PawnAppearanceData appearanceData;
|
||||
private StableDiffusionSettings generationSettings;
|
||||
|
||||
private Texture2D generatedImage;
|
||||
private bool isGenerating = false;
|
||||
private string generationStatus = "";
|
||||
|
||||
public Window_AIImage(Pawn pawn)
|
||||
{
|
||||
this.pawn = pawn;
|
||||
this.doCloseX = true;
|
||||
this.doCloseButton = true;
|
||||
this.forcePause = false; // Не ставим игру на паузу
|
||||
this.absorbInputAroundWindow = false; // Не блокируем клики вне окна
|
||||
this.draggable = true; // Делаем окно перемещаемым
|
||||
this.preventCameraMotion = false; // Не блокируем управление камерой
|
||||
this.forcePause = false;
|
||||
this.absorbInputAroundWindow = false;
|
||||
this.draggable = true;
|
||||
this.preventCameraMotion = false;
|
||||
|
||||
// Извлекаем данные персонажа
|
||||
RefreshPawnData();
|
||||
}
|
||||
|
||||
public override Vector2 InitialSize => new Vector2(700f, 700f);
|
||||
public override Vector2 InitialSize => new Vector2(900f, 800f);
|
||||
|
||||
private Vector2 scrollPosition = Vector2.zero;
|
||||
private float copiedMessageTime = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет данные персонажа
|
||||
/// </summary>
|
||||
private void RefreshPawnData()
|
||||
{
|
||||
appearanceData = AIImagesMod.PawnDescriptionService.ExtractAppearanceData(pawn);
|
||||
generationSettings = AIImagesMod.Settings.ToStableDiffusionSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет текущую пешку в окне
|
||||
/// </summary>
|
||||
public void UpdatePawn(Pawn newPawn)
|
||||
{
|
||||
this.pawn = newPawn;
|
||||
RefreshPawnData();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -70,7 +89,7 @@ namespace AIImages
|
||||
// Если выбрана новая колонистская пешка, обновляем окно
|
||||
if (selectedPawn != null && selectedPawn != pawn)
|
||||
{
|
||||
pawn = selectedPawn;
|
||||
UpdatePawn(selectedPawn);
|
||||
}
|
||||
|
||||
// Уменьшаем таймер сообщения о копировании
|
||||
@@ -81,274 +100,71 @@ namespace AIImages
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает описание внешности персонажа
|
||||
/// Асинхронная генерация изображения
|
||||
/// </summary>
|
||||
private string GetAppearanceDescription()
|
||||
private async System.Threading.Tasks.Task GenerateImage()
|
||||
{
|
||||
if (pawn?.story == null)
|
||||
return "AIImages.Appearance.NoInfo".Translate();
|
||||
if (isGenerating)
|
||||
return;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
isGenerating = true;
|
||||
generationStatus = "AIImages.Generation.InProgress".Translate();
|
||||
|
||||
// Пол
|
||||
sb.AppendLine("AIImages.Appearance.Gender".Translate(pawn.gender.GetLabel()));
|
||||
|
||||
// Возраст
|
||||
sb.AppendLine("AIImages.Appearance.Age".Translate(pawn.ageTracker.AgeBiologicalYears));
|
||||
|
||||
// Тип тела
|
||||
if (pawn.story.bodyType != null)
|
||||
try
|
||||
{
|
||||
sb.AppendLine(
|
||||
"AIImages.Appearance.BodyType".Translate(pawn.story.bodyType.defName)
|
||||
// Генерируем промпты
|
||||
string positivePrompt = AIImagesMod.PromptGeneratorService.GeneratePositivePrompt(
|
||||
appearanceData,
|
||||
generationSettings
|
||||
);
|
||||
}
|
||||
|
||||
// Цвет кожи
|
||||
if (pawn.story.SkinColor != null)
|
||||
{
|
||||
Color skinColor = pawn.story.SkinColor;
|
||||
sb.AppendLine(
|
||||
"AIImages.Appearance.SkinColor".Translate(
|
||||
skinColor.r.ToString("F2"),
|
||||
skinColor.g.ToString("F2"),
|
||||
skinColor.b.ToString("F2")
|
||||
)
|
||||
string negativePrompt = AIImagesMod.PromptGeneratorService.GenerateNegativePrompt(
|
||||
generationSettings
|
||||
);
|
||||
}
|
||||
|
||||
// Волосы
|
||||
if (pawn.story.hairDef != null)
|
||||
{
|
||||
sb.AppendLine("AIImages.Appearance.Hairstyle".Translate(pawn.story.hairDef.label));
|
||||
if (pawn.story.HairColor != null)
|
||||
// Создаем запрос
|
||||
var request = new GenerationRequest
|
||||
{
|
||||
sb.AppendLine(
|
||||
"AIImages.Appearance.HairColor".Translate(
|
||||
pawn.story.HairColor.r.ToString("F2"),
|
||||
pawn.story.HairColor.g.ToString("F2"),
|
||||
pawn.story.HairColor.b.ToString("F2")
|
||||
)
|
||||
Prompt = positivePrompt,
|
||||
NegativePrompt = negativePrompt,
|
||||
Steps = generationSettings.Steps,
|
||||
CfgScale = generationSettings.CfgScale,
|
||||
Width = generationSettings.Width,
|
||||
Height = generationSettings.Height,
|
||||
Sampler = generationSettings.Sampler,
|
||||
Seed = generationSettings.Seed,
|
||||
Model = AIImagesMod.Settings.apiEndpoint,
|
||||
};
|
||||
|
||||
// Генерируем изображение
|
||||
var result = await AIImagesMod.ApiService.GenerateImageAsync(request);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
// Загружаем текстуру
|
||||
generatedImage = new Texture2D(2, 2);
|
||||
generatedImage.LoadImage(result.ImageData);
|
||||
generationStatus = "AIImages.Generation.Success".Translate();
|
||||
|
||||
Messages.Message(
|
||||
"AIImages.Generation.SavedTo".Translate(result.SavedPath),
|
||||
MessageTypeDefOf.PositiveEvent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Черты характера
|
||||
if (pawn.story.traits?.allTraits != null && pawn.story.traits.allTraits.Any())
|
||||
{
|
||||
sb.AppendLine("\n" + "AIImages.Appearance.Traits".Translate());
|
||||
foreach (var trait in pawn.story.traits.allTraits)
|
||||
else
|
||||
{
|
||||
sb.AppendLine($" • {trait.LabelCap}");
|
||||
generationStatus =
|
||||
$"AIImages.Generation.Failed".Translate() + ": {result.ErrorMessage}";
|
||||
Messages.Message(generationStatus, MessageTypeDefOf.RejectInput);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает описание одежды персонажа
|
||||
/// </summary>
|
||||
private string GetApparelDescription()
|
||||
{
|
||||
if (pawn?.apparel == null)
|
||||
return "AIImages.Apparel.NoInfo".Translate();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
List<Apparel> wornApparel = pawn.apparel.WornApparel;
|
||||
|
||||
if (wornApparel == null || !wornApparel.Any())
|
||||
catch (Exception ex)
|
||||
{
|
||||
sb.AppendLine("AIImages.Apparel.NoClothes".Translate());
|
||||
generationStatus = $"Error: {ex.Message}";
|
||||
Log.Error($"[AI Images] Generation error: {ex}");
|
||||
}
|
||||
else
|
||||
finally
|
||||
{
|
||||
sb.AppendLine("AIImages.Apparel.ListHeader".Translate(wornApparel.Count) + "\n");
|
||||
foreach (Apparel apparel in wornApparel)
|
||||
{
|
||||
FormatApparelItem(sb, apparel);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Форматирует информацию об одном предмете одежды
|
||||
/// </summary>
|
||||
private void FormatApparelItem(StringBuilder sb, Apparel apparel)
|
||||
{
|
||||
sb.AppendLine($"• {apparel.LabelCap}");
|
||||
|
||||
if (apparel.TryGetQuality(out QualityCategory quality))
|
||||
{
|
||||
sb.AppendLine("AIImages.Apparel.Quality".Translate(quality.GetLabel()));
|
||||
}
|
||||
|
||||
if (apparel.Stuff != null)
|
||||
{
|
||||
sb.AppendLine("AIImages.Apparel.Material".Translate(apparel.Stuff.LabelCap));
|
||||
}
|
||||
|
||||
if (apparel.HitPoints < apparel.MaxHitPoints)
|
||||
{
|
||||
int percentage = (int)((float)apparel.HitPoints / apparel.MaxHitPoints * 100);
|
||||
sb.AppendLine(
|
||||
"AIImages.Apparel.Durability".Translate(
|
||||
apparel.HitPoints,
|
||||
apparel.MaxHitPoints,
|
||||
percentage
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (apparel.DrawColor != Color.white)
|
||||
{
|
||||
sb.AppendLine(
|
||||
"AIImages.Apparel.Color".Translate(
|
||||
apparel.DrawColor.r.ToString("F2"),
|
||||
apparel.DrawColor.g.ToString("F2"),
|
||||
apparel.DrawColor.b.ToString("F2")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Генерирует промпт для Stable Diffusion на основе внешности персонажа
|
||||
/// </summary>
|
||||
private string GenerateStableDiffusionPrompt()
|
||||
{
|
||||
if (pawn?.story == null)
|
||||
return "portrait of a person";
|
||||
|
||||
StringBuilder prompt = new StringBuilder("portrait of a ");
|
||||
|
||||
prompt.Append(GetAgeAndGenderDescription());
|
||||
prompt.Append(GetBodyTypeDescription());
|
||||
prompt.Append(GetSkinToneDescription());
|
||||
prompt.Append(GetHairDescription());
|
||||
prompt.Append(GetApparelPromptDescription());
|
||||
prompt.Append(
|
||||
"realistic, detailed, high quality, professional lighting, 8k, photorealistic"
|
||||
);
|
||||
|
||||
return prompt.ToString();
|
||||
}
|
||||
|
||||
private string GetAgeAndGenderDescription()
|
||||
{
|
||||
string ageGroup = pawn.ageTracker.AgeBiologicalYears switch
|
||||
{
|
||||
< 18 => "young",
|
||||
< 30 => "young adult",
|
||||
< 50 => "middle-aged",
|
||||
_ => "mature",
|
||||
};
|
||||
return $"{ageGroup} {pawn.gender.GetLabel()}, ";
|
||||
}
|
||||
|
||||
private string GetBodyTypeDescription()
|
||||
{
|
||||
if (pawn.story.bodyType == null)
|
||||
return "";
|
||||
|
||||
string bodyDesc = pawn.story.bodyType.defName.ToLower() switch
|
||||
{
|
||||
"thin" => "slender build",
|
||||
"hulk" => "muscular build",
|
||||
"fat" => "heavyset build",
|
||||
_ => "average build",
|
||||
};
|
||||
return $"{bodyDesc}, ";
|
||||
}
|
||||
|
||||
private string GetSkinToneDescription()
|
||||
{
|
||||
if (pawn.story.SkinColor == null)
|
||||
return "";
|
||||
|
||||
float brightness =
|
||||
(pawn.story.SkinColor.r + pawn.story.SkinColor.g + pawn.story.SkinColor.b) / 3f;
|
||||
string skinTone = brightness switch
|
||||
{
|
||||
>= 0.8f => "fair skin",
|
||||
>= 0.6f => "light skin",
|
||||
>= 0.4f => "olive skin",
|
||||
>= 0.2f => "brown skin",
|
||||
_ => "dark skin",
|
||||
};
|
||||
return $"{skinTone}, ";
|
||||
}
|
||||
|
||||
private string GetHairDescription()
|
||||
{
|
||||
if (pawn.story.hairDef == null)
|
||||
return "";
|
||||
|
||||
string result = $"{pawn.story.hairDef.label.ToLower()} hair";
|
||||
if (pawn.story.HairColor != null)
|
||||
{
|
||||
result += $", {GetColorDescription(pawn.story.HairColor)} hair color";
|
||||
}
|
||||
return result + ", ";
|
||||
}
|
||||
|
||||
private string GetApparelPromptDescription()
|
||||
{
|
||||
if (pawn.apparel?.WornApparel == null || !pawn.apparel.WornApparel.Any())
|
||||
return "";
|
||||
|
||||
List<string> items = pawn
|
||||
.apparel.WornApparel.Take(3)
|
||||
.Select(a =>
|
||||
a.Stuff != null
|
||||
? $"{a.Stuff.label.ToLower()} {a.def.label.ToLower()}"
|
||||
: a.def.label.ToLower()
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return $"wearing {string.Join(", ", items)}, ";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает текстовое описание цвета
|
||||
/// </summary>
|
||||
private string GetColorDescription(Color color)
|
||||
{
|
||||
// Определяем доминирующий цвет
|
||||
float max = Mathf.Max(color.r, color.g, color.b);
|
||||
float min = Mathf.Min(color.r, color.g, color.b);
|
||||
float diff = max - min;
|
||||
|
||||
if (diff < 0.1f)
|
||||
{
|
||||
// Оттенки серого
|
||||
return max switch
|
||||
{
|
||||
> 0.8f => "white",
|
||||
> 0.6f => "light gray",
|
||||
> 0.4f => "gray",
|
||||
> 0.2f => "dark gray",
|
||||
_ => "black",
|
||||
};
|
||||
}
|
||||
|
||||
// Цветные
|
||||
const float epsilon = 0.001f;
|
||||
if (Mathf.Abs(color.r - max) < epsilon)
|
||||
{
|
||||
return color.g > color.b ? "orange" : "red";
|
||||
}
|
||||
else if (Mathf.Abs(color.g - max) < epsilon)
|
||||
{
|
||||
return color.r > color.b ? "yellow" : "green";
|
||||
}
|
||||
else
|
||||
{
|
||||
return color.r > color.g ? "purple" : "blue";
|
||||
isGenerating = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,16 +192,29 @@ namespace AIImages
|
||||
Widgets.DrawLineHorizontal(0f, curY, inRect.width);
|
||||
curY += 10f;
|
||||
|
||||
// Область для прокрутки контента
|
||||
Rect scrollRect = new Rect(0f, curY, inRect.width, inRect.height - curY);
|
||||
Rect scrollViewRect = new Rect(
|
||||
0f,
|
||||
0f,
|
||||
scrollRect.width - 20f,
|
||||
CalculateContentHeight()
|
||||
);
|
||||
// Разделяем на две колонки: левая - информация, правая - изображение
|
||||
float leftColumnWidth = inRect.width * 0.55f;
|
||||
float rightColumnWidth = inRect.width * 0.42f;
|
||||
float columnGap = inRect.width * 0.03f;
|
||||
|
||||
Widgets.BeginScrollView(scrollRect, ref scrollPosition, scrollViewRect);
|
||||
// Левая колонка - прокручиваемая информация
|
||||
Rect leftColumnRect = new Rect(0f, curY, leftColumnWidth, inRect.height - curY);
|
||||
DrawLeftColumn(leftColumnRect);
|
||||
|
||||
// Правая колонка - превью и управление
|
||||
Rect rightColumnRect = new Rect(
|
||||
leftColumnWidth + columnGap,
|
||||
curY,
|
||||
rightColumnWidth,
|
||||
inRect.height - curY
|
||||
);
|
||||
DrawRightColumn(rightColumnRect);
|
||||
}
|
||||
|
||||
private void DrawLeftColumn(Rect rect)
|
||||
{
|
||||
Rect scrollViewRect = new Rect(0f, 0f, rect.width - 20f, CalculateContentHeight());
|
||||
Widgets.BeginScrollView(rect, ref scrollPosition, scrollViewRect);
|
||||
|
||||
float contentY = 0f;
|
||||
|
||||
@@ -398,7 +227,9 @@ namespace AIImages
|
||||
contentY += 35f;
|
||||
|
||||
Text.Font = GameFont.Small;
|
||||
string appearanceText = GetAppearanceDescription();
|
||||
string appearanceText = AIImagesMod.PawnDescriptionService.GetAppearanceDescription(
|
||||
pawn
|
||||
);
|
||||
float appearanceHeight = Text.CalcHeight(appearanceText, scrollViewRect.width - 30f);
|
||||
Widgets.Label(
|
||||
new Rect(20f, contentY, scrollViewRect.width - 30f, appearanceHeight),
|
||||
@@ -419,60 +250,133 @@ namespace AIImages
|
||||
contentY += 35f;
|
||||
|
||||
Text.Font = GameFont.Small;
|
||||
string apparelText = GetApparelDescription();
|
||||
string apparelText = AIImagesMod.PawnDescriptionService.GetApparelDescription(pawn);
|
||||
float apparelHeight = Text.CalcHeight(apparelText, scrollViewRect.width - 30f);
|
||||
Widgets.Label(
|
||||
new Rect(20f, contentY, scrollViewRect.width - 30f, apparelHeight),
|
||||
apparelText
|
||||
);
|
||||
contentY += apparelHeight + 20f;
|
||||
|
||||
// Разделитель
|
||||
Widgets.DrawLineHorizontal(10f, contentY, scrollViewRect.width - 20f);
|
||||
contentY += 15f;
|
||||
Widgets.EndScrollView();
|
||||
}
|
||||
|
||||
// Секция "Stable Diffusion Промпт"
|
||||
private void DrawRightColumn(Rect rect)
|
||||
{
|
||||
float curY = 0f;
|
||||
|
||||
// Превью изображения
|
||||
if (generatedImage != null)
|
||||
{
|
||||
float imageSize = Mathf.Min(rect.width, 400f);
|
||||
Rect imageRect = new Rect(
|
||||
(rect.width - imageSize) / 2f,
|
||||
curY,
|
||||
imageSize,
|
||||
imageSize
|
||||
);
|
||||
GUI.DrawTexture(imageRect, generatedImage);
|
||||
curY += imageSize + 10f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Placeholder для изображения
|
||||
float placeholderSize = Mathf.Min(rect.width, 300f);
|
||||
Rect placeholderRect = new Rect(
|
||||
(rect.width - placeholderSize) / 2f,
|
||||
curY,
|
||||
placeholderSize,
|
||||
placeholderSize
|
||||
);
|
||||
Widgets.DrawBoxSolid(placeholderRect, new Color(0.2f, 0.2f, 0.2f));
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
Widgets.Label(placeholderRect, "AIImages.Generation.NoImage".Translate());
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
curY += placeholderSize + 10f;
|
||||
}
|
||||
|
||||
// Статус генерации
|
||||
if (!string.IsNullOrEmpty(generationStatus))
|
||||
{
|
||||
Text.Font = GameFont.Small;
|
||||
float statusHeight = Text.CalcHeight(generationStatus, rect.width);
|
||||
Widgets.Label(new Rect(0f, curY, rect.width, statusHeight), generationStatus);
|
||||
curY += statusHeight + 10f;
|
||||
}
|
||||
|
||||
// Кнопка генерации
|
||||
Text.Font = GameFont.Small;
|
||||
if (
|
||||
Widgets.ButtonText(
|
||||
new Rect(0f, curY, rect.width, 35f),
|
||||
isGenerating
|
||||
? "AIImages.Generation.Generating".Translate()
|
||||
: "AIImages.Generation.Generate".Translate()
|
||||
) && !isGenerating
|
||||
)
|
||||
{
|
||||
_ = GenerateImage();
|
||||
}
|
||||
curY += 40f;
|
||||
|
||||
// Промпт секция
|
||||
Text.Font = GameFont.Medium;
|
||||
Widgets.Label(
|
||||
new Rect(10f, contentY, scrollViewRect.width - 20f, 30f),
|
||||
new Rect(0f, curY, rect.width, 30f),
|
||||
"AIImages.Prompt.SectionTitle".Translate()
|
||||
);
|
||||
contentY += 35f;
|
||||
curY += 35f;
|
||||
|
||||
// Промпт текст
|
||||
Text.Font = GameFont.Small;
|
||||
string promptText = GenerateStableDiffusionPrompt();
|
||||
float promptHeight = Text.CalcHeight(promptText, scrollViewRect.width - 30f);
|
||||
Widgets.Label(
|
||||
new Rect(20f, contentY, scrollViewRect.width - 30f, promptHeight),
|
||||
promptText
|
||||
// Получаем промпт
|
||||
Text.Font = GameFont.Tiny;
|
||||
string promptText = AIImagesMod.PromptGeneratorService.GeneratePositivePrompt(
|
||||
appearanceData,
|
||||
generationSettings
|
||||
);
|
||||
contentY += promptHeight + 10f;
|
||||
|
||||
// Кнопка копирования
|
||||
Rect copyButtonRect = new Rect(20f, contentY, 150f, 30f);
|
||||
float promptHeight = Mathf.Min(Text.CalcHeight(promptText, rect.width), 150f);
|
||||
Rect promptRect = new Rect(0f, curY, rect.width, promptHeight);
|
||||
|
||||
if (Widgets.ButtonText(copyButtonRect, "AIImages.Prompt.CopyButton".Translate()))
|
||||
// Рисуем промпт в скроллируемой области если он длинный
|
||||
Widgets.DrawBoxSolid(promptRect, new Color(0.1f, 0.1f, 0.1f, 0.5f));
|
||||
Widgets.Label(promptRect.ContractedBy(5f), promptText);
|
||||
curY += promptHeight + 10f;
|
||||
|
||||
// Кнопка копирования промпта
|
||||
if (
|
||||
Widgets.ButtonText(
|
||||
new Rect(0f, curY, rect.width / 2f - 5f, 30f),
|
||||
"AIImages.Prompt.CopyButton".Translate()
|
||||
)
|
||||
)
|
||||
{
|
||||
GUIUtility.systemCopyBuffer = promptText;
|
||||
copiedMessageTime = 2f; // Показываем сообщение на 2 секунды
|
||||
copiedMessageTime = 2f;
|
||||
}
|
||||
|
||||
// Кнопка обновления данных
|
||||
if (
|
||||
Widgets.ButtonText(
|
||||
new Rect(rect.width / 2f + 5f, curY, rect.width / 2f - 5f, 30f),
|
||||
"AIImages.Window.Refresh".Translate()
|
||||
)
|
||||
)
|
||||
{
|
||||
RefreshPawnData();
|
||||
}
|
||||
|
||||
// Сообщение о копировании
|
||||
if (copiedMessageTime > 0f)
|
||||
{
|
||||
Rect copiedRect = new Rect(copyButtonRect.xMax + 10f, contentY, 100f, 30f);
|
||||
GUI.color = new Color(0f, 1f, 0f, copiedMessageTime / 2f); // Затухающий зеленый
|
||||
Widgets.Label(copiedRect, "AIImages.Prompt.Copied".Translate());
|
||||
curY += 35f;
|
||||
GUI.color = new Color(0f, 1f, 0f, copiedMessageTime / 2f);
|
||||
Widgets.Label(
|
||||
new Rect(0f, curY, rect.width, 25f),
|
||||
"AIImages.Prompt.Copied".Translate()
|
||||
);
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
|
||||
Widgets.EndScrollView();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Вычисляет высоту всего контента для прокрутки
|
||||
/// </summary>
|
||||
private float CalculateContentHeight()
|
||||
{
|
||||
float height = 0f;
|
||||
@@ -481,8 +385,10 @@ namespace AIImages
|
||||
height += 35f;
|
||||
|
||||
// Текст внешности
|
||||
string appearanceText = GetAppearanceDescription();
|
||||
height += Text.CalcHeight(appearanceText, 640f) + 20f;
|
||||
string appearanceText = AIImagesMod.PawnDescriptionService.GetAppearanceDescription(
|
||||
pawn
|
||||
);
|
||||
height += Text.CalcHeight(appearanceText, 400f) + 20f;
|
||||
|
||||
// Разделитель
|
||||
height += 15f;
|
||||
@@ -491,21 +397,11 @@ namespace AIImages
|
||||
height += 35f;
|
||||
|
||||
// Текст одежды
|
||||
string apparelText = GetApparelDescription();
|
||||
height += Text.CalcHeight(apparelText, 640f) + 20f;
|
||||
string apparelText = AIImagesMod.PawnDescriptionService.GetApparelDescription(pawn);
|
||||
height += Text.CalcHeight(apparelText, 400f) + 20f;
|
||||
|
||||
// Разделитель
|
||||
height += 15f;
|
||||
|
||||
// Заголовок "Промпт"
|
||||
height += 35f;
|
||||
|
||||
// Текст промпта
|
||||
string promptText = GenerateStableDiffusionPrompt();
|
||||
height += Text.CalcHeight(promptText, 640f) + 10f;
|
||||
|
||||
// Кнопка и отступ
|
||||
height += 30f + 20f;
|
||||
// Дополнительный отступ
|
||||
height += 50f;
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user