diff --git a/Assemblies/AIImages.dll b/Assemblies/AIImages.dll index daa0bbb..569545a 100644 Binary files a/Assemblies/AIImages.dll and b/Assemblies/AIImages.dll differ diff --git a/Defs/ArtStyleDefs.xml b/Defs/ArtStyleDefs.xml new file mode 100644 index 0000000..1c3e3e4 --- /dev/null +++ b/Defs/ArtStyleDefs.xml @@ -0,0 +1,111 @@ + + + + + ArtStyle_None + + No predefined style - use only your custom prompts + + + + false + false + 0 + + + + ArtStyle_Realistic + + Photorealistic style with high detail + photorealistic, hyperrealistic, realistic photo, photography + cartoon, anime, painting, drawing, illustration + professional photography, 8k uhd, dslr, high quality, sharp focus + true + true + 10 + + + + ArtStyle_SemiRealistic + + Detailed illustration with realistic elements + semi-realistic, detailed illustration, realistic art + cartoon, anime, painting, drawing, illustration + professional photography, 8k uhd, dslr, high quality, sharp focus + true + true + 20 + + + + ArtStyle_Anime + + Japanese anime/manga style + anime style, manga style, anime character + realistic, photo, photography, 3d + anime masterpiece, high resolution, vibrant colors + true + true + 30 + + + + ArtStyle_ConceptArt + + Professional digital concept art + concept art, digital art, artstation, professional concept design + + trending on artstation, professional digital art + true + true + 40 + + + + ArtStyle_DigitalPainting + + Digital painting with brush strokes + digital painting, painterly, brush strokes, artistic + + + true + true + 50 + + + + ArtStyle_OilPainting + + Traditional oil painting style + oil painting, traditional painting, canvas, fine art + + + true + true + 60 + + + + ArtStyle_Sketch + + Pencil sketch or line art + pencil sketch, hand drawn, sketch art, line art + + + true + true + 70 + + + + ArtStyle_CellShaded + + Flat colors with toon shading + cell shaded, flat colors, toon shading, stylized + + + true + true + 80 + + diff --git a/Defs/ImageSizePresetDefs.xml b/Defs/ImageSizePresetDefs.xml new file mode 100644 index 0000000..1db4801 --- /dev/null +++ b/Defs/ImageSizePresetDefs.xml @@ -0,0 +1,78 @@ + + + + + Size_512x512 + + 512 + 512 + Square + 10 + + + Size_768x768 + + 768 + 768 + Square + 20 + + + Size_1024x1024 + + 1024 + 1024 + Square + 30 + + + + Size_512x768 + + 512 + 768 + Portrait + 40 + + + Size_768x1024 + + 768 + 1024 + Portrait + 50 + + + Size_896x1152 + + 896 + 1152 + Portrait + 60 + + + + Size_768x512 + + 768 + 512 + Landscape + 70 + + + Size_1024x768 + + 1024 + 768 + Landscape + 80 + + + Size_1152x896 + + 1152 + 896 + Landscape + 90 + + diff --git a/Defs/README.md b/Defs/README.md new file mode 100644 index 0000000..8567385 --- /dev/null +++ b/Defs/README.md @@ -0,0 +1,124 @@ +# AI Images - Defs Documentation + +This folder contains XML definition files that allow you to easily customize art styles and image size presets without recompiling the mod. + +## Art Style Definitions (ArtStyleDefs.xml) + +Art styles define how images should be generated, including prompts, quality tags, and negative prompts. + +### Structure + +```xml + + ArtStyle_MyStyle + + Description of the style + style keywords here + things to avoid + additional quality tags + true + true + 100 + +``` + +### Fields + +- **defName**: Unique identifier (must start with `ArtStyle_`) +- **label**: Display name shown in the UI +- **description**: Tooltip text explaining the style +- **positivePrompt**: Keywords added to the positive prompt (e.g., "photorealistic, 8k uhd") +- **negativePrompt**: Keywords added to the negative prompt (e.g., "cartoon, anime") +- **qualityTags**: Style-specific quality tags +- **addBaseQualityTags**: If true, adds "highly detailed, professional, masterpiece, best quality" +- **addBaseNegativePrompts**: If true, adds base negative prompts like "ugly, deformed, low quality" +- **sortOrder**: Determines order in the UI (lower numbers appear first) + +### Example: Custom Watercolor Style + +```xml + + ArtStyle_Watercolor + + Soft watercolor painting style + watercolor painting, soft colors, flowing paint, artistic + photograph, digital art, sharp edges + traditional art, paper texture + true + true + 65 + +``` + +## Image Size Presets (ImageSizePresetDefs.xml) + +Image size presets provide quick buttons for common image dimensions. + +### Structure + +```xml + + Size_1024x1024 + + 1024 + 1024 + Square + 30 + +``` + +### Fields + +- **defName**: Unique identifier (should start with `Size_`) +- **label**: Display text on the button +- **width**: Image width in pixels +- **height**: Image height in pixels +- **category**: Grouping category (Square, Portrait, Landscape, or custom) +- **sortOrder**: Determines button order (lower numbers appear first) + +### Example: Ultra-wide Size + +```xml + + Size_2048x1024 + + 2048 + 1024 + Ultrawide + 95 + +``` + +## Adding Custom Definitions + +1. **Create a new XML file** in the `Defs` folder +2. **Start with the XML header**: + ```xml + + + + + ``` +3. **Add your definitions** using the structures above +4. **Restart RimWorld** to load the new definitions + +## Tips + +- Keep `defName` unique to avoid conflicts +- Use descriptive `label` values for the UI +- Adjust `sortOrder` to organize items logically +- Test your prompts with different characters to ensure good results +- For art styles, experiment with different combinations of tags +- Consider using existing styles as templates + +## Compatibility + +These definitions are compatible with other mods. If another mod adds art styles or size presets, they will all appear together in the UI. + +## Troubleshooting + +- **Style doesn't appear**: Check that `defName` is unique and starts with `ArtStyle_` +- **Size preset missing**: Verify the XML syntax and that `defName` starts with `Size_` +- **Prompts not working**: Make sure prompts are in English and follow Stable Diffusion prompt syntax +- **XML errors**: Use an XML validator to check your file for syntax errors + diff --git a/Source/AIImages/Defs/ArtStyleDef.cs b/Source/AIImages/Defs/ArtStyleDef.cs new file mode 100644 index 0000000..5ec1c41 --- /dev/null +++ b/Source/AIImages/Defs/ArtStyleDef.cs @@ -0,0 +1,46 @@ +using System.Diagnostics.CodeAnalysis; +using Verse; + +namespace AIImages +{ + /// + /// Определение художественного стиля для генерации изображений + /// + [SuppressMessage( + "Major Code Smell", + "S1104:Fields should not have public accessibility", + Justification = "Required for RimWorld's Def system XML serialization" + )] + public class ArtStyleDef : Def + { + /// + /// Промпт для позитивного описания стиля + /// + public string positivePrompt = ""; + + /// + /// Промпт для негативного описания (что исключить) + /// + public string negativePrompt = ""; + + /// + /// Теги качества специфичные для этого стиля + /// + public string qualityTags = ""; + + /// + /// Добавлять ли базовые теги качества (highly detailed, professional, masterpiece, best quality) + /// + public bool addBaseQualityTags = true; + + /// + /// Добавлять ли базовые негативные промпты (ugly, deformed, low quality, etc.) + /// + public bool addBaseNegativePrompts = true; + + /// + /// Порядок сортировки в UI + /// + public int sortOrder = 100; + } +} diff --git a/Source/AIImages/Defs/ImageSizePresetDef.cs b/Source/AIImages/Defs/ImageSizePresetDef.cs new file mode 100644 index 0000000..c067c33 --- /dev/null +++ b/Source/AIImages/Defs/ImageSizePresetDef.cs @@ -0,0 +1,36 @@ +using System.Diagnostics.CodeAnalysis; +using Verse; + +namespace AIImages +{ + /// + /// Предустановка размера изображения + /// + [SuppressMessage( + "Major Code Smell", + "S1104:Fields should not have public accessibility", + Justification = "Required for RimWorld's Def system XML serialization" + )] + public class ImageSizePresetDef : Def + { + /// + /// Ширина изображения в пикселях + /// + public int width = 512; + + /// + /// Высота изображения в пикселях + /// + public int height = 512; + + /// + /// Категория размера (Square, Portrait, Landscape) + /// + public string category = "Square"; + + /// + /// Порядок сортировки в UI + /// + public int sortOrder = 100; + } +} diff --git a/Source/AIImages/Models/StableDiffusionSettings.cs b/Source/AIImages/Models/StableDiffusionSettings.cs index 90ffef8..f472b29 100644 --- a/Source/AIImages/Models/StableDiffusionSettings.cs +++ b/Source/AIImages/Models/StableDiffusionSettings.cs @@ -15,7 +15,7 @@ namespace AIImages.Models public string Scheduler { get; set; } public int Seed { get; set; } public string Model { get; set; } - public ArtStyle ArtStyle { get; set; } + public string ArtStyleDefName { get; set; } public StableDiffusionSettings() { @@ -25,27 +25,11 @@ namespace AIImages.Models Width = 512; Height = 768; Sampler = "Euler a"; - Scheduler = "Automatic"; + Scheduler = "Automatic"; // С большой буквы для API Seed = -1; // Случайный seed - ArtStyle = ArtStyle.Realistic; + ArtStyleDefName = "ArtStyle_Realistic"; PositivePrompt = ""; NegativePrompt = "ugly, deformed, low quality, blurry, bad anatomy, worst quality"; } } - - /// - /// Художественный стиль изображения - /// - public enum ArtStyle - { - None, // Без стиля - Realistic, - SemiRealistic, - Anime, - ConceptArt, - DigitalPainting, - OilPainting, - Sketch, - CellShaded, - } } diff --git a/Source/AIImages/Services/AdvancedPromptGenerator.cs b/Source/AIImages/Services/AdvancedPromptGenerator.cs index 8e3d90a..e2a717a 100644 --- a/Source/AIImages/Services/AdvancedPromptGenerator.cs +++ b/Source/AIImages/Services/AdvancedPromptGenerator.cs @@ -34,25 +34,6 @@ namespace AIImages.Services { "Pretty", "attractive features, pleasant appearance, charming" }, }; - private static readonly Dictionary ArtStylePrompts = new Dictionary< - ArtStyle, - string - > - { - { ArtStyle.None, "" }, - { ArtStyle.Realistic, "photorealistic, hyperrealistic, realistic photo, photography" }, - { ArtStyle.SemiRealistic, "semi-realistic, detailed illustration, realistic art" }, - { ArtStyle.Anime, "anime style, manga style, anime character" }, - { - ArtStyle.ConceptArt, - "concept art, digital art, artstation, professional concept design" - }, - { ArtStyle.DigitalPainting, "digital painting, painterly, brush strokes, artistic" }, - { ArtStyle.OilPainting, "oil painting, traditional painting, canvas, fine art" }, - { ArtStyle.Sketch, "pencil sketch, hand drawn, sketch art, line art" }, - { ArtStyle.CellShaded, "cell shaded, flat colors, toon shading, stylized" }, - }; - public string GeneratePositivePrompt( PawnAppearanceData appearanceData, StableDiffusionSettings settings @@ -66,17 +47,15 @@ namespace AIImages.Services // 1. Базовый пользовательский промпт (если указан) - идет первым if (!string.IsNullOrEmpty(settings.PositivePrompt)) { - prompt.Append(settings.PositivePrompt); + prompt.Append(settings.PositivePrompt.TrimEnd(',', ' ')); prompt.Append(", "); } // 2. Художественный стиль - if ( - ArtStylePrompts.TryGetValue(settings.ArtStyle, out string stylePrompt) - && !string.IsNullOrEmpty(stylePrompt) - ) + var styleDef = DefDatabase.GetNamedSilentFail(settings.ArtStyleDefName); + if (styleDef != null && !string.IsNullOrEmpty(styleDef.positivePrompt)) { - prompt.Append(stylePrompt); + prompt.Append(styleDef.positivePrompt); prompt.Append(", "); } @@ -127,7 +106,7 @@ namespace AIImages.Services } // 10. Качественные теги - prompt.Append(GetQualityTags(settings.ArtStyle)); + prompt.Append(GetQualityTags(settings.ArtStyleDefName)); return prompt.ToString().Trim().TrimEnd(','); } @@ -139,34 +118,38 @@ namespace AIImages.Services // 1. Пользовательский негативный промпт (если указан) - идет первым if (!string.IsNullOrEmpty(settings.NegativePrompt)) { - negativePrompt.Append(settings.NegativePrompt); - negativePrompt.Append(", "); + negativePrompt.Append(settings.NegativePrompt.TrimEnd(',', ' ')); + } + + // Получаем стиль из Def + var styleDef = DefDatabase.GetNamedSilentFail(settings.ArtStyleDefName); + if (styleDef == null || !styleDef.addBaseNegativePrompts) + { + // Для стилей без базовых негативов - используем только пользовательский промпт + return negativePrompt.ToString().Trim(); } // 2. Базовые негативные промпты + if (negativePrompt.Length > 0) + { + negativePrompt.Append(", "); + } + negativePrompt.Append( "ugly, deformed, low quality, blurry, bad anatomy, worst quality, " ); negativePrompt.Append( - "mutated, disfigured, bad proportions, extra limbs, missing limbs, " + "mutated, disfigured, bad proportions, extra limbs, missing limbs" ); - // 3. Специфичные для стиля негативы - switch (settings.ArtStyle) + // 3. Специфичные для стиля негативы из Def + if (!string.IsNullOrEmpty(styleDef.negativePrompt)) { - case ArtStyle.Realistic: - case ArtStyle.SemiRealistic: - negativePrompt.Append("cartoon, anime, painting, drawing, illustration, "); - break; - case ArtStyle.Anime: - negativePrompt.Append("realistic, photo, photography, 3d, "); - break; - case ArtStyle.None: - // Без дополнительных негативных промптов для стиля None - break; + negativePrompt.Append(", "); + negativePrompt.Append(styleDef.negativePrompt); } - return negativePrompt.ToString().Trim().TrimEnd(','); + return negativePrompt.ToString().Trim(); } public string GetFullPromptDescription( @@ -317,24 +300,33 @@ namespace AIImages.Services return apparelDesc.ToString(); } - private string GetQualityTags(ArtStyle style) + private string GetQualityTags(string styleDefName) { - var baseTags = "highly detailed, professional, masterpiece, best quality"; - - switch (style) + var styleDef = DefDatabase.GetNamedSilentFail(styleDefName); + if (styleDef == null) { - case ArtStyle.None: - return baseTags; - case ArtStyle.Realistic: - case ArtStyle.SemiRealistic: - return $"{baseTags}, professional photography, 8k uhd, dslr, high quality, sharp focus"; - case ArtStyle.Anime: - return $"{baseTags}, anime masterpiece, high resolution, vibrant colors"; - case ArtStyle.ConceptArt: - return $"{baseTags}, trending on artstation, professional digital art"; - default: - return baseTags; + return ""; } + + StringBuilder tags = new StringBuilder(); + + // Базовые теги качества + if (styleDef.addBaseQualityTags) + { + tags.Append("highly detailed, professional, masterpiece, best quality"); + } + + // Специфичные для стиля теги качества + if (!string.IsNullOrEmpty(styleDef.qualityTags)) + { + if (tags.Length > 0) + { + tags.Append(", "); + } + tags.Append(styleDef.qualityTags); + } + + return tags.ToString(); } /// diff --git a/Source/AIImages/Settings/AIImagesModSettings.cs b/Source/AIImages/Settings/AIImagesModSettings.cs index b9600e6..6c66461 100644 --- a/Source/AIImages/Settings/AIImagesModSettings.cs +++ b/Source/AIImages/Settings/AIImagesModSettings.cs @@ -14,7 +14,7 @@ namespace AIImages.Settings public string apiEndpoint = "http://127.0.0.1:7860"; public string selectedModel = ""; public string selectedSampler = "Euler a"; - public string selectedScheduler = "Automatic"; + public string selectedScheduler = "Automatic"; // С большой буквы для API // Кэшированные списки из API (не сохраняются) [Unsaved] @@ -38,8 +38,8 @@ namespace AIImages.Settings public string baseNegativePrompt = "ugly, deformed, low quality, blurry, bad anatomy, worst quality"; - // Художественный стиль - public ArtStyle artStyle = ArtStyle.Realistic; + // Художественный стиль (defName) + public string artStyleDefName = "ArtStyle_Realistic"; // Путь для сохранения public string savePath = "AIImages/Generated"; @@ -70,7 +70,7 @@ namespace AIImages.Settings "ugly, deformed, low quality, blurry, bad anatomy, worst quality" ); - Scribe_Values.Look(ref artStyle, "artStyle", ArtStyle.Realistic); + Scribe_Values.Look(ref artStyleDefName, "artStyleDefName", "ArtStyle_Realistic"); Scribe_Values.Look(ref savePath, "savePath", "AIImages/Generated"); @@ -97,7 +97,7 @@ namespace AIImages.Settings Scheduler = selectedScheduler, Seed = seed, Model = selectedModel, - ArtStyle = artStyle, + ArtStyleDefName = artStyleDefName, PositivePrompt = basePositivePrompt, NegativePrompt = baseNegativePrompt, }; diff --git a/Source/AIImages/UI/AIImagesSettingsUI.cs b/Source/AIImages/UI/AIImagesSettingsUI.cs index 1d4be27..0bf5e18 100644 --- a/Source/AIImages/UI/AIImagesSettingsUI.cs +++ b/Source/AIImages/UI/AIImagesSettingsUI.cs @@ -174,21 +174,37 @@ namespace AIImages AIImagesModSettings settings ) { + // Получаем текущий стиль + var currentStyleDef = DefDatabase.GetNamedSilentFail( + settings.artStyleDefName + ); + string currentStyleLabel = currentStyleDef?.label ?? settings.artStyleDefName; + if ( listingStandard.ButtonTextLabeled( "AIImages.Settings.ArtStyle".Translate(), - settings.artStyle.ToString() + currentStyleLabel ) ) { List styleOptions = new List(); - foreach (ArtStyle style in Enum.GetValues(typeof(ArtStyle))) + + // Получаем все стили из DefDatabase и сортируем по sortOrder + var allStyles = DefDatabase.AllDefs.OrderBy(s => s.sortOrder); + + foreach (var styleDef in allStyles) { - ArtStyle localStyle = style; + string localDefName = styleDef.defName; + string localLabel = styleDef.label; + styleOptions.Add( - new FloatMenuOption(style.ToString(), () => settings.artStyle = localStyle) + new FloatMenuOption( + localLabel, + () => settings.artStyleDefName = localDefName + ) ); } + Find.WindowStack.Add(new FloatMenu(styleOptions)); } } @@ -220,34 +236,47 @@ namespace AIImages AIImagesModSettings settings ) { - listingStandard.Gap(4f); - Rect presetRect1 = listingStandard.GetRect(30f); - DrawPresetButton(presetRect1, 0f, "512x512", 512, 512, settings); - DrawPresetButton(presetRect1, 85f, "512x768", 512, 768, settings); - DrawPresetButton(presetRect1, 170f, "768x768", 768, 768, settings); + // Получаем все предустановки размеров из DefDatabase + var allPresets = DefDatabase + .AllDefs.OrderBy(p => p.sortOrder) + .ToList(); - listingStandard.Gap(4f); - Rect presetRect2 = listingStandard.GetRect(30f); - DrawPresetButton(presetRect2, 0f, "896x1152", 896, 1152, settings, 90f); - DrawPresetButton(presetRect2, 95f, "1024x1024", 1024, 1024, settings, 90f); - } - - private static void DrawPresetButton( - Rect rect, - float xOffset, - string label, - int width, - int height, - AIImagesModSettings settings, - float buttonWidth = 80f - ) - { - if (Widgets.ButtonText(new Rect(rect.x + xOffset, rect.y, buttonWidth, 30f), label)) + if (!allPresets.Any()) { - settings.width = width; - settings.height = height; - widthBuffer = width.ToString(); - heightBuffer = height.ToString(); + return; + } + + listingStandard.Gap(4f); + + // Разбиваем на строки по 3 кнопки + int buttonsPerRow = 3; + float buttonWidth = 80f; + float spacing = 5f; + + for (int i = 0; i < allPresets.Count; i += buttonsPerRow) + { + Rect rowRect = listingStandard.GetRect(30f); + + for (int j = 0; j < buttonsPerRow && (i + j) < allPresets.Count; j++) + { + var preset = allPresets[i + j]; + float xOffset = j * (buttonWidth + spacing); + + if ( + Widgets.ButtonText( + new Rect(rowRect.x + xOffset, rowRect.y, buttonWidth, 30f), + preset.label + ) + ) + { + settings.width = preset.width; + settings.height = preset.height; + widthBuffer = preset.width.ToString(); + heightBuffer = preset.height.ToString(); + } + } + + listingStandard.Gap(4f); } } diff --git a/Source/AIImages/Window_AIImage.cs b/Source/AIImages/Window_AIImage.cs index 011a3ac..af067fa 100644 --- a/Source/AIImages/Window_AIImage.cs +++ b/Source/AIImages/Window_AIImage.cs @@ -70,6 +70,7 @@ namespace AIImages public override Vector2 InitialSize => new Vector2(900f, 800f); private Vector2 scrollPosition = Vector2.zero; + private Vector2 rightColumnScrollPosition = Vector2.zero; private Vector2 promptScrollPosition = Vector2.zero; private Vector2 negativePromptScrollPosition = Vector2.zero; private float copiedMessageTime = 0f; @@ -440,17 +441,23 @@ namespace AIImages private void DrawRightColumn(Rect rect) { + // Рассчитываем высоту контента для скролла + float contentHeight = CalculateRightColumnHeight(rect); + Rect scrollViewRect = new Rect(0f, 0f, rect.width - 20f, contentHeight); + + Widgets.BeginScrollView(rect, ref rightColumnScrollPosition, scrollViewRect); + float curY = 0f; - curY = DrawImagePreview(rect, curY); - curY = DrawGenerationStatus(rect, curY); - curY = DrawProgressBar(rect, curY); - curY = DrawGenerationButton(rect, curY); + curY = DrawImagePreview(scrollViewRect, curY); + curY = DrawGenerationStatus(scrollViewRect, curY); + curY = DrawProgressBar(scrollViewRect, curY); + curY = DrawGenerationButton(scrollViewRect, curY); // Позитивный промпт секция Text.Font = GameFont.Medium; Widgets.Label( - new Rect(rect.x, rect.y + curY, rect.width, 30f), + new Rect(0f, curY, scrollViewRect.width, 30f), "AIImages.Prompt.PositiveTitle".Translate() ); curY += 35f; @@ -464,10 +471,18 @@ namespace AIImages // Фиксированная высота для области промпта float promptBoxHeight = 100f; - float actualPositiveHeight = Text.CalcHeight(positivePrompt, rect.width - 20f); + float actualPositiveHeight = Text.CalcHeight( + positivePrompt, + scrollViewRect.width - 20f + ); - Rect positiveOuterRect = new Rect(rect.x, rect.y + curY, rect.width, promptBoxHeight); - Rect positiveViewRect = new Rect(0f, 0f, rect.width - 20f, actualPositiveHeight); + 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)); @@ -489,7 +504,7 @@ namespace AIImages // Негативный промпт секция Text.Font = GameFont.Medium; Widgets.Label( - new Rect(rect.x, rect.y + curY, rect.width, 30f), + new Rect(0f, curY, scrollViewRect.width, 30f), "AIImages.Prompt.NegativeTitle".Translate() ); curY += 35f; @@ -500,10 +515,18 @@ namespace AIImages generationSettings ); - float actualNegativeHeight = Text.CalcHeight(negativePrompt, rect.width - 20f); + float actualNegativeHeight = Text.CalcHeight( + negativePrompt, + scrollViewRect.width - 20f + ); - Rect negativeOuterRect = new Rect(rect.x, rect.y + curY, rect.width, promptBoxHeight); - Rect negativeViewRect = new Rect(0f, 0f, rect.width - 20f, actualNegativeHeight); + 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)); @@ -525,7 +548,7 @@ namespace AIImages // Кнопки копирования промптов if ( Widgets.ButtonText( - new Rect(rect.x, rect.y + curY, rect.width / 2f - 5f, 30f), + new Rect(0f, curY, scrollViewRect.width / 2f - 5f, 30f), "AIImages.Prompt.CopyPositive".Translate() ) ) @@ -541,9 +564,9 @@ namespace AIImages if ( Widgets.ButtonText( new Rect( - rect.x + rect.width / 2f + 5f, - rect.y + curY, - rect.width / 2f - 5f, + scrollViewRect.width / 2f + 5f, + curY, + scrollViewRect.width / 2f - 5f, 30f ), "AIImages.Prompt.CopyNegative".Translate() @@ -561,7 +584,7 @@ namespace AIImages // Кнопка обновления данных if ( Widgets.ButtonText( - new Rect(rect.x, rect.y + curY, rect.width, 30f), + new Rect(0f, curY, scrollViewRect.width, 30f), "AIImages.Window.Refresh".Translate() ) ) @@ -575,11 +598,13 @@ namespace AIImages curY += 35f; GUI.color = new Color(0f, 1f, 0f, copiedMessageTime / 2f); Widgets.Label( - new Rect(rect.x, rect.y + curY, rect.width, 25f), + new Rect(0f, curY, scrollViewRect.width, 25f), "AIImages.Prompt.Copied".Translate() ); GUI.color = Color.white; } + + Widgets.EndScrollView(); } private float CalculateContentHeight() @@ -613,15 +638,46 @@ namespace AIImages // Дополнительный отступ height += 50f; - // Позитивный промпт заголовок - height += 35f; - // Позитивный промпт контент - height += 100f + 10f; + return height; + } - // Негативный промпт заголовок - height += 35f; - // Негативный промпт контент - height += 100f + 10f; + private float CalculateRightColumnHeight(Rect rect) + { + float height = 0f; + float contentWidth = rect.width - 20f; + + // Превью изображения + if (generatedImage != null) + { + height += 200f + 10f; + } + else if (!isGenerating) + { + height += 100f + 10f; + } + + // Статус генерации + if (!string.IsNullOrEmpty(generationStatus)) + { + height += 30f; + } + + // Прогресс бар + if (isGenerating && generationProgress > 0.0) + { + height += 30f; + } + + // Кнопка генерации + height += 40f; + + // Позитивный промпт + height += 35f; // Заголовок + height += 100f + 10f; // Бокс + + // Негативный промпт + height += 35f; // Заголовок + height += 100f + 10f; // Бокс // Кнопки копирования height += 35f; @@ -635,7 +691,7 @@ namespace AIImages height += 30f; } - return height; + return height + 50f; // Дополнительный отступ } private float DrawImagePreview(Rect rect, float curY)