feat(math): offer the result next to a handwritten = (Issue #46, stage 3)
New module math_assist.rs: when the last two strokes form an = and the pen rests for 500 ms, the strokes left of it (in a band around the =, up to a wide gap) are recognized and computed. The suggestion appears next to the = and shows what was read, e.g. "2⁴ = 16"; tapping it with the pen or clicking inserts the result as a text box in the ink color and as high as the handwriting, as one undo step. The chip is not interactive for egui, so fingers and palms pass through. Any new stroke drops it. In a chain like x = 2 · 3 = the part after the previous = is computed. Off by default: ☰ → Input → "Compute formulas after =", saved in ui.json. math::display writes the recognized expression neatly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d16cd2db72
commit
307c803246
10 files changed
+775
-14
No files matched your search
@@ -317,6 +317,8 @@ pub enum UiAction {
|
||||
UnlockCorruptedPage(u64),
|
||||
/// Werkzeugleiste an die andere Fensterseite verlegen (wird gespeichert)
|
||||
SetSidebarSide(SidebarSide),
|
||||
/// Compute handwritten math after `=` on or off (Issue #46)
|
||||
SetMathAssist(bool),
|
||||
/// Sprache der Oberfläche; `None` = Sprache des Systems
|
||||
SetLanguage(Option<crate::i18n::Language>),
|
||||
/// Papierfarbe der Anzeige und Aufhellen dunkler Tinte (wird gespeichert)
|
||||
@@ -413,6 +415,8 @@ pub struct UiState {
|
||||
pub update_modal_rect: Option<egui::Rect>,
|
||||
/// Steuert, ob Touch-Eingaben mit einem Finger zeichnen oder das Dokument verschieben (Default: false = reine Gesten-Navigation & Zoom)
|
||||
pub touch_draw_enabled: bool,
|
||||
/// Computes handwritten math after an `=` (Issue #46)
|
||||
pub math: crate::math_assist::MathAssist,
|
||||
/// Zuletzt gelöschtes Notizbuch für Rückgängig während der Sitzung (Papierkorb-Pfad, Titel)
|
||||
pub last_deleted_notebook: Option<(std::path::PathBuf, String)>,
|
||||
/// Stifte der Werkzeugleiste (Farbe und Stärke) und der gewählte davon; `pen_color` und
|
||||
@@ -546,6 +550,7 @@ impl Default for UiState {
|
||||
show_updater_config: false,
|
||||
update_modal_rect: None,
|
||||
touch_draw_enabled: false,
|
||||
math: crate::math_assist::MathAssist::default(),
|
||||
last_deleted_notebook: None,
|
||||
pen_presets: DEFAULT_PEN_PRESETS,
|
||||
active_pen: 0,
|
||||
@@ -1652,6 +1657,57 @@ fn text_style_buttons(ui: &mut egui::Ui, btn: f32, state: &mut UiState) {
|
||||
|
||||
/// Bearbeitung eines Textfelds: egui-Textfeld an der Stelle und in der Größe des Felds auf
|
||||
/// dem Blatt (ungedreht), mit derselben Schrift. Esc beendet die Bearbeitung.
|
||||
/// The result offered next to a handwritten `=` (Issue #46): what was read and the result,
|
||||
/// e.g. `2⁴ = 16`. Not interactive for egui, so that fingers and palms pass through to the
|
||||
/// page; the pen and the mouse take it via [`crate::math_assist::MathAssist::take_hit`].
|
||||
fn math_suggestion(ctx: &egui::Context, state: &mut UiState, notebook: &Notebook) {
|
||||
state.math.chip_rect = None;
|
||||
if state.view_mode {
|
||||
return;
|
||||
}
|
||||
let (Some(view), Some(suggestion)) = (
|
||||
state.page_view,
|
||||
state.math.suggestion_for(notebook.current_page()).cloned(),
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
let size = (suggestion.symbol_height * view.scale * 0.8).clamp(14.0, 48.0);
|
||||
let response = egui::Area::new(egui::Id::new("math_suggestion"))
|
||||
.fixed_pos(view.to_screen(suggestion.anchor))
|
||||
.pivot(egui::Align2::LEFT_CENTER)
|
||||
.order(egui::Order::Foreground)
|
||||
.interactable(false)
|
||||
.show(ctx, |ui| {
|
||||
egui::Frame::none()
|
||||
.fill(egui::Color32::from_rgba_unmultiplied(30, 34, 44, 235))
|
||||
.stroke(egui::Stroke::new(
|
||||
1.5_f32,
|
||||
egui::Color32::from_rgb(70, 130, 230),
|
||||
))
|
||||
.rounding(size * 0.4)
|
||||
.inner_margin(egui::vec2(size * 0.45, size * 0.2))
|
||||
.show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = size * 0.25;
|
||||
ui.label(
|
||||
egui::RichText::new(format!("{} =", suggestion.expression))
|
||||
.size(size * 0.7)
|
||||
.color(egui::Color32::from_gray(170)),
|
||||
);
|
||||
ui.label(
|
||||
egui::RichText::new(&suggestion.result)
|
||||
.size(size)
|
||||
.strong()
|
||||
.color(egui::Color32::WHITE),
|
||||
);
|
||||
});
|
||||
});
|
||||
})
|
||||
.response;
|
||||
// A little larger than drawn, as the pen does not hit exactly
|
||||
state.math.chip_rect = Some(response.rect.expand(6.0));
|
||||
}
|
||||
|
||||
fn text_edit_overlay(ctx: &egui::Context, state: &mut UiState) {
|
||||
let adapt = state.adapt_ink_colors && state.paper_theme.is_dark();
|
||||
let (Some(view), Some(edit)) = (state.page_view, state.text_edit.as_mut()) else {
|
||||
@@ -3150,6 +3206,7 @@ pub fn render_ui(
|
||||
space_preview(ctx, state, notebook);
|
||||
ruler_overlay(ctx, state);
|
||||
text_edit_overlay(ctx, state);
|
||||
math_suggestion(ctx, state, notebook);
|
||||
context_menu(ctx, state, &mut actions);
|
||||
|
||||
let mut pen_btn_rect = None;
|
||||
@@ -4383,6 +4440,15 @@ pub fn render_ui(
|
||||
.on_hover_text(
|
||||
t!("Aus: ein Finger verschiebt, zwei Finger zoomen (empfohlen mit Stift).\nAn: ein Finger zeichnet."),
|
||||
);
|
||||
let mut math = state.math.enabled;
|
||||
if ui
|
||||
.checkbox(&mut math, t!("Formeln nach „=“ ausrechnen"))
|
||||
.on_hover_text(t!("Nach einem handgeschriebenen „=“ erscheint das Ergebnis daneben, z. B. 2⁴ = 16. Antippen mit dem Stift oder Klicken fügt es als Text ein.\nGelesen werden Ziffern, + − · : /, Klammern, Hochzahlen, Brüche und x, y, a, b."))
|
||||
.changed()
|
||||
{
|
||||
state.math.set_enabled(math);
|
||||
actions.push(UiAction::SetMathAssist(math));
|
||||
}
|
||||
if let Some(curve) = pressure_curve_editor(ui, state) {
|
||||
actions.push(UiAction::SavePressureCurve(curve));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user