Step 197: keyboard navigation audit

This commit is contained in:
Bill
2026-02-10 05:08:02 -07:00
parent 09fd7b011e
commit eb49e2a3a7
12 changed files with 183 additions and 2 deletions

View File

@@ -932,6 +932,38 @@ struct EditorState {
}
}
bool hasSidePanels() const {
if (ui.showOutline) return true;
if (library.showDependencyPanel) return true;
if (library.showLibraryBrowserPanel) return true;
if (library.showCompositionPanel) return true;
if (emacsState.showEmacsPackagesPanel) return true;
if (emacsState.showEmacsBridgePanel) return true;
return true; // Memory Strategies panel is always present.
}
void cyclePanelFocus() {
FocusRegion order[] = {
FocusRegion::Editor,
FocusRegion::Explorer,
FocusRegion::Side,
FocusRegion::Bottom
};
int current = 0;
for (int i = 0; i < 4; ++i) {
if (ui.focusedRegion == order[i]) {
current = i;
break;
}
}
for (int step = 1; step <= 4; ++step) {
FocusRegion next = order[(current + step) % 4];
if (next == FocusRegion::Side && !hasSidePanels()) continue;
ui.focusTarget = next;
return;
}
}
void applySettingsToState() {
ui.showMinimap = settings.getShowMinimap();
ui.showLineNumbers = settings.getShowLineNumbers();

View File

@@ -274,6 +274,8 @@ private:
if (name == "Separator") return ImGuiCol_Separator;
if (name == "SeparatorHovered") return ImGuiCol_SeparatorHovered;
if (name == "SeparatorActive") return ImGuiCol_SeparatorActive;
if (name == "NavHighlight") return ImGuiCol_NavHighlight;
if (name == "NavWindowingHighlight") return ImGuiCol_NavWindowingHighlight;
return -1;
}
@@ -345,6 +347,12 @@ private:
style.Colors[colId] = color;
}
}
if (theme.imguiColors.find(ImGuiCol_NavHighlight) == theme.imguiColors.end()) {
style.Colors[ImGuiCol_NavHighlight] = ImVec4(1.0f, 0.85f, 0.2f, 1.0f);
}
if (theme.imguiColors.find(ImGuiCol_NavWindowingHighlight) == theme.imguiColors.end()) {
style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.0f, 0.85f, 0.2f, 1.0f);
}
style.WindowPadding = theme.panelPadding;
style.ItemSpacing = theme.panelSpacing;
style.WindowBorderSize = theme.windowBorderSize;

View File

@@ -258,6 +258,14 @@ int main(int, char**) {
if (sdlMod & KMOD_SHIFT) mods |= WMOD_SHIFT;
if (sdlMod & KMOD_ALT) mods |= WMOD_ALT;
if (sym == SDLK_ESCAPE && mods == WMOD_NONE) {
state.ui.focusTarget = FocusRegion::Editor;
}
if (sym == SDLK_F6 && mods == WMOD_NONE) {
state.cyclePanelFocus();
}
if ((sdlMod & KMOD_CTRL) && (sdlMod & KMOD_SHIFT) && sym == SDLK_p) {
state.showCommandPalette = true;
state.commandQuery[0] = '\0';

View File

@@ -52,10 +52,17 @@ static std::vector<SimpleDiagnostic> collectDiagnostics(const EditorState& state
}
static void renderBottomPanel(EditorState& state) {
if (state.ui.focusTarget == FocusRegion::Bottom) {
ImGui::SetNextWindowFocus();
state.ui.focusTarget = FocusRegion::None;
}
// ---------------------------------------------------------------
// Bottom panel — Output / AST / Highlighted Preview / Terminal
// ---------------------------------------------------------------
ImGui::Begin("Panel");
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
state.ui.focusedRegion = FocusRegion::Bottom;
}
if (ImGui::BeginTabBar("PanelTabs")) {
// Output log

View File

@@ -9,6 +9,10 @@
#include <map>
static void renderEditorPanel(EditorState& state) {
if (state.ui.focusTarget == FocusRegion::Editor) {
ImGui::SetNextWindowFocus();
state.ui.focusTarget = FocusRegion::None;
}
if (state.ui.showAnnotations) {
queueFeatureHint(state.featureHints,
"hint.annotations",
@@ -18,6 +22,9 @@ static void renderEditorPanel(EditorState& state) {
// Editor (center) -- editable text area
// ---------------------------------------------------------------
ImGui::Begin("Editor");
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
state.ui.focusedRegion = FocusRegion::Editor;
}
ImGui::PushFont(state.uiFont);
ImGui::BeginChild("##breadcrumbs", ImVec2(0, 26.0f), false, ImGuiWindowFlags_NoScrollbar);
if (!state.active()) {

View File

@@ -3,7 +3,14 @@
#include "../EditorUtils.h"
static void renderExplorerPanel(EditorState& state) {
if (state.ui.focusTarget == FocusRegion::Explorer) {
ImGui::SetNextWindowFocus();
state.ui.focusTarget = FocusRegion::None;
}
ImGui::Begin("Explorer");
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
state.ui.focusedRegion = FocusRegion::Explorer;
}
ImGui::PushFont(state.uiFont);
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "OPEN EDITORS");
ImGui::Separator();

View File

@@ -4,7 +4,14 @@
static void renderOutlinePanel(EditorState& state) {
if (!state.ui.showOutline) return;
if (state.ui.focusTarget == FocusRegion::Side) {
ImGui::SetNextWindowFocus();
state.ui.focusTarget = FocusRegion::None;
}
ImGui::Begin("Outline", &state.ui.showOutline);
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
state.ui.focusedRegion = FocusRegion::Side;
}
queueFeatureHint(state.featureHints,
"hint.outline",
"Tip: Use the Outline to jump between symbols quickly.");
@@ -81,7 +88,14 @@ static void renderOutlinePanel(EditorState& state) {
static void renderDependenciesPanel(EditorState& state) {
if (!state.library.showDependencyPanel) return;
if (state.ui.focusTarget == FocusRegion::Side) {
ImGui::SetNextWindowFocus();
state.ui.focusTarget = FocusRegion::None;
}
ImGui::Begin("Dependencies", &state.library.showDependencyPanel);
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
state.ui.focusedRegion = FocusRegion::Side;
}
ImGui::PushFont(state.uiFont);
renderDependencyPanel(state.library.dependencyPanel,
state.workspaceRoot,
@@ -99,7 +113,14 @@ static void renderDependenciesPanel(EditorState& state) {
static void renderLibrariesPanel(EditorState& state) {
if (!state.library.showLibraryBrowserPanel) return;
if (state.ui.focusTarget == FocusRegion::Side) {
ImGui::SetNextWindowFocus();
state.ui.focusTarget = FocusRegion::None;
}
ImGui::Begin("Libraries", &state.library.showLibraryBrowserPanel);
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
state.ui.focusedRegion = FocusRegion::Side;
}
ImGui::PushFont(state.uiFont);
std::string insertText;
std::string insertLibrary;
@@ -118,7 +139,14 @@ static void renderLibrariesPanel(EditorState& state) {
static void renderCompositionPanel(EditorState& state) {
if (!state.library.showCompositionPanel) return;
if (state.ui.focusTarget == FocusRegion::Side) {
ImGui::SetNextWindowFocus();
state.ui.focusTarget = FocusRegion::None;
}
ImGui::Begin("Compose", &state.library.showCompositionPanel);
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
state.ui.focusedRegion = FocusRegion::Side;
}
ImGui::PushFont(state.uiFont);
std::string nodeId;
if (state.activeAST()) {
@@ -140,7 +168,14 @@ static void renderCompositionPanel(EditorState& state) {
static void renderEmacsPackagesPanel(EditorState& state) {
if (!state.emacsState.showEmacsPackagesPanel) return;
if (state.ui.focusTarget == FocusRegion::Side) {
ImGui::SetNextWindowFocus();
state.ui.focusTarget = FocusRegion::None;
}
ImGui::Begin("Emacs Packages", &state.emacsState.showEmacsPackagesPanel);
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
state.ui.focusedRegion = FocusRegion::Side;
}
ImGui::PushFont(state.uiFont);
if (renderEmacsPackageBrowser(state.emacsState.emacsPackages,
state.emacsState.emacs,
@@ -153,7 +188,14 @@ static void renderEmacsPackagesPanel(EditorState& state) {
static void renderEmacsBridgePanel(EditorState& state) {
if (!state.emacsState.showEmacsBridgePanel) return;
if (state.ui.focusTarget == FocusRegion::Side) {
ImGui::SetNextWindowFocus();
state.ui.focusTarget = FocusRegion::None;
}
ImGui::Begin("Emacs Bridge", &state.emacsState.showEmacsBridgePanel);
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
state.ui.focusedRegion = FocusRegion::Side;
}
ImGui::PushFont(state.uiFont);
if (state.active()) {
ImGui::Text("Active file: %s", state.active()->path.c_str());
@@ -182,7 +224,7 @@ static void renderMinibuffer(EditorState& state) {
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGuiWindowFlags mbFlags = ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNavFocus;
ImGuiWindowFlags_NoScrollbar;
float mbHeight = ImGui::GetFrameHeight() + 10;
ImVec2 mbPos(viewport->WorkPos.x,
viewport->WorkPos.y + viewport->WorkSize.y - mbHeight - 24.0f);
@@ -207,7 +249,14 @@ static void renderMinibuffer(EditorState& state) {
}
static void renderMemoryStrategiesPanel(EditorState& state) {
if (state.ui.focusTarget == FocusRegion::Side) {
ImGui::SetNextWindowFocus();
state.ui.focusTarget = FocusRegion::None;
}
ImGui::Begin("Memory Strategies");
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
state.ui.focusedRegion = FocusRegion::Side;
}
ImGui::PushFont(state.uiFont);
Module* ast = state.active() ? state.active()->sync.getAST() : nullptr;
if (state.active() && state.active()->bufferMode == BufferManager::BufferMode::Text) {

View File

@@ -1,6 +1,14 @@
#pragma once
#include "LayoutManager.h"
enum class FocusRegion {
None,
Editor,
Explorer,
Side,
Bottom
};
struct UIFlags {
bool showWhitespace = false;
bool showMinimap = false;
@@ -17,4 +25,6 @@ struct UIFlags {
bool showAgentWizard = false;
int bottomTab = 0; // 0=Output,1=AST,2=Highlighted
LayoutPreset layoutPreset = LayoutPreset::VSCode;
FocusRegion focusTarget = FocusRegion::None;
FocusRegion focusedRegion = FocusRegion::Editor;
};