Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 118 additions & 93 deletions src/Features/WeatherEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
#include "Util.h"
#include "Utils/Game.h"
#include "Utils/UI.h"
#include "WeatherEditor/EditorWindow.h"
#include "WeatherManager.h"
#include <cmath>

#include "WeatherEditor/EditorWindow.h"
#include <nlohmann/json.hpp>

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
Expand Down Expand Up @@ -67,47 +67,52 @@ void WeatherEditor::DrawSettings()
EditorWindow::GetSingleton()->open = true;
}

ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();

// Basic weather editor info
DrawWeatherStatusPanel();

ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();

// Integrated Weather Picker UI
DrawWeatherPickerSection();
}

void WeatherEditor::DrawWeatherPickerSection()
void WeatherEditor::Prepass()
{
if (ImGui::TreeNodeEx("Weather Details", ImGuiTreeNodeFlags_DefaultOpen)) {
const auto& themeSettings = Menu::GetSingleton()->GetTheme();
const auto& menuSettings = Menu::GetSingleton()->GetSettings();

// Show as Overlay checkbox
bool showInOverlay = WeatherDetailsWindow.ShowInOverlay;
if (ImGui::Checkbox("Show in Overlay", &showInOverlay)) {
WeatherDetailsWindow.ShowInOverlay = showInOverlay;
}
if (auto _tt = Util::HoverTooltipWrapper()) {
ImGui::Text("Opens weather details in a separate window that stays open\neven when the main menu is closed.");
ImGui::Text("Toggle with ");
ImGui::SameLine();
ImGui::TextColored(themeSettings.StatusPalette.CurrentHotkey, "%s", Util::Input::KeyIdToString(menuSettings.OverlayToggleKey).c_str());
// Re-enforce weather lock if active (handles time changes)
auto editorWindow = EditorWindow::GetSingleton();
if (editorWindow->IsWeatherLocked()) {
auto lockedWeather = editorWindow->GetLockedWeather();
auto sky = globals::game::sky;
if (sky && lockedWeather && sky->currentWeather != lockedWeather) {
sky->ForceWeather(lockedWeather, false);
}
ImGui::Spacing();
}
}

// Render core weather details
RenderCoreWeatherDetails(true); // true = show interactive elements in main settings panel
void WeatherEditor::DrawWeatherPickerSection()
{
ImGui::Spacing();
Util::DrawSectionHeader("Weather Details");

// Render weather analysis from features with collapsible headers
RenderFeatureWeatherAnalysis();
const auto& themeSettings = Menu::GetSingleton()->GetTheme();
const auto& menuSettings = Menu::GetSingleton()->GetSettings();

ImGui::TreePop();
// Show as Overlay checkbox
bool showInOverlay = WeatherDetailsWindow.ShowInOverlay;
if (ImGui::Checkbox("Show in Overlay", &showInOverlay)) {
WeatherDetailsWindow.ShowInOverlay = showInOverlay;
}
if (auto _tt = Util::HoverTooltipWrapper()) {
ImGui::Text("Opens weather details in a separate window that stays open\neven when the main menu is closed. ");
ImGui::Text("Toggle with ");
ImGui::SameLine();
ImGui::TextColored(themeSettings.StatusPalette.CurrentHotkey, "%s", Util::Input::KeyIdToString(menuSettings.OverlayToggleKey).c_str());
}
ImGui::Spacing();

// Render core weather details
RenderCoreWeatherDetails(true); // true = show interactive elements in main settings panel

// Render weather analysis from features with collapsible headers
RenderFeatureWeatherAnalysis();
}

void WeatherEditor::LerpWeather(RE::TESWeather* oldWeather, RE::TESWeather* newWeather, float currentWeatherPct)
Expand Down Expand Up @@ -185,39 +190,58 @@ void WeatherEditor::LerpWeather(RE::TESWeather* oldWeather, RE::TESWeather* newW

void WeatherEditor::DrawWeatherStatusPanel()
{
ImGui::Text("Current Weather Status");
ImGui::Separator();
ImGui::Spacing();
Util::DrawSectionHeader("Weather Status");
ImGui::Spacing();

auto weatherManager = WeatherManager::GetSingleton();
auto currentWeathers = weatherManager->GetCurrentWeathers();
const auto& theme = Menu::GetSingleton()->GetTheme();

if (currentWeathers.currentWeather) {
// Show if weather has custom settings
if (weatherManager->HasWeatherSettings(currentWeathers.currentWeather)) {
ImGui::TextColored(theme.StatusPalette.SuccessColor, "Has Custom Settings");
} else {
ImGui::TextColored(theme.StatusPalette.Disable, "Using Default Settings");
}

// Show what the current weather is
ImGui::Text("Current Weather: %s",
currentWeathers.currentWeather->GetFormEditorID() ?
currentWeathers.currentWeather->GetFormEditorID() :
std::format("{:08X}", currentWeathers.currentWeather->GetFormID()).c_str());

// Always reserve space for transition info to prevent UI shifting
if (currentWeathers.lastWeather && currentWeathers.lerpFactor < 1.0f) {
ImGui::Text("Transitioning From: %s",
currentWeathers.lastWeather->GetFormEditorID() ?
currentWeathers.lastWeather->GetFormEditorID() :
std::format("{:08X}", currentWeathers.lastWeather->GetFormID()).c_str());

ImGui::ProgressBar(currentWeathers.lerpFactor, ImVec2(-1, 0),
std::format("Transition: {:.1f}%%", currentWeathers.lerpFactor * 100.0f).c_str());
} else {
ImGui::Text("Transition: Complete (100%%)");
ImGui::Text("Transitioning From: No Transition");
}

// Show if weather has custom settings
if (weatherManager->HasWeatherSettings(currentWeathers.currentWeather)) {
ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "Has Custom Settings");
} else {
ImGui::TextColored({ 0.7f, 0.7f, 0.7f, 1.0f }, "Using Default Settings");
// Always show progress bar
const bool isTransitioning = currentWeathers.lastWeather && currentWeathers.lerpFactor < 1.0f;
float displayPct = isTransitioning ? currentWeathers.lerpFactor : 1.0f;

// Show background color when transition is complete
if (!isTransitioning) {
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImGui::GetStyleColorVec4(ImGuiCol_FrameBg));
}

ImGui::ProgressBar(displayPct, ImVec2(-1, 0),
isTransitioning ?
std::format("Transition: {:.1f}%", currentWeathers.lerpFactor * 100.0f).c_str() :
"");

if (!isTransitioning) {
ImGui::PopStyleColor();
}

} else {
ImGui::TextColored({ 1.0f, 0.5f, 0.0f, 1.0f }, "No Active Weather");
ImGui::TextColored(theme.StatusPalette.Warning, "No Active Weather");
}
}

Expand All @@ -227,7 +251,7 @@ void WeatherEditor::DrawWeatherStatusPanel()

void WeatherEditor::RenderWeatherDetailsWindow(bool* open)
{
if (!*open)
if (!open || !*open)
return;

// Set initial position if not already set
Expand All @@ -240,7 +264,7 @@ void WeatherEditor::RenderWeatherDetailsWindow(bool* open)
}

ImGui::SetNextWindowSize(ImVec2(600, 800), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Weather Details##Popup", nullptr, ImGuiWindowFlags_None)) {
if (ImGui::Begin("Weather Details##Popup", open, ImGuiWindowFlags_None)) {
// Remember window position for next frame
ImVec2 currentPos = ImGui::GetWindowPos();
if (currentPos.x != WeatherDetailsWindow.Position.x || currentPos.y != WeatherDetailsWindow.Position.y) {
Expand Down Expand Up @@ -270,7 +294,7 @@ ImVec4 WeatherEditor::GetWeatherTypeColor(RE::TESWeather* weather)
const auto& theme = Menu::GetSingleton()->GetTheme();

// Priority order for weather classification colors (highest priority first)
static const std::vector<RE::TESWeather::WeatherDataFlag> priorityFlags = {
static const std::vector<RE::TESWeather::WeatherDataFlag> priorityOrder = {
RE::TESWeather::WeatherDataFlag::kRainy,
RE::TESWeather::WeatherDataFlag::kSnow,
RE::TESWeather::WeatherDataFlag::kPermAurora,
Expand All @@ -280,7 +304,7 @@ ImVec4 WeatherEditor::GetWeatherTypeColor(RE::TESWeather* weather)
};

// Check flags in priority order
for (const auto& flag : priorityFlags) {
for (const auto& flag : priorityOrder) {
if (weather->data.flags.any(flag)) {
return GetWeatherFlagColor(flag);
}
Expand Down Expand Up @@ -376,13 +400,9 @@ void WeatherEditor::DisplayLightningInfo(RE::TESWeather* weather, bool showInter
ImGui::PopStyleVar();
}
if (colorChanged && showInteractiveElements) {
auto toByte = [](float value) -> std::uint8_t {
int scaled = static_cast<int>(std::lround(value * 255.0f));
return static_cast<std::uint8_t>(std::clamp(scaled, 0, 255));
};
weather->data.lightningColor.red = toByte(lightningColor[0]);
weather->data.lightningColor.green = toByte(lightningColor[1]);
weather->data.lightningColor.blue = toByte(lightningColor[2]);
weather->data.lightningColor.red = static_cast<std::uint8_t>(lightningColor[0] * 255.0f + 0.5f);
weather->data.lightningColor.green = static_cast<std::uint8_t>(lightningColor[1] * 255.0f + 0.5f);
weather->data.lightningColor.blue = static_cast<std::uint8_t>(lightningColor[2] * 255.0f + 0.5f);
}
int8_t thunderFreqRaw = weather->data.thunderLightningFrequency;
ImGui::BulletText("Thunder Frequency: %d (signed 8-bit)", static_cast<int>(thunderFreqRaw));
Expand Down Expand Up @@ -540,7 +560,7 @@ void WeatherEditor::RenderWeatherControls(RE::Sky* sky)
}
// Dynamic checkbox layout - calculate how many fit per row
float availableWidth = ImGui::GetContentRegionAvail().x;
float checkboxWidth = 80.0f; // Adjusted for "None"
float checkboxWidth = 110.0f; // Fits "Aurora Sun" label

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably be dynamic but meh idc

int checkboxesPerRow = std::max(1, static_cast<int>(availableWidth / checkboxWidth));

// Colored checkboxes with dynamic layout
Expand Down Expand Up @@ -597,38 +617,44 @@ void WeatherEditor::RenderWeatherControls(RE::Sky* sky)
// Accelerate checkbox
ImGui::Checkbox("Accelerate Weather Change", &s_accelerateWeatherChange);
if (auto _tt = Util::HoverTooltipWrapper()) {
Util::DrawMultiLineTooltip({ "When enabled, weather changes are immediate",
"When disabled, uses normal transition speed." });
ImGui::Text("When enabled, weather changes instantly");
} // Reset Weather button
std::string resetButtonLabel = "Reset Weather";
if (sky->defaultWeather) {
resetButtonLabel += " to " + Util::FormatWeather(sky->defaultWeather);
}

// Color the reset button to match the default weather
if (sky->defaultWeather) {
ImVec4 weatherColor = GetWeatherTypeColor(sky->defaultWeather);
ImGui::PushStyleColor(ImGuiCol_Text, weatherColor);
}

if (ImGui::Button(resetButtonLabel.c_str())) {
if (ImGui::Button("Reset Weather")) {
sky->ResetWeather();
// Update the selection box to reflect the reset weather without double-applying
s_selectedWeatherIdx = FindWeatherIndex(sky->defaultWeather);
logger::info("[WeatherEditor] Reset weather to default");
}

if (sky->defaultWeather) {
if (auto _tt = Util::HoverTooltipWrapper()) {
ImGui::Text("Resets weather to default");
}

// Lock Weather toggle
ImGui::SameLine();
auto editorWindow = EditorWindow::GetSingleton();
bool isLocked = editorWindow->IsWeatherLocked();
const char* lockLabel = isLocked ? "Unlock Weather" : "Lock Weather";

if (isLocked) {
const auto& theme = Menu::GetSingleton()->GetTheme();
ImGui::PushStyleColor(ImGuiCol_Button, theme.StatusPalette.SuccessColor);
}
if (ImGui::Button(lockLabel)) {
if (isLocked) {
editorWindow->UnlockWeather();
} else if (sky->currentWeather) {
editorWindow->LockWeather(sky->currentWeather);
}
}
if (isLocked) {
ImGui::PopStyleColor();
}
if (auto _tt = Util::HoverTooltipWrapper()) {
if (sky->defaultWeather) {
Util::DrawMultiLineTooltip({ "Resets to default weather:",
Util::FormatWeather(sky->defaultWeather).c_str() });
} else {
ImGui::Text("Resets weather to default (no default weather set)");
}
} // Weather Selection - now with colored text
ImGui::Text(isLocked ? "Unlock weather to allow natural changes" : "Lock current weather to prevent changes");
}
Comment thread
SkrubbySkrubInAShrub marked this conversation as resolved.

// Weather Selection - now with colored text
std::vector<std::string> weatherLabels;
weatherLabels.reserve(s_filteredWeathers.size());
for (const auto& weather : s_filteredWeathers) {
Expand All @@ -651,10 +677,13 @@ void WeatherEditor::RenderWeatherControls(RE::Sky* sky)
s_selectedWeatherIdx = i;
// Weather changed, apply it
auto selectedWeather = s_filteredWeathers[s_selectedWeatherIdx];
sky->SetWeather(selectedWeather, true, false);

if (s_accelerateWeatherChange) {
sky->currentWeatherPct = 1.0f;
// Instant transition - force the weather
sky->ForceWeather(selectedWeather, false);
} else {
// Normal transition
sky->SetWeather(selectedWeather, true, false);
}

logger::info("[WeatherEditor] Changed weather to: {}", Util::FormatWeather(selectedWeather));
Expand All @@ -680,11 +709,10 @@ void WeatherEditor::RenderWeatherControls(RE::Sky* sky)

void WeatherEditor::RenderWeatherInformationDisplay(RE::Sky* sky, bool showInteractiveElements)
{
static bool weatherInfoExpanded = true;
Util::DrawSectionHeader("Weather Information", false, true, &weatherInfoExpanded);

if (!weatherInfoExpanded)
return;
ImGui::Spacing();
ImGui::Spacing();
ImGui::Spacing();
Util::DrawSectionHeader("Weather Information", false, true);

// Update cache: store current lastWeather if it exists, otherwise keep the cached one
if (sky->lastWeather) {
Expand Down Expand Up @@ -1029,19 +1057,16 @@ std::string WeatherEditor::GetDisplayName(const RE::TESWeather* weather)
void WeatherEditor::DrawOverlay()
{
bool overlayVisible = Menu::GetSingleton()->overlayVisible;
static bool lastShowInOverlay = false;
const bool showInOverlay = WeatherDetailsWindow.ShowInOverlay;
static bool s_prevOverlayVisible = false;
// If ShowInOverlay is true and overlay is visible, auto-enable the window if not already enabled
if (showInOverlay && overlayVisible) {
if (!lastShowInOverlay) {
if (WeatherDetailsWindow.ShowInOverlay && overlayVisible) {
if (!s_prevOverlayVisible && !WeatherDetailsWindow.Enabled) {
WeatherDetailsWindow.Enabled = true;
}
if (WeatherDetailsWindow.Enabled) {
bool* p_open = &WeatherDetailsWindow.Enabled;
RenderWeatherDetailsWindow(p_open);
}
bool* p_open = &WeatherDetailsWindow.Enabled;
RenderWeatherDetailsWindow(p_open);
}
lastShowInOverlay = showInOverlay;
s_prevOverlayVisible = overlayVisible;
}

bool WeatherEditor::IsOverlayVisible() const
Expand Down
28 changes: 27 additions & 1 deletion src/Features/WeatherEditor.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,45 @@ struct WeatherEditor : OverlayFeature

virtual void DataLoaded() override;
virtual void DrawSettings() override;
virtual void Prepass() override;

void LerpWeather(RE::TESWeather*, RE::TESWeather*, float);

// Weather Picker functionality integrated into Weather Editor
/**
* Renders the standalone weather details window.
* @param open Pointer to the open/close state owned by the caller.
*/
void RenderWeatherDetailsWindow(bool* open);

// Core weather display functions that other features can use
/**
* Displays weather info for a given weather record.
* @param weather Weather record to display.
* @param weatherPct Optional blend percentage (0-1), or -1 to hide.
* @param showInteractiveElements Enables interactive controls when true.
*/
static void DisplayWeatherInfo(RE::TESWeather* weather, float weatherPct = -1.0f, bool showInteractiveElements = true);
/**
* Renders the core weather details UI section.
* @param showInteractiveElements Enables interactive controls when true.
*/
static void RenderCoreWeatherDetails(bool showInteractiveElements = true);
/**
* Renders weather analysis sections contributed by other features.
*/
static void RenderFeatureWeatherAnalysis();

// --- Refactor helpers for RenderCoreWeatherDetails ---
/**
* Renders the weather controls section.
* @param sky Active sky instance.
*/
static void RenderWeatherControls(RE::Sky* sky);
/**
* Renders the weather information display section.
* @param sky Active sky instance.
* @param showInteractiveElements Enables interactive controls when true.
*/
static void RenderWeatherInformationDisplay(RE::Sky* sky, bool showInteractiveElements = true);

struct WeatherDetailsWindowSettings
Expand Down