Skip to content

Commit c83b759

Browse files
committed
fix wasm version
1 parent 3ef94a9 commit c83b759

5 files changed

Lines changed: 192 additions & 29 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/target
2+
/dist

Cargo.lock

Lines changed: 13 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ description = "A training application for fitting curve parameters to a set of p
88
repository = "https://github.com/hexqnt/curve-fit"
99
homepage = "https://curve-fit.hexq.ru"
1010
readme = "README.md"
11+
keywords = ["curve-fitting", "spline", "optimization", "egui", "wasm"]
12+
categories = ["science", "mathematics", "visualization", "gui"]
1113

1214
[dependencies]
1315
argmin = { version = "0.11.0", default-features = false }
@@ -20,16 +22,17 @@ egui_extras = { version = "0.33.3", default-features = false, features = [
2022
] }
2123
egui_plot = "0.34.1"
2224

25+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
26+
sys-locale = "0.3.2"
27+
2328
[target.'cfg(target_arch = "wasm32")'.dependencies]
24-
getrandom = { version = "0.4.2", features = ["wasm_js"] }
25-
getrandom_03 = { package = "getrandom", version = "0.4.2", features = [
26-
"wasm_js",
27-
] }
29+
getrandom = { version = "0.3", features = ["wasm_js"] }
2830
wasm-bindgen = "0.2.114"
2931
wasm-bindgen-futures = "0.4.54"
3032
web-sys = { version = "0.3.91", features = [
3133
"Document",
3234
"Element",
3335
"HtmlCanvasElement",
36+
"Navigator",
3437
"Window",
3538
] }

src/app.rs

Lines changed: 146 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ mod param_init;
1313
mod plot_utils;
1414
mod points_text;
1515

16-
use self::formula::{formula_svg_bytes, formula_svg_uri, model_formula_info};
16+
#[cfg(target_arch = "wasm32")]
17+
use self::formula::formula_plain_text;
18+
use self::formula::model_formula_info;
19+
#[cfg(not(target_arch = "wasm32"))]
20+
use self::formula::{formula_svg_bytes, formula_svg_uri};
1721
use self::i18n::{
1822
center_origin_icon_image, clear_icon_image, family_label, fit_icon_image,
1923
fit_to_content_icon_image, github_mark_image, language_flag_image, model_choice_label,
@@ -80,6 +84,37 @@ impl UiLanguage {
8084
Self::Russian => "Русский",
8185
}
8286
}
87+
88+
fn from_locale_tag(locale: &str) -> Self {
89+
let language = locale
90+
.trim()
91+
.split(['-', '_', '.', '@', ':', ','])
92+
.next()
93+
.unwrap_or_default();
94+
95+
if language.eq_ignore_ascii_case("ru") {
96+
Self::Russian
97+
} else {
98+
Self::English
99+
}
100+
}
101+
102+
fn from_system_locale() -> Self {
103+
system_locale_tag()
104+
.as_deref()
105+
.map(Self::from_locale_tag)
106+
.unwrap_or(Self::English)
107+
}
108+
}
109+
110+
#[cfg(not(target_arch = "wasm32"))]
111+
fn system_locale_tag() -> Option<String> {
112+
sys_locale::get_locale()
113+
}
114+
115+
#[cfg(target_arch = "wasm32")]
116+
fn system_locale_tag() -> Option<String> {
117+
web_sys::window().and_then(|window| window.navigator().language())
83118
}
84119

85120
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
@@ -384,6 +419,7 @@ struct ModelFormulaInfo {
384419
notes: String,
385420
}
386421

422+
#[cfg(not(target_arch = "wasm32"))]
387423
#[derive(Debug, Clone)]
388424
struct FormulaSvgCache {
389425
formula: String,
@@ -657,6 +693,7 @@ pub struct CurveFitApp {
657693
result_metrics: Option<ExtendedMetrics>,
658694
residual_plot_points: Vec<PlotPoint>,
659695
spline_plot_curve: Option<Vec<PlotPoint>>,
696+
#[cfg(not(target_arch = "wasm32"))]
660697
formula_svg_cache: Option<FormulaSvgCache>,
661698
sampled_curve_cache: Option<SampledCurveCache>,
662699
iteration_diagnostics: IterationDiagnostics,
@@ -675,7 +712,10 @@ impl CurveFitApp {
675712
/// Создает приложение и настраивает загрузчики изображений для иконок/формул.
676713
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
677714
egui_extras::install_image_loaders(&cc.egui_ctx);
678-
Self::default()
715+
Self {
716+
ui_language: UiLanguage::from_system_locale(),
717+
..Self::default()
718+
}
679719
}
680720

681721
fn resolved_model(&self) -> ResolvedModel {
@@ -841,6 +881,7 @@ impl CurveFitApp {
841881
self.refresh_status_after_points_edit();
842882
}
843883

884+
#[cfg(not(target_arch = "wasm32"))]
844885
fn cached_formula_svg(&mut self, formula: &str, dark_mode: bool) -> (String, Arc<[u8]>) {
845886
if let Some(cache) = &self.formula_svg_cache
846887
&& cache.formula == formula
@@ -1111,10 +1152,56 @@ impl CurveFitApp {
11111152
}
11121153
}
11131154

1114-
fn write_points_text(&mut self, points: &[Point]) {
1115-
self.push_points_undo_snapshot(self.points_text.clone());
1116-
self.apply_points_text_change(points_to_text(points), false);
1117-
self.refresh_status_after_points_edit();
1155+
fn set_points_cache_from_valid_points(&mut self, points: &[Point]) {
1156+
let parsed_points = points.to_vec();
1157+
let plot_points = parsed_points
1158+
.iter()
1159+
.map(|point| PlotPoint::new(point.x(), point.y()))
1160+
.collect();
1161+
self.points_cache = Some(ParsedPointsCache {
1162+
parsed_points: Ok(parsed_points),
1163+
parse_error_line: None,
1164+
plot_points,
1165+
});
1166+
self.points_cache_dirty = false;
1167+
self.points_parse_debounce_deadline = None;
1168+
}
1169+
1170+
fn clear_points_text(&mut self, record_undo: bool) {
1171+
if self.points_text.is_empty() {
1172+
return;
1173+
}
1174+
let previous = std::mem::take(&mut self.points_text);
1175+
if record_undo {
1176+
self.push_points_undo_snapshot(previous);
1177+
}
1178+
self.points_redo_stack.clear();
1179+
self.set_points_cache_from_valid_points(&[]);
1180+
if matches!(
1181+
self.status.as_ref(),
1182+
Some(StatusMessage::Error(message)) if message.starts_with(POINTS_PARSE_ERROR_PREFIX)
1183+
) {
1184+
self.status = Some(self.idle_status_after_points_edit());
1185+
}
1186+
}
1187+
1188+
fn write_points_text(&mut self, points: &[Point], record_undo: bool) {
1189+
let new_text = points_to_text(points);
1190+
if self.points_text == new_text {
1191+
return;
1192+
}
1193+
if record_undo {
1194+
self.push_points_undo_snapshot(self.points_text.clone());
1195+
}
1196+
self.points_text = new_text;
1197+
self.points_redo_stack.clear();
1198+
self.set_points_cache_from_valid_points(points);
1199+
if matches!(
1200+
self.status.as_ref(),
1201+
Some(StatusMessage::Error(message)) if message.starts_with(POINTS_PARSE_ERROR_PREFIX)
1202+
) {
1203+
self.status = Some(self.idle_status_after_points_edit());
1204+
}
11181205
}
11191206

11201207
fn clear_fit_preview(&mut self) {
@@ -1812,7 +1899,7 @@ impl CurveFitApp {
18121899
match Point::try_new(x, y) {
18131900
Ok(point) => {
18141901
points.push(point);
1815-
self.write_points_text(&points);
1902+
self.write_points_text(&points, true);
18161903
}
18171904
Err(error) => {
18181905
self.status = Some(StatusMessage::Error(error.to_string()));
@@ -1844,7 +1931,7 @@ impl CurveFitApp {
18441931
}
18451932
}
18461933

1847-
self.write_points_text(&points);
1934+
self.write_points_text(&points, false);
18481935
}
18491936

18501937
fn erase_points_from_plot(
@@ -1872,7 +1959,7 @@ impl CurveFitApp {
18721959
dx * dx + dy * dy > 1.0
18731960
});
18741961

1875-
self.write_points_text(&points);
1962+
self.write_points_text(&points, false);
18761963
}
18771964

18781965
fn plot_position_from_screen(
@@ -1893,8 +1980,12 @@ impl CurveFitApp {
18931980
}
18941981

18951982
let response = &plot_response.response;
1983+
let is_continuous_tool = matches!(self.plot_tool, PlotTool::Spray | PlotTool::Eraser);
18961984
let primary_down_on_plot = response.is_pointer_button_down_on();
1897-
if matches!(self.plot_tool, PlotTool::Spray | PlotTool::Eraser) && primary_down_on_plot {
1985+
if is_continuous_tool && primary_down_on_plot {
1986+
if self.active_tool_bounds.is_none() {
1987+
self.push_points_undo_snapshot(self.points_text.clone());
1988+
}
18981989
self.active_tool_bounds
18991990
.get_or_insert(*plot_response.transform.bounds());
19001991
} else {
@@ -2180,8 +2271,7 @@ impl CurveFitApp {
21802271
)
21812272
.clicked()
21822273
{
2183-
self.push_points_undo_snapshot(self.points_text.clone());
2184-
self.apply_points_text_change(String::new(), false);
2274+
self.clear_points_text(true);
21852275
self.clear_fit_outputs();
21862276
self.status = Some(StatusMessage::Cleared);
21872277
}
@@ -2335,14 +2425,23 @@ impl CurveFitApp {
23352425
ui.add_space(6.0);
23362426
ui.group(|ui| {
23372427
ui.label(egui::RichText::new(tr(language, "Model Formula", "Формула модели")).strong());
2338-
let dark_mode = ui.visuals().dark_mode;
2339-
let (svg_uri, svg_bytes) =
2340-
self.cached_formula_svg(&formula_info.full_formula, dark_mode);
2341-
ui.add(
2342-
egui::Image::from_bytes(svg_uri, svg_bytes)
2343-
.max_width(ui.available_width())
2344-
.fit_to_original_size(1.0),
2345-
);
2428+
#[cfg(not(target_arch = "wasm32"))]
2429+
{
2430+
let dark_mode = ui.visuals().dark_mode;
2431+
let (svg_uri, svg_bytes) =
2432+
self.cached_formula_svg(&formula_info.full_formula, dark_mode);
2433+
ui.add(
2434+
egui::Image::from_bytes(svg_uri, svg_bytes)
2435+
.max_width(ui.available_width())
2436+
.fit_to_original_size(1.0),
2437+
);
2438+
}
2439+
#[cfg(target_arch = "wasm32")]
2440+
{
2441+
let plain_formula = formula_plain_text(&formula_info.full_formula);
2442+
let formula_label = egui::RichText::new(plain_formula).monospace();
2443+
ui.label(formula_label);
2444+
}
23462445
ui.label(egui::RichText::new(formula_info.notes).small());
23472446
});
23482447

@@ -3077,7 +3176,7 @@ impl Default for CurveFitApp {
30773176
lbfgs_preset: LbfgsPreset::infer_from_config(&default_lbfgs),
30783177
ui_language: UiLanguage::English,
30793178
plot_tool: PlotTool::SinglePoint,
3080-
spray_density: 8,
3179+
spray_density: 5,
30813180
spray_radius_rel: 0.02,
30823181
spray_brush: SprayBrush::Uniform,
30833182
eraser_radius_rel: 0.03,
@@ -3108,6 +3207,7 @@ impl Default for CurveFitApp {
31083207
result_metrics: None,
31093208
residual_plot_points: Vec::new(),
31103209
spline_plot_curve: None,
3210+
#[cfg(not(target_arch = "wasm32"))]
31113211
formula_svg_cache: None,
31123212
sampled_curve_cache: None,
31133213
iteration_diagnostics: IterationDiagnostics::default(),
@@ -3233,7 +3333,7 @@ fn diagnostics_plot_y_axis_width(plot_response: &PlotResponse<()>) -> f32 {
32333333
#[cfg(test)]
32343334
mod tests {
32353335
use super::{
3236-
CurveFitApp, IterationDiagnostics, ModelChoice, ParamInitMethod, StatusMessage,
3336+
CurveFitApp, IterationDiagnostics, ModelChoice, ParamInitMethod, StatusMessage, UiLanguage,
32373337
data_based_params_for_family,
32383338
};
32393339
use crate::domain::{CurveFamily, CurveParams, FitResult, Point, Points};
@@ -3267,6 +3367,30 @@ mod tests {
32673367
);
32683368
}
32693369

3370+
#[test]
3371+
fn ui_language_from_locale_tag_uses_russian_for_ru_tags() {
3372+
assert_eq!(UiLanguage::from_locale_tag("ru"), UiLanguage::Russian);
3373+
assert_eq!(UiLanguage::from_locale_tag("ru-RU"), UiLanguage::Russian);
3374+
assert_eq!(
3375+
UiLanguage::from_locale_tag("ru_RU.UTF-8"),
3376+
UiLanguage::Russian
3377+
);
3378+
assert_eq!(
3379+
UiLanguage::from_locale_tag("ru-RU,en-US;q=0.9"),
3380+
UiLanguage::Russian
3381+
);
3382+
}
3383+
3384+
#[test]
3385+
fn ui_language_from_locale_tag_uses_english_for_other_tags() {
3386+
assert_eq!(UiLanguage::from_locale_tag("en-US"), UiLanguage::English);
3387+
assert_eq!(
3388+
UiLanguage::from_locale_tag("de_DE.UTF-8"),
3389+
UiLanguage::English
3390+
);
3391+
assert_eq!(UiLanguage::from_locale_tag(""), UiLanguage::English);
3392+
}
3393+
32703394
#[test]
32713395
fn diagnostics_initialize_stores_iteration_zero_state() {
32723396
let points = line_points();

0 commit comments

Comments
 (0)