mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-24 10:46:50 -06:00
Merge PR #1331: Form dialogs open ready to type (Column Width, Row Height)
This commit is contained in:
@@ -36,6 +36,9 @@ pub struct Dialog {
|
||||
pub result: Option<Json>,
|
||||
/// "Show password" is ticked: password fields show their text.
|
||||
pub show_password: bool,
|
||||
/// The first text or number field still has to take keyboard focus (set when a form opens,
|
||||
/// used once): like Excel, Column Width opens ready to type, its value selected.
|
||||
focus_first_field: bool,
|
||||
picture_inbox: Option<crate::Inbox>,
|
||||
picture_target: Option<(u64, usize)>,
|
||||
/// The dialog this one was opened from (Name Manager › New…): it comes back when this closes.
|
||||
@@ -70,6 +73,7 @@ impl Dialog {
|
||||
list_index: 0,
|
||||
result: None,
|
||||
show_password: false,
|
||||
focus_first_field: true,
|
||||
picture_inbox: None,
|
||||
picture_target: None,
|
||||
back: None,
|
||||
@@ -88,6 +92,7 @@ impl Dialog {
|
||||
list_index: 0,
|
||||
result: None,
|
||||
show_password: false,
|
||||
focus_first_field: false,
|
||||
picture_inbox: None,
|
||||
picture_target: None,
|
||||
back: None,
|
||||
@@ -1387,6 +1392,28 @@ fn form(ui: &mut egui::Ui, d: &mut Dialog, confirm: &mut bool, open: &mut bool)
|
||||
ok_cancel(ui, confirm, open);
|
||||
}
|
||||
|
||||
/// Gives keyboard focus to the first text or number field the first time a form is drawn. A
|
||||
/// focused number field is in typing mode with its value selected, so Alt+H, O, W then 12 and
|
||||
/// Enter sets the width, as in Excel.
|
||||
/// Keeps asking until the field really has focus: a new window's first frame is an invisible
|
||||
/// sizing pass where a focus request is ignored.
|
||||
fn take_first_focus(d: &mut Dialog, r: &egui::Response) {
|
||||
if !d.focus_first_field {
|
||||
return;
|
||||
}
|
||||
if !r.has_focus() {
|
||||
r.request_focus();
|
||||
return;
|
||||
}
|
||||
// Focus has landed, and the field is now a text box: select its whole value so typing
|
||||
// replaces it (the field's own select-on-focus fired before it was a text box).
|
||||
let mut state = egui::text_edit::TextEditState::load(&r.ctx, r.id).unwrap_or_default();
|
||||
let all = egui::text::CCursorRange::two(egui::text::CCursor::new(0), egui::text::CCursor::new(usize::MAX / 2));
|
||||
state.cursor.set_char_range(Some(all));
|
||||
state.store(&r.ctx, r.id);
|
||||
d.focus_first_field = false;
|
||||
}
|
||||
|
||||
/// Draws a dialog's `fields`, editing its values.
|
||||
fn form_fields(ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
egui::Grid::new("form").num_columns(1).spacing(vec2(8.0, 6.0)).show(ui, |ui| {
|
||||
@@ -1406,6 +1433,7 @@ fn form_fields(ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
} else {
|
||||
ui.add(egui::TextEdit::singleline(&mut s).desired_width(320.0))
|
||||
};
|
||||
take_first_focus(d, &r);
|
||||
if r.changed() {
|
||||
d.values.insert(key.into(), json!(s));
|
||||
}
|
||||
@@ -1426,7 +1454,11 @@ fn form_fields(ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(label);
|
||||
let mut n = d.values.get(key).and_then(Json::as_f64).unwrap_or(0.0);
|
||||
if ui.add(egui::DragValue::new(&mut n).speed(0.5)).changed() {
|
||||
// Typed values count straight away: otherwise Enter or OK in the same frame
|
||||
// confirms the old value, because a number field only commits when it loses focus.
|
||||
let r = ui.add(egui::DragValue::new(&mut n).speed(0.5).update_while_editing(true));
|
||||
take_first_focus(d, &r);
|
||||
if r.changed() {
|
||||
d.values.insert(key.into(), json!(n));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
//! Form dialogs open ready to type, as in Excel: Alt+H, O, W then a number and Enter sets the
|
||||
//! column width without clicking into the field first.
|
||||
|
||||
use egui::{Event, Key, vec2};
|
||||
use gridcraft_engine::Session;
|
||||
use gridcraft_engine::core::CellRef;
|
||||
use gridcraft_ui_egui::SheetApp;
|
||||
use serde_json::json;
|
||||
|
||||
fn harness() -> egui_kittest::Harness<'static, SheetApp> {
|
||||
let mut session = Session::new();
|
||||
session.new_workbook();
|
||||
session.execute("selection.set", json!({"cell": "C3"})).unwrap();
|
||||
let mut h = egui_kittest::Harness::builder().with_size(vec2(1200.0, 800.0)).build_ui_state(
|
||||
|ui, app: &mut SheetApp| {
|
||||
app.logic(&ui.ctx().clone());
|
||||
app.ui(ui);
|
||||
},
|
||||
SheetApp::new(session, Default::default()),
|
||||
);
|
||||
h.run_steps(6);
|
||||
h
|
||||
}
|
||||
|
||||
fn type_and_enter(h: &mut egui_kittest::Harness<'static, SheetApp>, text: &str) {
|
||||
for c in text.chars() {
|
||||
h.input_mut().events.push(Event::Text(c.to_string()));
|
||||
h.run_steps(1);
|
||||
}
|
||||
h.key_press(Key::Enter);
|
||||
h.run_steps(6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_width_opens_with_the_width_ready_to_type() {
|
||||
let mut h = harness();
|
||||
h.state_mut().open_dialog("columnWidth", json!({}));
|
||||
h.run_steps(6);
|
||||
type_and_enter(&mut h, "12");
|
||||
assert!(h.state().dialog.is_none(), "Enter confirmed the dialog");
|
||||
let chars = {
|
||||
let d = h.state().session.active().unwrap();
|
||||
let sheet = d.wb.active().unwrap();
|
||||
gridcraft_engine::cmd::format::points_to_chars(sheet.col_width(CellRef::parse("C3").unwrap().col) as f64)
|
||||
};
|
||||
assert!((chars - 12.0).abs() < 0.2, "column C is 12 characters wide, got {chars}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row_height_opens_with_the_height_ready_to_type() {
|
||||
let mut h = harness();
|
||||
h.state_mut().open_dialog("rowHeight", json!({}));
|
||||
h.run_steps(6);
|
||||
type_and_enter(&mut h, "40");
|
||||
assert!(h.state().dialog.is_none(), "Enter confirmed the dialog");
|
||||
let height = {
|
||||
let d = h.state().session.active().unwrap();
|
||||
d.wb.active().unwrap().row_height(CellRef::parse("C3").unwrap().row)
|
||||
};
|
||||
assert!((height as f64 - 40.0).abs() < 0.6, "row 3 is 40 high, got {height}");
|
||||
}
|
||||
Reference in New Issue
Block a user