Merge PR #1905: Keep the in-cell editor at the cell's height for one line

crates/ui-egui/src/grid.rs: kept 1974's wrap branch; 1905's one-line height rule applied only in the non-wrap branch.
This commit is contained in:
aiongg
2026-10-22 02:34:53 +00:00
2 changed files with 90 additions and 1 deletions
+3 -1
View File
@@ -2745,7 +2745,9 @@ fn in_cell_editor(app: &mut SheetApp, ui: &mut egui::Ui, geo: &Geo, sh: &Sheet,
(rect.width(), (wrapped.size().y.max(size * 1.3 * lines) + 4.0).max(rect.height()))
} else {
let text_w = ui.painter().layout_no_wrap(ed.text.clone(), font.clone(), Color32::BLACK).size().x;
((text_w + 14.0).max(rect.width()).min(geo.cells.right() - rect.left()), (size * 1.3 * lines + 4.0).max(rect.height()))
// Excel keeps the cell's height for one line and only grows for extra lines.
let h = if lines > 1.0 { (size * 1.3 * lines + 4.0).max(rect.height()) } else { rect.height() };
((text_w + 14.0).max(rect.width()).min(geo.cells.right() - rect.left()), h)
};
// Grows to the left on a right-to-left sheet.
let erect = geo.mirror_rect(Rect::from_min_size(rect.min, vec2(w, h)));
@@ -0,0 +1,87 @@
//! The in-cell editor covers the edited cell exactly, as in Excel, at every zoom (#1755).
use egui::{Color32, Event, Modifiers, PointerButton, Rect, Shape};
use gridcraft_engine::Session;
use gridcraft_engine::core::CellRef;
use gridcraft_ui_egui::{SheetApp, grid::Geo};
use serde_json::json;
type Harness = egui_kittest::Harness<'static, SheetApp>;
fn harness() -> Harness {
let mut session = Session::new();
session.new_workbook();
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_ui_state(
|ui, app: &mut SheetApp| {
app.logic(ui.ctx());
app.ui(ui);
},
SheetApp::new(session, Default::default()),
);
h.run_steps(4);
h
}
fn cell_rect(h: &Harness, cell: &str) -> Rect {
let app = h.state();
let sheet = app.session.active().unwrap().wb.active().unwrap();
let geo = Geo::new(sheet, app.grid.rect.unwrap(), app.view().scroll);
geo.cell_rect(sheet, CellRef::parse(cell).unwrap())
}
fn click_cell(h: &mut Harness, cell: &str) {
let pos = cell_rect(h, cell).center();
h.input_mut().events.extend([
Event::PointerMoved(pos),
Event::PointerButton { pos, button: PointerButton::Primary, pressed: true, modifiers: Modifiers::NONE },
Event::PointerButton { pos, button: PointerButton::Primary, pressed: false, modifiers: Modifiers::NONE },
]);
h.run_steps(2);
}
fn type_text(h: &mut Harness, text: &str) {
h.input_mut().events.push(Event::Text(text.into()));
h.run_steps(3);
}
/// The white editor background painted from the cell's top-left corner.
fn editor_rect(h: &Harness, cell: Rect) -> Rect {
h.output()
.shapes
.iter()
.filter_map(|s| match &s.shape {
Shape::Rect(r) if r.fill == Color32::WHITE && (r.rect.min - cell.min).length() < 0.01 => Some(r.rect),
_ => None,
})
.next_back()
.expect("in-cell editor background")
}
fn assert_close(a: Rect, b: Rect, what: &str) {
assert!((a.min - b.min).length() < 0.01 && (a.max - b.max).length() < 0.01, "{what}: editor {a:?} must match cell {b:?}");
}
#[test]
fn editor_matches_the_cell_at_every_zoom() {
for zoom in [50u16, 100, 200, 400] {
let mut h = harness();
h.state_mut().run("view.zoom", json!({"percent": zoom})).unwrap();
h.run_steps(3);
click_cell(&mut h, "B2");
type_text(&mut h, "abc");
assert!(h.state().editor.is_some());
let cell = cell_rect(&h, "B2");
assert_close(editor_rect(&h, cell), cell, &format!("zoom {zoom}"));
}
}
#[test]
fn one_long_line_widens_the_editor_but_keeps_the_row_height() {
let mut h = harness();
click_cell(&mut h, "B2");
type_text(&mut h, "a much longer line of text than the cell can hold");
let cell = cell_rect(&h, "B2");
let ed = editor_rect(&h, cell);
assert!(ed.width() > cell.width() + 1.0, "long text widens the editor: {ed:?} vs {cell:?}");
assert!((ed.height() - cell.height()).abs() < 0.01, "one line keeps the row height: {ed:?} vs {cell:?}");
}