mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 13:01:52 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e894f4a12f | |||
| 7b9e66929b | |||
| bd3a94e946 | |||
| b0d7219115 | |||
| a7598a92c6 | |||
| 5c4932ba42 | |||
| ee72a98948 | |||
| 2fba25f564 | |||
| 7b90cdf3fb | |||
| 8b7586ba87 | |||
| b23d1ada5d | |||
| 9a0d9ce243 | |||
| be226abee7 | |||
| 414b8bb139 |
Generated
+31
-2
@@ -4097,6 +4097,7 @@ dependencies = [
|
||||
"solidcraft-gpu",
|
||||
"solidcraft-space-mouse",
|
||||
"solidcraft-ui-egui",
|
||||
"solidcraft-ui-text",
|
||||
"ureq",
|
||||
"windows-sys 0.61.2",
|
||||
"winit",
|
||||
@@ -4123,6 +4124,10 @@ dependencies = [
|
||||
"solidcraft-mcp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-color"
|
||||
version = "0.40.0-rc.3"
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-derive"
|
||||
version = "0.40.0-rc.3"
|
||||
@@ -4141,6 +4146,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"solidcore-record",
|
||||
"solidcraft-color",
|
||||
"solidcraft-derive",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-kernel",
|
||||
@@ -4173,6 +4179,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"solidcore-record",
|
||||
"solidcraft-color",
|
||||
"solidcraft-doc",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-io",
|
||||
@@ -4207,6 +4214,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"solidcore-record",
|
||||
"solidcraft-color",
|
||||
"solidcraft-engine",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-io",
|
||||
@@ -4234,6 +4242,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"solidcore-record",
|
||||
"solidcraft-color",
|
||||
"solidcraft-doc",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-kernel",
|
||||
@@ -4293,6 +4302,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"solidcore-record",
|
||||
"solidcraft-color",
|
||||
"solidcraft-geom",
|
||||
"web-time",
|
||||
"weezl",
|
||||
@@ -4363,20 +4373,38 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"solidcore-record",
|
||||
"solidcraft-color",
|
||||
"solidcraft-engine",
|
||||
"solidcraft-gpu",
|
||||
"solidcraft-space-mouse",
|
||||
"solidcraft-ui-registry",
|
||||
"solidcraft-ui-text",
|
||||
"syn 2.0.119",
|
||||
"sys-locale",
|
||||
"tiny-skia",
|
||||
"toml",
|
||||
"ttf-parser",
|
||||
"unicode-bidi",
|
||||
"web-time",
|
||||
"winreg",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-ui-registry"
|
||||
version = "0.40.0-rc.3"
|
||||
dependencies = [
|
||||
"egui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-ui-text"
|
||||
version = "0.40.0-rc.3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde_json",
|
||||
"sys-locale",
|
||||
"unicode-bidi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-web"
|
||||
version = "0.40.0-rc.3"
|
||||
@@ -4389,6 +4417,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"solidcraft-engine",
|
||||
"solidcraft-ui-egui",
|
||||
"solidcraft-ui-text",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
|
||||
@@ -28,6 +28,9 @@ solidcraft-doc = { path = "crates/doc" }
|
||||
solidcraft-io = { path = "crates/io" }
|
||||
solidcraft-engine = { path = "crates/engine" }
|
||||
solidcraft-ui-egui = { path = "crates/ui-egui" }
|
||||
solidcraft-ui-registry = { path = "crates/ui-registry" }
|
||||
solidcraft-color = { path = "crates/color" }
|
||||
solidcraft-ui-text = { path = "crates/ui-text" }
|
||||
solidcraft-space-mouse = { path = "crates/space-mouse" }
|
||||
solidcraft-mcp = { path = "crates/mcp" }
|
||||
solidcraft-derive = { path = "crates/derive" }
|
||||
|
||||
@@ -23,6 +23,7 @@ profile = ["solidcraft-engine/profile"]
|
||||
[dependencies]
|
||||
solidcraft-engine = { workspace = true }
|
||||
solidcraft-ui-egui = { workspace = true }
|
||||
solidcraft-ui-text = { workspace = true }
|
||||
egui = { workspace = true }
|
||||
log = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -48,15 +48,15 @@ impl Download {
|
||||
}
|
||||
if solidcraft_ui_egui::documents::any_dirty(app) {
|
||||
let mut answer = None;
|
||||
let closed = ui_kit::Window::new(egui::Id::unique("open_link"), "status_warning", solidcraft_ui_egui::i18n::tr("web.open_link"))
|
||||
let closed = ui_kit::Window::new(egui::Id::unique("open_link"), "status_warning", solidcraft_ui_text::tr("web.open_link"))
|
||||
.required()
|
||||
.show_body(ctx, |ui| {
|
||||
ui.label(solidcraft_ui_egui::i18n::tr("web.open_link_confirm"));
|
||||
ui.label(solidcraft_ui_text::tr("web.open_link_confirm"));
|
||||
ui.horizontal(|ui| {
|
||||
if ui_kit::button(ui, solidcraft_ui_egui::i18n::tr("ui.open"), ui_kit::Button::Normal).clicked() {
|
||||
if ui_kit::button(ui, solidcraft_ui_text::tr("ui.open"), ui_kit::Button::Normal).clicked() {
|
||||
answer = Some(true);
|
||||
}
|
||||
if ui_kit::button(ui, solidcraft_ui_egui::i18n::tr("ui.cancel"), ui_kit::Button::Normal).clicked() {
|
||||
if ui_kit::button(ui, solidcraft_ui_text::tr("ui.cancel"), ui_kit::Button::Normal).clicked() {
|
||||
answer = Some(false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -810,7 +810,7 @@ mod web {
|
||||
files::pick_from_disk(&self.files, Intent::Open);
|
||||
}
|
||||
if ui_kit::button(ui, "Insert from computer…", ui_kit::Button::Normal)
|
||||
.backing_on_hover_text(solidcraft_ui_egui::i18n::tr("web.files.insert_drawings_hint"))
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr("web.files.insert_drawings_hint"))
|
||||
.clicked()
|
||||
{
|
||||
files::pick_from_disk(&self.files, Intent::Insert);
|
||||
@@ -864,7 +864,7 @@ mod web {
|
||||
let design = solidcraft_engine::io::is_design_path(&e.name);
|
||||
if !design
|
||||
&& ui_kit::button(ui, "Insert", ui_kit::Button::Small)
|
||||
.backing_on_hover_text(solidcraft_ui_egui::i18n::tr("web.files.insert_stored_drawings_hint"))
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr("web.files.insert_stored_drawings_hint"))
|
||||
.clicked()
|
||||
{
|
||||
act = Some(("insert", e.name.clone()));
|
||||
@@ -1016,7 +1016,7 @@ mod web {
|
||||
|
||||
/// Whether the shell already has the on-demand face the live language needs.
|
||||
fn script_font_ready(&self) -> bool {
|
||||
match solidcraft_ui_egui::fonts::web_script_font_kind(solidcraft_ui_egui::i18n::effective_language(&self.app.preferences.language)) {
|
||||
match solidcraft_ui_egui::fonts::web_script_font_kind(solidcraft_ui_text::effective_language(&self.app.preferences.language)) {
|
||||
Some("zh") => self.chinese_bytes.is_some() && self.chinese_kind == Some("zh"),
|
||||
Some("zh-TW") => self.chinese_bytes.is_some() && self.chinese_kind == Some("zh-TW"),
|
||||
Some("ja") => self.cjk_bytes.is_some(),
|
||||
@@ -1058,7 +1058,7 @@ mod web {
|
||||
false
|
||||
}
|
||||
};
|
||||
let effective = solidcraft_ui_egui::i18n::effective_language(&self.app.preferences.language);
|
||||
let effective = solidcraft_ui_text::effective_language(&self.app.preferences.language);
|
||||
if !installed && solidcraft_ui_egui::fonts::needs_cjk_font(effective) {
|
||||
log::warn!("script font unavailable; showing English for this session");
|
||||
}
|
||||
@@ -1067,13 +1067,13 @@ mod web {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
let ready = self.script_font_ready();
|
||||
let effective = solidcraft_ui_egui::i18n::effective_language(&self.app.preferences.language);
|
||||
let effective = solidcraft_ui_text::effective_language(&self.app.preferences.language);
|
||||
if self.font.tick(&mut self.app.preferences.language, ready) {
|
||||
self.fetch_font(ctx, solidcraft_ui_egui::fonts::web_script_font_kind(effective).unwrap_or("ja"));
|
||||
}
|
||||
// The CJK face leads only a CJK UI: its Cyrillic glyphs are full-width, so switching
|
||||
// to Ukrainian (or English) must put the Latin face back in front.
|
||||
if solidcraft_ui_egui::i18n::effective_language(&self.app.preferences.language) != self.font_language {
|
||||
if solidcraft_ui_text::effective_language(&self.app.preferences.language) != self.font_language {
|
||||
self.apply_fonts(ctx);
|
||||
}
|
||||
}
|
||||
@@ -1081,7 +1081,7 @@ mod web {
|
||||
/// Set up the UI fonts for the live language, keeping fetched script fonts as fallbacks
|
||||
/// (Chinese leads a Chinese UI; see [`solidcraft_ui_egui::fonts::install_web_script_fonts`]).
|
||||
fn apply_fonts(&mut self, ctx: &egui::Context) {
|
||||
let effective = solidcraft_ui_egui::i18n::effective_language(&self.app.preferences.language);
|
||||
let effective = solidcraft_ui_text::effective_language(&self.app.preferences.language);
|
||||
if self.cjk_bytes.is_none() && self.chinese_bytes.is_none() {
|
||||
solidcraft_ui_egui::fonts::setup_fonts(ctx);
|
||||
} else {
|
||||
@@ -1149,7 +1149,7 @@ mod web {
|
||||
UNSAVED.with(|u| u.set(self.asks_before_unload()));
|
||||
if self.launch.embed {
|
||||
ui_kit::Container::new(ui.ctx(), ui_kit::Surface::Chrome).edge("embed_header", ui_kit::Edge::Top).show(ui, |ui| {
|
||||
if ui_kit::button(ui, solidcraft_ui_egui::i18n::tr("web.open_full"), ui_kit::Button::Normal).clicked()
|
||||
if ui_kit::button(ui, solidcraft_ui_text::tr("web.open_full"), ui_kit::Button::Normal).clicked()
|
||||
&& let (Some(window), Some(url)) = (web_sys::window(), &self.full_url)
|
||||
{
|
||||
let _ = window.open_with_url_and_target_and_features(url, "_blank", "noopener");
|
||||
@@ -1225,6 +1225,7 @@ mod web {
|
||||
|
||||
pub fn start() {
|
||||
eframe::WebLogger::init(log::LevelFilter::Info).ok();
|
||||
solidcraft_ui_egui::i18n::install();
|
||||
wasm_bindgen_futures::spawn_local(async {
|
||||
let Some(window) = web_sys::window() else { return };
|
||||
let launch = crate::Launch::parse(&query());
|
||||
|
||||
@@ -34,6 +34,7 @@ solidcraft-engine = { workspace = true }
|
||||
# The D3D12 shader compiler the window starts with (#6756).
|
||||
solidcraft-gpu = { workspace = true }
|
||||
solidcraft-ui-egui = { workspace = true }
|
||||
solidcraft-ui-text = { workspace = true }
|
||||
solidcraft-space-mouse = { workspace = true }
|
||||
eframe = { workspace = true, features = ["default_fonts", "wgpu", "accesskit", "wayland", "x11", "links"] }
|
||||
egui = { workspace = true }
|
||||
|
||||
@@ -518,7 +518,7 @@ fn services(cc: &eframe::CreationContext<'_>, folder: &DialogFolder) -> Services
|
||||
// The groups come from the format registry, so a new format joins this dialog by itself.
|
||||
let mut everything = io::formats::open_extensions(true);
|
||||
everything.extend(["dxf", "svg"]);
|
||||
dialog = dialog.add_filter(solidcraft_ui_egui::i18n::tr("file.open.drawing_filter"), &case_variants(&everything));
|
||||
dialog = dialog.add_filter(solidcraft_ui_text::tr("file.open.drawing_filter"), &case_variants(&everything));
|
||||
for (label, exts) in io::formats::import_filters(true) {
|
||||
dialog = dialog.add_filter(label, &case_variants(&exts));
|
||||
}
|
||||
@@ -945,6 +945,7 @@ fn main() -> eframe::Result {
|
||||
#[cfg(target_os = "macos")]
|
||||
objc2_foundation::NSProcessInfo::processInfo().setProcessName(objc2_foundation::ns_string!("SolidCraft"));
|
||||
mark("main");
|
||||
solidcraft_ui_egui::i18n::install();
|
||||
// On OCLP-patched Macs, wgpu picks a Metal Shading Language standard that the system's
|
||||
// compiler refuses (`invalid value 'metal3.2'`), because the patched OS reports a newer
|
||||
// macOS version than the GPU's Metal compiler supports. Re-launching with
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"ar": {
|
||||
"material.editor.remove": "إزالة"
|
||||
},
|
||||
"bg": {
|
||||
"material.editor.remove": "Remove"
|
||||
},
|
||||
"bs": {
|
||||
"material.editor.remove": "Ukloni"
|
||||
},
|
||||
"ca": {
|
||||
"material.editor.remove": "Eliminar"
|
||||
},
|
||||
"cs": {
|
||||
"material.editor.remove": "Odebrat"
|
||||
},
|
||||
"da": {
|
||||
"material.editor.remove": "Fjern"
|
||||
},
|
||||
"de": {
|
||||
"material.editor.remove": "Entfernen"
|
||||
},
|
||||
"en": {
|
||||
"material.editor.remove": "Remove"
|
||||
},
|
||||
"es": {
|
||||
"material.editor.remove": "Eliminar"
|
||||
},
|
||||
"fi": {
|
||||
"material.editor.remove": "Poista"
|
||||
},
|
||||
"fr": {
|
||||
"material.editor.remove": "Retirer"
|
||||
},
|
||||
"gl": {
|
||||
"material.editor.remove": "Quitar"
|
||||
},
|
||||
"he": {
|
||||
"material.editor.remove": "הסרה"
|
||||
},
|
||||
"hr": {
|
||||
"material.editor.remove": "Ukloni"
|
||||
},
|
||||
"hu": {
|
||||
"material.editor.remove": "Eltávolítás"
|
||||
},
|
||||
"id": {
|
||||
"material.editor.remove": "Hapus"
|
||||
},
|
||||
"it": {
|
||||
"material.editor.remove": "Rimuovi"
|
||||
},
|
||||
"ja": {
|
||||
"material.editor.remove": "削除"
|
||||
},
|
||||
"ka": {
|
||||
"material.editor.remove": "წაშლა"
|
||||
},
|
||||
"ms": {
|
||||
"material.editor.remove": "Buang"
|
||||
},
|
||||
"nb": {
|
||||
"material.editor.remove": "Fjern"
|
||||
},
|
||||
"nl": {
|
||||
"material.editor.remove": "Verwijderen"
|
||||
},
|
||||
"pl": {
|
||||
"material.editor.remove": "Usuń"
|
||||
},
|
||||
"pt-BR": {
|
||||
"material.editor.remove": "Remover"
|
||||
},
|
||||
"ro": {
|
||||
"material.editor.remove": "Elimină"
|
||||
},
|
||||
"ru": {
|
||||
"material.editor.remove": "Убрать"
|
||||
},
|
||||
"sr": {
|
||||
"material.editor.remove": "Уклони"
|
||||
},
|
||||
"sv": {
|
||||
"material.editor.remove": "Ta bort"
|
||||
},
|
||||
"tr": {
|
||||
"material.editor.remove": "Kaldır"
|
||||
},
|
||||
"uk": {
|
||||
"material.editor.remove": "Вилучити"
|
||||
},
|
||||
"vi": {
|
||||
"material.editor.remove": "Xóa"
|
||||
},
|
||||
"zh-TW": {
|
||||
"material.editor.remove": "刪除"
|
||||
},
|
||||
"zh": {
|
||||
"material.editor.remove": "删除"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "solidcraft-color"
|
||||
description = "Colour rules and data any crate may share: the sRGB transfer function and the surface analysis palette"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,20 @@
|
||||
# solidcraft-color
|
||||
|
||||
Colour rules and data that any crate may share. It has no dependencies and holds no app types,
|
||||
so the renderer, the document, the importers and the UI theme all use the same rule and the same
|
||||
colours instead of keeping copies.
|
||||
|
||||
The sRGB transfer function (IEC 61966-2-1) lives here once: every encode and decode in Rust goes
|
||||
through `srgb`, so a change to the rule is one change. Shaders can't call Rust and keep their own
|
||||
copies.
|
||||
|
||||
## API
|
||||
|
||||
| Item | What it is |
|
||||
|---|---|
|
||||
| `srgb::encode(linear)` | Linear light (0..1) to an sRGB value (0..1) |
|
||||
| `srgb::decode(encoded)` | An sRGB value (0..1) to linear light |
|
||||
| `srgb::encode8(linear)` | Linear light to the nearest 8-bit sRGB code (out of range clamps, NaN reads as 0) |
|
||||
| `srgb::decode8(code)` | An 8-bit sRGB code to linear light |
|
||||
| `AnalysisColors` | The surface analysis palette (draft, accessibility, curvature, hatch), straight sRGB 0..1 as the shader takes it; the theme fills it and the renderer reads it |
|
||||
| `AnalysisColors::DEFAULT` | The light theme's colours with no colour vision mode, as headless pictures use |
|
||||
@@ -0,0 +1,32 @@
|
||||
//! The colours a surface analysis is shaded in.
|
||||
|
||||
/// Surface analysis colours, straight sRGB 0..1 as the shader takes them.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct AnalysisColors {
|
||||
/// Draft Analysis: at least the draft angle, between, at most minus the angle.
|
||||
pub draft: [[f32; 3]; 3],
|
||||
/// Accessibility Analysis: reachable, blocked.
|
||||
pub access: [[f32; 3]; 2],
|
||||
/// Curvature Map: flat, the middle of the scale, at or below the analysis radius. Dark blue
|
||||
/// to light yellow in one straight blend, so the lightness rises the whole way and the scale
|
||||
/// reads with hue lost too (#975).
|
||||
pub curvature: [[f32; 3]; 3],
|
||||
/// Stripe the negative draft and blocked faces too, so colour is not the only cue.
|
||||
pub hatch: bool,
|
||||
}
|
||||
|
||||
impl AnalysisColors {
|
||||
/// The light theme's colours (no colour vision mode): what headless pictures use.
|
||||
pub const DEFAULT: AnalysisColors = AnalysisColors {
|
||||
draft: [[0.20, 0.75, 0.30], [0.95, 0.80, 0.15], [0.85, 0.22, 0.20]],
|
||||
access: [[0.20, 0.75, 0.30], [0.85, 0.22, 0.20]],
|
||||
curvature: [[0.08, 0.12, 0.45], [0.53, 0.51, 0.425], [0.98, 0.90, 0.40]],
|
||||
hatch: false,
|
||||
};
|
||||
}
|
||||
|
||||
impl Default for AnalysisColors {
|
||||
fn default() -> Self {
|
||||
AnalysisColors::DEFAULT
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
//! Colour rules and data shared by every crate: the sRGB transfer function ([`srgb`]) and the surface analysis palette
|
||||
//! ([`AnalysisColors`]). No app types and no dependencies, so the renderer, the document and the theme all read the same
|
||||
//! rule and the same colours.
|
||||
|
||||
mod analysis;
|
||||
pub mod srgb;
|
||||
|
||||
pub use analysis::AnalysisColors;
|
||||
@@ -10,6 +10,7 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
solidcore-record = { workspace = true }
|
||||
solidcraft-color = { workspace = true }
|
||||
solidcraft-geom = { workspace = true }
|
||||
solidcraft-sketch = { workspace = true }
|
||||
solidcraft-kernel = { workspace = true }
|
||||
|
||||
@@ -154,7 +154,7 @@ impl VisualPreset {
|
||||
match self.recipe {
|
||||
Recipe::Opaque(r) => r.color,
|
||||
// A UI swatch of the transmission values, not rendered glass or absorption.
|
||||
Recipe::Glass(r) => r.transmission_linear_rgb.map(solidcraft_geom::srgb::encode8),
|
||||
Recipe::Glass(r) => r.transmission_linear_rgb.map(solidcraft_color::srgb::encode8),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ required-features = ["solver-profiling"]
|
||||
|
||||
[dependencies]
|
||||
solidcore-record = { workspace = true }
|
||||
solidcraft-color = { workspace = true }
|
||||
solidcraft-geom = { workspace = true }
|
||||
solidcraft-sketch = { workspace = true }
|
||||
solidcraft-kernel = { workspace = true }
|
||||
|
||||
@@ -1721,7 +1721,7 @@ fn cpu_saved_thin_sheet_uses_its_authored_ior_beer_thickness_and_roundtrips_offl
|
||||
let a = (-sigma * 2.0).exp();
|
||||
let denominator = 1.0 - f * f * a * a;
|
||||
let scene = f + (1.0 - f).powi(2) * (f * a * a + a) / denominator;
|
||||
solidcraft_geom::srgb::encode8(scene / (1.0 + scene))
|
||||
solidcraft_color::srgb::encode8(scene / (1.0 + scene))
|
||||
});
|
||||
assert_eq!(decoded.get_pixel(0, 0).0, [rgb[0], rgb[1], rgb[2], 255]);
|
||||
assert!(Arc::ptr_eq(&before, &s.doc));
|
||||
|
||||
@@ -401,7 +401,7 @@ pub(super) fn apply_appearance(s: &mut Session, params: &Value) -> Result<Value>
|
||||
let value = item("surfaces", &input.id, command)?;
|
||||
let lo = triple(&value["base_linear_low"], command)?;
|
||||
let hi = triple(&value["base_linear_high"], command)?;
|
||||
let color = std::array::from_fn::<_, 3, _>(|i| crate::geom::srgb::encode8((lo[i] + hi[i]) * 0.5));
|
||||
let color = std::array::from_fn::<_, 3, _>(|i| solidcraft_color::srgb::encode8((lo[i] + hi[i]) * 0.5));
|
||||
(
|
||||
value,
|
||||
LibraryLook {
|
||||
|
||||
@@ -6,8 +6,9 @@ use solidcraft_geom::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
use solidcore_record::{Plane, Vec3};
|
||||
use solidcraft_color::AnalysisColors;
|
||||
use solidcraft_geom::Aabb3;
|
||||
use solidcraft_render::{AnalysisColors, Camera, Rgb, Scene, SceneAnalysis, SceneLine, SceneMesh};
|
||||
use solidcraft_render::{Camera, Rgb, Scene, SceneAnalysis, SceneLine, SceneMesh};
|
||||
use solidcraft_sketch::Sketch;
|
||||
|
||||
use crate::{Result, Sel, Session};
|
||||
|
||||
@@ -222,7 +222,7 @@ const MARK: Palette = Palette {
|
||||
cap: Rgb(55, 56, 57),
|
||||
hatch: [Rgb(61, 62, 63), Rgb(64, 65, 66)],
|
||||
isocurve: Rgb(58, 59, 60),
|
||||
analysis: solidcraft_render::AnalysisColors {
|
||||
analysis: solidcraft_color::AnalysisColors {
|
||||
draft: [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.7, 0.8, 0.9]],
|
||||
access: [[0.11, 0.12, 0.13], [0.14, 0.15, 0.16]],
|
||||
curvature: [[0.17, 0.18, 0.19], [0.21, 0.22, 0.23], [0.24, 0.25, 0.26]],
|
||||
@@ -253,7 +253,7 @@ fn the_default_palette_is_the_colour_table() {
|
||||
(colors::SKETCH_PROJECTED, colors::SKETCH_LOST, colors::SKETCH_CONSTRUCTION, colors::SKETCH_CENTERLINE)
|
||||
);
|
||||
assert_eq!((p.axes, p.cap, p.isocurve), ([colors::AXIS_X, colors::AXIS_Y, colors::AXIS_Z], colors::SECTION_CAP, colors::ISOCURVE));
|
||||
assert_eq!(p.analysis, solidcraft_render::AnalysisColors::DEFAULT);
|
||||
assert_eq!(p.analysis, solidcraft_color::AnalysisColors::DEFAULT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -40,7 +40,6 @@ mod profile;
|
||||
pub mod section;
|
||||
pub mod simplify;
|
||||
pub mod spline_space;
|
||||
pub mod srgb;
|
||||
pub mod stderr;
|
||||
pub mod tess;
|
||||
pub mod title_block;
|
||||
|
||||
@@ -11,6 +11,7 @@ workspace = true
|
||||
[dependencies]
|
||||
solidcore-record = { workspace = true }
|
||||
robust = "1.2"
|
||||
solidcraft-color = { workspace = true }
|
||||
solidcraft-geom = { workspace = true }
|
||||
solidcraft-render = { workspace = true }
|
||||
wgpu = { workspace = true }
|
||||
|
||||
@@ -4528,7 +4528,7 @@ fn uniform_bytes_placing(cb: &Frame, w: f32, h: f32, linear: bool, reversed: boo
|
||||
/// The shader's analysis colours (`ana_c`: draft positive, between, negative; reachable,
|
||||
/// blocked; curvature flat, middle, tight; alpha 1) and flags (`ana_f.x`: 1 to stripe the bad
|
||||
/// faces).
|
||||
pub fn analysis_colors(c: &solidcraft_render::AnalysisColors) -> [f32; 36] {
|
||||
pub fn analysis_colors(c: &solidcraft_color::AnalysisColors) -> [f32; 36] {
|
||||
let mut v = [0.0; 36];
|
||||
let colors = c.draft.iter().chain(&c.access).chain(&c.curvature).flat_map(|rgb| [rgb[0], rgb[1], rgb[2], 1.0]);
|
||||
for (slot, x) in v.iter_mut().zip(colors) {
|
||||
|
||||
@@ -301,7 +301,7 @@ fn pictures_show_the_surface_analysis() {
|
||||
s.execute("inspect.curvature_map", &json!({})).unwrap();
|
||||
let curvature = picture(&s);
|
||||
let near = |p: [u8; 4], c: [u8; 3]| (0..3).all(|k| (i32::from(p[k]) - i32::from(c[k])).abs() < 60);
|
||||
let flat = solidcraft_engine::render::AnalysisColors::DEFAULT.curvature[0].map(|v| (v * 255.0).round() as u8);
|
||||
let flat = solidcraft_color::AnalysisColors::DEFAULT.curvature[0].map(|v| (v * 255.0).round() as u8);
|
||||
let flats = |img: &[u8]| pixels(img).filter(|p| near(*p, flat)).count();
|
||||
// The flat colour is dark, so the plain picture's edge lines come near it in places.
|
||||
assert!(
|
||||
@@ -317,7 +317,7 @@ fn pictures_show_the_surface_analysis() {
|
||||
assert!(black > 500 && white > 500, "zebra: {black} black, {white} white pixels");
|
||||
s.execute("inspect.accessibility", &json!({})).unwrap();
|
||||
let access = picture(&s);
|
||||
let [ok, blocked] = colors_of(solidcraft_engine::render::AnalysisColors::DEFAULT.access);
|
||||
let [ok, blocked] = colors_of(solidcraft_color::AnalysisColors::DEFAULT.access);
|
||||
let (reachable, under) = (pixels(&access).filter(|p| near(*p, ok)).count(), pixels(&access).filter(|p| near(*p, blocked)).count());
|
||||
assert!(reachable > 1000 && under > 1000, "accessibility: {reachable} reachable, {under} blocked pixels");
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ trust-unknown-source = []
|
||||
|
||||
[dependencies]
|
||||
solidcore-record = { workspace = true }
|
||||
solidcraft-color = { workspace = true }
|
||||
solidcraft-geom = { workspace = true }
|
||||
solidcraft-kernel = { workspace = true }
|
||||
solidcraft-doc = { workspace = true }
|
||||
|
||||
@@ -842,7 +842,7 @@ impl<'d> Reader<'d, '_, '_> {
|
||||
|
||||
/// A linear base colour channel as an 8-bit sRGB code; one that isn't a number reads as 0.8.
|
||||
fn srgb(c: f32) -> u8 {
|
||||
solidcraft_geom::srgb::encode8(if c.is_finite() { f64::from(c) } else { 0.8 })
|
||||
solidcraft_color::srgb::encode8(if c.is_finite() { f64::from(c) } else { 0.8 })
|
||||
}
|
||||
|
||||
/// A material's look: base colour, alpha when blended, metallic/roughness when opaque.
|
||||
|
||||
@@ -14,6 +14,7 @@ raytrace-cpu = ["dep:robust"]
|
||||
|
||||
[dependencies]
|
||||
solidcore-record = { workspace = true }
|
||||
solidcraft-color = { workspace = true }
|
||||
solidcraft-geom = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -1,37 +1,7 @@
|
||||
//! Surface analysis as a view draws it: the shading mode and the colours it uses.
|
||||
|
||||
use solidcore_record::Vec3;
|
||||
|
||||
/// Surface analysis colours, straight sRGB 0..1 as the shader takes them.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct AnalysisColors {
|
||||
/// Draft Analysis: at least the draft angle, between, at most minus the angle.
|
||||
pub draft: [[f32; 3]; 3],
|
||||
/// Accessibility Analysis: reachable, blocked.
|
||||
pub access: [[f32; 3]; 2],
|
||||
/// Curvature Map: flat, the middle of the scale, at or below the analysis radius. Dark blue
|
||||
/// to light yellow in one straight blend, so the lightness rises the whole way and the scale
|
||||
/// reads with hue lost too (#975).
|
||||
pub curvature: [[f32; 3]; 3],
|
||||
/// Stripe the negative draft and blocked faces too, so colour is not the only cue.
|
||||
pub hatch: bool,
|
||||
}
|
||||
|
||||
impl AnalysisColors {
|
||||
/// The light theme's colours (no colour vision mode): what headless pictures use.
|
||||
pub const DEFAULT: AnalysisColors = AnalysisColors {
|
||||
draft: [[0.20, 0.75, 0.30], [0.95, 0.80, 0.15], [0.85, 0.22, 0.20]],
|
||||
access: [[0.20, 0.75, 0.30], [0.85, 0.22, 0.20]],
|
||||
curvature: [[0.08, 0.12, 0.45], [0.53, 0.51, 0.425], [0.98, 0.90, 0.40]],
|
||||
hatch: false,
|
||||
};
|
||||
}
|
||||
|
||||
impl Default for AnalysisColors {
|
||||
fn default() -> Self {
|
||||
AnalysisColors::DEFAULT
|
||||
}
|
||||
}
|
||||
use solidcraft_color::AnalysisColors;
|
||||
|
||||
/// A way of shading the model's faces to judge their shape.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
|
||||
@@ -10,7 +10,7 @@ use super::{CompletePass, MAX_BYTES, invalid, limit};
|
||||
use crate::job::encoding::PngEncoding;
|
||||
use crate::job::{CancelToken, CpuHost, ErrorCode, JobError, Phase, Progress, Reservation, Result};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_geom::srgb::encode8;
|
||||
use solidcraft_color::srgb::encode8;
|
||||
|
||||
/// Encoded bytes retain the actual existing host lease. No detached byte getter.
|
||||
pub struct CompletedPng {
|
||||
|
||||
@@ -2757,7 +2757,7 @@ mod retained_request_tests {
|
||||
assert!(frame.linear_exr().is_some());
|
||||
let color = [128, 64, 255].map(|v| {
|
||||
let x = crate::surface::srgb8_to_linear(v) as f32 as f64;
|
||||
solidcraft_geom::srgb::encode8(x / (1.0 + x))
|
||||
solidcraft_color::srgb::encode8(x / (1.0 + x))
|
||||
});
|
||||
assert_eq!(image::load_from_memory(frame.png()).unwrap().to_rgba8().get_pixel(0, 0).0, [color[0], color[1], color[2], 255]);
|
||||
let held = job.clone();
|
||||
|
||||
@@ -375,7 +375,7 @@ impl PhotographicState {
|
||||
let grain = (random(index as u32 ^ self.options.seed) - 0.5) * self.options.grain * (luminance + 0.03).sqrt();
|
||||
for channel in 0..3 {
|
||||
let linear = (rgb[channel] + flare[channel] + grain).max(0.0);
|
||||
self.output.push(solidcraft_geom::srgb::encode8(linear));
|
||||
self.output.push(solidcraft_color::srgb::encode8(linear));
|
||||
}
|
||||
self.output.push(255);
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ impl Display {
|
||||
let strength = self.finishing.bloom.map_or(0.0, |b| b.strength);
|
||||
value += strength * self.tone_map.map(f64::from(glow[channel]) * self.exposure * self.finishing.rgb_gain[channel]);
|
||||
}
|
||||
*out = solidcraft_geom::srgb::encode8(value);
|
||||
*out = solidcraft_color::srgb::encode8(value);
|
||||
}
|
||||
rgba[3] = if self.transparent { (alpha * 255.0).round() as u8 } else { 255 };
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ pub mod texture_lod;
|
||||
pub mod texture_sampling;
|
||||
pub mod transmission;
|
||||
|
||||
pub use analysis::{AnalysisColors, AnalysisMode, SceneAnalysis};
|
||||
pub use analysis::{AnalysisMode, SceneAnalysis};
|
||||
pub use backend::{
|
||||
Availability, BackendDescriptor, BackendId, ExecutorDescriptor, ExecutorId, MAX_RENDER_ID_BYTES, RenderFeature, RenderFormat, RenderLimits,
|
||||
RenderMode, RenderOperation, RenderPolicy, RenderProfile, RenderRequest, ResolveError, ResolvedRequest, ScratchLayout, builtin_backend,
|
||||
|
||||
@@ -15,8 +15,8 @@ use crate::{
|
||||
surface::{OpaqueSurface, srgb8_to_linear},
|
||||
};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_color::srgb::encode8;
|
||||
use solidcraft_geom::prelude::*;
|
||||
use solidcraft_geom::srgb::encode8;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "environment_image_tests.rs"]
|
||||
|
||||
@@ -111,7 +111,7 @@ impl StagePanel {
|
||||
mesh.tri_face.extend([0, 0]);
|
||||
// The surface carries exact linear reflectance. This display colour is not
|
||||
// used as an additional multiplier by PBR consumers.
|
||||
let color = self.reflectance_linear.map(solidcraft_geom::srgb::encode8);
|
||||
let color = self.reflectance_linear.map(solidcraft_color::srgb::encode8);
|
||||
Ok(ProductMesh {
|
||||
name: format!("__render_stage__/{}", self.id),
|
||||
mesh: Arc::new(mesh),
|
||||
|
||||
@@ -160,8 +160,8 @@ impl OpaqueSurface {
|
||||
}
|
||||
}
|
||||
|
||||
/// Exact piecewise sRGB decoding of an existing 8-bit color channel ([`solidcraft_geom::srgb::decode8`]).
|
||||
pub use solidcraft_geom::srgb::decode8 as srgb8_to_linear;
|
||||
/// Exact piecewise sRGB decoding of an existing 8-bit color channel ([`solidcraft_color::srgb::decode8`]).
|
||||
pub use solidcraft_color::srgb::decode8 as srgb8_to_linear;
|
||||
|
||||
/// Normalize finite directions without overflow or an arbitrary magnitude threshold.
|
||||
pub fn unit_direction(v: Vec3) -> Result<Vec3, SurfaceError> {
|
||||
|
||||
@@ -179,7 +179,7 @@ impl NormalizationSource {
|
||||
let linear = match conversion {
|
||||
ColorConversion::RawData => bounded,
|
||||
ColorConversion::PngGammaSrgbPrimaries { encoding_gamma } => bounded.powf(1.0 / encoding_gamma),
|
||||
_ => solidcraft_geom::srgb::decode(bounded),
|
||||
_ => solidcraft_color::srgb::decode(bounded),
|
||||
};
|
||||
if !linear.is_finite() || !(0.0..=1.0).contains(&linear) {
|
||||
return Err(invalid("normalization produced invalid linear/data channels"));
|
||||
|
||||
@@ -11,6 +11,10 @@ workspace = true
|
||||
[dependencies]
|
||||
solidcore-record = { workspace = true }
|
||||
solidcraft-engine = { workspace = true }
|
||||
# The widget registry (handles, rects, counts) the UI kit publishes into.
|
||||
solidcraft-ui-registry = { workspace = true }
|
||||
solidcraft-color = { workspace = true }
|
||||
solidcraft-ui-text = { workspace = true }
|
||||
egui = { workspace = true }
|
||||
egui-wgpu = { workspace = true }
|
||||
solidcraft-gpu = { workspace = true }
|
||||
@@ -25,10 +29,8 @@ image = { workspace = true, features = ["jpeg", "ico", "gif", "webp", "bmp", "ti
|
||||
ttf-parser = { workspace = true }
|
||||
# Right-to-left interface text (Hebrew): the Unicode Bidirectional Algorithm puts translated
|
||||
# strings in display order, which egui does not do itself (MIT OR Apache-2.0).
|
||||
unicode-bidi = "0.3"
|
||||
# The system's preferred languages on a first start (MIT OR Apache-2.0, maintained by
|
||||
# 1Password): the OS locale on desktop and, with `js`, `navigator.languages` in the browser (#10138).
|
||||
sys-locale = { version = "0.3.2", features = ["js"] }
|
||||
semver = { workspace = true }
|
||||
# Updates: the downloaded package's SHA-256 (against the release's SHA256SUMS.txt), its optional
|
||||
# minisign signature (MIT), and the portable .zip's executables.
|
||||
|
||||
@@ -248,7 +248,7 @@ impl AcpBackend {
|
||||
// Drain replies first: an acknowledgement already waiting must keep the connection.
|
||||
if self.cancelling.is_some_and(|(_, started)| now.saturating_duration_since(started) >= CANCEL_GRACE) {
|
||||
self.proc.shutdown(false);
|
||||
out.push(self.finish_exit(vec![crate::i18n::tr("ui.agent_chat_cancel_timeout").to_string()]));
|
||||
out.push(self.finish_exit(vec![solidcraft_ui_text::tr("ui.agent_chat_cancel_timeout").to_string()]));
|
||||
}
|
||||
out
|
||||
}
|
||||
@@ -359,9 +359,9 @@ pub fn test_agent(argv: &[String], cwd: &std::path::Path) -> Result<String, Test
|
||||
}
|
||||
}
|
||||
Ok(FromAgent::Exited(tail)) => {
|
||||
return Err(TestError::Other(tail.last().cloned().unwrap_or_else(|| crate::i18n::tr("ui.agent_chat_exited").to_string())));
|
||||
return Err(TestError::Other(tail.last().cloned().unwrap_or_else(|| solidcraft_ui_text::tr("ui.agent_chat_exited").to_string())));
|
||||
}
|
||||
Err(_) => return Err(TestError::Other(crate::i18n::tr("ui.ai_no_answer").to_string())),
|
||||
Err(_) => return Err(TestError::Other(solidcraft_ui_text::tr("ui.ai_no_answer").to_string())),
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -372,7 +372,7 @@ pub fn test_agent(argv: &[String], cwd: &std::path::Path) -> Result<String, Test
|
||||
p.send(&acp::request(2, "session/new", acp::session_new_params(&session_cwd, None, &v, acp::SYSTEM_NOTE)));
|
||||
wait_for(&p, 2).map(|_| {
|
||||
let who = acp::agent_info(&v);
|
||||
crate::i18n::format("ui.ai_test_acp_ok", &[("agent", if who.is_empty() { "?" } else { &who })])
|
||||
solidcraft_ui_text::format("ui.ai_test_acp_ok", &[("agent", if who.is_empty() { "?" } else { &who })])
|
||||
})
|
||||
});
|
||||
p.shutdown(true);
|
||||
|
||||
@@ -32,7 +32,7 @@ impl Mode {
|
||||
Mode::ALL.into_iter().find(|m| m.id() == s)
|
||||
}
|
||||
pub fn label(self) -> &'static str {
|
||||
crate::i18n::tr(match self {
|
||||
solidcraft_ui_text::tr(match self {
|
||||
Mode::Ask => "ui.ai_mode_ask",
|
||||
Mode::Edits => "ui.ai_mode_edits",
|
||||
Mode::All => "ui.ai_mode_all",
|
||||
@@ -217,7 +217,7 @@ impl Entry {
|
||||
match self.kind {
|
||||
Kind::Api | Kind::Anthropic => format!("{} · {}", self.model, self.base_url),
|
||||
Kind::Acp => match (&self.problem, &self.version) {
|
||||
(Some(p), _) => crate::i18n::format("ui.agent_chat_cannot_start", &[("error", p)]),
|
||||
(Some(p), _) => solidcraft_ui_text::format("ui.agent_chat_cannot_start", &[("error", p)]),
|
||||
(None, Some(v)) => v.clone(),
|
||||
(None, None) => super::panel::short_command(&self.argv.join(" ")),
|
||||
},
|
||||
@@ -228,7 +228,7 @@ impl Entry {
|
||||
pub fn problem_hint(&self) -> Option<String> {
|
||||
let problem = self.problem.as_deref()?;
|
||||
let program = self.argv.first().map(String::as_str).unwrap_or_default();
|
||||
Some(crate::i18n::format(
|
||||
Some(solidcraft_ui_text::format(
|
||||
"ui.agent_chat_reinstall",
|
||||
&[("agent", &self.name), ("program", program), ("error", problem), ("install", self.install)],
|
||||
))
|
||||
@@ -381,7 +381,7 @@ pub fn normalize_url(url: &str) -> Result<String, String> {
|
||||
let u = url.trim().trim_end_matches('/').to_string();
|
||||
let lower = u.to_ascii_lowercase();
|
||||
if !(lower.starts_with("http://") || lower.starts_with("https://")) || u.len() < 10 || u.contains(char::is_whitespace) {
|
||||
return Err(crate::i18n::tr("ui.ai_bad_url").to_string());
|
||||
return Err(solidcraft_ui_text::tr("ui.ai_bad_url").to_string());
|
||||
}
|
||||
Ok(u)
|
||||
}
|
||||
|
||||
@@ -672,7 +672,7 @@ pub struct ApiBackend {
|
||||
impl ApiBackend {
|
||||
pub fn start(cfg: ApiConfig, ctx: StartContext) -> Result<ApiBackend, String> {
|
||||
if cfg.key.is_some() && !super::agents::key_allowed_for(&cfg.base_url, cfg.protocol == Protocol::OpenAi && cfg.allow_insecure_http) {
|
||||
return Err(crate::i18n::tr("ui.ai_key_needs_https").to_string());
|
||||
return Err(solidcraft_ui_text::tr("ui.ai_key_needs_https").to_string());
|
||||
}
|
||||
// One stable session ID per chat for OpenCode Go's routing and prompt cache.
|
||||
let session_id = new_session_id();
|
||||
@@ -1295,7 +1295,7 @@ impl Worker {
|
||||
|
||||
/// What the chat says when a request ran out of time: the limit, and where to raise it.
|
||||
pub fn timed_out(limit_s: u64) -> String {
|
||||
crate::i18n::format("ui.ai_request_timed_out", &[("seconds", &limit_s.to_string())])
|
||||
solidcraft_ui_text::format("ui.ai_request_timed_out", &[("seconds", &limit_s.to_string())])
|
||||
}
|
||||
|
||||
/// A failed request as people read it: one that ran into the endpoint's request timeout (the
|
||||
@@ -1309,10 +1309,10 @@ pub fn http_error(status: u16, body: &Value) -> String {
|
||||
let detail =
|
||||
body.pointer("/error/message").or_else(|| body.get("error")).and_then(Value::as_str).unwrap_or("").chars().take(300).collect::<String>();
|
||||
let what = match status {
|
||||
401 | 403 => crate::i18n::tr("ui.ai_http_key"),
|
||||
404 => crate::i18n::tr("ui.ai_http_not_found"),
|
||||
429 => crate::i18n::tr("ui.ai_http_busy"),
|
||||
413 => crate::i18n::tr("ui.ai_http_too_large"),
|
||||
401 | 403 => solidcraft_ui_text::tr("ui.ai_http_key"),
|
||||
404 => solidcraft_ui_text::tr("ui.ai_http_not_found"),
|
||||
429 => solidcraft_ui_text::tr("ui.ai_http_busy"),
|
||||
413 => solidcraft_ui_text::tr("ui.ai_http_too_large"),
|
||||
_ => "",
|
||||
};
|
||||
format!(
|
||||
@@ -1643,7 +1643,7 @@ fn anthropic_headers(base_url: &str, key: Option<&str>) -> Result<Vec<(String, S
|
||||
let mut headers = vec![("anthropic-version".to_string(), ANTHROPIC_VERSION.to_string())];
|
||||
if let Some(k) = key {
|
||||
if !super::agents::key_safe_for(base_url) {
|
||||
return Err(crate::i18n::tr("ui.ai_key_needs_https").to_string());
|
||||
return Err(solidcraft_ui_text::tr("ui.ai_key_needs_https").to_string());
|
||||
}
|
||||
headers.push(("x-api-key".to_string(), k.to_string()));
|
||||
}
|
||||
@@ -1670,7 +1670,7 @@ pub fn anthropic_model_list(http: &HttpFn, base_url: &str, key: Option<&str>) ->
|
||||
pub fn test_anthropic_endpoint(http: &HttpFn, base_url: &str, key: Option<&str>, model: &str) -> Result<String, String> {
|
||||
let model = model.trim();
|
||||
if model.is_empty() {
|
||||
return Err(crate::i18n::tr("ui.ai_need_model").to_string());
|
||||
return Err(solidcraft_ui_text::tr("ui.ai_need_model").to_string());
|
||||
}
|
||||
let mut headers = anthropic_headers(base_url, key)?;
|
||||
headers.push(("Content-Type".to_string(), "application/json".to_string()));
|
||||
@@ -1687,7 +1687,7 @@ pub fn test_anthropic_endpoint(http: &HttpFn, base_url: &str, key: Option<&str>,
|
||||
if status != 200 {
|
||||
return Err(http_error(status, &v));
|
||||
}
|
||||
Ok(crate::i18n::format("ui.ai_test_answered", &[("model", model)]))
|
||||
Ok(solidcraft_ui_text::format("ui.ai_test_answered", &[("model", model)]))
|
||||
}
|
||||
|
||||
/// A tool call's result as text for the model, and whether it failed.
|
||||
@@ -1741,7 +1741,7 @@ fn tool_title(name: &str, args: &Value) -> String {
|
||||
/// endpoint should be, say): an error, so the picker says so and asks again later.
|
||||
fn listed_models(body: &Value, name_key: &str) -> Result<Vec<(String, Option<String>)>, String> {
|
||||
let Some(entries) = body.get("data").and_then(Value::as_array) else {
|
||||
return Err(crate::i18n::tr("ui.agent_chat_models_not_a_list").to_string());
|
||||
return Err(solidcraft_ui_text::tr("ui.agent_chat_models_not_a_list").to_string());
|
||||
};
|
||||
Ok(entries
|
||||
.iter()
|
||||
@@ -1771,7 +1771,7 @@ pub fn model_list(http: &HttpFn, base_url: &str, key: Option<&str>, allow_insecu
|
||||
let mut headers = Vec::new();
|
||||
if let Some(k) = key {
|
||||
if !super::agents::key_allowed_for(base_url, allow_insecure_http) {
|
||||
return Err(crate::i18n::tr("ui.ai_key_needs_https").to_string());
|
||||
return Err(solidcraft_ui_text::tr("ui.ai_key_needs_https").to_string());
|
||||
}
|
||||
headers.push(("Authorization".to_string(), format!("Bearer {k}")));
|
||||
}
|
||||
@@ -1783,13 +1783,16 @@ pub fn test_endpoint(http: &HttpFn, base_url: &str, key: Option<&str>, model: &s
|
||||
let mut headers = Vec::new();
|
||||
if let Some(k) = key {
|
||||
if !super::agents::key_allowed_for(base_url, allow_insecure_http) {
|
||||
return Err(crate::i18n::tr("ui.ai_key_needs_https").to_string());
|
||||
return Err(solidcraft_ui_text::tr("ui.ai_key_needs_https").to_string());
|
||||
}
|
||||
headers.push(("Authorization".to_string(), format!("Bearer {k}")));
|
||||
}
|
||||
let models = model_entries(http, base_url, headers)?;
|
||||
let found = model.is_empty() || models.iter().any(|m| m.id == model);
|
||||
Ok(crate::i18n::format(if found { "ui.ai_test_ok" } else { "ui.ai_test_no_model" }, &[("count", &models.len().to_string()), ("model", model)]))
|
||||
Ok(solidcraft_ui_text::format(
|
||||
if found { "ui.ai_test_ok" } else { "ui.ai_test_no_model" },
|
||||
&[("count", &models.len().to_string()), ("model", model)],
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -2642,6 +2645,7 @@ mod tests {
|
||||
/// id that could not be sent back is left out, and the list stays within its caps.
|
||||
#[test]
|
||||
fn a_listing_keeps_the_names_and_stays_within_its_caps() {
|
||||
crate::i18n::install();
|
||||
let mut data = vec![
|
||||
json!({"id": "openai/gpt-x", "name": "OpenAI: GPT X", "description": "Long text", "supported_parameters": ["tools"]}),
|
||||
json!({"id": "same", "name": "same", "display_name": "Same One"}),
|
||||
@@ -2675,7 +2679,7 @@ mod tests {
|
||||
/// picker says why and asks again later; a list with nothing in it is a list.
|
||||
#[test]
|
||||
fn an_answer_that_is_no_model_list_is_an_error() {
|
||||
let expected = crate::i18n::tr("ui.agent_chat_models_not_a_list").to_string();
|
||||
let expected = solidcraft_ui_text::tr("ui.agent_chat_models_not_a_list").to_string();
|
||||
let page: HttpFn = Arc::new(|_: &HttpRequest| Ok((200, b"<!doctype html><html><body>Control UI</body></html>".to_vec())));
|
||||
assert_eq!(model_list(&page, "http://localhost:18789/v1", None, false).unwrap_err(), expected);
|
||||
assert_eq!(anthropic_model_list(&page, "http://localhost:18789/v1", None).unwrap_err(), expected);
|
||||
@@ -3110,6 +3114,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn the_anthropic_test_sends_one_small_message_and_checks_the_key_rule() {
|
||||
crate::i18n::install();
|
||||
let seen = Arc::new(std::sync::Mutex::new(Vec::<HttpRequest>::new()));
|
||||
let log = seen.clone();
|
||||
let http: HttpFn = Arc::new(move |r: &HttpRequest| {
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::backend::{self, PromptPart};
|
||||
use crate::i18n::format as trf;
|
||||
use solidcraft_ui_text::format as trf;
|
||||
|
||||
/// A refusal in the app's language: the file's name, then why.
|
||||
fn refused(key: &str, name: &str, args: &[(&str, &str)]) -> String {
|
||||
@@ -508,6 +508,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn bad_files_are_refused_with_a_reason() {
|
||||
crate::i18n::install();
|
||||
let d = dir("bad");
|
||||
let fake = d.join("fake.png");
|
||||
std::fs::write(&fake, b"not a picture at all").unwrap();
|
||||
@@ -533,15 +534,16 @@ mod tests {
|
||||
/// Review of #1744: the refusals were English whatever the app's language.
|
||||
#[test]
|
||||
fn refusals_speak_the_apps_language() {
|
||||
crate::i18n::install();
|
||||
let d = dir("lang");
|
||||
let binary = d.join("bin.json");
|
||||
std::fs::write(&binary, [0xff, 0xfe, 0x00, 0x80]).unwrap();
|
||||
{
|
||||
let _ja = crate::i18n::scope("ja");
|
||||
let _ja = solidcraft_ui_text::scope("ja");
|
||||
assert!(read(&binary).unwrap_err().contains("UTF-8 のテキストではありません"));
|
||||
}
|
||||
{
|
||||
let _zh = crate::i18n::scope("zh");
|
||||
let _zh = solidcraft_ui_text::scope("zh");
|
||||
assert!(read(&binary).unwrap_err().contains("不是 UTF-8 文本"));
|
||||
}
|
||||
assert!(read(&binary).unwrap_err().contains("not UTF-8 text"), "English again outside the scope");
|
||||
@@ -552,6 +554,7 @@ mod tests {
|
||||
/// sent as PNG; SVG goes as its source text; HEIC says why not.
|
||||
#[test]
|
||||
fn each_picture_format_goes_the_way_the_agent_reads_it() {
|
||||
crate::i18n::install();
|
||||
let d = dir("formats");
|
||||
let img = image::RgbaImage::from_pixel(30, 20, image::Rgba([20, 120, 220, 255]));
|
||||
for (file, format, mime) in [
|
||||
@@ -617,6 +620,7 @@ mod tests {
|
||||
/// its size and its place) instead of refused; the agent reads it with its own tools.
|
||||
#[test]
|
||||
fn other_files_go_by_reference() {
|
||||
crate::i18n::install();
|
||||
let d = dir("reference");
|
||||
let zip = d.join("fixtures.zip");
|
||||
std::fs::write(&zip, vec![0u8; 3000]).unwrap();
|
||||
|
||||
@@ -21,7 +21,7 @@ pub enum TestError {
|
||||
impl TestError {
|
||||
pub fn message(&self) -> &str {
|
||||
match self {
|
||||
Self::AuthRequired => crate::i18n::tr("ui.agent_chat_state_auth"),
|
||||
Self::AuthRequired => solidcraft_ui_text::tr("ui.agent_chat_state_auth"),
|
||||
Self::Other(message) => message,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,7 +483,7 @@ fn detect_in(dirs: &[PathBuf], home: Option<&Path>, limit: Duration) -> Detectio
|
||||
argv.extend(k.args.iter().map(|a| a.to_string()));
|
||||
let mut result = if k.reports_version { probe(&path, limit) } else { Probe::default() };
|
||||
if let Some(version) = result.old_node.as_deref() {
|
||||
result.problem = Some(crate::i18n::format("ui.agent_chat_node_too_old", &[("version", version)]));
|
||||
result.problem = Some(solidcraft_ui_text::format("ui.agent_chat_node_too_old", &[("version", version)]));
|
||||
if uses_env_node(&path)
|
||||
&& let Some(node) = compatible_node(dirs, limit)
|
||||
{
|
||||
@@ -561,6 +561,7 @@ mod tests {
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn a_node_adapter_recovers_from_an_incompatible_desktop_runtime() {
|
||||
crate::i18n::install();
|
||||
use super::super::process::{AgentProcess, FromAgent};
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ use super::markdown::{self, Block, Span};
|
||||
use egui::text::{LayoutJob, TextFormat};
|
||||
use egui::{Color32, FontId, Margin, RichText, Stroke, vec2};
|
||||
|
||||
use crate::i18n::tr;
|
||||
use crate::theme::Tokens;
|
||||
use solidcraft_ui_text::tr;
|
||||
|
||||
/// The parsed blocks of `src`, kept in the context's temporary memory under `slot` (the item's
|
||||
/// index) and replaced when the text changes.
|
||||
|
||||
@@ -45,9 +45,9 @@ use serde_json::{Value, json};
|
||||
|
||||
use context::Context;
|
||||
|
||||
use crate::i18n::{format as trf, tr};
|
||||
use crate::{ControlRequest, SolveApp};
|
||||
use backend::{ChatBackend, Event, PermissionOption, PromptPart, ToolStatus};
|
||||
use solidcraft_ui_text::{format as trf, tr};
|
||||
|
||||
pub(crate) use panel::{finish_shared, left_inset, owns_keyboard_input, prepare_shared, request_new_chat, shared_content, tool_space};
|
||||
pub use panel::{show, tip as panel_tip, toggle, toggle_shortcut};
|
||||
@@ -565,11 +565,11 @@ impl AgentChat {
|
||||
job: impl FnOnce() -> std::result::Result<attach::Attachment, String> + Send + 'static,
|
||||
) -> std::result::Result<String, String> {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let language = crate::i18n::language();
|
||||
let language = solidcraft_ui_text::language();
|
||||
std::thread::Builder::new()
|
||||
.name("chat-file".into())
|
||||
.spawn(move || {
|
||||
let _language = crate::i18n::scope(language);
|
||||
let _language = solidcraft_ui_text::scope(language);
|
||||
let _ = tx.send(job());
|
||||
})
|
||||
.map_err(|e| trf("ui.agent_chat_file_error", &[("name", &name), ("error", &e.to_string())]))?;
|
||||
|
||||
@@ -260,7 +260,7 @@ pub(crate) fn left_inset(app: &SolveApp, available: Rect) -> f32 {
|
||||
&& app.preferences.agent_chat_side == "left"
|
||||
{
|
||||
let width = if app.preferences.agent_chat_minimized { STRIP } else { app.preferences.agent_chat_width.clamp(MIN_WIDTH, MAX_WIDTH) };
|
||||
(width + crate::shell::SHELL_GAP).min(available.width().max(0.0))
|
||||
(width + crate::ui_kit::SHELL_GAP).min(available.width().max(0.0))
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
@@ -276,9 +276,9 @@ pub(crate) fn tool_space(app: &SolveApp, mut available: Rect) -> Rect {
|
||||
&& let Some(chat) = app.agent_chat.panel_rect.filter(|r| r.is_finite() && r.is_positive())
|
||||
{
|
||||
if app.preferences.agent_chat_side == "left" {
|
||||
available.min.x = crate::shell::within(chat.right() + crate::shell::SHELL_GAP, available.left(), available.right());
|
||||
available.min.x = crate::ui_kit::within(chat.right() + crate::ui_kit::SHELL_GAP, available.left(), available.right());
|
||||
} else {
|
||||
available.max.x = crate::shell::within(chat.left() - crate::shell::SHELL_GAP, available.left(), available.right());
|
||||
available.max.x = crate::ui_kit::within(chat.left() - crate::ui_kit::SHELL_GAP, available.left(), available.right());
|
||||
}
|
||||
}
|
||||
available
|
||||
@@ -287,7 +287,7 @@ pub(crate) fn tool_space(app: &SolveApp, mut available: Rect) -> Rect {
|
||||
/// The same outer rectangle is used for the floating sheet and its docking preview.
|
||||
fn floating_bounds(app: &SolveApp, available: Rect, left: bool) -> Rect {
|
||||
let t = Tokens::get();
|
||||
let gap = crate::shell::SHELL_GAP;
|
||||
let gap = crate::ui_kit::SHELL_GAP;
|
||||
let mut bounds = available.shrink(gap);
|
||||
// The ribbon already supplies the top gap shared by browser and history cards.
|
||||
bounds.min.y = available.top();
|
||||
@@ -1772,7 +1772,7 @@ fn copy_message_menu(ui: &egui::Ui, response: &egui::Response, n: usize, text: &
|
||||
.frame(ui_kit::Menu::frame())
|
||||
.open_memory(if opened { Some(egui::SetOpenCommand::Bool(true)) } else { None })
|
||||
.show(|ui| {
|
||||
ui_kit::Menu::show(ui, crate::context_menu::LIST_W, |ui| {
|
||||
ui_kit::Menu::show(ui, crate::ui_kit::LIST_W, |ui| {
|
||||
let item = ui_kit::MenuItem::new("copy_message", tr("ui.agent_chat_copy_message"), "copy");
|
||||
let row = item.show(ui);
|
||||
crate::scenario::publish_handle(&format!("agent_chat:copy_menu:{n}"), row.rect.center());
|
||||
|
||||
@@ -208,10 +208,10 @@ fn start_listing(app: &mut SolveApp, ctx: &egui::Context, key: ListingKey, secre
|
||||
};
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let (protocol, url, allow_insecure_http) = (key.0, key.1.clone(), key.3);
|
||||
let language = crate::i18n::language();
|
||||
let language = solidcraft_ui_text::language();
|
||||
let wake = ctx.clone();
|
||||
let spawned = std::thread::Builder::new().name("endpoint-models".into()).spawn(move || {
|
||||
let _language = crate::i18n::scope(language);
|
||||
let _language = solidcraft_ui_text::scope(language);
|
||||
let list = secret().and_then(|secret| match protocol {
|
||||
Protocol::OpenAi => super::api_client::model_list(&http, &url, secret.as_deref(), allow_insecure_http),
|
||||
Protocol::Anthropic => super::api_client::anthropic_model_list(&http, &url, secret.as_deref()),
|
||||
@@ -519,7 +519,7 @@ pub fn page(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
permissions_section(app, ui);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let browsing = !crate::prefs_search::querying(ui) || app.prefs_window.section == crate::prefs::PAGE_ASSISTANT;
|
||||
let browsing = !crate::ui_kit::settings_search::querying(ui) || app.prefs_window.section == crate::prefs::PAGE_ASSISTANT;
|
||||
if app.preferences.ai_features && browsing && app.agent_chat.detection.is_none() && app.agent_chat.detecting.is_none() {
|
||||
refresh(app, &ui.ctx().clone());
|
||||
}
|
||||
@@ -561,7 +561,7 @@ fn instructions_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let response = ui_kit::text_area(ui, tr("ai.prefs.instructions"), id, text, 6, acp::INSTRUCTIONS_LIMIT);
|
||||
if response.has_focus() {
|
||||
// Typed text belongs to the field, not to Preferences search.
|
||||
crate::prefs_search::note_key_sink(ui.ctx());
|
||||
crate::ui_kit::settings_search::note_key_sink(ui.ctx());
|
||||
}
|
||||
crate::scenario::publish_target("prefs:agent_instructions", response.rect);
|
||||
ui_kit::text(ui, tr("ai.prefs.instructions_note"), ui_kit::Text::Hint("instructions"));
|
||||
@@ -658,20 +658,20 @@ fn panel_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let previous = usize::from(p.agent_chat_turn_limit != 0);
|
||||
let mut limit = previous;
|
||||
let options = [tr("ui.agent_chat_turn_unlimited"), tr("prefs.assistant_limited")];
|
||||
crate::prefs_search::note_current(ui, &options.join(" "));
|
||||
crate::ui_kit::settings_search::note_current(ui, &options.join(" "));
|
||||
let dropdown_width = ui_kit::ContentWidth::dropdown(ui, &options);
|
||||
let response =
|
||||
ui_kit::settings_control_width(ui, dropdown_width, |ui| ui_kit::dropdown(ui, "assistant_turn_limit", &mut limit, &options));
|
||||
let query = crate::prefs_search::query(ui);
|
||||
let query = crate::ui_kit::settings_search::query(ui);
|
||||
if let Some(option) = options
|
||||
.iter()
|
||||
.find(|option| option.to_lowercase() == query)
|
||||
.or_else(|| options.iter().find(|option| crate::prefs_search::texts_hit(ui, &[*option])))
|
||||
.or_else(|| options.iter().find(|option| crate::ui_kit::settings_search::texts_hit(ui, &[*option])))
|
||||
{
|
||||
crate::prefs_search::remember(ui, option, response.rect);
|
||||
crate::ui_kit::settings_search::remember(ui, option, response.rect);
|
||||
}
|
||||
for option in options {
|
||||
crate::prefs_search::follow(ui, option, response.rect);
|
||||
crate::ui_kit::settings_search::follow(ui, option, response.rect);
|
||||
}
|
||||
crate::scenario::publish_target("prefs:agent_chat_turn_limit:mode", response.rect);
|
||||
if limit != previous {
|
||||
@@ -699,14 +699,14 @@ fn pointer_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
ui,
|
||||
|ui| {
|
||||
let response = ui_kit::checkbox(ui, tr("ai.prefs.show_pointer"), &mut app.ui.agent_cursor.show)
|
||||
.backing_on_hover_text(crate::i18n::tr_label("Show the pointer an AI agent moves (#35)"));
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr_label("Show the pointer an AI agent moves (#35)"));
|
||||
crate::scenario::publish_target("prefs:agent_pointer_show", response.rect);
|
||||
ui.add_enabled_ui(app.ui.agent_cursor.show, |ui| {
|
||||
let speed_label_width = ui_kit::ContentWidth::label(ui, tr("ai.prefs.pointer_speed")) + crate::theme::Tokens::get().space_4;
|
||||
ui_kit::settings_form(ui, speed_label_width, |ui| {
|
||||
ui_kit::row(ui, tr("ai.prefs.pointer_speed"), |ui| {
|
||||
let name = tr("prefs.assistant_pointer_speed");
|
||||
crate::prefs_search::note_current(ui, name);
|
||||
crate::ui_kit::settings_search::note_current(ui, name);
|
||||
let response =
|
||||
ui_kit::named_control(ui, name, |ui| crate::agent_cursor::speed_slider(ui, &mut app.ui.agent_cursor.speed));
|
||||
crate::scenario::publish_target("prefs:agent_pointer_speed", response.rect);
|
||||
@@ -717,11 +717,11 @@ fn pointer_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
response.widget_info(|| egui::WidgetInfo::slider(ui.is_enabled(), value, name));
|
||||
// The visible row owns Speed and its legacy Motion speed alias. The
|
||||
// native editor's name owns queries that only match Pointer speed.
|
||||
if !crate::prefs_search::texts_hit(ui, &[tr("ai.prefs.pointer_speed")]) {
|
||||
crate::prefs_search::remember(ui, name, response.rect);
|
||||
if !crate::ui_kit::settings_search::texts_hit(ui, &[tr("ai.prefs.pointer_speed")]) {
|
||||
crate::ui_kit::settings_search::remember(ui, name, response.rect);
|
||||
}
|
||||
for label in [tr("ai.prefs.pointer_speed"), name] {
|
||||
crate::prefs_search::follow(ui, label, response.rect);
|
||||
crate::ui_kit::settings_search::follow(ui, label, response.rect);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -734,7 +734,7 @@ fn pointer_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
});
|
||||
(app.ui.agent_cursor.show, app.ui.agent_cursor.speed.label(), app.ui.agent_cursor.follow_camera)
|
||||
},
|
||||
|ui, (visible, speed, follow)| ui_kit::settings_pointer_preview(ui, visible, crate::i18n::tr_label(&speed), follow),
|
||||
|ui, (visible, speed, follow)| ui_kit::settings_pointer_preview(ui, visible, solidcraft_ui_text::tr_label(&speed), follow),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -781,12 +781,12 @@ fn agents_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
for entry in &all {
|
||||
ui.push_id(&entry.id, |ui| {
|
||||
let detail = entry.detail();
|
||||
let gate = crate::prefs_search::block_gate(ui, &entry.name, Some(&format!("{} {detail}", entry.id)));
|
||||
let before = crate::prefs_search::hits(ui);
|
||||
let gate = crate::ui_kit::settings_search::block_gate(ui, &entry.name, Some(&format!("{} {detail}", entry.id)));
|
||||
let before = crate::ui_kit::settings_search::hits(ui);
|
||||
let mut paint = |ui: &mut egui::Ui| {
|
||||
let card = ui_kit::settings_card(ui, |ui| {
|
||||
ui.with_layout(egui::Layout::top_down(egui::Align::Min), |ui| {
|
||||
crate::prefs_search::visible(ui, &[&entry.name, &entry.id, &detail]);
|
||||
crate::ui_kit::settings_search::visible(ui, &[&entry.name, &entry.id, &detail]);
|
||||
ui.label(egui::RichText::new(&entry.name).strong().size(t.font_label).color(t.form_ink));
|
||||
ui.allocate_ui_with_layout(
|
||||
egui::vec2(ui.available_width(), t.control_height),
|
||||
@@ -851,7 +851,7 @@ fn agents_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let shown = current.map_or_else(|| tr("ui.ai_mode_global").to_owned(), |mode| mode.label().to_owned());
|
||||
let mut pick: Option<Option<Mode>> = None;
|
||||
let options: Vec<_> = std::iter::once(tr("ui.ai_mode_global")).chain(Mode::ALL.map(Mode::label)).collect();
|
||||
crate::prefs_search::note_current(ui, &options.join(" "));
|
||||
crate::ui_kit::settings_search::note_current(ui, &options.join(" "));
|
||||
let response = ui_kit::settings_control_width(ui, t.space_24 * 9.0 + t.space_4, |ui| {
|
||||
ui_kit::dropdown_menu(ui, ("sc_ai_mode", &entry.id), &shown, |ui| {
|
||||
if ui_kit::list_picker(ui, "global", tr("ui.ai_mode_global"), current.is_none()).clicked() {
|
||||
@@ -864,16 +864,16 @@ fn agents_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
})
|
||||
});
|
||||
let query = crate::prefs_search::query(ui);
|
||||
let query = crate::ui_kit::settings_search::query(ui);
|
||||
if let Some(option) = options
|
||||
.iter()
|
||||
.find(|option| option.to_lowercase() == query)
|
||||
.or_else(|| options.iter().find(|option| crate::prefs_search::texts_hit(ui, &[*option])))
|
||||
.or_else(|| options.iter().find(|option| crate::ui_kit::settings_search::texts_hit(ui, &[*option])))
|
||||
{
|
||||
crate::prefs_search::remember(ui, option, response.rect);
|
||||
crate::ui_kit::settings_search::remember(ui, option, response.rect);
|
||||
}
|
||||
for option in options {
|
||||
crate::prefs_search::follow(ui, option, response.rect);
|
||||
crate::ui_kit::settings_search::follow(ui, option, response.rect);
|
||||
}
|
||||
match pick {
|
||||
Some(Some(mode)) => {
|
||||
@@ -965,23 +965,23 @@ fn agents_section(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
});
|
||||
crate::scenario::publish_rect(&format!("prefs:ai_card:{}", entry.id), card.response.rect);
|
||||
let metadata = [&entry.name, &entry.id, &detail];
|
||||
if let Some(text) = metadata.iter().find(|text| crate::prefs_search::texts_hit(ui, &[*text])) {
|
||||
crate::prefs_search::remember(ui, text, card.response.rect);
|
||||
if let Some(text) = metadata.iter().find(|text| crate::ui_kit::settings_search::texts_hit(ui, &[*text])) {
|
||||
crate::ui_kit::settings_search::remember(ui, text, card.response.rect);
|
||||
}
|
||||
for text in metadata {
|
||||
crate::prefs_search::follow(ui, text, card.response.rect);
|
||||
crate::ui_kit::settings_search::follow(ui, text, card.response.rect);
|
||||
}
|
||||
};
|
||||
let show = gate.as_ref().is_none_or(|gate| gate.show);
|
||||
if show {
|
||||
paint(ui);
|
||||
} else {
|
||||
crate::prefs_search::run_hidden(ui, &entry.id, |ui| {
|
||||
crate::ui_kit::settings_search::run_hidden(ui, &entry.id, |ui| {
|
||||
paint(ui);
|
||||
});
|
||||
}
|
||||
if let Some(gate) = gate {
|
||||
crate::prefs_search::block_finish(ui, &gate, show, before);
|
||||
crate::ui_kit::settings_search::block_finish(ui, &gate, show, before);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1524,7 +1524,7 @@ mod card_tests {
|
||||
|
||||
#[test]
|
||||
fn pointer_speed_search_reveals_one_real_slider_in_all_enablement_states() {
|
||||
use crate::prefs_search::{nav, set_cursor};
|
||||
use crate::ui_kit::settings_search::{nav, set_cursor};
|
||||
for theme in ["light", "dark"] {
|
||||
for (ai, show) in [(true, true), (true, false), (false, true), (false, false)] {
|
||||
let mut h = Harness::new();
|
||||
@@ -1569,7 +1569,7 @@ mod card_tests {
|
||||
|
||||
#[test]
|
||||
fn overlapping_agent_metadata_has_one_card_target_and_keeps_fields_indexed() {
|
||||
use crate::prefs_search::{nav, set_cursor};
|
||||
use crate::ui_kit::settings_search::{nav, set_cursor};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use crate::i18n::{format as trf, tr, tr_label};
|
||||
use solidcraft_ui_text::{format as trf, tr, tr_label};
|
||||
|
||||
/// Steps of a batch named before "and N more".
|
||||
const MAX_STEPS: usize = 4;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::*;
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
fn transcript_clipboard_harness(layout: crate::shell::LayoutMode) -> crate::scenario::Harness {
|
||||
fn transcript_clipboard_harness(layout: crate::ui_kit::LayoutMode) -> crate::scenario::Harness {
|
||||
let mut h = narrow_chat();
|
||||
h.app.ui.layout = layout;
|
||||
h.app.preferences.agent_chat_width = 500.0;
|
||||
@@ -36,7 +36,7 @@ fn transcript_click(h: &mut crate::scenario::Harness, pos: egui::Pos2) {
|
||||
|
||||
#[test]
|
||||
fn transcript_copy_and_cut_do_not_touch_the_cad_clipboard() {
|
||||
for layout in [crate::shell::LayoutMode::Docked, crate::shell::LayoutMode::Floating] {
|
||||
for layout in [crate::ui_kit::LayoutMode::Docked, crate::ui_kit::LayoutMode::Floating] {
|
||||
let mut h = transcript_clipboard_harness(layout);
|
||||
let at = transcript_text_position(&h);
|
||||
transcript_click(&mut h, at);
|
||||
@@ -57,7 +57,7 @@ fn transcript_copy_and_cut_do_not_touch_the_cad_clipboard() {
|
||||
|
||||
#[test]
|
||||
fn transcript_paste_does_not_insert_a_previously_copied_body() {
|
||||
for layout in [crate::shell::LayoutMode::Docked, crate::shell::LayoutMode::Floating] {
|
||||
for layout in [crate::ui_kit::LayoutMode::Docked, crate::ui_kit::LayoutMode::Floating] {
|
||||
let mut h = transcript_clipboard_harness(layout);
|
||||
h.frame_with(vec![egui::Event::Copy]);
|
||||
assert!(!h.app.copied_bodies.is_empty(), "prime the real CAD clipboard");
|
||||
@@ -77,7 +77,7 @@ fn transcript_paste_does_not_insert_a_previously_copied_body() {
|
||||
|
||||
#[test]
|
||||
fn transcript_key_events_do_not_run_cad_shortcuts() {
|
||||
for layout in [crate::shell::LayoutMode::Docked, crate::shell::LayoutMode::Floating] {
|
||||
for layout in [crate::ui_kit::LayoutMode::Docked, crate::ui_kit::LayoutMode::Floating] {
|
||||
let mut h = transcript_clipboard_harness(layout);
|
||||
let at = transcript_text_position(&h);
|
||||
transcript_click(&mut h, at);
|
||||
@@ -106,7 +106,7 @@ fn transcript_key_events_do_not_run_cad_shortcuts() {
|
||||
#[test]
|
||||
fn transcript_shortcut_ownership_ends_when_the_panel_is_hidden() {
|
||||
for hidden in ["closed", "minimized", "disabled"] {
|
||||
let mut h = transcript_clipboard_harness(crate::shell::LayoutMode::Floating);
|
||||
let mut h = transcript_clipboard_harness(crate::ui_kit::LayoutMode::Floating);
|
||||
h.frame_with(vec![egui::Event::Copy]);
|
||||
let at = transcript_text_position(&h);
|
||||
transcript_click(&mut h, at);
|
||||
@@ -2906,7 +2906,7 @@ fn pictures_are_read_off_the_window_thread() {
|
||||
send(&mut app, &ctx, words);
|
||||
assert_eq!(script.prompts().len(), 1);
|
||||
{
|
||||
let _ja = crate::i18n::scope("ja");
|
||||
let _ja = solidcraft_ui_text::scope("ja");
|
||||
control(&mut app, &ctx, &json!({"attach": at(&d, "fake.png")})).unwrap();
|
||||
}
|
||||
settle_files(&mut app, &ctx);
|
||||
@@ -3184,7 +3184,7 @@ fn a_press_at_the_edge_without_a_drag_towards_it_scrolls_nothing() {
|
||||
/// at the panel's edge or is cut off at the other one.
|
||||
#[test]
|
||||
fn the_agent_list_keeps_the_panels_side_margin() {
|
||||
use crate::shell::LayoutMode;
|
||||
use crate::ui_kit::LayoutMode;
|
||||
for layout in [LayoutMode::Docked, LayoutMode::Floating] {
|
||||
let mut h = narrow_chat();
|
||||
h.app.ui.layout = layout;
|
||||
@@ -4614,9 +4614,9 @@ fn the_composer_hint_stays_on_one_row_at_the_narrowest_width() {
|
||||
continue;
|
||||
}
|
||||
let language = path.file_stem().unwrap().to_string_lossy().into_owned();
|
||||
let _scope = crate::i18n::scope(&language);
|
||||
let _scope = solidcraft_ui_text::scope(&language);
|
||||
for key in ["ui.agent_chat_placeholder", "ui.agent_chat_placeholder_busy"] {
|
||||
let hint = crate::i18n::tr(key).to_owned();
|
||||
let hint = solidcraft_ui_text::tr(key).to_owned();
|
||||
let mut rows = 0;
|
||||
let mut output = ctx.run_ui(egui::RawInput::default(), |ui| {
|
||||
rows = crate::ui_kit::assistant_hint_galley(ui, &hint, width - 2.0 * crate::theme::Tokens::get().form_inset).rows.len();
|
||||
@@ -4634,7 +4634,7 @@ fn the_composer_hint_stays_on_one_row_at_the_narrowest_width() {
|
||||
#[test]
|
||||
fn tool_room_beside_the_docked_assistant_survives_an_inside_out_view() {
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default());
|
||||
app.ui.layout = crate::shell::LayoutMode::Floating;
|
||||
app.ui.layout = crate::ui_kit::LayoutMode::Floating;
|
||||
app.preferences.ai_features = true;
|
||||
app.agent_chat.open = true;
|
||||
app.preferences.agent_chat_popped = false;
|
||||
|
||||
@@ -109,7 +109,7 @@ pub fn speed_slider(ui: &mut egui::Ui, speed: &mut Speed) -> egui::Response {
|
||||
1.0..=5.5,
|
||||
crate::ui_kit::Slider {
|
||||
step: Some(0.5),
|
||||
format: |v| if v > 5.25 { crate::i18n::tr_label("Instant").into() } else { format!("{v:.2}x") },
|
||||
format: |v| if v > 5.25 { solidcraft_ui_text::tr_label("Instant").into() } else { format!("{v:.2}x") },
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
@@ -504,7 +504,7 @@ pub fn paint(app: &SolveApp, ctx: &egui::Context) {
|
||||
.show(ctx, |ui| {
|
||||
let frame = Container::popup_frame(ui.style()).fill(t.overlay);
|
||||
Container::show_backing(ui, frame, |ui| {
|
||||
ui.label(egui::RichText::new(crate::i18n::tr_label(label)).size(t.font_label).color(t.text));
|
||||
ui.label(egui::RichText::new(solidcraft_ui_text::tr_label(label)).size(t.font_label).color(t.text));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ pub fn tick(app: &mut SolveApp) {
|
||||
}
|
||||
let _ = std::fs::write(&file, serde_json::to_string(&all).unwrap_or_default());
|
||||
let names = fresh.iter().map(|c| client_name(c)).collect::<Vec<_>>().join(", ");
|
||||
app.set_status(crate::i18n::format("ai.connected_toast", &[("clients", &names)]), false);
|
||||
app.set_status(solidcraft_ui_text::format("ai.connected_toast", &[("clients", &names)]), false);
|
||||
}
|
||||
Err(std::sync::mpsc::TryRecvError::Empty) => {}
|
||||
Err(std::sync::mpsc::TryRecvError::Disconnected) => app.ai_connect.job = None,
|
||||
|
||||
@@ -115,7 +115,7 @@ fn cell<R>(ui: &mut egui::Ui, width: f32, add: impl FnOnce(&mut egui::Ui) -> R)
|
||||
|
||||
/// The label of one of [`BOM_KEYS`].
|
||||
fn field_label(key: &str) -> &str {
|
||||
metadata::key(key).map_or(key, |k| crate::i18n::tr(k.label))
|
||||
metadata::key(key).map_or(key, |k| solidcraft_ui_text::tr(k.label))
|
||||
}
|
||||
|
||||
/// Apply the picked row's fields to each of its definitions (one undo step each).
|
||||
@@ -146,8 +146,9 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let bom = d.rows(app);
|
||||
let rows: Vec<Value> = bom["rows"].as_array().cloned().unwrap_or_default();
|
||||
let (mut export, mut apply_row) = (None, None);
|
||||
let closed =
|
||||
crate::ui_kit::Window::new(egui::Id::unique("sc_bom"), "bom", crate::i18n::tr_label("Bill of Materials")).cards(3.0).show_body(ctx, |ui| {
|
||||
let closed = crate::ui_kit::Window::new(egui::Id::unique("sc_bom"), "bom", solidcraft_ui_text::tr_label("Bill of Materials"))
|
||||
.cards(3.0)
|
||||
.show_body(ctx, |ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
let was = (d.top, d.by_definition, d.group_children);
|
||||
let mut levels = usize::from(d.top);
|
||||
@@ -166,7 +167,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
});
|
||||
d.by_definition = grouping == 1;
|
||||
ui.separator();
|
||||
crate::ui_kit::checkbox(ui, crate::i18n::tr("bom.group_children"), &mut d.group_children);
|
||||
crate::ui_kit::checkbox(ui, solidcraft_ui_text::tr("bom.group_children"), &mut d.group_children);
|
||||
if was != (d.top, d.by_definition, d.group_children) {
|
||||
d.picked = None;
|
||||
}
|
||||
|
||||
@@ -148,8 +148,9 @@ use serde_json::{Value, json};
|
||||
use solidcraft_engine::Sel;
|
||||
use solidcraft_engine::doc::{BrowserEntry, Document, FeatureKind};
|
||||
|
||||
use crate::context_menu::{Item, Target};
|
||||
use crate::context_menu::Target;
|
||||
use crate::theme::Tokens;
|
||||
use crate::ui_kit::Item;
|
||||
|
||||
use crate::SolveApp;
|
||||
|
||||
@@ -1392,11 +1393,11 @@ fn browser_collapsed_rail(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
crate::ui_kit::disclosure(ui.painter(), disclosure.center(), false, t.text_dim);
|
||||
crate::scenario::publish_handle("left-browser-toggle", disclosure.center());
|
||||
crate::scenario::publish_target("browser:header", disclosure);
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), crate::i18n::tr("ui.browser_title")));
|
||||
if response.backing_on_hover_text(crate::i18n::tr("ui.browser_expand")).clicked() {
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), solidcraft_ui_text::tr("ui.browser_title")));
|
||||
if response.backing_on_hover_text(solidcraft_ui_text::tr("ui.browser_expand")).clicked() {
|
||||
app.preferences.timeline_left_browser_collapsed = false;
|
||||
}
|
||||
let galley = ui.painter().layout_no_wrap(crate::i18n::tr("ui.browser_title").to_owned(), crate::fonts::semibold(t.font_body), t.text);
|
||||
let galley = ui.painter().layout_no_wrap(solidcraft_ui_text::tr("ui.browser_title").to_owned(), crate::fonts::semibold(t.font_body), t.text);
|
||||
let title = Rect::from_min_max(rect.min + vec2(0.0, title_top), rect.max);
|
||||
ui.painter().add(
|
||||
egui::epaint::TextShape::new(title.center() - galley.size() / 2.0, galley, t.text)
|
||||
@@ -1483,7 +1484,7 @@ fn browser_header(app: &mut SolveApp, ui: &mut egui::Ui, collapsible: bool) {
|
||||
if collapsible && (response.hovered() || response.has_focus()) {
|
||||
crate::ui_kit::panel_header_hover(ui.painter(), rect);
|
||||
}
|
||||
let title = crate::i18n::tr("ui.browser_title").to_string();
|
||||
let title = solidcraft_ui_text::tr("ui.browser_title").to_string();
|
||||
let kind = if collapsible { egui::Role::Button } else { egui::Role::Label };
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(kind, true, &title));
|
||||
let galley = ui.painter().layout_no_wrap(title, crate::fonts::semibold(t.font_heading), t.text);
|
||||
@@ -1495,11 +1496,11 @@ fn browser_header(app: &mut SolveApp, ui: &mut egui::Ui, collapsible: bool) {
|
||||
}
|
||||
let bodies = app.session.model.state().bodies.len();
|
||||
let key = if bodies == 1 { "ui.browser_bodies_one" } else { "ui.browser_bodies_other" };
|
||||
let count = crate::i18n::format(key, &[("count", &bodies.to_string())]);
|
||||
let count = solidcraft_ui_text::format(key, &[("count", &bodies.to_string())]);
|
||||
let galley = ui.painter().layout_no_wrap(count, FontId::proportional(t.font_body), t.text_dim);
|
||||
ui.painter().galley(pos2(right - galley.size().x, rect.center().y - galley.size().y / 2.0), galley, t.text_dim);
|
||||
if collapsible {
|
||||
let tip = crate::i18n::tr(if collapsed { "ui.browser_expand" } else { "ui.browser_collapse" });
|
||||
let tip = solidcraft_ui_text::tr(if collapsed { "ui.browser_expand" } else { "ui.browser_collapse" });
|
||||
if response.on_hover_cursor(egui::CursorIcon::PointingHand).backing_on_hover_text(tip).clicked() {
|
||||
if stacked {
|
||||
app.preferences.timeline_left_browser_collapsed = !app.preferences.timeline_left_browser_collapsed;
|
||||
@@ -2467,7 +2468,7 @@ fn canvas_rows(app: &mut SolveApp, ui: &mut egui::Ui, depth: usize, acts: &mut A
|
||||
}
|
||||
}
|
||||
if r.double && locked {
|
||||
app.set_status(crate::i18n::tr("browser.canvas_locked"), true);
|
||||
app.set_status(solidcraft_ui_text::tr("browser.canvas_locked"), true);
|
||||
} else if r.double {
|
||||
app.tree.canvas_panel = Some(CanvasPanel::new(app, id, false));
|
||||
}
|
||||
@@ -2491,7 +2492,7 @@ pub fn canvas_items(app: &SolveApp, id: u64) -> Vec<Item> {
|
||||
let visible = canvas.is_some_and(|c| c.visible);
|
||||
// A locked canvas (#12310) is not moved, calibrated or deleted until it is unlocked.
|
||||
let locked = canvas.is_some_and(|c| c.locked);
|
||||
let why = locked.then(|| crate::i18n::tr("browser.canvas_locked").to_string());
|
||||
let why = locked.then(|| solidcraft_ui_text::tr("browser.canvas_locked").to_string());
|
||||
vec![
|
||||
Item::action("ui.editCanvas", "Edit Canvas", "canvas").with(json!({ "canvas": id })).off_because(why.clone()),
|
||||
Item::action("ui.calibrateCanvas", "Calibrate", "measure").with(json!({ "canvas": id })).off_because(why.clone()),
|
||||
@@ -2631,7 +2632,11 @@ impl Entry {
|
||||
row_key: format!("b:{world}"),
|
||||
patterns: None,
|
||||
// A part the modelling kernel refuses says so (Repair Part is in its menu).
|
||||
label: if b.body.smlib_refused().is_some() { crate::i18n::format("browser.needs_repair", &[("name", &b.name)]) } else { b.name.clone() },
|
||||
label: if b.body.smlib_refused().is_some() {
|
||||
solidcraft_ui_text::format("browser.needs_repair", &[("name", &b.name)])
|
||||
} else {
|
||||
b.name.clone()
|
||||
},
|
||||
icon,
|
||||
tip: Some(tip),
|
||||
visible: app.body_visible(&world),
|
||||
@@ -2657,9 +2662,9 @@ impl Entry {
|
||||
thing: Thing::Sketch(f.id),
|
||||
patterns: crate::sketch_pattern_tree::has_patterns(app, f.id).then(|| format!("s:{}/patterns", f.id)),
|
||||
label: if lost {
|
||||
crate::i18n::format("browser.sketch_attachment_lost", &[("name", &crate::i18n::tr_name(&f.name))])
|
||||
solidcraft_ui_text::format("browser.sketch_attachment_lost", &[("name", &crate::i18n::tr_name(&f.name))])
|
||||
} else if active {
|
||||
crate::i18n::format("browser.sketch_editing", &[("name", &crate::i18n::tr_name(&f.name))])
|
||||
solidcraft_ui_text::format("browser.sketch_editing", &[("name", &crate::i18n::tr_name(&f.name))])
|
||||
} else {
|
||||
f.name.clone()
|
||||
},
|
||||
@@ -3023,7 +3028,8 @@ fn folder_level(
|
||||
} else {
|
||||
&group.label
|
||||
};
|
||||
let count = if render && !gopen { crate::i18n::format("render.tree_more", &[("count", &group.count.to_string())]) } else { String::new() };
|
||||
let count =
|
||||
if render && !gopen { solidcraft_ui_text::format("render.tree_more", &[("count", &group.count.to_string())]) } else { String::new() };
|
||||
let r = draw_row(
|
||||
ui,
|
||||
ui.scope_id().with(gkey),
|
||||
|
||||
@@ -95,7 +95,7 @@ pub(super) fn rows(app: &mut SolveApp, ui: &mut egui::Ui, component: u64, path:
|
||||
let scope = if path.is_empty() { format!("c{component}") } else { occurrence_key(path) };
|
||||
// The history opens by default; the Features folder stays closed until opened.
|
||||
let (key, label) =
|
||||
if compact { (format!("{scope}/history"), crate::i18n::tr("ui.history_title")) } else { (format!("{scope}/features"), "Features") };
|
||||
if compact { (format!("{scope}/history"), solidcraft_ui_text::tr("ui.history_title")) } else { (format!("{scope}/features"), "Features") };
|
||||
let open = is_open(app, &key, compact);
|
||||
// Folded, it carries the worst mark of what it holds. A row scrolled out of view paints
|
||||
// nothing, so its mark is not worked out: in a long tree of placements of a part with a long
|
||||
@@ -400,7 +400,7 @@ fn marker_row(app: &SolveApp, ui: &mut egui::Ui, depth: usize) -> egui::Response
|
||||
if response.hovered() || response.dragged() {
|
||||
ui.ctx().set_cursor_icon(egui::CursorIcon::Grab);
|
||||
}
|
||||
response.backing_on_hover_text(crate::i18n::tr("ui.history_marker_tip"))
|
||||
response.backing_on_hover_text(solidcraft_ui_text::tr("ui.history_marker_tip"))
|
||||
}
|
||||
|
||||
/// Dragging the marker shows where it lands; letting go rolls the history there, through the
|
||||
|
||||
@@ -12,8 +12,9 @@ use solidcraft_engine::view::POINT_CLOUDS_FOLDER;
|
||||
|
||||
use super::{Actions, Row, draw_row, is_open, panel_pos, rows_start_open, toggle, toggle_folder};
|
||||
use crate::SolveApp;
|
||||
use crate::context_menu::{Item, Target};
|
||||
use crate::context_menu::Target;
|
||||
use crate::ui_kit;
|
||||
use crate::ui_kit::Item;
|
||||
|
||||
#[path = "point_cloud_tracks.rs"]
|
||||
pub(crate) mod point_cloud_tracks;
|
||||
@@ -267,7 +268,7 @@ pub(crate) fn panel(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
};
|
||||
let mut edit: Option<Value> = None;
|
||||
let mut reload = false;
|
||||
let title = crate::i18n::format("browser.edit_point_cloud_title", &[("name", &c.name)]);
|
||||
let title = solidcraft_ui_text::format("browser.edit_point_cloud_title", &[("name", &c.name)]);
|
||||
let auto = if c.color_by == ColorBy::Height { m.auto_range(&c) } else { None };
|
||||
let dots = m.dots(&c);
|
||||
let now = ctx.input(|i| i.time);
|
||||
@@ -299,16 +300,16 @@ pub(crate) fn panel(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let (closed, action) = ui_kit::Window::new(egui::Id::unique("sc_point_cloud_panel"), "point", &title)
|
||||
.placed(egui::Align2::LEFT_TOP, at)
|
||||
.fields()
|
||||
.actions("", crate::i18n::tr("ui.close"), None)
|
||||
.actions("", solidcraft_ui_text::tr("ui.close"), None)
|
||||
.show(ctx, true, |ui| {
|
||||
ui_kit::row(ui, crate::i18n::tr_label("Points"), |ui| ui.label(c.len().to_string()));
|
||||
ui_kit::row(ui, solidcraft_ui_text::tr_label("Points"), |ui| ui.label(c.len().to_string()));
|
||||
// The dots' size, where some points are dots: those no segment or triangle ends on.
|
||||
if dots > 0 {
|
||||
if dots < c.len() {
|
||||
ui_kit::row(ui, crate::i18n::tr_label("Dots"), |ui| ui.label(dots.to_string()));
|
||||
ui_kit::row(ui, solidcraft_ui_text::tr_label("Dots"), |ui| ui.label(dots.to_string()));
|
||||
}
|
||||
let mut size = c.point_size;
|
||||
if ui_kit::row(ui, crate::i18n::tr("ui.point_cloud_size"), |ui| ui_kit::slider(ui, &mut size, 0.5..=16.0, Default::default()))
|
||||
if ui_kit::row(ui, solidcraft_ui_text::tr("ui.point_cloud_size"), |ui| ui_kit::slider(ui, &mut size, 0.5..=16.0, Default::default()))
|
||||
.changed()
|
||||
{
|
||||
edit = Some(json!({ "cloud": c.id, "point_size": size }));
|
||||
@@ -318,7 +319,7 @@ pub(crate) fn panel(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let was = units.iter().position(|u| *u == c.units).unwrap_or(0);
|
||||
let mut unit = was;
|
||||
// A dropdown's response doesn't report the pick: compare the index.
|
||||
ui_kit::row(ui, crate::i18n::tr("ui.point_cloud_units"), |ui| ui_kit::dropdown(ui, "point_cloud_units", &mut unit, &units));
|
||||
ui_kit::row(ui, solidcraft_ui_text::tr("ui.point_cloud_units"), |ui| ui_kit::dropdown(ui, "point_cloud_units", &mut unit, &units));
|
||||
if unit != was
|
||||
&& let Some(u) = units.get(unit)
|
||||
{
|
||||
@@ -334,7 +335,7 @@ pub(crate) fn panel(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
by => COLOR_BY.iter().position(|(b, _)| *b == by).unwrap_or(0),
|
||||
};
|
||||
let mut by = was;
|
||||
ui_kit::row(ui, crate::i18n::tr("ui.point_cloud_color_by"), |ui| ui_kit::dropdown(ui, "point_cloud_color_by", &mut by, &labels));
|
||||
ui_kit::row(ui, solidcraft_ui_text::tr("ui.point_cloud_color_by"), |ui| ui_kit::dropdown(ui, "point_cloud_color_by", &mut by, &labels));
|
||||
if by != was {
|
||||
let name = COLOR_BY.get(by).map(|(b, _)| b.name()).or_else(|| properties.get(by - COLOR_BY.len()).copied());
|
||||
if let Some(name) = name {
|
||||
@@ -345,12 +346,12 @@ pub(crate) fn panel(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let legend_shown = colored.and_then(|n| label(&c, n));
|
||||
match c.color_by {
|
||||
ColorBy::File if c.colors.is_empty() => {
|
||||
ui_kit::row(ui, "", |ui| ui.weak(crate::i18n::tr("ui.point_cloud_no_file_colors")));
|
||||
ui_kit::row(ui, "", |ui| ui.weak(solidcraft_ui_text::tr("ui.point_cloud_no_file_colors")));
|
||||
}
|
||||
ColorBy::File => {}
|
||||
ColorBy::Single => {
|
||||
let mut rgb = c.color.unwrap_or(PLAIN).map(byte);
|
||||
if ui_kit::row(ui, crate::i18n::tr_label("Colour"), |ui| ui_kit::color_field(ui, &mut rgb)).changed() {
|
||||
if ui_kit::row(ui, solidcraft_ui_text::tr_label("Colour"), |ui| ui_kit::color_field(ui, &mut rgb)).changed() {
|
||||
edit = Some(json!({ "cloud": c.id, "color": rgb.map(|v| f64::from(v) / 255.0) }));
|
||||
}
|
||||
}
|
||||
@@ -366,7 +367,7 @@ pub(crate) fn panel(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
{
|
||||
// A ramp over what is shown, unless its range is set.
|
||||
let automatic = c.height_range.is_none();
|
||||
if ui_kit::checkbox_row(ui, "point_cloud_auto_range", crate::i18n::tr("ui.point_cloud_auto_range"), automatic) {
|
||||
if ui_kit::checkbox_row(ui, "point_cloud_auto_range", solidcraft_ui_text::tr("ui.point_cloud_auto_range"), automatic) {
|
||||
edit = Some(json!({ "cloud": c.id, "color_range": if automatic { json!(crate::point_clouds::color_range(ui.ctx(), &live)) } else { Value::Null } }));
|
||||
}
|
||||
// The ramp's range is a range on a track of the property, over what the
|
||||
@@ -422,9 +423,9 @@ pub(crate) fn panel(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
&& ui_kit::button(
|
||||
ui,
|
||||
if playing {
|
||||
format!("⏸ {}", crate::i18n::tr_label("Pause"))
|
||||
format!("⏸ {}", solidcraft_ui_text::tr_label("Pause"))
|
||||
} else {
|
||||
format!("▶ {}", crate::i18n::tr_label("Play"))
|
||||
format!("▶ {}", solidcraft_ui_text::tr_label("Play"))
|
||||
},
|
||||
ui_kit::Button::Small,
|
||||
)
|
||||
@@ -442,7 +443,7 @@ pub(crate) fn panel(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
match (&f.keep, span) {
|
||||
(Keep::Values { .. }, _) => match label(&c, &f.property) {
|
||||
Some(names) if legend_shown.is_some_and(|l| l.property == names.property) => {
|
||||
ui.weak(crate::i18n::tr_label("Shown and hidden in the legend above"));
|
||||
ui.weak(solidcraft_ui_text::tr_label("Shown and hidden in the legend above"));
|
||||
}
|
||||
Some(names) => {
|
||||
if let Some(v) = legend(ui, &c, names) {
|
||||
@@ -520,10 +521,10 @@ pub(crate) fn panel(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
}
|
||||
let all: Vec<&str> = c.property_names().collect();
|
||||
if c.filters.len() < MAX_FILTERS {
|
||||
let mut add_labels = vec![crate::i18n::tr_label("Add filter…")];
|
||||
let mut add_labels = vec![solidcraft_ui_text::tr_label("Add filter…")];
|
||||
add_labels.extend(all.iter().copied());
|
||||
let mut pick = 0;
|
||||
ui_kit::row(ui, crate::i18n::tr_label("Filter by"), |ui| ui_kit::dropdown(ui, "point_cloud_add_filter", &mut pick, &add_labels));
|
||||
ui_kit::row(ui, solidcraft_ui_text::tr_label("Filter by"), |ui| ui_kit::dropdown(ui, "point_cloud_add_filter", &mut pick, &add_labels));
|
||||
if let Some(name) = all.get(pick.wrapping_sub(1)) {
|
||||
// A label keeps all its values at first, anything else its whole span.
|
||||
let keep = match (label(&c, name), c.span(name)) {
|
||||
@@ -540,7 +541,7 @@ pub(crate) fn panel(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
if changed {
|
||||
edit = Some(json!({ "cloud": c.id, "filters": filters_json(&filters) }));
|
||||
}
|
||||
if !c.source.is_empty() && ui_kit::button(ui, crate::i18n::tr("ui.point_cloud_reload"), ui_kit::Button::Normal).clicked() {
|
||||
if !c.source.is_empty() && ui_kit::button(ui, solidcraft_ui_text::tr("ui.point_cloud_reload"), ui_kit::Button::Normal).clicked() {
|
||||
reload = true;
|
||||
}
|
||||
});
|
||||
@@ -599,7 +600,7 @@ mod tests {
|
||||
}
|
||||
h.app.run("pointcloud.edit", json!({"cloud": id, "color_by": "height"})).unwrap();
|
||||
h.frames(2);
|
||||
assert!(h.widget(crate::i18n::tr("ui.point_cloud_auto_range")).is_some());
|
||||
assert!(h.widget(solidcraft_ui_text::tr("ui.point_cloud_auto_range")).is_some());
|
||||
h.step(&json!({"widget": "Close"})).unwrap();
|
||||
h.frames(2);
|
||||
assert!(h.app.tree.cloud_panel.is_none());
|
||||
|
||||
@@ -22,10 +22,14 @@ pub(crate) fn panel(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
};
|
||||
let mut edit: Option<Value> = None;
|
||||
let (units, decimals) = (app.session.doc.units.clone(), app.preferences.length_decimals);
|
||||
let title = crate::i18n::format(if m.calibrate { "browser.calibrate_title" } else { "browser.edit_canvas_title" }, &[("name", &c.name)]);
|
||||
let title = solidcraft_ui_text::format(if m.calibrate { "browser.calibrate_title" } else { "browser.edit_canvas_title" }, &[("name", &c.name)]);
|
||||
let at = panel_pos(app);
|
||||
let (calibrate, name) = (m.calibrate, c.id);
|
||||
let (ok, cancel) = if calibrate { (crate::i18n::tr_label("Calibrate"), crate::i18n::tr("ui.cancel")) } else { ("", crate::i18n::tr("ui.close")) };
|
||||
let (ok, cancel) = if calibrate {
|
||||
(solidcraft_ui_text::tr_label("Calibrate"), solidcraft_ui_text::tr("ui.cancel"))
|
||||
} else {
|
||||
("", solidcraft_ui_text::tr("ui.close"))
|
||||
};
|
||||
let (closed, action) = ui_kit::Window::new(egui::Id::unique("sc_canvas_panel"), "canvas", &title)
|
||||
.placed(egui::Align2::LEFT_TOP, at)
|
||||
.fields()
|
||||
@@ -34,11 +38,11 @@ pub(crate) fn panel(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
if calibrate {
|
||||
ui_kit::text(
|
||||
ui,
|
||||
crate::i18n::tr(if m.next == 0 { "browser.calibrate_click_a" } else { "browser.calibrate_click_b" }),
|
||||
solidcraft_ui_text::tr(if m.next == 0 { "browser.calibrate_click_a" } else { "browser.calibrate_click_b" }),
|
||||
ui_kit::Text::Note("calibrate_hint"),
|
||||
);
|
||||
for (label, p) in [("Point A", &mut m.a), ("Point B", &mut m.b)] {
|
||||
ui_kit::row(ui, crate::i18n::tr_label(label), |ui| {
|
||||
ui_kit::row(ui, solidcraft_ui_text::tr_label(label), |ui| {
|
||||
let [x, y] = p;
|
||||
ui.horizontal(|ui| {
|
||||
ui_kit::length_field(ui, x, &units, decimals);
|
||||
@@ -46,18 +50,20 @@ pub(crate) fn panel(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
});
|
||||
});
|
||||
}
|
||||
ui_kit::row(ui, crate::i18n::tr_label("Distance"), |ui| ui_kit::length_field(ui, &mut m.distance, &units, decimals));
|
||||
ui_kit::row(ui, solidcraft_ui_text::tr_label("Distance"), |ui| ui_kit::length_field(ui, &mut m.distance, &units, decimals));
|
||||
m.distance = m.distance.clamp(0.001, 1e6);
|
||||
} else {
|
||||
let (mut x, mut y, mut w, mut ang, mut op, mut flip) = (c.center.x, c.center.y, c.width, c.angle.to_degrees(), c.opacity, c.flip);
|
||||
let mut changed = false;
|
||||
changed |= ui_kit::row(ui, crate::i18n::tr_label("Center X"), |ui| ui_kit::length_field(ui, &mut x, &units, decimals)).changed();
|
||||
changed |= ui_kit::row(ui, crate::i18n::tr_label("Center Y"), |ui| ui_kit::length_field(ui, &mut y, &units, decimals)).changed();
|
||||
changed |= ui_kit::row(ui, crate::i18n::tr_label("Width"), |ui| ui_kit::length_field(ui, &mut w, &units, decimals)).changed();
|
||||
changed |= ui_kit::row(ui, crate::i18n::tr_label("Angle"), |ui| ui_kit::angle_field(ui, &mut ang)).changed();
|
||||
changed |=
|
||||
ui_kit::row(ui, crate::i18n::tr_label("Opacity"), |ui| ui_kit::slider(ui, &mut op, 0.0..=1.0, Default::default())).changed();
|
||||
if ui_kit::checkbox_row(ui, "flip", crate::i18n::tr_label("Flip"), flip) {
|
||||
ui_kit::row(ui, solidcraft_ui_text::tr_label("Center X"), |ui| ui_kit::length_field(ui, &mut x, &units, decimals)).changed();
|
||||
changed |=
|
||||
ui_kit::row(ui, solidcraft_ui_text::tr_label("Center Y"), |ui| ui_kit::length_field(ui, &mut y, &units, decimals)).changed();
|
||||
changed |= ui_kit::row(ui, solidcraft_ui_text::tr_label("Width"), |ui| ui_kit::length_field(ui, &mut w, &units, decimals)).changed();
|
||||
changed |= ui_kit::row(ui, solidcraft_ui_text::tr_label("Angle"), |ui| ui_kit::angle_field(ui, &mut ang)).changed();
|
||||
changed |= ui_kit::row(ui, solidcraft_ui_text::tr_label("Opacity"), |ui| ui_kit::slider(ui, &mut op, 0.0..=1.0, Default::default()))
|
||||
.changed();
|
||||
if ui_kit::checkbox_row(ui, "flip", solidcraft_ui_text::tr_label("Flip"), flip) {
|
||||
flip = !flip;
|
||||
changed = true;
|
||||
}
|
||||
@@ -113,11 +119,11 @@ pub(crate) fn click(app: &mut SolveApp, proj: &Proj, screen: Pos2) -> bool {
|
||||
if panel.next == 0 {
|
||||
panel.a = point;
|
||||
panel.next = 1;
|
||||
app.set_status(crate::i18n::tr("browser.calibrate_click_b"), false);
|
||||
app.set_status(solidcraft_ui_text::tr("browser.calibrate_click_b"), false);
|
||||
} else {
|
||||
panel.b = point;
|
||||
panel.next = 0;
|
||||
app.set_status(crate::i18n::tr("browser.calibrate_click_a"), false);
|
||||
app.set_status(solidcraft_ui_text::tr("browser.calibrate_click_a"), false);
|
||||
}
|
||||
app.tree.canvas_panel = Some(panel);
|
||||
true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::*;
|
||||
use crate::scenario::{Harness, handle_at, rect_of};
|
||||
use crate::semantic_content::{self, ContentOwner, ContentRequest};
|
||||
use crate::shell::{LayoutMode, SHELL_GAP};
|
||||
use crate::ui_kit::{LayoutMode, SHELL_GAP};
|
||||
|
||||
#[test]
|
||||
fn clicked_browser_folders_toggle_visibility_with_v() {
|
||||
@@ -318,7 +318,7 @@ fn selected_row_paints_tab_soft_fill_and_accent_text_in_both_layouts() {
|
||||
let mut app = SolveApp::new(Default::default(), Default::default());
|
||||
app.ui.layout = mode;
|
||||
app.ui.tab = tab.into();
|
||||
crate::theme::TabAccent::publish(&app, &ctx);
|
||||
app.publish_tab_accent(&ctx);
|
||||
let accent = crate::theme::TabAccent::for_tab(&Tokens::get(), tab);
|
||||
let mut row_rect = Rect::NOTHING;
|
||||
let out = crate::test_support::frame(&ctx, egui::RawInput::default(), |ui| {
|
||||
@@ -595,7 +595,7 @@ fn browser_title_uses_a_single_semibold_run() {
|
||||
ui.set_width(250.0);
|
||||
browser_header(&mut app, ui, true);
|
||||
});
|
||||
let title = crate::i18n::tr("ui.browser_title");
|
||||
let title = solidcraft_ui_text::tr("ui.browser_title");
|
||||
let titles: Vec<_> = output
|
||||
.shapes
|
||||
.iter()
|
||||
@@ -646,7 +646,7 @@ fn browser_width_rejects_hostile_preferences_and_tiny_windows_still_fit() {
|
||||
assert_eq!(browser_width(-100.0), MIN_BROWSER_WIDTH);
|
||||
assert_eq!(browser_width(f32::MAX), MAX_BROWSER_WIDTH);
|
||||
let available = Rect::from_min_size(Pos2::ZERO, vec2(100.0, 300.0));
|
||||
for top_gap in [0.0, crate::shell::SHELL_GAP] {
|
||||
for top_gap in [0.0, crate::ui_kit::SHELL_GAP] {
|
||||
let bounds = panel_bounds(available, None, MAX_BROWSER_WIDTH, top_gap);
|
||||
assert!(available.contains_rect(bounds));
|
||||
}
|
||||
@@ -677,7 +677,7 @@ fn collapsed_rail_shares_header_hover_inset_and_expands_from_its_whole_card() {
|
||||
app.ui.tab = "SURFACE".into();
|
||||
app.preferences.timeline_left = true;
|
||||
app.preferences.timeline_left_browser_collapsed = collapsed;
|
||||
crate::theme::TabAccent::publish(&app, &ctx);
|
||||
app.publish_tab_accent(&ctx);
|
||||
let bounds = Rect::from_min_size(Pos2::ZERO, vec2(600.0, 700.0));
|
||||
let draw = |app: &mut SolveApp, events| {
|
||||
crate::test_support::frame(&ctx, egui::RawInput { screen_rect: Some(bounds), events, ..Default::default() }, |ui| {
|
||||
@@ -751,8 +751,8 @@ fn mesh_and_solid_body_rows_have_different_icons_and_tooltips() {
|
||||
let row = |name: &str| rows.iter().find(|e| e.key == name).unwrap_or_else(|| panic!("no {name} row in {:?}", body_rows(&h.app)));
|
||||
assert_eq!((row("Mesh").icon, row("Mesh").tip), ("mesh_body", Some("ui.body_kind_mesh")));
|
||||
assert_eq!((row("Solid").icon, row("Solid").tip), ("body", Some("ui.body_kind_solid")));
|
||||
assert_eq!(crate::i18n::tr("ui.body_kind_mesh"), "Mesh body");
|
||||
assert_eq!(crate::i18n::tr("ui.body_kind_solid"), "Solid body");
|
||||
assert_eq!(solidcraft_ui_text::tr("ui.body_kind_mesh"), "Mesh body");
|
||||
assert_eq!(solidcraft_ui_text::tr("ui.body_kind_solid"), "Solid body");
|
||||
// Its own drawing in the mesh colours, not the solid body's grey cube.
|
||||
let icon = crate::icon_svg::resolve("mesh_body");
|
||||
assert!(!icon.placeholder);
|
||||
|
||||
@@ -211,11 +211,7 @@ fn with_rows_starting_closed_a_designs_folders_open_with_their_arrow_or_find_in_
|
||||
"the Bodies folder stays closed"
|
||||
);
|
||||
|
||||
crate::context_menu::run_item(
|
||||
&mut h.app,
|
||||
&crate::context_menu::Item::action("ui.findBrowser", "", "").with(json!({"body":"Block"})),
|
||||
egui::Pos2::ZERO,
|
||||
);
|
||||
crate::context_menu::run_item(&mut h.app, &crate::ui_kit::Item::action("ui.findBrowser", "", "").with(json!({"body":"Block"})), egui::Pos2::ZERO);
|
||||
h.frames(2);
|
||||
assert!(super::is_open(&h.app, "c0/bodies", false), "Find in Browser opens the Bodies folder");
|
||||
assert!(
|
||||
@@ -571,7 +567,7 @@ fn feature_editor_browser_rows_follow_the_marker_and_return_after_cancel() {
|
||||
fn the_edited_sketch_is_marked_in_the_users_language() {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.run("sketch.create", json!({"plane":"XY","name":"Sketch1"})).unwrap();
|
||||
let _japanese = crate::i18n::scope("ja");
|
||||
let _japanese = solidcraft_ui_text::scope("ja");
|
||||
let rows = super::entries(&h.app, 0, "sketches");
|
||||
assert_eq!(rows.iter().map(|r| r.label.as_str()).collect::<Vec<_>>(), ["スケッチ1 (編集中)"]);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ pub(crate) fn occurrence_panel(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let name = app.session.doc.occurrences.iter().find(|o| o.id == m.occurrence).map(|o| o.name.clone()).unwrap_or_default();
|
||||
let before = (m.translate, m.angles);
|
||||
let shared = !m.path.is_empty() && app.session.occurrence_scope_count(&m.path).is_ok_and(|n| n > 1);
|
||||
let title = crate::i18n::format("browser.move_title", &[("name", &name)]);
|
||||
let title = solidcraft_ui_text::format("browser.move_title", &[("name", &name)]);
|
||||
let at = panel_pos(app);
|
||||
let (units, decimals) = (app.session.doc.units.clone(), app.preferences.length_decimals);
|
||||
let (closed, action) = ui_kit::Window::new(egui::Id::unique("sc_occ_move"), "move", &title)
|
||||
@@ -50,19 +50,19 @@ pub(crate) fn occurrence_panel(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
if shared {
|
||||
ui_kit::text(
|
||||
ui,
|
||||
crate::i18n::tr_label("This child is shared. Position changes apply to every copy of its parent."),
|
||||
solidcraft_ui_text::tr_label("This child is shared. Position changes apply to every copy of its parent."),
|
||||
ui_kit::Text::Note("shared"),
|
||||
);
|
||||
}
|
||||
for (k, axis) in ["X distance", "Y distance", "Z distance"].into_iter().enumerate() {
|
||||
if let Some(v) = m.translate.get_mut(k) {
|
||||
let response = ui_kit::row(ui, crate::i18n::tr_label(axis), |ui| ui_kit::length_field(ui, v, &units, decimals));
|
||||
let response = ui_kit::row(ui, solidcraft_ui_text::tr_label(axis), |ui| ui_kit::length_field(ui, v, &units, decimals));
|
||||
crate::scenario::publish_handle(&format!("occurrence-move:distance:{k}"), response.rect.center());
|
||||
}
|
||||
}
|
||||
for (k, axis) in ["X angle", "Y angle", "Z angle"].into_iter().enumerate() {
|
||||
if let Some(v) = m.angles.get_mut(k) {
|
||||
let response = ui_kit::row(ui, crate::i18n::tr_label(axis), |ui| ui_kit::angle_field(ui, v));
|
||||
let response = ui_kit::row(ui, solidcraft_ui_text::tr_label(axis), |ui| ui_kit::angle_field(ui, v));
|
||||
crate::scenario::publish_handle(&format!("occurrence-move:angle:{k}"), response.rect.center());
|
||||
}
|
||||
}
|
||||
@@ -97,17 +97,17 @@ pub(crate) fn capture_prompt(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
return;
|
||||
}
|
||||
let n = app.session.pending_moves.len();
|
||||
let (closed, action) = ui_kit::Window::new(egui::Id::unique("sc_capture_prompt"), "capture", crate::i18n::tr_label("Capture Position"))
|
||||
let (closed, action) = ui_kit::Window::new(egui::Id::unique("sc_capture_prompt"), "capture", solidcraft_ui_text::tr_label("Capture Position"))
|
||||
.anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, -80.0))
|
||||
.actions("Capture Position", "Cancel", Some("Revert"))
|
||||
.show(ctx, true, |ui| {
|
||||
let question = if n == 1 {
|
||||
crate::i18n::tr("browser.capture_one").to_string()
|
||||
solidcraft_ui_text::tr("browser.capture_one").to_string()
|
||||
} else {
|
||||
crate::i18n::format("browser.capture_many", &[("count", &n.to_string())])
|
||||
solidcraft_ui_text::format("browser.capture_many", &[("count", &n.to_string())])
|
||||
};
|
||||
ui_kit::text(ui, &question, ui_kit::Text::Report("question", false));
|
||||
ui_kit::text(ui, crate::i18n::tr("browser.capture_note"), ui_kit::Text::Note("capture"));
|
||||
ui_kit::text(ui, solidcraft_ui_text::tr("browser.capture_note"), ui_kit::Text::Note("capture"));
|
||||
});
|
||||
let answer = match action {
|
||||
ui_kit::Action::Ok => Some("capture"),
|
||||
|
||||
@@ -955,7 +955,7 @@ fn value_box_full(
|
||||
let frame = Container::popup_frame(ui.style()).inner_margin(egui::Margin::symmetric(6, 3));
|
||||
Container::show_backing(ui, frame, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(egui::RichText::new(crate::i18n::tr_label(label)).color(t.text_dim).size(crate::theme::Tokens::get().font_caption));
|
||||
ui.label(egui::RichText::new(solidcraft_ui_text::tr_label(label)).color(t.text_dim).size(crate::theme::Tokens::get().font_caption));
|
||||
let r = crate::ui_kit::canvas_value_field(ui, id, value, keep_tab);
|
||||
crate::params_dialog::complete(ui, &r, value);
|
||||
if select_all {
|
||||
@@ -1049,7 +1049,7 @@ fn box_size(ui: &egui::Ui, area: egui::Id, label: &str, note: Option<&str>) -> e
|
||||
let t = Tokens::get();
|
||||
let font = egui::FontId::proportional(t.font_caption);
|
||||
let width = |text: String| ui.painter().layout_no_wrap(text, font.clone(), t.text_dim).size().x;
|
||||
let mut w = width(crate::i18n::tr_label(label).to_string()) + t.space_24 * 3.0;
|
||||
let mut w = width(solidcraft_ui_text::tr_label(label).to_string()) + t.space_24 * 3.0;
|
||||
let mut parts = 1;
|
||||
if let Some(note) = note {
|
||||
w += width(note.to_string());
|
||||
@@ -1324,7 +1324,7 @@ fn radius_spline_handles(app: &SolveApp, ui: &mut egui::Ui, painter: &egui::Pain
|
||||
let at = |u: f64| e.start + (e.other - e.start) * u;
|
||||
let g = e.spline.greville();
|
||||
let tips: Vec<Option<Pos2>> = g.iter().zip(&e.spline.radii).map(|(u, r)| proj.to_screen(at(*u) + e.n * *r)).collect();
|
||||
painter.extend(crate::viewport::dashed_line(
|
||||
painter.extend(crate::ui_kit::dashed_line(
|
||||
&tips.iter().flatten().copied().collect::<Vec<_>>(),
|
||||
painter.clip_rect(),
|
||||
Stroke::new(1.0, t.manipulator),
|
||||
@@ -1588,7 +1588,7 @@ fn measure_components(
|
||||
let (at, offset) = if let Some((sa, sb)) = screen {
|
||||
let leg = sb - sa;
|
||||
if leg.length_sq() > 1.0 {
|
||||
painter.extend(crate::viewport::dashed_line(&[sa, sb], painter.clip_rect(), Stroke::new(1.5, color), 5.0, 3.0));
|
||||
painter.extend(crate::ui_kit::dashed_line(&[sa, sb], painter.clip_rect(), Stroke::new(1.5, color), 5.0, 3.0));
|
||||
let normal = vec2(-leg.y, leg.x).normalized();
|
||||
for p in [sa, sb] {
|
||||
painter.line_segment([p - normal * 3.0, p + normal * 3.0], Stroke::new(1.0, color));
|
||||
@@ -1709,7 +1709,7 @@ fn taper_handle(
|
||||
crate::scenario::publish_handle("taper", es);
|
||||
let hot = r.hovered() || r.dragged();
|
||||
let col = if hot { t.accent } else { t.manipulator };
|
||||
painter.extend(crate::viewport::dashed_line(&[fs, es], painter.clip_rect(), Stroke::new(1.2, col), 5.0, 3.0));
|
||||
painter.extend(crate::ui_kit::dashed_line(&[fs, es], painter.clip_rect(), Stroke::new(1.2, col), 5.0, 3.0));
|
||||
painter.circle(es, if hot { 6.5 } else { 5.0 }, t.overlay, Stroke::new(2.0, col));
|
||||
if hot {
|
||||
ui.ctx().set_cursor_icon(egui::CursorIcon::Grab);
|
||||
@@ -1901,12 +1901,13 @@ fn draw_revolve(
|
||||
} else {
|
||||
"Choose target".into()
|
||||
};
|
||||
ui.label(text).backing_on_hover_text(crate::i18n::tr_label("Determined by To Object. Choose Angle to edit the angular extent."));
|
||||
ui.label(text)
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr_label("Determined by To Object. Choose Angle to edit the angular extent."));
|
||||
});
|
||||
});
|
||||
} else if let Kind::Revolve { angle, .. } = &mut d.kind {
|
||||
let dragging = *angle != angle_before;
|
||||
value_box(ui, crate::i18n::tr_label("Angle"), angle, box_at, focus || dragging, box_place(app, proj));
|
||||
value_box(ui, solidcraft_ui_text::tr_label("Angle"), angle, box_at, focus || dragging, box_place(app, proj));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@ fn analysis_shader_colours_come_from_the_uniform() {
|
||||
"vec3<f32>(0.35)",
|
||||
"vec3<f32>(1.0)",
|
||||
],
|
||||
"a new colour in the analysis shader must come from `ana_c` (theme::AnalysisColors)"
|
||||
"a new colour in the analysis shader must come from `ana_c` (solidcraft_color::AnalysisColors)"
|
||||
);
|
||||
assert_eq!(body.matches("u.ana_c[").count(), 9, "every analysis colour reads `ana_c`");
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
//! tooltip. Text only for now; pictures and animations are a later step. A new toolbar command
|
||||
//! ships with its help: `every_toolbar_command_has_help` fails otherwise.
|
||||
|
||||
use crate::i18n;
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
|
||||
/// The commands that have help text. Each one needs a `help.<id>` message in the catalogs.
|
||||
@@ -330,7 +329,7 @@ pub fn help(command: &str) -> Option<String> {
|
||||
return None;
|
||||
}
|
||||
let key = format!("help.{command}");
|
||||
let text = i18n::tr(&key);
|
||||
let text = solidcraft_ui_text::tr(&key);
|
||||
(text != key).then(|| text.to_owned())
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ impl Default for Editor {
|
||||
}
|
||||
}
|
||||
fn choose(ui: &mut Ui, id: &str, value: &mut String, options: &[String]) {
|
||||
let mut labels = vec![crate::i18n::tr_label("Choose…")];
|
||||
let mut labels = vec![solidcraft_ui_text::tr_label("Choose…")];
|
||||
labels.extend(options.iter().map(String::as_str));
|
||||
let mut selected = options.iter().position(|o| o == value).map_or(0, |i| i + 1);
|
||||
let before = selected;
|
||||
@@ -92,13 +92,13 @@ pub(super) fn show(ui: &mut Ui, app: &mut SolveApp, error: &mut Option<String>,
|
||||
let unlisted_features = suppression_features.len() < features.len();
|
||||
let unlisted_parameters = all_parameters.iter().any(|p| !parameters.contains(p));
|
||||
ui_kit::divider(ui, "configuration_rules");
|
||||
ui_kit::text(ui, crate::i18n::tr_label("Configuration rules"), ui_kit::Text::Heading("rules_heading"));
|
||||
ui_kit::text(ui, solidcraft_ui_text::tr_label("Configuration rules"), ui_kit::Text::Heading("rules_heading"));
|
||||
ui_kit::text(
|
||||
ui,
|
||||
crate::i18n::tr_label("Rules run from top to bottom. Matched drives change effective values."),
|
||||
solidcraft_ui_text::tr_label("Rules run from top to bottom. Matched drives change effective values."),
|
||||
ui_kit::Text::Note("rules_drives"),
|
||||
);
|
||||
ui_kit::text(ui, crate::i18n::tr_label("Matched restrictions check the final values."), ui_kit::Text::Note("rules_restrictions"));
|
||||
ui_kit::text(ui, solidcraft_ui_text::tr_label("Matched restrictions check the final values."), ui_kit::Text::Note("rules_restrictions"));
|
||||
for (index, rule) in rules.iter().enumerate() {
|
||||
ui.push_id(&rule.id, |ui| {
|
||||
let summary = match &rule.effect {
|
||||
@@ -114,21 +114,22 @@ pub(super) fn show(ui: &mut Ui, app: &mut SolveApp, error: &mut Option<String>,
|
||||
+ crate::theme::Tokens::get().space_4 * 3.0;
|
||||
let mut enabled = rule.enabled;
|
||||
if ui_kit::summary_row(ui, &rule.name, &summary, &mut enabled, actions_width, |ui| {
|
||||
if ui_kit::button(ui, crate::i18n::tr_label("Edit"), ui_kit::Button::Normal).clicked() {
|
||||
if ui_kit::button(ui, solidcraft_ui_text::tr_label("Edit"), ui_kit::Button::Normal).clicked() {
|
||||
editor.rule = rule.clone();
|
||||
}
|
||||
let up = ui_kit::button(ui, "↑", ui_kit::Button::Enabled(index > 0)).backing_on_hover_text(crate::i18n::tr_label("Move earlier"));
|
||||
let up =
|
||||
ui_kit::button(ui, "↑", ui_kit::Button::Enabled(index > 0)).backing_on_hover_text(solidcraft_ui_text::tr_label("Move earlier"));
|
||||
crate::scenario::publish_handle(&format!("config:rule:{}:up", rule.id), up.rect.center());
|
||||
if up.clicked() {
|
||||
*error = app.run("config.rule", json!({"id":rule.id,"index":index-1})).err();
|
||||
}
|
||||
if ui_kit::button(ui, "↓", ui_kit::Button::Enabled(index + 1 < rules.len()))
|
||||
.backing_on_hover_text(crate::i18n::tr_label("Move later"))
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr_label("Move later"))
|
||||
.clicked()
|
||||
{
|
||||
*error = app.run("config.rule", json!({"id":rule.id,"index":index+1})).err();
|
||||
}
|
||||
if ui_kit::button(ui, crate::i18n::tr_label("Delete"), ui_kit::Button::Normal).clicked() {
|
||||
if ui_kit::button(ui, solidcraft_ui_text::tr_label("Delete"), ui_kit::Button::Normal).clicked() {
|
||||
*error = app.run("config.rule", json!({"id":rule.id,"delete":true})).err();
|
||||
}
|
||||
}) {
|
||||
@@ -141,8 +142,8 @@ pub(super) fn show(ui: &mut Ui, app: &mut SolveApp, error: &mut Option<String>,
|
||||
ui_kit::section(ui, "rule_editor", "Add or edit rule", false, |ui| {
|
||||
ui.vertical(|ui| {
|
||||
expression(ui, "Rule name", &mut editor.rule.name);
|
||||
ui_kit::checkbox(ui, crate::i18n::tr_label("Enabled"), &mut editor.rule.enabled);
|
||||
if ui_kit::button(ui, crate::i18n::tr_label("New"), ui_kit::Button::Small).clicked() {
|
||||
ui_kit::checkbox(ui, solidcraft_ui_text::tr_label("Enabled"), &mut editor.rule.enabled);
|
||||
if ui_kit::button(ui, solidcraft_ui_text::tr_label("New"), ui_kit::Button::Small).clicked() {
|
||||
editor.rule = Editor::default().rule;
|
||||
}
|
||||
});
|
||||
@@ -192,7 +193,7 @@ pub(super) fn show(ui: &mut Ui, app: &mut SolveApp, error: &mut Option<String>,
|
||||
ui.vertical(|ui| {
|
||||
feature_choice(ui, "effect_feature", feature, &suppression_features);
|
||||
columns_note(ui, unlisted_features, "config.rule_suppression_columns");
|
||||
ui_kit::checkbox(ui, crate::i18n::tr_label("Suppressed"), value);
|
||||
ui_kit::checkbox(ui, solidcraft_ui_text::tr_label("Suppressed"), value);
|
||||
});
|
||||
}
|
||||
RuleEffect::ParameterRange { parameter, minimum, maximum } => {
|
||||
@@ -258,7 +259,7 @@ pub(super) fn show(ui: &mut Ui, app: &mut SolveApp, error: &mut Option<String>,
|
||||
}
|
||||
if let Some(preview) = &editor.preview {
|
||||
ui_kit::section(ui, "rule_preview", &format!("Preview: {}", preview["active"].as_str().unwrap_or("row")), false, |ui| {
|
||||
report(ui, crate::i18n::tr_label("Preview leaves the current design unchanged."));
|
||||
report(ui, solidcraft_ui_text::tr_label("Preview leaves the current design unchanged."));
|
||||
if let Some(styles) = preview["styles"].as_array() {
|
||||
for style in styles {
|
||||
report(
|
||||
@@ -326,11 +327,11 @@ pub(super) fn show(ui: &mut Ui, app: &mut SolveApp, error: &mut Option<String>,
|
||||
/// how to add one.
|
||||
fn columns_note(ui: &mut Ui, unlisted: bool, message: &'static str) {
|
||||
if unlisted {
|
||||
ui_kit::text(ui, crate::i18n::tr(message), ui_kit::Text::Note(message));
|
||||
ui_kit::text(ui, solidcraft_ui_text::tr(message), ui_kit::Text::Note(message));
|
||||
}
|
||||
}
|
||||
fn report(ui: &mut Ui, text: impl AsRef<str>) {
|
||||
ui_kit::text(ui, crate::i18n::tr_label(text.as_ref()), ui_kit::Text::Report("rule_report", false));
|
||||
ui_kit::text(ui, solidcraft_ui_text::tr_label(text.as_ref()), ui_kit::Text::Report("rule_report", false));
|
||||
}
|
||||
|
||||
fn feature_choice(ui: &mut Ui, id: &str, feature: &mut u64, features: &[(u64, String)]) {
|
||||
@@ -398,7 +399,7 @@ fn condition_editor(ui: &mut Ui, condition: &mut RuleCondition, parameters: &[St
|
||||
RuleCondition::Suppression { feature, value } => {
|
||||
ui.vertical(|ui| {
|
||||
feature_choice(ui, "condition_feature", feature, features);
|
||||
ui_kit::checkbox(ui, crate::i18n::tr_label("Suppressed"), value);
|
||||
ui_kit::checkbox(ui, solidcraft_ui_text::tr_label("Suppressed"), value);
|
||||
});
|
||||
}
|
||||
RuleCondition::All { conditions } | RuleCondition::Any { conditions } => {
|
||||
@@ -407,7 +408,7 @@ fn condition_editor(ui: &mut Ui, condition: &mut RuleCondition, parameters: &[St
|
||||
ui.push_id(index, |ui| {
|
||||
ui.indent("condition_group", |ui| {
|
||||
condition_editor(ui, child, parameters, features, depth + 1);
|
||||
if ui_kit::button(ui, crate::i18n::tr_label("Remove condition"), ui_kit::Button::Small).clicked() {
|
||||
if ui_kit::button(ui, solidcraft_ui_text::tr_label("Remove condition"), ui_kit::Button::Small).clicked() {
|
||||
remove = Some(index);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ pub(super) fn configurations_rows(
|
||||
let run = |app: &mut SolveApp, error: &mut Option<String>, id: &str, p: Value| {
|
||||
*error = app.run(id, p).err();
|
||||
};
|
||||
let labels: Vec<_> = std::iter::once(crate::i18n::tr_label("Configuration").to_owned())
|
||||
let labels: Vec<_> = std::iter::once(solidcraft_ui_text::tr_label("Configuration").to_owned())
|
||||
.chain(tb.columns.iter().map(|c| {
|
||||
let (kind, name) = column_parts(c);
|
||||
v["targets"]
|
||||
@@ -36,13 +36,13 @@ pub(super) fn configurations_rows(
|
||||
.filter(|_| matches!(kind, "component_appearance" | "face_appearance"))
|
||||
.map(str::to_string)
|
||||
.unwrap_or_else(|| match kind {
|
||||
"material" => format!("{}: {name}", crate::i18n::tr_label("Material")),
|
||||
"appearance" => format!("{}: {name}", crate::i18n::tr_label("Appearance")),
|
||||
"suppress" => format!("{}: {name}", crate::i18n::tr_label("Suppress")),
|
||||
"material" => format!("{}: {name}", solidcraft_ui_text::tr_label("Material")),
|
||||
"appearance" => format!("{}: {name}", solidcraft_ui_text::tr_label("Appearance")),
|
||||
"suppress" => format!("{}: {name}", solidcraft_ui_text::tr_label("Suppress")),
|
||||
_ => name.to_owned(),
|
||||
})
|
||||
}))
|
||||
.chain(std::iter::once(crate::i18n::tr_label("Actions").to_owned()))
|
||||
.chain(std::iter::once(solidcraft_ui_text::tr_label("Actions").to_owned()))
|
||||
.collect();
|
||||
let gap = ui.spacing().item_spacing.x;
|
||||
let name_width = tb.rows.iter().map(|(name, _)| ui_kit::ContentWidth::name(ui, name)).fold(0.0, f32::max);
|
||||
@@ -76,7 +76,7 @@ pub(super) fn configurations_rows(
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
ui_kit::table_custom(ui, "configuration_table", &columns, crate::i18n::tr("manage.empty"), |ui, table| {
|
||||
ui_kit::table_custom(ui, "configuration_table", &columns, solidcraft_ui_text::tr("manage.empty"), |ui, table| {
|
||||
table.headers(ui, |ui, index| {
|
||||
if index == labels.len() - 1 {
|
||||
column_menu(app, ui, error, &tb.columns, rule_editor);
|
||||
@@ -112,14 +112,14 @@ pub(super) fn configurations_rows(
|
||||
if active {
|
||||
ui_kit::active_pill(ui);
|
||||
if v.get("matches").and_then(Value::as_bool) == Some(false) {
|
||||
let apply = ui_kit::button(ui, crate::i18n::tr_label("Apply"), ui_kit::Button::Small);
|
||||
let apply = ui_kit::button(ui, solidcraft_ui_text::tr_label("Apply"), ui_kit::Button::Small);
|
||||
crate::scenario::publish_handle(&format!("config:activate:{name}"), apply.rect.center());
|
||||
if apply.clicked() {
|
||||
run(app, error, "config.activate", json!({"row":name}));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let activate = ui_kit::button(ui, crate::i18n::tr_label("Activate"), ui_kit::Button::Small);
|
||||
let activate = ui_kit::button(ui, solidcraft_ui_text::tr_label("Activate"), ui_kit::Button::Small);
|
||||
crate::scenario::publish_handle(&format!("config:activate:{name}"), activate.rect.center());
|
||||
if activate.clicked() {
|
||||
run(app, error, "config.activate", json!({"row":name}));
|
||||
@@ -343,7 +343,7 @@ fn column_menu(
|
||||
let mut pick: Option<Value> = None;
|
||||
let filter = &mut rule_editor.column_filter;
|
||||
let was_open = rule_editor.column_menu_open;
|
||||
let face_appearance = crate::i18n::tr_label("Face Appearance");
|
||||
let face_appearance = solidcraft_ui_text::tr_label("Face Appearance");
|
||||
let shown = ui_kit::plus_header(ui, |ui| {
|
||||
// A design has many parameters, features and bodies: a filter narrows the list as the
|
||||
// Parameters dialog's does. It takes focus as the menu opens, so typing filters at once.
|
||||
@@ -361,7 +361,7 @@ fn column_menu(
|
||||
}
|
||||
let faces = query.is_empty() || face_appearance.to_lowercase().contains(&query);
|
||||
if matching.is_empty() && !faces {
|
||||
ui_kit::text(ui, crate::i18n::tr("config.column_filter_empty"), ui_kit::Text::Note("column_filter_empty"));
|
||||
ui_kit::text(ui, solidcraft_ui_text::tr("config.column_filter_empty"), ui_kit::Text::Note("column_filter_empty"));
|
||||
}
|
||||
// The popup remembers its size: once the filter has emptied the list, a list that fills
|
||||
// again would be clipped to that small height, so a list that scrolls takes the full one.
|
||||
@@ -379,7 +379,7 @@ fn column_menu(
|
||||
ui_kit::action_menu(ui, "Face Appearance", |ui| {
|
||||
ui_kit::text(
|
||||
ui,
|
||||
crate::i18n::tr_label("Semantic faces share their appearance across instances."),
|
||||
solidcraft_ui_text::tr_label("Semantic faces share their appearance across instances."),
|
||||
ui_kit::Text::Note("semantic_faces"),
|
||||
);
|
||||
let state = app.session.model.state();
|
||||
@@ -486,7 +486,7 @@ fn value_control(ui: &mut egui::Ui, id: egui::Id, text: &mut String, kind: &str,
|
||||
"Default material"
|
||||
};
|
||||
ui_kit::library_cell(ui, id, text, follow, |ui| {
|
||||
if ui_kit::button(ui, crate::i18n::tr_label(follow), ui_kit::Button::Normal).clicked() {
|
||||
if ui_kit::button(ui, solidcraft_ui_text::tr_label(follow), ui_kit::Button::Normal).clicked() {
|
||||
// An appearance's "Default" keyword follows; a material's is the default material.
|
||||
let follow_value = if appearance { "Default" } else { DEFAULT_MATERIAL };
|
||||
choice = Some(follow_value.to_owned());
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//! state; [`items`] lists them and [`run_item`] runs one, so the control channel (`ui.menu`,
|
||||
//! `ui.menuPick`) and tests reach everything a click does.
|
||||
|
||||
use crate::ui_kit::{BackingTooltipExt as _, Container, Surface};
|
||||
use crate::ui_kit::{BackingTooltipExt as _, Container, Item, LIST_W, Surface};
|
||||
use egui::{Color32, Pos2, Rect, Sense, Vec2, pos2, vec2};
|
||||
use serde::Serialize;
|
||||
use serde_json::{Value, json};
|
||||
@@ -127,88 +127,6 @@ pub enum Target {
|
||||
},
|
||||
}
|
||||
|
||||
/// One menu entry: a command id (run with `params` when given, else started like a toolbar
|
||||
/// click) or a `ui.*` action. `-` is a separator, `#…` a section heading.
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct Item {
|
||||
pub id: String,
|
||||
pub label: String,
|
||||
pub shortcut: String,
|
||||
pub enabled: bool,
|
||||
pub icon: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub params: Option<Value>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub children: Vec<Item>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub swatch: Option<[u8; 3]>,
|
||||
/// Why the entry is off: shown on hover, and in the control channel's report.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tip: Option<String>,
|
||||
}
|
||||
|
||||
impl Item {
|
||||
/// A view action (`ui.*`).
|
||||
pub fn action(id: &str, label: &str, icon: &str) -> Item {
|
||||
act(id, label, icon)
|
||||
}
|
||||
pub fn sep() -> Item {
|
||||
Item {
|
||||
id: "-".into(),
|
||||
label: String::new(),
|
||||
shortcut: String::new(),
|
||||
enabled: false,
|
||||
icon: String::new(),
|
||||
params: None,
|
||||
children: Vec::new(),
|
||||
swatch: None,
|
||||
tip: None,
|
||||
}
|
||||
}
|
||||
pub(crate) fn heading(label: &str) -> Item {
|
||||
Item {
|
||||
id: format!("#{label}"),
|
||||
label: label.into(),
|
||||
shortcut: String::new(),
|
||||
enabled: false,
|
||||
icon: String::new(),
|
||||
params: None,
|
||||
children: Vec::new(),
|
||||
swatch: None,
|
||||
tip: None,
|
||||
}
|
||||
}
|
||||
fn is_sep(&self) -> bool {
|
||||
self.id == "-"
|
||||
}
|
||||
fn is_heading(&self) -> bool {
|
||||
self.id.starts_with('#')
|
||||
}
|
||||
pub fn with(mut self, params: Value) -> Item {
|
||||
self.params = Some(params);
|
||||
self
|
||||
}
|
||||
pub fn on_if(self, enabled: bool) -> Item {
|
||||
self.on(enabled)
|
||||
}
|
||||
pub(crate) fn on(mut self, enabled: bool) -> Item {
|
||||
self.enabled &= enabled;
|
||||
self
|
||||
}
|
||||
/// Off, saying why, when there is a reason.
|
||||
pub(crate) fn off_because(mut self, why: Option<String>) -> Item {
|
||||
if let Some(why) = why {
|
||||
self.enabled = false;
|
||||
self.tip = Some(why);
|
||||
}
|
||||
self
|
||||
}
|
||||
pub fn key(mut self, k: &str) -> Item {
|
||||
self.shortcut = k.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A command entry: label, icon, shortcut and enablement come from the registry.
|
||||
pub(crate) fn cmd(app: &SolveApp, id: &str, label: &str) -> Item {
|
||||
let info = solidcraft_engine::find_command(id).map(|c| c.info(&app.session));
|
||||
@@ -232,17 +150,7 @@ pub(crate) fn cmd(app: &SolveApp, id: &str, label: &str) -> Item {
|
||||
|
||||
/// A view action (`ui.*`).
|
||||
pub(crate) fn act(id: &str, label: &str, icon: &str) -> Item {
|
||||
Item {
|
||||
id: id.into(),
|
||||
label: label.into(),
|
||||
shortcut: String::new(),
|
||||
enabled: true,
|
||||
icon: icon.into(),
|
||||
params: None,
|
||||
children: Vec::new(),
|
||||
swatch: None,
|
||||
tip: None,
|
||||
}
|
||||
Item::action(id, label, icon)
|
||||
}
|
||||
|
||||
/// Menu state kept between frames. The anchor is `app.viewport.context_menu`.
|
||||
@@ -1701,7 +1609,7 @@ pub fn run_item(app: &mut SolveApp, item: &Item, at: Pos2) {
|
||||
if inserted.is_ok() {
|
||||
app.fit_view();
|
||||
if p.get("unsaved").and_then(Value::as_bool) == Some(true) {
|
||||
app.set_status(crate::i18n::format("ui.insert_design.saved_file", &[("name", name)]), false);
|
||||
app.set_status(solidcraft_ui_text::format("ui.insert_design.saved_file", &[("name", name)]), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2015,7 +1923,7 @@ pub fn run_item(app: &mut SolveApp, item: &Item, at: Pos2) {
|
||||
let calibrate = item.id == "ui.calibrateCanvas";
|
||||
app.tree.canvas_panel = Some(crate::browser::CanvasPanel::new(app, c, calibrate));
|
||||
if calibrate {
|
||||
app.set_status(crate::i18n::tr("browser.calibrate_click_a"), false);
|
||||
app.set_status(solidcraft_ui_text::tr("browser.calibrate_click_a"), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2048,15 +1956,18 @@ fn sketches_to_parts(app: &mut SolveApp, p: Value) {
|
||||
let mut why: Vec<String> = Vec::new();
|
||||
for kept in out.get("kept").and_then(Value::as_array).into_iter().flatten() {
|
||||
let code = kept.get("code").and_then(Value::as_str).unwrap_or_default();
|
||||
let reason = crate::i18n::tr(&format!("browser.sketches_to_parts_{code}")).to_string();
|
||||
let reason = solidcraft_ui_text::tr(&format!("browser.sketches_to_parts_{code}")).to_string();
|
||||
if !why.contains(&reason) {
|
||||
why.push(reason);
|
||||
}
|
||||
}
|
||||
let message = if why.is_empty() {
|
||||
crate::i18n::format("browser.sketches_to_parts_moved", &[("moved", &count("moved"))])
|
||||
solidcraft_ui_text::format("browser.sketches_to_parts_moved", &[("moved", &count("moved"))])
|
||||
} else {
|
||||
crate::i18n::format("browser.sketches_to_parts_result", &[("moved", &count("moved")), ("stayed", &count("stayed")), ("why", &why.join("; "))])
|
||||
solidcraft_ui_text::format(
|
||||
"browser.sketches_to_parts_result",
|
||||
&[("moved", &count("moved")), ("stayed", &count("stayed")), ("why", &why.join("; "))],
|
||||
)
|
||||
};
|
||||
app.set_status(message, false);
|
||||
}
|
||||
@@ -2535,9 +2446,6 @@ fn list_place(c: Pos2, h: f32, screen: Rect) -> Pos2 {
|
||||
pos2((c.x - RX - 12.0 - LIST_W).max(screen.left() + 4.0), y)
|
||||
}
|
||||
|
||||
/// Width of list menus.
|
||||
pub const LIST_W: f32 = 250.0;
|
||||
|
||||
/// Where a menu of `size` opened at `at` goes: right of and below the point (`gap` away) when it
|
||||
/// fits in `screen`, flipped to the left or above it when it would run past that edge, and
|
||||
/// against the far edge when it fits on neither side. A flipped menu ends at the cursor, so it
|
||||
@@ -3405,7 +3313,7 @@ mod tests {
|
||||
assert!(h.app.cam.back().dist(flat.plane_normal.unwrap()) < 2e-6);
|
||||
assert_eq!((h.app.cam.distance, h.app.cam.fov), (before.distance, before.fov));
|
||||
assert!(h.app.viewport.context_menu.is_none());
|
||||
assert_eq!(crate::i18n::Locale::for_language("ja").label("Normal to Selection"), "選択アイテムに垂直");
|
||||
assert_eq!(solidcraft_ui_text::Locale::for_language("ja").label("Normal to Selection"), "選択アイテムに垂直");
|
||||
// Ctrl+8 and the context menu share Look At, including its repeat-to-flip behavior.
|
||||
h.step(&json!({"key":"8","cmd":true})).unwrap();
|
||||
h.frames(90);
|
||||
|
||||
@@ -182,7 +182,7 @@ impl ControlJob {
|
||||
}
|
||||
|
||||
fn label(app: &SolveApp, command: &str) -> String {
|
||||
let language = crate::i18n::Locale::for_language(&app.preferences.language);
|
||||
let language = solidcraft_ui_text::Locale::for_language(&app.preferences.language);
|
||||
language.label(solidcraft_engine::find_command(command).map_or(command, |c| c.label)).to_string()
|
||||
}
|
||||
|
||||
@@ -473,7 +473,7 @@ impl SolveApp {
|
||||
let reply = reply.unwrap_or_else(|| json!({"ok": false, "error": format!("`{}` ended without a reply", job.label)}));
|
||||
let export = executed(&job.req.params).and_then(|(command, _)| spec_id(command)).is_some_and(|id| ANSWERED_ON_A_COPY.contains(&id));
|
||||
if reply["ok"] == true && export {
|
||||
let language = crate::i18n::Locale::for_language(&self.preferences.language);
|
||||
let language = solidcraft_ui_text::Locale::for_language(&self.preferences.language);
|
||||
self.session.echo(language.format("ui.command_done", &[("command", job.label.as_str())]));
|
||||
}
|
||||
let _ = job.req.reply.send(reply);
|
||||
|
||||
@@ -37,7 +37,7 @@ impl CrashUi {
|
||||
}
|
||||
|
||||
fn kind_label(kind: &str) -> &'static str {
|
||||
crate::i18n::tr(match kind {
|
||||
solidcraft_ui_text::tr(match kind {
|
||||
"command_panic" => "crash.kind_command",
|
||||
"kernel_panic_recovered" => "crash.kind_kernel",
|
||||
"unclean_exit" => "crash.kind_unclean",
|
||||
@@ -48,7 +48,7 @@ fn kind_label(kind: &str) -> &'static str {
|
||||
fn report_time(r: &Summary) -> String {
|
||||
let time = r.time.replace('T', " ");
|
||||
let time = time.trim_end_matches('Z');
|
||||
if r.kind == "unclean_exit" { crate::i18n::format("crash.detected_time", &[("time", time)]) } else { format!("{time} UTC") }
|
||||
if r.kind == "unclean_exit" { solidcraft_ui_text::format("crash.detected_time", &[("time", time)]) } else { format!("{time} UTC") }
|
||||
}
|
||||
|
||||
fn report_heading(r: &Summary) -> String {
|
||||
@@ -66,7 +66,7 @@ pub fn announce(app: &mut SolveApp, fresh: &[Summary]) {
|
||||
}
|
||||
app.crashes.open = true;
|
||||
app.crashes.refresh();
|
||||
let locale = crate::i18n::Locale::for_language(&app.preferences.language);
|
||||
let locale = solidcraft_ui_text::Locale::for_language(&app.preferences.language);
|
||||
app.set_status(locale.text("crash.previous_session"), true);
|
||||
}
|
||||
|
||||
@@ -76,17 +76,17 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
}
|
||||
let mut report: Option<String> = None;
|
||||
let mut delete: Option<Option<String>> = None;
|
||||
let (closed, action) = ui_kit::Window::new(egui::Id::unique("sc_crash_reports"), "bug_report", crate::i18n::tr_label("Crash Reports"))
|
||||
let (closed, action) = ui_kit::Window::new(egui::Id::unique("sc_crash_reports"), "bug_report", solidcraft_ui_text::tr_label("Crash Reports"))
|
||||
.cards(2.0)
|
||||
.actions("", crate::i18n::tr("ui.close"), None)
|
||||
.actions("", solidcraft_ui_text::tr("ui.close"), None)
|
||||
.show(ctx, true, |ui| {
|
||||
ui.spacing_mut().item_spacing.y = crate::theme::Tokens::get().space_10;
|
||||
let intro = ui_kit::text(ui, crate::i18n::tr("crash.intro"), ui_kit::Text::Hint("intro"));
|
||||
let intro = ui_kit::text(ui, solidcraft_ui_text::tr("crash.intro"), ui_kit::Text::Hint("intro"));
|
||||
crate::scenario::publish_rect("dialog:note:intro", intro.rect);
|
||||
if let Some(d) = app.crashes.dir.clone() {
|
||||
// A long path is cut short (hover shows it all): it must not widen the card.
|
||||
ui_kit::path_chip(ui, &d, |ui| {
|
||||
if ui_kit::icon_label_button(ui, crate::i18n::tr("crash.copy_folder"), "copy", true, false).clicked() {
|
||||
if ui_kit::icon_label_button(ui, solidcraft_ui_text::tr("crash.copy_folder"), "copy", true, false).clicked() {
|
||||
ctx.copy_text(d.clone());
|
||||
}
|
||||
});
|
||||
@@ -97,7 +97,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
egui::Rect::from_min_size(ui.next_widget_position(), egui::vec2(ui.available_width(), 1.0)),
|
||||
);
|
||||
if app.crashes.reports.is_empty() {
|
||||
ui_kit::empty_box(ui, "none", crate::i18n::tr_label("No crash reports."));
|
||||
ui_kit::empty_box(ui, "none", solidcraft_ui_text::tr_label("No crash reports."));
|
||||
}
|
||||
if !app.crashes.reports.is_empty() {
|
||||
ui_kit::report_list(
|
||||
@@ -112,27 +112,31 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
ui.spacing_mut().item_spacing.y = crate::theme::Tokens::get().space_6;
|
||||
ui.add_space(crate::theme::Tokens::get().space_10);
|
||||
ui_kit::report_header(ui, kind_label(&r.kind), r.count, &report_time(r), &title, |ui| {
|
||||
if ui_kit::bin_button(ui, crate::i18n::tr("ui.delete")).clicked() {
|
||||
if ui_kit::bin_button(ui, solidcraft_ui_text::tr("ui.delete")).clicked() {
|
||||
delete = Some(Some(r.id.clone()));
|
||||
}
|
||||
ui_kit::icon_label_button(ui, crate::i18n::tr("crash.copy_path"), "copy", true, false)
|
||||
ui_kit::icon_label_button(ui, solidcraft_ui_text::tr("crash.copy_path"), "copy", true, false)
|
||||
.clicked()
|
||||
.then(|| ctx.copy_text(r.path.clone()));
|
||||
if ui_kit::icon_label_button(ui, crate::i18n::tr("crash.report"), "bug_report", true, false).clicked() {
|
||||
if ui_kit::icon_label_button(ui, solidcraft_ui_text::tr("crash.report"), "bug_report", true, false).clicked() {
|
||||
report = Some(r.id.clone());
|
||||
}
|
||||
});
|
||||
let mut detail = r.command.clone().unwrap_or_default();
|
||||
if r.replayable {
|
||||
let sep = if detail.is_empty() { "" } else { " · " };
|
||||
detail = format!("{detail}{sep}{}", crate::i18n::tr("crash.replayable"));
|
||||
detail = format!("{detail}{sep}{}", solidcraft_ui_text::tr("crash.replayable"));
|
||||
}
|
||||
if !detail.is_empty() {
|
||||
ui_kit::text(ui, &detail, ui_kit::Text::MonoNote(&format!("detail:{}", r.id)));
|
||||
}
|
||||
ui_kit::text(ui, &r.message, ui_kit::Text::Report(&format!("message:{}", r.id), false));
|
||||
if r.kind == "unclean_exit" {
|
||||
ui_kit::text(ui, crate::i18n::tr("crash.unclean_explanation"), ui_kit::Text::Note(&format!("unclean:{}", r.id)));
|
||||
ui_kit::text(
|
||||
ui,
|
||||
solidcraft_ui_text::tr("crash.unclean_explanation"),
|
||||
ui_kit::Text::Note(&format!("unclean:{}", r.id)),
|
||||
);
|
||||
}
|
||||
ui.add_space(crate::theme::Tokens::get().space_6);
|
||||
if index != last {
|
||||
@@ -146,7 +150,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
if !app.crashes.note.is_empty() {
|
||||
ui_kit::text(ui, &app.crashes.note, ui_kit::Text::Note("note"));
|
||||
}
|
||||
if ui_kit::icon_label_button(ui, crate::i18n::tr_label("Delete all"), "delete", !app.crashes.reports.is_empty(), true).clicked() {
|
||||
if ui_kit::icon_label_button(ui, solidcraft_ui_text::tr_label("Delete all"), "delete", !app.crashes.reports.is_empty(), true).clicked() {
|
||||
delete = Some(None);
|
||||
}
|
||||
});
|
||||
@@ -163,7 +167,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
&& let Some(dir) = app.crashes.dir_path()
|
||||
{
|
||||
let n = crash::clear(&dir, which.as_deref());
|
||||
app.crashes.note = crate::i18n::format("crash.deleted", &[("count", &n.to_string())]);
|
||||
app.crashes.note = solidcraft_ui_text::format("crash.deleted", &[("count", &n.to_string())]);
|
||||
app.crashes.refresh();
|
||||
}
|
||||
}
|
||||
@@ -218,7 +222,7 @@ mod tests {
|
||||
assert!(status.contains("detected at startup"), "{status}");
|
||||
assert!(h.widget("Detected 2026-10-12 16:23:41 UTC Closed unexpectedly ×2").is_some());
|
||||
assert!(h.widget(report["message"].as_str().unwrap()).is_some(), "keep the original session start visible");
|
||||
assert!(h.widget(crate::i18n::tr("crash.unclean_explanation")).is_some());
|
||||
assert!(h.widget(solidcraft_ui_text::tr("crash.unclean_explanation")).is_some());
|
||||
let _ = std::fs::remove_dir_all(&d);
|
||||
}
|
||||
|
||||
@@ -236,7 +240,7 @@ mod tests {
|
||||
h.frames(6);
|
||||
assert!(h.app.crashes.open);
|
||||
assert!(h.widget("2026-10-12 16:23:41 UTC Internal error in a command ×2").is_some());
|
||||
assert!(h.widget(crate::i18n::tr("crash.unclean_explanation")).is_none());
|
||||
assert!(h.widget(solidcraft_ui_text::tr("crash.unclean_explanation")).is_none());
|
||||
let _ = std::fs::remove_dir_all(&d);
|
||||
}
|
||||
|
||||
|
||||
@@ -185,14 +185,14 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
};
|
||||
let (deleted, fails) = (list("deleted"), list("would_fail"));
|
||||
let shared = shared_occurrences(app, &p.params);
|
||||
let (closed, action) = ui_kit::Confirm::new(egui::Id::unique("sc_delete_confirm"), "delete", crate::i18n::tr_label("Delete"))
|
||||
let (closed, action) = ui_kit::Confirm::new(egui::Id::unique("sc_delete_confirm"), "delete", solidcraft_ui_text::tr_label("Delete"))
|
||||
.tone(ui_kit::Tone::Danger)
|
||||
.actions("Delete", "Cancel", None)
|
||||
.show(ctx, true, |ui| {
|
||||
if shared {
|
||||
ui_kit::text(
|
||||
ui,
|
||||
crate::i18n::tr_label("This child is shared. Deleting it removes it from every copy of its parent."),
|
||||
solidcraft_ui_text::tr_label("This child is shared. Deleting it removes it from every copy of its parent."),
|
||||
ui_kit::Text::Report("shared", false),
|
||||
);
|
||||
}
|
||||
@@ -202,7 +202,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
if names.is_empty() {
|
||||
continue;
|
||||
}
|
||||
ui_kit::text(ui, crate::i18n::tr_label(heading), ui_kit::Text::Report(key, error));
|
||||
ui_kit::text(ui, solidcraft_ui_text::tr_label(heading), ui_kit::Text::Report(key, error));
|
||||
for name in names.iter().take(MAX_LISTED) {
|
||||
ui_kit::text(ui, &format!(" • {name}"), ui_kit::Text::Report(&format!("{key}:{name}"), error));
|
||||
}
|
||||
@@ -210,7 +210,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let more = (names.len() - MAX_LISTED).to_string();
|
||||
ui_kit::text(
|
||||
ui,
|
||||
&crate::i18n::format("delete.and_more", &[("count", &more)]),
|
||||
&solidcraft_ui_text::format("delete.and_more", &[("count", &more)]),
|
||||
ui_kit::Text::Report(&format!("{key}:more"), error),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -99,10 +99,10 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
d.refused = None;
|
||||
}
|
||||
let metadata = app.session.doc.metadata.clone();
|
||||
let title = crate::i18n::tr_label("Design Properties");
|
||||
let title = solidcraft_ui_text::tr_label("Design Properties");
|
||||
let (closed, action) = ui_kit::Window::new(egui::Id::unique("sc_design_properties"), "settings", title).fields().show(ctx, true, |ui| {
|
||||
for (i, k) in keys(Owner::Design).enumerate() {
|
||||
let (key, label) = (k.name, crate::i18n::tr(k.label));
|
||||
let (key, label) = (k.name, solidcraft_ui_text::tr(k.label));
|
||||
let Some(field) = d.fields.get_mut(i) else { continue };
|
||||
if key == DESCRIPTION {
|
||||
ui_kit::multiline_text_field(ui, label, egui::Id::unique(("sc_design_properties", key)), field);
|
||||
@@ -111,15 +111,15 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
}
|
||||
// Saved by another tool as something other than text: kept unless typed over.
|
||||
if metadata.get(key).is_some() && metadata.text(key).is_none() && d.opened.get(i).is_some_and(String::is_empty) {
|
||||
ui_kit::caption(ui, label, crate::i18n::tr("ui.design_properties.not_text"));
|
||||
ui_kit::caption(ui, label, solidcraft_ui_text::tr("ui.design_properties.not_text"));
|
||||
}
|
||||
}
|
||||
ui_kit::text(ui, crate::i18n::tr("ui.design_properties.exports"), ui_kit::Text::Note("exports"));
|
||||
ui_kit::text(ui, solidcraft_ui_text::tr("ui.design_properties.exports"), ui_kit::Text::Note("exports"));
|
||||
let others: Vec<(&String, &Value)> = metadata.others().collect();
|
||||
if !others.is_empty() {
|
||||
let heading = format!("{} ({})", crate::i18n::tr("ui.design_properties.others"), others.len());
|
||||
let heading = format!("{} ({})", solidcraft_ui_text::tr("ui.design_properties.others"), others.len());
|
||||
ui_kit::section(ui, "sc_design_properties_others", &heading, false, |ui| {
|
||||
ui_kit::text(ui, crate::i18n::tr("ui.design_properties.others_note"), ui_kit::Text::Note("others"));
|
||||
ui_kit::text(ui, solidcraft_ui_text::tr("ui.design_properties.others_note"), ui_kit::Text::Note("others"));
|
||||
egui::ScrollArea::vertical().max_height(ui_kit::row_height() * OTHERS_ROWS).show(ui, |ui| {
|
||||
for (key, value) in others {
|
||||
ui_kit::row(ui, key, |ui| ui_kit::text(ui, &preview(value), ui_kit::Text::Cell));
|
||||
@@ -263,7 +263,9 @@ fn show_measuring(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let bodies = m.bodies();
|
||||
let heading = app.tr("ui.properties").to_string();
|
||||
let text = match bodies {
|
||||
Some((done, total)) => crate::i18n::format("ui.properties.measuring_bodies", &[("done", &done.to_string()), ("total", &total.to_string())]),
|
||||
Some((done, total)) => {
|
||||
solidcraft_ui_text::format("ui.properties.measuring_bodies", &[("done", &done.to_string()), ("total", &total.to_string())])
|
||||
}
|
||||
None => app.tr("ui.properties.measuring").to_string(),
|
||||
};
|
||||
let (closed, action) = ui_kit::Window::new(egui::Id::unique("sc_props"), "settings", &heading).actions("", "Close", None).show(ctx, true, |ui| {
|
||||
@@ -344,7 +346,7 @@ fn inertia_rows(app: &SolveApp, total: &Value) -> Vec<(String, Vec<String>)> {
|
||||
}
|
||||
for (n, axis) in total["principal_axes"].as_array().into_iter().flatten().enumerate() {
|
||||
let Some(e) = point(axis) else { continue };
|
||||
let label = crate::i18n::format("ui.properties.principal_axis", &[("number", &(n + 1).to_string())]);
|
||||
let label = solidcraft_ui_text::format("ui.properties.principal_axis", &[("number", &(n + 1).to_string())]);
|
||||
rows.push((label, [e.x, e.y, e.z].map(|x| format!("{:.4}", if x.abs() < 5e-5 { 0.0 } else { x })).to_vec()));
|
||||
}
|
||||
rows
|
||||
@@ -354,9 +356,9 @@ fn inertia_rows(app: &SolveApp, total: &Value) -> Vec<(String, Vec<String>)> {
|
||||
/// file's), else with the name of the component, placement or bodies it measured.
|
||||
fn window_title(title: &str, v: &Value) -> String {
|
||||
if v["component"].as_u64() == Some(0) {
|
||||
crate::i18n::tr("ui.properties").to_string()
|
||||
solidcraft_ui_text::tr("ui.properties").to_string()
|
||||
} else {
|
||||
crate::i18n::format("properties.title", &[("name", title)])
|
||||
solidcraft_ui_text::format("properties.title", &[("name", title)])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,7 +408,7 @@ pub fn show_object(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
rows.push(("Density", format!("{:.3} g/cm³", number(b, "density_g_cm3"))));
|
||||
}
|
||||
if !bodies.is_empty() && skipped == bodies.len() as u64 {
|
||||
rows.push(("", crate::i18n::tr_label("Open surface — no volume").to_string()));
|
||||
rows.push(("", solidcraft_ui_text::tr_label("Open surface — no volume").to_string()));
|
||||
} else {
|
||||
rows.push(("Mass", format!("{:.3} g", number(total, "mass_g"))));
|
||||
rows.push(("Volume", crate::prefs::show_mm(app, number(total, "volume_mm3"), 3)));
|
||||
@@ -436,7 +438,7 @@ pub fn show_object(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
format!("({}) g·{unit}", m.join(", "))
|
||||
});
|
||||
let inertia = inertia_rows(app, total);
|
||||
let note = crate::i18n::format("ui.properties.inertia_note", &[("unit", unit)]);
|
||||
let note = solidcraft_ui_text::format("ui.properties.inertia_note", &[("unit", unit)]);
|
||||
// A component's closed bodies have mass, but a mesh that would not close gave no tensor.
|
||||
let unavailable =
|
||||
inertia.is_empty() && total["inertia_g_mm2"].is_null() && number(total, "mass_g") > 0.0 && total.get("first_moment_g_mm").is_some();
|
||||
@@ -450,7 +452,7 @@ pub fn show_object(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let heading = window_title(&title, &v);
|
||||
let (closed, action) = ui_kit::Window::new(egui::Id::unique("sc_props"), "settings", &heading).actions("", "Close", None).show(ctx, true, |ui| {
|
||||
for (label, value) in &rows {
|
||||
ui_kit::row(ui, crate::i18n::tr_label(label), |ui| ui_kit::text(ui, value, ui_kit::Text::Cell));
|
||||
ui_kit::row(ui, solidcraft_ui_text::tr_label(label), |ui| ui_kit::text(ui, value, ui_kit::Text::Cell));
|
||||
}
|
||||
if let Some(first) = &first {
|
||||
ui_kit::row(ui, &first_label, |ui| ui_kit::text(ui, first, ui_kit::Text::Cell));
|
||||
@@ -591,7 +593,7 @@ mod tests {
|
||||
#[test]
|
||||
fn each_field_is_labelled_from_the_table_of_keys() {
|
||||
use solidcraft_engine::doc::metadata::{KEYS, Owner, keys};
|
||||
let english = crate::i18n::Locale::for_language("en");
|
||||
let english = solidcraft_ui_text::Locale::for_language("en");
|
||||
for k in KEYS {
|
||||
assert_ne!(english.text(k.label), k.label, "`{}`'s label is in the English catalog", k.name);
|
||||
}
|
||||
|
||||
@@ -2558,7 +2558,7 @@ pub(crate) fn combo(ui: &mut egui::Ui, id: &str, labels: &[&str], sel: &mut usiz
|
||||
}
|
||||
|
||||
fn tr(text: &str) -> &str {
|
||||
crate::i18n::tr_label(text)
|
||||
solidcraft_ui_text::tr_label(text)
|
||||
}
|
||||
|
||||
fn publish_check(ui: &egui::Ui, response: &egui::Response, selected: bool) {
|
||||
@@ -2755,7 +2755,8 @@ fn object_type_row(d: &mut Dialog, ui: &mut egui::Ui) {
|
||||
let cur = usize::from(inp.accept == selection::FEATURES);
|
||||
let mut ty = cur;
|
||||
// What the two kinds are, and that a sketch is neither (#10057).
|
||||
choice_row(ui, "Object Type", "pat_obj", &mut ty, &["Bodies", "Features"]).backing_on_hover_text(crate::i18n::tr("ui.pattern_object_type_tip"));
|
||||
choice_row(ui, "Object Type", "pat_obj", &mut ty, &["Bodies", "Features"])
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr("ui.pattern_object_type_tip"));
|
||||
if ty != cur {
|
||||
inp.accept = if ty == 1 { selection::FEATURES } else { BODIES };
|
||||
inp.label = objects_label(inp.accept);
|
||||
@@ -2927,25 +2928,27 @@ fn occurrence_paths(s: &Session, items: &[Sel]) -> Vec<Vec<u64>> {
|
||||
/// the name the preferences give it.
|
||||
pub(crate) fn selection_label(s: &Session, names: crate::prefs::OriginPlaneNames, d: &Dialog, inp: &SelInput, item: &Sel) -> String {
|
||||
let axis = inp.accept == selection::AXIS_PICKS && round_axis(s, item).is_some();
|
||||
let sketch_name =
|
||||
|id| s.doc.feature(id).map(|f| f.name.clone()).unwrap_or_else(|| crate::i18n::format("browser.sketch_name", &[("number", &id.to_string())]));
|
||||
let sketch_name = |id| {
|
||||
s.doc.feature(id).map(|f| f.name.clone()).unwrap_or_else(|| solidcraft_ui_text::format("browser.sketch_name", &[("number", &id.to_string())]))
|
||||
};
|
||||
match item {
|
||||
Sel::SketchCurve { id, .. } => id.clone(),
|
||||
Sel::SketchPoint { id } => crate::sketch_modify_ui::point_id(s, item).unwrap_or(id).to_string(),
|
||||
Sel::SketchMidpoint { id } => crate::i18n::format("dialog.selection_midpoint", &[("id", id)]),
|
||||
Sel::SketchMidpoint { id } => solidcraft_ui_text::format("dialog.selection_midpoint", &[("id", id)]),
|
||||
Sel::ExternalSketchCurve { sketch, id } => {
|
||||
crate::i18n::format("dialog.selection_external_curve", &[("sketch", &sketch_name(*sketch)), ("id", id)])
|
||||
solidcraft_ui_text::format("dialog.selection_external_curve", &[("sketch", &sketch_name(*sketch)), ("id", id)])
|
||||
}
|
||||
// Another sketch's point (a Sphere's centre, #9403), named as Select Other names it.
|
||||
Sel::ExternalSketchPoint { sketch, id } => {
|
||||
let state = crate::open_meshing::session_world(s);
|
||||
let point = state.sketch(*sketch).map_or_else(|| id.clone(), |ss| crate::select_other::point_label(&ss.sketch, id));
|
||||
crate::i18n::format("dialog.selection_external_point", &[("point", &point), ("sketch", &sketch_name(*sketch))])
|
||||
}
|
||||
Sel::SketchConstraint { id } => crate::i18n::format("dialog.selection_constraint", &[("id", id)]),
|
||||
Sel::Profile { sketch, index } => {
|
||||
crate::i18n::format("dialog.selection_profile", &[("sketch", &sketch_name(*sketch)), ("number", &index.saturating_add(1).to_string())])
|
||||
solidcraft_ui_text::format("dialog.selection_external_point", &[("point", &point), ("sketch", &sketch_name(*sketch))])
|
||||
}
|
||||
Sel::SketchConstraint { id } => solidcraft_ui_text::format("dialog.selection_constraint", &[("id", id)]),
|
||||
Sel::Profile { sketch, index } => solidcraft_ui_text::format(
|
||||
"dialog.selection_profile",
|
||||
&[("sketch", &sketch_name(*sketch)), ("number", &index.saturating_add(1).to_string())],
|
||||
),
|
||||
Sel::Plane { name } => names.label(name).to_string(),
|
||||
Sel::Axis { name } | Sel::ConstructionPoint { name } | Sel::Body { name } => name.clone(),
|
||||
Sel::Face { body, index, .. } if axis => format!("{body} · face {index} axis"),
|
||||
@@ -3045,18 +3048,18 @@ pub(crate) fn input_rows_range(d: &mut Dialog, ui: &mut egui::Ui, s: &Session, n
|
||||
let filled = !inp.items.is_empty() || custom_axis || stored_reference.is_some();
|
||||
let text = if filled {
|
||||
RichText::new(if stored_reference.is_some() && matches!(d.kind, Kind::Section { .. }) {
|
||||
crate::i18n::tr("ui.viewport_status_section_plane").into()
|
||||
solidcraft_ui_text::tr("ui.viewport_status_section_plane").into()
|
||||
} else if custom_axis || stored_reference.is_some() {
|
||||
crate::i18n::tr("dialog.custom_axis").into()
|
||||
solidcraft_ui_text::tr("dialog.custom_axis").into()
|
||||
} else if !inp.multi {
|
||||
inp.items.first().map(|item| selection_label(s, names, d, inp, item)).unwrap_or_default()
|
||||
} else {
|
||||
let count = if i == 0 { shell_count.unwrap_or(inp.items.len()) } else { inp.items.len() };
|
||||
crate::i18n::format("dialog.selected_count", &[("count", &count.to_string())])
|
||||
solidcraft_ui_text::format("dialog.selected_count", &[("count", &count.to_string())])
|
||||
})
|
||||
.color(t.on_accent)
|
||||
} else {
|
||||
RichText::new(crate::i18n::tr("dialog.select")).color(t.text)
|
||||
RichText::new(solidcraft_ui_text::tr("dialog.select")).color(t.text)
|
||||
};
|
||||
let selected_text = text.text().strip_prefix("↖ ").unwrap_or_else(|| text.text());
|
||||
let prompt = if !filled && inp.label == "Edges" { tr("Select edges") } else { selected_text };
|
||||
@@ -3086,7 +3089,7 @@ pub(crate) fn input_rows_range(d: &mut Dialog, ui: &mut egui::Ui, s: &Session, n
|
||||
hint(inp).to_string()
|
||||
}
|
||||
} else if let Some(id) = inp.tip {
|
||||
crate::i18n::tr(id).to_string()
|
||||
solidcraft_ui_text::tr(id).to_string()
|
||||
} else {
|
||||
format!("{} (click to pick into this input)", hint(inp))
|
||||
};
|
||||
@@ -3111,7 +3114,7 @@ pub(crate) fn input_rows_range(d: &mut Dialog, ui: &mut egui::Ui, s: &Session, n
|
||||
if inp.multi {
|
||||
let open = d.listed == Some(i);
|
||||
let at = rect.translate(vec2(-t.space_24, 0.0));
|
||||
let label = crate::i18n::tr_label(if open { "Hide the picked items" } else { "Show the picked items" });
|
||||
let label = solidcraft_ui_text::tr_label(if open { "Hide the picked items" } else { "Show the picked items" });
|
||||
let arrow = crate::ui_kit::disclosure_button_at(ui, at, open, label);
|
||||
crate::scenario::publish_handle(&format!("dialog:list_toggle:{}", inp.label), arrow.rect.center());
|
||||
if arrow.clicked() {
|
||||
@@ -3243,7 +3246,7 @@ fn error_rows(app: &mut SolveApp, ctx: &egui::Context, d: &mut Dialog, ui: &mut
|
||||
// Under the fold in a short window, a new note scrolls into view once, as a refusal does.
|
||||
let note_shown_id = egui::Id::unique("sc_dialog_pick_refused_shown");
|
||||
if let Some(id) = d.pick_refused.filter(|_| d.error.is_none() && app.preview.error.is_none()) {
|
||||
let note = crate::ui_kit::text(ui, crate::i18n::tr(id), crate::ui_kit::Text::Hint("pick_refused"));
|
||||
let note = crate::ui_kit::text(ui, solidcraft_ui_text::tr(id), crate::ui_kit::Text::Hint("pick_refused"));
|
||||
crate::scenario::publish_rect("dialog:pick_refused", note.rect);
|
||||
if ui.data(|m| m.get_temp::<&'static str>(note_shown_id)) != Some(id) {
|
||||
ui.scroll_to_rect(note.rect, None);
|
||||
@@ -3275,13 +3278,13 @@ fn error_rows(app: &mut SolveApp, ctx: &egui::Context, d: &mut Dialog, ui: &mut
|
||||
ui.horizontal(|ui| {
|
||||
let ready = solidcraft_engine::find_command(crate::refused_part::REPAIR).map(|c| c.info(&app.session));
|
||||
let enabled = ready.as_ref().is_some_and(|i| i.enabled);
|
||||
let repair = crate::ui_kit::button(ui, crate::i18n::tr("refused.repair"), crate::ui_kit::Button::Enabled(enabled));
|
||||
let repair = crate::ui_kit::button(ui, solidcraft_ui_text::tr("refused.repair"), crate::ui_kit::Button::Enabled(enabled));
|
||||
crate::scenario::publish_handle("dialog:repair_part", repair.rect.center());
|
||||
if repair.clicked() {
|
||||
crate::refused_part::repair(app, &part.body);
|
||||
}
|
||||
if part.face.is_some() {
|
||||
let show = crate::ui_kit::button(ui, crate::i18n::tr("refused.show_face"), crate::ui_kit::Button::Normal);
|
||||
let show = crate::ui_kit::button(ui, solidcraft_ui_text::tr("refused.show_face"), crate::ui_kit::Button::Normal);
|
||||
crate::scenario::publish_handle("dialog:show_face", show.rect.center());
|
||||
if show.clicked() {
|
||||
crate::refused_part::show_face(app, &part);
|
||||
@@ -3371,7 +3374,7 @@ pub(crate) fn fillet_plain_error(error: &str, setback_in_field: bool) -> Option<
|
||||
if let Some((_, rest)) = error.split_once(solidcraft_engine::kernel::SETBACK_AT_LEAST)
|
||||
&& let Some(least) = rest.split_once(" mm").map(|(n, _)| n.trim()).filter(|n| n.parse::<f64>().is_ok())
|
||||
{
|
||||
return Some(FilletRefusal::Plain(crate::i18n::format("ui.fillet_error_setback_short", &[("least", &format!("{least} mm"))])));
|
||||
return Some(FilletRefusal::Plain(solidcraft_ui_text::format("ui.fillet_error_setback_short", &[("least", &format!("{least} mm"))])));
|
||||
}
|
||||
let too_large = error.contains("larger than the faces beside its edges allow");
|
||||
let corner = error.contains("setback") || error.contains("G2 corner") || error.contains("corner patch");
|
||||
@@ -3409,7 +3412,7 @@ fn reveal_refusal(on_ok: bool, typing: bool, shown: Option<&str>, error: &str) -
|
||||
/// message is in the catalogs.
|
||||
fn shown_error(error: &str) -> String {
|
||||
let msg = error.strip_prefix("invalid parameters for `").and_then(|rest| rest.split_once("`: ")).map_or(error, |(_, msg)| msg);
|
||||
crate::i18n::tr_diagnostic(crate::i18n::tr_label(msg))
|
||||
crate::i18n::tr_diagnostic(solidcraft_ui_text::tr_label(msg))
|
||||
}
|
||||
|
||||
/// Find the Boundary Fill cells again when the tool bodies changed (keeping the choices of
|
||||
@@ -3776,7 +3779,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
return;
|
||||
}
|
||||
crate::sketch_text::sync_path(&mut d);
|
||||
let _language = crate::i18n::scope(&app.preferences.language);
|
||||
let _language = solidcraft_ui_text::scope(&app.preferences.language);
|
||||
if let Kind::TextMove(k) = &d.kind
|
||||
&& !crate::sketch_text_move::valid(app, k)
|
||||
{
|
||||
@@ -3844,7 +3847,8 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let wide = matches!(&d.kind, Kind::Motion(k) if k.wide());
|
||||
|
||||
let raw_title = title_of(&d);
|
||||
let heading = if d.editing.is_some() { crate::i18n::format("dialog.edit_title", &[("title", tr(raw_title))]) } else { tr(raw_title).to_string() };
|
||||
let heading =
|
||||
if d.editing.is_some() { solidcraft_ui_text::format("dialog.edit_title", &[("title", tr(raw_title))]) } else { tr(raw_title).to_string() };
|
||||
let frame = crate::ui_kit::Panel::feature_frame(ctx, app.ui.layout, side);
|
||||
// OK is offered once the inputs make a feature.
|
||||
let has_ok = !matches!(d.kind, Kind::Sketch | Kind::Measure { .. });
|
||||
@@ -3949,7 +3953,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
}
|
||||
let text = app.applying.as_ref().map(|job| {
|
||||
let seconds = ((crate::now_ms() - job.started) / 1000.0).floor().max(0.0);
|
||||
crate::i18n::format("dialog.computing", &[("seconds", &format!("{seconds:.0}"))])
|
||||
solidcraft_ui_text::format("dialog.computing", &[("seconds", &format!("{seconds:.0}"))])
|
||||
});
|
||||
crate::ui_kit::ComputeStatus { text, busy: app.applying.is_some() || app.preview.busy, applying: app.applying.is_some() }
|
||||
};
|
||||
@@ -5765,10 +5769,10 @@ fn dialog_commands(app: &SolveApp, d: &Dialog) -> Result<Vec<(String, Value)>, S
|
||||
}
|
||||
Kind::Material { index, editor } => {
|
||||
if editor.editing() {
|
||||
return Err(crate::i18n::tr("material.editor.save_or_discard").to_string());
|
||||
return Err(solidcraft_ui_text::tr("material.editor.save_or_discard").to_string());
|
||||
}
|
||||
need(0, "bodies")?;
|
||||
let name = s.doc.material_names().get(*index).cloned().ok_or_else(|| crate::i18n::tr("material.selection.choose").to_string())?;
|
||||
let name = s.doc.material_names().get(*index).cloned().ok_or_else(|| solidcraft_ui_text::tr("material.selection.choose").to_string())?;
|
||||
("material.assign", json!({"bodies": sels(d, 0).body_names(), "material": name}))
|
||||
}
|
||||
Kind::Section { offset, flip, hatch } => {
|
||||
@@ -8223,7 +8227,7 @@ mod awaiting_selection_tests {
|
||||
h.step(&serde_json::json!({"key":"Enter"})).unwrap();
|
||||
h.frames(2);
|
||||
assert_eq!(h.app.dialog.as_ref().and_then(|d| d.error.as_deref()), Some(english), "{command}");
|
||||
let _japanese = crate::i18n::scope("ja");
|
||||
let _japanese = solidcraft_ui_text::scope("ja");
|
||||
let shown = super::shown_error(english);
|
||||
assert!(shown != english && !shown.is_ascii(), "{command} shows {shown:?}");
|
||||
}
|
||||
|
||||
@@ -453,7 +453,7 @@ fn motion_dialog_hints_are_translated() {
|
||||
}
|
||||
let mut h = fixture();
|
||||
h.app.preferences.language = "ja".into();
|
||||
let _japanese = crate::i18n::scope("ja");
|
||||
let _japanese = solidcraft_ui_text::scope("ja");
|
||||
for (command, english) in [("motion.study", "add a key for a joint first"), ("motion.trace", "select a point on a component")] {
|
||||
let mut d = Dialog::for_command(&h.app, command).unwrap();
|
||||
let mut painted = Vec::new();
|
||||
@@ -472,7 +472,7 @@ fn motion_dialog_hints_are_translated() {
|
||||
out.shapes.iter().for_each(|shape| texts(&shape.shape, &mut painted));
|
||||
}
|
||||
assert!(!painted.iter().any(|p| p == english), "{command} paints {english:?} in Japanese");
|
||||
assert!(painted.iter().any(|p| p == crate::i18n::tr_label(english)), "{command} paints {painted:?}");
|
||||
assert!(painted.iter().any(|p| p == solidcraft_ui_text::tr_label(english)), "{command} paints {painted:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -746,7 +746,7 @@ fn hand_drawn_dialog_texts_are_translated() {
|
||||
}
|
||||
let mut h = fixture();
|
||||
h.app.preferences.language = "ja".into();
|
||||
let _japanese = crate::i18n::scope("ja");
|
||||
let _japanese = solidcraft_ui_text::scope("ja");
|
||||
for (command, english) in [
|
||||
("joint.gear", "▶ Play"),
|
||||
("motion.study", "Loop"),
|
||||
@@ -790,7 +790,7 @@ fn revolve_layout_fixture_builds_a_valid_solid() {
|
||||
|
||||
#[test]
|
||||
fn new_cards_settle_before_the_first_click_in_both_layouts() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
for command in ["solid.box", "solid.revolve", "solid.shell", "joint.create"] {
|
||||
let mut h = fixture();
|
||||
h.call("ui.set", json!({"layout":mode}));
|
||||
@@ -1401,13 +1401,13 @@ fn extrude_builds_the_one_region_clicked_where_the_face_outline_cuts_a_profile()
|
||||
#[test]
|
||||
fn every_selection_field_label_is_translated() {
|
||||
let h = fixture();
|
||||
let _japanese = crate::i18n::scope("ja");
|
||||
let _japanese = solidcraft_ui_text::scope("ja");
|
||||
let mut missing = std::collections::BTreeSet::new();
|
||||
for spec in solidcraft_engine::command_specs() {
|
||||
let Some(d) = Dialog::for_command(&h.app, spec.id) else { continue };
|
||||
for input in &d.inputs {
|
||||
let label = input.label.trim();
|
||||
if label.chars().any(|c| c.is_ascii_alphabetic()) && crate::i18n::tr_label(label) == label {
|
||||
if label.chars().any(|c| c.is_ascii_alphabetic()) && solidcraft_ui_text::tr_label(label) == label {
|
||||
missing.insert(format!("{}: {label}", spec.id));
|
||||
}
|
||||
}
|
||||
@@ -1570,9 +1570,9 @@ fn pattern_says_why_a_clicked_sketch_is_not_taken() {
|
||||
}
|
||||
}
|
||||
assert!(crate::dialogs::missing_pick(crate::dialogs::SKETCH_NOT_OBJECT), "shown as what to pick, with no Create Issue");
|
||||
let _japanese = crate::i18n::scope("ja");
|
||||
assert!(!crate::i18n::tr_label(crate::dialogs::SKETCH_NOT_OBJECT).is_ascii(), "the hint reads in the user's language");
|
||||
assert!(!crate::i18n::tr("ui.pattern_object_type_tip").is_ascii(), "so does the Object Type tooltip");
|
||||
let _japanese = solidcraft_ui_text::scope("ja");
|
||||
assert!(!solidcraft_ui_text::tr_label(crate::dialogs::SKETCH_NOT_OBJECT).is_ascii(), "the hint reads in the user's language");
|
||||
assert!(!solidcraft_ui_text::tr("ui.pattern_object_type_tip").is_ascii(), "so does the Object Type tooltip");
|
||||
}
|
||||
|
||||
/// Features stored with bare numbers edit with their units written out, as the Plastic ones do
|
||||
@@ -1682,7 +1682,7 @@ fn dialog_sketch_selection_labels_translate_and_keep_identifiers() {
|
||||
(Sel::SketchConstraint { id: "k3".into() }, "Constraint k3", "Bedingung k3"),
|
||||
] {
|
||||
for (language, expected) in [("en", english), ("de", german)] {
|
||||
let _language = crate::i18n::scope(language);
|
||||
let _language = solidcraft_ui_text::scope(language);
|
||||
assert_eq!(selection_label(&h.app.session, h.app.preferences.origin_plane_names, &d, &d.inputs[0], &sel), expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,7 +385,9 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Ap, inputs: &mut [Sel
|
||||
// Apply To: bodies or faces (the input follows).
|
||||
if let Some(component) = &k.component {
|
||||
ui_kit::row(ui, "Apply To", |ui| ui_kit::text(ui, &format!("Component: {component}"), ui_kit::Text::Cell));
|
||||
if ui_kit::row(ui, "", |ui| ui_kit::button(ui, crate::i18n::tr_label("Select bodies or faces instead"), ui_kit::Button::Normal)).clicked() {
|
||||
if ui_kit::row(ui, "", |ui| ui_kit::button(ui, solidcraft_ui_text::tr_label("Select bodies or faces instead"), ui_kit::Button::Normal))
|
||||
.clicked()
|
||||
{
|
||||
k.component = None;
|
||||
}
|
||||
} else {
|
||||
@@ -462,7 +464,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Ap, inputs: &mut [Sel
|
||||
});
|
||||
}
|
||||
if ui_kit::button(ui, "Remove override", ui_kit::Button::Enabled(has_target))
|
||||
.backing_on_hover_text(crate::i18n::tr_label("Restore the inherited component or physical material appearance"))
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr_label("Restore the inherited component or physical material appearance"))
|
||||
.clicked()
|
||||
&& let Some(p) = clear_params(k, &picked)
|
||||
{
|
||||
@@ -523,7 +525,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Ap, inputs: &mut [Sel
|
||||
.filter(|(_, l)| matches_library_filter(l, (k.category != 0).then_some(want.as_str()), &query, k.only_available))
|
||||
.collect();
|
||||
if shown.is_empty() {
|
||||
ui_kit::text(ui, crate::i18n::tr_label("No matching appearances"), ui_kit::Text::Note("appearances_empty"));
|
||||
ui_kit::text(ui, solidcraft_ui_text::tr_label("No matching appearances"), ui_kit::Text::Note("appearances_empty"));
|
||||
}
|
||||
ui_kit::swatch_grid(ui, &shown, |ui, (i, l)| {
|
||||
let picked_look = k.look.as_deref() == Some(l.name.as_str()) && k.pbr == l.pbr;
|
||||
|
||||
@@ -73,7 +73,7 @@ pub(crate) fn kind_label(kind: JointKind) -> &'static str {
|
||||
|
||||
/// A joint as the joint lists name it: its name, and its type in the user's language.
|
||||
pub(crate) fn joint_choice(name: &str, kind: JointKind) -> String {
|
||||
format!("{name} ({})", crate::i18n::tr_label(kind_label(kind)))
|
||||
format!("{name} ({})", solidcraft_ui_text::tr_label(kind_label(kind)))
|
||||
}
|
||||
|
||||
/// What a joint origin input accepts: snaps on faces, edges and vertices, the world origin, and
|
||||
@@ -819,7 +819,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Asm, inputs: &mut [Se
|
||||
}
|
||||
}
|
||||
Asm::NewComponent { name, activate } => {
|
||||
enter |= text_row(ui, "Name", name, crate::i18n::tr_label("Component1")).0;
|
||||
enter |= text_row(ui, "Name", name, solidcraft_ui_text::tr_label("Component1")).0;
|
||||
crate::ui_kit::options(ui, "component_options", &mut [("Activate", activate)]);
|
||||
}
|
||||
Asm::Joint(f) => {
|
||||
@@ -832,8 +832,8 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Asm, inputs: &mut [Se
|
||||
ui.vertical(|ui| {
|
||||
crate::ui_kit::status_line(ui, &why, crate::ui_kit::StatusTone::Warning);
|
||||
if !bodies.is_empty()
|
||||
&& crate::ui_kit::button(ui, crate::i18n::tr("cmd.component_from_bodies"), crate::ui_kit::Button::Normal)
|
||||
.backing_on_hover_text(crate::i18n::tr("joint.from_bodies_hint"))
|
||||
&& crate::ui_kit::button(ui, solidcraft_ui_text::tr("cmd.component_from_bodies"), crate::ui_kit::Button::Normal)
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr("joint.from_bodies_hint"))
|
||||
.clicked()
|
||||
{
|
||||
// A refusal shows on the status line; the picks stay either way.
|
||||
@@ -869,9 +869,9 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Asm, inputs: &mut [Se
|
||||
let kind = f.joint_kind();
|
||||
if kind != JointKind::Rigid {
|
||||
form_row(ui, "Animate", |ui| {
|
||||
let label = crate::i18n::tr(if f.animate { "ui.joint_stop" } else { "ui.joint_play" });
|
||||
let label = solidcraft_ui_text::tr(if f.animate { "ui.joint_stop" } else { "ui.joint_play" });
|
||||
if crate::ui_kit::button(ui, label, crate::ui_kit::Button::Normal)
|
||||
.backing_on_hover_text(crate::i18n::tr_label("Play the joint's motion in the view"))
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr_label("Play the joint's motion in the view"))
|
||||
.clicked()
|
||||
{
|
||||
f.animate = !f.animate;
|
||||
@@ -915,7 +915,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Asm, inputs: &mut [Se
|
||||
}
|
||||
}
|
||||
Asm::JointOrigin { name, .. } => {
|
||||
enter |= text_row(ui, "Name", name, crate::i18n::tr_label("Joint Origin1")).0;
|
||||
enter |= text_row(ui, "Name", name, solidcraft_ui_text::tr_label("Joint Origin1")).0;
|
||||
}
|
||||
Asm::RigidGroup => {}
|
||||
Asm::Drive { joint, values, focus } => {
|
||||
@@ -927,9 +927,9 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Asm, inputs: &mut [Se
|
||||
if let Some((id, name)) = &rigid {
|
||||
let mut edit = false;
|
||||
ui.vertical(|ui| {
|
||||
let why = crate::i18n::format("drive.rigid", &[("joint", name.as_str())]);
|
||||
let why = solidcraft_ui_text::format("drive.rigid", &[("joint", name.as_str())]);
|
||||
crate::ui_kit::status_line(ui, &why, crate::ui_kit::StatusTone::Warning);
|
||||
let r = crate::ui_kit::button(ui, crate::i18n::tr_label("Edit Joint"), crate::ui_kit::Button::Normal);
|
||||
let r = crate::ui_kit::button(ui, solidcraft_ui_text::tr_label("Edit Joint"), crate::ui_kit::Button::Normal);
|
||||
crate::scenario::publish_handle("drive:edit_joint", r.rect.center());
|
||||
edit = r.clicked();
|
||||
});
|
||||
@@ -941,7 +941,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Asm, inputs: &mut [Se
|
||||
if list.is_empty() {
|
||||
if rigid.is_none() {
|
||||
ui.vertical(|ui| {
|
||||
ui.label(RichText::new(crate::i18n::tr_label("no joint that moves yet")).color(t.text_dim));
|
||||
ui.label(RichText::new(solidcraft_ui_text::tr_label("no joint that moves yet")).color(t.text_dim));
|
||||
});
|
||||
}
|
||||
return false;
|
||||
@@ -1085,13 +1085,15 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Asm, inputs: &mut [Se
|
||||
ui.horizontal(|ui| {
|
||||
let seconds = ((crate::now_ms() - started) / 1000.0).floor().max(0.0);
|
||||
ui.add(egui::Spinner::new().size(t.font_small));
|
||||
ui.label(RichText::new(crate::i18n::format("dialog.computing", &[("seconds", &format!("{seconds:.0}"))])).color(t.text_dim));
|
||||
ui.label(
|
||||
RichText::new(solidcraft_ui_text::format("dialog.computing", &[("seconds", &format!("{seconds:.0}"))])).color(t.text_dim),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
form_row(ui, "", |ui| {
|
||||
if check.is_some() {
|
||||
if crate::ui_kit::button(ui, crate::i18n::tr("ui.cancel"), crate::ui_kit::Button::Normal).clicked() {
|
||||
if crate::ui_kit::button(ui, solidcraft_ui_text::tr("ui.cancel"), crate::ui_kit::Button::Normal).clicked() {
|
||||
// Its thread finishes the check; the reply is left unread.
|
||||
*check = None;
|
||||
}
|
||||
@@ -1100,7 +1102,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Asm, inputs: &mut [Se
|
||||
let stale = result.is_some() && *of != picked;
|
||||
if crate::ui_kit::button(
|
||||
ui,
|
||||
crate::i18n::tr(if stale { "dialog.compute_again" } else { "dialog.compute" }),
|
||||
solidcraft_ui_text::tr(if stale { "dialog.compute_again" } else { "dialog.compute" }),
|
||||
crate::ui_kit::Button::Normal,
|
||||
)
|
||||
.clicked()
|
||||
@@ -1132,7 +1134,8 @@ fn find_limits_row(app: &mut SolveApp, ui: &mut egui::Ui, f: &mut JointForm, id:
|
||||
let t = Tokens::get();
|
||||
if let Some(search) = &f.search {
|
||||
// A poisoned lock: the search's thread panicked.
|
||||
let reply = search.reply.lock().map(|mut r| r.take()).unwrap_or_else(|_| Some(Err(crate::i18n::tr("joint.find_limits_failed").to_string())));
|
||||
let reply =
|
||||
search.reply.lock().map(|mut r| r.take()).unwrap_or_else(|_| Some(Err(solidcraft_ui_text::tr("joint.find_limits_failed").to_string())));
|
||||
match reply {
|
||||
Some(reply) => {
|
||||
// Found for a design changed since (by a script, the control channel, undo), or
|
||||
@@ -1143,7 +1146,7 @@ fn find_limits_row(app: &mut SolveApp, ui: &mut egui::Ui, f: &mut JointForm, id:
|
||||
f.searched = Some(searched);
|
||||
fill_limits(&app.session.doc, f, reply);
|
||||
} else {
|
||||
f.found_note = Some(crate::i18n::tr("joint.find_limits_stale").to_string());
|
||||
f.found_note = Some(solidcraft_ui_text::tr("joint.find_limits_stale").to_string());
|
||||
}
|
||||
// The rows above were drawn before this: once more, with what was found.
|
||||
ui.ctx().request_repaint();
|
||||
@@ -1159,20 +1162,20 @@ fn find_limits_row(app: &mut SolveApp, ui: &mut egui::Ui, f: &mut JointForm, id:
|
||||
{
|
||||
f.searched = None;
|
||||
f.found.clear();
|
||||
f.found_note = Some(crate::i18n::tr("joint.find_limits_stale").to_string());
|
||||
f.found_note = Some(solidcraft_ui_text::tr("joint.find_limits_stale").to_string());
|
||||
}
|
||||
form_row(ui, "", |ui| {
|
||||
if f.search.is_some() {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(egui::Spinner::new().size(t.font_small));
|
||||
ui.label(RichText::new(crate::i18n::tr("joint.finding_limits")).color(t.text_dim));
|
||||
if crate::ui_kit::button(ui, crate::i18n::tr("ui.cancel"), crate::ui_kit::Button::Normal).clicked() {
|
||||
ui.label(RichText::new(solidcraft_ui_text::tr("joint.finding_limits")).color(t.text_dim));
|
||||
if crate::ui_kit::button(ui, solidcraft_ui_text::tr("ui.cancel"), crate::ui_kit::Button::Normal).clicked() {
|
||||
// Its thread finishes within its work budget; the reply is left unread.
|
||||
f.search = None;
|
||||
}
|
||||
});
|
||||
} else if crate::ui_kit::button(ui, crate::i18n::tr("joint.find_limits"), crate::ui_kit::Button::Normal)
|
||||
.backing_on_hover_text(crate::i18n::tr("joint.find_limits_hint"))
|
||||
} else if crate::ui_kit::button(ui, solidcraft_ui_text::tr("joint.find_limits"), crate::ui_kit::Button::Normal)
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr("joint.find_limits_hint"))
|
||||
.clicked()
|
||||
{
|
||||
find_limits(app, f, id, inputs);
|
||||
@@ -1274,20 +1277,20 @@ fn fill_limits(doc: &solidcraft_engine::doc::Document, f: &mut JointForm, reply:
|
||||
let (min, max) = (lo.map(shown), hi.map(shown));
|
||||
let mut lines = Vec::new();
|
||||
for (label, found, by) in [("Minimum", &min, &v["min_by"]), ("Maximum", &max, &v["max_by"])] {
|
||||
let which = crate::i18n::tr_label(label);
|
||||
let which = solidcraft_ui_text::tr_label(label);
|
||||
let value = found.as_ref().map(|(_, shown)| shown.as_str()).unwrap_or_default();
|
||||
lines.push(match by.as_str() {
|
||||
Some("loop") => crate::i18n::format("joint.limit_loop", &[("side", which), ("value", value)]),
|
||||
Some("limit") => crate::i18n::format("joint.limit_limit", &[("side", which), ("value", value)]),
|
||||
Some("turn") => crate::i18n::format("joint.limit_turn", &[("side", which), ("value", value)]),
|
||||
Some("loop") => solidcraft_ui_text::format("joint.limit_loop", &[("side", which), ("value", value)]),
|
||||
Some("limit") => solidcraft_ui_text::format("joint.limit_limit", &[("side", which), ("value", value)]),
|
||||
Some("turn") => solidcraft_ui_text::format("joint.limit_turn", &[("side", which), ("value", value)]),
|
||||
Some(_) => {
|
||||
// Down to a tenth, so it never claims more than was searched.
|
||||
let range = (v["range"].as_f64().filter(|x| x.is_finite()).unwrap_or(0.0) / scale * 10.0).floor() / 10.0;
|
||||
crate::i18n::format("joint.limit_range", &[("side", which), ("range", &num(range, &unit))])
|
||||
solidcraft_ui_text::format("joint.limit_range", &[("side", which), ("range", &num(range, &unit))])
|
||||
}
|
||||
None => {
|
||||
let (a, b) = (by["a"].as_str().unwrap_or_default(), by["b"].as_str().unwrap_or_default());
|
||||
crate::i18n::format("joint.limit_contact", &[("side", which), ("value", value), ("a", a), ("b", b)])
|
||||
solidcraft_ui_text::format("joint.limit_contact", &[("side", which), ("value", value), ("a", a), ("b", b)])
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1308,7 +1311,7 @@ fn fill_limits(doc: &solidcraft_engine::doc::Document, f: &mut JointForm, reply:
|
||||
.map(|o| format!("{} / {}", o["a"].as_str().unwrap_or_default(), o["b"].as_str().unwrap_or_default()))
|
||||
.collect();
|
||||
if !overlapping.is_empty() {
|
||||
f.found_note = Some(crate::i18n::format("joint.limits_overlapping", &[("pairs", &overlapping.join(", "))]));
|
||||
f.found_note = Some(solidcraft_ui_text::format("joint.limits_overlapping", &[("pairs", &overlapping.join(", "))]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1343,7 +1346,7 @@ fn interference(app: &SolveApp, picked: &[Sel], clearance: &str) -> Check {
|
||||
}
|
||||
check_now(app.session.ahead(), &crate::open_meshing::session_world(&app.session), p)
|
||||
}
|
||||
_ => (json!({ "error": crate::i18n::tr("dialog.clearance_invalid") }), Vec::new()),
|
||||
_ => (json!({ "error": solidcraft_ui_text::tr("dialog.clearance_invalid") }), Vec::new()),
|
||||
};
|
||||
if let Ok(mut slot) = check.reply.lock() {
|
||||
*slot = Some(reply);
|
||||
@@ -1389,7 +1392,7 @@ fn interference_rows(app: &SolveApp, ui: &mut egui::Ui, r: &Value) {
|
||||
}
|
||||
let hits = r["overlaps"].as_array().cloned().unwrap_or_default();
|
||||
form_row(ui, "Checked", |ui| {
|
||||
ui.label(crate::i18n::format("dialog.bodies_count", &[("count", &r["bodies"].as_u64().unwrap_or(0).to_string())]));
|
||||
ui.label(solidcraft_ui_text::format("dialog.bodies_count", &[("count", &r["bodies"].as_u64().unwrap_or(0).to_string())]));
|
||||
});
|
||||
let unchecked = r["unchecked"].as_array().cloned().unwrap_or_default();
|
||||
let near = r["near"].as_array().cloned().unwrap_or_default();
|
||||
@@ -1400,7 +1403,7 @@ fn interference_rows(app: &SolveApp, ui: &mut egui::Ui, r: &Value) {
|
||||
// Pairs closer than the clearance, nearest first.
|
||||
if !near.is_empty() {
|
||||
form_row(ui, "Too close", |ui| {
|
||||
ui.label(RichText::new(crate::i18n::tr("ui.distance")).semibold().color(crate::theme::Tokens::get().text));
|
||||
ui.label(RichText::new(solidcraft_ui_text::tr("ui.distance")).semibold().color(crate::theme::Tokens::get().text));
|
||||
});
|
||||
}
|
||||
for h in &near {
|
||||
@@ -1412,7 +1415,7 @@ fn interference_rows(app: &SolveApp, ui: &mut egui::Ui, r: &Value) {
|
||||
let nearest = r["nearest"].as_object().cloned().unwrap_or_default();
|
||||
if !nearest.is_empty() {
|
||||
form_row(ui, "Nearest", |ui| {
|
||||
ui.label(RichText::new(crate::i18n::tr("ui.distance")).semibold().color(crate::theme::Tokens::get().text));
|
||||
ui.label(RichText::new(solidcraft_ui_text::tr("ui.distance")).semibold().color(crate::theme::Tokens::get().text));
|
||||
});
|
||||
}
|
||||
for (body, list) in &nearest {
|
||||
@@ -1426,7 +1429,7 @@ fn interference_rows(app: &SolveApp, ui: &mut egui::Ui, r: &Value) {
|
||||
}
|
||||
if !hits.is_empty() {
|
||||
form_row(ui, "Interferes", |ui| {
|
||||
ui.label(RichText::new(crate::i18n::tr_label("Volume")).semibold().color(crate::theme::Tokens::get().text));
|
||||
ui.label(RichText::new(solidcraft_ui_text::tr_label("Volume")).semibold().color(crate::theme::Tokens::get().text));
|
||||
});
|
||||
}
|
||||
for h in hits {
|
||||
@@ -1437,7 +1440,7 @@ fn interference_rows(app: &SolveApp, ui: &mut egui::Ui, r: &Value) {
|
||||
// Pairs the check could not work out: not clear, not known to interfere.
|
||||
for u in unchecked {
|
||||
form_row(ui, &format!("{} · {}", u["a"].as_str().unwrap_or(""), u["b"].as_str().unwrap_or("")), |ui| {
|
||||
ui.label(RichText::new(crate::i18n::tr("dialog.could_not_be_checked")).color(t.warning))
|
||||
ui.label(RichText::new(solidcraft_ui_text::tr("dialog.could_not_be_checked")).color(t.warning))
|
||||
.backing_on_hover_text(u["reason"].as_str().unwrap_or(""));
|
||||
});
|
||||
}
|
||||
@@ -1474,8 +1477,8 @@ fn joint_problem(app: &SolveApp, k: &Asm, inputs: &[SelInput]) -> Option<(String
|
||||
let occs: Vec<Option<u64>> = bodies.iter().map(|b| occurrence_of_body(&app.session, b)).collect();
|
||||
if occs.first().is_some_and(|first| first.is_some() && occs.iter().all(|o| o == first)) {
|
||||
return Some(match bodies.as_slice() {
|
||||
[body] => (crate::i18n::format("joint.one_body", &[("body", body.as_str())]), Vec::new()),
|
||||
_ => (crate::i18n::format("joint.one_component", &[("bodies", &bodies.join(", "))]), bodies),
|
||||
[body] => (solidcraft_ui_text::format("joint.one_body", &[("body", body.as_str())]), Vec::new()),
|
||||
_ => (solidcraft_ui_text::format("joint.one_component", &[("bodies", &bodies.join(", "))]), bodies),
|
||||
});
|
||||
}
|
||||
commands(app, k, inputs).err().map(|e| (e, Vec::new()))
|
||||
@@ -1509,7 +1512,7 @@ pub fn commands(app: &SolveApp, k: &Asm, inputs: &[SelInput]) -> Result<Vec<(Str
|
||||
Asm::FromBodies => {
|
||||
let bodies = sel_items(inputs, 0).body_names();
|
||||
if bodies.is_empty() {
|
||||
return Err(crate::i18n::tr("ui.components_from_bodies_required").into());
|
||||
return Err(solidcraft_ui_text::tr("ui.components_from_bodies_required").into());
|
||||
}
|
||||
cmd("component.from_bodies", json!({ "bodies": bodies }))
|
||||
}
|
||||
@@ -1599,12 +1602,12 @@ pub fn commands(app: &SolveApp, k: &Asm, inputs: &[SelInput]) -> Result<Vec<(Str
|
||||
let occs = occurrences(s, items);
|
||||
if occs.len() < 2 {
|
||||
if occs.len() == 1 && items.len() > 1 && items.iter().all(|x| matches!(x, Sel::Body { .. })) {
|
||||
return Err(crate::i18n::format(
|
||||
return Err(solidcraft_ui_text::format(
|
||||
"joint.rigid_group_one_component",
|
||||
&[("count", &items.len().to_string()), ("command", crate::i18n::tr("cmd.component_from_bodies"))],
|
||||
&[("count", &items.len().to_string()), ("command", solidcraft_ui_text::tr("cmd.component_from_bodies"))],
|
||||
));
|
||||
}
|
||||
return Err(crate::i18n::tr("joint.rigid_group_select_components").into());
|
||||
return Err(solidcraft_ui_text::tr("joint.rigid_group_select_components").into());
|
||||
}
|
||||
cmd("joint.rigid_group", json!({ "occurrences": occs }))
|
||||
}
|
||||
@@ -1612,7 +1615,7 @@ pub fn commands(app: &SolveApp, k: &Asm, inputs: &[SelInput]) -> Result<Vec<(Str
|
||||
let j = s.doc.assembly.joints.get(*joint).ok_or("no joint that moves yet")?;
|
||||
// A rigid joint has nothing to drive: the dialog says why and offers Edit Joint (#6981).
|
||||
if j.kind == JointKind::Rigid {
|
||||
return Err(crate::i18n::format("drive.rigid", &[("joint", &j.name)]));
|
||||
return Err(solidcraft_ui_text::format("drive.rigid", &[("joint", &j.name)]));
|
||||
}
|
||||
cmd("joint.drive", json!({"joint": j.id, "values": values}))
|
||||
}
|
||||
@@ -1725,7 +1728,7 @@ pub fn world_preview(s: &Session, cmds: &[(String, Value)], colors: Colors) -> O
|
||||
/// joined to nothing grounded moves nothing, and says which part that is.
|
||||
pub fn drive_stop(reply: &Value) -> Option<String> {
|
||||
if let Some(part) = reply.get("unconnected").and_then(Value::as_str) {
|
||||
return Some(crate::i18n::format("drive.unconnected", &[("part", part)]));
|
||||
return Some(solidcraft_ui_text::format("drive.unconnected", &[("part", part)]));
|
||||
}
|
||||
// The values come as joint.list gives them: degrees and millimetres.
|
||||
let at: Vec<String> = reply
|
||||
@@ -1739,10 +1742,10 @@ pub fn drive_stop(reply: &Value) -> Option<String> {
|
||||
let at = at.join(", ");
|
||||
let name = |v: &Value| v.as_str().unwrap_or_default().to_string();
|
||||
if let Some(l) = reply.get("stopped_by_loop").filter(|v| v.is_object()) {
|
||||
return Some(crate::i18n::format("drive.stopped_by_loop", &[("value", &at), ("joint", &name(&l["joint"]))]));
|
||||
return Some(solidcraft_ui_text::format("drive.stopped_by_loop", &[("value", &at), ("joint", &name(&l["joint"]))]));
|
||||
}
|
||||
let c = reply.get("stopped_by_contact").filter(|v| v.is_object())?;
|
||||
Some(crate::i18n::format("drive.stopped_by_contact", &[("value", &at), ("a", &name(&c["a"])), ("b", &name(&c["b"]))]))
|
||||
Some(solidcraft_ui_text::format("drive.stopped_by_contact", &[("value", &at), ("a", &name(&c["a"])), ("b", &name(&c["b"]))]))
|
||||
}
|
||||
|
||||
// ---- overlay ----
|
||||
@@ -1848,20 +1851,22 @@ fn joint_menu(
|
||||
open: &mut Option<Dialog>,
|
||||
run: &mut Option<(&'static str, Value)>,
|
||||
) {
|
||||
if crate::ui_kit::button(ui, crate::i18n::tr_label("Edit Joint"), crate::ui_kit::Button::Normal).clicked() {
|
||||
if crate::ui_kit::button(ui, solidcraft_ui_text::tr_label("Edit Joint"), crate::ui_kit::Button::Normal).clicked() {
|
||||
*open = edit_joint(app, id);
|
||||
ui.close();
|
||||
}
|
||||
if kind != JointKind::Rigid && crate::ui_kit::button(ui, crate::i18n::tr_label("Drive Joint"), crate::ui_kit::Button::Normal).clicked() {
|
||||
if kind != JointKind::Rigid && crate::ui_kit::button(ui, solidcraft_ui_text::tr_label("Drive Joint"), crate::ui_kit::Button::Normal).clicked() {
|
||||
*open = drive_joint(app, id);
|
||||
ui.close();
|
||||
}
|
||||
if crate::ui_kit::button(ui, crate::i18n::tr_label(if suppressed { "Unsuppress" } else { "Suppress" }), crate::ui_kit::Button::Normal).clicked() {
|
||||
if crate::ui_kit::button(ui, solidcraft_ui_text::tr_label(if suppressed { "Unsuppress" } else { "Suppress" }), crate::ui_kit::Button::Normal)
|
||||
.clicked()
|
||||
{
|
||||
*run = Some(("joint.edit", json!({"joint": id, "suppressed": !suppressed})));
|
||||
ui.close();
|
||||
}
|
||||
ui.separator();
|
||||
if crate::ui_kit::button(ui, crate::i18n::tr_label("Delete"), crate::ui_kit::Button::Normal).clicked() {
|
||||
if crate::ui_kit::button(ui, solidcraft_ui_text::tr_label("Delete"), crate::ui_kit::Button::Normal).clicked() {
|
||||
*run = Some(("selection.delete", json!({ "joints": [id] })));
|
||||
ui.close();
|
||||
}
|
||||
|
||||
@@ -740,9 +740,9 @@ fn picks_on_one_component_say_why_and_offer_components() {
|
||||
frame(&mut app, vec![]);
|
||||
}
|
||||
let out = frame(&mut app, vec![]);
|
||||
let why = crate::i18n::format("joint.one_component", &[("bodies", "Body1, Body2")]);
|
||||
let why = solidcraft_ui_text::format("joint.one_component", &[("bodies", "Body1, Body2")]);
|
||||
assert!(text_at(&out, &why).is_some(), "the dialog says why");
|
||||
let at = text_at(&out, crate::i18n::tr("cmd.component_from_bodies")).expect("and offers the way out");
|
||||
let at = text_at(&out, solidcraft_ui_text::tr("cmd.component_from_bodies")).expect("and offers the way out");
|
||||
let button = |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
|
||||
frame(&mut app, vec![egui::Event::PointerMoved(at)]);
|
||||
frame(&mut app, vec![button(true)]);
|
||||
@@ -1334,7 +1334,7 @@ fn components_from_bodies_dialog_select_all_cancel_and_ok() {
|
||||
assert!(matches!(d.kind, Kind::Assembly(Asm::FromBodies)));
|
||||
assert!(apply_commands(&h.app, d).is_err(), "OK requires at least one body");
|
||||
assert_eq!(h.app.session.doc, before);
|
||||
h.step(&json!({"widget":crate::i18n::tr("ui.select_all")})).unwrap();
|
||||
h.step(&json!({"widget":solidcraft_ui_text::tr("ui.select_all")})).unwrap();
|
||||
let commands = apply_commands(&h.app, h.app.dialog.as_ref().unwrap()).unwrap();
|
||||
assert_eq!(commands, vec![("component.from_bodies".into(), json!({"bodies":names}))]);
|
||||
if let Ok(path) = std::env::var("SOLIDCRAFT_COMPONENTS_DIALOG_EVIDENCE") {
|
||||
@@ -1361,7 +1361,7 @@ fn components_from_bodies_dialog_select_all_cancel_and_ok() {
|
||||
h.step(&json!({"start":"component.from_bodies"})).unwrap();
|
||||
let d = h.app.dialog.as_ref().unwrap();
|
||||
assert_eq!(d.inputs[0].items.body_names(), vec![names[0].clone()]);
|
||||
h.step(&json!({"widget":crate::i18n::tr("ui.select_all")})).unwrap();
|
||||
h.step(&json!({"widget":solidcraft_ui_text::tr("ui.select_all")})).unwrap();
|
||||
h.step(&json!({"widget":"OK"})).unwrap();
|
||||
assert!(h.app.dialog.is_none());
|
||||
for name in &names {
|
||||
@@ -1415,8 +1415,8 @@ fn find_limits_fills_the_limits_from_where_the_door_meets_the_block() {
|
||||
frame(app, vec![button(true)]);
|
||||
frame(app, vec![button(false)]);
|
||||
};
|
||||
click(&mut app, &crate::i18n::tr_label("Limits").to_uppercase());
|
||||
click(&mut app, crate::i18n::tr("joint.find_limits"));
|
||||
click(&mut app, &solidcraft_ui_text::tr_label("Limits").to_uppercase());
|
||||
click(&mut app, solidcraft_ui_text::tr("joint.find_limits"));
|
||||
assert_eq!(*app.session.doc, *before, "nothing changes until OK");
|
||||
let Some(Kind::Assembly(Asm::Joint(f))) = app.dialog.as_ref().map(|d| &d.kind) else { panic!("the dialog stays open") };
|
||||
// The door's face 4 mm off its hinge line meets the block's corner (80, 30) opening, and the
|
||||
@@ -1455,7 +1455,11 @@ fn find_limits_searches_the_joint_as_edited_and_leaves_a_free_side() {
|
||||
// from it the other, where nothing is in its way.
|
||||
assert_eq!(f.limits[0].0, untouched.0, "one side found: the row stays as it was: {:?}", f.found);
|
||||
assert!(f.limits[0].1 == untouched.1 || f.limits[0].2 == untouched.2, "{:?} {:?}", f.limits, f.found);
|
||||
assert!(f.found[0].contains(crate::i18n::tr_label("Minimum")) && f.found[0].contains(crate::i18n::tr_label("Maximum")), "{:?}", f.found);
|
||||
assert!(
|
||||
f.found[0].contains(solidcraft_ui_text::tr_label("Minimum")) && f.found[0].contains(solidcraft_ui_text::tr_label("Maximum")),
|
||||
"{:?}",
|
||||
f.found
|
||||
);
|
||||
assert!(f.found_note.is_none(), "{:?}", f.found_note);
|
||||
// Edited after the search, the joint is no longer the one searched (its notes then go).
|
||||
let searched = f.searched.clone().expect("what was searched");
|
||||
|
||||
@@ -116,25 +116,25 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, f: &mut CamForm, inputs: &[Se
|
||||
joint_row(ui, "Cam", "cam_a", &cams, &mut f.a);
|
||||
joint_row(ui, "Follower", "cam_b", &followers, &mut f.b);
|
||||
form_row(ui, "Type", |ui| {
|
||||
let labels: Vec<&str> = FOLLOWER_LABELS.iter().map(|l| crate::i18n::tr_label(l)).collect();
|
||||
let labels: Vec<&str> = FOLLOWER_LABELS.iter().map(|l| solidcraft_ui_text::tr_label(l)).collect();
|
||||
combo(ui, "cam_follower", &labels, &mut f.follower);
|
||||
});
|
||||
if FOLLOWERS.get(f.follower) == Some(&"roller") {
|
||||
form_row(ui, "Radius", |ui| match (pick(inputs, ROLLER), picked_radius(s, inputs, ROLLER)) {
|
||||
(_, Some(r)) => {
|
||||
ui.label(format!("{} mm ({})", trim(r), crate::i18n::tr_label("picked")));
|
||||
ui.label(format!("{} mm ({})", trim(r), solidcraft_ui_text::tr_label("picked")));
|
||||
}
|
||||
(Some(_), None) => {
|
||||
ui.label(RichText::new(crate::i18n::tr_label("not a round face")).color(t.warning));
|
||||
ui.label(RichText::new(solidcraft_ui_text::tr_label("not a round face")).color(t.warning));
|
||||
}
|
||||
(None, None) => enter |= field(ui, &mut f.radius),
|
||||
});
|
||||
}
|
||||
crate::ui_kit::options(ui, "cam_options", &mut [("Other side", &mut f.flip)]);
|
||||
form_row(ui, "Animate", |ui| {
|
||||
let label = crate::i18n::tr(if f.animate { "ui.joint_stop" } else { "ui.joint_play" });
|
||||
let label = solidcraft_ui_text::tr(if f.animate { "ui.joint_stop" } else { "ui.joint_play" });
|
||||
let r = crate::ui_kit::button(ui, label, crate::ui_kit::Button::Normal)
|
||||
.backing_on_hover_text(crate::i18n::tr_label("Play the joint's motion in the view"));
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr_label("Play the joint's motion in the view"));
|
||||
crate::scenario::publish_handle("dialog:Animate", r.rect.center());
|
||||
if r.clicked() {
|
||||
f.animate = !f.animate;
|
||||
|
||||
@@ -105,9 +105,9 @@ fn a_picked_cam_edge_makes_the_cam() {
|
||||
/// The dialog's rows pass their English text, which the catalogs translate by its key.
|
||||
#[test]
|
||||
fn the_cam_dialog_labels_translate() {
|
||||
let de = crate::i18n::Locale::for_language("de");
|
||||
let de = solidcraft_ui_text::Locale::for_language("de");
|
||||
for (english, german) in [("Cam", "Nocken"), ("Follower", "Stößel"), ("Roller", "Rolle"), ("Tip (optional)", "Spitze (optional)")] {
|
||||
assert_eq!(de.label(english), german);
|
||||
}
|
||||
assert_eq!(crate::i18n::Locale::for_language("zh").label("Knife edge"), "尖顶");
|
||||
assert_eq!(solidcraft_ui_text::Locale::for_language("zh").label("Knife edge"), "尖顶");
|
||||
}
|
||||
|
||||
@@ -160,11 +160,11 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, f: &mut CouplingForm, inputs:
|
||||
form_row(ui, label, |ui| {
|
||||
match (pick(inputs, i), picked_radius(s, inputs, i)) {
|
||||
(_, Some(r)) => {
|
||||
ui.label(format!("{} mm ({})", trim(r), crate::i18n::tr_label("picked")));
|
||||
ui.label(format!("{} mm ({})", trim(r), solidcraft_ui_text::tr_label("picked")));
|
||||
}
|
||||
// A pick on a flat face or a straight edge has no radius: say so rather than fail on OK.
|
||||
(Some(_), None) => {
|
||||
ui.label(RichText::new(crate::i18n::tr_label("not a round face")).color(t.warning));
|
||||
ui.label(RichText::new(solidcraft_ui_text::tr_label("not a round face")).color(t.warning));
|
||||
}
|
||||
(None, None) => *enter |= field(ui, text),
|
||||
}
|
||||
@@ -205,9 +205,9 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, f: &mut CouplingForm, inputs:
|
||||
ui.label(RichText::new(shown.unwrap_or_else(|| "-".into())).color(t.text_dim));
|
||||
});
|
||||
form_row(ui, "Animate", |ui| {
|
||||
let label = crate::i18n::tr(if f.animate { "ui.joint_stop" } else { "ui.joint_play" });
|
||||
let label = solidcraft_ui_text::tr(if f.animate { "ui.joint_stop" } else { "ui.joint_play" });
|
||||
let r = crate::ui_kit::button(ui, label, crate::ui_kit::Button::Normal)
|
||||
.backing_on_hover_text(crate::i18n::tr_label("Play the joint's motion in the view"));
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr_label("Play the joint's motion in the view"));
|
||||
crate::scenario::publish_handle("dialog:Animate", r.rect.center());
|
||||
if r.clicked() {
|
||||
f.animate = !f.animate;
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::dialogs::{Kind, apply_commands, preview_commands};
|
||||
#[test]
|
||||
fn missing_joint_messages_are_drawn_in_full() {
|
||||
for language in ["en", "de", "vi"] {
|
||||
let _language = crate::i18n::scope(language);
|
||||
let _language = solidcraft_ui_text::scope(language);
|
||||
for (command, message) in [
|
||||
("joint.screw", "needs a joint that turns and one that slides"),
|
||||
("joint.gear", "needs two joints that turn"),
|
||||
@@ -44,7 +44,7 @@ fn missing_joint_messages_are_drawn_in_full() {
|
||||
.save(std::path::Path::new(&dir).join(format!("{command}-{language}.png")))
|
||||
.unwrap();
|
||||
}
|
||||
let message = crate::i18n::tr_label(message);
|
||||
let message = solidcraft_ui_text::tr_label(message);
|
||||
let text = crate::test_support::text_shapes(&output).find(|text| text.galley.text() == message).expect("missing-joint message");
|
||||
assert!(!text.galley.elided, "{command} ({language}) cuts off: {message}");
|
||||
let bounds = text.galley.rect.translate(text.pos.to_vec2());
|
||||
@@ -220,7 +220,7 @@ fn play_turns_the_angle_of_a_planar_pinion() {
|
||||
/// The dialog's rows pass their English text, which the catalogs translate by its key.
|
||||
#[test]
|
||||
fn the_dialog_labels_translate() {
|
||||
let de = crate::i18n::Locale::for_language("de");
|
||||
let de = solidcraft_ui_text::Locale::for_language("de");
|
||||
for (english, german) in [
|
||||
("Gear 1", "Zahnrad 1"),
|
||||
("Radius 2", "Radius 2"),
|
||||
@@ -230,5 +230,5 @@ fn the_dialog_labels_translate() {
|
||||
] {
|
||||
assert_eq!(de.label(english), german);
|
||||
}
|
||||
assert_eq!(crate::i18n::Locale::for_language("ja").label("not a round face"), "円筒面ではありません");
|
||||
assert_eq!(solidcraft_ui_text::Locale::for_language("ja").label("not a round face"), "円筒面ではありません");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Dense ports of the common feature forms. Commands and parameter construction stay in dialogs.
|
||||
use super::*;
|
||||
use crate::dialogs_surface::continuity_row_explained;
|
||||
use crate::ui_kit;
|
||||
use crate::ui_kit::{choice_row, expr_row, hint_row};
|
||||
use solidcraft_engine::kernel::Continuity;
|
||||
@@ -127,7 +128,7 @@ fn setback_factor() -> String {
|
||||
/// The Corners row's hover text: a line for each choice.
|
||||
fn fillet_corner_tip() -> String {
|
||||
let factor = setback_factor();
|
||||
FILLET_CORNERS.map(|(_, tip)| crate::i18n::format(tip, &[("factor", &factor)])).join("\n")
|
||||
FILLET_CORNERS.map(|(_, tip)| solidcraft_ui_text::format(tip, &[("factor", &factor)])).join("\n")
|
||||
}
|
||||
|
||||
/// What `solid.fillet` does with a radius too large for the faces beside an edge (`fit`): stop
|
||||
@@ -213,7 +214,7 @@ fn fillet_corner_rows(
|
||||
}
|
||||
let mut enter = false;
|
||||
let mut corner = if patch { Continuity::G2 } else { Continuity::G1 };
|
||||
ui_kit::continuity_row_explained(ui, "ui.corners", "fi_corner", &mut corner, &FILLET_CORNERS.map(|(g, _)| g), &fillet_corner_tip());
|
||||
continuity_row_explained(ui, "ui.corners", "fi_corner", &mut corner, &FILLET_CORNERS.map(|(g, _)| g), &fillet_corner_tip());
|
||||
if (corner == Continuity::G2) != patch {
|
||||
if corner == Continuity::G2 {
|
||||
extra.insert("corner".into(), json!(Continuity::G2.as_str()));
|
||||
@@ -233,13 +234,13 @@ fn fillet_corner_rows(
|
||||
let least = r.and_then(|r| crate::dialogs::fillet_least_setback(ui.ctx(), s, items, &corners, r));
|
||||
let warning = least
|
||||
.filter(|least| length(&shown).is_some_and(|v| v <= least * (1.0 + 1e-9)))
|
||||
.map(|least| crate::i18n::format("ui.fillet_setback_least", &[("least", &crate::length_input::shown(&s.doc, least))]));
|
||||
.map(|least| solidcraft_ui_text::format("ui.fillet_setback_least", &[("least", &crate::length_input::shown(&s.doc, least))]));
|
||||
let mut text = shown.clone();
|
||||
let row = ui.scope(|ui| {
|
||||
let hint = crate::i18n::format("ui.fillet_setback_hint", &[("factor", &factor)]);
|
||||
let hint = solidcraft_ui_text::format("ui.fillet_setback_hint", &[("factor", &factor)]);
|
||||
ui_kit::checked_row(ui, "ui.fillet_setback", &mut text, &hint, warning.as_deref())
|
||||
});
|
||||
row.response.backing_on_hover_text(crate::i18n::format("ui.fillet_setback_tip", &[("factor", &factor)]));
|
||||
row.response.backing_on_hover_text(solidcraft_ui_text::format("ui.fillet_setback_tip", &[("factor", &factor)]));
|
||||
let (accepted, field) = row.inner;
|
||||
enter |= accepted;
|
||||
if text != shown {
|
||||
@@ -358,7 +359,7 @@ pub(super) fn rows(app: &mut SolveApp, ctx: &egui::Context, d: &mut Dialog, ui:
|
||||
ui_kit::text(ui, &prompt, ui_kit::Text::Hint("tool")).backing_on_hover_text(&prompt);
|
||||
if matches!(&d.kind, Kind::SketchModify(k) if k.previews()) {
|
||||
let label = d.active_input().map(|input| tr(input.label)).unwrap_or_else(|| tr("Objects"));
|
||||
let step = crate::i18n::format(
|
||||
let step = solidcraft_ui_text::format(
|
||||
"dialog.selection_step",
|
||||
&[("step", &(d.active + 1).to_string()), ("total", &d.inputs.len().to_string()), ("input", label)],
|
||||
);
|
||||
@@ -937,7 +938,7 @@ fn additional_rows(app: &mut SolveApp, d: &mut Dialog, ui: &mut egui::Ui) -> boo
|
||||
ui_kit::segmented(ui, &mut i, &["Emboss", "Deboss"], Default::default());
|
||||
*deboss = i == 1;
|
||||
});
|
||||
ui.add(egui::Label::new(crate::i18n::tr("ui.emboss_wrap_note")).wrap());
|
||||
ui.add(egui::Label::new(solidcraft_ui_text::tr("ui.emboss_wrap_note")).wrap());
|
||||
}
|
||||
Kind::Align { flip, components, axes: [ax, ay, az], angle } => {
|
||||
// Where on the picked face or edge to snap: as picked (a face turns to meet
|
||||
@@ -1138,7 +1139,7 @@ fn measure_rows(app: &SolveApp, ui: &mut egui::Ui, r: Option<&Value>, show: &mut
|
||||
for (i, it) in r["items"].as_array().into_iter().flatten().enumerate() {
|
||||
let lines = crate::quick_measure::item_lines(app, it);
|
||||
for (k, line) in lines.into_iter().enumerate() {
|
||||
let label = if k == 0 { crate::i18n::format("measure.item", &[("number", &(i + 1).to_string())]) } else { String::new() };
|
||||
let label = if k == 0 { solidcraft_ui_text::format("measure.item", &[("number", &(i + 1).to_string())]) } else { String::new() };
|
||||
// Coordinates need all three values; a fixed-height form cell elides the end.
|
||||
let text = if label.is_empty() { line } else { format!("{label} {line}") };
|
||||
ui_kit::text(ui, &text, ui_kit::Text::Report(&format!("measure_item_{i}_{k}"), false));
|
||||
@@ -1158,7 +1159,7 @@ fn measure_rows(app: &SolveApp, ui: &mut egui::Ui, r: Option<&Value>, show: &mut
|
||||
}
|
||||
} else {
|
||||
let text = crate::prefs::show_mm(app, d, 1);
|
||||
form_row(ui, crate::i18n::tr_label("Distance"), |ui| {
|
||||
form_row(ui, solidcraft_ui_text::tr_label("Distance"), |ui| {
|
||||
let size = crate::theme::Tokens::get().form_close_size;
|
||||
// The value keeps the room left of the button, so the button stays inside the row.
|
||||
ui.scope(|ui| {
|
||||
@@ -1166,7 +1167,7 @@ fn measure_rows(app: &SolveApp, ui: &mut egui::Ui, r: Option<&Value>, show: &mut
|
||||
ui_kit::text(ui, &text, ui_kit::Text::Cell);
|
||||
});
|
||||
let (rect, _) = ui.allocate_exact_size(egui::vec2(size, size), egui::Sense::hover());
|
||||
let copy = ui_kit::icon_button_at(ui, rect, "copy", crate::i18n::tr_label("Copy"));
|
||||
let copy = ui_kit::icon_button_at(ui, rect, "copy", solidcraft_ui_text::tr_label("Copy"));
|
||||
crate::scenario::publish_handle("measure:copy:distance", copy.rect.center());
|
||||
if copy.clicked() {
|
||||
ui.ctx().copy_text(text.clone());
|
||||
@@ -1181,7 +1182,7 @@ fn measure_rows(app: &SolveApp, ui: &mut egui::Ui, r: Option<&Value>, show: &mut
|
||||
if let Some(a) = r["angle_deg"].as_f64()
|
||||
&& r["items"].as_array().is_some_and(|v| v.len() == 2)
|
||||
{
|
||||
value(ui, crate::i18n::tr_label("Angle"), &format!("{a:.3}°"));
|
||||
value(ui, solidcraft_ui_text::tr_label("Angle"), &format!("{a:.3}°"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1202,7 +1203,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn common_forms_fit_both_layouts_and_scales_and_pin_footer_at_small_heights() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
for scale in [1.0, 2.0] {
|
||||
for height in [950.0, 720.0] {
|
||||
let mut h = Harness::new();
|
||||
@@ -1244,7 +1245,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn tiny_windows_do_not_panic_or_publish_non_finite_cards() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
for size in [(1.0, 1.0), (40.0, 80.0), (180.0, 240.0)] {
|
||||
let mut h = Harness::new();
|
||||
h.call("ui.set", json!({"layout": mode}));
|
||||
@@ -1551,7 +1552,7 @@ mod tests {
|
||||
};
|
||||
let shown = |h: &Harness| {
|
||||
let tree = h.output.accesskit_update.as_ref().unwrap();
|
||||
let label = crate::i18n::tr_label("ui.fillet_setback");
|
||||
let label = solidcraft_ui_text::tr_label("ui.fillet_setback");
|
||||
tree.nodes.iter().find(|(_, n)| n.label() == Some(label)).and_then(|(_, n)| n.value()).unwrap_or_default().to_string()
|
||||
};
|
||||
let typed = |h: &Harness| match &h.app.dialog.as_ref().unwrap().kind {
|
||||
@@ -1612,10 +1613,10 @@ mod tests {
|
||||
fn fillet_labels_use_words_not_continuity_codes() {
|
||||
let coded = |text: &str| ["G0", "G1", "G2", "G3"].iter().any(|code| text.contains(code));
|
||||
for label in FILLET_TYPES.map(|(label, _)| label).into_iter().chain(FILLET_CORNERS.map(|(g, _)| g.label_id())) {
|
||||
assert!(!coded(crate::i18n::tr_label(label)), "{label}: {}", crate::i18n::tr_label(label));
|
||||
assert!(!coded(solidcraft_ui_text::tr_label(label)), "{label}: {}", solidcraft_ui_text::tr_label(label));
|
||||
}
|
||||
for (g, tip) in FILLET_CORNERS {
|
||||
assert!(crate::i18n::tr(tip).contains(g.code()), "the hover text keeps the code for those who know it: {tip}");
|
||||
assert!(solidcraft_ui_text::tr(tip).contains(g.code()), "the hover text keeps the code for those who know it: {tip}");
|
||||
}
|
||||
let tip = fillet_corner_tip();
|
||||
assert!(tip.contains("rolling") && tip.contains("1.5") && tip.contains("Setback"), "what each corner builds: {tip}");
|
||||
@@ -1649,7 +1650,7 @@ mod tests {
|
||||
// A blend too large for its faces keeps its own words.
|
||||
assert_eq!(
|
||||
crate::dialogs::fillet_plain_error("the fillet is larger than the faces beside its edges allow", true),
|
||||
Some(crate::dialogs::FilletRefusal::Plain(crate::i18n::tr("ui.fillet_error_too_large").to_string()))
|
||||
Some(crate::dialogs::FilletRefusal::Plain(solidcraft_ui_text::tr("ui.fillet_error_too_large").to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ pub fn start(_app: &SolveApp, id: &str) -> Option<(Kind, Vec<SelInput>)> {
|
||||
}
|
||||
|
||||
fn tr(text: &str) -> &str {
|
||||
crate::i18n::tr_label(text)
|
||||
solidcraft_ui_text::tr_label(text)
|
||||
}
|
||||
|
||||
/// The dialog's own rows (after the selection inputs); true when Enter was pressed in a value.
|
||||
|
||||
@@ -128,12 +128,12 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Ap, inputs: &[SelInpu
|
||||
let _ = inputs;
|
||||
ui_kit::text(
|
||||
ui,
|
||||
crate::i18n::tr_label("Open local libraries in the desktop app. You can export this design's library here."),
|
||||
solidcraft_ui_text::tr_label("Open local libraries in the desktop app. You can export this design's library here."),
|
||||
ui_kit::Text::Note("library_platform"),
|
||||
);
|
||||
}
|
||||
ui_kit::checkbox(ui, crate::i18n::tr_label("Replace existing library file"), &mut k.local.overwrite);
|
||||
let save = ui_kit::button(ui, crate::i18n::tr_label("Export design library..."), ui_kit::Button::Normal);
|
||||
ui_kit::checkbox(ui, solidcraft_ui_text::tr_label("Replace existing library file"), &mut k.local.overwrite);
|
||||
let save = ui_kit::button(ui, solidcraft_ui_text::tr_label("Export design library..."), ui_kit::Button::Normal);
|
||||
crate::scenario::publish_handle("library:export", save.rect.center());
|
||||
if save.clicked() {
|
||||
if app.services.pick_save.is_some() {
|
||||
@@ -144,7 +144,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Ap, inputs: &[SelInpu
|
||||
}
|
||||
ui_kit::text(
|
||||
ui,
|
||||
crate::i18n::tr_label("Apply your appearance first. Exports assigned appearances and materials created in this design."),
|
||||
solidcraft_ui_text::tr_label("Apply your appearance first. Exports assigned appearances and materials created in this design."),
|
||||
ui_kit::Text::Note("library_export"),
|
||||
);
|
||||
});
|
||||
@@ -162,7 +162,7 @@ fn import_rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Ap, inputs: &[SelI
|
||||
k.local.selected = None;
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
if ui_kit::button(ui, crate::i18n::tr_label("Open library..."), ui_kit::Button::Normal).clicked() {
|
||||
if ui_kit::button(ui, solidcraft_ui_text::tr_label("Open library..."), ui_kit::Button::Normal).clicked() {
|
||||
open_library(app, k.local.token.clone());
|
||||
}
|
||||
let load_button = ui_kit::button(ui, "Load library", ui_kit::Button::Enabled(!k.local.path.is_empty()));
|
||||
@@ -175,7 +175,7 @@ fn import_rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Ap, inputs: &[SelI
|
||||
});
|
||||
if let Some(catalog) = &k.local.catalog {
|
||||
ui_kit::text(ui, &catalog.name, ui_kit::Text::Report("library_name", false));
|
||||
let mode = ui_kit::checkbox(ui, crate::i18n::tr_label("Physical material definitions"), &mut k.local.materials);
|
||||
let mode = ui_kit::checkbox(ui, solidcraft_ui_text::tr_label("Physical material definitions"), &mut k.local.materials);
|
||||
crate::scenario::publish_handle("library:materials", mode.rect.center());
|
||||
if mode.changed() {
|
||||
k.local.selected = None;
|
||||
@@ -200,7 +200,7 @@ fn import_rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Ap, inputs: &[SelI
|
||||
.collect();
|
||||
if appearances.is_empty() {
|
||||
let message = if k.local.materials { "No matching physical materials." } else { "No matching appearance presets." };
|
||||
ui_kit::text(ui, crate::i18n::tr_label(message), ui_kit::Text::Note("library_empty"));
|
||||
ui_kit::text(ui, solidcraft_ui_text::tr_label(message), ui_kit::Text::Note("library_empty"));
|
||||
}
|
||||
let row_height = ui_kit::row_height();
|
||||
ui_kit::list_frame(ui, "local_library_entries", |ui| {
|
||||
@@ -267,7 +267,7 @@ fn import_rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Ap, inputs: &[SelI
|
||||
{
|
||||
ui_kit::text(
|
||||
ui,
|
||||
&format!("{}: {} g/cm³", crate::i18n::tr("material.editor.assigned_density"), material.density_g_cm3),
|
||||
&format!("{}: {} g/cm³", solidcraft_ui_text::tr("material.editor.assigned_density"), material.density_g_cm3),
|
||||
ui_kit::Text::Report("library_material_density", false),
|
||||
);
|
||||
if let Some(card) = &material.physical_card {
|
||||
@@ -276,12 +276,12 @@ fn import_rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Ap, inputs: &[SelI
|
||||
}
|
||||
ui_kit::text(
|
||||
ui,
|
||||
crate::i18n::tr_label("Copies the definition only. Assign it from Physical Material when ready."),
|
||||
solidcraft_ui_text::tr_label("Copies the definition only. Assign it from Physical Material when ready."),
|
||||
ui_kit::Text::Note("library_material_copy"),
|
||||
);
|
||||
let copy = ui_kit::button(
|
||||
ui,
|
||||
crate::i18n::tr_label("Import material into design"),
|
||||
solidcraft_ui_text::tr_label("Import material into design"),
|
||||
ui_kit::Button::Enabled(entry.is_some() && !k.local.material_name.trim().is_empty()),
|
||||
);
|
||||
crate::scenario::publish_handle("library:material-import", copy.rect.center());
|
||||
@@ -290,7 +290,7 @@ fn import_rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Ap, inputs: &[SelI
|
||||
{
|
||||
k.note = Some(
|
||||
match app.run("library.apply", json!({"path":k.local.path,"entry":entry.id,"revision":entry.revision,"name":k.local.material_name})) {
|
||||
Ok(_) => crate::i18n::tr_label("Material copied. Existing body assignments are unchanged.").to_string(),
|
||||
Ok(_) => solidcraft_ui_text::tr_label("Material copied. Existing body assignments are unchanged.").to_string(),
|
||||
Err(error) => error,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -208,7 +208,7 @@ fn transport(ui: &mut egui::Ui, what: Transport, tip: &str) -> egui::Response {
|
||||
p.add(bar(c.x + 6.0));
|
||||
}
|
||||
}
|
||||
resp.backing_on_hover_text(crate::i18n::tr_label(tip))
|
||||
resp.backing_on_hover_text(solidcraft_ui_text::tr_label(tip))
|
||||
}
|
||||
|
||||
/// Study `key` as the Motion Study dialog edits it: (id, name, steps, keys as tracks with values
|
||||
@@ -423,7 +423,7 @@ fn export_animation(app: &mut SolveApp, params: Value, name: &str) {
|
||||
)
|
||||
});
|
||||
match made {
|
||||
Ok(_) => app.set_status(crate::i18n::format("ui.saved_to", &[("path", &path)]), false),
|
||||
Ok(_) => app.set_status(solidcraft_ui_text::format("ui.saved_to", &[("path", &path)]), false),
|
||||
Err(e) => app.set_status(e.to_string(), true),
|
||||
}
|
||||
});
|
||||
@@ -459,7 +459,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Mo, inputs: &mut [Sel
|
||||
let list = app.session.execute("contact.list", &json!({})).unwrap_or(Value::Null);
|
||||
form_row(ui, "Contact", |ui| {
|
||||
let mut on = list.get("enabled").and_then(Value::as_bool).unwrap_or(false);
|
||||
if crate::ui_kit::checkbox(ui, crate::i18n::tr_label("Enabled"), &mut on).changed() {
|
||||
if crate::ui_kit::checkbox(ui, solidcraft_ui_text::tr_label("Enabled"), &mut on).changed() {
|
||||
let _ = app.run("contact.enable_sets", json!({ "enabled": on }));
|
||||
}
|
||||
});
|
||||
@@ -469,14 +469,20 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Mo, inputs: &mut [Sel
|
||||
crate::ui_kit::section(ui, format!("contact:{n}"), &n, true, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let count = s.get("occurrences").and_then(Value::as_array).map_or(0, Vec::len);
|
||||
ui.label(RichText::new(crate::i18n::format("contact.components_count", &[("count", &count.to_string())])).color(t.text_dim));
|
||||
if crate::ui_kit::button(ui, crate::i18n::tr_label(if off { "Unsuppress" } else { "Suppress" }), crate::ui_kit::Button::Small)
|
||||
.clicked()
|
||||
ui.label(
|
||||
RichText::new(solidcraft_ui_text::format("contact.components_count", &[("count", &count.to_string())])).color(t.text_dim),
|
||||
);
|
||||
if crate::ui_kit::button(
|
||||
ui,
|
||||
solidcraft_ui_text::tr_label(if off { "Unsuppress" } else { "Suppress" }),
|
||||
crate::ui_kit::Button::Small,
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
let _ = app.run("contact.edit", json!({"set": n, "suppressed": !off}));
|
||||
}
|
||||
if crate::ui_kit::button(ui, crate::i18n::tr_label("×"), crate::ui_kit::Button::Small)
|
||||
.backing_on_hover_text(crate::i18n::tr_label("Delete the set"))
|
||||
if crate::ui_kit::button(ui, solidcraft_ui_text::tr_label("×"), crate::ui_kit::Button::Small)
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr_label("Delete the set"))
|
||||
.clicked()
|
||||
{
|
||||
let _ = app.run("contact.edit", json!({"set": n, "delete": true}));
|
||||
@@ -485,7 +491,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Mo, inputs: &mut [Sel
|
||||
});
|
||||
}
|
||||
ui.vertical(|ui| {
|
||||
if crate::ui_kit::button(ui, crate::i18n::tr_label("Check collisions"), crate::ui_kit::Button::Normal).clicked() {
|
||||
if crate::ui_kit::button(ui, solidcraft_ui_text::tr_label("Check collisions"), crate::ui_kit::Button::Normal).clicked() {
|
||||
*check = Some(app.session.execute("contact.check", &json!({})).unwrap_or_else(|e| json!({"error": e.to_string()})));
|
||||
}
|
||||
});
|
||||
@@ -493,9 +499,9 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Mo, inputs: &mut [Sel
|
||||
let hits = c.get("collisions").and_then(Value::as_array).cloned().unwrap_or_default();
|
||||
crate::ui_kit::section(ui, "contact_collisions", "Collisions", true, |ui| {
|
||||
if let Some(e) = c.get("error").and_then(Value::as_str) {
|
||||
ui.label(RichText::new(crate::i18n::tr_label(e)).color(t.error));
|
||||
ui.label(RichText::new(solidcraft_ui_text::tr_label(e)).color(t.error));
|
||||
} else if hits.is_empty() {
|
||||
ui.label(RichText::new(crate::i18n::tr_label("none")).color(t.text_dim));
|
||||
ui.label(RichText::new(solidcraft_ui_text::tr_label("none")).color(t.text_dim));
|
||||
} else {
|
||||
ui.vertical(|ui| {
|
||||
for h in hits {
|
||||
@@ -533,7 +539,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Mo, inputs: &mut [Sel
|
||||
}
|
||||
if let Some(e) = error {
|
||||
ui.vertical(|ui| {
|
||||
ui.label(RichText::new(crate::i18n::tr_label(e)).color(t.error));
|
||||
ui.label(RichText::new(solidcraft_ui_text::tr_label(e)).color(t.error));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -569,7 +575,7 @@ fn motion_study_rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Mo, inputs:
|
||||
let moving = joints_moving(app);
|
||||
if moving.is_empty() {
|
||||
ui.vertical(|ui| {
|
||||
ui.label(RichText::new(crate::i18n::tr_label("no joint that moves yet")).color(t.text_dim));
|
||||
ui.label(RichText::new(solidcraft_ui_text::tr_label("no joint that moves yet")).color(t.text_dim));
|
||||
});
|
||||
return enter;
|
||||
}
|
||||
@@ -602,7 +608,7 @@ fn motion_study_rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Mo, inputs:
|
||||
*playing = false;
|
||||
}
|
||||
ui.add_space(t.space_6);
|
||||
crate::ui_kit::checkbox(ui, crate::i18n::tr_label("Loop"), looping);
|
||||
crate::ui_kit::checkbox(ui, solidcraft_ui_text::tr_label("Loop"), looping);
|
||||
let names: Vec<String> = SPEEDS.iter().map(|x| format!("{x}×")).collect();
|
||||
let refs: Vec<&str> = names.iter().map(String::as_str).collect();
|
||||
ui.allocate_ui(vec2(t.form_label_width, t.control_height), |ui| combo(ui, "ms_speed", &refs, speed));
|
||||
@@ -644,12 +650,12 @@ fn motion_study_rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Mo, inputs:
|
||||
let labels: Vec<String> = moving.iter().map(|(_, n, _)| n.clone()).collect();
|
||||
let refs: Vec<&str> = labels.iter().map(String::as_str).collect();
|
||||
let mut pos = moving.iter().position(|(i, _, _)| *i == tr.joint).unwrap_or(0);
|
||||
form_row(ui, &crate::i18n::format("motion.track_name", &[("number", &(ti + 1).to_string())]), |ui| {
|
||||
form_row(ui, &solidcraft_ui_text::format("motion.track_name", &[("number", &(ti + 1).to_string())]), |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let width = (ui.available_width() - t.control_height - t.space_8).max(0.0);
|
||||
ui.allocate_ui(vec2(width, t.control_height), |ui| combo(ui, &format!("ms_joint{ti}"), &refs, &mut pos));
|
||||
if crate::ui_kit::button(ui, crate::i18n::tr_label("×"), crate::ui_kit::Button::Small)
|
||||
.backing_on_hover_text(crate::i18n::tr_label("Remove the track"))
|
||||
if crate::ui_kit::button(ui, solidcraft_ui_text::tr_label("×"), crate::ui_kit::Button::Small)
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr_label("Remove the track"))
|
||||
.clicked()
|
||||
{
|
||||
remove = Some(ti);
|
||||
@@ -678,8 +684,8 @@ fn motion_study_rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Mo, inputs:
|
||||
}
|
||||
ui.vertical(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
if crate::ui_kit::button(ui, crate::i18n::tr_label("Key here"), crate::ui_kit::Button::Small)
|
||||
.backing_on_hover_text(crate::i18n::tr_label("Add a key at this step with the joint's value now"))
|
||||
if crate::ui_kit::button(ui, solidcraft_ui_text::tr_label("Key here"), crate::ui_kit::Button::Small)
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr_label("Add a key at this step with the joint's value now"))
|
||||
.clicked()
|
||||
&& !tr.points.iter().any(|(s, _)| *s == now)
|
||||
{
|
||||
@@ -694,7 +700,7 @@ fn motion_study_rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Mo, inputs:
|
||||
tr.points.push((now, format!("0 {unit}")));
|
||||
tr.points.sort_by_key(|p| p.0);
|
||||
}
|
||||
if crate::ui_kit::button(ui, crate::i18n::tr_label("Delete key"), crate::ui_kit::Button::Small).clicked() {
|
||||
if crate::ui_kit::button(ui, solidcraft_ui_text::tr_label("Delete key"), crate::ui_kit::Button::Small).clicked() {
|
||||
tr.points.retain(|(s, _)| *s != now);
|
||||
}
|
||||
});
|
||||
@@ -706,16 +712,16 @@ fn motion_study_rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Mo, inputs:
|
||||
let mut export = false;
|
||||
ui.vertical(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
if crate::ui_kit::button(ui, crate::i18n::tr_label("Add Key"), crate::ui_kit::Button::Normal)
|
||||
.backing_on_hover_text(crate::i18n::tr_label("Key a joint's value at this step"))
|
||||
if crate::ui_kit::button(ui, solidcraft_ui_text::tr_label("Add Key"), crate::ui_kit::Button::Normal)
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr_label("Key a joint's value at this step"))
|
||||
.clicked()
|
||||
&& let Some((ji, _, kind)) = moving.first()
|
||||
{
|
||||
let unit = if kind.is_angle(0) { "deg" } else { crate::length_input::unit(&app.session.doc) };
|
||||
tracks.push(Track { joint: *ji, index: 0, points: vec![(now, format!("0 {unit}"))] });
|
||||
}
|
||||
export = crate::ui_kit::button(ui, crate::i18n::tr_label("Export Animation…"), crate::ui_kit::Button::Normal)
|
||||
.backing_on_hover_text(crate::i18n::tr_label("Save the study as an animated GIF, seen as the viewport shows it"))
|
||||
export = crate::ui_kit::button(ui, solidcraft_ui_text::tr_label("Export Animation…"), crate::ui_kit::Button::Normal)
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr_label("Save the study as an animated GIF, seen as the viewport shows it"))
|
||||
.clicked();
|
||||
});
|
||||
});
|
||||
@@ -745,7 +751,7 @@ fn motion_study_rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Mo, inputs:
|
||||
}
|
||||
if let Some(e) = error {
|
||||
ui.vertical(|ui| {
|
||||
ui.label(RichText::new(crate::i18n::tr_label(e)).color(t.text_dim));
|
||||
ui.label(RichText::new(solidcraft_ui_text::tr_label(e)).color(t.text_dim));
|
||||
});
|
||||
}
|
||||
enter
|
||||
|
||||
@@ -285,7 +285,7 @@ fn collision_rows(
|
||||
ui.vertical(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
if hits.is_empty() {
|
||||
ui.label(RichText::new(crate::i18n::tr("motion.no_collisions")).color(t.text_dim));
|
||||
ui.label(RichText::new(solidcraft_ui_text::tr("motion.no_collisions")).color(t.text_dim));
|
||||
}
|
||||
for (i, c) in hits.iter().enumerate() {
|
||||
let steps: Vec<String> = c["steps"]
|
||||
@@ -298,7 +298,8 @@ fn collision_rows(
|
||||
})
|
||||
.collect();
|
||||
let name = |k: &str| c[k].as_str().unwrap_or_default().to_string();
|
||||
let text = crate::i18n::format("motion.collision", &[("a", &name("a")), ("b", &name("b")), ("steps", &steps.join(", "))]);
|
||||
let text =
|
||||
solidcraft_ui_text::format("motion.collision", &[("a", &name("a")), ("b", &name("b")), ("steps", &steps.join(", "))]);
|
||||
// A click shows where they first collide.
|
||||
let link = ui.link(RichText::new(text).color(t.error));
|
||||
crate::scenario::publish_handle(&format!("motion:collision:{i}"), link.rect.center());
|
||||
@@ -310,14 +311,14 @@ fn collision_rows(
|
||||
}
|
||||
if v["truncated"] == true {
|
||||
let to = v["checked_to"].as_f64().map(|x| x.round().to_string()).unwrap_or_default();
|
||||
ui.label(RichText::new(crate::i18n::format("motion.checked_to", &[("step", &to)])).color(t.warning));
|
||||
ui.label(RichText::new(solidcraft_ui_text::format("motion.checked_to", &[("step", &to)])).color(t.warning));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
ui.vertical(|ui| {
|
||||
ui.add(egui::Label::new(RichText::new(crate::i18n::tr_label(e)).color(t.error)).wrap());
|
||||
ui.add(egui::Label::new(RichText::new(solidcraft_ui_text::tr_label(e)).color(t.error)).wrap());
|
||||
});
|
||||
}
|
||||
None => {}
|
||||
@@ -385,7 +386,7 @@ fn plot_rows(
|
||||
});
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
ui.add(egui::Label::new(RichText::new(crate::i18n::tr_label(e)).color(t.text_dim)).wrap());
|
||||
ui.add(egui::Label::new(RichText::new(solidcraft_ui_text::tr_label(e)).color(t.text_dim)).wrap());
|
||||
}
|
||||
None => {}
|
||||
});
|
||||
|
||||
@@ -197,7 +197,7 @@ pub fn rows(app: &SolveApp, ui: &mut egui::Ui, k: &mut Resize) -> bool {
|
||||
ui.label(configuration_name(&k.family.id, size, k.length));
|
||||
});
|
||||
}
|
||||
crate::ui_kit::text(ui, crate::i18n::tr("ui.part_size_shared"), crate::ui_kit::Text::Hint("part_size_shared"));
|
||||
crate::ui_kit::text(ui, solidcraft_ui_text::tr("ui.part_size_shared"), crate::ui_kit::Text::Hint("part_size_shared"));
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ pub fn families(v: &Value) -> Vec<Family> {
|
||||
/// name for a family the catalogs don't know.
|
||||
pub(crate) fn family_name(f: &Family) -> String {
|
||||
let id = format!("parts.family.{}", f.id);
|
||||
match crate::i18n::tr(&id) {
|
||||
match solidcraft_ui_text::tr(&id) {
|
||||
shown if shown == id => f.name.clone(),
|
||||
shown => shown.to_string(),
|
||||
}
|
||||
@@ -170,7 +170,11 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Pt, inputs: &[SelInpu
|
||||
};
|
||||
if lib.is_empty() {
|
||||
ui.vertical(|ui| {
|
||||
crate::ui_kit::text(ui, crate::i18n::tr_label("the parts library is not available"), crate::ui_kit::Text::Note("parts_unavailable"));
|
||||
crate::ui_kit::text(
|
||||
ui,
|
||||
solidcraft_ui_text::tr_label("the parts library is not available"),
|
||||
crate::ui_kit::Text::Note("parts_unavailable"),
|
||||
);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@@ -222,7 +226,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Pt, inputs: &[SelInpu
|
||||
k.length = Some(i);
|
||||
});
|
||||
}
|
||||
text_row(ui, "Name", &mut k.name, crate::i18n::tr("parts.automatic_name"));
|
||||
text_row(ui, "Name", &mut k.name, solidcraft_ui_text::tr("parts.automatic_name"));
|
||||
// The part, and its table values.
|
||||
let key = (k.family, k.size, k.length);
|
||||
let tex_id = egui::Id::unique("sc_part_picture");
|
||||
@@ -244,7 +248,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Pt, inputs: &[SelInpu
|
||||
ui.add(egui::Image::new(&tx).fit_to_exact_size(vec2(180.0, 112.0)).corner_radius(t.radius_sm));
|
||||
}
|
||||
None => {
|
||||
crate::ui_kit::text(ui, crate::i18n::tr_label("no picture"), crate::ui_kit::Text::Note("parts_picture"));
|
||||
crate::ui_kit::text(ui, solidcraft_ui_text::tr_label("no picture"), crate::ui_kit::Text::Note("parts_picture"));
|
||||
}
|
||||
});
|
||||
if let Some((_, row)) = f.sizes.get(k.size)
|
||||
@@ -264,7 +268,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Pt, inputs: &[SelInpu
|
||||
}
|
||||
if inputs.first().is_some_and(|i| i.items.is_empty()) {
|
||||
ui.vertical(|ui| {
|
||||
crate::ui_kit::text(ui, crate::i18n::tr("parts.placement_hint"), crate::ui_kit::Text::Note("parts_placement"));
|
||||
crate::ui_kit::text(ui, solidcraft_ui_text::tr("parts.placement_hint"), crate::ui_kit::Text::Note("parts_placement"));
|
||||
});
|
||||
}
|
||||
false
|
||||
@@ -332,7 +336,7 @@ mod tests {
|
||||
fn family_names_follow_the_language() {
|
||||
let l = families(&solidcraft_engine::Session::default().execute("parts.library", &json!({})).unwrap());
|
||||
assert!(l.len() >= 7, "{}", l.len());
|
||||
let _german = crate::i18n::scope("de");
|
||||
let _german = solidcraft_ui_text::scope("de");
|
||||
for f in &l {
|
||||
assert_ne!(family_name(f), f.name, "{}", f.id);
|
||||
}
|
||||
@@ -415,7 +419,7 @@ mod tests {
|
||||
for f in &l {
|
||||
for (size, row) in &f.sizes {
|
||||
for key in row.as_object().into_iter().flat_map(|o| o.keys()).filter(|k| !matches!(k.as_str(), "size" | "default_length")) {
|
||||
let label = dimension_label(key).map(crate::i18n::tr);
|
||||
let label = dimension_label(key).map(solidcraft_ui_text::tr);
|
||||
assert!(
|
||||
label.is_some_and(|l| l.starts_with(char::is_uppercase) && !l.contains('_') && !l.starts_with("parts.")),
|
||||
"{} {size}: {key} is labelled {label:?}",
|
||||
|
||||
@@ -137,7 +137,7 @@ pub fn start(app: &SolveApp, id: &str) -> Option<(Kind, Vec<SelInput>)> {
|
||||
|
||||
/// A value row whose empty value means "from the rule" (or another default).
|
||||
fn opt_field(ui: &mut egui::Ui, label: &str, v: &mut String, unit: &str, hint: &str) -> bool {
|
||||
let hint = format!("{unit}, {}", crate::i18n::tr_label(hint));
|
||||
let hint = format!("{unit}, {}", solidcraft_ui_text::tr_label(hint));
|
||||
hint_row(ui, label, v, &hint)
|
||||
}
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ fn inch_plastic_rule_edit_preserves_material_angles_and_stored_lengths() {
|
||||
/// face", …) are in the user's language: each has a message.
|
||||
#[test]
|
||||
fn optional_field_hints_have_messages() {
|
||||
let ja = crate::i18n::Locale::for_language("ja");
|
||||
let ja = solidcraft_ui_text::Locale::for_language("ja");
|
||||
for hint in ["no hole", "to the face", "from the rule", "none", "at the face", "solid"] {
|
||||
assert_ne!(ja.label(hint), hint, "`{hint}` has a Japanese message");
|
||||
}
|
||||
|
||||
@@ -178,10 +178,10 @@ fn spawn_wait(
|
||||
/// Run `file.send_to_slicer` and report what happened. A slicer that has to be started first is
|
||||
/// started and waited for in the background.
|
||||
fn send_to_slicer(app: &mut SolveApp, params: Value) {
|
||||
let _language = crate::i18n::scope(&app.preferences.language);
|
||||
let _language = solidcraft_ui_text::scope(&app.preferences.language);
|
||||
if let Some(wait) = &app.slicer_wait {
|
||||
// Whatever was picked this time (another slicer, other bodies): nothing else is sent.
|
||||
let line = crate::i18n::format("ui.slicer_still_starting", &[("slicer", &wait.slicer.name)]);
|
||||
let line = solidcraft_ui_text::format("ui.slicer_still_starting", &[("slicer", &wait.slicer.name)]);
|
||||
app.set_status(line, false);
|
||||
return;
|
||||
}
|
||||
@@ -202,7 +202,7 @@ fn begin_wait(app: &mut SolveApp, params: Value, result: &Value) {
|
||||
let spec = params["slicer"].as_str().unwrap_or_default().to_string();
|
||||
let Some(slicer) = slicers::resolve(&spec) else {
|
||||
// Started, but it can't be found again to be sent the file: say so rather than go quiet.
|
||||
app.set_status(crate::i18n::format("ui.slicer_lost", &[("slicer", &name), ("path", &path)]), true);
|
||||
app.set_status(solidcraft_ui_text::format("ui.slicer_lost", &[("slicer", &name), ("path", &path)]), true);
|
||||
return;
|
||||
};
|
||||
// Same reading as `slicers::wait_for_ready`: when `busctl` is missing or fails, send; when it is
|
||||
@@ -211,7 +211,7 @@ fn begin_wait(app: &mut SolveApp, params: Value, result: &Value) {
|
||||
let done =
|
||||
spawn_wait(move |left| probe.is_ready_within(left.min(slicers::PROBE_LIMIT)) != Some(false), slicers::READY_LIMIT, slicers::READY_POLL);
|
||||
app.slicer_wait = Some(SlicerWait { done, slicer, result: result.clone() });
|
||||
app.set_status(crate::i18n::format("ui.slicer_starting", &[("slicer", &name)]), false);
|
||||
app.set_status(solidcraft_ui_text::format("ui.slicer_starting", &[("slicer", &name)]), false);
|
||||
}
|
||||
|
||||
/// Per frame: when the slicer being waited for is ready, send the file; when the wait ran out, say so.
|
||||
@@ -224,11 +224,11 @@ pub fn tick_wait(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
Err(TryRecvError::Disconnected) => false,
|
||||
};
|
||||
let Some(wait) = app.slicer_wait.take() else { return };
|
||||
let _language = crate::i18n::scope(&app.preferences.language);
|
||||
let _language = solidcraft_ui_text::scope(&app.preferences.language);
|
||||
let path = wait.result.get("path").and_then(Value::as_str).unwrap_or_default().to_string();
|
||||
if ready && path.is_empty() {
|
||||
// Nothing was written to send (the answer had no path): say so, rather than open "".
|
||||
app.set_status(crate::i18n::format("ui.slicer_no_file", &[("slicer", &wait.slicer.name)]), true);
|
||||
app.set_status(solidcraft_ui_text::format("ui.slicer_no_file", &[("slicer", &wait.slicer.name)]), true);
|
||||
} else if ready {
|
||||
// The file is already written: open that one, rather than export the design again.
|
||||
let mut result = wait.result;
|
||||
@@ -243,7 +243,7 @@ pub fn tick_wait(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
}
|
||||
report(app, &result);
|
||||
} else {
|
||||
app.set_status(crate::i18n::format("ui.slicer_no_response", &[("slicer", &wait.slicer.name), ("path", &path)]), true);
|
||||
app.set_status(solidcraft_ui_text::format("ui.slicer_no_response", &[("slicer", &wait.slicer.name), ("path", &path)]), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,7 +338,7 @@ fn sync_print_face(app: &mut SolveApp, k: &mut SaveMesh, inputs: &mut [SelInput]
|
||||
for face in picked.iter().filter(|f| !shown.contains(f)) {
|
||||
let Sel::Face { body, .. } = face else { continue };
|
||||
if !bodies.contains(body) {
|
||||
app.set_status(crate::i18n::tr("ui.print_face_body_not_sent"), true);
|
||||
app.set_status(solidcraft_ui_text::tr("ui.print_face_body_not_sent"), true);
|
||||
} else if !done.contains(body) {
|
||||
crate::print_face_ui::set(app, body, Some(face));
|
||||
done.push(body.clone());
|
||||
@@ -371,7 +371,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut SaveMesh, inputs: &mu
|
||||
// One row each: the two side by side do not fit the dialog's width.
|
||||
for (label, visible) in [("Add all bodies", false), ("Add visible bodies", true)] {
|
||||
form_row(ui, "", |ui| {
|
||||
if crate::ui_kit::button(ui, crate::i18n::tr_label(label), crate::ui_kit::Button::Small).clicked()
|
||||
if crate::ui_kit::button(ui, solidcraft_ui_text::tr_label(label), crate::ui_kit::Button::Small).clicked()
|
||||
&& let Some(input) = inputs.first_mut()
|
||||
{
|
||||
input.items = body_picks(app, visible);
|
||||
@@ -420,11 +420,11 @@ fn slicer_combo(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut SaveMesh) {
|
||||
let current = entries.iter().find(|e| e.key == k.slicer);
|
||||
let pad = "\u{2003}\u{2003}";
|
||||
let easyprint = crate::easyprint_ui::available(app);
|
||||
let easyprint_label = crate::i18n::tr("ui.easyprint_row");
|
||||
let easyprint_label = solidcraft_ui_text::tr("ui.easyprint_row");
|
||||
let text = match current {
|
||||
Some(e) => format!("{pad}{}", e.slicer.name),
|
||||
None if easyprint && k.slicer == EASYPRINT => format!("{pad}{easyprint_label}"),
|
||||
None => crate::i18n::tr_label("No slicer found").to_string(),
|
||||
None => solidcraft_ui_text::tr_label("No slicer found").to_string(),
|
||||
};
|
||||
let mut pick_custom = false;
|
||||
let mut delete: Option<String> = None;
|
||||
@@ -450,7 +450,7 @@ fn slicer_combo(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut SaveMesh) {
|
||||
ui.painter().galley(at, text, Tokens::get().text);
|
||||
crate::scenario::publish_handle(&format!("dialog:Slicer:{}", e.slicer.name), rect.center());
|
||||
if e.user {
|
||||
let hint = crate::i18n::format("ui.slicer_delete_tip", &[("program", e.slicer.command.first().map_or("", String::as_str))]);
|
||||
let hint = solidcraft_ui_text::format("ui.slicer_delete_tip", &[("program", e.slicer.command.first().map_or("", String::as_str))]);
|
||||
resp.clone().backing_on_hover_text(if elided { format!("{}\n{hint}", e.slicer.name) } else { hint });
|
||||
if resp.hovered() && ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Delete)) {
|
||||
delete = Some(e.key.clone());
|
||||
@@ -491,12 +491,12 @@ fn slicer_combo(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut SaveMesh) {
|
||||
ui.painter().text(
|
||||
rect.left_center() + vec2(ICON + 9.0, 0.0),
|
||||
egui::Align2::LEFT_CENTER,
|
||||
crate::i18n::tr_label("Custom…"),
|
||||
solidcraft_ui_text::tr_label("Custom…"),
|
||||
egui::FontId::proportional(crate::theme::Tokens::get().font_button),
|
||||
Tokens::get().text,
|
||||
);
|
||||
crate::scenario::publish_handle("dialog:Slicer:Custom", rect.center());
|
||||
if resp.backing_on_hover_text(crate::i18n::tr("ui.custom_program_tip")).clicked() {
|
||||
if resp.backing_on_hover_text(solidcraft_ui_text::tr("ui.custom_program_tip")).clicked() {
|
||||
pick_custom = true;
|
||||
ui.close();
|
||||
}
|
||||
@@ -530,7 +530,7 @@ fn slicer_combo(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut SaveMesh) {
|
||||
/// Put a program the user picked at the top of the list (and keep it).
|
||||
fn add_custom(app: &mut SolveApp, path: &str) -> Result<(), String> {
|
||||
if slicers::custom(path).is_none() {
|
||||
return Err(crate::i18n::format("ui.not_a_program", &[("path", path)]));
|
||||
return Err(solidcraft_ui_text::format("ui.not_a_program", &[("path", path)]));
|
||||
}
|
||||
let list = &mut app.preferences.custom_slicers;
|
||||
list.retain(|p| p != path);
|
||||
@@ -551,12 +551,12 @@ pub fn commands(k: &SaveMesh, inputs: &[SelInput]) -> Result<Vec<(String, Value)
|
||||
}
|
||||
let names = crate::selection::items(inputs, 0).body_names();
|
||||
if names.is_empty() {
|
||||
return Err(crate::i18n::tr("ui.save_mesh_pick_bodies").into());
|
||||
return Err(solidcraft_ui_text::tr("ui.save_mesh_pick_bodies").into());
|
||||
}
|
||||
p["bodies"] = json!(names);
|
||||
if k.open {
|
||||
if k.slicer.is_empty() {
|
||||
return Err(crate::i18n::tr("ui.save_mesh_no_slicer_open").into());
|
||||
return Err(solidcraft_ui_text::tr("ui.save_mesh_no_slicer_open").into());
|
||||
}
|
||||
p["format"] = json!("3mf");
|
||||
if k.slicer == EASYPRINT {
|
||||
@@ -612,7 +612,7 @@ pub fn run(app: &mut SolveApp, k: &SaveMesh, inputs: &[SelInput]) -> Result<(),
|
||||
/// Run `file.save_mesh` into `path` and report what happened.
|
||||
fn save(app: &mut SolveApp, mut params: Value, path: &str) {
|
||||
// Runs from the file picker's answer, outside the frame's language scope.
|
||||
let _language = crate::i18n::scope(&app.preferences.language);
|
||||
let _language = solidcraft_ui_text::scope(&app.preferences.language);
|
||||
params["path"] = json!(path);
|
||||
let Ok(result) = app.run("file.save_mesh", params) else { return };
|
||||
report(app, &result);
|
||||
@@ -626,19 +626,21 @@ fn report(app: &mut SolveApp, result: &Value) {
|
||||
None => match result.get("slicer").and_then(Value::as_str) {
|
||||
Some(s) => {
|
||||
let mut line = match (result.get("numbered").and_then(Value::as_bool), std::path::Path::new(path).file_name()) {
|
||||
(Some(true), Some(file)) => crate::i18n::format("ui.sent_to_slicer_copy", &[("slicer", s), ("file", &file.to_string_lossy())]),
|
||||
_ => crate::i18n::format("ui.sent_to_slicer", &[("slicer", s)]),
|
||||
(Some(true), Some(file)) => {
|
||||
solidcraft_ui_text::format("ui.sent_to_slicer_copy", &[("slicer", s), ("file", &file.to_string_lossy())])
|
||||
}
|
||||
_ => solidcraft_ui_text::format("ui.sent_to_slicer", &[("slicer", s)]),
|
||||
};
|
||||
// Said once: sending the same bodies again overwrites the file the slicer has open.
|
||||
if result.get("numbered").is_some() && !std::mem::replace(&mut app.preferences.slicer_resend_hint_seen, true) {
|
||||
line = format!("{line}. {}", crate::i18n::tr("ui.slicer_resend_hint"));
|
||||
line = format!("{line}. {}", solidcraft_ui_text::tr("ui.slicer_resend_hint"));
|
||||
}
|
||||
if result.get("stl_fallback").and_then(Value::as_bool) == Some(true) {
|
||||
line = format!("{line}. {}", crate::i18n::tr("ui.slicer_stl_fallback"));
|
||||
line = format!("{line}. {}", solidcraft_ui_text::tr("ui.slicer_stl_fallback"));
|
||||
}
|
||||
app.set_status(line, false);
|
||||
}
|
||||
None => app.set_status(crate::i18n::format("ui.saved_to", &[("path", path)]), false),
|
||||
None => app.set_status(solidcraft_ui_text::format("ui.saved_to", &[("path", path)]), false),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Sm, inputs: &mut [Sel
|
||||
Sm::Flange { ty, height, angle, radius, position, height_from, flip, distance, reverse, rule, own, relief, relief_size, then } => {
|
||||
*ty = flange_type(inputs);
|
||||
form_row(ui, "Type", |ui| {
|
||||
ui.label(FLANGE_TYPES.get(*ty).copied().map(crate::i18n::tr).unwrap_or(""));
|
||||
ui.label(FLANGE_TYPES.get(*ty).copied().map(solidcraft_ui_text::tr).unwrap_or(""));
|
||||
});
|
||||
match *ty {
|
||||
1 => {
|
||||
@@ -262,7 +262,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Sm, inputs: &mut [Sel
|
||||
// Further bends: each a flange on the far edge of the one before it.
|
||||
form_row(ui, "Further Bends", |ui| {
|
||||
let add = crate::ui_kit::button(ui, "Add bend", crate::ui_kit::Button::Normal)
|
||||
.backing_on_hover_text(crate::i18n::tr_label("Another bend on the far edge of the last flange"));
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr_label("Another bend on the far edge of the last flange"));
|
||||
if add.clicked() && then.len() < 16 {
|
||||
then.push(("45 deg".into(), "20 mm".into(), false));
|
||||
}
|
||||
@@ -272,7 +272,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Sm, inputs: &mut [Sel
|
||||
let bend = n + 2;
|
||||
// The bend's heading: what it makes, and a bin to take it out.
|
||||
form_row(ui, &format!("Bend {bend}"), |ui| {
|
||||
if crate::ui_kit::bin_button(ui, crate::i18n::tr_label("Remove this bend")).clicked() {
|
||||
if crate::ui_kit::bin_button(ui, solidcraft_ui_text::tr_label("Remove this bend")).clicked() {
|
||||
drop = Some(n);
|
||||
}
|
||||
ui.label(format!("{} · {}", a.trim(), h.trim()));
|
||||
@@ -287,16 +287,16 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Sm, inputs: &mut [Sel
|
||||
then.remove(n);
|
||||
}
|
||||
crate::ui_kit::section(ui, "flange_advanced", "Advanced", false, |ui| {
|
||||
enter |= hint_row(ui, "Bend Radius", radius, crate::i18n::tr_label("from the rule"));
|
||||
enter |= hint_row(ui, "Bend Radius", radius, solidcraft_ui_text::tr_label("from the rule"));
|
||||
// The relief at the corners these flanges make: the rule's unless set here.
|
||||
choice_row(ui, "Corner Relief Shape", "sm_flange_relief", relief, &FLANGE_RELIEF_LABELS);
|
||||
enter |= hint_row(ui, "Corner Relief", relief_size, crate::i18n::tr_label("from the rule"));
|
||||
enter |= hint_row(ui, "Corner Relief", relief_size, solidcraft_ui_text::tr_label("from the rule"));
|
||||
// With several edges, a row for each: an angle and a height of its own, left
|
||||
// empty for the values above (shown as the hints).
|
||||
let points = edge_point_list(inputs);
|
||||
if points.len() > 1 || own.iter().any(|o| !(o.1.trim().is_empty() && o.2.trim().is_empty())) {
|
||||
form_row(ui, "Per Edge", |ui| {
|
||||
ui.label(RichText::new(crate::i18n::tr_label("angle, height")).weak())
|
||||
ui.label(RichText::new(solidcraft_ui_text::tr_label("angle, height")).weak())
|
||||
.backing_on_hover_text("An edge's own angle and height. Empty: the values above.");
|
||||
});
|
||||
for (n, p) in points.iter().enumerate() {
|
||||
@@ -342,21 +342,21 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Sm, inputs: &mut [Sel
|
||||
Sm::Perforate { diameter, pitch, margin, pattern } => {
|
||||
enter |= expr_row(ui, "ui.diameter", diameter);
|
||||
enter |= expr_row(ui, "ui.perforate_pitch", pitch);
|
||||
enter |= hint_row(ui, "Margin", margin, crate::i18n::tr_label("ui.perforate_margin_default"));
|
||||
enter |= hint_row(ui, "Margin", margin, solidcraft_ui_text::tr_label("ui.perforate_margin_default"));
|
||||
choice_row(ui, "Pattern", "sm_perforate_pattern", pattern, &PERFORATION_LABELS);
|
||||
}
|
||||
Sm::Hem { length, gap, flip } => {
|
||||
form_row(ui, "Type", |ui| {
|
||||
ui.label(crate::i18n::tr_label("Flat"));
|
||||
ui.label(solidcraft_ui_text::tr_label("Flat"));
|
||||
});
|
||||
enter |= expr_row(ui, "Length", length);
|
||||
enter |= hint_row(ui, "ui.sheet_gap", gap, crate::i18n::tr_label("from the rule"));
|
||||
enter |= hint_row(ui, "ui.sheet_gap", gap, solidcraft_ui_text::tr_label("from the rule"));
|
||||
crate::ui_kit::options(ui, "sheet_options", &mut [("Flip", flip)]);
|
||||
}
|
||||
Sm::Fold { angle, radius, position, flip } => {
|
||||
choice_row(ui, "Bend Line Position", "sm_fold_pos", position, &FOLD_POSITION_LABELS);
|
||||
enter |= expr_row(ui, "Angle", angle);
|
||||
enter |= hint_row(ui, "Bend Radius", radius, crate::i18n::tr_label("from the rule"));
|
||||
enter |= hint_row(ui, "Bend Radius", radius, solidcraft_ui_text::tr_label("from the rule"));
|
||||
crate::ui_kit::options(ui, "sheet_options", &mut [("Flip", flip)]);
|
||||
}
|
||||
Sm::Path { radius } => {
|
||||
@@ -365,14 +365,14 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Sm, inputs: &mut [Sel
|
||||
let lengths = path_lengths(app, inputs);
|
||||
form_row(ui, "Flanges", |ui| {
|
||||
ui.label(if lengths.is_empty() {
|
||||
crate::i18n::tr_label("pick the path").to_string()
|
||||
solidcraft_ui_text::tr_label("pick the path").to_string()
|
||||
} else {
|
||||
let each: Vec<String> = lengths.iter().map(|l| format!("{l:.1}")).collect();
|
||||
format!("{} mm", each.join(" · "))
|
||||
});
|
||||
});
|
||||
crate::ui_kit::caption(ui, "Flanges", crate::i18n::tr_label("set by the sketch's lines"));
|
||||
enter |= hint_row(ui, "Bend Radius", radius, crate::i18n::tr_label("from the rule"));
|
||||
crate::ui_kit::caption(ui, "Flanges", solidcraft_ui_text::tr_label("set by the sketch's lines"));
|
||||
enter |= hint_row(ui, "Bend Radius", radius, solidcraft_ui_text::tr_label("from the rule"));
|
||||
}
|
||||
Sm::Unfold { .. } => {}
|
||||
Sm::Convert { rule } => rule_combo(app, ui, "sm_conv_rule", rule),
|
||||
@@ -411,7 +411,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Sm, inputs: &mut [Sel
|
||||
}
|
||||
if *pick < rules.len()
|
||||
&& !app.session.doc.sheet.rules.is_empty()
|
||||
&& crate::ui_kit::button(ui, crate::i18n::tr_label("Delete rule"), crate::ui_kit::Button::Normal).clicked()
|
||||
&& crate::ui_kit::button(ui, solidcraft_ui_text::tr_label("Delete rule"), crate::ui_kit::Button::Normal).clicked()
|
||||
{
|
||||
let n = rules.get(*pick).map(|r| r.name.clone()).unwrap_or_default();
|
||||
match app.run("sheet.manage_rules", json!({"name": n, "delete": true})) {
|
||||
@@ -467,12 +467,12 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Sm, inputs: &mut [Sel
|
||||
});
|
||||
}
|
||||
ui.vertical(|ui| {
|
||||
if crate::ui_kit::button(ui, crate::i18n::tr_label("Export DXF…"), crate::ui_kit::Button::Normal).clicked() {
|
||||
if crate::ui_kit::button(ui, solidcraft_ui_text::tr_label("Export DXF…"), crate::ui_kit::Button::Normal).clicked() {
|
||||
*status = Some(export_flat(app, v["body"].as_str().unwrap_or(""), "dxf"));
|
||||
}
|
||||
});
|
||||
ui.vertical(|ui| {
|
||||
if crate::ui_kit::button(ui, crate::i18n::tr_label("Export Bend Table…"), crate::ui_kit::Button::Normal).clicked() {
|
||||
if crate::ui_kit::button(ui, solidcraft_ui_text::tr_label("Export Bend Table…"), crate::ui_kit::Button::Normal).clicked() {
|
||||
*status = Some(export_flat(app, v["body"].as_str().unwrap_or(""), "csv"));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::Services;
|
||||
use crate::dialogs::{apply_commands, for_feature as edit_dialog};
|
||||
|
||||
/// A 100 x 60 base flange on XY (2.5 mm steel).
|
||||
fn plate(mode: crate::shell::LayoutMode) -> SolveApp {
|
||||
fn plate(mode: crate::ui_kit::LayoutMode) -> SolveApp {
|
||||
let mut s = Session::default();
|
||||
s.execute("sketch.create", &json!({"plane": "XY", "name": "Base"})).unwrap();
|
||||
s.execute("sketch.rectangle.two_point", &json!({"p0": [0, 0], "p1": [100, 60]})).unwrap();
|
||||
@@ -21,7 +21,7 @@ fn plate(mode: crate::shell::LayoutMode) -> SolveApp {
|
||||
/// dialog's height, angle and bend position; Edit Feature brings the edges back.
|
||||
#[test]
|
||||
fn flange_follows_the_picks() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let mut app = plate(mode);
|
||||
let sk = app.session.doc.features[0].id;
|
||||
app.session.selection = vec![Sel::Profile { sketch: sk, index: 0 }];
|
||||
@@ -95,7 +95,7 @@ fn a_click_on_the_bottom_edge_makes_an_edge_flange() {
|
||||
/// Hem takes edges and an optional gap; rules make and pick a rule with its expressions.
|
||||
#[test]
|
||||
fn hem_and_rules() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let mut app = plate(mode);
|
||||
app.run("sheet.flange", json!({"sketch": "Base"})).unwrap();
|
||||
app.session.selection = edge_sel(&app.session, Vec3::new(50.0, 0.0, 2.5)).into_iter().collect();
|
||||
@@ -133,7 +133,7 @@ fn hem_and_rules() {
|
||||
|
||||
#[test]
|
||||
fn flat_exports_use_the_save_picker() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
use std::sync::{Arc, Mutex};
|
||||
let mut app = plate(mode);
|
||||
app.run("sheet.flange", json!({"sketch": "Base"})).unwrap();
|
||||
@@ -157,7 +157,7 @@ fn flat_exports_use_the_save_picker() {
|
||||
|
||||
#[test]
|
||||
fn shallow_pocket_overlay_labels_depth_without_drawing_a_through_void() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let mut app = plate(mode);
|
||||
app.run("sheet.flange", json!({"sketch": "Base"})).unwrap();
|
||||
app.run("sketch.create", json!({"plane": "XY", "name": "Pocket"})).unwrap();
|
||||
@@ -225,7 +225,7 @@ fn flat_pattern_shows_each_side_of_the_flat_size_whole() {
|
||||
|
||||
#[test]
|
||||
fn flat_pattern_islands_fit_before_the_actual_command_panel() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let mut app = plate(mode);
|
||||
app.run("sheet.flange", json!({"sketch": "Base"})).unwrap();
|
||||
app.run("sketch.create", json!({"plane": "XY", "name": "Retain"})).unwrap();
|
||||
@@ -247,7 +247,7 @@ fn flat_pattern_islands_fit_before_the_actual_command_panel() {
|
||||
let viewport = Rect::from_min_max(pos2(250.0, crate::toolbar::APP_BAR_HEIGHT + crate::toolbar::RIBBON_HEIGHT), pos2(1400.0, 832.0));
|
||||
let browser = Rect::from_min_max(viewport.min, viewport.min + vec2(250.0, 450.0));
|
||||
let history = Rect::from_min_max(pos2(viewport.left(), viewport.bottom() - 40.0), viewport.max);
|
||||
if mode == crate::shell::LayoutMode::Floating {
|
||||
if mode == crate::ui_kit::LayoutMode::Floating {
|
||||
app.shell.content = Some(Rect::from_min_max(pos2(browser.right(), viewport.top()), pos2(viewport.right(), history.top())));
|
||||
}
|
||||
let ctx = crate::fonts::test_context();
|
||||
@@ -286,7 +286,7 @@ fn flat_pattern_islands_fit_before_the_actual_command_panel() {
|
||||
outlines.iter().flat_map(|p| &p.points).all(|p| p.x <= panel.left() - 19.0),
|
||||
"both complete islands must remain exposed before the command panel: {panel:?}"
|
||||
);
|
||||
if mode == crate::shell::LayoutMode::Floating {
|
||||
if mode == crate::ui_kit::LayoutMode::Floating {
|
||||
assert!(
|
||||
outlines.iter().flat_map(|p| &p.points).all(|p| !browser.contains(*p) && !history.contains(*p)),
|
||||
"flat pattern fits clear of the shell cards"
|
||||
@@ -297,7 +297,7 @@ fn flat_pattern_islands_fit_before_the_actual_command_panel() {
|
||||
|
||||
#[test]
|
||||
fn coincident_top_and_bottom_pocket_depths_share_a_readable_annotation() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let mut app = plate(mode);
|
||||
app.run("sheet.flange", json!({"sketch":"Base"})).unwrap();
|
||||
app.run("sketch.create", json!({"plane":"XY","name":"Retain"})).unwrap();
|
||||
@@ -329,7 +329,7 @@ fn coincident_top_and_bottom_pocket_depths_share_a_readable_annotation() {
|
||||
|
||||
#[test]
|
||||
fn pocket_export_button_uses_the_picker_and_preserves_depth_layers() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.call("ui.set", json!({"layout":mode}));
|
||||
for (command, params) in [
|
||||
@@ -366,7 +366,7 @@ fn pocket_export_button_uses_the_picker_and_preserves_depth_layers() {
|
||||
#[test]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn bend_table_picker_writes_the_current_body() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let mut app = plate(mode);
|
||||
app.run("sheet.flange", json!({"sketch": "Base"})).unwrap();
|
||||
app.run("sheet.flange", json!({"edges": [[50, 0, 2.5]], "height": 20})).unwrap();
|
||||
@@ -394,7 +394,7 @@ fn bend_table_picker_writes_the_current_body() {
|
||||
/// made with it, and the dialog shows it the next time.
|
||||
#[test]
|
||||
fn rules_dialog_picks_the_corner_relief_shape() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.call("ui.set", json!({"layout":mode}));
|
||||
h.app
|
||||
@@ -447,7 +447,7 @@ fn rules_dialog_picks_the_corner_relief_shape() {
|
||||
/// of the part made with it, and the dialog shows it the next time.
|
||||
#[test]
|
||||
fn rules_dialog_sets_how_bend_angles_read() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.call("ui.set", json!({"layout":mode}));
|
||||
h.app.run("sheet.manage_rules", json!({"name": "AL2", "thickness": "2 mm", "bend_radius": "1 mm", "active": true})).unwrap();
|
||||
@@ -551,7 +551,7 @@ fn the_rule_manager_relieves_a_corner_that_is_not_square() {
|
||||
/// a shape and a size reach the feature, Edit Feature shows them again, and Rule takes them back.
|
||||
#[test]
|
||||
fn flange_dialog_picks_a_corner_relief_of_its_own() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.call("ui.set", json!({"layout":mode}));
|
||||
h.app
|
||||
@@ -627,7 +627,7 @@ fn flange_dialog_picks_a_corner_relief_of_its_own() {
|
||||
|
||||
#[test]
|
||||
fn pocket_depth_labels_follow_inches_without_changing_flat_data() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let mut app = plate(mode);
|
||||
app.run("document.units", json!({"units":"in"})).unwrap();
|
||||
assert_eq!(super::pocket_label(&app, &json!({"side":"top","depth_mm":25.4*0.1234})).as_deref(), Some("Top pocket: 0.1234 in deep"));
|
||||
@@ -640,7 +640,7 @@ fn pocket_depth_labels_follow_inches_without_changing_flat_data() {
|
||||
/// brings them back and rebuilds the same flanges.
|
||||
#[test]
|
||||
fn edge_flanges_can_have_an_angle_and_a_height_of_their_own() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let mut app = plate(mode);
|
||||
app.run("sheet.flange", json!({"sketch": "Base"})).unwrap();
|
||||
let edges: Vec<Sel> = [[50.0, 0.0, 2.5], [50.0, 60.0, 2.5], [0.0, 30.0, 2.5], [100.0, 30.0, 2.5]]
|
||||
@@ -703,7 +703,7 @@ fn edge_flanges_can_have_an_angle_and_a_height_of_their_own() {
|
||||
/// one flange command, one body and one undo step, and a bend with no height is refused.
|
||||
#[test]
|
||||
fn further_bends_in_the_flange_dialog_make_one_chained_flap() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let mut app = plate(mode);
|
||||
app.run("sheet.flange", json!({"sketch": "Base"})).unwrap();
|
||||
app.run("sheet.flange", json!({"edges": [[0, 30, 2.5]], "height": 30})).unwrap();
|
||||
@@ -777,7 +777,7 @@ fn add_bend_and_remove_bend_buttons_work() {
|
||||
/// reaches the feature, and Edit Feature shows it again.
|
||||
#[test]
|
||||
fn flange_dialog_measures_the_height_from_the_chosen_datum() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.call("ui.set", json!({"layout":mode}));
|
||||
h.app.run("sketch.create", json!({"plane": "XY", "name": "Base"})).unwrap();
|
||||
@@ -901,7 +901,7 @@ fn convert_takes_the_fixed_face_and_the_bend_edges() {
|
||||
/// and Edit Feature brings the edge and the path back.
|
||||
#[test]
|
||||
fn path_flange_dialog_follows_the_path() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let mut app = plate(mode);
|
||||
app.run("sheet.flange", json!({"sketch": "Base"})).unwrap();
|
||||
app.run("sheet.flange", json!({"edges": [[0, 30, 2.5]], "height": 30})).unwrap();
|
||||
@@ -949,7 +949,7 @@ fn path_flange_dialog_follows_the_path() {
|
||||
|
||||
// ---- flat pattern navigation ----
|
||||
|
||||
fn flat_app(preset: &str, mode: crate::shell::LayoutMode) -> SolveApp {
|
||||
fn flat_app(preset: &str, mode: crate::ui_kit::LayoutMode) -> SolveApp {
|
||||
let mut app = plate(mode);
|
||||
app.run("sheet.flange", json!({"sketch": "Base"})).unwrap();
|
||||
let flat = app.session.execute("sheet.flat_pattern", &json!({})).unwrap();
|
||||
@@ -989,7 +989,7 @@ fn frame(app: &mut SolveApp, ctx: &egui::Context, time: &mut f64, mods: egui::Mo
|
||||
/// cursor. The flat view never orbits.
|
||||
#[test]
|
||||
fn flat_pattern_pans_and_zooms_like_the_design_viewport() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
use egui::{Event, Modifiers, PointerButton};
|
||||
let none = Modifiers::NONE;
|
||||
let chords = [none, Modifiers::SHIFT, Modifiers::CTRL, Modifiers::ALT, Modifiers::SHIFT | Modifiers::ALT];
|
||||
@@ -1094,7 +1094,7 @@ impl IntoNav for egui::PointerButton {
|
||||
/// camera and selection alone.
|
||||
#[test]
|
||||
fn design_camera_is_unchanged_while_the_flat_pattern_has_focus() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
use egui::{Event, Modifiers, PointerButton};
|
||||
let none = Modifiers::NONE;
|
||||
for preset in crate::prefs::NAV_PRESETS.iter().map(|p| p.id) {
|
||||
@@ -1136,7 +1136,7 @@ fn design_camera_is_unchanged_while_the_flat_pattern_has_focus() {
|
||||
/// Fit and Home frame the flat pattern (not the model), and leave the Design camera alone.
|
||||
#[test]
|
||||
fn fit_and_home_frame_the_flat_pattern() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let ctx = crate::fonts::test_context();
|
||||
let mut app = flat_app("fusion", mode);
|
||||
let mut t = 0.0;
|
||||
@@ -1172,7 +1172,7 @@ fn fit_and_home_frame_the_flat_pattern() {
|
||||
}
|
||||
|
||||
fn perforated_flat() -> (SolveApp, Dialog) {
|
||||
let mut app = plate(crate::shell::LayoutMode::Floating);
|
||||
let mut app = plate(crate::ui_kit::LayoutMode::Floating);
|
||||
app.run("sheet.flange", json!({"sketch": "Base"})).unwrap();
|
||||
app.run("sheet.perforate", json!({"diameter": 3, "pitch": 6})).unwrap();
|
||||
let flat = app.session.execute("sheet.flat_pattern", &json!({})).unwrap();
|
||||
@@ -1251,7 +1251,7 @@ fn flat_perforations_cull_offscreen_and_subpixel_holes() {
|
||||
#[test]
|
||||
fn hem_handle_runs_along_the_hem_to_its_tip() {
|
||||
for flipped in [false, true] {
|
||||
let mut app = plate(crate::shell::LayoutMode::Floating);
|
||||
let mut app = plate(crate::ui_kit::LayoutMode::Floating);
|
||||
app.run("sheet.flange", json!({"sketch": "Base"})).unwrap();
|
||||
let body = app.session.model.state().sheets[0].body.clone();
|
||||
app.session.selection = vec![edge_sel(&app.session, Vec3::new(50.0, 0.0, 2.5)).unwrap()];
|
||||
@@ -1296,7 +1296,7 @@ fn inch_sheet_rule_edit_preserves_stored_lengths_and_dimensionless_k_factor() {
|
||||
|
||||
#[test]
|
||||
fn perforate_toolbar_opens_without_running_empty_parameters() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
for mode in [crate::ui_kit::LayoutMode::Floating, crate::ui_kit::LayoutMode::Docked] {
|
||||
let mut app = plate(mode);
|
||||
app.run("sheet.flange", json!({"sketch": "Base"})).unwrap();
|
||||
let features = app.session.doc.features.len();
|
||||
@@ -1328,7 +1328,7 @@ fn perforate_toolbar_opens_without_running_empty_parameters() {
|
||||
#[test]
|
||||
fn perforate_ok_targets_the_picked_sheet_and_updates_flat_data() {
|
||||
for face in [false, true] {
|
||||
let mut app = plate(crate::shell::LayoutMode::Docked);
|
||||
let mut app = plate(crate::ui_kit::LayoutMode::Docked);
|
||||
app.run("sheet.flange", json!({"sketch": "Base"})).unwrap();
|
||||
let body = app.session.model.state().sheets[0].body.clone();
|
||||
app.session.selection =
|
||||
@@ -1349,7 +1349,7 @@ fn perforate_ok_targets_the_picked_sheet_and_updates_flat_data() {
|
||||
|
||||
#[test]
|
||||
fn perforate_edit_restores_dimensions_pattern_and_target() {
|
||||
let mut app = plate(crate::shell::LayoutMode::Docked);
|
||||
let mut app = plate(crate::ui_kit::LayoutMode::Docked);
|
||||
app.run("sheet.flange", json!({"sketch": "Base"})).unwrap();
|
||||
let body = app.session.model.state().sheets[0].body.clone();
|
||||
app.run("sheet.perforate", json!({"body": body, "diameter": "4 mm", "pitch": "9 mm", "margin": "5 mm", "pattern": "square"})).unwrap();
|
||||
@@ -1366,7 +1366,7 @@ fn perforate_edit_restores_dimensions_pattern_and_target() {
|
||||
#[test]
|
||||
fn flat_pattern_navigation_slots_hide_orbit_controls_and_keep_fit_home() {
|
||||
let ctx = crate::fonts::test_context();
|
||||
let mut app = flat_app("fusion", crate::shell::LayoutMode::Floating);
|
||||
let mut app = flat_app("fusion", crate::ui_kit::LayoutMode::Floating);
|
||||
app.preferences.navigation_bar = serde_json::from_value(json!({
|
||||
"slots":["orbit","spin","look_at","projection","visual_style","pan","zoom","fit","home","display"]
|
||||
}))
|
||||
|
||||
@@ -174,7 +174,7 @@ fn equation_curve_window_gives_the_variable_its_ranges_unit() {
|
||||
*start = "a".into();
|
||||
*end = "3 * a".into();
|
||||
h.settle();
|
||||
let handle = format!("dialog:{}", crate::i18n::tr("ui.equation_variable_unit"));
|
||||
let handle = format!("dialog:{}", solidcraft_ui_text::tr("ui.equation_variable_unit"));
|
||||
h.step(&json!({"click": {"handle": handle}})).unwrap();
|
||||
h.settle();
|
||||
let Some(Kind::SketchModify(Mod::Equation { range_unit, .. })) = h.app.dialog.as_ref().map(|d| &d.kind) else { panic!("no equation window") };
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! Fair Surface, Match Surface and Split Face. Each has a live preview and Edit Feature.
|
||||
|
||||
use crate::dialogs::put;
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::ui_kit::{choice_row, expr_row, hint_row};
|
||||
use serde_json::{Map, Value, json};
|
||||
use solidcore_record::Vec3;
|
||||
@@ -150,6 +151,38 @@ impl Surf {
|
||||
}
|
||||
}
|
||||
|
||||
/// The one control that chooses a continuity order, wherever one is chosen: a dropdown of
|
||||
/// `allowed` (what the construction builds, `Construction::offered`). Its labels are the orders'
|
||||
/// plain words and its hover text a line per order naming its code, both from the kernel's one
|
||||
/// table of orders; a value not in `allowed` shows the first. Tokens: `row, dropdown`.
|
||||
pub(crate) fn continuity_row(ui: &mut egui::Ui, label: &str, id: &str, value: &mut Continuity, allowed: &[Continuity]) -> egui::Response {
|
||||
continuity_row_explained(ui, label, id, value, allowed, &continuity_tip(allowed))
|
||||
}
|
||||
|
||||
/// [`continuity_row`] whose hover text says what each order builds there (`tip`, which names
|
||||
/// each order's code), for a choice whose orders stand for constructions (a fillet's corners).
|
||||
pub(crate) fn continuity_row_explained(
|
||||
ui: &mut egui::Ui,
|
||||
label: &str,
|
||||
id: &str,
|
||||
value: &mut Continuity,
|
||||
allowed: &[Continuity],
|
||||
tip: &str,
|
||||
) -> egui::Response {
|
||||
let labels: Vec<&str> = allowed.iter().map(|g| g.label_id()).collect();
|
||||
let mut index = allowed.iter().position(|g| g == value).unwrap_or(0);
|
||||
let response = choice_row(ui, label, id, &mut index, &labels);
|
||||
if let Some(g) = allowed.get(index) {
|
||||
*value = *g;
|
||||
}
|
||||
response.backing_on_hover_text(tip)
|
||||
}
|
||||
|
||||
/// The hover text of a continuity choice: each order's own line, which names its code.
|
||||
pub(crate) fn continuity_tip(allowed: &[Continuity]) -> String {
|
||||
allowed.iter().map(|g| solidcraft_ui_text::tr(g.tip_id())).collect::<Vec<_>>().join("\n")
|
||||
}
|
||||
|
||||
/// What each dialog's continuity rows offer: the orders its construction builds, from the
|
||||
/// kernel's table, so an order a construction learns shows up by itself.
|
||||
fn patch_orders() -> Vec<Continuity> {
|
||||
@@ -160,7 +193,7 @@ fn patch_orders() -> Vec<Continuity> {
|
||||
/// faces meet each other with different curvatures (tangent only: no surface smooth at the corner
|
||||
/// has both curvatures there).
|
||||
fn patch_continuity_tip() -> String {
|
||||
format!("{}\n{}", ui_kit::continuity_tip(&patch_orders()), crate::i18n::tr("ui.patch_continuity_corner_tip"))
|
||||
format!("{}\n{}", continuity_tip(&patch_orders()), solidcraft_ui_text::tr("ui.patch_continuity_corner_tip"))
|
||||
}
|
||||
|
||||
fn blend_orders() -> Vec<Continuity> {
|
||||
@@ -262,14 +295,14 @@ pub fn rows(ui: &mut egui::Ui, k: &mut Surf) -> bool {
|
||||
form_row(ui, "ui.fair_iterations", |ui| {
|
||||
ui_kit::number_field(ui, iterations, 1..=1000, 1.0, "");
|
||||
});
|
||||
enter |= hint_row(ui, "Tolerance", tolerance, crate::i18n::tr_label("ui.fair_tolerance_default"));
|
||||
enter |= hint_row(ui, "Tolerance", tolerance, solidcraft_ui_text::tr_label("ui.fair_tolerance_default"));
|
||||
ui_kit::options(ui, "fair_options", &mut [("ui.fair_keep_edges", keep_edges)]);
|
||||
}
|
||||
Surf::Match { order } => {
|
||||
choice_row(ui, "Continuity", "match_order", order, &MATCH_CONTINUITY.map(|(label, _)| label));
|
||||
}
|
||||
Surf::Stitch { tolerance } => {
|
||||
enter |= hint_row(ui, "Tolerance", tolerance, crate::i18n::tr("ui.stitch_tolerance_hint"));
|
||||
enter |= hint_row(ui, "Tolerance", tolerance, solidcraft_ui_text::tr("ui.stitch_tolerance_hint"));
|
||||
}
|
||||
Surf::Thicken { thickness, symmetric, operation } => {
|
||||
enter |= expr_row(ui, "Thickness", thickness);
|
||||
@@ -319,23 +352,23 @@ pub fn rows(ui: &mut egui::Ui, k: &mut Surf) -> bool {
|
||||
ui_kit::options(ui, "untrim_options", &mut [("Restore outer boundary", outer), ("In place (surfaces)", in_place)]);
|
||||
}
|
||||
Surf::Patch { continuity } => {
|
||||
ui_kit::continuity_row_explained(ui, "Continuity", "pa_cont", continuity, &patch_orders(), &patch_continuity_tip());
|
||||
continuity_row_explained(ui, "Continuity", "pa_cont", continuity, &patch_orders(), &patch_continuity_tip());
|
||||
}
|
||||
Surf::Blend { continuity, bulge, stitch } => {
|
||||
let [from, to] = continuity;
|
||||
let [from_bulge, to_bulge] = bulge;
|
||||
ui_kit::continuity_row(ui, "Start continuity", "bl_cont0", from, &blend_orders());
|
||||
continuity_row(ui, "Start continuity", "bl_cont0", from, &blend_orders());
|
||||
form_row(ui, "Start bulge", |ui| {
|
||||
ui_kit::scalar_field(ui, from_bulge, -0.9..=3.0);
|
||||
});
|
||||
ui_kit::continuity_row(ui, "End continuity", "bl_cont1", to, &blend_orders());
|
||||
continuity_row(ui, "End continuity", "bl_cont1", to, &blend_orders());
|
||||
form_row(ui, "End bulge", |ui| {
|
||||
ui_kit::scalar_field(ui, to_bulge, -0.9..=3.0);
|
||||
});
|
||||
ui_kit::options(ui, "blend_options", &mut [("Stitch to both bodies", stitch)]);
|
||||
}
|
||||
Surf::Boundary { continuity, join } => {
|
||||
ui_kit::continuity_row(ui, "Continuity", "bo_cont", continuity, &boundary_orders());
|
||||
continuity_row(ui, "Continuity", "bo_cont", continuity, &boundary_orders());
|
||||
form_row(ui, "Operation", |ui| {
|
||||
let mut op = usize::from(*join);
|
||||
combo(ui, "bo_op", &BOUNDARY_OPS, &mut op);
|
||||
@@ -1182,7 +1215,7 @@ mod tests {
|
||||
}
|
||||
assert_eq!(blend_orders().last(), Some(&Continuity::G3), "a blend builds up to G3 today");
|
||||
for lang in ["en", "ar", "he", "ja", "zh", "de", "ko"] {
|
||||
let locale = crate::i18n::Locale::for_language(lang);
|
||||
let locale = solidcraft_ui_text::Locale::for_language(lang);
|
||||
for g in Continuity::ALL {
|
||||
let (label, tip) = (locale.text(g.label_id()), locale.text(g.tip_id()));
|
||||
assert!(label != g.label_id() && tip != g.tip_id(), "{lang}: {g:?} has no text");
|
||||
@@ -1190,7 +1223,7 @@ mod tests {
|
||||
assert!(tip.contains(g.code()), "{lang}: the {g:?} hover text names its code: {tip}");
|
||||
}
|
||||
}
|
||||
assert_eq!(ui_kit::continuity_tip(&blend_orders()).lines().count(), 4);
|
||||
assert_eq!(continuity_tip(&blend_orders()).lines().count(), 4);
|
||||
// The patch's hover text adds what it does at a corner whose faces' curvatures differ.
|
||||
let tip = patch_continuity_tip();
|
||||
assert_eq!(tip.lines().count(), patch_orders().len() + 1, "{tip}");
|
||||
|
||||
@@ -13,9 +13,9 @@ use solidcraft_engine::doc::{
|
||||
use solidcraft_engine::{Sel, Session};
|
||||
|
||||
use crate::dialogs::{Dialog, Kind, combo, edge_sel, face_sel, form_row, object_point, plane_sel, put, set_object_accepting};
|
||||
use crate::i18n::tr;
|
||||
use crate::selection::{Accept, EDGES, FACES, PLANES, SelInput};
|
||||
use crate::ui_kit::{choice_row, expr_row, hint_row, text_row};
|
||||
use solidcraft_ui_text::tr;
|
||||
|
||||
/// Common metric pitch presets besides the coarse one, not an exhaustive catalogue of standard
|
||||
/// diameter/pitch pairs; the current pitch is included separately, custom pairs remain editable.
|
||||
@@ -179,7 +179,7 @@ pub(crate) fn hole_rows(
|
||||
(false, true) => "thread.drill_range",
|
||||
(false, false) => "thread.drill_range_custom",
|
||||
};
|
||||
let tip = crate::i18n::format(id, &args);
|
||||
let tip = solidcraft_ui_text::format(id, &args);
|
||||
let mut text = drill.clone().unwrap_or_else(|| default.clone());
|
||||
let (submitted, changed) = crate::ui_kit::text_field_tip(ui, &mut text, &default, &tip);
|
||||
if changed {
|
||||
@@ -434,7 +434,7 @@ pub(crate) fn rows(
|
||||
if !f.sizes().is_empty() {
|
||||
size_rows(ui, f, designation, suggested);
|
||||
}
|
||||
let hint = crate::i18n::format("thread.fits_the_face", &[("example", &example(f))]);
|
||||
let hint = solidcraft_ui_text::format("thread.fits_the_face", &[("example", &example(f))]);
|
||||
let (submitted, changed) = text_row(ui, "Designation", designation, &hint);
|
||||
enter |= submitted;
|
||||
if let Some(at) = crate::scenario::handle_at("dialog:Designation") {
|
||||
|
||||
@@ -127,10 +127,10 @@ pub(crate) fn stop_text(v: &Value) -> Option<String> {
|
||||
// The values come from the engine: a missing key is an empty name, never a panic.
|
||||
let name = |m: &serde_json::Map<String, Value>, key: &str| m.get(key).and_then(Value::as_str).unwrap_or_default().to_string();
|
||||
if let Some(l) = v["stopped_by_loop"].as_object() {
|
||||
return Some(crate::i18n::format("trace.stopped_by_loop", &[("sample", &at), ("joint", &name(l, "joint"))]));
|
||||
return Some(solidcraft_ui_text::format("trace.stopped_by_loop", &[("sample", &at), ("joint", &name(l, "joint"))]));
|
||||
}
|
||||
let c = v["stopped_by_contact"].as_object()?;
|
||||
Some(crate::i18n::format("trace.stopped_by_contact", &[("sample", &at), ("a", &name(c, "a")), ("b", &name(c, "b"))]))
|
||||
Some(solidcraft_ui_text::format("trace.stopped_by_contact", &[("sample", &at), ("a", &name(c, "a")), ("b", &name(c, "b"))]))
|
||||
}
|
||||
|
||||
/// The dialog's rows; true when Enter was pressed in a value.
|
||||
@@ -191,9 +191,9 @@ pub fn rows(app: &mut crate::SolveApp, ui: &mut egui::Ui, f: &mut TraceForm, inp
|
||||
let (text, tone) = match r {
|
||||
Ok(v) => match stop_text(v) {
|
||||
Some(why) => (why, crate::ui_kit::StatusTone::Warning),
|
||||
None => (crate::i18n::format("trace.points", &[("count", &v["samples"].to_string())]), crate::ui_kit::StatusTone::Dim),
|
||||
None => (solidcraft_ui_text::format("trace.points", &[("count", &v["samples"].to_string())]), crate::ui_kit::StatusTone::Dim),
|
||||
},
|
||||
Err(e) => (crate::i18n::tr_label(e).to_string(), crate::ui_kit::StatusTone::Dim),
|
||||
Err(e) => (solidcraft_ui_text::tr_label(e).to_string(), crate::ui_kit::StatusTone::Dim),
|
||||
};
|
||||
crate::ui_kit::status_line(ui, &text, tone);
|
||||
}
|
||||
@@ -235,15 +235,15 @@ mod shared_status_baseline_tests {
|
||||
}
|
||||
assert_eq!(
|
||||
stop_text(&json!({"stopped_at":3,"stopped_by_loop":{"joint":"Hinge"},"stopped_by_contact":{"a":"A","b":"B"}})),
|
||||
Some(crate::i18n::format("trace.stopped_by_loop", &[("sample", "3"), ("joint", "Hinge")]))
|
||||
Some(solidcraft_ui_text::format("trace.stopped_by_loop", &[("sample", "3"), ("joint", "Hinge")]))
|
||||
);
|
||||
assert_eq!(
|
||||
stop_text(&json!({"stopped_at":4,"stopped_by_contact":{"a":"A","b":"B"}})),
|
||||
Some(crate::i18n::format("trace.stopped_by_contact", &[("sample", "4"), ("a", "A"), ("b", "B")]))
|
||||
Some(solidcraft_ui_text::format("trace.stopped_by_contact", &[("sample", "4"), ("a", "A"), ("b", "B")]))
|
||||
);
|
||||
assert_eq!(
|
||||
stop_text(&json!({"stopped_at":0,"stopped_by_contact":{"a":1,"b":null}})),
|
||||
Some(crate::i18n::format("trace.stopped_by_contact", &[("sample", "0"), ("a", ""), ("b", "")]))
|
||||
Some(solidcraft_ui_text::format("trace.stopped_by_contact", &[("sample", "0"), ("a", ""), ("b", "")]))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ pub(crate) fn progress_text(work: Option<(&str, usize, usize)>) -> String {
|
||||
"dxf_write" => "dxf.progress.write",
|
||||
_ => "dxf.progress.meshes",
|
||||
};
|
||||
format!("{}: {done}/{total}", crate::i18n::tr_label(label))
|
||||
format!("{}: {done}/{total}", solidcraft_ui_text::tr_label(label))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -163,7 +163,7 @@ impl DimensionDraft {
|
||||
/// of its id, and without the `value:` field tag, since the box is the value
|
||||
/// ("Sketch Dimension: `mm` is a unit name, so it cannot be a parameter name").
|
||||
fn shown_error(id: &str, e: &str) -> String {
|
||||
let label = crate::i18n::tr_label(crate::command_label(id));
|
||||
let label = solidcraft_ui_text::tr_label(crate::command_label(id));
|
||||
crate::friendly_error(id, e).replacen(&format!("{label}: value: "), &format!("{label}: "), 1)
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ pub fn begin_dimension(app: &mut SolveApp, entities: Vec<String>, at: Vec2) -> R
|
||||
ellipse_as_before(draft.half_axes, &mut params);
|
||||
AXIS.with(|axis| *axis.borrow_mut() = None);
|
||||
app.run("sketch.dimension", params)?;
|
||||
app.set_status(crate::i18n::tr("sketch.dimension_added_driven"), false);
|
||||
app.set_status(solidcraft_ui_text::tr("sketch.dimension_added_driven"), false);
|
||||
return Ok(());
|
||||
}
|
||||
EDIT.with(|edit| {
|
||||
@@ -753,7 +753,7 @@ fn label_marked(k: &ConstraintKind, radius_mark: bool, expr: Option<&str>, drive
|
||||
let plain = expr.is_none_or(|e| {
|
||||
let e = e.trim();
|
||||
let e = ["mm", "cm", "in", "ft", "m", "deg", "rad", "°"].iter().find_map(|unit| e.strip_suffix(unit)).unwrap_or(e);
|
||||
crate::prefs::parse_number(e).is_some()
|
||||
crate::ui_kit::parse_number(e).is_some()
|
||||
});
|
||||
let text = if plain { num } else { format!("fx: {num}") };
|
||||
if driven { format!("({text})") } else { text }
|
||||
@@ -1166,8 +1166,8 @@ fn edit_box(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
Container::show_backing(ui, frame, |ui| {
|
||||
crate::ui_kit::scope(ui, |ui| {
|
||||
ui.set_max_width(popup_width);
|
||||
crate::ui_kit::text(ui, &crate::i18n::format("dialog.dimension_name", &[("name", &e.param)]), crate::ui_kit::Text::Inline)
|
||||
.backing_on_hover_text(crate::i18n::tr("dialog.dimension_insert_hint"));
|
||||
crate::ui_kit::text(ui, &solidcraft_ui_text::format("dialog.dimension_name", &[("name", &e.param)]), crate::ui_kit::Text::Inline)
|
||||
.backing_on_hover_text(solidcraft_ui_text::tr("dialog.dimension_insert_hint"));
|
||||
if !e.owned {
|
||||
// The whole value is selected before the field reads its state, every frame
|
||||
// until the person takes it over, so typing replaces it (#11204). Only on the
|
||||
@@ -1266,9 +1266,9 @@ fn settle(app: &mut SolveApp, mut e: Edit, done: Option<bool>, escaped: bool) {
|
||||
// The sentence comes from the catalog, so it reads in the interface language (#8863).
|
||||
let named = reply.get("renamed").and_then(|r| r.get("to")).and_then(serde_json::Value::as_str);
|
||||
let status = match (reply.get("defined").and_then(serde_json::Value::as_str), named) {
|
||||
(Some(name), _) => crate::i18n::format("sketch.dimension_applied_defined", &[("name", name)]),
|
||||
(None, Some(name)) => crate::i18n::format("sketch.dimension_applied_renamed", &[("name", name)]),
|
||||
(None, None) => crate::i18n::tr("sketch.dimension_applied").to_string(),
|
||||
(Some(name), _) => solidcraft_ui_text::format("sketch.dimension_applied_defined", &[("name", name)]),
|
||||
(None, Some(name)) => solidcraft_ui_text::format("sketch.dimension_applied_renamed", &[("name", name)]),
|
||||
(None, None) => solidcraft_ui_text::tr("sketch.dimension_applied").to_string(),
|
||||
};
|
||||
app.set_status(status, false);
|
||||
}
|
||||
|
||||
@@ -229,17 +229,17 @@ pub(super) fn show(app: &mut SolveApp, ctx: &egui::Context, e: &mut Edit, at: Po
|
||||
ui.set_width(width);
|
||||
// The parameter's name, then the check and the cross.
|
||||
let (title, _) = ui.allocate_exact_size(egui::vec2(width, t.control_height), egui::Sense::hover());
|
||||
let name = crate::i18n::format("dialog.dimension_name", &[("name", &e.param)]);
|
||||
let name = solidcraft_ui_text::format("dialog.dimension_name", &[("name", &e.param)]);
|
||||
ui.scope_builder(egui::UiBuilder::new().max_rect(title).layout(egui::Layout::left_to_right(egui::Align::Center)), |ui| {
|
||||
crate::ui_kit::text(ui, &name, crate::ui_kit::Text::Inline).on_hover_text(crate::i18n::tr("dialog.dimension_insert_hint"));
|
||||
crate::ui_kit::text(ui, &name, crate::ui_kit::Text::Inline).on_hover_text(solidcraft_ui_text::tr("dialog.dimension_insert_hint"));
|
||||
});
|
||||
let side = t.control_height;
|
||||
let cancel = Rect::from_min_size(egui::pos2(title.right() - side, title.top()), egui::vec2(side, side));
|
||||
let ok = cancel.translate(egui::vec2(-(side + t.space_2), 0.0));
|
||||
if button(ui, ok, "dimwin:ok", crate::i18n::tr("ui.heading_ok"), "finish") {
|
||||
if button(ui, ok, "dimwin:ok", solidcraft_ui_text::tr("ui.heading_ok"), "finish") {
|
||||
answer = Some(true);
|
||||
}
|
||||
if button(ui, cancel, "dimwin:cancel", crate::i18n::tr("ui.cancel"), "close") {
|
||||
if button(ui, cancel, "dimwin:cancel", solidcraft_ui_text::tr("ui.cancel"), "close") {
|
||||
answer = Some(false);
|
||||
escaped = true;
|
||||
}
|
||||
@@ -261,9 +261,9 @@ pub(super) fn show(app: &mut SolveApp, ctx: &egui::Context, e: &mut Edit, at: Po
|
||||
let can_step = step(&e.expr, 1.0).is_some();
|
||||
let tip = |id: &str| {
|
||||
if can_step {
|
||||
crate::i18n::format(id, &[("step", &step_text)])
|
||||
solidcraft_ui_text::format(id, &[("step", &step_text)])
|
||||
} else {
|
||||
crate::i18n::tr("dialog.dimension_step_expression").to_string()
|
||||
solidcraft_ui_text::tr("dialog.dimension_step_expression").to_string()
|
||||
}
|
||||
};
|
||||
let up = crate::ui_kit::compact_button(ui, crate::ui_kit::MOVE_UP, &tip("dialog.dimension_step_up"), can_step, false);
|
||||
|
||||
@@ -394,7 +394,7 @@ fn the_sketch_page_switches_dimension_values_to_a_window() {
|
||||
let mut h = Harness::new();
|
||||
h.call("ui.prefs", json!({"open": "Sketch"}));
|
||||
h.frames(4);
|
||||
let handle = format!("dialog:{}:{}", crate::i18n::tr("prefs.dimension_values"), crate::i18n::tr("prefs.dimension_values_window"));
|
||||
let handle = format!("dialog:{}:{}", solidcraft_ui_text::tr("prefs.dimension_values"), solidcraft_ui_text::tr("prefs.dimension_values_window"));
|
||||
crate::prefs::reveal_preference(&mut h, &handle);
|
||||
h.step(&json!({"click":{"handle": handle}})).unwrap();
|
||||
h.frames(2);
|
||||
@@ -1970,8 +1970,8 @@ fn editing_a_dimension_to_a_definition_names_its_parameter() {
|
||||
/// the parameter name kept as typed.
|
||||
#[test]
|
||||
fn the_status_after_a_dimension_is_in_the_interface_language() {
|
||||
let pl = crate::i18n::Locale::for_language("pl");
|
||||
let en = crate::i18n::Locale::for_language("en");
|
||||
let pl = solidcraft_ui_text::Locale::for_language("pl");
|
||||
let en = solidcraft_ui_text::Locale::for_language("en");
|
||||
let status = |h: &Harness| h.app.status.as_ref().map(|s| (s.0.clone(), s.2));
|
||||
let polish = |mut h: Harness| {
|
||||
h.app.preferences.language = "pl".into();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
//! Closing a design with unsaved changes asks first, and so does quitting: one "save changes?"
|
||||
//! per design with unsaved changes ([`request_quit`]).
|
||||
|
||||
use crate::i18n::LocaleNames as _;
|
||||
use crate::ui_kit::BackingTooltipExt as _;
|
||||
use crate::ui_kit::{Container, Surface};
|
||||
use egui::{FontId, Rect, Sense, pos2, vec2};
|
||||
@@ -874,8 +875,8 @@ fn tab_paths(app: &SolveApp) -> Vec<Option<String>> {
|
||||
/// the full name above when the tab cut it short (#5868), and below, that a saved design has
|
||||
/// changes not saved yet (`dirty`), which the bullet after a cut name no longer shows (#11408).
|
||||
fn tab_tooltip(name: &str, path: Option<&str>, cut: bool, dirty: bool) -> String {
|
||||
let place = path.map_or_else(|| crate::i18n::tr_label("Not saved yet").to_string(), crate::home::friendly_path);
|
||||
let place = if dirty && path.is_some() { format!("{place}\n{}", crate::i18n::tr("document.tab_unsaved_changes")) } else { place };
|
||||
let place = path.map_or_else(|| solidcraft_ui_text::tr_label("Not saved yet").to_string(), crate::home::friendly_path);
|
||||
let place = if dirty && path.is_some() { format!("{place}\n{}", solidcraft_ui_text::tr("document.tab_unsaved_changes")) } else { place };
|
||||
if cut { format!("{name}\n{place}") } else { place }
|
||||
}
|
||||
|
||||
@@ -996,7 +997,7 @@ pub fn tabs(app: &mut SolveApp, ui: &mut egui::Ui, r: Rect, x0: f32, x1: f32) {
|
||||
.map(|(i, (name, dirty))| {
|
||||
let shown_name = match &typed {
|
||||
Some(text) if i == selected => text.clone(),
|
||||
_ => crate::i18n::Locale::for_language(&app.preferences.language).given_name(name),
|
||||
_ => solidcraft_ui_text::Locale::for_language(&app.preferences.language).given_name(name),
|
||||
};
|
||||
let label = format!("{shown_name}{}", if *dirty { " •" } else { "" });
|
||||
let text = ui.painter().layout_no_wrap(label, crate::fonts::semibold(crate::theme::Tokens::get().font_button), t.app_bar_text).size().x;
|
||||
@@ -1084,7 +1085,7 @@ pub fn tabs(app: &mut SolveApp, ui: &mut egui::Ui, r: Rect, x0: f32, x1: f32) {
|
||||
let lit = resp.hovered() || held.is_some_and(|(from, _)| from == i);
|
||||
tab_shape(ui.painter(), &t, tr, active, lit || resp.has_focus());
|
||||
let ink = if active || lit { t.app_bar_text } else { t.app_bar_text.gamma_multiply(0.7) };
|
||||
let name_display = crate::i18n::Locale::for_language(&app.preferences.language).given_name(name);
|
||||
let name_display = solidcraft_ui_text::Locale::for_language(&app.preferences.language).given_name(name);
|
||||
let label = format!("{name_display}{}", if *dirty { " •" } else { "" });
|
||||
let font = if active {
|
||||
crate::fonts::semibold(crate::theme::Tokens::get().font_button)
|
||||
@@ -1125,7 +1126,7 @@ pub fn tabs(app: &mut SolveApp, ui: &mut egui::Ui, r: Rect, x0: f32, x1: f32) {
|
||||
menu = Some((i, ui.ctx().pointer_interact_pos().unwrap_or(tr.left_bottom())));
|
||||
}
|
||||
// Named apart from a dialog's Close: egui 0.37 names an unnamed widget after its tooltip.
|
||||
xresp.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, show_close, crate::i18n::tr("ui.close_tab")));
|
||||
xresp.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, show_close, solidcraft_ui_text::tr("ui.close_tab")));
|
||||
if xresp.backing_on_hover_text("Close").clicked() {
|
||||
shut = Some(i);
|
||||
} else if (if held.is_some() { resp } else { resp.backing_on_hover_text(tip) }).clicked() {
|
||||
@@ -1155,7 +1156,7 @@ pub fn tabs(app: &mut SolveApp, ui: &mut egui::Ui, r: Rect, x0: f32, x1: f32) {
|
||||
t.app_bar_text,
|
||||
);
|
||||
let id = egui::Id::unique("sc_doc_more");
|
||||
if mresp.clone().backing_on_hover_text(crate::i18n::tr_label("All open designs")).clicked() {
|
||||
if mresp.clone().backing_on_hover_text(solidcraft_ui_text::tr_label("All open designs")).clicked() {
|
||||
ui.ctx().data_mut(|d| d.insert_temp(id, !d.get_temp::<bool>(id).unwrap_or(false)));
|
||||
}
|
||||
if ui.ctx().data(|d| d.get_temp::<bool>(id).unwrap_or(false)) {
|
||||
@@ -1189,7 +1190,7 @@ pub fn tabs(app: &mut SolveApp, ui: &mut egui::Ui, r: Rect, x0: f32, x1: f32) {
|
||||
crate::context_menu::menu_frame(ui, |ui| {
|
||||
let mut chosen = None;
|
||||
for (id, label) in items {
|
||||
let mut item = crate::context_menu::Item::action(id, label, "");
|
||||
let mut item = crate::ui_kit::Item::action(id, label, "");
|
||||
if id == "ui.closeTab" && i == app.docs.active {
|
||||
item = item.key("Ctrl+W");
|
||||
}
|
||||
@@ -1290,7 +1291,7 @@ fn renaming_tab(app: &SolveApp) -> bool {
|
||||
|
||||
/// Starts editing the selected design's name in its tab, with the name as shown selected.
|
||||
fn start_tab_rename(app: &mut SolveApp, name: &str, tab: Rect) {
|
||||
let text = crate::i18n::Locale::for_language(&app.preferences.language).given_name(name);
|
||||
let text = solidcraft_ui_text::Locale::for_language(&app.preferences.language).given_name(name);
|
||||
app.menu.rename = Some(crate::context_menu::Rename {
|
||||
what: crate::context_menu::RenameWhat::Component(0),
|
||||
text,
|
||||
@@ -1363,7 +1364,7 @@ pub enum Answer {
|
||||
|
||||
/// The "save changes?" prompt for a tab being closed, or for each unsaved design when quitting.
|
||||
pub fn prompt(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let _language = crate::i18n::scope(&app.preferences.language);
|
||||
let _language = solidcraft_ui_text::scope(&app.preferences.language);
|
||||
quit_step(app);
|
||||
// Saved, it asks the Capture Position question first (#9736): that question is the one to
|
||||
// answer meanwhile, not this one again.
|
||||
@@ -1397,10 +1398,10 @@ pub fn prompt(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
if enter {
|
||||
answer = Some(choice);
|
||||
}
|
||||
let title = crate::i18n::format("document.save_changes", &[("name", &name_display)]);
|
||||
let title = solidcraft_ui_text::format("document.save_changes", &[("name", &name_display)]);
|
||||
let (closed, action) = ui_kit::Confirm::new(egui::Id::unique("sc_close_prompt"), "status_warning", &title)
|
||||
.tone(ui_kit::Tone::Warning)
|
||||
.actions(crate::i18n::tr("ui.save"), crate::i18n::tr("ui.cancel"), Some(crate::i18n::tr("ui.don_t_save")))
|
||||
.actions(solidcraft_ui_text::tr("ui.save"), solidcraft_ui_text::tr("ui.cancel"), Some(solidcraft_ui_text::tr("ui.don_t_save")))
|
||||
.secondary_left()
|
||||
.selected(match choice {
|
||||
Answer::Save => ui_kit::Action::Ok,
|
||||
@@ -1408,7 +1409,11 @@ pub fn prompt(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
Answer::Cancel => ui_kit::Action::Cancel,
|
||||
})
|
||||
.show(ctx, true, |ui| {
|
||||
ui_kit::text(ui, &crate::i18n::format("document.unsaved_changes", &[("name", &name_display)]), ui_kit::Text::Report("unsaved", false));
|
||||
ui_kit::text(
|
||||
ui,
|
||||
&solidcraft_ui_text::format("document.unsaved_changes", &[("name", &name_display)]),
|
||||
ui_kit::Text::Report("unsaved", false),
|
||||
);
|
||||
});
|
||||
answer = match action {
|
||||
ui_kit::Action::Ok => Some(Answer::Save),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user