Merge pull request #228 from webdevtodayjason/fix/200-formula-helpers
Keep formula helpers inside the worksheet when scrolling (#200) # Conflicts: # contributors/people.toml # docs/ui-parity.md
This commit is contained in:
commit
ea0e971e27
|
|
@ -38,3 +38,7 @@ sync_github_name = true
|
|||
|
||||
[people.alexanderalber]
|
||||
real_name = "Alexander Alber"
|
||||
|
||||
[people.webdevtodayjason]
|
||||
display_name = "Jason Brashear"
|
||||
sync_github_name = false
|
||||
|
|
|
|||
|
|
@ -340,9 +340,11 @@ pub fn editor_widget(app: &mut SheetApp, ui: &mut egui::Ui, id: egui::Id, font:
|
|||
ui.painter().galley(pos, g, Color32::WHITE);
|
||||
}
|
||||
// Autocomplete list and argument hint under the editor.
|
||||
if mine && (!ed.autocomplete.is_empty() || ed.current_function().is_some()) {
|
||||
if mine && ui.is_rect_visible(resp.rect) && (!ed.autocomplete.is_empty() || ed.current_function().is_some()) {
|
||||
let below = resp.rect.left_bottom() + vec2(0.0, 4.0);
|
||||
egui::Area::new(id.with("ac")).fixed_pos(below).order(egui::Order::Tooltip).show(ui.ctx(), |ui| {
|
||||
// Areas have their own clip rect: keep cell helpers inside the worksheet.
|
||||
let bounds = if ed.from_formula_bar { ui.ctx().content_rect() } else { ui.clip_rect() };
|
||||
egui::Area::new(id.with("ac")).fixed_pos(below).constrain_to(bounds).order(egui::Order::Tooltip).show(ui.ctx(), |ui| {
|
||||
egui::Frame::popup(ui.style()).inner_margin(4.0).show(ui, |ui| {
|
||||
if !ed.autocomplete.is_empty() {
|
||||
let mut pick = None;
|
||||
|
|
|
|||
|
|
@ -1847,10 +1847,18 @@ fn in_cell_editor(app: &mut SheetApp, ui: &mut egui::Ui, geo: &Geo, sh: &Sheet,
|
|||
PatternType::Solid => st.fill.fg.resolve(&wb.theme).map(theme::color32).unwrap_or(Color32::WHITE),
|
||||
_ => Color32::WHITE,
|
||||
};
|
||||
ui.painter().rect_filled(erect, 0.0, fill);
|
||||
ui.painter().rect_stroke(erect.expand(1.0), 0.0, Stroke::new(2.0, Tokens::get(ui.ctx()).sel_border), StrokeKind::Middle);
|
||||
let split = pos2(geo.cells.left() + geo.frozen_w, geo.cells.top() + geo.frozen_h);
|
||||
let quadrant = Rect::from_min_max(
|
||||
pos2(if cell.col >= geo.fc { split.x } else { geo.cells.left() }, if cell.row >= geo.fr { split.y } else { geo.cells.top() }),
|
||||
pos2(if cell.col >= geo.fc { geo.cells.right() } else { split.x }, if cell.row >= geo.fr { geo.cells.bottom() } else { split.y }),
|
||||
);
|
||||
let clip = ui.clip_rect().intersect(quadrant);
|
||||
let painter = ui.painter().with_clip_rect(clip);
|
||||
painter.rect_filled(erect, 0.0, fill);
|
||||
painter.rect_stroke(erect.expand(1.0), 0.0, Stroke::new(2.0, Tokens::get(ui.ctx()).sel_border), StrokeKind::Middle);
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
//! Formula helpers must stay in the worksheet while its editor scrolls.
|
||||
use egui::{Context, Id, LayerId, Order, RawInput, Rect, pos2, vec2};
|
||||
use gridcraft_engine::Session;
|
||||
use gridcraft_ui_egui::SheetApp;
|
||||
|
||||
fn frame(ctx: &Context, app: &mut SheetApp) -> egui::FullOutput {
|
||||
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();
|
||||
output
|
||||
}
|
||||
|
||||
fn frames(ctx: &Context, app: &mut SheetApp) {
|
||||
for _ in 0..4 {
|
||||
frame(ctx, app);
|
||||
}
|
||||
}
|
||||
|
||||
fn visible(ctx: &Context, editor: &str) -> bool {
|
||||
ctx.memory(|m| m.areas().is_visible(&LayerId::new(Order::Tooltip, Id::new(editor).with("ac"))))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cell_formula_helpers_hide_when_scrolled_out_and_return_with_the_editor() {
|
||||
for text in ["=SU", "=SUM("] {
|
||||
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);
|
||||
if text == "=SU" {
|
||||
app.editor.as_mut().unwrap().update_autocomplete(&["SUM".into(), "SUMIF".into()]);
|
||||
}
|
||||
frames(&ctx, &mut app);
|
||||
assert!(visible(&ctx, "gridcraft.cell_editor"), "visible cell shows {text} help");
|
||||
app.view_mut().unwrap().scroll.y = 400.0;
|
||||
frames(&ctx, &mut app);
|
||||
assert!(!visible(&ctx, "gridcraft.cell_editor"), "scrolled-out cell must hide {text} help");
|
||||
assert_eq!(app.editor.as_ref().unwrap().text, text, "scrolling preserves the edit");
|
||||
app.view_mut().unwrap().scroll.y = 0.0;
|
||||
frames(&ctx, &mut app);
|
||||
assert!(visible(&ctx, "gridcraft.cell_editor"), "scrolling back restores {text} help");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formula_bar_hint_remains_visible_when_the_worksheet_scrolls() {
|
||||
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("=SUM(".into()), true);
|
||||
frames(&ctx, &mut app);
|
||||
assert!(visible(&ctx, "gridcraft.formula_bar"));
|
||||
app.view_mut().unwrap().scroll.y = 400.0;
|
||||
frames(&ctx, &mut app);
|
||||
assert!(visible(&ctx, "gridcraft.formula_bar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cell_argument_hint_is_clipped_to_the_worksheet() {
|
||||
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("=SUM(".into()), false);
|
||||
frames(&ctx, &mut app);
|
||||
app.view_mut().unwrap().scroll.y = 10.0;
|
||||
frames(&ctx, &mut app);
|
||||
let output = frame(&ctx, &mut app);
|
||||
let cells = app.grid.cells_rect.unwrap();
|
||||
let hint = output.shapes.iter().find(|s| matches!(&s.shape, egui::Shape::Text(t) if t.galley.text() == "SUM(")).expect("argument hint painted");
|
||||
assert!(cells.contains_rect(hint.clip_rect), "helper must not paint over the headers: {:?}", hint.clip_rect);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scrolled_out_cell_editor_still_handles_escape() {
|
||||
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("=SUM(".into()), false);
|
||||
frames(&ctx, &mut app);
|
||||
app.view_mut().unwrap().scroll.y = 400.0;
|
||||
frames(&ctx, &mut app);
|
||||
let mut output = ctx.run_ui(
|
||||
RawInput {
|
||||
screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(1200.0, 800.0))),
|
||||
events: vec![egui::Event::Key {
|
||||
key: egui::Key::Escape,
|
||||
physical_key: None,
|
||||
pressed: true,
|
||||
repeat: false,
|
||||
modifiers: egui::Modifiers::NONE,
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
|ui| app.ui(ui),
|
||||
);
|
||||
output.textures_delta.clear();
|
||||
assert!(app.editor.is_none(), "scrolling must not disable editor keys");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cell_editor_and_helper_stay_below_the_frozen_top_row() {
|
||||
let ctx = Context::default();
|
||||
let mut session = Session::new();
|
||||
session.new_workbook();
|
||||
session.run("view.freezeTopRow", serde_json::json!({})).unwrap();
|
||||
session.run("selection.set", serde_json::json!({"range": "B5"})).unwrap();
|
||||
let mut app = SheetApp::new(session, Default::default());
|
||||
frames(&ctx, &mut app);
|
||||
let sh = app.session.active().unwrap().wb.active().unwrap();
|
||||
let frozen_h = sh.row_height(0);
|
||||
let scroll = sh.row_top(4) as f32;
|
||||
app.begin_edit(Some("=SUM(".into()), false);
|
||||
frames(&ctx, &mut app);
|
||||
assert!(visible(&ctx, "gridcraft.cell_editor"));
|
||||
app.view_mut().unwrap().scroll.y = scroll - frozen_h / 2.0;
|
||||
frames(&ctx, &mut app);
|
||||
let output = frame(&ctx, &mut app);
|
||||
let frozen_bottom = app.grid.cells_rect.unwrap().top() + frozen_h * gridcraft_ui_egui::grid::DISPLAY_SCALE;
|
||||
let editor = output
|
||||
.shapes
|
||||
.iter()
|
||||
.find(|s| s.clip_rect.bottom() > frozen_bottom && matches!(&s.shape, egui::Shape::Text(t) if t.galley.text() == "=SUM("))
|
||||
.expect("editor painted");
|
||||
assert!(editor.clip_rect.top() >= frozen_bottom, "editor must be clipped below the frozen row: {:?}", editor.clip_rect);
|
||||
app.view_mut().unwrap().scroll.y = scroll;
|
||||
frames(&ctx, &mut app);
|
||||
assert!(!visible(&ctx, "gridcraft.cell_editor"), "cell hidden under the frozen row must hide its helper");
|
||||
assert_eq!(app.editor.as_ref().unwrap().text, "=SUM(");
|
||||
app.view_mut().unwrap().scroll.y = 0.0;
|
||||
frames(&ctx, &mut app);
|
||||
assert!(visible(&ctx, "gridcraft.cell_editor"), "scrolling back restores the helper");
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ Sources: `crates/ui-egui/src` (ribbon, grid, editor, formula bar, dialogs, panes
|
|||
| Drag-move and drag-copy cells (border drag, Ctrl/Option, Shift to insert) | done (M3) |
|
||||
| In-cell editing, Enter/Tab movement, Alt/Option+Enter line breaks | done |
|
||||
| Point mode (click/drag/arrow references), reference colouring, F4 | done |
|
||||
| AutoComplete in columns, function autocomplete and argument tips | done |
|
||||
| AutoComplete in columns, function autocomplete and argument tips | done; cell helpers clip to the worksheet and hide when the editor scrolls out (#200) |
|
||||
| Header resize and double-click AutoFit | done (#42, #88 for header dividers) |
|
||||
| Freeze panes, split panes | freeze done; split to verify |
|
||||
| Copy as HTML table for other apps | done (#36, 10-10) |
|
||||
|
|
@ -93,5 +93,6 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12.
|
|||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-11 | minor | Delete and Backspace now remove the selected chart, picture or shape (#213) |
|
||||
| 2026-10-10 | minor | Keep formula helpers inside the worksheet when scrolling (#200) |
|
||||
| 2026-10-10 | minor | Chart resize bug (#190) |
|
||||
| 2026-10-10 | major | Created: chrome, grid, keyboard, dialogs, accessibility; key tips, Page Layout view and accessibility tree flagged |
|
||||
|
|
|
|||
Loading…
Reference in New Issue