Refactor AIImages mod to replace ArtStyle enum with ArtStyleDef name for improved flexibility in prompt generation. Update UI components to reflect changes in art style selection and enhance scrolling functionality in the right column. Update AIImages.dll to incorporate the latest modifications.

This commit is contained in:
Leonid Pershin
2025-10-27 00:19:31 +03:00
parent 9e675dd804
commit ce98638e55
11 changed files with 594 additions and 138 deletions

Binary file not shown.

111
Defs/ArtStyleDefs.xml Normal file
View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<Defs>
<!-- None/Custom Style -->
<AIImages.ArtStyleDef>
<defName>ArtStyle_None</defName>
<label>None (Custom)</label>
<description>No predefined style - use only your custom prompts</description>
<positivePrompt></positivePrompt>
<negativePrompt></negativePrompt>
<qualityTags></qualityTags>
<addBaseQualityTags>false</addBaseQualityTags>
<addBaseNegativePrompts>false</addBaseNegativePrompts>
<sortOrder>0</sortOrder>
</AIImages.ArtStyleDef>
<!-- Realistic Style -->
<AIImages.ArtStyleDef>
<defName>ArtStyle_Realistic</defName>
<label>Realistic</label>
<description>Photorealistic style with high detail</description>
<positivePrompt>photorealistic, hyperrealistic, realistic photo, photography</positivePrompt>
<negativePrompt>cartoon, anime, painting, drawing, illustration</negativePrompt>
<qualityTags>professional photography, 8k uhd, dslr, high quality, sharp focus</qualityTags>
<addBaseQualityTags>true</addBaseQualityTags>
<addBaseNegativePrompts>true</addBaseNegativePrompts>
<sortOrder>10</sortOrder>
</AIImages.ArtStyleDef>
<!-- Semi-Realistic Style -->
<AIImages.ArtStyleDef>
<defName>ArtStyle_SemiRealistic</defName>
<label>Semi-Realistic</label>
<description>Detailed illustration with realistic elements</description>
<positivePrompt>semi-realistic, detailed illustration, realistic art</positivePrompt>
<negativePrompt>cartoon, anime, painting, drawing, illustration</negativePrompt>
<qualityTags>professional photography, 8k uhd, dslr, high quality, sharp focus</qualityTags>
<addBaseQualityTags>true</addBaseQualityTags>
<addBaseNegativePrompts>true</addBaseNegativePrompts>
<sortOrder>20</sortOrder>
</AIImages.ArtStyleDef>
<!-- Anime Style -->
<AIImages.ArtStyleDef>
<defName>ArtStyle_Anime</defName>
<label>Anime</label>
<description>Japanese anime/manga style</description>
<positivePrompt>anime style, manga style, anime character</positivePrompt>
<negativePrompt>realistic, photo, photography, 3d</negativePrompt>
<qualityTags>anime masterpiece, high resolution, vibrant colors</qualityTags>
<addBaseQualityTags>true</addBaseQualityTags>
<addBaseNegativePrompts>true</addBaseNegativePrompts>
<sortOrder>30</sortOrder>
</AIImages.ArtStyleDef>
<!-- Concept Art Style -->
<AIImages.ArtStyleDef>
<defName>ArtStyle_ConceptArt</defName>
<label>Concept Art</label>
<description>Professional digital concept art</description>
<positivePrompt>concept art, digital art, artstation, professional concept design</positivePrompt>
<negativePrompt></negativePrompt>
<qualityTags>trending on artstation, professional digital art</qualityTags>
<addBaseQualityTags>true</addBaseQualityTags>
<addBaseNegativePrompts>true</addBaseNegativePrompts>
<sortOrder>40</sortOrder>
</AIImages.ArtStyleDef>
<!-- Digital Painting Style -->
<AIImages.ArtStyleDef>
<defName>ArtStyle_DigitalPainting</defName>
<label>Digital Painting</label>
<description>Digital painting with brush strokes</description>
<positivePrompt>digital painting, painterly, brush strokes, artistic</positivePrompt>
<negativePrompt></negativePrompt>
<qualityTags></qualityTags>
<addBaseQualityTags>true</addBaseQualityTags>
<addBaseNegativePrompts>true</addBaseNegativePrompts>
<sortOrder>50</sortOrder>
</AIImages.ArtStyleDef>
<!-- Oil Painting Style -->
<AIImages.ArtStyleDef>
<defName>ArtStyle_OilPainting</defName>
<label>Oil Painting</label>
<description>Traditional oil painting style</description>
<positivePrompt>oil painting, traditional painting, canvas, fine art</positivePrompt>
<negativePrompt></negativePrompt>
<qualityTags></qualityTags>
<addBaseQualityTags>true</addBaseQualityTags>
<addBaseNegativePrompts>true</addBaseNegativePrompts>
<sortOrder>60</sortOrder>
</AIImages.ArtStyleDef>
<!-- Sketch Style -->
<AIImages.ArtStyleDef>
<defName>ArtStyle_Sketch</defName>
<label>Sketch</label>
<description>Pencil sketch or line art</description>
<positivePrompt>pencil sketch, hand drawn, sketch art, line art</positivePrompt>
<negativePrompt></negativePrompt>
<qualityTags></qualityTags>
<addBaseQualityTags>true</addBaseQualityTags>
<addBaseNegativePrompts>true</addBaseNegativePrompts>
<sortOrder>70</sortOrder>
</AIImages.ArtStyleDef>
<!-- Cell Shaded Style -->
<AIImages.ArtStyleDef>
<defName>ArtStyle_CellShaded</defName>
<label>Cell Shaded</label>
<description>Flat colors with toon shading</description>
<positivePrompt>cell shaded, flat colors, toon shading, stylized</positivePrompt>
<negativePrompt></negativePrompt>
<qualityTags></qualityTags>
<addBaseQualityTags>true</addBaseQualityTags>
<addBaseNegativePrompts>true</addBaseNegativePrompts>
<sortOrder>80</sortOrder>
</AIImages.ArtStyleDef>
</Defs>

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<Defs>
<!-- Square Sizes -->
<AIImages.ImageSizePresetDef>
<defName>Size_512x512</defName>
<label>512x512 (Square)</label>
<width>512</width>
<height>512</height>
<category>Square</category>
<sortOrder>10</sortOrder>
</AIImages.ImageSizePresetDef>
<AIImages.ImageSizePresetDef>
<defName>Size_768x768</defName>
<label>768x768 (Square)</label>
<width>768</width>
<height>768</height>
<category>Square</category>
<sortOrder>20</sortOrder>
</AIImages.ImageSizePresetDef>
<AIImages.ImageSizePresetDef>
<defName>Size_1024x1024</defName>
<label>1024x1024 (Square)</label>
<width>1024</width>
<height>1024</height>
<category>Square</category>
<sortOrder>30</sortOrder>
</AIImages.ImageSizePresetDef>
<!-- Portrait Sizes -->
<AIImages.ImageSizePresetDef>
<defName>Size_512x768</defName>
<label>512x768 (Portrait)</label>
<width>512</width>
<height>768</height>
<category>Portrait</category>
<sortOrder>40</sortOrder>
</AIImages.ImageSizePresetDef>
<AIImages.ImageSizePresetDef>
<defName>Size_768x1024</defName>
<label>768x1024 (Portrait)</label>
<width>768</width>
<height>1024</height>
<category>Portrait</category>
<sortOrder>50</sortOrder>
</AIImages.ImageSizePresetDef>
<AIImages.ImageSizePresetDef>
<defName>Size_896x1152</defName>
<label>896x1152 (Portrait)</label>
<width>896</width>
<height>1152</height>
<category>Portrait</category>
<sortOrder>60</sortOrder>
</AIImages.ImageSizePresetDef>
<!-- Landscape Sizes -->
<AIImages.ImageSizePresetDef>
<defName>Size_768x512</defName>
<label>768x512 (Landscape)</label>
<width>768</width>
<height>512</height>
<category>Landscape</category>
<sortOrder>70</sortOrder>
</AIImages.ImageSizePresetDef>
<AIImages.ImageSizePresetDef>
<defName>Size_1024x768</defName>
<label>1024x768 (Landscape)</label>
<width>1024</width>
<height>768</height>
<category>Landscape</category>
<sortOrder>80</sortOrder>
</AIImages.ImageSizePresetDef>
<AIImages.ImageSizePresetDef>
<defName>Size_1152x896</defName>
<label>1152x896 (Landscape)</label>
<width>1152</width>
<height>896</height>
<category>Landscape</category>
<sortOrder>90</sortOrder>
</AIImages.ImageSizePresetDef>
</Defs>

124
Defs/README.md Normal file
View File

@@ -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
<AIImages.ArtStyleDef>
<defName>ArtStyle_MyStyle</defName>
<label>My Custom Style</label>
<description>Description of the style</description>
<positivePrompt>style keywords here</positivePrompt>
<negativePrompt>things to avoid</negativePrompt>
<qualityTags>additional quality tags</qualityTags>
<addBaseQualityTags>true</addBaseQualityTags>
<addBaseNegativePrompts>true</addBaseNegativePrompts>
<sortOrder>100</sortOrder>
</AIImages.ArtStyleDef>
```
### 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
<AIImages.ArtStyleDef>
<defName>ArtStyle_Watercolor</defName>
<label>Watercolor</label>
<description>Soft watercolor painting style</description>
<positivePrompt>watercolor painting, soft colors, flowing paint, artistic</positivePrompt>
<negativePrompt>photograph, digital art, sharp edges</negativePrompt>
<qualityTags>traditional art, paper texture</qualityTags>
<addBaseQualityTags>true</addBaseQualityTags>
<addBaseNegativePrompts>true</addBaseNegativePrompts>
<sortOrder>65</sortOrder>
</AIImages.ArtStyleDef>
```
## Image Size Presets (ImageSizePresetDefs.xml)
Image size presets provide quick buttons for common image dimensions.
### Structure
```xml
<AIImages.ImageSizePresetDef>
<defName>Size_1024x1024</defName>
<label>1024x1024</label>
<width>1024</width>
<height>1024</height>
<category>Square</category>
<sortOrder>30</sortOrder>
</AIImages.ImageSizePresetDef>
```
### 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
<AIImages.ImageSizePresetDef>
<defName>Size_2048x1024</defName>
<label>2048x1024</label>
<width>2048</width>
<height>1024</height>
<category>Ultrawide</category>
<sortOrder>95</sortOrder>
</AIImages.ImageSizePresetDef>
```
## Adding Custom Definitions
1. **Create a new XML file** in the `Defs` folder
2. **Start with the XML header**:
```xml
<?xml version="1.0" encoding="utf-8"?>
<Defs>
<!-- Your definitions here -->
</Defs>
```
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

View File

@@ -0,0 +1,46 @@
using System.Diagnostics.CodeAnalysis;
using Verse;
namespace AIImages
{
/// <summary>
/// Определение художественного стиля для генерации изображений
/// </summary>
[SuppressMessage(
"Major Code Smell",
"S1104:Fields should not have public accessibility",
Justification = "Required for RimWorld's Def system XML serialization"
)]
public class ArtStyleDef : Def
{
/// <summary>
/// Промпт для позитивного описания стиля
/// </summary>
public string positivePrompt = "";
/// <summary>
/// Промпт для негативного описания (что исключить)
/// </summary>
public string negativePrompt = "";
/// <summary>
/// Теги качества специфичные для этого стиля
/// </summary>
public string qualityTags = "";
/// <summary>
/// Добавлять ли базовые теги качества (highly detailed, professional, masterpiece, best quality)
/// </summary>
public bool addBaseQualityTags = true;
/// <summary>
/// Добавлять ли базовые негативные промпты (ugly, deformed, low quality, etc.)
/// </summary>
public bool addBaseNegativePrompts = true;
/// <summary>
/// Порядок сортировки в UI
/// </summary>
public int sortOrder = 100;
}
}

View File

@@ -0,0 +1,36 @@
using System.Diagnostics.CodeAnalysis;
using Verse;
namespace AIImages
{
/// <summary>
/// Предустановка размера изображения
/// </summary>
[SuppressMessage(
"Major Code Smell",
"S1104:Fields should not have public accessibility",
Justification = "Required for RimWorld's Def system XML serialization"
)]
public class ImageSizePresetDef : Def
{
/// <summary>
/// Ширина изображения в пикселях
/// </summary>
public int width = 512;
/// <summary>
/// Высота изображения в пикселях
/// </summary>
public int height = 512;
/// <summary>
/// Категория размера (Square, Portrait, Landscape)
/// </summary>
public string category = "Square";
/// <summary>
/// Порядок сортировки в UI
/// </summary>
public int sortOrder = 100;
}
}

View File

@@ -15,7 +15,7 @@ namespace AIImages.Models
public string Scheduler { get; set; } public string Scheduler { get; set; }
public int Seed { get; set; } public int Seed { get; set; }
public string Model { get; set; } public string Model { get; set; }
public ArtStyle ArtStyle { get; set; } public string ArtStyleDefName { get; set; }
public StableDiffusionSettings() public StableDiffusionSettings()
{ {
@@ -25,27 +25,11 @@ namespace AIImages.Models
Width = 512; Width = 512;
Height = 768; Height = 768;
Sampler = "Euler a"; Sampler = "Euler a";
Scheduler = "Automatic"; Scheduler = "Automatic"; // С большой буквы для API
Seed = -1; // Случайный seed Seed = -1; // Случайный seed
ArtStyle = ArtStyle.Realistic; ArtStyleDefName = "ArtStyle_Realistic";
PositivePrompt = ""; PositivePrompt = "";
NegativePrompt = "ugly, deformed, low quality, blurry, bad anatomy, worst quality"; NegativePrompt = "ugly, deformed, low quality, blurry, bad anatomy, worst quality";
} }
} }
/// <summary>
/// Художественный стиль изображения
/// </summary>
public enum ArtStyle
{
None, // Без стиля
Realistic,
SemiRealistic,
Anime,
ConceptArt,
DigitalPainting,
OilPainting,
Sketch,
CellShaded,
}
} }

View File

@@ -34,25 +34,6 @@ namespace AIImages.Services
{ "Pretty", "attractive features, pleasant appearance, charming" }, { "Pretty", "attractive features, pleasant appearance, charming" },
}; };
private static readonly Dictionary<ArtStyle, string> 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( public string GeneratePositivePrompt(
PawnAppearanceData appearanceData, PawnAppearanceData appearanceData,
StableDiffusionSettings settings StableDiffusionSettings settings
@@ -66,17 +47,15 @@ namespace AIImages.Services
// 1. Базовый пользовательский промпт (если указан) - идет первым // 1. Базовый пользовательский промпт (если указан) - идет первым
if (!string.IsNullOrEmpty(settings.PositivePrompt)) if (!string.IsNullOrEmpty(settings.PositivePrompt))
{ {
prompt.Append(settings.PositivePrompt); prompt.Append(settings.PositivePrompt.TrimEnd(',', ' '));
prompt.Append(", "); prompt.Append(", ");
} }
// 2. Художественный стиль // 2. Художественный стиль
if ( var styleDef = DefDatabase<ArtStyleDef>.GetNamedSilentFail(settings.ArtStyleDefName);
ArtStylePrompts.TryGetValue(settings.ArtStyle, out string stylePrompt) if (styleDef != null && !string.IsNullOrEmpty(styleDef.positivePrompt))
&& !string.IsNullOrEmpty(stylePrompt)
)
{ {
prompt.Append(stylePrompt); prompt.Append(styleDef.positivePrompt);
prompt.Append(", "); prompt.Append(", ");
} }
@@ -127,7 +106,7 @@ namespace AIImages.Services
} }
// 10. Качественные теги // 10. Качественные теги
prompt.Append(GetQualityTags(settings.ArtStyle)); prompt.Append(GetQualityTags(settings.ArtStyleDefName));
return prompt.ToString().Trim().TrimEnd(','); return prompt.ToString().Trim().TrimEnd(',');
} }
@@ -139,34 +118,38 @@ namespace AIImages.Services
// 1. Пользовательский негативный промпт (если указан) - идет первым // 1. Пользовательский негативный промпт (если указан) - идет первым
if (!string.IsNullOrEmpty(settings.NegativePrompt)) if (!string.IsNullOrEmpty(settings.NegativePrompt))
{ {
negativePrompt.Append(settings.NegativePrompt); negativePrompt.Append(settings.NegativePrompt.TrimEnd(',', ' '));
negativePrompt.Append(", "); }
// Получаем стиль из Def
var styleDef = DefDatabase<ArtStyleDef>.GetNamedSilentFail(settings.ArtStyleDefName);
if (styleDef == null || !styleDef.addBaseNegativePrompts)
{
// Для стилей без базовых негативов - используем только пользовательский промпт
return negativePrompt.ToString().Trim();
} }
// 2. Базовые негативные промпты // 2. Базовые негативные промпты
if (negativePrompt.Length > 0)
{
negativePrompt.Append(", ");
}
negativePrompt.Append( negativePrompt.Append(
"ugly, deformed, low quality, blurry, bad anatomy, worst quality, " "ugly, deformed, low quality, blurry, bad anatomy, worst quality, "
); );
negativePrompt.Append( negativePrompt.Append(
"mutated, disfigured, bad proportions, extra limbs, missing limbs, " "mutated, disfigured, bad proportions, extra limbs, missing limbs"
); );
// 3. Специфичные для стиля негативы // 3. Специфичные для стиля негативы из Def
switch (settings.ArtStyle) if (!string.IsNullOrEmpty(styleDef.negativePrompt))
{ {
case ArtStyle.Realistic: negativePrompt.Append(", ");
case ArtStyle.SemiRealistic: negativePrompt.Append(styleDef.negativePrompt);
negativePrompt.Append("cartoon, anime, painting, drawing, illustration, ");
break;
case ArtStyle.Anime:
negativePrompt.Append("realistic, photo, photography, 3d, ");
break;
case ArtStyle.None:
// Без дополнительных негативных промптов для стиля None
break;
} }
return negativePrompt.ToString().Trim().TrimEnd(','); return negativePrompt.ToString().Trim();
} }
public string GetFullPromptDescription( public string GetFullPromptDescription(
@@ -317,24 +300,33 @@ namespace AIImages.Services
return apparelDesc.ToString(); return apparelDesc.ToString();
} }
private string GetQualityTags(ArtStyle style) private string GetQualityTags(string styleDefName)
{ {
var baseTags = "highly detailed, professional, masterpiece, best quality"; var styleDef = DefDatabase<ArtStyleDef>.GetNamedSilentFail(styleDefName);
if (styleDef == null)
switch (style)
{ {
case ArtStyle.None: return "";
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;
} }
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();
} }
/// <summary> /// <summary>

View File

@@ -14,7 +14,7 @@ namespace AIImages.Settings
public string apiEndpoint = "http://127.0.0.1:7860"; public string apiEndpoint = "http://127.0.0.1:7860";
public string selectedModel = ""; public string selectedModel = "";
public string selectedSampler = "Euler a"; public string selectedSampler = "Euler a";
public string selectedScheduler = "Automatic"; public string selectedScheduler = "Automatic"; // С большой буквы для API
// Кэшированные списки из API (не сохраняются) // Кэшированные списки из API (не сохраняются)
[Unsaved] [Unsaved]
@@ -38,8 +38,8 @@ namespace AIImages.Settings
public string baseNegativePrompt = public string baseNegativePrompt =
"ugly, deformed, low quality, blurry, bad anatomy, worst quality"; "ugly, deformed, low quality, blurry, bad anatomy, worst quality";
// Художественный стиль // Художественный стиль (defName)
public ArtStyle artStyle = ArtStyle.Realistic; public string artStyleDefName = "ArtStyle_Realistic";
// Путь для сохранения // Путь для сохранения
public string savePath = "AIImages/Generated"; public string savePath = "AIImages/Generated";
@@ -70,7 +70,7 @@ namespace AIImages.Settings
"ugly, deformed, low quality, blurry, bad anatomy, worst quality" "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"); Scribe_Values.Look(ref savePath, "savePath", "AIImages/Generated");
@@ -97,7 +97,7 @@ namespace AIImages.Settings
Scheduler = selectedScheduler, Scheduler = selectedScheduler,
Seed = seed, Seed = seed,
Model = selectedModel, Model = selectedModel,
ArtStyle = artStyle, ArtStyleDefName = artStyleDefName,
PositivePrompt = basePositivePrompt, PositivePrompt = basePositivePrompt,
NegativePrompt = baseNegativePrompt, NegativePrompt = baseNegativePrompt,
}; };

View File

@@ -174,21 +174,37 @@ namespace AIImages
AIImagesModSettings settings AIImagesModSettings settings
) )
{ {
// Получаем текущий стиль
var currentStyleDef = DefDatabase<ArtStyleDef>.GetNamedSilentFail(
settings.artStyleDefName
);
string currentStyleLabel = currentStyleDef?.label ?? settings.artStyleDefName;
if ( if (
listingStandard.ButtonTextLabeled( listingStandard.ButtonTextLabeled(
"AIImages.Settings.ArtStyle".Translate(), "AIImages.Settings.ArtStyle".Translate(),
settings.artStyle.ToString() currentStyleLabel
) )
) )
{ {
List<FloatMenuOption> styleOptions = new List<FloatMenuOption>(); List<FloatMenuOption> styleOptions = new List<FloatMenuOption>();
foreach (ArtStyle style in Enum.GetValues(typeof(ArtStyle)))
// Получаем все стили из DefDatabase и сортируем по sortOrder
var allStyles = DefDatabase<ArtStyleDef>.AllDefs.OrderBy(s => s.sortOrder);
foreach (var styleDef in allStyles)
{ {
ArtStyle localStyle = style; string localDefName = styleDef.defName;
string localLabel = styleDef.label;
styleOptions.Add( styleOptions.Add(
new FloatMenuOption(style.ToString(), () => settings.artStyle = localStyle) new FloatMenuOption(
localLabel,
() => settings.artStyleDefName = localDefName
)
); );
} }
Find.WindowStack.Add(new FloatMenu(styleOptions)); Find.WindowStack.Add(new FloatMenu(styleOptions));
} }
} }
@@ -220,34 +236,47 @@ namespace AIImages
AIImagesModSettings settings AIImagesModSettings settings
) )
{ {
listingStandard.Gap(4f); // Получаем все предустановки размеров из DefDatabase
Rect presetRect1 = listingStandard.GetRect(30f); var allPresets = DefDatabase<ImageSizePresetDef>
DrawPresetButton(presetRect1, 0f, "512x512", 512, 512, settings); .AllDefs.OrderBy(p => p.sortOrder)
DrawPresetButton(presetRect1, 85f, "512x768", 512, 768, settings); .ToList();
DrawPresetButton(presetRect1, 170f, "768x768", 768, 768, settings);
listingStandard.Gap(4f); if (!allPresets.Any())
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))
{ {
settings.width = width; return;
settings.height = height; }
widthBuffer = width.ToString();
heightBuffer = height.ToString(); 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);
} }
} }

View File

@@ -70,6 +70,7 @@ namespace AIImages
public override Vector2 InitialSize => new Vector2(900f, 800f); public override Vector2 InitialSize => new Vector2(900f, 800f);
private Vector2 scrollPosition = Vector2.zero; private Vector2 scrollPosition = Vector2.zero;
private Vector2 rightColumnScrollPosition = Vector2.zero;
private Vector2 promptScrollPosition = Vector2.zero; private Vector2 promptScrollPosition = Vector2.zero;
private Vector2 negativePromptScrollPosition = Vector2.zero; private Vector2 negativePromptScrollPosition = Vector2.zero;
private float copiedMessageTime = 0f; private float copiedMessageTime = 0f;
@@ -440,17 +441,23 @@ namespace AIImages
private void DrawRightColumn(Rect rect) 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; float curY = 0f;
curY = DrawImagePreview(rect, curY); curY = DrawImagePreview(scrollViewRect, curY);
curY = DrawGenerationStatus(rect, curY); curY = DrawGenerationStatus(scrollViewRect, curY);
curY = DrawProgressBar(rect, curY); curY = DrawProgressBar(scrollViewRect, curY);
curY = DrawGenerationButton(rect, curY); curY = DrawGenerationButton(scrollViewRect, curY);
// Позитивный промпт секция // Позитивный промпт секция
Text.Font = GameFont.Medium; Text.Font = GameFont.Medium;
Widgets.Label( Widgets.Label(
new Rect(rect.x, rect.y + curY, rect.width, 30f), new Rect(0f, curY, scrollViewRect.width, 30f),
"AIImages.Prompt.PositiveTitle".Translate() "AIImages.Prompt.PositiveTitle".Translate()
); );
curY += 35f; curY += 35f;
@@ -464,10 +471,18 @@ namespace AIImages
// Фиксированная высота для области промпта // Фиксированная высота для области промпта
float promptBoxHeight = 100f; 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 positiveOuterRect = new Rect(0f, curY, scrollViewRect.width, promptBoxHeight);
Rect positiveViewRect = new Rect(0f, 0f, rect.width - 20f, actualPositiveHeight); Rect positiveViewRect = new Rect(
0f,
0f,
scrollViewRect.width - 20f,
actualPositiveHeight
);
// Рисуем фон // Рисуем фон
Widgets.DrawBoxSolid(positiveOuterRect, new Color(0.1f, 0.3f, 0.1f, 0.5f)); Widgets.DrawBoxSolid(positiveOuterRect, new Color(0.1f, 0.3f, 0.1f, 0.5f));
@@ -489,7 +504,7 @@ namespace AIImages
// Негативный промпт секция // Негативный промпт секция
Text.Font = GameFont.Medium; Text.Font = GameFont.Medium;
Widgets.Label( Widgets.Label(
new Rect(rect.x, rect.y + curY, rect.width, 30f), new Rect(0f, curY, scrollViewRect.width, 30f),
"AIImages.Prompt.NegativeTitle".Translate() "AIImages.Prompt.NegativeTitle".Translate()
); );
curY += 35f; curY += 35f;
@@ -500,10 +515,18 @@ namespace AIImages
generationSettings 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 negativeOuterRect = new Rect(0f, curY, scrollViewRect.width, promptBoxHeight);
Rect negativeViewRect = new Rect(0f, 0f, rect.width - 20f, actualNegativeHeight); Rect negativeViewRect = new Rect(
0f,
0f,
scrollViewRect.width - 20f,
actualNegativeHeight
);
// Рисуем фон (красноватый для негативного) // Рисуем фон (красноватый для негативного)
Widgets.DrawBoxSolid(negativeOuterRect, new Color(0.3f, 0.1f, 0.1f, 0.5f)); Widgets.DrawBoxSolid(negativeOuterRect, new Color(0.3f, 0.1f, 0.1f, 0.5f));
@@ -525,7 +548,7 @@ namespace AIImages
// Кнопки копирования промптов // Кнопки копирования промптов
if ( if (
Widgets.ButtonText( 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() "AIImages.Prompt.CopyPositive".Translate()
) )
) )
@@ -541,9 +564,9 @@ namespace AIImages
if ( if (
Widgets.ButtonText( Widgets.ButtonText(
new Rect( new Rect(
rect.x + rect.width / 2f + 5f, scrollViewRect.width / 2f + 5f,
rect.y + curY, curY,
rect.width / 2f - 5f, scrollViewRect.width / 2f - 5f,
30f 30f
), ),
"AIImages.Prompt.CopyNegative".Translate() "AIImages.Prompt.CopyNegative".Translate()
@@ -561,7 +584,7 @@ namespace AIImages
// Кнопка обновления данных // Кнопка обновления данных
if ( if (
Widgets.ButtonText( Widgets.ButtonText(
new Rect(rect.x, rect.y + curY, rect.width, 30f), new Rect(0f, curY, scrollViewRect.width, 30f),
"AIImages.Window.Refresh".Translate() "AIImages.Window.Refresh".Translate()
) )
) )
@@ -575,11 +598,13 @@ namespace AIImages
curY += 35f; curY += 35f;
GUI.color = new Color(0f, 1f, 0f, copiedMessageTime / 2f); GUI.color = new Color(0f, 1f, 0f, copiedMessageTime / 2f);
Widgets.Label( Widgets.Label(
new Rect(rect.x, rect.y + curY, rect.width, 25f), new Rect(0f, curY, scrollViewRect.width, 25f),
"AIImages.Prompt.Copied".Translate() "AIImages.Prompt.Copied".Translate()
); );
GUI.color = Color.white; GUI.color = Color.white;
} }
Widgets.EndScrollView();
} }
private float CalculateContentHeight() private float CalculateContentHeight()
@@ -613,15 +638,46 @@ namespace AIImages
// Дополнительный отступ // Дополнительный отступ
height += 50f; height += 50f;
// Позитивный промпт заголовок return height;
height += 35f; }
// Позитивный промпт контент
height += 100f + 10f;
// Негативный промпт заголовок private float CalculateRightColumnHeight(Rect rect)
height += 35f; {
// Негативный промпт контент float height = 0f;
height += 100f + 10f; 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; height += 35f;
@@ -635,7 +691,7 @@ namespace AIImages
height += 30f; height += 30f;
} }
return height; return height + 50f; // Дополнительный отступ
} }
private float DrawImagePreview(Rect rect, float curY) private float DrawImagePreview(Rect rect, float curY)