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:
Leonid Pershin
2025-10-26 18:09:30 +03:00
parent 990c8695b7
commit 0f60721162
17 changed files with 1907 additions and 335 deletions

View File

@@ -0,0 +1,284 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AIImages.Models;
using AIImages.Settings;
using RimWorld;
using UnityEngine;
using Verse;
namespace AIImages
{
/// <summary>
/// UI для настроек мода в меню настроек RimWorld
/// </summary>
public static class AIImagesSettingsUI
{
private static Vector2 scrollPosition = Vector2.zero;
private static string stepsBuffer;
private static string widthBuffer;
private static string heightBuffer;
public static void DoSettingsWindowContents(Rect inRect, AIImagesModSettings settings)
{
// Инициализируем буферы при первом вызове
if (string.IsNullOrEmpty(stepsBuffer))
{
stepsBuffer = settings.steps.ToString();
widthBuffer = settings.width.ToString();
heightBuffer = settings.height.ToString();
}
Listing_Standard listingStandard = new Listing_Standard();
Rect viewRect = new Rect(0f, 0f, inRect.width - 20f, 1200f);
Widgets.BeginScrollView(inRect, ref scrollPosition, viewRect);
listingStandard.Begin(viewRect);
// === API Settings ===
listingStandard.Label(
"AIImages.Settings.ApiSection".Translate(),
-1f,
"AIImages.Settings.ApiSectionTooltip".Translate()
);
listingStandard.GapLine();
listingStandard.Label("AIImages.Settings.ApiEndpoint".Translate() + ":");
settings.apiEndpoint = listingStandard.TextEntry(settings.apiEndpoint);
listingStandard.Gap(8f);
// Кнопка проверки подключения
if (listingStandard.ButtonText("AIImages.Settings.TestConnection".Translate()))
{
_ = TestApiConnection(settings.apiEndpoint);
}
// Кнопка загрузки моделей
if (listingStandard.ButtonText("AIImages.Settings.LoadModels".Translate()))
{
_ = LoadModelsFromApi(settings);
}
listingStandard.Gap(12f);
// === Generation Settings ===
listingStandard.Label(
"AIImages.Settings.GenerationSection".Translate(),
-1f,
"AIImages.Settings.GenerationSectionTooltip".Translate()
);
listingStandard.GapLine();
// Art Style
if (
listingStandard.ButtonTextLabeled(
"AIImages.Settings.ArtStyle".Translate(),
settings.artStyle.ToString()
)
)
{
List<FloatMenuOption> styleOptions = new List<FloatMenuOption>();
foreach (ArtStyle style in Enum.GetValues(typeof(ArtStyle)))
{
ArtStyle localStyle = style;
styleOptions.Add(
new FloatMenuOption(style.ToString(), () => settings.artStyle = localStyle)
);
}
Find.WindowStack.Add(new FloatMenu(styleOptions));
}
// Shot Type
if (
listingStandard.ButtonTextLabeled(
"AIImages.Settings.ShotType".Translate(),
settings.shotType.ToString()
)
)
{
List<FloatMenuOption> shotOptions = new List<FloatMenuOption>();
foreach (ShotType shot in Enum.GetValues(typeof(ShotType)))
{
ShotType localShot = shot;
shotOptions.Add(
new FloatMenuOption(shot.ToString(), () => settings.shotType = localShot)
);
}
Find.WindowStack.Add(new FloatMenu(shotOptions));
}
listingStandard.Gap(8f);
// Steps
listingStandard.Label("AIImages.Settings.Steps".Translate() + $": {settings.steps}");
settings.steps = (int)listingStandard.Slider(settings.steps, 1, 150);
listingStandard.Gap(8f);
// CFG Scale
listingStandard.Label(
"AIImages.Settings.CfgScale".Translate() + $": {settings.cfgScale:F1}"
);
settings.cfgScale = listingStandard.Slider(settings.cfgScale, 1f, 30f);
listingStandard.Gap(8f);
// Width
listingStandard.Label("AIImages.Settings.Width".Translate() + ":");
widthBuffer = listingStandard.TextEntry(widthBuffer);
if (int.TryParse(widthBuffer, out int width))
{
settings.width = Mathf.Clamp(width, 64, 2048);
}
// Height
listingStandard.Label("AIImages.Settings.Height".Translate() + ":");
heightBuffer = listingStandard.TextEntry(heightBuffer);
if (int.TryParse(heightBuffer, out int height))
{
settings.height = Mathf.Clamp(height, 64, 2048);
}
// Common size presets
listingStandard.Gap(4f);
Rect presetRect = listingStandard.GetRect(30f);
if (Widgets.ButtonText(new Rect(presetRect.x, presetRect.y, 80f, 30f), "512x512"))
{
settings.width = 512;
settings.height = 512;
widthBuffer = "512";
heightBuffer = "512";
}
if (Widgets.ButtonText(new Rect(presetRect.x + 85f, presetRect.y, 80f, 30f), "512x768"))
{
settings.width = 512;
settings.height = 768;
widthBuffer = "512";
heightBuffer = "768";
}
if (
Widgets.ButtonText(new Rect(presetRect.x + 170f, presetRect.y, 80f, 30f), "768x768")
)
{
settings.width = 768;
settings.height = 768;
widthBuffer = "768";
heightBuffer = "768";
}
listingStandard.Gap(12f);
// Sampler
listingStandard.Label("AIImages.Settings.Sampler".Translate() + ":");
settings.selectedSampler = listingStandard.TextEntry(settings.selectedSampler);
listingStandard.Gap(12f);
// === Prompts ===
listingStandard.Label(
"AIImages.Settings.PromptsSection".Translate(),
-1f,
"AIImages.Settings.PromptsSectionTooltip".Translate()
);
listingStandard.GapLine();
listingStandard.Label("AIImages.Settings.BasePositivePrompt".Translate() + ":");
settings.basePositivePrompt = listingStandard.TextEntry(settings.basePositivePrompt, 3);
listingStandard.Gap(8f);
listingStandard.Label("AIImages.Settings.BaseNegativePrompt".Translate() + ":");
settings.baseNegativePrompt = listingStandard.TextEntry(settings.baseNegativePrompt, 3);
listingStandard.Gap(12f);
// === Options ===
listingStandard.Label("AIImages.Settings.OptionsSection".Translate());
listingStandard.GapLine();
listingStandard.CheckboxLabeled(
"AIImages.Settings.AutoLoadModels".Translate(),
ref settings.autoLoadModels
);
listingStandard.CheckboxLabeled(
"AIImages.Settings.ShowTechnicalInfo".Translate(),
ref settings.showTechnicalInfo
);
listingStandard.CheckboxLabeled(
"AIImages.Settings.SaveHistory".Translate(),
ref settings.saveGenerationHistory
);
listingStandard.Gap(12f);
// Save path
listingStandard.Label("AIImages.Settings.SavePath".Translate() + ":");
settings.savePath = listingStandard.TextEntry(settings.savePath);
listingStandard.End();
Widgets.EndScrollView();
}
private static async System.Threading.Tasks.Task TestApiConnection(string endpoint)
{
try
{
Log.Message($"[AI Images] Testing connection to {endpoint}...");
bool available = await AIImagesMod.ApiService.CheckApiAvailability(endpoint);
if (available)
{
Messages.Message(
"AIImages.Settings.ConnectionSuccess".Translate(),
MessageTypeDefOf.PositiveEvent
);
}
else
{
Messages.Message(
"AIImages.Settings.ConnectionFailed".Translate(),
MessageTypeDefOf.RejectInput
);
}
}
catch (Exception ex)
{
Messages.Message($"Error: {ex.Message}", MessageTypeDefOf.RejectInput);
}
}
private static async System.Threading.Tasks.Task LoadModelsFromApi(
AIImagesModSettings settings
)
{
try
{
Log.Message("[AI Images] Loading models from API...");
var models = await AIImagesMod.ApiService.GetAvailableModels(settings.apiEndpoint);
if (models.Count > 0)
{
Messages.Message(
"AIImages.Settings.ModelsLoaded".Translate(models.Count),
MessageTypeDefOf.PositiveEvent
);
// Если модель не выбрана, выбираем первую
if (string.IsNullOrEmpty(settings.selectedModel) && models.Count > 0)
{
settings.selectedModel = models[0];
}
}
else
{
Messages.Message(
"AIImages.Settings.NoModelsFound".Translate(),
MessageTypeDefOf.RejectInput
);
}
}
catch (Exception ex)
{
Messages.Message(
$"Error loading models: {ex.Message}",
MessageTypeDefOf.RejectInput
);
}
}
}
}