diff --git a/crates/engine/src/cmd/options.rs b/crates/engine/src/cmd/options.rs
--- a/crates/engine/src/cmd/options.rs
+++ b/crates/engine/src/cmd/options.rs
@@ -27,6 +27,7 @@ pub const PROFILE_VARS: &[&str] = &[
     "DYNMODE",
     "DYNPIFORMAT",
     "DYNPICOORDS",
+    "DYNPIVIS",
     "DYNPROMPT",
     "DYNDIGRIP",
     "DYNDIVIS",
@@ -107,9 +108,13 @@ pub fn apply(s: &mut Session, values: &Value) -> Result<()> {
 /// skipped (an old or hand-edited profile never stops the app from starting).
 pub fn restore(s: &mut Session, saved: &Value) {
     let Some(obj) = saved.as_object() else { return };
+    // Profiles saved before DYNMODE took its bits (no DYNPIVIS yet) stored 1 for "on": both inputs.
+    let old_dynmode = !obj.contains_key("DYNPIVIS");
+    let both = json!(3);
     for (name, v) in obj.iter().take(MAX_ENTRIES) {
         if is_profile_var(name) {
             let before = s.settings.clone();
+            let v = if old_dynmode && name.eq_ignore_ascii_case("DYNMODE") && v.as_i64() == Some(1) { &both } else { v };
             if crate::sysvars::set(s, name, v).is_err() {
                 s.settings = before;
             }
@@ -132,6 +137,8 @@ pub fn reset(s: &mut Session) {
     st.gridmode = d.gridmode;
     st.gridmajor = d.gridmajor;
     st.dynmode = d.dynmode;
+    st.dyninput = d.dyninput;
+    st.dynpivis = d.dynpivis;
     st.dynpi_cartesian = d.dynpi_cartesian;
     st.dynpi_absolute = d.dynpi_absolute;
     st.osnaphatch = d.osnaphatch;
@@ -191,4 +198,41 @@ mod tests {
         restore(&mut b, &saved);
         assert_eq!(profile(&b), saved);
     }
+
+    #[test]
+    fn dynmode_keeps_pointer_and_dimension_input_apart() {
+        let mut s = Session::new();
+        assert_eq!(crate::sysvars::get(&s, "DYNMODE"), Some(json!(3)));
+        apply(&mut s, &json!({"DYNMODE": 2})).unwrap();
+        assert!(!s.settings.dyn_pointer() && s.settings.dyn_dimension());
+        // F12 turns it off and back on to the same inputs; DYNMODE reads negative meanwhile.
+        s.execute("dynmode", &Value::Null).unwrap();
+        assert_eq!(crate::sysvars::get(&s, "DYNMODE"), Some(json!(-2)));
+        assert!(!s.settings.dyn_dimension());
+        s.execute("dynmode", &Value::Null).unwrap();
+        assert_eq!(crate::sysvars::get(&s, "DYNMODE"), Some(json!(2)));
+        // Both off: F12 turns both on.
+        apply(&mut s, &json!({"DYNMODE": 0})).unwrap();
+        s.execute("dynmode", &Value::Null).unwrap();
+        assert_eq!(crate::sysvars::get(&s, "DYNMODE"), Some(json!(3)));
+        // `false`/`true` switch it like F12.
+        apply(&mut s, &json!({"DYNMODE": 1})).unwrap();
+        apply(&mut s, &json!({"DYNMODE": false})).unwrap();
+        assert_eq!(crate::sysvars::get(&s, "DYNMODE"), Some(json!(-1)));
+        apply(&mut s, &json!({"DYNMODE": true})).unwrap();
+        assert_eq!(crate::sysvars::get(&s, "DYNMODE"), Some(json!(1)));
+        assert!(apply(&mut s, &json!({"DYNMODE": 4})).is_err());
+        assert!(apply(&mut s, &json!({"DYNPIVIS": 3})).is_err());
+    }
+
+    #[test]
+    fn a_profile_saved_before_dynmode_bits_keeps_both_inputs() {
+        // Saved when DYNMODE was on/off: 1 meant on.
+        let mut s = Session::new();
+        restore(&mut s, &json!({"DYNMODE": 1}));
+        assert_eq!(crate::sysvars::get(&s, "DYNMODE"), Some(json!(3)));
+        // Saved with DYNPIVIS: 1 is pointer input only.
+        restore(&mut s, &json!({"DYNMODE": 1, "DYNPIVIS": 1}));
+        assert_eq!(crate::sysvars::get(&s, "DYNMODE"), Some(json!(1)));
+    }
 }
diff --git a/crates/engine/src/cmd/settings.rs b/crates/engine/src/cmd/settings.rs
--- a/crates/engine/src/cmd/settings.rs
+++ b/crates/engine/src/cmd/settings.rs
@@ -47,12 +47,19 @@ pub fn specs() -> Vec<CommandSpec> {
             .enabled(always)
             .noundo()
             .transparent(),
-        CommandSpec::new("dynmode", "Dynamic Input", |s, p| toggle(s, p, |st| &mut st.dynmode, "Dynamic Input"))
-            .key("F12")
-            .params("{on?: bool}")
-            .enabled(always)
-            .noundo()
-            .transparent(),
+        CommandSpec::new("dynmode", "Dynamic Input", |s, p| {
+            let r = toggle(s, p, |st| &mut st.dynmode, "Dynamic Input");
+            // DYNMODE 0 (both inputs off): F12 turns both back on.
+            if s.settings.dynmode && s.settings.dyninput == 0 {
+                s.settings.dyninput = 3;
+            }
+            r
+        })
+        .key("F12")
+        .params("{on?: bool}")
+        .enabled(always)
+        .noundo()
+        .transparent(),
         CommandSpec::new("lwdisplay", "Show/Hide Lineweight", |s, p| toggle(s, p, |st| &mut st.lwdisplay, "Lineweight"))
             .params("{on?: bool}")
             .enabled(always)
diff --git a/crates/engine/src/grip_dims.rs b/crates/engine/src/grip_dims.rs
--- a/crates/engine/src/grip_dims.rs
+++ b/crates/engine/src/grip_dims.rs
@@ -178,10 +178,10 @@ impl Locus {
 }
 
 /// The dimensions to show for the grip the current prompt stretches, in cycle order (Tab), as
-/// DYNDIVIS and DYNDIGRIP ask; empty when no single grip is being stretched or Dynamic Input
+/// DYNDIVIS and DYNDIGRIP ask; empty when no single grip is being stretched or dimension input
 /// is off.
 pub fn grip_dims(s: &Session) -> Vec<GripDim> {
-    if !s.settings.dynmode {
+    if !s.settings.dyn_dimension() {
         return Vec::new();
     }
     let Some((h, i)) = s.current_prompt().and_then(|p| p.grip) else { return Vec::new() };
diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs
--- a/crates/engine/src/lib.rs
+++ b/crates/engine/src/lib.rs
@@ -364,7 +364,14 @@ pub struct Settings {
     pub measureinit: bool,
     /// -VIEW sEttings > Layer snapshot: named views save the layer settings with them.
     pub view_layer_snapshot: bool,
+    /// Dynamic Input on (F12, the sign of DYNMODE).
     pub dynmode: bool,
+    /// What Dynamic Input shows (DYNMODE's magnitude, bits): 1 pointer input, 2 dimension input;
+    /// 3 (default) both.
+    pub dyninput: u8,
+    /// DYNPIVIS: when pointer input shows: 0 once coordinates are typed, 1 (default) when a command
+    /// asks for a point, 2 always, even with no command running.
+    pub dynpivis: u8,
     /// DYNDIGRIP: which dimensions grip dimensional input shows while a grip is stretched (bits:
     /// 1 resulting length, 2 length change, 4 absolute angle, 8 angle change, 16 radius); read
     /// when DYNDIVIS = 2.
@@ -483,6 +490,8 @@ impl Default for Settings {
             measureinit: false,
             view_layer_snapshot: true,
             dynmode: true,
+            dyninput: 3,
+            dynpivis: 1,
             dyndigrip: 31,
             dyndivis: 1,
             dynpi_cartesian: false,
@@ -532,6 +541,19 @@ impl Default for Settings {
     }
 }
 
+impl Settings {
+    /// Dynamic Input shows pointer input: X,Y boxes for a first point, and the boxes for next
+    /// points in the DYNPIFORMAT/DYNPICOORDS format.
+    pub fn dyn_pointer(&self) -> bool {
+        self.dynmode && self.dyninput & 1 != 0
+    }
+    /// Dynamic Input shows dimension input: distance and angle from the last point, grip
+    /// dimensions (DYNDIGRIP, DYNDIVIS) and a distance prompt's length field.
+    pub fn dyn_dimension(&self) -> bool {
+        self.dynmode && self.dyninput & 2 != 0
+    }
+}
+
 /// An interactive command in progress.
 pub struct Running {
     pub id: String,
diff --git a/crates/engine/src/sysvars.rs b/crates/engine/src/sysvars.rs
--- a/crates/engine/src/sysvars.rs
+++ b/crates/engine/src/sysvars.rs
@@ -33,6 +33,7 @@ const SESSION_VARS: &[&str] = &[
     "DYNMODE",
     "DYNPIFORMAT",
     "DYNPICOORDS",
+    "DYNPIVIS",
     "DYNPROMPT",
     "LWDISPLAY",
     "PLOTTRANSPARENCYOVERRIDE",
@@ -102,7 +103,12 @@ pub fn get(s: &Session, name: &str) -> Option<Value> {
         "GRIDMAJOR" => json!(st.gridmajor),
         "DYNDIGRIP" => json!(st.dyndigrip),
         "DYNDIVIS" => json!(st.dyndivis),
-        "DYNMODE" => json!(i32::from(st.dynmode)),
+        // 1 pointer input, 2 dimension input, 3 both; negative while F12 has turned it off.
+        "DYNMODE" => {
+            let d = i32::from(st.dyninput);
+            json!(if st.dynmode { d } else { -d })
+        }
+        "DYNPIVIS" => json!(st.dynpivis),
         "DYNPIFORMAT" => json!(i32::from(st.dynpi_cartesian)),
         "DYNPICOORDS" => json!(i32::from(st.dynpi_absolute)),
         "DYNPROMPT" => json!(i32::from(st.dynprompt)),
@@ -313,7 +319,19 @@ pub fn set(s: &mut Session, name: &str, v: &Value) -> Result<()> {
         "GRIDMAJOR" => st.gridmajor = as_f64(v).ok_or_else(bad)?.clamp(1.0, 100.0) as u32,
         "DYNDIGRIP" => st.dyndigrip = as_i(v).filter(|i| (0..=31).contains(i)).and_then(|i| u32::try_from(i).ok()).ok_or_else(bad)?,
         "DYNDIVIS" => st.dyndivis = as_i(v).filter(|i| (0..=2).contains(i)).and_then(|i| u8::try_from(i).ok()).ok_or_else(bad)?,
-        "DYNMODE" => st.dynmode = as_bool(v).ok_or_else(bad)?,
+        // `true`/`false` turn Dynamic Input on and off as F12 does, keeping what it shows.
+        "DYNMODE" if v.is_boolean() => {
+            st.dynmode = v.as_bool() == Some(true);
+            if st.dynmode && st.dyninput == 0 {
+                st.dyninput = 3;
+            }
+        }
+        "DYNMODE" => {
+            let i = as_i(v).filter(|i| (-3..=3).contains(i)).ok_or_else(bad)?;
+            st.dynmode = i > 0;
+            st.dyninput = u8::try_from(i.unsigned_abs()).map_err(|_| bad())?;
+        }
+        "DYNPIVIS" => st.dynpivis = small(v, 2).ok_or_else(bad)?,
         "DYNPIFORMAT" => st.dynpi_cartesian = as_bool(v).ok_or_else(bad)?,
         "DYNPICOORDS" => st.dynpi_absolute = as_bool(v).ok_or_else(bad)?,
         "DYNPROMPT" => st.dynprompt = as_bool(v).ok_or_else(bad)?,
diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs
--- a/crates/ui-egui/src/canvas.rs
+++ b/crates/ui-egui/src/canvas.rs
@@ -2349,12 +2349,33 @@ fn dynamic_input(app: &mut CadApp, p: &egui::Painter, hover: Pos2, fields: bool)
             hover
         }
     };
-    let Some(prompt) = app.session.current_prompt() else { return };
+    let st = &app.session.settings;
+    // Live values beside the cursor: pointer input unless DYNPIVIS 0 waits for typing, and
+    // dimension input's distance and angle from the last point.
+    let (pointer_values, dimension_values) = (st.dyn_pointer() && st.dynpivis != 0, st.dyn_dimension());
+    let Some(prompt) = app.session.current_prompt() else {
+        // DYNPIVIS 2: the cursor's coordinates even with no command running.
+        if pointer_values
+            && st.dynpivis == 2
+            && app.cmd.buffer.is_empty()
+            && let Some(c) = app.canvas.cursor
+        {
+            let galley = p.layout_no_wrap(
+                format!("{}, {}", distance_text(&app.session, c.x), distance_text(&app.session, c.y)),
+                crate::theme::small(),
+                Tokens::get().text,
+            );
+            let r = tooltip_frame(p, at, galley.size() + vec2(12.0, 8.0));
+            p.galley(r.min + vec2(6.0, 4.0), galley, Tokens::get().text);
+        }
+        return;
+    };
     // DYNPROMPT = 0: no prompt beside the cursor (the value boxes stay).
-    let show_prompt = app.session.settings.dynprompt;
+    let show_prompt = st.dynprompt;
     let options = show_prompt && !prompt.keywords.is_empty();
     if !fields
         && let Some(f) = crate::cmdline::dyn_frame(app)
+        && crate::cmdline::dyn_values_shown(app, &f)
         && let Some(e) = crate::dyninput::parse(&app.cmd.buffer, &f)
         && let Some(c) = app.canvas.cursor
     {
@@ -2371,14 +2392,14 @@ fn dynamic_input(app: &mut CadApp, p: &egui::Painter, hover: Pos2, fields: bool)
         // The field shows the distance and what is typed.
     } else if !app.cmd.buffer.is_empty() {
         text = format!("{}: {}", crate::i18n::t(&prompt.message), app.cmd.buffer);
-    } else if let (Some(base), Some(c)) = (prompt.base, app.canvas.cursor) {
+    } else if let (Some(base), Some(c), true) = (prompt.base, app.canvas.cursor, pointer_values || dimension_values) {
         text = format!(
             "{}   {}  <  {}°",
             crate::i18n::t(&prompt.message),
             distance_text(&app.session, base.dist(c)),
             (base.angle_to(c).to_degrees() * 10.0).round() / 10.0
         );
-    } else if let Some(c) = app.canvas.cursor {
+    } else if let (Some(c), true) = (app.canvas.cursor, pointer_values) {
         text = format!("{}   {}, {}", crate::i18n::t(&prompt.message), distance_text(&app.session, c.x), distance_text(&app.session, c.y));
     }
     let t = Tokens::get();
diff --git a/crates/ui-egui/src/cmdline.rs b/crates/ui-egui/src/cmdline.rs
--- a/crates/ui-egui/src/cmdline.rs
+++ b/crates/ui-egui/src/cmdline.rs
@@ -40,14 +40,27 @@ pub const MAX_LINES: usize = 12;
 const LINE_H: f32 = 16.0;
 
 /// The Dynamic Input frame for the prompt on screen, or `None` when the value boxes are not live
-/// (Dynamic Input off, no point prompt or a pending selection window).
+/// (Dynamic Input off, no point prompt or a pending selection window). A first point takes pointer
+/// input; next points take pointer input or dimension input, which is distance and angle from
+/// the last point whatever DYNPIFORMAT and DYNPICOORDS say.
 pub fn dyn_frame(app: &CadApp) -> Option<crate::dyninput::Frame> {
     let st = &app.session.settings;
-    if !st.dynmode || app.session.pending_window.is_some() {
+    if app.session.pending_window.is_some() {
         return None;
     }
     let p = app.session.current_prompt().filter(cadcraft_engine::pointmod::accepts)?;
-    Some(crate::dyninput::Frame { base: p.base, cartesian: st.dynpi_cartesian, absolute: st.dynpi_absolute })
+    let pointer = st.dyn_pointer();
+    if !pointer && !(p.base.is_some() && st.dyn_dimension()) {
+        return None;
+    }
+    Some(crate::dyninput::Frame { base: p.base, cartesian: pointer && st.dynpi_cartesian, absolute: pointer && st.dynpi_absolute })
+}
+
+/// Whether the value boxes show before anything is typed: DYNPIVIS 0 keeps pointer input out of
+/// sight until coordinates are typed (dimension input shows at once).
+pub fn dyn_values_shown(app: &CadApp, f: &crate::dyninput::Frame) -> bool {
+    let st = &app.session.settings;
+    st.dynpivis != 0 || (f.base.is_some() && st.dyn_dimension()) || !app.cmd.buffer.trim().is_empty()
 }
 
 /// Take Tab away from egui before it moves keyboard focus to the next widget (which would leave
@@ -667,6 +680,42 @@ mod tests {
         assert_eq!(app.ui.history_lines, 12);
     }
 
+    #[test]
+    fn pointer_and_dimension_input_each_drive_the_value_boxes() {
+        let mut app = CadApp::new(Session::new(), crate::Services::default());
+        let set = |app: &mut CadApp, dynmode: i32| {
+            app.session.settings.dynmode = dynmode > 0;
+            app.session.settings.dyninput = dynmode.unsigned_abs() as u8;
+        };
+        app.session.settings.dynpi_cartesian = true;
+        app.cmdline("line");
+        // A first point takes pointer input only.
+        set(&mut app, 2);
+        assert!(dyn_frame(&app).is_none());
+        set(&mut app, 1);
+        assert!(dyn_frame(&app).is_some_and(|f| f.base.is_none()));
+        app.cmdline("0,0");
+        // Next points: dimension input alone is distance and angle from the last point.
+        set(&mut app, 2);
+        let f = dyn_frame(&app).expect("dimension input at a next point");
+        assert!(f.base.is_some() && !f.cartesian && !f.absolute);
+        set(&mut app, 3);
+        assert!(dyn_frame(&app).is_some_and(|f| f.cartesian), "pointer input keeps DYNPIFORMAT");
+        set(&mut app, -3);
+        assert!(dyn_frame(&app).is_none(), "F12 off");
+        // DYNPIVIS 0: pointer input waits for typing; dimension input shows at once.
+        app.session.settings.dynpivis = 0;
+        set(&mut app, 1);
+        let f = dyn_frame(&app).expect("pointer input");
+        assert!(!dyn_values_shown(&app, &f));
+        app.cmd.buffer = "5".into();
+        assert!(dyn_values_shown(&app, &f));
+        app.cmd.buffer.clear();
+        set(&mut app, 3);
+        let f = dyn_frame(&app).expect("both inputs");
+        assert!(dyn_values_shown(&app, &f));
+    }
+
     fn frame(app: &mut CadApp, ctx: &egui::Context, events: Vec<Event>) {
         let raw = egui::RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(1400.0, 900.0))), events, ..Default::default() };
         let mut out = ctx.run_ui(raw, |ui| {
diff --git a/crates/ui-egui/src/context_menu.rs b/crates/ui-egui/src/context_menu.rs
--- a/crates/ui-egui/src/context_menu.rs
+++ b/crates/ui-egui/src/context_menu.rs
@@ -267,7 +267,7 @@ pub fn snap_entries(app: &CadApp) -> Vec<Vec<Entry>> {
 /// Run an item of the object snap menu: type its modifier at the prompt, or open the settings.
 pub fn choose_snap(app: &mut CadApp, id: &str) {
     if id == OSNAP_SETTINGS {
-        activate(app, id);
+        app.ui.dialog = Some("dsettings:osnap".into());
     } else {
         app.cmdline(id);
     }
diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs
--- a/crates/ui-egui/src/dialogs.rs
+++ b/crates/ui-egui/src/dialogs.rs
@@ -19,7 +19,7 @@ pub fn show(app: &mut CadApp, ctx: &egui::Context) {
         "layers" => crate::layers::dialog(app, ctx, &mut open),
         "qselect" => crate::quick::qselect_dialog(app, ctx, &mut open),
         "parameters" => crate::parametric::parameters_dialog(app, ctx, &mut open),
-        "dsettings" => dsettings(app, ctx, &mut open),
+        o if o == "dsettings" || o.starts_with("dsettings:") => dsettings(app, ctx, o, &mut open),
         "units" => units(app, ctx, &mut open),
         "hatch" => crate::hatchdlg::dialog(app, ctx, &mut open),
         "aliasedit" => crate::aliasdlg::dialog(app, ctx, &mut open),
@@ -39,113 +39,335 @@ pub fn show(app: &mut CadApp, ctx: &egui::Context) {
     }
 }
 
-fn dsettings(app: &mut CadApp, ctx: &egui::Context, open: &mut bool) {
+/// The Drafting Settings tabs: (id in the dialog name, label).
+const DSETTINGS_TABS: [(&str, &str); 4] =
+    [("snap", "Snap and Grid"), ("polar", "Polar Tracking"), ("osnap", "Object Snap"), ("dynamic", "Dynamic Input")];
+const DSETTINGS_WIDTH: f32 = 460.0;
+const DSETTINGS_MIN_TAB_HEIGHT: f32 = 300.0;
+/// The ids of the windows the Dynamic Input tab's Settings... buttons open.
+const POINTER_SETTINGS: &str = "dsettings_pointer";
+const DIMENSION_SETTINGS: &str = "dsettings_dimension";
+
+/// Drafting Settings, one tab at a time. The open tab is part of the dialog name
+/// (`"dsettings:dynamic"`), as in Options, so a menu can open the tab it is about.
+fn dsettings(app: &mut CadApp, ctx: &egui::Context, name: &str, open: &mut bool) {
+    let tab = name.strip_prefix("dsettings:").and_then(|t| DSETTINGS_TABS.iter().find(|(id, _)| *id == t)).map_or("snap", |(id, _)| *id);
     let settings = app.session.drafting_settings();
     let (mut snapunit, mut gridunit) = (settings.snapunit, settings.gridunit);
+    let mut next_tab = None;
     egui::Window::new(crate::tl!("Drafting Settings")).id(egui::Id::new("Drafting Settings")).open(open).resizable(false).show(ctx, |ui| {
+        ui.set_width(DSETTINGS_WIDTH);
+        ui.horizontal_wrapped(|ui| {
+            for (id, label) in DSETTINGS_TABS {
+                if ui.selectable_label(tab == id, crate::i18n::t(label)).clicked() {
+                    next_tab = Some(id);
+                }
+            }
+        });
+        ui.separator();
+        let top = ui.cursor().top();
         let s = &mut app.session.settings;
-        ui.heading(crate::tl!("Snap and Grid"));
-        ui.checkbox(&mut s.snapmode, crate::tl!("Snap On (F9)"));
-        // SNAPSTYL: the isometric snap and grid (ISODRAFT).
+        match tab {
+            "polar" => polar_tab(ui, s),
+            "osnap" => osnap_tab(ui, s),
+            "dynamic" => dynamic_tab(ui, s),
+            _ => snap_tab(ui, s, &mut snapunit, &mut gridunit),
+        }
+        // The same height on every tab, so switching tabs doesn't resize the dialog.
+        ui.add_space((DSETTINGS_MIN_TAB_HEIGHT - (ui.cursor().top() - top)).max(0.0));
+    });
+    if let Some(t) = next_tab {
+        app.ui.dialog = Some(format!("dsettings:{t}"));
+    }
+    if *open && tab == "dynamic" {
+        pointer_settings(ctx, &mut app.session.settings);
+        dimension_settings(ctx, &mut app.session.settings);
+    } else {
+        set_window_open(ctx, POINTER_SETTINGS, false);
+        set_window_open(ctx, DIMENSION_SETTINGS, false);
+    }
+    // Drawing edits go through the same command as the CLI, including dirty state and undo.
+    for (name, before, after) in [("SNAPUNIT", settings.snapunit, snapunit), ("GRIDUNIT", settings.gridunit, gridunit)] {
+        if before != after {
+            let _ = app.run("setvar", json!({ "name": name, "value": [after.x, after.y] }));
+        }
+    }
+}
+
+fn snap_tab(ui: &mut egui::Ui, s: &mut cadcraft_engine::Settings, snapunit: &mut cadcraft_geom::Vec2, gridunit: &mut cadcraft_geom::Vec2) {
+    ui.checkbox(&mut s.snapmode, crate::tl!("Snap On (F9)"));
+    // SNAPSTYL: the isometric snap and grid (ISODRAFT).
+    ui.horizontal(|ui| {
+        ui.radio_value(&mut s.isodraft, false, crate::tl!("Rectangular snap"));
+        ui.radio_value(&mut s.isodraft, true, crate::tl!("Isometric snap"));
+    });
+    ui.horizontal(|ui| {
+        ui.label(crate::tl!("Snap X spacing"));
+        ui.add(egui::DragValue::new(&mut snapunit.x).speed(0.05).range(0.0001..=1e6).clamp_existing_to_range(false));
+        ui.label(crate::tl!("Y"));
+        ui.add(egui::DragValue::new(&mut snapunit.y).speed(0.05).range(0.0001..=1e6).clamp_existing_to_range(false));
+    });
+    // SNAPTYPE and POLARDIST.
+    ui.horizontal(|ui| {
+        ui.label(crate::tl!("Snap type"));
+        ui.radio_value(&mut s.polarsnap, false, crate::tl!("Grid snap"));
+        ui.radio_value(&mut s.polarsnap, true, crate::tl!("PolarSnap"));
+    });
+    ui.add_enabled_ui(s.polarsnap, |ui| {
         ui.horizontal(|ui| {
-            ui.radio_value(&mut s.isodraft, false, crate::tl!("Rectangular snap"));
-            ui.radio_value(&mut s.isodraft, true, crate::tl!("Isometric snap"));
+            ui.label(crate::tl!("Polar distance"));
+            ui.add(egui::DragValue::new(&mut s.polardist).speed(0.05).range(0.0..=1e6));
         });
-        ui.horizontal(|ui| {
-            ui.label(crate::tl!("Snap X spacing"));
-            ui.add(egui::DragValue::new(&mut snapunit.x).speed(0.05).range(0.0001..=1e6).clamp_existing_to_range(false));
-            ui.label(crate::tl!("Y"));
-            ui.add(egui::DragValue::new(&mut snapunit.y).speed(0.05).range(0.0001..=1e6).clamp_existing_to_range(false));
+    });
+    ui.checkbox(&mut s.gridmode, crate::tl!("Grid On (F7)"));
+    ui.horizontal(|ui| {
+        ui.label(crate::tl!("Grid spacing"));
+        ui.add(egui::DragValue::new(&mut gridunit.x).speed(0.05).range(0.0..=1e6).clamp_existing_to_range(false));
+        ui.label(crate::tl!("Major line every"));
+        ui.add(egui::DragValue::new(&mut s.gridmajor).range(1..=100));
+    });
+}
+
+fn polar_tab(ui: &mut egui::Ui, s: &mut cadcraft_engine::Settings) {
+    ui.checkbox(&mut s.polarmode, crate::tl!("Polar Tracking On (F10)"));
+    // POLARANG is held in radians and 15 or 30 come back as 14.999999999999998: round like
+    // the additional angles, and write back only a newly picked angle.
+    let current = (s.polarang.to_degrees() * 1e6).round() / 1e6;
+    let mut deg = current;
+    ui.horizontal(|ui| {
+        ui.label(crate::tl!("Increment angle"));
+        egui::ComboBox::from_id_salt("polarang").selected_text(format!("{deg}")).show_ui(ui, |ui| {
+            for a in [90.0, 45.0, 30.0, 22.5, 18.0, 15.0, 10.0, 5.0] {
+                ui.selectable_value(&mut deg, a, format!("{a}"));
+            }
         });
-        // SNAPTYPE and POLARDIST.
+    });
+    if deg != current {
+        s.polarang = deg.to_radians();
+    }
+    tracking_options(ui, s);
+    ui.separator();
+    ui.checkbox(&mut s.orthomode, crate::tl!("Ortho (F8)"));
+}
+
+fn osnap_tab(ui: &mut egui::Ui, s: &mut cadcraft_engine::Settings) {
+    let mut on = s.osmode & cadcraft_engine::snap::mode::OFF == 0;
+    if ui.checkbox(&mut on, crate::tl!("Object Snap On (F3)")).changed() {
+        if on {
+            s.osmode &= !cadcraft_engine::snap::mode::OFF;
+        } else {
+            s.osmode |= cadcraft_engine::snap::mode::OFF;
+        }
+    }
+    egui::Grid::new("osnap_grid").num_columns(2).show(ui, |ui| {
+        for (i, (bit, name)) in cadcraft_engine::snap::mode::ALL.iter().enumerate() {
+            let mut v = s.osmode & bit != 0;
+            if ui.checkbox(&mut v, crate::i18n::t(name)).changed() {
+                if v {
+                    s.osmode |= bit;
+                } else {
+                    s.osmode &= !bit;
+                }
+            }
+            if i % 2 == 1 {
+                ui.end_row();
+            }
+        }
+    });
+    ui.separator();
+    ui.checkbox(&mut s.otrack, crate::tl!("Object Snap Tracking On (F11)"));
+}
+
+/// The Dynamic Input tab: pointer input and dimension input (DYNMODE's bits, each with its
+/// settings window) and dynamic prompts (DYNPROMPT).
+fn dynamic_tab(ui: &mut egui::Ui, s: &mut cadcraft_engine::Settings) {
+    // While F12 has turned Dynamic Input off (DYNMODE negative) both show unticked; ticking one
+    // turns it back on with that input.
+    let shown = if s.dynmode { s.dyninput } else { 0 };
+    let mut bits = shown;
+    ui.columns(2, |cols| {
+        if let [left, right] = cols {
+            input_group(left, &mut bits, 1, crate::tl!("Enable Pointer Input"), crate::tl!("Pointer Input"), Preview::Pointer, POINTER_SETTINGS);
+            input_group(
+                right,
+                &mut bits,
+                2,
+                crate::tl!("Enable Dimension Input where possible"),
+                crate::tl!("Dimension Input"),
+                Preview::Dimension,
+                DIMENSION_SETTINGS,
+            );
+        }
+    });
+    if bits != shown {
+        s.dynmode = bits != 0;
+        s.dyninput = bits;
+    }
+    ui.add_space(6.0);
+    ui.group(|ui| {
+        ui.set_width(ui.available_width());
+        ui.label(crate::tl!("Dynamic Prompts"));
         ui.horizontal(|ui| {
-            ui.label(crate::tl!("Snap type"));
-            ui.radio_value(&mut s.polarsnap, false, crate::tl!("Grid snap"));
-            ui.radio_value(&mut s.polarsnap, true, crate::tl!("PolarSnap"));
+            preview(ui, Preview::Prompt);
+            // DYNPROMPT: the prompt and its options (Down Arrow) beside the cursor.
+            ui.checkbox(&mut s.dynprompt, crate::tl!("Show command prompting near the crosshairs"));
         });
-        ui.add_enabled_ui(s.polarsnap, |ui| {
+    });
+}
+
+/// One of the Dynamic Input tab's inputs: its Enable box (DYNMODE `bit`), and a group with a
+/// picture of it and the Settings... button that opens its window.
+fn input_group(ui: &mut egui::Ui, bits: &mut u8, bit: u8, enable: &str, title: &str, kind: Preview, window: &str) {
+    let mut on = *bits & bit != 0;
+    if ui.checkbox(&mut on, enable).changed() {
+        *bits ^= bit;
+    }
+    ui.group(|ui| {
+        ui.set_width(ui.available_width());
+        ui.label(title);
+        preview(ui, kind);
+        ui.vertical_centered(|ui| {
+            if ui.button(crate::tl!("Settings...")).clicked() {
+                set_window_open(ui.ctx(), window, true);
+            }
+        });
+    });
+}
+
+fn window_open(ctx: &egui::Context, window: &str) -> bool {
+    ctx.data(|d| d.get_temp::<bool>(egui::Id::new(window))).unwrap_or(false)
+}
+
+fn set_window_open(ctx: &egui::Context, window: &str, open: bool) {
+    ctx.data_mut(|d| d.insert_temp(egui::Id::new(window), open));
+}
+
+/// What a Dynamic Input picture shows.
+#[derive(Clone, Copy)]
+enum Preview {
+    /// The crosshair with its X and Y boxes.
+    Pointer,
+    /// A rubber band from the last point with its length field.
+    Dimension,
+    /// The crosshair with a prompt beside it.
+    Prompt,
+}
+
+/// A small picture of what an input shows beside the cursor, drawn like the drawing area.
+fn preview(ui: &mut egui::Ui, kind: Preview) {
+    use egui::{Pos2, Rect, Stroke};
+    let t = Tokens::get();
+    let (r, _) = ui.allocate_exact_size(vec2(ui.available_width().min(200.0), 72.0), egui::Sense::hover());
+    let p = ui.painter_at(r);
+    p.rect_filled(r, 3.0, t.canvas);
+    let ink = Stroke::new(1.0, t.crosshair);
+    let cross = |c: Pos2| {
+        p.line_segment([c - vec2(16.0, 0.0), c + vec2(16.0, 0.0)], ink);
+        p.line_segment([c - vec2(0.0, 16.0), c + vec2(0.0, 16.0)], ink);
+    };
+    let tip = |at: Pos2, text: String| {
+        let g = p.layout_no_wrap(text, crate::theme::small(), t.text);
+        let b = Rect::from_min_size(at, g.size() + vec2(8.0, 4.0));
+        p.rect_filled(b, 2.0, t.cmd_bg);
+        p.rect_stroke(b, 2.0, Stroke::new(1.0, t.cmd_border), egui::StrokeKind::Inside);
+        p.galley(b.min + vec2(4.0, 2.0), g, t.text);
+        b
+    };
+    match kind {
+        Preview::Pointer => {
+            let c = r.left_center() + vec2(36.0, -10.0);
+            cross(c);
+            let b = tip(c + vec2(14.0, 8.0), "12.5000".into());
+            tip(egui::pos2(b.right() + 4.0, b.top()), "8.2500".into());
+        }
+        Preview::Dimension => {
+            let a = r.left_bottom() + vec2(16.0, -14.0);
+            let c = r.right_top() + vec2(-40.0, 24.0);
+            p.line_segment([a, c], Stroke::new(1.0, t.accent));
+            cross(c);
+            // The dimension line beside the rubber band, its length field halfway along.
+            let n = (c - a).normalized().rot90() * 12.0;
+            let dim = Stroke::new(1.0, t.text_dim);
+            p.line_segment([a, a + n * 1.3], dim);
+            p.line_segment([c, c + n * 1.3], dim);
+            p.line_segment([a + n, c + n], dim);
+            let size = p.layout_no_wrap("4.2500".into(), crate::theme::small(), t.text).size() + vec2(8.0, 4.0);
+            tip(a + (c - a) * 0.5 + n - size * 0.5, "4.2500".into());
+        }
+        Preview::Prompt => {
+            let c = r.left_center() + vec2(30.0, -8.0);
+            cross(c);
+            tip(c + vec2(14.0, 8.0), format!("{}:", crate::i18n::t("Specify first point")));
+        }
+    }
+}
+
+/// Pointer Input settings: the format of second and next points (DYNPIFORMAT, DYNPICOORDS) and
+/// when the coordinates show (DYNPIVIS).
+fn pointer_settings(ctx: &egui::Context, s: &mut cadcraft_engine::Settings) {
+    let mut open = window_open(ctx, POINTER_SETTINGS);
+    if !open {
+        return;
+    }
+    egui::Window::new(crate::tl!("Pointer Input Settings"))
+        .id(egui::Id::new(POINTER_SETTINGS))
+        .open(&mut open)
+        .resizable(false)
+        .collapsible(false)
+        .show(ctx, |ui| {
+            ui.label(crate::tl!("Second and next points:"));
             ui.horizontal(|ui| {
-                ui.label(crate::tl!("Polar distance"));
-                ui.add(egui::DragValue::new(&mut s.polardist).speed(0.05).range(0.0..=1e6));
+                ui.radio_value(&mut s.dynpi_cartesian, false, crate::tl!("Polar (distance < angle)"));
+                ui.radio_value(&mut s.dynpi_cartesian, true, crate::tl!("Cartesian (x, y)"));
             });
-        });
-        ui.checkbox(&mut s.gridmode, crate::tl!("Grid On (F7)"));
-        ui.horizontal(|ui| {
-            ui.label(crate::tl!("Grid spacing"));
-            ui.add(egui::DragValue::new(&mut gridunit.x).speed(0.05).range(0.0..=1e6).clamp_existing_to_range(false));
-            ui.label(crate::tl!("Major line every"));
-            ui.add(egui::DragValue::new(&mut s.gridmajor).range(1..=100));
-        });
-        ui.separator();
-        ui.heading(crate::tl!("Polar Tracking"));
-        ui.checkbox(&mut s.polarmode, crate::tl!("Polar Tracking On (F10)"));
-        // POLARANG is held in radians and 15 or 30 come back as 14.999999999999998: round like
-        // the additional angles, and write back only a newly picked angle.
-        let current = (s.polarang.to_degrees() * 1e6).round() / 1e6;
-        let mut deg = current;
-        ui.horizontal(|ui| {
-            ui.label(crate::tl!("Increment angle"));
-            egui::ComboBox::from_id_salt("polarang").selected_text(format!("{deg}")).show_ui(ui, |ui| {
-                for a in [90.0, 45.0, 30.0, 22.5, 18.0, 15.0, 10.0, 5.0] {
-                    ui.selectable_value(&mut deg, a, format!("{a}"));
-                }
+            ui.horizontal(|ui| {
+                ui.radio_value(&mut s.dynpi_absolute, false, crate::tl!("Relative"));
+                ui.radio_value(&mut s.dynpi_absolute, true, crate::tl!("Absolute"));
             });
+            ui.separator();
+            ui.label(crate::tl!("Show coordinate tooltips:"));
+            ui.radio_value(&mut s.dynpivis, 0, crate::tl!("Once coordinates are typed"));
+            ui.radio_value(&mut s.dynpivis, 1, crate::tl!("When a command asks for a point"));
+            ui.radio_value(&mut s.dynpivis, 2, crate::tl!("Always, even with no command running"));
         });
-        if deg != current {
-            s.polarang = deg.to_radians();
-        }
-        tracking_options(ui, s);
-        ui.separator();
-        ui.heading(crate::tl!("Object Snap"));
-        let mut on = s.osmode & cadcraft_engine::snap::mode::OFF == 0;
-        if ui.checkbox(&mut on, crate::tl!("Object Snap On (F3)")).changed() {
-            if on {
-                s.osmode &= !cadcraft_engine::snap::mode::OFF;
-            } else {
-                s.osmode |= cadcraft_engine::snap::mode::OFF;
-            }
-        }
-        egui::Grid::new("osnap_grid").num_columns(2).show(ui, |ui| {
-            for (i, (bit, name)) in cadcraft_engine::snap::mode::ALL.iter().enumerate() {
-                let mut v = s.osmode & bit != 0;
-                if ui.checkbox(&mut v, crate::i18n::t(name)).changed() {
-                    if v {
-                        s.osmode |= bit;
-                    } else {
-                        s.osmode &= !bit;
+    set_window_open(ctx, POINTER_SETTINGS, open);
+}
+
+/// Dimension Input settings: which dimensions show while a grip is stretched (DYNDIVIS, and
+/// DYNDIGRIP's bits when it is 2).
+fn dimension_settings(ctx: &egui::Context, s: &mut cadcraft_engine::Settings) {
+    let mut open = window_open(ctx, DIMENSION_SETTINGS);
+    if !open {
+        return;
+    }
+    egui::Window::new(crate::tl!("Dimension Input Settings"))
+        .id(egui::Id::new(DIMENSION_SETTINGS))
+        .open(&mut open)
+        .resizable(false)
+        .collapsible(false)
+        .show(ctx, |ui| {
+            ui.label(crate::tl!("While a grip is stretched, show:"));
+            ui.radio_value(&mut s.dyndivis, 0, crate::tl!("One dimension input field at a time"));
+            ui.radio_value(&mut s.dyndivis, 1, crate::tl!("Two dimension input fields at a time"));
+            ui.radio_value(&mut s.dyndivis, 2, crate::tl!("These dimension input fields together:"));
+            ui.add_enabled_ui(s.dyndivis == 2, |ui| {
+                ui.indent("dyndigrip", |ui| {
+                    for (bit, label) in [
+                        (1, crate::tl!("Resulting Dimension")),
+                        (2, crate::tl!("Length Change")),
+                        (4, crate::tl!("Absolute Angle")),
+                        (8, crate::tl!("Angle Change")),
+                        (16, crate::tl!("Arc Radius")),
+                    ] {
+                        let mut on = s.dyndigrip & bit != 0;
+                        if ui.checkbox(&mut on, label).changed() {
+                            s.dyndigrip ^= bit;
+                        }
                     }
-                }
-                if i % 2 == 1 {
-                    ui.end_row();
-                }
-            }
-        });
-        ui.separator();
-        ui.checkbox(&mut s.dynmode, crate::tl!("Enable Dynamic Input (F12)"));
-        // Pointer input format for second and next points (DYNPIFORMAT, DYNPICOORDS).
-        ui.indent("dynpi", |ui| {
-            ui.add_enabled_ui(s.dynmode, |ui| {
-                ui.label(crate::tl!("Second and next points:"));
-                ui.horizontal(|ui| {
-                    ui.radio_value(&mut s.dynpi_cartesian, false, crate::tl!("Polar (distance < angle)"));
-                    ui.radio_value(&mut s.dynpi_cartesian, true, crate::tl!("Cartesian (x, y)"));
-                });
-                ui.horizontal(|ui| {
-                    ui.radio_value(&mut s.dynpi_absolute, false, crate::tl!("Relative"));
-                    ui.radio_value(&mut s.dynpi_absolute, true, crate::tl!("Absolute"));
                 });
-                // DYNPROMPT: the prompt and its options (Down Arrow) beside the cursor.
-                ui.checkbox(&mut s.dynprompt, crate::tl!("Show command prompting near the crosshairs"));
             });
         });
-        ui.checkbox(&mut s.orthomode, crate::tl!("Ortho (F8)"));
-    });
-    // Drawing edits go through the same command as the CLI, including dirty state and undo.
-    for (name, before, after) in [("SNAPUNIT", settings.snapunit, snapunit), ("GRIDUNIT", settings.gridunit, gridunit)] {
-        if before != after {
-            let _ = app.run("setvar", json!({ "name": name, "value": [after.x, after.y] }));
-        }
-    }
+    set_window_open(ctx, DIMENSION_SETTINGS, open);
 }
 
 /// The polar and object snap tracking options of Drafting Settings: additional angles
diff --git a/crates/ui-egui/src/grip_dyn.rs b/crates/ui-egui/src/grip_dyn.rs
--- a/crates/ui-egui/src/grip_dyn.rs
+++ b/crates/ui-egui/src/grip_dyn.rs
@@ -33,8 +33,11 @@ pub struct GripDyn {
 }
 
 /// The base of the prompt on show when it asks for a distance from it (a circle's radius, a
-/// polyline arc's): its one field is that distance.
+/// polyline arc's): its one field is that distance (dimension input).
 fn distance_base(app: &CadApp) -> Option<Vec2> {
+    if !app.session.settings.dyn_dimension() {
+        return None;
+    }
     crate::cmdline::dyn_frame(app).and_then(|_| app.session.current_prompt()).filter(|p| p.distance).and_then(|p| p.base)
 }
 
diff --git a/crates/ui-egui/tests/it/drafting_dynamic_input.rs b/crates/ui-egui/tests/it/drafting_dynamic_input.rs
new file mode 100644
--- /dev/null
+++ b/crates/ui-egui/tests/it/drafting_dynamic_input.rs
@@ -0,0 +1,80 @@
+//! Drafting Settings opens on its tabs, and the Dynamic Input tab turns pointer input and
+//! dimension input on and off apart (DYNMODE's bits) and opens their settings windows.
+
+use cadcraft_engine::Session;
+use cadcraft_ui_egui::{CadApp, Services, dialogs, theme};
+use egui_kittest::Harness;
+use egui_kittest::kittest::Queryable;
+use serde_json::json;
+
+fn harness(dialog: &str) -> Harness<'static, CadApp> {
+    let mut app = CadApp::new(Session::new(), Services::default());
+    app.ui.dialog = Some(dialog.into());
+    let mut h = Harness::builder().with_size(egui::vec2(900.0, 700.0)).build_ui_state(
+        |ui, app: &mut CadApp| {
+            theme::apply(ui.ctx(), egui::Theme::Dark, false);
+            dialogs::show(app, ui.ctx());
+        },
+        app,
+    );
+    h.run_steps(5);
+    h
+}
+
+fn dynmode(h: &mut Harness<'_, CadApp>) -> serde_json::Value {
+    h.state_mut().run("getvar", json!({"name": "DYNMODE"})).unwrap()["value"].clone()
+}
+
+#[test]
+fn the_tabs_switch_and_keep_the_dialog_open() {
+    let mut h = harness("dsettings");
+    assert!(h.query_by_label("Snap On (F9)").is_some(), "Snap and Grid is the first tab");
+    h.get_by_label("Object Snap").click();
+    h.run_steps(3);
+    assert_eq!(h.state().ui.dialog.as_deref(), Some("dsettings:osnap"));
+    assert!(h.query_by_label("Object Snap On (F3)").is_some());
+    assert!(h.query_by_label("Snap On (F9)").is_none());
+    h.get_by_label("Dynamic Input").click();
+    h.run_steps(3);
+    assert!(h.query_by_label("Enable Pointer Input").is_some());
+}
+
+#[test]
+fn pointer_and_dimension_input_tick_apart() {
+    let mut h = harness("dsettings:dynamic");
+    assert_eq!(dynmode(&mut h), json!(3));
+    h.get_by_label("Enable Pointer Input").click();
+    h.run_steps(3);
+    assert_eq!(dynmode(&mut h), json!(2), "dimension input stays on");
+    h.get_by_label("Enable Dimension Input where possible").click();
+    h.run_steps(3);
+    assert_eq!(dynmode(&mut h), json!(0));
+    h.get_by_label("Enable Pointer Input").click();
+    h.run_steps(3);
+    assert_eq!(dynmode(&mut h), json!(1));
+    // F12 off: both show unticked; ticking one turns Dynamic Input back on with it.
+    h.state_mut().run("dynmode", json!({"on": false})).unwrap();
+    h.run_steps(3);
+    h.get_by_label("Enable Dimension Input where possible").click();
+    h.run_steps(3);
+    assert_eq!(dynmode(&mut h), json!(2));
+}
+
+#[test]
+fn settings_buttons_open_the_pointer_and_dimension_windows() {
+    let mut h = harness("dsettings:dynamic");
+    h.get_all_by_label("Settings...").next().expect("Pointer Input's Settings...").click();
+    h.run_steps(3);
+    h.get_by_label("Always, even with no command running").click();
+    h.run_steps(3);
+    assert_eq!(h.state_mut().run("getvar", json!({"name": "DYNPIVIS"})).unwrap()["value"], json!(2));
+    h.get_all_by_label("Settings...").nth(1).expect("Dimension Input's Settings...").click();
+    h.run_steps(3);
+    h.get_by_label("These dimension input fields together:").click();
+    h.run_steps(3);
+    h.get_by_label("Arc Radius").click();
+    h.run_steps(3);
+    let app = h.state_mut();
+    assert_eq!(app.run("getvar", json!({"name": "DYNDIVIS"})).unwrap()["value"], json!(2));
+    assert_eq!(app.run("getvar", json!({"name": "DYNDIGRIP"})).unwrap()["value"], json!(15));
+}
diff --git a/crates/ui-egui/tests/it/drafting_polar_increment.rs b/crates/ui-egui/tests/it/drafting_polar_increment.rs
--- a/crates/ui-egui/tests/it/drafting_polar_increment.rs
+++ b/crates/ui-egui/tests/it/drafting_polar_increment.rs
@@ -11,7 +11,7 @@ fn shown_increment(degrees: f64) -> (String, f64, f64) {
     let mut app = CadApp::new(Session::new(), Services::default());
     app.run("setvar", json!({"name": "POLARANG", "value": degrees})).unwrap();
     let stored = app.session.settings.polarang;
-    app.ui.dialog = Some("dsettings".into());
+    app.ui.dialog = Some("dsettings:polar".into());
     let mut h = Harness::builder().with_size(egui::vec2(800.0, 600.0)).build_ui_state(
         |ui, app: &mut CadApp| {
             theme::apply(ui.ctx(), egui::Theme::Dark, false);
diff --git a/crates/ui-egui/tests/it/main.rs b/crates/ui-egui/tests/it/main.rs
--- a/crates/ui-egui/tests/it/main.rs
+++ b/crates/ui-egui/tests/it/main.rs
@@ -7,6 +7,7 @@ mod control_cmdline_key;
 mod control_methods;
 mod dialog_dispatch;
 mod docking_workflows;
+mod drafting_dynamic_input;
 mod drafting_polar_increment;
 mod drafting_spacing;
 mod file_tab_titles;
diff --git a/docs/ui-parity.md b/docs/ui-parity.md
--- a/docs/ui-parity.md
+++ b/docs/ui-parity.md
@@ -1,6 +1,6 @@
 # UI parity
 
-> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (distinct polyline midpoint grips) · **Target:** Autodesk AutoCAD 2027 for Mac (26.0)
+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-25 · **Change:** minor (Drafting Settings tabs; pointer and dimension input apart) · **Target:** Autodesk AutoCAD 2027 for Mac (26.0)
 
 How CADCraft's interaction compares with AutoCAD's: point entry, object snaps, tracking, dynamic
 input, grips, selection, the command line, chrome and navigation. A drafter's speed in AutoCAD
@@ -91,7 +91,7 @@ ignored; there are no context menus. Remaining: **≈ 110–170 Opus 5.5 hours**
 | Grid (rectangular, major lines, adaptive, limits) | yes | rectangular and isometric (30°, 90° and 150° lines), major lines, adaptive display |
 | Snap (rectangular, isometric, polar snap) | yes | rectangular and isometric (SNAPSTYL 1: the snap Y spacing along the isometric axes), from SNAPBASE turned by SNAPANG |
 | Isometric drafting (ISODRAFT, isoplanes F5) | yes | yes (#556): ISODRAFT and the status-bar toggle set SNAPSTYL and the plane; ISOPLANE / F5 / Ctrl+E cycle Left, Top, Right (SNAPISOPAIR); ortho, direct distance entry and the crosshair follow the plane's axes; ELLIPSE Isocircle (radius or Diameter, also for elliptical arcs); Drafting Settings snap style radio. Engine: `snap/iso.rs`, `cmd/settings/iso.rs` |
-| Drafting Settings dialog | yes | yes (`dialogs.rs:36-80`) |
+| Drafting Settings dialog | yes | yes (`dialogs.rs`): Snap and Grid, Polar Tracking, Object Snap and Dynamic Input tabs (`dsettings:<tab>` opens one; Osnap Settings opens Object Snap). Dynamic Input: Enable Pointer Input and Enable Dimension Input (DYNMODE 1/2/3, negative while F12 has it off), each with a picture and a Settings window (pointer: DYNPIFORMAT, DYNPICOORDS, DYNPIVIS; dimension: DYNDIVIS, DYNDIGRIP), and Dynamic Prompts (DYNPROMPT); no additional tips (DYNINFOTIPS) or Drafting Tooltip Appearance |
 | Hatch and Gradient / Hatch Edit dialogs | yes | yes (`hatchdlg.rs`): pattern swatches, gradient colours and shapes, origin, pick points or objects, associative, separate, island detection; Hatch Edit adds boundaries from points or closed objects and removes selected loops; opened by HATCH, GRADIENT (HPDLGMODE), HATCHEDIT, right-click or double-clicking a hatch; no Preview or Inherit Properties |
 
 ## Dynamic input (≈ 45%)
@@ -203,6 +203,7 @@ down-arrow options menu (DYNPROMPT), and DYNDIM.
 
 | Date | Change | Summary |
 |---|---|---|
+| 2026-10-25 | minor | Drafting Settings in tabs, with a Dynamic Input tab: pointer input and dimension input turn on apart (DYNMODE bits), DYNPIVIS, and Settings windows for both |
 | 2026-10-22 | minor | Polyline segment midpoint grips use rectangles, distinct from square vertex grips, with matching hover and pick bounds (#1950) |
 | 2026-10-13 | minor | Filled-region highlight boundaries are traversed in deterministic coordinate order, fixing dash-phase jumps on CPU hover/selection redraws and GPU highlight rebuilds; CPU dash-position, pixel and GPU mesh regressions |
 | 2026-10-14 | minor | Selection cycling (#1143): the list and badge of SELECTIONCYCLING, Shift+Space through overlapping objects (#1145) |
