Skip to content

Commit 38a493c

Browse files
committed
annotations moved to (i)
1 parent 175ff2f commit 38a493c

8 files changed

Lines changed: 292 additions & 167 deletions

File tree

src/app.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ const SPLINE_AUTO_SAMPLES_PER_KNOT: usize = 30;
102102
const SPLINE_AUTO_SAMPLES_PER_POINT: usize = 3;
103103
const DIAGNOSTICS_PANEL_DEFAULT_HEIGHT: f32 = 230.0;
104104
const DIAGNOSTICS_PANEL_MIN_HEIGHT: f32 = 120.0;
105-
const LEFT_PANEL_DEFAULT_WIDTH: f32 = 350.0;
106-
const LEFT_PANEL_MIN_WIDTH: f32 = 350.0;
107-
const RIGHT_PANEL_DEFAULT_WIDTH: f32 = 300.0;
108-
const RIGHT_PANEL_MIN_WIDTH: f32 = 300.0;
105+
const LEFT_PANEL_DEFAULT_WIDTH: f32 = 320.0;
106+
const LEFT_PANEL_MIN_WIDTH: f32 = 320.0;
107+
const RIGHT_PANEL_DEFAULT_WIDTH: f32 = 280.0;
108+
const RIGHT_PANEL_MIN_WIDTH: f32 = 280.0;
109109
const POINTS_PARSE_DEBOUNCE_MS: u64 = 180;
110110
const POINTS_HISTORY_LIMIT: usize = 256;
111111
const POINTS_PARSE_ERROR_PREFIX: &str = "Points parse error: ";

src/app/ui.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,42 @@ impl CurveFitApp {
166166
.inner
167167
}
168168

169+
fn info_tooltip(ui: &mut egui::Ui, text: impl AsRef<str>) -> egui::Response {
170+
let lines = text
171+
.as_ref()
172+
.lines()
173+
.map(str::trim)
174+
.filter(|line| !line.is_empty())
175+
.collect::<Vec<_>>();
176+
let response = ui
177+
.add(
178+
egui::Label::new(
179+
egui::RichText::new("ℹ")
180+
.small()
181+
.color(ui.visuals().weak_text_color()),
182+
)
183+
.sense(egui::Sense::hover()),
184+
)
185+
.on_hover_cursor(egui::CursorIcon::Default);
186+
187+
response.on_hover_ui(|ui| {
188+
ui.set_max_width(380.0);
189+
ui.spacing_mut().item_spacing.y = 3.0;
190+
match lines.as_slice() {
191+
[] => {}
192+
[single] => {
193+
ui.label(*single);
194+
}
195+
[title, details @ ..] => {
196+
ui.label(egui::RichText::new(*title).strong());
197+
for line in details {
198+
ui.label(egui::RichText::new(*line).small());
199+
}
200+
}
201+
}
202+
})
203+
}
204+
169205
pub(super) fn next_unit_random(&mut self) -> f64 {
170206
self.spray_seed = self
171207
.spray_seed

src/app/ui/family_params.rs

Lines changed: 98 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,22 @@ pub(super) fn ui_family_and_params(app: &mut CurveFitApp, ui: &mut egui::Ui) {
6161
let formula_info = model_formula_info(language, app.selected_model, app.polynomial_degree);
6262
let plain_formula = formula_plain_text(&formula_info.full_formula);
6363
let formula_preview = formula_preview_text(&plain_formula, 78);
64+
let formula_help = format!(
65+
"{}\n{}\n{}\n{}\n{}",
66+
tr(language, "Model formula", "Формула модели"),
67+
tr(
68+
language,
69+
"- This card shows a shortened preview",
70+
"- В этой карточке показывается сокращенное превью",
71+
),
72+
tr(
73+
language,
74+
"- Use the fx button to open the full formula in a separate window",
75+
"- Используйте кнопку fx, чтобы открыть полную формулу в отдельном окне",
76+
),
77+
tr(language, "Model notes:", "Примечания по модели:",),
78+
formula_info.notes
79+
);
6480
ui.add_space(2.0);
6581
egui::Frame::new()
6682
.inner_margin(egui::Margin::symmetric(10, 8))
@@ -75,6 +91,7 @@ pub(super) fn ui_family_and_params(app: &mut CurveFitApp, ui: &mut egui::Ui) {
7591
ui.label(
7692
egui::RichText::new(tr(language, "Model Formula", "Формула модели")).strong(),
7793
);
94+
CurveFitApp::info_tooltip(ui, formula_help.as_str());
7895
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
7996
if ui
8097
.add(egui::Button::image(open_formula_icon_image(icon_tint)))
@@ -86,22 +103,14 @@ pub(super) fn ui_family_and_params(app: &mut CurveFitApp, ui: &mut egui::Ui) {
86103
});
87104
});
88105
ui.monospace(formula_preview.as_ref());
89-
ui.label(
90-
egui::RichText::new(tr(
91-
language,
92-
"Preview only. Open in a separate window to inspect long formulas.",
93-
"Показано превью. Откройте отдельное окно для длинных формул.",
94-
))
95-
.small(),
96-
);
97-
ui.label(egui::RichText::new(formula_info.notes).small());
98106
});
99107

100108
if let Some(family) = app.resolved_model().parametric_family() {
101109
let mut method_to_apply = None;
102110
let mut apply_fitted_init = false;
103111
ui.horizontal_wrapped(|ui| {
104112
ui.label(tr(language, "Initial parameters", "Начальные параметры"));
113+
CurveFitApp::info_tooltip(ui, parametric_init_hint(language));
105114
ui.add_enabled_ui(can_edit_params, |ui| {
106115
ui.menu_button(
107116
tr(language, "+ Initialize", "+ Инициализация"),
@@ -170,12 +179,6 @@ pub(super) fn ui_family_and_params(app: &mut CurveFitApp, ui: &mut egui::Ui) {
170179
}
171180
});
172181
} else {
173-
ui.label(tr(
174-
language,
175-
"Spline models are non-parametric, but they optimize knot y-values as parameters.",
176-
"Сплайны непараметрические, но оптимизируют knot y как параметры.",
177-
));
178-
ui.add_space(4.0);
179182
let min_knots = app
180183
.resolved_model()
181184
.spline_min_knots()
@@ -186,6 +189,7 @@ pub(super) fn ui_family_and_params(app: &mut CurveFitApp, ui: &mut egui::Ui) {
186189

187190
ui.horizontal_wrapped(|ui| {
188191
ui.label(tr(language, "Initial parameters", "Начальные параметры"));
192+
CurveFitApp::info_tooltip(ui, spline_init_hint(language));
189193
ui.add_enabled_ui(can_edit_params, |ui| {
190194
ui.menu_button(
191195
tr(language, "+ Initialize", "+ Инициализация"),
@@ -215,7 +219,8 @@ pub(super) fn ui_family_and_params(app: &mut CurveFitApp, ui: &mut egui::Ui) {
215219
"Knot count",
216220
"Число узлов",
217221
)),
218-
);
222+
)
223+
.on_hover_text(knot_count_hint(language));
219224
egui::ComboBox::from_label(tr(language, "Knot reduction", "Редукция узлов"))
220225
.selected_text(spline_knot_strategy_label(
221226
language,
@@ -229,7 +234,9 @@ pub(super) fn ui_family_and_params(app: &mut CurveFitApp, ui: &mut egui::Ui) {
229234
spline_knot_strategy_label(language, strategy),
230235
);
231236
}
232-
});
237+
})
238+
.response
239+
.on_hover_text(knot_reduction_hint(language));
233240
egui::ComboBox::from_label(tr(language, "Extrapolation", "Экстраполяция"))
234241
.selected_text(spline_extrapolation_label(
235242
language,
@@ -243,7 +250,9 @@ pub(super) fn ui_family_and_params(app: &mut CurveFitApp, ui: &mut egui::Ui) {
243250
spline_extrapolation_label(language, extrapolation),
244251
);
245252
}
246-
});
253+
})
254+
.response
255+
.on_hover_text(extrapolation_hint(language));
247256
egui::ComboBox::from_label(tr(language, "Duplicate x", "Дубли x"))
248257
.selected_text(spline_duplicate_policy_label(
249258
language,
@@ -257,18 +266,23 @@ pub(super) fn ui_family_and_params(app: &mut CurveFitApp, ui: &mut egui::Ui) {
257266
spline_duplicate_policy_label(language, policy),
258267
);
259268
}
260-
});
269+
})
270+
.response
271+
.on_hover_text(duplicate_x_hint(language));
261272
});
262273
app.sync_spline_initial_knot_y_inputs(app.spline_knots);
263-
ui.label(format!(
264-
"{}: {}",
265-
tr(
266-
language,
267-
"Target spline parameter count",
268-
"Целевое число параметров сплайна"
269-
),
270-
app.spline_knots
271-
));
274+
ui.horizontal_wrapped(|ui| {
275+
ui.label(format!(
276+
"{}: {}",
277+
tr(
278+
language,
279+
"Target spline parameter count",
280+
"Целевое число параметров сплайна"
281+
),
282+
app.spline_knots
283+
));
284+
CurveFitApp::info_tooltip(ui, spline_sampling_hint(language));
285+
});
272286
ui.label(tr(
273287
language,
274288
"Initial knot y values",
@@ -295,23 +309,61 @@ pub(super) fn ui_family_and_params(app: &mut CurveFitApp, ui: &mut egui::Ui) {
295309
}
296310
});
297311
});
298-
ui.label(egui::RichText::new(tr(
299-
language,
300-
"More knots means better fit, less smoothing; fewer knots means stronger smoothing.",
301-
"Больше узлов — более точная подгонка, меньше сглаживания; меньше узлов — более сильное сглаживание.",
302-
)).small());
303-
ui.label(egui::RichText::new(tr(
304-
language,
305-
"When x-values contain duplicates you can merge them automatically instead of failing.",
306-
"При повторяющихся x можно автоматически объединять точки вместо ошибки.",
307-
)).small());
308-
ui.label(
309-
egui::RichText::new(tr(
310-
language,
311-
"Sample density is selected automatically from knot count and data size.",
312-
"Плотность сэмплирования выбирается автоматически по числу узлов и размеру данных.",
313-
))
314-
.small(),
315-
);
316312
}
317313
}
314+
315+
fn parametric_init_hint(language: UiLanguage) -> &'static str {
316+
tr(
317+
language,
318+
"Initial parameters (parametric models)\n- These values are the optimizer starting point\n- +Initialize can fill defaults/data-based/randomized values\n- \"From fitted model\" reuses parameters from the latest fit of the same family",
319+
"Начальные параметры (параметрические модели)\n- Эти значения являются стартовой точкой оптимизатора\n- +Инициализация может подставить значения по умолчанию/по данным/случайно\n- \"Из обученной модели\" берёт параметры из последнего фитинга того же семейства",
320+
)
321+
}
322+
323+
fn spline_init_hint(language: UiLanguage) -> &'static str {
324+
tr(
325+
language,
326+
"Initial parameters (spline models)\n- Spline is non-parametric, but optimizer still tunes knot y-values\n- +Initialize sets starting knot_y values\n- Better initialization usually reduces iteration count",
327+
"Начальные параметры (сплайны)\n- Сплайн непараметрический, но оптимизатор всё равно настраивает knot y\n- +Инициализация задаёт стартовые значения knot_y\n- Более удачная инициализация обычно снижает число итераций",
328+
)
329+
}
330+
331+
fn knot_count_hint(language: UiLanguage) -> &'static str {
332+
tr(
333+
language,
334+
"Knot count\n- More knots: more flexible fit, weaker smoothing\n- Fewer knots: stronger smoothing, simpler curve",
335+
"Число узлов\n- Больше узлов: более гибкая подгонка, слабее сглаживание\n- Меньше узлов: сильнее сглаживание, проще кривая",
336+
)
337+
}
338+
339+
fn knot_reduction_hint(language: UiLanguage) -> &'static str {
340+
tr(
341+
language,
342+
"Knot reduction strategy\n- Bin mean: smoother on clean data, more sensitive to outliers\n- Bin median: more robust to spikes/noise",
343+
"Стратегия редукции узлов\n- Среднее по окнам: обычно более гладко на чистых данных, но чувствительнее к выбросам\n- Медиана по окнам: устойчивее к пикам и шуму",
344+
)
345+
}
346+
347+
fn extrapolation_hint(language: UiLanguage) -> &'static str {
348+
tr(
349+
language,
350+
"Extrapolation outside knot range\n- Clamp to edge: keep boundary y-value\n- Linear: continue using boundary slope",
351+
"Экстраполяция вне диапазона узлов\n- Фиксация на краю: удерживать граничное значение y\n- Линейная: продолжать по граничному наклону",
352+
)
353+
}
354+
355+
fn duplicate_x_hint(language: UiLanguage) -> &'static str {
356+
tr(
357+
language,
358+
"Duplicate x handling\n- Error: fail on repeated x\n- Merge by mean/median: aggregate duplicates before fitting\n- Keep first y: preserve earliest sample per x",
359+
"Обработка дублей x\n- Error: завершить с ошибкой при повторяющихся x\n- Слияние по mean/median: агрегировать дубли до фитинга\n- Keep first y: оставить первый y для каждого x",
360+
)
361+
}
362+
363+
fn spline_sampling_hint(language: UiLanguage) -> &'static str {
364+
tr(
365+
language,
366+
"Spline sampling density\n- Selected automatically from knot count and data size\n- Increased as needed, but capped to keep UI responsive",
367+
"Плотность сэмплирования сплайна\n- Выбирается автоматически по числу узлов и объёму данных\n- Увеличивается по необходимости, но ограничивается для отзывчивого UI",
368+
)
369+
}

src/app/ui/formula_window.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ pub(super) fn ui_formula_window(app: &mut CurveFitApp, ctx: &egui::Context) {
2323
{
2424
ui.ctx().copy_text(plain_formula.clone());
2525
}
26-
ui.label(
27-
egui::RichText::new(tr(
26+
CurveFitApp::info_tooltip(
27+
ui,
28+
tr(
2829
language,
29-
"Use horizontal scroll for very long formulas.",
30-
"Для очень длинных формул используйте горизонтальный скролл.",
31-
))
32-
.small(),
30+
"Formula window\n- Use horizontal scroll for very long formulas\n- Copy formula exports plain-text representation",
31+
"Окно формулы\n- Для очень длинных формул используйте горизонтальный скролл\n- Копирование формулы экспортирует текстовое представление",
32+
),
3333
);
3434
});
3535
ui.add_space(4.0);

src/app/ui/header_bar.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ pub(super) fn ui_header(app: &mut CurveFitApp, ui: &mut egui::Ui) {
115115
{
116116
app.toggle_replay_autoplay();
117117
}
118+
CurveFitApp::info_tooltip(ui, replay_controls_hint(language));
118119
ui.separator();
119120
ui.add(
120121
egui::Slider::new(&mut app.replay.iteration_delay_seconds, 0.0..=3.0)
@@ -125,6 +126,14 @@ pub(super) fn ui_header(app: &mut CurveFitApp, ui: &mut egui::Ui) {
125126
});
126127
}
127128

129+
fn replay_controls_hint(language: UiLanguage) -> &'static str {
130+
tr(
131+
language,
132+
"Replay controls\n- Displayed iteration selects the frame shown on plot/diagnostics\n- Auto-play starts replay automatically after fit\n- Play/Pause controls manual playback\n- Replay step sets delay between frames in seconds",
133+
"Управление промоткой\n- Показываемая итерация выбирает кадр на графике и в диагностике\n- Автопромотка автоматически запускается после фитинга\n- Пуск/Пауза управляют ручным воспроизведением\n- Шаг промотки задаёт задержку между кадрами в секундах",
134+
)
135+
}
136+
128137
pub(super) fn ui_status_bar(app: &mut CurveFitApp, ui: &mut egui::Ui) {
129138
ui.horizontal(|ui| {
130139
app.ui_status(ui);

src/app/ui/optimizer_panel.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,10 @@ pub(super) fn ui_optimizer(app: &mut CurveFitApp, ui: &mut egui::Ui) {
2525
OptimizerUiMode::Advanced,
2626
tr(language, "Advanced", "Продвинутый"),
2727
);
28+
CurveFitApp::info_tooltip(ui, optimizer_mode_hint(language, app.optimizer_mode));
2829
});
2930

3031
if app.optimizer_mode == OptimizerUiMode::Basic {
31-
ui.label(tr(
32-
language,
33-
"Use presets to quickly control convergence speed and stability.",
34-
"Используйте пресеты для быстрого выбора баланса скорости и устойчивости.",
35-
));
36-
3732
let previous_preset = app.selected_optimizer_preset();
3833
let mut selected_preset = previous_preset;
3934
egui::ComboBox::from_label(tr(language, "Preset", "Пресет"))
@@ -145,11 +140,6 @@ pub(super) fn ui_optimizer(app: &mut CurveFitApp, ui: &mut egui::Ui) {
145140
}
146141
});
147142
} else {
148-
ui.label(tr(
149-
language,
150-
"Use sliders to tune optimizer parameters.",
151-
"Используйте бегунки для настройки оптимизатора.",
152-
));
153143
match app.optimizer_method {
154144
OptimizerMethod::Lbfgs => {
155145
let before = app.lbfgs_inputs.clone();
@@ -409,3 +399,18 @@ pub(super) fn ui_optimizer(app: &mut CurveFitApp, ui: &mut egui::Ui) {
409399
));
410400
}
411401
}
402+
403+
fn optimizer_mode_hint(language: UiLanguage, mode: OptimizerUiMode) -> &'static str {
404+
match mode {
405+
OptimizerUiMode::Basic => tr(
406+
language,
407+
"Basic mode\n- Choose a preset to balance speed vs stability\n- Good default for quick model comparison\n- Switch to Advanced for per-parameter tuning",
408+
"Базовый режим\n- Выберите пресет для баланса скорости и устойчивости\n- Хороший режим по умолчанию для быстрого сравнения моделей\n- Для точной настройки параметров перейдите в Продвинутый",
409+
),
410+
OptimizerUiMode::Advanced => tr(
411+
language,
412+
"Advanced mode\n- Tune solver parameters directly with sliders\n- Any manual change switches preset to Custom\n- Use this mode when convergence is slow or unstable",
413+
"Продвинутый режим\n- Параметры решателя настраиваются напрямую бегунками\n- Любое ручное изменение переводит пресет в Custom\n- Используйте, если сходимость медленная или нестабильная",
414+
),
415+
}
416+
}

0 commit comments

Comments
 (0)