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
54 changes: 53 additions & 1 deletion crates/app/src/ui/canvas/breadcrumb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub(crate) fn canvas_breadcrumb(app: &mut PlotiumApp, ci: usize, ui: &mut Ui) {
.object(id)
.map(|object| (id, object.name.clone()))
});
let phase = (app.session.tool == Tool::ManualPhase).then(|| phase_target(app, ci));
let mut clicked = None;
ui.horizontal(|ui| {
ui.add_space(2.0);
Expand All @@ -30,10 +31,14 @@ pub(crate) fn canvas_breadcrumb(app: &mut PlotiumApp, ci: usize, ui: &mut Ui) {
if ui.small_button(name).clicked() {
clicked = Some(BreadcrumbTarget::Content(id));
}
} else if lead.is_none() {
} else if lead.is_none() && phase.is_none() {
ui.weak("›");
ui.small(app.session.tool.label());
}
if let Some(phase) = phase {
ui.weak("›");
ui.small(phase);
}
});
ui.add_space(2.0);
match clicked {
Expand All @@ -44,6 +49,53 @@ pub(crate) fn canvas_breadcrumb(app: &mut PlotiumApp, ci: usize, ui: &mut Ui) {
}
}

fn phase_target(app: &PlotiumApp, ci: usize) -> String {
use plotium_core::state::PhaseAxis;
let target = data_edit_target(app, ci)
.and_then(|id| app.doc.canvases[ci].object(id))
.and_then(|object| object.dataset())
.and_then(|id| app.doc.dataset_index(id))
.and_then(|di| app.doc.datasets.get(di));
let Some(dataset) = target else {
return "Phase · Select a spectrum plot".into();
};
if crate::ui::nmr_volume::phase_target(app) != Some(dataset.resource_id()) {
return "Phase · Select a plot of the chosen spectrum".into();
}
let axis = dataset.active_phase_axis(app.session.ui.phase_axis);
let (nucleus, scope) = match dataset {
Dataset::Nmr(nmr) => (nmr.data.axes().first(), "whole spectrum"),
Dataset::Nmr2D(nmr) if nmr.volume.is_none() => (
match axis {
PhaseAxis::F1 => nmr.native_base.axes().first(),
_ => nmr.native_base.axes().last(),
},
"all traces along this axis",
),
_ => return "Phase · Select a phaseable spectrum plot".into(),
};
if !crate::ui::nmr_volume::canvas_axis_phaseable(dataset, axis) {
return format!(
"Phase · {} · No enabled frequency-domain phase step on {}",
dataset.display_name(),
axis.label()
);
}
let nucleus = nucleus
.and_then(|axis| axis.nucleus.as_deref())
.unwrap_or("unknown");
let direction = if axis == PhaseAxis::F1 {
"vertical axis"
} else {
"horizontal axis"
};
format!(
"Phase · {} · {} · {nucleus} · {direction} · {scope}",
dataset.display_name(),
axis.label()
)
}

enum BreadcrumbTarget {
Canvas,
Panel(PanelId),
Expand Down
7 changes: 5 additions & 2 deletions crates/app/src/ui/canvas/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ pub(crate) enum CanvasInteractionClearScope {
}

pub fn render_central(app: &mut PlotiumApp, ui: &mut Ui) {
super::nmr_volume::poll_phase(app, ui.ctx());
let Some(ci) = app.session.active_canvas else {
if app.doc.canvases.is_empty() && !board_frames(app).is_empty() {
board::render_sheets_only(app, ui);
Expand Down Expand Up @@ -339,7 +338,10 @@ pub fn render_central(app: &mut PlotiumApp, ui: &mut Ui) {
match app.session.tool {
// Ahead of the `pointer_owned` guard: the pivot line is painted here,
// and it stays on screen whatever the pointer is over.
Tool::ManualPhase => {
Tool::ManualPhase
if super::nmr_volume::phase_target(app)
== Some(app.doc.datasets[di].resource_id()) =>
{
let axis = app.doc.datasets[di].active_phase_axis(app.session.ui.phase_axis);
if let Some(pivot_ppm) = displayed_phase_pivot_ppm(app, di, axis) {
let fig = &app.doc.canvases[ci]
Expand Down Expand Up @@ -411,6 +413,7 @@ pub fn render_central(app: &mut PlotiumApp, ui: &mut Ui) {
| Tool::Rect
| Tool::Ellipse
| Tool::Line
| Tool::ManualPhase
| Tool::Arrow => {}
}
}
Expand Down
26 changes: 26 additions & 0 deletions crates/app/src/ui/canvas/phase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,28 @@ pub(crate) struct PhaseDragCtx {
pub map_rev: bool,
}

#[test]
fn pivot_is_hidden_when_the_axis_phase_step_is_disabled_or_removed() {
let mut app = PlotiumApp::new();
app.doc
.datasets
.push(crate::ui::properties::fixture::homonuclear_frequency_2d());
let axis = PhaseAxis::F2;
assert!(displayed_phase_pivot_ppm(&app, 0, axis).is_some());
for step in &mut app.doc.datasets[0].axis_pipeline_mut(axis).unwrap().steps {
if matches!(step.kind, plotium_processing::StepKind::Phase(_)) {
step.enabled = false;
}
}
assert!(displayed_phase_pivot_ppm(&app, 0, axis).is_none());
app.doc.datasets[0]
.axis_pipeline_mut(axis)
.unwrap()
.steps
.retain(|step| !matches!(step.kind, plotium_processing::StepKind::Phase(_)));
assert!(displayed_phase_pivot_ppm(&app, 0, axis).is_none());
}

/// The committed pivot, overridden by the canvas-only position while its handle
/// is being dragged.
pub(crate) fn displayed_phase_pivot_ppm(
Expand All @@ -22,6 +44,7 @@ pub(crate) fn displayed_phase_pivot_ppm(
if app.doc.datasets[dataset]
.as_nmr2d()
.is_some_and(|n| n.volume.is_some())
|| !crate::ui::nmr_volume::canvas_axis_phaseable(&app.doc.datasets[dataset], axis)
{
return None;
}
Expand Down Expand Up @@ -61,6 +84,9 @@ pub(crate) fn handle_phase_before_paint(
else {
return;
};
if crate::ui::nmr_volume::phase_target(app) != Some(app.doc.datasets[di].resource_id()) {
return;
}
let Some(outer) = content_screen_rect(
app.session.board,
&app.doc.canvases[ci],
Expand Down
4 changes: 2 additions & 2 deletions crates/app/src/ui/command_exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ fn execute_inner(
}
CommandId::OpenFile => super::file_dialogs::open_file(app),
CommandId::OpenFolder => super::file_dialogs::open_folder(app),
CommandId::CancelNmrVolumePhase => super::nmr_volume::cancel_phase(ctx),
CommandId::CancelNmrVolumePhase => super::nmr_volume::cancel_phase(app, ctx),
CommandId::Peak3DExport
| CommandId::Peak3DDelete
| CommandId::Peak3DLocate
Expand Down Expand Up @@ -356,7 +356,7 @@ fn execute_inner(
CommandId::Tool(Tool::Symmetry) => {
reveal_tool_group(app, Tool::Symmetry, ToolGroup::Nmr2dExperiment);
}
CommandId::OpenNmrVolumePhase => {
CommandId::Tool(Tool::ManualPhase) if super::nmr_volume::active_volume(app) => {
super::nmr_volume::open_phase(app);
}
CommandId::Tool(tool) => app.toggle_tool(tool),
Expand Down
19 changes: 5 additions & 14 deletions crates/app/src/ui/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ pub enum CommandId {
OpenFolder,
ImportNmrSampling,
ApplyNmrVolumePhase,
OpenNmrVolumePhase,
CancelNmrVolumePhase,
PreviousNmrPlane,
NextNmrPlane,
Expand Down Expand Up @@ -300,7 +299,6 @@ pub fn describe(app: &PlotiumApp, id: CommandId) -> CommandDescriptor {
app.session.ui.table_import_preview.is_none(),
"Finish or cancel the current table import preview before importing another table.",
),
CommandId::OpenNmrVolumePhase => requires(super::nmr_volume::active_volume(app), "Select a three-dimensional NMR spectrum before opening phase settings."),
CommandId::CancelNmrVolumePhase => requires(app.session.ui.nmr_volume_phase_busy, "Start phase processing before cancelling it."),
CommandId::Peak3DExport | CommandId::Peak3DDelete | CommandId::Peak3DLocate | CommandId::Peak3DPrevious | CommandId::Peak3DNext => super::peaks3d::gate(app, id),
CommandId::ApplyNmrVolumePhase => super::nmr_volume::phase_gate(app),
Expand Down Expand Up @@ -721,14 +719,7 @@ pub fn describe(app: &PlotiumApp, id: CommandId) -> CommandDescriptor {
.is_some_and(|dataset| dataset.supports_symmetry_review()),
"Select a homonuclear COSY, TOCSY, or NOESY / ROESY contour spectrum.",
),
// Share the Phase settings gate so its tool stays consistent.
CommandId::Tool(Tool::ManualPhase) => requires(
super::properties::discovery::group_applies(
app,
super::properties::panel::PHASE_SECTION,
),
"Select an NMR spectrum with a phase processing step before phasing it by hand.",
),
CommandId::Tool(Tool::ManualPhase) => super::nmr_volume::open_phase_gate(app),
CommandId::Tool(tool) if tool.is_data_tool() => requires(
dataset().is_some(),
"Select a dataset before using this data tool.",
Expand All @@ -743,17 +734,17 @@ pub fn describe(app: &PlotiumApp, id: CommandId) -> CommandDescriptor {
let palette_available = helpers::palette_available(app, id);
// Activation requirements must not trap an already-active tool after the
// dataset context changes: its command remains available for deactivation.
let active_tool = id
.tool_target()
.is_some_and(|tool| app.session.tool == tool);
let active_tool = id.tool_target().is_some_and(|tool| {
app.session.tool == tool
&& !(tool == Tool::ManualPhase && super::nmr_volume::active_volume(app))
});
let enabled = (gate.is_ok() || active_tool) && palette_available;
let disabled_reason = if enabled { None } else { gate.err() };
// A contextual group is dropped from the Ribbon entirely rather than shown
// permanently dead: a dead group still consumes the width budget in
// `groups_for_tab`, pushing usable groups into the overflow menu.
let ribbon = ribbon_placement(id).filter(|placement| match placement.applicability {
Applicability::Always => true,
Applicability::NmrVolumeOnly => super::nmr_volume::active_volume(app),
Applicability::LineAlignmentOnly => app.trace_alignment_target().is_some(),
Applicability::TableOnly => is_table(),
Applicability::XasOnly => dataset().and_then(Dataset::as_xas).is_some(),
Expand Down
11 changes: 5 additions & 6 deletions crates/app/src/ui/commands/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,8 @@ pub(super) fn command_identity(
CommandId::CloseWindow => plain("Close Window", Some(icon::X)),
CommandId::OpenFile => plain("Open File…", Some(icon::FILE)),
CommandId::OpenFolder => plain("Open Folder…", Some(icon::FOLDER)),
CommandId::OpenNmrVolumePhase => plain("3D Phase settings", Some(icon::ANGLE)),
CommandId::CancelNmrVolumePhase => plain("Cancel 3D NMR Phase", Some(icon::X)),
CommandId::ApplyNmrVolumePhase => plain("Apply 3D NMR Phase", Some(icon::PLAY)),
CommandId::CancelNmrVolumePhase => plain("Stop phase calculation", Some(icon::X)),
CommandId::ApplyNmrVolumePhase => plain("Apply volume phase", Some(icon::PLAY)),
CommandId::PreviousNmrPlane => plain("Previous NMR Plane", Some(icon::CARET_LEFT)),
CommandId::ImportProtein => plain("Import protein sequence…", None),
CommandId::ProteinAssignments => plain("Protein assignments", None),
Expand Down Expand Up @@ -385,9 +384,10 @@ pub(super) fn command_identity(
}
CommandId::CycleCursor => plain("Next Cursor", Some(icon::CROSSHAIR)),
CommandId::Tool(Tool::ManualPhase) => (
"Interactive phase".into(),
"Phase".into(),
tool_icon(Tool::ManualPhase),
Some(app.session.tool == Tool::ManualPhase),
(!super::super::nmr_volume::active_volume(app))
.then_some(app.session.tool == Tool::ManualPhase),
),
CommandId::Tool(tool) => (
format!("Tool: {}", tool.label()),
Expand Down Expand Up @@ -475,7 +475,6 @@ fn simple_stable_id(id: CommandId) -> &'static str {
CommandId::OpenFile => "file.open_file",
CommandId::OpenFolder => "file.open_folder",
CommandId::ImportNmrSampling => "file.import_nmr_sampling",
CommandId::OpenNmrVolumePhase => "nmr.phase.open",
CommandId::CancelNmrVolumePhase => "nmr.phase.cancel",
CommandId::ApplyNmrVolumePhase => "nmr.phase.apply",
CommandId::PreviousNmrPlane => "nmr.plane.previous",
Expand Down
6 changes: 3 additions & 3 deletions crates/app/src/ui/commands/ribbon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ pub enum Applicability {
XasOnly,
SeriesOnly,
Homonuclear2dOnly,
NmrVolumeOnly,
ToolGroup(ToolGroup),
}

Expand Down Expand Up @@ -68,7 +67,6 @@ pub(super) fn ribbon_placement(id: CommandId) -> Option<RibbonPlacement> {
CommandId::Tool(Tool::Symmetry) => (Analyze, "Review", 1, Homonuclear2dOnly),
CommandId::AlignTraces => (Analyze, "Overlay", 1, LineAlignmentOnly),
CommandId::XpsWorkbench(_) => (Analyze, "XPS", 0, Applicability::ToolGroup(ToolGroup::Xps)),
CommandId::OpenNmrVolumePhase => (Process, "3D Phase", 0, Applicability::NmrVolumeOnly),
CommandId::Tool(Tool::ManualPhase) => (Process, "Correct", 0, Always),
CommandId::SpectrumArithmetic | CommandId::AlignSpectra | CommandId::Craft => {
(Process, "Transform", 1, Always)
Expand Down Expand Up @@ -139,7 +137,9 @@ pub(super) fn ribbon_placement(id: CommandId) -> Option<RibbonPlacement> {
) => (Arrange, "Annotate", 3, Always),
// The group's own declaration decides where it lands, so a new group
// needs no arm here.
CommandId::PropertyGroup(section) => {
CommandId::PropertyGroup(section)
if section != crate::ui::properties::panel::PHASE_SECTION =>
{
let spot = crate::ui::properties::discovery::group(section)?.ribbon;
(spot.tab, spot.group, spot.priority, Always)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/app/src/ui/commands/roster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ pub(super) fn command_ids(recent_files: usize) -> Vec<CommandId> {
CommandId::OpenFolder,
CommandId::ImportNmrSampling,
CommandId::ApplyNmrVolumePhase,
CommandId::OpenNmrVolumePhase,
CommandId::CancelNmrVolumePhase,
CommandId::PreviousNmrPlane,
CommandId::NextNmrPlane,
Expand Down Expand Up @@ -190,6 +189,7 @@ pub(super) fn command_ids(recent_files: usize) -> Vec<CommandId> {
ids.extend(
crate::ui::properties::GROUPS
.iter()
.filter(|group| group.section != crate::ui::properties::panel::PHASE_SECTION)
.map(|group| CommandId::PropertyGroup(group.section)),
);
ids.extend([PropertyStep::Lower, PropertyStep::Raise].map(CommandId::StepProperty));
Expand Down
4 changes: 2 additions & 2 deletions crates/app/src/ui/commands_identity_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ fn phase_label_retains_interactive_tool_toggle_identity() {
let mut app = PlotiumApp::new();
let id = CommandId::Tool(Tool::ManualPhase);
let description = describe(&app, id);
assert_eq!(description.label, "Interactive phase");
assert_eq!(description.label, "Phase");
assert_eq!(description.checked, Some(false));
app.session.tool = Tool::ManualPhase;
let description = describe(&app, id);
assert_eq!(description.label, "Interactive phase");
assert_eq!(description.label, "Phase");
assert_eq!(description.checked, Some(true));
}
Loading
Loading