mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
* Claim #9479 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Preferences: an option to keep value boxes out of the view (#9479) Design > "Show values beside their handles in the view", on by default. Off, a command's value box (Extrude's Distance, Fillet's Radius, a hole's reference distances...) isn't drawn over the model; the command window's value field takes the keyboard that opening the window or a pick gives the box, its text selected, so a number typed after a pick still sets it. The handles stay and still drag the value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Preferences: the value box option in Arabic, Hebrew and Vietnamese too (#9479) The complete catalogs must carry every English message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"ar": {
|
||||
"prefs.value_boxes_in_view": "إظهار القيم بجانب مقابضها في العرض",
|
||||
"prefs.value_boxes_in_view_tip": "قيمة الأمر (المسافة، نصف القطر…) في مربع بجانب مقبضه، فوق النموذج. عند الإيقاف: تكتبها في نافذة الأمر، التي تتلقى لوحة المفاتيح بعد التحديد، ولا شيء يغطي النموذج."
|
||||
},
|
||||
"en": {
|
||||
"prefs.value_boxes_in_view": "Show values beside their handles in the view",
|
||||
"prefs.value_boxes_in_view_tip": "A command's value (Distance, Radius…) in a box next to its handle, over the model. Off: you type it in the command window, which takes the keyboard after a pick, and nothing covers the model."
|
||||
},
|
||||
"de": {
|
||||
"prefs.value_boxes_in_view": "Werte in der Ansicht neben ihren Griffen zeigen",
|
||||
"prefs.value_boxes_in_view_tip": "Der Wert eines Befehls (Abstand, Radius …) in einem Feld neben seinem Griff, über dem Modell. Aus: Sie geben ihn im Befehlsfenster ein, das nach einer Auswahl die Tastatur erhält, und nichts verdeckt das Modell."
|
||||
},
|
||||
"he": {
|
||||
"prefs.value_boxes_in_view": "הצגת ערכים ליד הידיות שלהם בתצוגה",
|
||||
"prefs.value_boxes_in_view_tip": "ערך הפקודה (מרחק, רדיוס…) בתיבה ליד הידית שלו, מעל המודל. כבוי: מקלידים אותו בחלון הפקודה, שמקבל את המקלדת אחרי בחירה, ושום דבר לא מסתיר את המודל."
|
||||
},
|
||||
"ja": {
|
||||
"prefs.value_boxes_in_view": "ビューでハンドルの横に値を表示",
|
||||
"prefs.value_boxes_in_view_tip": "コマンドの値(距離、半径など)をモデル上のハンドルの横のボックスに表示します。オフにすると、値はコマンドウィンドウに入力します。選択後はそのウィンドウにキーボード入力が入り、モデルを覆うものはありません。"
|
||||
},
|
||||
"ko": {
|
||||
"prefs.value_boxes_in_view": "뷰에서 핸들 옆에 값 표시",
|
||||
"prefs.value_boxes_in_view_tip": "명령의 값(거리, 반경 등)을 모델 위 핸들 옆 상자에 표시합니다. 끄면 값은 명령 창에 입력하며, 선택 후 키보드 입력이 그 창으로 가고 모델을 가리는 것이 없습니다."
|
||||
},
|
||||
"vi": {
|
||||
"prefs.value_boxes_in_view": "Hiện giá trị cạnh tay nắm trong khung nhìn",
|
||||
"prefs.value_boxes_in_view_tip": "Giá trị của lệnh (Khoảng cách, Bán kính…) trong một ô cạnh tay nắm, phía trên mô hình. Tắt: bạn nhập giá trị trong cửa sổ lệnh, cửa sổ này nhận bàn phím sau khi chọn, và không có gì che mô hình."
|
||||
},
|
||||
"zh": {
|
||||
"prefs.value_boxes_in_view": "在视图中于手柄旁显示数值",
|
||||
"prefs.value_boxes_in_view_tip": "命令的数值(距离、半径等)显示在模型上方手柄旁的框中。关闭后,在命令窗口中输入数值,选取后键盘输入会进入该窗口,模型不会被遮挡。"
|
||||
}
|
||||
}
|
||||
@@ -349,7 +349,7 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui, painter: &egui::Painter, proj
|
||||
crate::dialogs_move::step_value_field(&mut d, if back { -1 } else { 1 });
|
||||
d.focus = true;
|
||||
}
|
||||
let focus = std::mem::take(&mut d.focus);
|
||||
let focus = take_focus(ui.ctx(), &mut d);
|
||||
if let Some(bs) = at.and_then(|base| proj.to_screen(base))
|
||||
&& let Some((label, _, value)) = d.primary()
|
||||
{
|
||||
@@ -541,7 +541,7 @@ fn draw(app: &SolveApp, ui: &mut egui::Ui, painter: &egui::Painter, proj: &Proj,
|
||||
Kind::SketchModify(crate::sketch_modify_ui::Mod::Offset { mode, .. }) => !crate::sketch_modify_ui::offset_symmetric(*mode),
|
||||
_ => false,
|
||||
};
|
||||
let mut focus = std::mem::take(&mut d.focus);
|
||||
let mut focus = take_focus(ui.ctx(), d);
|
||||
let half_height = app.cam.half_height();
|
||||
let axis = revolve_axis(app, d);
|
||||
if matches!(d.kind, Kind::Revolve { .. }) {
|
||||
@@ -785,6 +785,28 @@ pub(crate) fn box_place(app: &SolveApp, proj: &Proj) -> BoxPlace {
|
||||
BoxPlace { dialog: app.viewport.dialog_rect, view: Some(proj.rect), handles: None }
|
||||
}
|
||||
|
||||
fn boxes_shown_id() -> egui::Id {
|
||||
egui::Id::new("sc_canvas_value_boxes")
|
||||
}
|
||||
|
||||
/// Show commands' values in boxes in the view or not, from the preference (the app calls this
|
||||
/// every frame).
|
||||
pub(crate) fn set_boxes_shown(ctx: &egui::Context, on: bool) {
|
||||
ctx.data_mut(|d| d.insert_temp(boxes_shown_id(), on));
|
||||
}
|
||||
|
||||
/// Whether a command's values have boxes in the view. Off (Preferences), they are typed in the
|
||||
/// command window only, and the view stays clear for picking (#9479).
|
||||
pub(crate) fn boxes_shown(ctx: &egui::Context) -> bool {
|
||||
ctx.data(|d| d.get_temp::<bool>(boxes_shown_id())).unwrap_or(true)
|
||||
}
|
||||
|
||||
/// Whether the command's value box takes the keyboard this frame: as the window opens, and after
|
||||
/// a pick. Without boxes in the view, it is left for the command window's value field (#9479).
|
||||
fn take_focus(ctx: &egui::Context, d: &mut Dialog) -> bool {
|
||||
boxes_shown(ctx) && std::mem::take(&mut d.focus)
|
||||
}
|
||||
|
||||
/// The command's main value in the viewport, next to the geometry: the keyboard goes here while
|
||||
/// the window is open, so a number typed straight after a pick sets the value. `at` is its
|
||||
/// top-left corner. `select_all` re-selects the whole field, both right after a pick and on every
|
||||
@@ -838,6 +860,10 @@ fn value_box_full(
|
||||
note: Option<&str>,
|
||||
place: BoxPlace,
|
||||
) -> Option<egui::Rect> {
|
||||
// Off in Preferences: the value is typed in the command window only (#9479).
|
||||
if !boxes_shown(ui.ctx()) {
|
||||
return None;
|
||||
}
|
||||
let t = Tokens::get();
|
||||
// Held in place while the pointer is over it and value stepping is on (see `value_box`): by
|
||||
// its pivot, the point of the box that sits at `at`, which is not always its top-left corner.
|
||||
|
||||
@@ -1586,6 +1586,13 @@ impl Dialog {
|
||||
true
|
||||
}
|
||||
|
||||
/// Give the keyboard to the form's value field `id`, its text selected, once the pointer is
|
||||
/// idle: where a pick gives it when values have no boxes in the view (#9479).
|
||||
pub(crate) fn give_keyboard(&mut self, id: egui::Id) {
|
||||
self.value_focus = Some(id);
|
||||
self.restore_value_focus = true;
|
||||
}
|
||||
|
||||
/// A pick in the viewport (already accepted by the active input).
|
||||
pub fn pick(&mut self, s: &Session, sel: Sel) {
|
||||
if self.take_extrude_target(s, &sel) {
|
||||
@@ -3288,6 +3295,12 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
if let Some(field) = crate::ui_kit::canvas_value_focus(ctx) {
|
||||
value_fields.push(field);
|
||||
}
|
||||
// Without value boxes in the view (#9479), the keyboard the window's opening or a pick gives
|
||||
// the box goes to the form's value field, once the form has drawn one.
|
||||
if d.focus && !crate::canvas::boxes_shown(ctx) && !value_fields.is_empty() {
|
||||
d.focus = false;
|
||||
d.restore_value_focus = true;
|
||||
}
|
||||
// The editor loses the keyboard while the click is still being handled, possibly in a later
|
||||
// pass of the same frame, so give it back once the pointer is idle again.
|
||||
let pointer_busy = ctx.input(|i| i.pointer.any_down() || i.pointer.any_pressed() || i.pointer.any_released());
|
||||
|
||||
@@ -503,3 +503,21 @@ fn two_sides_with_all_shows_distance_2_and_reverse_swaps_the_sides() {
|
||||
h.step(&json!({"widget":"OK"})).unwrap();
|
||||
assert_eq!(extent(&h), e);
|
||||
}
|
||||
|
||||
/// Without value boxes in the view (Preferences, #9479), nothing covers the model: the command
|
||||
/// window's Distance takes the keyboard instead, its text selected, and the arrow still drags it.
|
||||
#[test]
|
||||
fn without_value_boxes_in_the_view_the_distance_is_typed_in_the_window() {
|
||||
let mut h = make(json!({"distance":"4 mm"}));
|
||||
h.app.preferences.value_boxes_in_view = false;
|
||||
open(&mut h);
|
||||
assert!(h.ctx.memory(|m| m.area_rect(egui::Id::new("sc_canvas_area"))).is_none(), "a value box in the view");
|
||||
let focused = h.ctx.memory(|m| m.focused());
|
||||
assert!(focused.is_some() && focused != Some(egui::Id::new("sc_canvas_value")), "{focused:?}");
|
||||
h.step(&json!({"text":"25 mm"})).unwrap();
|
||||
h.frames(2);
|
||||
assert!((dialog_distance(&h) - 25.0).abs() < 1e-9, "{}", dialog_distance(&h));
|
||||
h.step(&json!({"drag":[{"handle":"arrow"},on_arrow(&h, 30.0)]})).unwrap();
|
||||
assert!((dialog_distance(&h) - 30.0).abs() < 0.5, "{}", dialog_distance(&h));
|
||||
assert!(h.ctx.memory(|m| m.area_rect(egui::Id::new("sc_canvas_area"))).is_none(), "a value box in the view");
|
||||
}
|
||||
|
||||
@@ -245,10 +245,20 @@ pub(crate) fn input_rows(d: &mut Dialog, ui: &mut egui::Ui, s: &Session, names:
|
||||
let Some(k) = d.inputs.get(i).filter(|inp| !inp.items.is_empty()).and_then(|inp| REFERENCES.iter().position(|l| *l == inp.label)) else {
|
||||
continue;
|
||||
};
|
||||
let mut keyboard = None;
|
||||
if let Some(refs) = refs_mut(d)
|
||||
&& let (Some(label), Some(value)) = (DISTANCES.get(k), refs.distance.get_mut(k))
|
||||
{
|
||||
enter |= crate::ui_kit::expr_row(ui, label, value);
|
||||
// Without value boxes in the view (#9479), a newly picked edge's distance takes the
|
||||
// keyboard here, as its box would.
|
||||
if refs.focus == Some(k) && !crate::canvas::boxes_shown(ui.ctx()) {
|
||||
refs.focus = None;
|
||||
keyboard = crate::ui_kit::last_value_field(ui.ctx());
|
||||
}
|
||||
}
|
||||
if let Some(id) = keyboard {
|
||||
d.give_keyboard(id);
|
||||
}
|
||||
}
|
||||
sync(s, d);
|
||||
@@ -342,7 +352,8 @@ pub(crate) fn canvas(app: &SolveApp, ui: &mut egui::Ui, painter: &egui::Painter,
|
||||
}
|
||||
dimension(painter, a, b, t.dimension);
|
||||
let (Some(label), Some(refs)) = (DISTANCES.get(*k), refs_mut(d)) else { continue };
|
||||
let focus = refs.focus == Some(*k);
|
||||
// Without boxes in the view, the distance's row in the command window takes it (#9479).
|
||||
let focus = refs.focus == Some(*k) && crate::canvas::boxes_shown(ui.ctx());
|
||||
if focus {
|
||||
refs.focus = None;
|
||||
took = true;
|
||||
|
||||
@@ -162,6 +162,28 @@ fn a_number_typed_after_an_edge_pick_sets_its_distance() {
|
||||
assert_eq!(diameter, "5 mm", "the diameter is left as it was");
|
||||
}
|
||||
|
||||
/// Without value boxes in the view (#9479), the distance's row in the command window takes the
|
||||
/// number typed after an edge pick, as its box would.
|
||||
#[test]
|
||||
fn without_value_boxes_a_number_typed_after_an_edge_pick_sets_its_distance() {
|
||||
let mut h = plate();
|
||||
h.app.preferences.value_boxes_in_view = false;
|
||||
h.step(&json!({"start": "solid.hole"})).unwrap();
|
||||
let at = screen(&mut h, Vec3::new(30.0, 25.0, 10.0));
|
||||
h.step(&json!({"click": at})).unwrap();
|
||||
h.step(&json!({"click": {"handle": "dialog:Reference 1"}})).unwrap();
|
||||
let left = screen(&mut h, Vec3::new(0.0, 20.0, 10.0));
|
||||
h.step(&json!({"click": left})).unwrap();
|
||||
h.frames(2);
|
||||
assert!(h.ctx.memory(|m| m.area_rect(egui::Id::new(("sc_hole_ref_area", 0usize)))).is_none(), "a distance box in the view");
|
||||
h.step(&json!({"text": "7 mm"})).unwrap();
|
||||
h.frames(2);
|
||||
assert_eq!(distances(&h)[0], "7 mm");
|
||||
assert!((place(&h).x - 7.0).abs() < 1e-6, "the hole is 7 mm from the left edge: {:?}", place(&h));
|
||||
let Some(Kind::Hole { diameter, .. }) = h.app.dialog.as_ref().map(|d| &d.kind) else { panic!("the hole dialog") };
|
||||
assert_eq!(diameter, "5 mm", "the diameter is left as it was");
|
||||
}
|
||||
|
||||
/// The open Cylinder dialog's reference distances and where it stands.
|
||||
fn cylinder_dialog(h: &Harness) -> ([String; 2], Vec3) {
|
||||
match h.app.dialog.as_ref().map(|d| &d.kind) {
|
||||
|
||||
@@ -2638,6 +2638,7 @@ impl SolveApp {
|
||||
/// Lay out the whole window.
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
value_step::set_enabled(ui.ctx(), self.preferences.step_values);
|
||||
canvas::set_boxes_shown(ui.ctx(), self.preferences.value_boxes_in_view);
|
||||
let t0 = now_ms();
|
||||
// The first frames draw with the bundled faces while the system's CJK, Hangul and Thai
|
||||
// faces are read on a thread; the fonts are installed again once they are there.
|
||||
|
||||
@@ -1014,6 +1014,10 @@ preference_settings! {
|
||||
/// Up/Down and the wheel over a focused value field step its plain number (`value_step`,
|
||||
/// #2320). Off by default: the keys move the text cursor and the wheel scrolls, as before.
|
||||
step_values: bool = false, PAGE_DESIGN => check_id("ui.step_values");
|
||||
/// A command's value (Extrude's Distance, Fillet's Radius…) in a box beside its handle in the
|
||||
/// view (`canvas::boxes_shown`). Off: it is typed in the command window only, which takes the
|
||||
/// keyboard after a pick instead, and nothing covers the model (#9479).
|
||||
value_boxes_in_view: bool = true, PAGE_DESIGN => check_id("prefs.value_boxes_in_view");
|
||||
/// A design coming back to its tab reads again the linked designs whose files changed since
|
||||
/// it last read them. Off: they wait for Update Links, as before.
|
||||
update_changed_links: bool = true, PAGE_DESIGN => check_id("settings.update_changed_links");
|
||||
|
||||
@@ -39,6 +39,8 @@ pub(super) fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let tip = app.tr("ui.step_values_tip").to_string();
|
||||
let step = setting::step_values(app, ui).on_hover_text(tip);
|
||||
crate::scenario::publish_handle("prefs:step_values", step.rect.center());
|
||||
let tip = app.tr("prefs.value_boxes_in_view_tip").to_string();
|
||||
setting::value_boxes_in_view(app, ui).on_hover_text(tip);
|
||||
});
|
||||
ui_kit::settings_section(ui, "orientation", app.tr("prefs.orientation"), |ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
|
||||
@@ -10,6 +10,11 @@ pub(crate) fn value_fields(ctx: &egui::Context, draw: impl FnOnce() -> bool) ->
|
||||
(enter, fields)
|
||||
}
|
||||
|
||||
/// The expression editor the form drew last so far, in [`value_fields`].
|
||||
pub(crate) fn last_value_field(ctx: &egui::Context) -> Option<egui::Id> {
|
||||
ctx.data(|data| data.get_temp::<Vec<(egui::Id, usize)>>(egui::Id::new("sc_value_fields"))).and_then(|fields| fields.last().map(|(id, _)| *id))
|
||||
}
|
||||
|
||||
/// Only a value box drawn this pass can receive the keyboard after a viewport pick.
|
||||
pub(crate) fn canvas_value_focus(ctx: &egui::Context) -> Option<(egui::Id, usize)> {
|
||||
ctx.data(|data| data.get_temp::<(u64, egui::Id, usize)>(egui::Id::new("sc_canvas_value_focus")))
|
||||
|
||||
Reference in New Issue
Block a user