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:
284
Source/AIImages/UI/AIImagesSettingsUI.cs
Normal file
284
Source/AIImages/UI/AIImagesSettingsUI.cs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user