mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-29 05:06:51 -06:00
This commit is contained in:
@@ -279,7 +279,13 @@ pub fn editor_widget(app: &mut SheetApp, ui: &mut egui::Ui, id: egui::Id, font:
|
||||
job.wrap.max_width = if multiline { wrap_width } else { f32::INFINITY };
|
||||
ui.fonts_mut(|f| f.layout_job(job))
|
||||
};
|
||||
let edit = if multiline || ed.text.contains('\n') { TextEdit::multiline(&mut ed.text) } else { TextEdit::singleline(&mut ed.text) };
|
||||
// The in-cell editor is as tall as its lines (a multiline field reserves four rows).
|
||||
let in_cell = id == egui::Id::new("gridcraft.cell_editor");
|
||||
let edit = if multiline || ed.text.contains('\n') {
|
||||
TextEdit::multiline(&mut ed.text).desired_rows(if in_cell { 1 } else { 4 })
|
||||
} else {
|
||||
TextEdit::singleline(&mut ed.text)
|
||||
};
|
||||
let mut output = edit
|
||||
.id(id)
|
||||
.frame(egui::Frame::NONE)
|
||||
|
||||
@@ -2734,10 +2734,19 @@ fn in_cell_editor(app: &mut SheetApp, ui: &mut egui::Ui, geo: &Geo, sh: &Sheet,
|
||||
let st = wb.styles.get(sh.style_id(cell)).clone();
|
||||
let size = st.font.size * 96.0 / 72.0 * geo.z;
|
||||
let font = FontId::new(size, theme::cell_family(&st.font.name, st.font.bold, st.font.italic));
|
||||
let text_w = ui.painter().layout_no_wrap(ed.text.clone(), font.clone(), Color32::BLACK).size().x;
|
||||
let lines = ed.text.lines().count().max(1) as f32 + if ed.text.ends_with('\n') { 1.0 } else { 0.0 };
|
||||
let w = (text_w + 14.0).max(rect.width()).min(geo.cells.right() - rect.left());
|
||||
let h = (size * 1.3 * lines + 4.0).max(rect.height());
|
||||
// Wrap Text: the editor keeps the column's width and wraps, growing down as Excel's does.
|
||||
// Otherwise it widens to the text's one-line width.
|
||||
let wrap = st.align.wrap;
|
||||
let (w, h) = if wrap {
|
||||
// The field wraps at the width it is given (the editor's less its 2 px inset a side): its
|
||||
// frame is `Frame::NONE`, so no text margin is taken off.
|
||||
let wrapped = ui.painter().layout(ed.text.clone(), font.clone(), Color32::BLACK, (rect.width() - 4.0).max(1.0));
|
||||
(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()))
|
||||
};
|
||||
// Grows to the left on a right-to-left sheet.
|
||||
let erect = geo.mirror_rect(Rect::from_min_size(rect.min, vec2(w, h)));
|
||||
let fill = match st.fill.pattern {
|
||||
@@ -2756,7 +2765,7 @@ fn in_cell_editor(app: &mut SheetApp, ui: &mut egui::Ui, geo: &Geo, sh: &Sheet,
|
||||
let id = egui::Id::new("gridcraft.cell_editor");
|
||||
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(erect.shrink2(vec2(2.0, 1.0))));
|
||||
child.set_clip_rect(clip);
|
||||
crate::formula_bar::editor_widget(app, &mut child, id, font, false, erect.width() - 4.0);
|
||||
crate::formula_bar::editor_widget(app, &mut child, id, font, wrap, erect.width() - 4.0);
|
||||
}
|
||||
|
||||
/// Test the popup itself, not whether the pointer is over any part of the app.
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
//! The in-cell editor of a Wrap Text cell keeps the column's width and wraps, growing down, as
|
||||
//! Excel's does; it used to widen to one long line.
|
||||
use egui::{Context, Id, RawInput, Rect, pos2, vec2};
|
||||
use gridcraft_engine::Session;
|
||||
use gridcraft_ui_egui::SheetApp;
|
||||
use serde_json::json;
|
||||
|
||||
const TEXT: &str = "Since the 1960s, NASA has left hardware across the solar system, on the Moon, on Mars, and in deep space.";
|
||||
|
||||
/// Runs a few frames of the app in a 1200 × 800 window.
|
||||
fn frames(ctx: &Context, app: &mut SheetApp) {
|
||||
for _ in 0..4 {
|
||||
let mut output =
|
||||
ctx.run_ui(RawInput { screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(1200.0, 800.0))), ..Default::default() }, |ui| {
|
||||
let ctx = ui.ctx().clone();
|
||||
app.logic(&ctx);
|
||||
app.ui(ui);
|
||||
});
|
||||
output.textures_delta.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// The editor's text field rect while A1 (narrow, wrap on or off) is edited with [`TEXT`].
|
||||
fn editor_rect(wrap: bool) -> Rect {
|
||||
let ctx = Context::default();
|
||||
let mut session = Session::new();
|
||||
session.new_workbook();
|
||||
session.execute("home.columnWidth", json!({"cols": "A:A", "chars": 12})).unwrap();
|
||||
session.execute("home.wrapText", json!({"range": "A1", "on": wrap})).unwrap();
|
||||
let mut app = SheetApp::new(session, Default::default());
|
||||
frames(&ctx, &mut app);
|
||||
app.begin_edit(Some(TEXT.into()), false);
|
||||
frames(&ctx, &mut app);
|
||||
ctx.read_response(Id::new("gridcraft.cell_editor")).expect("the in-cell editor is shown").rect
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_wrap_text_cell_edits_wrapped_at_the_column_width() {
|
||||
let wrapped = editor_rect(true);
|
||||
let one_line = editor_rect(false);
|
||||
assert!(one_line.width() > 400.0, "without Wrap Text the editor widens to the text: {one_line:?}");
|
||||
assert!(wrapped.width() < 120.0, "with Wrap Text the editor keeps the column's width: {wrapped:?}");
|
||||
assert!(wrapped.height() > 4.0 * one_line.height(), "and grows down to show every line: {wrapped:?} vs {one_line:?}");
|
||||
}
|
||||
|
||||
/// A short Wrap Text edit stays one line tall: the field doesn't reserve rows over the cells below.
|
||||
#[test]
|
||||
fn a_short_wrap_text_edit_stays_one_line_tall() {
|
||||
let ctx = Context::default();
|
||||
let mut session = Session::new();
|
||||
session.new_workbook();
|
||||
session.execute("home.wrapText", json!({"range": "A1", "on": true})).unwrap();
|
||||
let mut app = SheetApp::new(session, Default::default());
|
||||
frames(&ctx, &mut app);
|
||||
app.begin_edit(Some("short".into()), false);
|
||||
frames(&ctx, &mut app);
|
||||
let field = ctx.read_response(Id::new("gridcraft.cell_editor")).expect("the in-cell editor is shown").rect;
|
||||
assert!(field.height() < 30.0, "a one-line edit doesn't reach over the rows below: {field:?}");
|
||||
}
|
||||
|
||||
/// The editor is sized from the text wrapped where the field wraps it: the editor's width less its
|
||||
/// 2 px inset a side, with no text margin (the field has no frame). Measured narrower, the editor
|
||||
/// would count rows the field doesn't have and reach over the cells below.
|
||||
#[test]
|
||||
fn the_editor_measures_the_text_at_the_fields_wrap_width() {
|
||||
let ctx = Context::default();
|
||||
let mut session = Session::new();
|
||||
session.new_workbook();
|
||||
session.execute("home.columnWidth", json!({"cols": "A:A", "chars": 12})).unwrap();
|
||||
session.execute("home.wrapText", json!({"range": "A1", "on": true})).unwrap();
|
||||
let mut app = SheetApp::new(session, Default::default());
|
||||
frames(&ctx, &mut app);
|
||||
app.begin_edit(Some(TEXT.into()), false);
|
||||
frames(&ctx, &mut app);
|
||||
let field = ctx.read_response(Id::new("gridcraft.cell_editor")).expect("the in-cell editor is shown").rect;
|
||||
let sheet = app.session.active().unwrap().wb.active().unwrap().clone();
|
||||
let st = app.session.active().unwrap().wb.styles.get(sheet.style_id(gridcraft_engine::cell(0, 0))).clone();
|
||||
// The grid draws at its zoom: the cell is the field plus the 2 px inset a side, the font scales.
|
||||
let z = (field.width() + 4.0) / sheet.col_width(0);
|
||||
let font = egui::FontId::new(st.font.size * 96.0 / 72.0 * z, gridcraft_ui_egui::theme::cell_family(&st.font.name, st.font.bold, st.font.italic));
|
||||
let at =
|
||||
|w: f32| ctx.fonts_mut(|f| f.layout_job(egui::text::LayoutJob::simple(TEXT.into(), font.clone(), egui::Color32::PLACEHOLDER, w)).size().y);
|
||||
let (own, with_margin) = (at(field.width()), at(field.width() - 8.0));
|
||||
assert!(own != with_margin, "the test text must wrap differently 8 px narrower");
|
||||
assert!((field.height() - own).abs() < 1.0, "the field wraps at its whole width, no text margin: {} vs {own}", field.height());
|
||||
}
|
||||
|
||||
/// An edit with line breaks (Alt+Enter) in a cell without Wrap Text shows every line: the field's
|
||||
/// one row is a minimum, it grows with the text and never scrolls.
|
||||
#[test]
|
||||
fn an_unwrapped_multiline_edit_shows_every_line() {
|
||||
let field = |text: &str| {
|
||||
let ctx = Context::default();
|
||||
let mut session = Session::new();
|
||||
session.new_workbook();
|
||||
let mut app = SheetApp::new(session, Default::default());
|
||||
frames(&ctx, &mut app);
|
||||
app.begin_edit(Some(text.into()), false);
|
||||
frames(&ctx, &mut app);
|
||||
ctx.read_response(Id::new("gridcraft.cell_editor")).expect("the in-cell editor is shown").rect
|
||||
};
|
||||
let one = field("one").height();
|
||||
let three = field("one\ntwo\nthree").height();
|
||||
assert!(three > 2.5 * one, "three lines are shown: {three} vs one line {one}");
|
||||
assert!(three < 3.5 * one, "and no more rows are reserved: {three} vs one line {one}");
|
||||
}
|
||||
Reference in New Issue
Block a user