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 Center editor codebase With Claude
src/states/game_state.cpp | 49 +++++++++--- src/systems/wall_connection.cpp | 166 ++++++++++++++++++++++++++-------------- src/systems/wall_connection.hpp | 11 ++- src/wall/editor_wall.cpp | 54 ++++++++----- src/wall/editor_wall.hpp | 7 +- 5 files changed, 194 insertions(+), 93 deletions(-) diff --git a/src/states/game_state.cpp b/src/states/game_state.cpp index 51308fd..d7d86e8 100644 --- a/src/states/game_state.cpp +++ b/src/states/game_state.cpp @@ -1177,25 +1177,52 @@ GameState::editor_PlaceLines(const DynArray<std::string>& lines) const double x_wall = -static_cast<double>(ws) + 0.05; Wall::Transform tf = Wall::get_Transform(Wall::Role::Editor, ws); - double y = static_cast<double>(ws) - 2.0; - const double z_start = static_cast<double>(ws) - 1.0; + const double lh = ConfigWall::line_height; + const double gap = 0.5; + const double floor_y = World::get_FloorY<double>(); + + size_t n_lines = 0; + for (const std::string& l : lines) + if (l.find_first_not_of(' ') != std::string::npos) n_lines++; + if (n_lines == 0) return; + + const double bottom_y = floor_y + 1.2; + double y = bottom_y + static_cast<double>(n_lines - 1) * lh; for (const std::string& line : lines) { - double z = z_start; + if (line.find_first_not_of(' ') == std::string::npos) + continue; + + struct Tok { Text3dPtr t; double half; }; + DynArray<Tok> toks; + double total_w = 0.0; std::istringstream in(line); std::string tk; - while (in >> tk) { Text3dPtr t = make_Text3d(tk, Text3d::depth_wall, 0.003f, - Vec3{ x_wall, y, z }, ConfigColor::wall); - t->yaw = tf.yaw; - Vec3 snapped = _editor_wall.on_WordPlaced(t.get(), Vec3{ x_wall, y, z }); - t->pos = snapped; - _ctx.add_WallText(Wall::Position::NegX, Wall::Role::Editor, std::move(t)); - z = snapped.z - 0.5; + Vec3{ x_wall, y, 0.0 }, ConfigColor::wall); + t->yaw = tf.yaw; + TokenColor::apply(t.get()); + const double half = static_cast<double>(t->half_extent); + total_w += 2.0 * half; + toks.push_back({ std::move(t), half }); + } + if (toks.empty()) { y -= lh; continue; } + total_w += gap * static_cast<double>(toks.size() - 1); + + DynArray<Text3d*> placed; + double cursor = total_w * 0.5; + for (Tok& tok : toks) { + const double center_z = cursor - tok.half; + tok.t->pos = Vec3{ x_wall, y, center_z }; + placed.push_back(tok.t.get()); + _ctx.add_WallText(Wall::Position::NegX, Wall::Role::Editor, std::move(tok.t)); + cursor = center_z - tok.half - gap; } - y -= ConfigWall::line_height; + _editor_wall.add_PlacedLine(placed, y); + + y -= lh; } } diff --git a/src/systems/wall_connection.cpp b/src/systems/wall_connection.cpp index f719601..fbf9732 100644 --- a/src/systems/wall_connection.cpp +++ b/src/systems/wall_connection.cpp @@ -1,6 +1,8 @@ #include <SDL3/SDL.h> #include <cstdlib> +#include <sstream> +#include "c_token/token_color.hpp" #include "entities/camera.hpp" #include "entities/world.hpp" #include "rendering/gl_renderer.hpp" @@ -45,7 +47,6 @@ WallConnection::end() { _target = Target::None; _prog_input.reset(); _prog_last.clear(); - _render. clear(); _linenums. clear(); _cmdline. reset(); _status. clear(); _blink = 0.0; } @@ -123,7 +124,7 @@ WallConnection::lines() const void WallConnection::rebuild_Editor() { - _render.clear(); + _rows.clear(); _linenums.clear(); const float ws = World::get_BoxScale(); @@ -139,60 +140,124 @@ WallConnection::rebuild_Editor() const auto& lines = _vim.lines(); - // Pass 1: build renderables in a local left-aligned frame, find block width. - struct Row { Text3dPtr num; Text3dPtr txt; double txt_w; }; - DynArray<Row> rows; - rows.reserve(lines.size()); + // ── Pass 1: build line-number labels (orange), measure gutter width. ── + struct NumRow { Text3dPtr num; }; + DynArray<NumRow> nums; + nums.reserve(lines.size()); double gutter_w = 0.0; for (size_t i = 0; i < lines.size(); ++i) { Text3dPtr num = make_Text3d(std::to_string(i + 1), Text3d::depth_wall, 0.0022f, - tf.position, ConfigColor::editor); + tf.position, ConfigColor::orange); num->yaw = tf.yaw; + num->set_Color(ConfigColor::orange); // digit would token-lookup to red; force orange gutter_w = SDL_max(gutter_w, 2.0 * static_cast<double>(num->half_extent)); - Row r; r.num = std::move(num); r.txt_w = 0.0; rows.push_back(std::move(r)); + nums.push_back({ std::move(num) }); } const double gutter_gap = 0.6; const double code_u0 = gutter_w + gutter_gap; - double block_w = code_u0; + // ── Pass 2: build per-token meshes for each line; measure block width. ── + // We need a uniform char width per row so the block-relative cursor lands + // correctly. Measure each line by rendering one hidden full-line mesh just + // to get its total width; tokens are then laid out within that width. + struct BuiltRow { + DynArray<Text3dPtr> toks; // colored token meshes + DynArray<double> tok_half; // half-extent per token + double line_w { 0.0 }; // full line render width (incl. spaces) + double char_w { 0.18 }; + double he_y { 0.2 }; + size_t n_chars { 0 }; + }; + DynArray<BuiltRow> built; + built.reserve(lines.size()); + + double block_code_w = 0.0; for (size_t i = 0; i < lines.size(); ++i) { - const bool drawable = lines[i].find_first_not_of(' ') != std::string::npos; + BuiltRow br; + const std::string& s = lines[i]; + br.n_chars = s.size(); + + // Measure the full line (spaces included) for a faithful char width. + const bool drawable = s.find_first_not_of(' ') != std::string::npos; if (drawable) { - Text3dPtr t = make_Text3d(lines[i], Text3d::depth_wall, 0.003f, tf.position, ConfigColor::wall); - t->yaw = tf.yaw; - rows[i].txt_w = 2.0 * static_cast<double>(t->half_extent); - rows[i].txt = std::move(t); + Text3dPtr meas = make_Text3d(s, Text3d::depth_wall, 0.003f, tf.position, ConfigColor::white); + br.line_w = 2.0 * static_cast<double>(meas->half_extent); + br.he_y = static_cast<double>(meas->half_extent_y); + br.char_w = (s.empty() ? 0.18 : br.line_w / static_cast<double>(s.size())); + // meas is discarded — measurement only. + + // Build per-token colored meshes. + std::istringstream in(s); + std::string tk; + while (in >> tk) { + Text3dPtr t = make_Text3d(tk, Text3d::depth_wall, 0.003f, tf.position, ConfigColor::white); + t->yaw = tf.yaw; + TokenColor::apply(t.get()); + br.tok_half.push_back(static_cast<double>(t->half_extent)); + br.toks.push_back(std::move(t)); + } } - block_w = SDL_max(block_w, code_u0 + rows[i].txt_w); + block_code_w = SDL_max(block_code_w, br.line_w); + built.push_back(std::move(br)); } - // Pass 2: center the whole block on the wall (z = block_w/2 → -block_w/2). + const double block_w = code_u0 + block_code_w; const double z_block_left = block_w * 0.5; + _code_left_z = z_block_left - code_u0; // left edge of the CODE area (char 0 starts here) - for (size_t i = 0; i < rows.size(); ++i) { + // ── Pass 3: place numbers + tokens, fill _rows for the cursor. ── + for (size_t i = 0; i < lines.size(); ++i) { const double row_y = _row0_y - static_cast<double>(i) * lh; - if (rows[i].num) { - const double he = static_cast<double>(rows[i].num->half_extent); - rows[i].num->pos = tf.position; - rows[i].num->pos.y = row_y; - rows[i].num->pos.z = z_block_left - he; - _linenums.push_back(std::move(rows[i].num)); - } - if (rows[i].txt) { - const double he = static_cast<double>(rows[i].txt->half_extent); - rows[i].txt->pos = tf.position; - rows[i].txt->pos.y = row_y; - rows[i].txt->pos.z = z_block_left - code_u0 - he; - _render.push_back(std::move(rows[i].txt)); + // line number in the gutter (right-edge of number at z_block_left) + if (nums[i].num) { + const double he = static_cast<double>(nums[i].num->half_extent); + nums[i].num->pos = tf.position; + nums[i].num->pos.y = row_y; + nums[i].num->pos.z = z_block_left - he; + _linenums.push_back(std::move(nums[i].num)); } - else { - _render.push_back(Text3dPtr{}); + + EditorRow er; + er.left_z = _code_left_z; + er.char_w = built[i].char_w; + er.he_y = built[i].he_y; + er.line_x = tf.position.x; + er.has_text = !built[i].toks.empty(); + + // Lay tokens left→right from the code-left edge, advancing by token + // width + one space-width so columns stay aligned with char_w. + const std::string& s = lines[i]; + double cursor_z = _code_left_z; // left edge of next token + size_t char_pos = 0; // running character column + std::istringstream in(s); + std::string tk; + size_t ti = 0; + // Walk the string to honor leading/internal spaces between tokens. + { + size_t pos = 0; + while (ti < built[i].toks.size()) { + // advance over spaces + while (pos < s.size() && s[pos] == ' ') { cursor_z -= built[i].char_w; ++pos; ++char_pos; } + // place token ti at cursor_z (left edge), center it + const double half = built[i].tok_half[ti]; + Text3dPtr& t = built[i].toks[ti]; + t->pos = tf.position; + t->pos.y = row_y; + t->pos.z = cursor_z - half; // center = left edge − half + // advance cursor by the token's character count + size_t tok_len = 0; + while (pos < s.size() && s[pos] != ' ') { ++pos; ++tok_len; ++char_pos; } + cursor_z -= built[i].char_w * static_cast<double>(tok_len); + er.toks.push_back(std::move(t)); + ++ti; + } } + + _rows.push_back(std::move(er)); } - _code_left_z = z_block_left - code_u0; rebuild_Cmdline(); } @@ -231,11 +296,11 @@ WallConnection::render(GlRenderer& gl, const Camera& cam, const Light& light) co draw_Electric (gl, cam); } else if (_target == Target::Editor) { - for (const Text3dPtr& t : _render) if (t) t->draw(gl, cam, light); + for (const EditorRow& er : _rows) + for (const Text3dPtr& t : er.toks) + if (t) t->draw(gl, cam, light); for (const Text3dPtr& n : _linenums) if (n) n->draw(gl, cam, light); - if (_cmdline) _cmdline->draw(gl, cam, light); - draw_EditorCursor(gl, cam, light); draw_Electric (gl, cam); } @@ -270,31 +335,18 @@ WallConnection::draw_EditorCursor(GlRenderer& gl, const Camera& cam, const Light if (SDL_fmod(_blink, 0.8) > 0.4) return; const int row = _vim.row(); - if (row < 0 || row >= (int)_vim.lines().size()) return; + if (row < 0 || row >= (int)_rows.size()) return; - const float ws = World::get_BoxScale(); - Wall::Transform tf = Wall::get_Transform(Wall::Role::Editor, ws); + Wall::Transform tf = Wall::get_Transform(Wall::Role::Editor, World::get_BoxScale()); const double lh = ConfigWall::line_height; + const double row_y = _row0_y - static_cast<double>(row) * lh; - const std::string& s = _vim.lines()[(size_t)row]; - const double row_y = _row0_y - static_cast<double>(row) * lh; - - double cw, line_x = tf.position.x, he_y = 0.2; - if (row < (int)_render.size() && _render[(size_t)row]) { - const Text3dPtr& line = _render[(size_t)row]; - const int N = s.empty() ? 1 : (int)s.size(); - cw = (2.0 * static_cast<double>(line->half_extent)) / static_cast<double>(N); - line_x = line->pos.x; - he_y = static_cast<double>(line->half_extent_y); - } - else { - cw = 0.18; - } - - const int col = _vim.col(); - const double cz = _code_left_z - (static_cast<double>(col) + 0.5) * cw; + const EditorRow& er = _rows[(size_t)row]; + const double cw = er.char_w; + const int col = _vim.col(); + const double cz = er.left_z - (static_cast<double>(col) + 0.5) * cw; - Vec3 c { line_x, row_y - (he_y + 0.06), cz }; + Vec3 c { er.line_x, row_y - (er.he_y + 0.06), cz }; Mat4 view = buildViewMat4(cam); Mat4 model = buildModelMat4(c, tf.yaw); diff --git a/src/systems/wall_connection.hpp b/src/systems/wall_connection.hpp index 7a1b6d4..df20388 100644 --- a/src/systems/wall_connection.hpp +++ b/src/systems/wall_connection.hpp @@ -12,6 +12,15 @@ class Camera; class GlRenderer; struct Light; +struct EditorRow { + DynArray<Text3dPtr> toks; // per-token meshes, left→right (decreasing z) + double left_z { 0.0 }; // z of the row's left edge (start of char 0) + double char_w { 0.18 }; // mean character width in z (for cursor placement) + double he_y { 0.2 }; // glyph half-height (for cursor vertical offset) + double line_x { 0.0 }; // wall-plane x + bool has_text { false }; +}; + class WallConnection { public: @@ -55,7 +64,7 @@ private: VimBuffer _vim; - DynArray<Text3dPtr> _render; + DynArray<EditorRow> _rows; DynArray<Text3dPtr> _linenums; DynArray<std::string> _status; diff --git a/src/wall/editor_wall.cpp b/src/wall/editor_wall.cpp index 088c156..a6905ea 100644 --- a/src/wall/editor_wall.cpp +++ b/src/wall/editor_wall.cpp @@ -186,27 +186,6 @@ EditorWall::place_OnLine(Line& line, double new_half_z, double impact_z) } -Vec3 -EditorWall::on_WordPlaced(Text3d* word, const Vec3& impact_pos) -{ - if (!word) return impact_pos; - - Target t = resolve_Target(impact_pos.y); - Line& target = apply_Target(t); - - const double new_half_z = word->half_extent; - const Placement p = place_OnLine(target, new_half_z, impact_pos.z); - - Vec3 snapped { impact_pos.x, target.y, p.z }; - - target.words.insert( - target.words.begin() + static_cast<std::ptrdiff_t>(p.insert_index), - word); - - return snapped; -} - - void EditorWall::on_LinePlaced(const DynArray<Text3d*>& words_in, const Vec3& impact) { @@ -304,6 +283,39 @@ EditorWall::on_WordRemoved(Text3d* word) } +Vec3 +EditorWall::on_WordPlaced(Text3d* word, const Vec3& impact_pos) +{ + if (!word) return impact_pos; + + Target t = resolve_Target(impact_pos.y); + Line& target = apply_Target(t); + + const double new_half_z = word->half_extent; + const Placement p = place_OnLine(target, new_half_z, impact_pos.z); + + Vec3 snapped { impact_pos.x, target.y, p.z }; + + target.words.insert( + target.words.begin() + static_cast<std::ptrdiff_t>(p.insert_index), + word); + + return snapped; +} + + +void +EditorWall::add_PlacedLine(const DynArray<Text3d*>& words, double row_y) +{ + if (words.empty()) return; + Line line; + line.y = row_y; + for (Text3d* w : words) + if (w) line.words.push_back(w); + _lines.push_back(std::move(line)); +} + + // ───────────────────────────────────────────────────────────────────────────── // Line numbers // ───────────────────────────────────────────────────────────────────────────── diff --git a/src/wall/editor_wall.hpp b/src/wall/editor_wall.hpp index bb88ab1..e64baa1 100644 --- a/src/wall/editor_wall.hpp +++ b/src/wall/editor_wall.hpp @@ -58,17 +58,18 @@ public: void clear (); void update (double dt, const Vec3& eye_pos); void draw (GlRenderer& gl, const Camera& cam, const Light& light) const; - + void bounce (size_t line_id); void scroll(double delta) { for (Line& l : _lines) l.y += delta; } + void on_LinePlaced (const DynArray<Text3d*>& words, const Vec3& impact); void on_WordRemoved(Text3d* word); Vec3 on_WordPlaced (Text3d* word, const Vec3& impact_pos); + void add_PlacedLine(const DynArray<Text3d*>& words, double row_y); - size_t line_Count () const { return _lines.size(); } bool row_IsEmpty(double y) const; double snap_RowY (double y) const; double scroll_DeltaToEye(size_t line_id, double eye_y) const; - void bounce (size_t line_id); + size_t line_Count () const { return _lines.size(); } DynArray<NumberHit> number_Hitboxes() const;