Step 176: smooth UI transitions

This commit is contained in:
Bill
2026-02-09 22:18:11 -07:00
parent 6f5005b326
commit 1693fd08d6
17 changed files with 480 additions and 39 deletions

View File

@@ -3,6 +3,7 @@
#include "../EditorState.h"
#include "../EditorUtils.h"
#include "../CompletionUtils.h"
#include "../AnimationUtils.h"
static void renderEditorPanel(EditorState& state) {
// ---------------------------------------------------------------
@@ -69,6 +70,12 @@ static void renderEditorPanel(EditorState& state) {
if (!state.active() || state.active()->path != path) {
state.switchToBuffer(path);
}
const double nowSeconds = ImGui::GetTime();
float tabAlpha = state.uiAnimations.tabFadeAlpha(path,
nowSeconds,
state.settings.getReduceMotion());
ImGui::PushStyleVar(ImGuiStyleVar_Alpha,
ImGui::GetStyle().Alpha * tabAlpha);
// Editable text area fills available space
ImVec2 avail = ImGui::GetContentRegionAvail();
avail.y -= 4; // small margin
@@ -177,6 +184,9 @@ static void renderEditorPanel(EditorState& state) {
opts.showLineNumbers = state.ui.showLineNumbers;
opts.lineHeightScale = state.settings.getLineHeightScale();
opts.letterSpacing = state.settings.getLetterSpacing();
opts.cursorBlinkRate = state.settings.getCursorBlinkRate();
opts.reduceMotion = state.settings.getReduceMotion();
opts.nowSeconds = nowSeconds;
if (state.ui.layoutPreset == LayoutPreset::Emacs) opts.annotationLayout = 1;
else if (state.ui.layoutPreset == LayoutPreset::JetBrains) opts.annotationLayout = 2;
else opts.annotationLayout = 0;
@@ -186,6 +196,8 @@ static void renderEditorPanel(EditorState& state) {
opts.annotations = &annoMarkers;
opts.suggestions = &suggestionMarkers;
opts.conflicts = &conflictMarkers;
opts.searchPulseLine = state.search.pulseLine;
opts.searchPulseStart = state.search.pulseStart;
if (buf->bufferMode == BufferManager::BufferMode::Structured) {
opts.syncScrollX = &buf->splitScrollX;
@@ -232,6 +244,9 @@ static void renderEditorPanel(EditorState& state) {
genOpts.showCurrentLine = false;
genOpts.lineHeightScale = state.settings.getLineHeightScale();
genOpts.letterSpacing = state.settings.getLetterSpacing();
genOpts.cursorBlinkRate = state.settings.getCursorBlinkRate();
genOpts.reduceMotion = state.settings.getReduceMotion();
genOpts.nowSeconds = nowSeconds;
genOpts.highlightLine = buf->generatedHighlightLine;
genOpts.syncScrollX = &buf->splitScrollX;
genOpts.syncScrollY = &buf->splitScrollY;
@@ -494,10 +509,20 @@ static void renderEditorPanel(EditorState& state) {
state.hoverPending = false;
}
std::string hover = state.lsp->getHoverContents();
if (!hover.empty()) {
static std::string lastHoverText;
bool hoverActive = !hover.empty();
if (hoverActive) lastHoverText = hover;
float alpha = AnimationUtils::tooltipAlpha("lsp_hover",
hoverActive,
nowSeconds,
state.settings.getReduceMotion());
if (alpha > 0.01f && !lastHoverText.empty()) {
ImGui::PushStyleVar(ImGuiStyleVar_Alpha,
ImGui::GetStyle().Alpha * alpha);
ImGui::BeginTooltip();
ImGui::TextUnformatted(hover.c_str());
ImGui::TextUnformatted(lastHoverText.c_str());
ImGui::EndTooltip();
ImGui::PopStyleVar();
}
}
@@ -529,16 +554,34 @@ static void renderEditorPanel(EditorState& state) {
if (res.hoverValid && (ImGui::GetIO().KeyCtrl || ImGui::GetIO().KeySuper)) {
std::string preview;
if (state.previewDefinitionAt(res.hoverLine, res.hoverCol, preview)) {
ImGui::BeginTooltip();
ImGui::TextUnformatted(preview.c_str());
ImGui::EndTooltip();
float alpha = AnimationUtils::tooltipAlpha("definition_preview",
true,
nowSeconds,
state.settings.getReduceMotion());
if (alpha > 0.01f) {
ImGui::PushStyleVar(ImGuiStyleVar_Alpha,
ImGui::GetStyle().Alpha * alpha);
ImGui::BeginTooltip();
ImGui::TextUnformatted(preview.c_str());
ImGui::EndTooltip();
ImGui::PopStyleVar();
}
} else if (state.definitionPreviewValid) {
std::string path = EditorState::fromFileUri(state.definitionPreview.uri);
std::string label = path + ":" +
std::to_string(state.definitionPreview.range.start.line + 1);
ImGui::BeginTooltip();
ImGui::TextUnformatted(label.c_str());
ImGui::EndTooltip();
float alpha = AnimationUtils::tooltipAlpha("definition_preview_fallback",
true,
nowSeconds,
state.settings.getReduceMotion());
if (alpha > 0.01f) {
ImGui::PushStyleVar(ImGuiStyleVar_Alpha,
ImGui::GetStyle().Alpha * alpha);
ImGui::BeginTooltip();
ImGui::TextUnformatted(label.c_str());
ImGui::EndTooltip();
ImGui::PopStyleVar();
}
}
}
@@ -709,6 +752,7 @@ static void renderEditorPanel(EditorState& state) {
ImGui::EndPopup();
}
ImGui::PopStyleVar();
ImGui::EndTabItem();
}
if (!open) {

View File

@@ -1,11 +1,27 @@
#pragma once
#include "../EditorState.h"
#include "../EditorUtils.h"
#include "../AnimationUtils.h"
static void renderFindReplaceBar(EditorState& state) {
if (!state.search.showFind) return;
ImGui::Begin("Find & Replace", &state.search.showFind, ImGuiWindowFlags_AlwaysAutoResize);
const double now = ImGui::GetTime();
float alpha = 1.0f;
float offset = 0.0f;
bool render = AnimationUtils::panelTransition("FindReplacePanel",
state.search.showFind,
now,
state.settings.getReduceMotion(),
0.14f,
12.0f,
alpha,
offset);
if (!render) return;
bool open = state.search.showFind;
ImGui::SetNextWindowBgAlpha(alpha);
ImGui::Begin("Find & Replace", &open, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * alpha);
ImGui::PushFont(state.uiFont);
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offset);
ImGui::SetNextItemWidth(300);
if (ImGui::InputText("Find", state.search.findBuf, sizeof(state.search.findBuf),
ImGuiInputTextFlags_EnterReturnsTrue)) {
@@ -19,13 +35,32 @@ static void renderFindReplaceBar(EditorState& state) {
ImGui::SameLine();
if (ImGui::Button("Replace All")) state.doReplaceAll();
ImGui::PopFont();
ImGui::PopStyleVar();
ImGui::End();
if (!open) {
state.search.showFind = false;
}
}
static void renderProjectSearchPanel(EditorState& state) {
if (!state.search.showProjectSearch) return;
ImGui::Begin("Search", &state.search.showProjectSearch);
const double now = ImGui::GetTime();
float alpha = 1.0f;
float offset = 0.0f;
bool render = AnimationUtils::panelTransition("ProjectSearchPanel",
state.search.showProjectSearch,
now,
state.settings.getReduceMotion(),
0.16f,
16.0f,
alpha,
offset);
if (!render) return;
bool open = state.search.showProjectSearch;
ImGui::SetNextWindowBgAlpha(alpha);
ImGui::Begin("Search", &open);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * alpha);
ImGui::PushFont(state.uiFont);
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offset);
bool doSearch = false;
ImGui::SetNextItemWidth(420);
if (ImGui::InputText("Query", state.search.searchQuery, sizeof(state.search.searchQuery),
@@ -76,5 +111,9 @@ static void renderProjectSearchPanel(EditorState& state) {
}
ImGui::EndChild();
ImGui::PopFont();
ImGui::PopStyleVar();
ImGui::End();
if (!open) {
state.search.showProjectSearch = false;
}
}

View File

@@ -2,6 +2,7 @@
#include "../EditorState.h"
#include "../EditorUtils.h"
#include "../ThemeEngine.h"
#include "../AnimationUtils.h"
#include <cstdlib>
#include <filesystem>
#include <vector>
@@ -67,9 +68,26 @@ static int findFontIndex(const std::vector<FontOption>& options, const std::stri
}
static void renderSettingsPanel(EditorState& state) {
if (!state.ui.showSettingsPanel) return;
ImGui::Begin("Settings", &state.ui.showSettingsPanel);
const double now = ImGui::GetTime();
const bool reduceMotion = state.settings.getReduceMotion();
float alpha = 1.0f;
float offset = 0.0f;
const bool render = AnimationUtils::panelTransition("SettingsPanel",
state.ui.showSettingsPanel,
now,
reduceMotion,
0.18f,
18.0f,
alpha,
offset);
if (!render) return;
bool open = state.ui.showSettingsPanel;
ImGui::SetNextWindowBgAlpha(alpha);
ImGui::Begin("Settings", &open);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * alpha);
ImGui::PushFont(state.uiFont);
float startX = ImGui::GetCursorPosX();
ImGui::SetCursorPosX(startX + offset);
bool settingsChanged = false;
bool emacsConfigChanged = false;
bool themeChanged = false;
@@ -132,6 +150,11 @@ static void renderSettingsPanel(EditorState& state) {
state.settings.setLetterSpacing(letterSpacing);
settingsChanged = true;
}
float blinkRate = state.settings.getCursorBlinkRate();
if (ImGui::SliderFloat("Cursor Blink Rate (Hz)", &blinkRate, 0.0f, 4.0f, "%.1f")) {
state.settings.setCursorBlinkRate(blinkRate);
settingsChanged = true;
}
int tabSize = state.settings.getTabSize();
const int tabSizes[] = {2, 4, 8};
@@ -321,6 +344,11 @@ static void renderSettingsPanel(EditorState& state) {
state.ui.showLineNumbers = showLineNumbers;
settingsChanged = true;
}
bool reduceMotionSetting = state.settings.getReduceMotion();
if (ImGui::Checkbox("Reduce Motion", &reduceMotionSetting)) {
state.settings.setReduceMotion(reduceMotionSetting);
settingsChanged = true;
}
LayoutPreset preset = state.ui.layoutPreset;
int presetIndex = 0;
@@ -386,5 +414,9 @@ static void renderSettingsPanel(EditorState& state) {
state.events.publish(UIEventType::ThemeChanged, {}, {}, ImGui::GetTime());
}
ImGui::PopFont();
ImGui::PopStyleVar();
ImGui::End();
if (!open) {
state.ui.showSettingsPanel = false;
}
}