Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions crates/app/src/ui/canvas/integrals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ fn apply_integral_drag_live(app: &mut PlotxApp, dataset: usize, ppm: f64) {
}
}
n.recompute_integrals();
app.sync_integral_curves_for(dataset);
}

fn finish_integral_drag(app: &mut PlotxApp, dataset: usize, xspan: f64) {
Expand Down Expand Up @@ -254,15 +255,15 @@ fn finish_integral_drag(app: &mut PlotxApp, dataset: usize, xspan: f64) {
{
let id = n.next_integral_id;
n.next_integral_id += 1;
let is_reference = n.integrals.is_empty();
let reference_value = n.integrals.is_empty().then_some(1.0);
n.integrals.push(IntegralResult {
id,
start_ppm: lo,
end_ppm: hi,
area: 0.0,
normalized_area: 0.0,
normalized_area: reference_value.unwrap_or(0.0),
mode: plotx_core::DisplayModeLabel::Real,
is_reference,
reference_value,
});
n.recompute_integrals();
app.session.ui.selected_integral = Some(id);
Expand Down Expand Up @@ -306,8 +307,8 @@ fn integral_context_menu(
ui.close();
return;
}
if ui.button("Set as reference (=1)").clicked() {
app.set_integral_reference(dataset, id);
if ui.button("Use as normalization reference").clicked() {
app.set_integral_reference(dataset, id, 1.0);
ui.close();
}
if ui.button("Delete").clicked() {
Expand Down
24 changes: 8 additions & 16 deletions crates/app/src/ui/canvas/integrals2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ fn finish_integral_2d_drag(
.and_then(Dataset::as_nmr2d_mut)
{
let id = n.next_integral_id();
let is_reference = n.integrals.is_empty();
let reference_value = n.integrals.is_empty().then_some(1.0);
let mode = n.display_mode().into();
n.integrals.push(Integral2D {
id,
Expand All @@ -391,8 +391,7 @@ fn finish_integral_2d_drag(
f1,
volume: 0.0,
normalized_volume: None,
is_reference,
reference_value: 1.0,
reference_value,
mode,
method: IntegralMethod::Sum,
baseline: BaselineMode::None,
Expand Down Expand Up @@ -437,8 +436,8 @@ fn integral_2d_context_menu(
ui.close();
return;
};
if ui.button("Set as reference").clicked() {
app.set_integral_2d_reference(dataset, id);
if ui.button("Use as normalization reference").clicked() {
app.set_integral_2d_reference(dataset, id, 1.0);
ui.close();
}
if ui.button("Delete").clicked() {
Expand Down Expand Up @@ -488,11 +487,7 @@ pub(crate) fn paint_integrals_2d(
if r.width() < 1.0 || r.height() < 1.0 {
continue;
}
let color = if integral.is_reference {
INTEGRAL_REF_COLOR
} else {
INTEGRAL_COLOR
};
let color = INTEGRAL_COLOR;
let [red, green, blue, _] = color.to_array();
painter.rect_filled(
r,
Expand All @@ -509,11 +504,10 @@ pub(crate) fn paint_integrals_2d(
let value = integral
.normalized_volume
.map_or_else(|| "—".to_owned(), |v| format!("{v:.3}"));
let reference = if integral.is_reference { " (ref)" } else { "" };
painter.text(
r.left_top() + egui::vec2(3.0, 2.0),
egui::Align2::LEFT_TOP,
format!("{}: {}{}", integral.name, value, reference),
format!("{}: {}", integral.name, value),
egui::FontId::proportional(11.0),
color,
);
Expand Down Expand Up @@ -548,8 +542,7 @@ pub(crate) fn paint_integrals_2d(
f1: (drag.anchor[1], drag.current[1]),
volume: 0.0,
normalized_volume: None,
is_reference: false,
reference_value: 1.0,
reference_value: None,
mode: DisplayModeLabel::Real,
method: IntegralMethod::Sum,
baseline: BaselineMode::None,
Expand Down Expand Up @@ -577,8 +570,7 @@ mod tests {
f1: (3.0, 6.0),
volume: 0.0,
normalized_volume: None,
is_reference: false,
reference_value: 1.0,
reference_value: None,
mode: DisplayModeLabel::Real,
method: IntegralMethod::Sum,
baseline: BaselineMode::None,
Expand Down
1 change: 0 additions & 1 deletion crates/app/src/ui/canvas/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ const SNAP_PX: f32 = 6.0;
const GRID_COLOR: Color32 = Color32::from_rgb(0x5a, 0xa9, 0xc4);
const GUIDE_COLOR: Color32 = Color32::from_rgb(0xff, 0x2d, 0x92);
const INTEGRAL_COLOR: Color32 = Color32::from_rgb(0x2b, 0x6c, 0xb0);
const INTEGRAL_REF_COLOR: Color32 = Color32::from_rgb(0xc7, 0x8a, 0x14);
const PEAK_COLOR: Color32 = Color32::from_rgb(0x8a, 0x1c, 0x1c);

mod authoring;
Expand Down
58 changes: 31 additions & 27 deletions crates/app/src/ui/canvas/painting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ pub(crate) fn paint_integrals(
plot: PlotRect,
painter: &egui::Painter,
) {
if app.session.tool != Tool::Integrate {
return;
}
let Some(fig) = app.doc.canvases[ci]
.object(object_id)
.and_then(|object| object.plot())
Expand All @@ -236,6 +239,13 @@ pub(crate) fn paint_integrals(
return;
};
let selected = app.session.ui.selected_integral;
let hover_x = painter.ctx().input(|input| {
input
.pointer
.hover_pos()
.filter(|position| plot_rect(plot).contains(*position))
.map(|position| position.x)
});
for integ in &n.integrals {
let x0 = x_to_screen(
integ.start_ppm,
Expand All @@ -253,37 +263,31 @@ pub(crate) fn paint_integrals(
if r.width() < 1.0 {
continue;
}
let color = if integ.is_reference {
INTEGRAL_REF_COLOR
} else {
INTEGRAL_COLOR
};
let color = INTEGRAL_COLOR;
let [cr, cg, cb, _] = color.to_array();
painter.rect_filled(r, 0.0, Color32::from_rgba_unmultiplied(cr, cg, cb, 30));
let is_sel = selected == Some(integ.id);
painter.rect_stroke(
r,
0.0,
Stroke::new(if is_sel { 2.0_f32 } else { 1.0_f32 }, color),
StrokeKind::Inside,
);
let label = if integ.is_reference {
format!("{:.3} (ref)", integ.normalized_area)
} else {
format!("{:.3}", integ.normalized_area)
};
painter.text(
Pos2::new(r.left() + 3.0, r.top() + 2.0),
egui::Align2::LEFT_TOP,
label,
egui::FontId::proportional(11.0),
color,
);
let is_hovered = hover_x.is_some_and(|x| x >= r.left() && x <= r.right());
if is_sel || is_hovered {
painter.rect_filled(r, 0.0, Color32::from_rgba_unmultiplied(cr, cg, cb, 30));
}
for edge in [r.left(), r.right()] {
painter.line_segment(
[Pos2::new(edge, r.top()), Pos2::new(edge, r.bottom())],
Stroke::new(
if is_sel { 2.0_f32 } else { 1.0_f32 },
color.gamma_multiply(0.65),
),
);
}
if is_sel {
for ex in [r.left(), r.right()] {
painter.line_segment(
[Pos2::new(ex, r.top()), Pos2::new(ex, r.bottom())],
Stroke::new(2.5_f32, color),
painter.rect_filled(
EguiRect::from_center_size(
Pos2::new(ex, (r.top() + r.bottom()) * 0.5),
Vec2::new(6.0, 16.0),
),
1.0,
color,
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/app/src/ui/primary_sidebar/data_browser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ mod tests {
area: 1.0,
normalized_area: 1.0,
mode: DisplayModeLabel::Real,
is_reference: false,
reference_value: None,
});
nmr.line_fits.push(StoredLineFit {
id: 13,
Expand Down
110 changes: 78 additions & 32 deletions crates/app/src/ui/tools/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ mod statistics_config;
mod task_card;

use curve_fit::curve_fit_group;
use egui::{Button, DragValue, Response, Ui};
use egui::{Button, DragValue, Id, Response, Ui};
use egui_phosphor::regular as icon;
use line_fit::line_fit_group;
use plotx_core::actions::{DatasetProcessingState, PendingProcessingEdit};
Expand All @@ -24,6 +24,40 @@ use slice::slice_group;

pub(super) use line_fit::line_fit_shape_id;

#[derive(Clone, Copy, Default)]
struct DeferredReferenceValue {
value: f64,
changed: bool,
}

/// Keep a reference-value edit outside the document until the widget gesture
/// ends, so one drag or typing run produces exactly one undoable action.
fn reference_value_drag(ui: &mut Ui, id: Id, committed: f64) -> Option<f64> {
let mut pending = ui
.data_mut(|data| data.get_temp::<DeferredReferenceValue>(id))
.unwrap_or(DeferredReferenceValue {
value: committed,
changed: false,
});
let response = ui
.add(
DragValue::new(&mut pending.value)
.speed(0.1)
.max_decimals(3),
)
.on_hover_text("Normalization value assigned to this reference integral");
pending.changed |= response.changed();

if response.drag_stopped() || response.lost_focus() {
ui.data_mut(|data| data.remove_temp::<DeferredReferenceValue>(id));
return pending.changed.then_some(pending.value);
}
if response.dragged() || response.has_focus() || response.changed() {
ui.data_mut(|data| data.insert_temp(id, pending));
}
None
}

pub(crate) fn render_region_task(app: &mut PlotxApp, ui: &mut Ui) {
region_analysis::render_task(app, ui);
}
Expand Down Expand Up @@ -278,12 +312,12 @@ pub(super) fn integrate_group(app: &mut PlotxApp, di: usize, ui: &mut Ui) {
if drawing {
ui.small(
"Drag across a peak to add · drag edges to resize · drag middle to move · \
right-click to set the reference or delete.",
right-click to set the normalization reference or delete.",
);
}

let selected = app.session.ui.selected_integral;
let mut set_ref: Option<u64> = None;
let mut set_ref: Option<(u64, f64)> = None;
let mut delete_id: Option<u64> = None;
let mut select_id: Option<u64> = None;

Expand All @@ -309,13 +343,21 @@ pub(super) fn integrate_group(app: &mut PlotxApp, di: usize, ui: &mut Ui) {
{
select_id = Some(integ.id);
}
ui.label(format!("{:.3}", integ.normalized_area));
if ui
.selectable_label(integ.is_reference, "ref")
.on_hover_text("Use this integral as the =1 reference")
.clicked()
{
set_ref = Some(integ.id);
if let Some(value) = integ.reference_value {
let id = ui.make_persistent_id(("integral_reference_1d", di, integ.id));
if let Some(value) = reference_value_drag(ui, id, value) {
set_ref = Some((integ.id, value));
}
ui.weak("reference");
} else {
ui.label(format!("{:.3}", integ.normalized_area));
if ui
.small_button("set reference")
.on_hover_text("Use this integral as the normalization reference")
.clicked()
{
set_ref = Some((integ.id, 1.0));
}
}
if ui.small_button(icon::X).clicked() {
delete_id = Some(integ.id);
Expand All @@ -326,8 +368,8 @@ pub(super) fn integrate_group(app: &mut PlotxApp, di: usize, ui: &mut Ui) {
if let Some(id) = select_id {
app.session.ui.selected_integral = Some(id);
}
if let Some(id) = set_ref {
app.set_integral_reference(di, id);
if let Some((id, value)) = set_ref {
app.set_integral_reference(di, id, value);
}
if let Some(id) = delete_id {
app.delete_integral(di, id);
Expand Down Expand Up @@ -370,7 +412,15 @@ fn integrate_2d_group(app: &mut PlotxApp, di: usize, ui: &mut Ui) {
if integrals.is_empty() {
ui.weak("No 2D integrals yet — draw a rectangle around a peak.");
}
if integrals
let has_reference = integrals
.iter()
.any(|integral| integral.reference_value.is_some());
if !integrals.is_empty() && !has_reference {
ui.colored_label(
ui.visuals().warn_fg_color,
"Choose a normalization reference to show normalized values.",
);
} else if integrals
.iter()
.any(|integral| integral.normalized_volume.is_none())
{
Expand Down Expand Up @@ -406,9 +456,21 @@ fn integrate_2d_group(app: &mut PlotxApp, di: usize, ui: &mut Ui) {
let normalized = integral
.normalized_volume
.map_or_else(|| "—".to_owned(), |value| format!("{value:.3}"));
ui.label(normalized);
if ui.selectable_label(integral.is_reference, "ref").clicked() {
app.set_integral_2d_reference(di, integral.id);
if let Some(value) = integral.reference_value {
let id = ui.make_persistent_id(("integral_reference_2d", di, integral.id));
if let Some(value) = reference_value_drag(ui, id, value) {
app.set_integral_2d_reference(di, integral.id, value);
}
ui.weak("reference");
} else {
ui.label(normalized);
if ui
.small_button("set reference")
.on_hover_text("Use this integral as the normalization reference")
.clicked()
{
app.set_integral_2d_reference(di, integral.id, 1.0);
}
}
if ui.small_button(icon::X).clicked() {
app.delete_integral_2d(di, integral.id);
Expand Down Expand Up @@ -448,22 +510,6 @@ fn integrate_2d_group(app: &mut PlotxApp, di: usize, ui: &mut Ui) {
}
});
}
if integral.is_reference {
ui.label("Reference value");
let mut reference_value = integral.reference_value;
if ui
.add(DragValue::new(&mut reference_value).speed(0.1))
.changed()
{
app.edit_integrals_2d(di, |values, _| {
if let Some(value) =
values.iter_mut().find(|value| value.id == integral.id)
{
value.reference_value = reference_value;
}
});
}
}
});
});
}
Expand Down
Loading
Loading