diff --git a/Assemblies/AIImages.dll b/Assemblies/AIImages.dll index e13e64c..e20ccc7 100644 Binary files a/Assemblies/AIImages.dll and b/Assemblies/AIImages.dll differ diff --git a/Source/AIImages/Services/AdvancedPromptGenerator.cs b/Source/AIImages/Services/AdvancedPromptGenerator.cs index e2a717a..8d73dff 100644 --- a/Source/AIImages/Services/AdvancedPromptGenerator.cs +++ b/Source/AIImages/Services/AdvancedPromptGenerator.cs @@ -59,14 +59,19 @@ namespace AIImages.Services prompt.Append(", "); } - // 3. Тип кадра - автоматически добавляем "portrait" для генерации персонажей - prompt.Append("portrait, head and shoulders of "); + // 3. Тип кадра + prompt.Append("portrait, "); - // 4. Базовое описание (возраст и пол) - prompt.Append(GetAgeAndGenderDescription(appearanceData)); + // 4. Пол персонажа (в формате anime/SD теги) + string genderTag = appearanceData.Gender == Gender.Female ? "1girl" : "1boy"; + prompt.Append(genderTag); prompt.Append(", "); - // 5. Тип тела + // 5. Точный возраст + prompt.Append($"{appearanceData.Age} y.o."); + prompt.Append(", "); + + // 6. Тип тела string bodyType = GetBodyTypeDescription(appearanceData.BodyType); if (!string.IsNullOrEmpty(bodyType)) { @@ -74,14 +79,14 @@ namespace AIImages.Services prompt.Append(", "); } - // 6. Цвет кожи + // 7. Цвет кожи string skinTone = ColorDescriptionService.GetSkinToneDescription( appearanceData.SkinColor ); prompt.Append(skinTone); prompt.Append(", "); - // 7. Волосы + // 8. Волосы string hairDescription = GetHairDescription(appearanceData); if (!string.IsNullOrEmpty(hairDescription)) { @@ -89,7 +94,7 @@ namespace AIImages.Services prompt.Append(", "); } - // 8. Настроение и выражение на основе черт характера + // 9. Настроение и выражение на основе черт характера string moodDescription = GetMoodFromTraits(appearanceData.Traits); if (!string.IsNullOrEmpty(moodDescription)) { @@ -97,7 +102,7 @@ namespace AIImages.Services prompt.Append(", "); } - // 9. Одежда + // 10. Одежда string apparelDescription = GetApparelDescription(appearanceData.Apparel); if (!string.IsNullOrEmpty(apparelDescription)) { @@ -105,7 +110,7 @@ namespace AIImages.Services prompt.Append(", "); } - // 10. Качественные теги + // 11. Качественные теги prompt.Append(GetQualityTags(settings.ArtStyleDefName)); return prompt.ToString().Trim().TrimEnd(','); @@ -179,27 +184,6 @@ namespace AIImages.Services return description.ToString(); } - private string GetAgeAndGenderDescription(PawnAppearanceData data) - { - string ageGroup = data.Age switch - { - < 18 => "young", - < 25 => "young adult", - < 35 => "adult", - < 50 => "middle-aged", - < 65 => "mature", - _ => "elderly", - }; - - string genderLabel = data.Gender switch - { - Gender.Male => "man", - Gender.Female => "woman", - _ => "person", - }; - return $"{ageGroup} {genderLabel}"; - } - private string GetBodyTypeDescription(string bodyType) { if (string.IsNullOrEmpty(bodyType)) diff --git a/Source/AIImages/Window_AIImage.cs b/Source/AIImages/Window_AIImage.cs index b859683..637fab6 100644 --- a/Source/AIImages/Window_AIImage.cs +++ b/Source/AIImages/Window_AIImage.cs @@ -69,16 +69,14 @@ namespace AIImages public override Vector2 InitialSize => new Vector2(900f, 800f); - private Vector2 scrollPosition = Vector2.zero; - private Vector2 rightColumnScrollPosition = Vector2.zero; + private Vector2 mainScrollPosition = Vector2.zero; private Vector2 promptScrollPosition = Vector2.zero; private Vector2 negativePromptScrollPosition = Vector2.zero; private float copiedMessageTime = 0f; - // Состояние сворачиваемых секций + // Состояние сворачиваемых секций промптов private bool showPositivePrompt = false; private bool showNegativePrompt = false; - private bool showCharacterInfo = true; /// /// Обновляет данные персонажа @@ -342,12 +340,18 @@ namespace AIImages public override void DoWindowContents(Rect inRect) { + // Рассчитываем общую высоту контента + float totalContentHeight = CalculateTotalContentHeight(); + Rect viewRect = new Rect(0f, 0f, inRect.width - 20f, totalContentHeight); + + Widgets.BeginScrollView(inRect, ref mainScrollPosition, viewRect); + float curY = 0f; // Заголовок Text.Font = GameFont.Medium; Widgets.Label( - new Rect(0f, curY, inRect.width, 40f), + new Rect(0f, curY, viewRect.width, 40f), "AIImages.Window.Title".Translate() ); curY += 45f; @@ -355,22 +359,25 @@ namespace AIImages // Имя пешки Text.Font = GameFont.Small; Widgets.Label( - new Rect(0f, curY, inRect.width, 30f), + new Rect(0f, curY, viewRect.width, 30f), "AIImages.Window.PawnLabel".Translate(pawn.NameShortColored.Resolve()) ); curY += 40f; // Разделитель - Widgets.DrawLineHorizontal(0f, curY, inRect.width); + Widgets.DrawLineHorizontal(0f, curY, viewRect.width); curY += 10f; // Разделяем на две колонки: левая - информация, правая - изображение - float leftColumnWidth = inRect.width * 0.55f; - float rightColumnWidth = inRect.width * 0.42f; - float columnGap = inRect.width * 0.03f; + float leftColumnWidth = viewRect.width * 0.35f; + float rightColumnWidth = viewRect.width * 0.62f; + float columnGap = viewRect.width * 0.03f; - // Левая колонка - прокручиваемая информация - Rect leftColumnRect = new Rect(0f, curY, leftColumnWidth, inRect.height - curY); + // Определяем высоту колонок (берем большую из двух) + float columnHeight = Mathf.Max(CalculateContentHeight(), CalculateRightColumnHeight()); + + // Левая колонка - информация + Rect leftColumnRect = new Rect(0f, curY, leftColumnWidth, columnHeight); DrawLeftColumn(leftColumnRect); // Правая колонка - превью и управление @@ -378,24 +385,23 @@ namespace AIImages leftColumnWidth + columnGap, curY, rightColumnWidth, - inRect.height - curY + columnHeight ); DrawRightColumn(rightColumnRect); + + Widgets.EndScrollView(); } private void DrawLeftColumn(Rect rect) { - Rect scrollViewRect = new Rect(0f, 0f, rect.width - 20f, CalculateContentHeight()); - Widgets.BeginScrollView(rect, ref scrollPosition, scrollViewRect); - - float contentY = 0f; + float contentY = rect.y; // Портрет персонажа (если есть) if (generatedImage != null) { - float portraitSize = Mathf.Min(scrollViewRect.width - 20f, 200f); + float portraitSize = Mathf.Min(rect.width, 250f); Rect portraitRect = new Rect( - (scrollViewRect.width - portraitSize) / 2f, + rect.x + (rect.width - portraitSize) / 2f, contentY, portraitSize, portraitSize @@ -404,29 +410,34 @@ namespace AIImages // Рамка вокруг портрета Widgets.DrawBox(portraitRect); GUI.DrawTexture(portraitRect.ContractedBy(2f), generatedImage); - contentY += portraitSize + 20f; + contentY += portraitSize + 15f; } - // Сворачиваемая секция "Информация о персонаже" - DrawCollapsibleSection( - scrollViewRect, - contentY, - "AIImages.CharacterInfo.Title".Translate(), - ref showCharacterInfo, - () => DrawCharacterInfoContent(scrollViewRect) + // Заголовок секции + Text.Font = GameFont.Medium; + Widgets.Label( + new Rect(rect.x, contentY, rect.width, 30f), + "AIImages.CharacterInfo.Title".Translate() ); + contentY += 35f; - Widgets.EndScrollView(); + // Разделитель + Widgets.DrawLineHorizontal(rect.x, contentY, rect.width); + contentY += 10f; + + // Информация о персонаже + DrawCharacterInfoContent(rect, contentY); } /// /// Отрисовывает информацию о персонаже в компактном виде /// - private float DrawCharacterInfoContent(Rect parentRect) + private void DrawCharacterInfoContent(Rect parentRect, float startY) { - float contentHeight = 0f; - float lineHeight = 24f; - float indent = 10f; + float contentY = startY; + float lineHeight = 22f; + float labelWidth = parentRect.width * 0.45f; + float valueWidth = parentRect.width * 0.50f; Text.Font = GameFont.Tiny; @@ -449,47 +460,48 @@ namespace AIImages foreach (var (label, value) in info) { + // Подсветка строк через одну + var infoArray = info.ToArray(); + int index = Array.IndexOf(infoArray, (label, value)); + if ((index % 2) == 0) + { + Widgets.DrawBoxSolid( + new Rect(parentRect.x, contentY, parentRect.width, lineHeight), + new Color(0.15f, 0.15f, 0.15f, 0.3f) + ); + } + Widgets.Label( - new Rect(indent, contentHeight, parentRect.width * 0.4f, lineHeight), + new Rect(parentRect.x + 5f, contentY, labelWidth, lineHeight), label + ":" ); Widgets.Label( - new Rect( - parentRect.width * 0.42f, - contentHeight, - parentRect.width * 0.55f, - lineHeight - ), + new Rect(parentRect.x + labelWidth + 10f, contentY, valueWidth, lineHeight), value ); - contentHeight += lineHeight; + contentY += lineHeight; } // Черты характера if (pawn.story?.traits?.allTraits != null && pawn.story.traits.allTraits.Any()) { - contentHeight += 10f; + contentY += 15f; Text.Font = GameFont.Small; Widgets.Label( - new Rect(indent, contentHeight, parentRect.width - indent * 2, lineHeight), + new Rect(parentRect.x + 5f, contentY, parentRect.width - 10f, lineHeight), "AIImages.Info.Traits".Translate() + ":" ); - contentHeight += lineHeight; + contentY += lineHeight + 2f; 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 - ), + new Rect(parentRect.x + 15f, contentY, parentRect.width - 20f, lineHeight), "• " + traitLabel ); - contentHeight += lineHeight; + contentY += lineHeight; } } @@ -497,13 +509,13 @@ namespace AIImages var apparel = pawn.apparel?.WornApparel; if (apparel != null && apparel.Any()) { - contentHeight += 10f; + contentY += 15f; Text.Font = GameFont.Small; Widgets.Label( - new Rect(indent, contentHeight, parentRect.width - indent * 2, lineHeight), + new Rect(parentRect.x + 5f, contentY, parentRect.width - 10f, lineHeight), "AIImages.Info.Apparel".Translate() + ":" ); - contentHeight += lineHeight; + contentY += lineHeight + 2f; Text.Font = GameFont.Tiny; foreach (var item in apparel) @@ -512,44 +524,33 @@ namespace AIImages item.DrawColor ); string apparelLabel = $"• {colorDesc} {item.def.label}"; - float apparelHeight = Text.CalcHeight( - apparelLabel, - parentRect.width - indent * 3 - ); + float apparelHeight = Text.CalcHeight(apparelLabel, parentRect.width - 25f); Widgets.Label( new Rect( - indent * 2, - contentHeight, - parentRect.width - indent * 3, + parentRect.x + 15f, + contentY, + parentRect.width - 25f, apparelHeight ), apparelLabel ); - contentHeight += apparelHeight; + contentY += apparelHeight; } } - - return contentHeight + 10f; } private void DrawRightColumn(Rect rect) { - // Рассчитываем высоту контента для скролла - float contentHeight = CalculateRightColumnHeight(); - Rect scrollViewRect = new Rect(0f, 0f, rect.width - 20f, contentHeight); + float curY = rect.y; - Widgets.BeginScrollView(rect, ref rightColumnScrollPosition, scrollViewRect); - - float curY = 0f; - - curY = DrawImagePreview(scrollViewRect, curY); - curY = DrawGenerationStatus(scrollViewRect, curY); - curY = DrawProgressBar(scrollViewRect, curY); - curY = DrawGenerationButton(scrollViewRect, curY); + curY = DrawImagePreview(rect, curY); + curY = DrawGenerationStatus(rect, curY); + curY = DrawProgressBar(rect, curY); + curY = DrawGenerationButton(rect, curY); // Сворачиваемая секция с позитивным промптом curY = DrawPromptSection( - scrollViewRect, + rect, curY, "AIImages.Prompt.PositiveTitle".Translate(), ref showPositivePrompt, @@ -566,7 +567,7 @@ namespace AIImages // Сворачиваемая секция с негативным промптом curY = DrawPromptSection( - scrollViewRect, + rect, curY, "AIImages.Prompt.NegativeTitle".Translate(), ref showNegativePrompt, @@ -580,7 +581,7 @@ namespace AIImages // Кнопка обновления данных if ( Widgets.ButtonText( - new Rect(0f, curY, scrollViewRect.width, 30f), + new Rect(rect.x, curY, rect.width, 30f), "AIImages.Window.Refresh".Translate() ) ) @@ -594,13 +595,11 @@ namespace AIImages curY += 35f; GUI.color = new Color(0f, 1f, 0f, copiedMessageTime / 2f); Widgets.Label( - new Rect(0f, curY, scrollViewRect.width, 25f), + new Rect(rect.x, curY, rect.width, 25f), "AIImages.Prompt.Copied".Translate() ); GUI.color = Color.white; } - - Widgets.EndScrollView(); } private float CalculateContentHeight() @@ -610,26 +609,38 @@ namespace AIImages // Портрет персонажа (если есть) if (generatedImage != null) { - float portraitSize = Mathf.Min(400f, 200f); + float portraitSize = 250f; // Максимальный размер портрета height += portraitSize + 15f; } - // Заголовок "Внешность" + // Заголовок "Информация о персонаже" height += 35f; - // Текст внешности - string appearanceText = pawnDescriptionService.GetAppearanceDescription(pawn); - height += Text.CalcHeight(appearanceText, 400f) + 20f; - // Разделитель - height += 15f; + height += 10f; - // Заголовок "Одежда" - height += 35f; + // Базовая информация (6 строк по 22px) + height += 6 * 22f; - // Текст одежды - string apparelText = pawnDescriptionService.GetApparelDescription(pawn); - height += Text.CalcHeight(apparelText, 400f) + 20f; + // Черты характера (если есть) + if (pawn.story?.traits?.allTraits != null && pawn.story.traits.allTraits.Any()) + { + height += 15f; // Отступ + height += 22f; // Заголовок "Черты характера" + height += 2f; // Отступ + height += pawn.story.traits.allTraits.Count * 22f; // Каждая черта + } + + // Одежда (если есть) + var apparel = pawn.apparel?.WornApparel; + if (apparel != null && apparel.Any()) + { + height += 15f; // Отступ + height += 22f; // Заголовок "Одежда" + height += 2f; // Отступ + // Примерно по 22-30px на предмет одежды + height += apparel.Count * 26f; + } // Дополнительный отступ height += 50f; @@ -644,11 +655,11 @@ namespace AIImages // Превью изображения if (generatedImage != null) { - height += 200f + 10f; + height += 410f; // 400f изображение + 10f отступ } else if (!isGenerating) { - height += 100f + 10f; + height += 310f; // 300f placeholder + 10f отступ } // Статус генерации @@ -667,71 +678,55 @@ namespace AIImages height += 40f; // Позитивный промпт - height += 35f; // Заголовок - height += 100f + 10f; // Бокс + height += 36f; // Заголовок (32f + 4f отступ) + if (showPositivePrompt) + { + height += 140f; // Бокс развернут (120f + 10f отступ + запас) + } + height += 5f; // Отступ между промптами // Негативный промпт - height += 35f; // Заголовок - height += 100f + 10f; // Бокс - - // Кнопки копирования - height += 35f; + height += 36f; // Заголовок (32f + 4f отступ) + if (showNegativePrompt) + { + height += 140f; // Бокс развернут (120f + 10f отступ + запас) + } + height += 10f; // Отступ после промптов // Кнопка обновления - height += 35f; + height += 40f; // Увеличено с 35f до 40f // Сообщение о копировании if (copiedMessageTime > 0f) { - height += 30f; + height += 40f; } - return height + 50f; // Дополнительный отступ + return height + 100f; // Увеличен дополнительный отступ снизу } - /// - /// Отрисовывает сворачиваемую секцию с заголовком и содержимым - /// - private void DrawCollapsibleSection( - Rect parentRect, - float startY, - string title, - ref bool expanded, - System.Func drawContentFunc - ) + private float CalculateTotalContentHeight() { - float curY = startY; - float headerHeight = 32f; + float height = 0f; - // Рисуем заголовок с фоном - 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); + // Заголовок и имя пешки + height += 45f + 40f; - // Иконка раскрытия (треугольник) - string icon = expanded ? "▼" : "►"; - Text.Font = GameFont.Small; - Widgets.Label(new Rect(8f, curY + 6f, 20f, headerHeight), icon); + // Разделитель + height += 10f; - // Заголовок - Text.Font = GameFont.Small; - Widgets.Label(new Rect(30f, curY + 6f, parentRect.width - 40f, headerHeight), title); + // Высота колонок (берем большую) + float columnHeight = Mathf.Max(CalculateContentHeight(), CalculateRightColumnHeight()); + height += columnHeight; - // Клик для раскрытия/сворачивания - if (Widgets.ButtonInvisible(headerRect)) - { - expanded = !expanded; - } + // Дополнительный отступ снизу + height += 30f; - // Рисуем содержимое если развернуто - if (expanded) - { - drawContentFunc(); - } + return height; } /// - /// Отрисовывает секцию с промптом + /// /// Отрисовывает секцию с промптом /// private float DrawPromptSection( Rect parentRect, @@ -750,20 +745,28 @@ namespace AIImages string prompt = getPromptFunc(); // Рисуем заголовок с фоном - Rect headerRect = new Rect(0f, curY, parentRect.width, headerHeight); + Rect headerRect = new Rect(parentRect.x, 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(parentRect.x + 8f, curY + 6f, 20f, headerHeight), icon); // Заголовок - Widgets.Label(new Rect(30f, curY + 6f, parentRect.width - 80f, headerHeight), title); + Widgets.Label( + new Rect(parentRect.x + 30f, curY + 6f, parentRect.width - 110f, headerHeight), + title + ); - // Кнопка копирования в заголовке - Rect copyButtonRect = new Rect(parentRect.width - 70f, curY + 4f, 65f, 24f); + // Кнопка копирования в заголовке (увеличена ширина) + Rect copyButtonRect = new Rect( + parentRect.x + parentRect.width - 100f, + curY + 4f, + 95f, + 24f + ); if (Widgets.ButtonText(copyButtonRect, "📋 " + "AIImages.Copy".Translate())) { GUIUtility.systemCopyBuffer = prompt; @@ -771,7 +774,12 @@ namespace AIImages } // Клик на остальной области для раскрытия/сворачивания - Rect clickableHeaderRect = new Rect(0f, curY, parentRect.width - 75f, headerHeight); + Rect clickableHeaderRect = new Rect( + parentRect.x, + curY, + parentRect.width - 105f, + headerHeight + ); if (Widgets.ButtonInvisible(clickableHeaderRect)) { expanded = !expanded; @@ -786,7 +794,12 @@ namespace AIImages Text.Font = GameFont.Tiny; float actualPromptHeight = Text.CalcHeight(prompt, parentRect.width - 20f); - Rect promptOuterRect = new Rect(0f, curY, parentRect.width, promptBoxHeight); + Rect promptOuterRect = new Rect( + parentRect.x, + curY, + parentRect.width, + promptBoxHeight + ); Rect promptViewRect = new Rect(0f, 0f, parentRect.width - 20f, actualPromptHeight); // Рисуем фон @@ -827,7 +840,7 @@ namespace AIImages float imageSize = Mathf.Min(rect.width, 400f); Rect imageRect = new Rect( rect.x + (rect.width - imageSize) / 2f, - rect.y + curY, + curY, imageSize, imageSize ); @@ -838,7 +851,7 @@ namespace AIImages float placeholderSize = Mathf.Min(rect.width, 300f); Rect placeholderRect = new Rect( rect.x + (rect.width - placeholderSize) / 2f, - rect.y + curY, + curY, placeholderSize, placeholderSize ); @@ -856,10 +869,7 @@ namespace AIImages Text.Font = GameFont.Small; float statusHeight = Text.CalcHeight(generationStatus, rect.width); - Widgets.Label( - new Rect(rect.x, rect.y + curY, rect.width, statusHeight), - generationStatus - ); + Widgets.Label(new Rect(rect.x, curY, rect.width, statusHeight), generationStatus); return curY + statusHeight + 10f; } @@ -868,7 +878,7 @@ namespace AIImages if (!isGenerating || generationProgress <= 0.0) return curY; - Rect progressBarRect = new Rect(rect.x, rect.y + curY, rect.width, 24f); + Rect progressBarRect = new Rect(rect.x, curY, rect.width, 24f); string progressText; if (totalSteps > 0) @@ -900,7 +910,7 @@ namespace AIImages ? "AIImages.Generation.Cancel".Translate() : "AIImages.Generation.Generate".Translate(); - if (Widgets.ButtonText(new Rect(rect.x, rect.y + curY, rect.width, 35f), buttonLabel)) + if (Widgets.ButtonText(new Rect(rect.x, curY, rect.width, 35f), buttonLabel)) { if (isGenerating) CancelGeneration();