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