Add 'CenterOnCursor' command and improve cursor scrolling logic

- Introduced `CommandId::CenterOnCursor` to center viewport on the cursor line.
- Improved scrolling behavior in `ImGuiRenderer` to avoid aggressive centering and keep visible lines stable.
- Updated `make-app-release` to rename the output app to `kge-qt.app`.
- Adjusted padding in `ImGuiFrontend` to align with `ImGuiRenderer` settings for consistent scrolling.
- Bumped version to 1.4.1.
This commit is contained in:
2025-12-05 08:15:23 -08:00
parent 7069943df5
commit 952e1ed3f2
7 changed files with 68 additions and 13 deletions

View File

@@ -4,7 +4,7 @@ project(kte)
include(GNUInstallDirs)
set(CMAKE_CXX_STANDARD 20)
set(KTE_VERSION "1.4.0")
set(KTE_VERSION "1.4.1")
# Default to terminal-only build to avoid SDL/OpenGL dependency by default.
# Enable with -DBUILD_GUI=ON when SDL2/OpenGL/Freetype are available.

View File

@@ -109,6 +109,33 @@ ensure_cursor_visible(const Editor &ed, Buffer &buf)
}
static bool
cmd_center_on_cursor(CommandContext &ctx)
{
Buffer *buf = ctx.editor.CurrentBuffer();
if (!buf)
return false;
const auto &rows = buf->Rows();
std::size_t total = rows.size();
std::size_t content = ctx.editor.ContentRows();
if (content == 0)
content = 1;
std::size_t cy = buf->Cury();
std::size_t half = content / 2;
std::size_t new_rowoffs = (cy > half) ? (cy - half) : 0;
// Clamp to valid range
if (total > content) {
std::size_t max_rowoffs = total - content;
if (new_rowoffs > max_rowoffs)
new_rowoffs = max_rowoffs;
} else {
new_rowoffs = 0;
}
buf->SetOffsets(new_rowoffs, buf->Coloffs());
return true;
}
static void
ensure_at_least_one_line(Buffer &buf)
{
@@ -4445,6 +4472,11 @@ InstallDefaultCommands()
// Syntax highlighting (public commands)
CommandRegistry::Register({CommandId::Syntax, "syntax", "Syntax: on|off|reload", cmd_syntax, true});
CommandRegistry::Register({CommandId::SetOption, "set", "Set option: key=value", cmd_set_option, true});
// Viewport control
CommandRegistry::Register({
CommandId::CenterOnCursor, "center-on-cursor", "Center viewport on current line", cmd_center_on_cursor,
false, false
});
}

View File

@@ -106,6 +106,8 @@ enum class CommandId {
// Syntax highlighting
Syntax, // ":syntax on|off|reload"
SetOption, // generic ":set key=value" (v1: filetype=<lang>)
// Viewport control
CenterOnCursor, // center the viewport on the current cursor line (C-k k)
};

View File

@@ -272,10 +272,11 @@ GUIFrontend::Step(Editor &ed, bool &running)
float disp_w = io.DisplaySize.x > 0 ? io.DisplaySize.x : static_cast<float>(width_);
float disp_h = io.DisplaySize.y > 0 ? io.DisplaySize.y : static_cast<float>(height_);
// Account for the GUI window padding and the status bar height used in GUIRenderer
const ImGuiStyle &style = ImGui::GetStyle();
float pad_x = style.WindowPadding.x;
float pad_y = style.WindowPadding.y;
// Account for the GUI window padding and the status bar height used in ImGuiRenderer.
// ImGuiRenderer pushes WindowPadding = (6,6) every frame, so use the same constants here
// to avoid mismatches that would cause premature scrolling.
const float pad_x = 6.0f;
const float pad_y = 6.0f;
// Status bar reserves one frame height (with spacing) inside the window
float status_h = ImGui::GetFrameHeightWithSpacing();

View File

@@ -140,7 +140,8 @@ ImGuiRenderer::Draw(Editor &ed)
prev_buf_coloffs = buf_coloffs;
// Synchronize cursor and scrolling.
// Ensure the cursor is visible even on the first frame or when it didn't move.
// Ensure the cursor is visible, but avoid aggressive centering so that
// the same lines remain visible until the cursor actually goes off-screen.
{
// Compute visible row range using the child window height
float child_h = ImGui::GetWindowHeight();
@@ -151,15 +152,30 @@ ImGuiRenderer::Draw(Editor &ed)
long last_row = first_row + vis_rows - 1;
long cyr = static_cast<long>(cy);
if (cyr < first_row || cyr > last_row) {
float target = (static_cast<float>(cyr) - std::max(0L, vis_rows / 2)) * row_h;
if (cyr < first_row) {
// Scroll just enough to bring the cursor line to the top
float target = static_cast<float>(cyr) * row_h;
if (target < 0.f)
target = 0.f;
float max_y = ImGui::GetScrollMaxY();
if (max_y >= 0.f && target > max_y)
target = max_y;
ImGui::SetScrollY(target);
scroll_y = ImGui::GetScrollY();
first_row = static_cast<long>(scroll_y / row_h);
last_row = first_row + vis_rows - 1;
} else if (cyr > last_row) {
// Scroll just enough to bring the cursor line to the bottom
long new_first = cyr - vis_rows + 1;
if (new_first < 0)
new_first = 0;
float target = static_cast<float>(new_first) * row_h;
float max_y = ImGui::GetScrollMaxY();
if (target < 0.f)
target = 0.f;
if (max_y >= 0.f && target > max_y)
target = max_y;
ImGui::SetScrollY(target);
// refresh local variables
scroll_y = ImGui::GetScrollY();
first_row = static_cast<long>(scroll_y / row_h);
last_row = first_row + vis_rows - 1;

View File

@@ -42,6 +42,9 @@ KLookupKCommand(const int ascii_key, const bool ctrl, CommandId &out) -> bool
case 'a':
out = CommandId::MarkAllAndJumpEnd;
return true;
case 'k':
out = CommandId::CenterOnCursor; // C-k k center current line
return true;
case 'b':
out = CommandId::BufferSwitchStart;
return true;
@@ -215,4 +218,4 @@ KLookupEscCommand(const int ascii_key, CommandId &out) -> bool
break;
}
return false;
}
}

View File

@@ -20,9 +20,10 @@ cmake -S . -B cmake-build-release -DBUILD_GUI=ON -DCMAKE_BUILD_TYPE=Release -DEN
cd cmake-build-release-qt
make clean
rm -fr kge.app*
rm -fr kge.app* kge-qt.app*
make
zip -r kge.app.zip kge.app
sha256sum kge.app.zip
mv kge.app kge-qt.app
zip -r kge-qt.app.zip kge-qt.app
sha256sum kge-qt.app.zip
open .
cd ..