sdl_runtime_compiler git · main
SDL3 game for running and compiling code at runtime
C++ 72.3% C 26.2%git clone https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler.gitwget https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler/archive/sdl_runtime_compiler.tar.gzFix vim mode scroll out of scope And add vim meta commands bottom right With Claude
src/systems/wall_connection.cpp | 73 +++++++++++++++++++++++++++++++++-------- src/systems/wall_connection.hpp | 21 +++++++----- src/text/handler.cpp | 28 +++++++++++----- src/text/handler.hpp | 9 +++++ src/text/vim_buffer.hpp | 12 +++++++ 5 files changed, 114 insertions(+), 29 deletions(-) diff --git a/src/systems/wall_connection.cpp b/src/systems/wall_connection.cpp index 388badb..fa15678 100644 --- a/src/systems/wall_connection.cpp +++ b/src/systems/wall_connection.cpp @@ -35,7 +35,7 @@ Connection::begin_Editor(const Vec3& pult_pos, const Text::Handler& editor) { _target = Target::Editor; _pult_pos = pult_pos; - _editor = &editor; + _editor = &editor; _cmd_last.clear(); _cmdline.reset(); } @@ -84,22 +84,40 @@ Connection::rebuild_Cmdline(const std::string& text) _cmdline.reset(); if (text.empty()) return; - + _cmdline = Text::make_Token(text, Text::Token::depth_wall, 0.0016f, Vec3{0,0,0}, Config::Color::white); } +void +Connection::rebuild_ShowCmd(const std::string& text) +{ + _showcmd.reset(); + if (text.empty()) return; + _showcmd = Text::make_Token(text, Text::Token::depth_wall, 0.0016f, Vec3{0,0,0}, Config::Color::white); +} + + void Connection::update(double dt, const Camera& cam, float aspect, float fov_y) { if (!is_Active()) return; _blink += dt; - - + + if (_target == Target::Editor && _editor) { std::string ml = _editor->modeline(); if (ml != _cmd_last) { rebuild_Cmdline(ml); _cmd_last = ml; } + + // Bottom-right "showcmd": the pending operator/count (e.g. "2d", "c"). + std::string sc = _editor->vim_ShowCmd(); + if (sc != _showcmd_last) { rebuild_ShowCmd(sc); _showcmd_last = sc; } + + // Tell the scrolloff how many rows actually fit, so the first/last line + // stays in view when zoomed in. Distance to the editor wall is along x. + const double dist_wall = SDL_fabs(cam.pos.x - _editor->wall_X()); + _editor->set_ViewHalfHeight(dist_wall * static_cast<double>(SDL_tanf(fov_y * 0.5f))); } const Vec3 up = Math::cross(cam.right, cam.fwd); @@ -108,9 +126,9 @@ Connection::update(double dt, const Camera& cam, float aspect, float fov_y) const double half_h = dist * static_cast<double>(SDL_tanf(fov_y * 0.5f)); const double half_w = half_h * static_cast<double>(aspect); - - const double fx = 0.82; - const double fy = 0.78; + + const double fx = 0.82; + const double fy = 0.78; const double left = half_w * fx; const double down = half_h * fy; @@ -127,6 +145,18 @@ Connection::update(double dt, const Camera& cam, float aspect, float fov_y) _cmd_pos.y += (target.y - _cmd_pos.y) * k; _cmd_pos.z += (target.z - _cmd_pos.z) * k; + // Lower-RIGHT frustum corner for the showcmd (mirror of the cmdline corner). + const double right_off = half_w * 0.72; // a touch inboard so short text stays on-screen + Vec3 sc_target { + cam.pos.x + cam.fwd.x * dist + cam.right.x * right_off - up.x * down, + cam.pos.y + cam.fwd.y * dist + cam.right.y * right_off - up.y * down, + cam.pos.z + cam.fwd.z * dist + cam.right.z * right_off - up.z * down, + }; + if (!_showcmd_pos_init) { _showcmd_pos = sc_target; _showcmd_pos_init = true; } + _showcmd_pos.x += (sc_target.x - _showcmd_pos.x) * k; + _showcmd_pos.y += (sc_target.y - _showcmd_pos.y) * k; + _showcmd_pos.z += (sc_target.z - _showcmd_pos.z) * k; + _cmd_yaw = static_cast<float>(SDL_atan2(-cam.fwd.x, -cam.fwd.z)); } @@ -141,8 +171,8 @@ Connection::render(GlRenderer& gl, const Camera& cam, const Light& light) const draw_Electric (gl, cam); } else if (_target == Target::Editor) { - - + + if (_cmdline) { const double hw = static_cast<double>(_cmdline->half_extent); _cmdline->pos = Vec3{ @@ -159,6 +189,23 @@ Connection::render(GlRenderer& gl, const Camera& cam, const Light& light) const draw_EditorCursor(gl, cam, light); draw_Electric (gl, cam); + + // Bottom-right showcmd: right-align by anchoring its right edge at the + // corner (offset left by its half-width), mirroring the cmdline's left + // anchor on the opposite side. + if (_showcmd) { + const double hw = static_cast<double>(_showcmd->half_extent); + _showcmd->pos = Vec3{ + _showcmd_pos.x - cam.right.x * hw, + _showcmd_pos.y - cam.right.y * hw, + _showcmd_pos.z - cam.right.z * hw, + }; + _showcmd->yaw = _cmd_yaw; + _showcmd->pitch = 0.0f; + + gl.clear_Depth(); + _showcmd->draw(gl, cam, light); + } } } @@ -194,7 +241,7 @@ Connection::draw_EditorCursor(GlRenderer& gl, const Camera& cam, const Light& li if (SDL_fmod(_blink, 0.8) > 0.4) return; const Text::Handler::EditingMetrics m = _editor->editing_Metrics(); - if (m.char_w <= 0.0) return; + if (m.char_w <= 0.0) return; const int row = _editor->cursor_Row(); const int col = _editor->cursor_Col(); @@ -205,8 +252,8 @@ Connection::draw_EditorCursor(GlRenderer& gl, const Camera& cam, const Light& li Vec3 c { tf.position.x, row_y - (m.he_y + 0.06), cz }; - - + + const Vec3 ref { tf.position.x, row_y, cz }; const float pitch = Wall::tilt_TwardEye(ref, cam.pos); @@ -260,6 +307,6 @@ Connection::draw_Electric(GlRenderer& gl, const Camera& cam) const } -} +} } // namespace Nimbin diff --git a/src/systems/wall_connection.hpp b/src/systems/wall_connection.hpp index f84e4da..8ecf833 100644 --- a/src/systems/wall_connection.hpp +++ b/src/systems/wall_connection.hpp @@ -4,6 +4,7 @@ #include "text/token.hpp" + namespace Nimbin { @@ -37,25 +38,29 @@ public: private: - std::string _cmd_last; - std::string _prog_last; - - Vec3 _cmd_pos {}; - Vec3 _pult_pos{}; + bool _cmd_pos_init { false }; + bool _showcmd_pos_init { false }; + float _cmd_yaw { 0.0f }; + double _blink { 0.0 }; Target _target { Target::None }; const Text::Handler* _editor { nullptr }; Text::TokenPtr _cmdline; + Text::TokenPtr _showcmd; Text::TokenPtr _prog_input; - float _cmd_yaw { 0.0f }; - double _blink { 0.0 }; + Vec3 _cmd_pos {}; + Vec3 _showcmd_pos {}; + Vec3 _pult_pos {}; - bool _cmd_pos_init { false }; + std::string _cmd_last; + std::string _showcmd_last; + std::string _prog_last; void rebuild_Cmdline (const std::string& text); + void rebuild_ShowCmd (const std::string& text); void rebuild_ProgramPreview(const std::string& buffer); void draw_EditorCursor (GlRenderer& gl, const Camera& cam, const Light& light) const; void draw_ProgramCursor(GlRenderer& gl, const Camera& cam, const Light& light) const; diff --git a/src/text/handler.cpp b/src/text/handler.cpp index 2efbeb5..50536be 100644 --- a/src/text/handler.cpp +++ b/src/text/handler.cpp @@ -808,18 +808,23 @@ Handler::relayout_Editing() const double anchor_bottom = bottom_y + static_cast<double>(N - 1) * lh; if (_capturing) { - // vim-style scrolloff. MARGIN lines are kept above & below the cursor, so the - // cursor stays centred and the text scrolls on every step. PAD leaves a few - // blank rows of overscroll at the file ends so the first/last line sits just - // inside the view instead of flush against (and clipped by) the screen edge. - constexpr int MARGIN = 4; // lines above & below the cursor (lower if rows clip) - constexpr int PAD = 1; // blank rows kept above the first / below the last line - const int visible = 2 * MARGIN + 1; + // vim-style scrolloff, zoom-aware: rows_half = how many rows fit between + // the eye and the top edge at the current zoom; MARGIN can't exceed it, so + // the first/last line stays in view (gg no longer loses line 1 off the top + // when zoomed in). 0 → unknown → fall back to the cap. + constexpr int MARGIN_MAX = 4; // cap when zoomed out (vim scrolloff feel) + constexpr int PAD = 1; // blank rows kept at the file ends + const int rows_half = (_view_half_h > 0.0) + ? static_cast<int>(SDL_floor(_view_half_h / lh)) + : MARGIN_MAX; + const int MARGIN = SDL_clamp(rows_half - PAD, 1, MARGIN_MAX); + const int visible = 2 * MARGIN + 1; _scroll_top = SDL_clamp(R - MARGIN, -PAD, SDL_max(0, N - visible) + PAD); _row0_y = (_eye_y + static_cast<double>(MARGIN) * lh) + static_cast<double>(_scroll_top) * lh; - } else { + } + else { _row0_y = anchor_bottom; // at rest: whole file bottom-aligned to the floor } @@ -1419,6 +1424,13 @@ Handler::modeline() const } +std::string +Handler::vim_ShowCmd() const +{ + return _vim ? _vim->get_ShowCmd() : std::string{}; +} + + void Handler::set_Style(SDL_FColor color, float depth, float scale) { diff --git a/src/text/handler.hpp b/src/text/handler.hpp index 1d3798e..0d0c201 100644 --- a/src/text/handler.hpp +++ b/src/text/handler.hpp @@ -118,10 +118,18 @@ public: void place_CursorInsert(const CursorHit& hit); // apply after begin_Edit + scroll in const std::string& cmdline_Text() const; std::string modeline () const; + std::string vim_ShowCmd () const; VimBuffer::Mode cursor_Mode () const; const DynArray<std::string>& status_Lines() const; + // Frustum half-height (world units) visible at the editor wall, so the Editing + // scrolloff knows how many rows actually fit and can keep the first/last line + // in view when zoomed in. Set each frame by the Connection (const view-state + // cache); 0 means "unknown" -> fall back to the default margin. + void set_ViewHalfHeight(double h) const { _view_half_h = h; } + double wall_X() const { return _transform.position.x; } + // ── facecam label ─────────────────────────────────────────────────── void set_Text (const std::string& text); void set_Text (const std::string& text, SDL_FColor color, float depth, float scale); @@ -179,6 +187,7 @@ private: double _code_left_z{ 0.0 }; double _row0_y { 0.0 }; double _eye_y { 0.0 }; + mutable double _view_half_h { 0.0 }; // frustum half-height visible at the wall (set by Connection) int _scroll_top { 0 }; // index of the row stuck to the top of the window cpputils::unique_ptr<VimBuffer> _vim; diff --git a/src/text/vim_buffer.hpp b/src/text/vim_buffer.hpp index 81e2ff9..73339cb 100644 --- a/src/text/vim_buffer.hpp +++ b/src/text/vim_buffer.hpp @@ -8,6 +8,7 @@ namespace Nimbin { + class VimBuffer { public: enum class Mode : uint8_t { Normal, Insert, Command }; @@ -50,6 +51,17 @@ public: get_Cmdline () const { return _cmd; } const std::string& get_Status () const { return _status; } + + // Pending operator + count for the bottom-right "showcmd" area, e.g. "d", + // "2d", "c", "g", or a bare count "3". Empties as soon as the command + // completes (both _pending and _count are cleared on dispatch). + std::string get_ShowCmd() const + { + std::string s; + if (_count > 0) s += std::to_string(_count); + s += _pending; + return s; + } void start_Insert() { _mode = Mode::Insert; _col = 0; touch(); } void enter_Insert() { _mode = Mode::Insert; clamp_Col(); touch(); } // insert at current cursor