From f2e71ccebdc3978dd16730e3d80f10a4b23f1a0f Mon Sep 17 00:00:00 2001 From: Sam Sung <51686767+samwhosung@users.noreply.github.com> Date: Wed, 30 Sep 2026 18:37:30 +0700 Subject: [PATCH] A multi-line EditBox grows with its text; a Lua-made box draws its text (#729) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A multi-line EditBox kept its authored height, so a scroll frame around a long mail body or ticket had no range. A box made in Lua with no template never placed its text, so it drew nothing. In 1.12.1, the flush's relayout sets a multi-line box's height to its insets plus its wrapped text height, or one line when empty (`0x77d4d0` at `0x77d8ad`). Every resize, including the first layout, re-seats the text at the top-left inset (`0x77a8d0` β†’ `0x77b8c0`). `SetTextInsets`, `GetTextInsets`, `SetMultiLine` and a font change re-seat it too. benilla now does the same. A focused multi-line box's caret and text are clipped only by a scroll frame, as the reference clips neither. Tests cover growth and shrink, the stock mail and ticket scroll ranges, each re-seat trigger, a Lua box's first layout, and the caret in an empty box and after a trailing newline. Fixes #722 Fixes #726 πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) --- .../benilla-app/src/ui_script/extract/text.rs | 137 ++++- .../src/ui_script/help_frame_tests.rs | 50 ++ crates/benilla-app/src/ui_text/engine/mod.rs | 12 + crates/benilla-app/src/ui_text/measurer.rs | 49 ++ crates/benilla-ui/src/script/editbox.rs | 61 +-- .../benilla-ui/src/script/editbox/methods.rs | 22 +- .../benilla-ui/src/script/editbox/relayout.rs | 227 ++++++++ .../src/script/editbox/relayout_tests.rs | 484 ++++++++++++++++++ crates/benilla-ui/src/script/font_block.rs | 65 ++- .../src/script/messageframe/plain.rs | 1 + .../src/script/messageframe/scrolling.rs | 1 + crates/benilla-ui/src/script/mod.rs | 20 +- crates/benilla-ui/src/script/region/text.rs | 2 +- crates/benilla-ui/src/script/tick.rs | 8 +- crates/benilla-ui/src/script/types.rs | 11 +- crates/benilla-ui/src/widget/kinds/editbox.rs | 11 +- crates/benilla-ui/src/widget/mod.rs | 10 + 17 files changed, 1104 insertions(+), 67 deletions(-) create mode 100644 crates/benilla-ui/src/script/editbox/relayout.rs create mode 100644 crates/benilla-ui/src/script/editbox/relayout_tests.rs diff --git a/crates/benilla-app/src/ui_script/extract/text.rs b/crates/benilla-app/src/ui_script/extract/text.rs index acfbf53b..00d95d38 100644 --- a/crates/benilla-app/src/ui_script/extract/text.rs +++ b/crates/benilla-app/src/ui_script/extract/text.rs @@ -107,8 +107,13 @@ pub(super) fn emit( let (x0, top, cell_h) = crate::ui_text::line_origin(&mut atlas.lock(), drawn, host.rect, draw_justify, spec); ebox_geom = Some((x0, top, cell_h)); - text_clip = Some(host.clip.map_or(host.rect, |c| c.intersect(host.rect))); + // A single-line box shows only what fits its text region (`0x77d858` sets the window's + // substring), so the whole string drawn here is cut at the region. A multi-line region is + // only as tall as its text, and the reference clips neither it nor the caret texture + // (`E+0x368`), which after a trailing break stands a line below it: only a ScrollFrame's + // clip applies. if !ui.multi_line { + text_clip = Some(host.clip.map_or(host.rect, |c| c.intersect(host.rect))); draw_text = drawn; draw_rect = Rect::new(x0, host.rect.min.y, x0 + 100_000.0, host.rect.max.y); draw_justify = crate::ui_text::Justify { @@ -419,3 +424,133 @@ mod tests { assert_eq!(gaps, [1, 2].into_iter().collect()); } } + +/// The focused multi-line box's caret through the whole UI pass, on the client faces: the region +/// that draws its text is only as tall as that text, and the caret must not be cut to it. +#[cfg(test)] +mod caret_tests { + use bevy::prelude::*; + use bevy::window::PrimaryWindow; + + use benilla_ui::script::UiScript; + + use super::super::{paint_script, tick_script, UiQuad, UiQuads}; + use crate::portrait::PortraitImages; + use crate::ui_text::UiFontAtlas; + + /// A focused 270-wide multi-line box at 14 px on a 1024Γ—768 window (`s = 1`), holding `text`. + fn app_with_focused_box(text: &str) -> App { + let script = UiScript::new().unwrap(); + script + .run(&format!( + r#" + E = CreateFrame("EditBox", "E") + E:SetPoint("TOPLEFT", 100, -100) + E:SetWidth(270); E:SetHeight(200) + E:SetFont("Fonts\\FRIZQT__.TTF", 14) + E:SetMultiLine(true) + E:SetFocus() + E:SetText({text:?}) + "# + )) + .unwrap(); + let mut app = App::new(); + app.insert_non_send_resource(script); + app.insert_resource(UiFontAtlas::for_test(1.0).expect("the client faces open")); + app.init_resource::(); + app.init_resource::(); + app.init_resource::>(); + app.init_resource::(); + app.init_resource::(); + app.init_resource::(); + app.init_resource::(); + app.init_resource::(); + app.init_resource::(); + app.init_resource::