Skip to content

Commit 1315953

Browse files
committed
Viewer3D: Complete Ux restructuring...
- Fix ping-pong animation speed - Add customizeAxes command - Add anatomical labels to animation prompt - Fix centering dialog prompts when parent is not a frame - Improve split pane - Improve snapshot retrieval
1 parent 57bd3b0 commit 1315953

5 files changed

Lines changed: 411 additions & 50 deletions

File tree

‎src/main/java/sc/fiji/snt/gui/GuiUtils.java‎

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1382,7 +1382,10 @@ public static void centerOnParent(final Component parent, final Window dialog) {
13821382
dialog.setLocationRelativeTo(null);
13831383
return;
13841384
}
1385-
centerOnParent(parent.getBounds(), parent.getGraphicsConfiguration(), dialog);
1385+
final java.awt.Point screenLoc = parent.getLocationOnScreen();
1386+
final Rectangle screenBounds = new Rectangle(screenLoc.x, screenLoc.y,
1387+
parent.getWidth(), parent.getHeight());
1388+
centerOnParent(screenBounds, parent.getGraphicsConfiguration(), dialog);
13861389
}
13871390

13881391
public static void centerOnParent(final Rectangle parentBounds, final Window dialog) {
@@ -3495,6 +3498,30 @@ public static void expandAllNodes(final javax.swing.JTree tree) {
34953498
for (int i = 0; i < tree.getRowCount(); i++) tree.expandRow(i);
34963499
}
34973500

3501+
public static void scrollToLastRow(final javax.swing.JTree tree) {
3502+
int lastRow = tree.getRowCount() - 1;
3503+
if (lastRow >= 0) {
3504+
tree.scrollRowToVisible(lastRow);
3505+
}
3506+
}
3507+
3508+
public static void expandToLevel(final javax.swing.JTree tree, final int level) {
3509+
final DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
3510+
expandToLevel(tree, new TreePath(root), level);
3511+
}
3512+
3513+
private static void expandToLevel(final javax.swing.JTree tree, final TreePath parentPath, final int level) {
3514+
final DefaultMutableTreeNode node = (DefaultMutableTreeNode) parentPath.getLastPathComponent();
3515+
if (node.getLevel() < level) {
3516+
tree.expandPath(parentPath);
3517+
final Enumeration<? extends TreeNode> children = node.children();
3518+
while (children.hasMoreElements()) {
3519+
final DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
3520+
expandToLevel(tree, parentPath.pathByAddingChild(child), level);
3521+
}
3522+
}
3523+
}
3524+
34983525
public static void collapseNodesOfSameLevel(final javax.swing.JTree tree, final TreePath path) {
34993526
final DefaultMutableTreeNode root = (DefaultMutableTreeNode)tree.getModel().getRoot();
35003527
final DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
@@ -3673,4 +3700,102 @@ else if (current instanceof Container) {
36733700
return null;
36743701
}
36753702
}
3703+
3704+
public static class SplitPanes {
3705+
3706+
public static JSplitPane nonDraggableRightSplitPane() {
3707+
final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
3708+
splitPane.setOneTouchExpandable(true);
3709+
splitPane.putClientProperty("JSplitPane.expandableSide", "left");
3710+
splitPane.putClientProperty(FlatClientProperties.STYLE,
3711+
"style: plain;" +
3712+
"arrowType: triangle;" + //oneTouchArrowColor; oneTouchHoverArrowColor
3713+
"oneTouchHoverArrowColor: " + SNTColor.colorToString(getSelectionColor()) + ";" +
3714+
"oneTouchArrowColor: " + SNTColor.colorToString(IconFactory.defaultColor()) + ";");
3715+
splitPane.setResizeWeight(1.0); // give all extra space to the left side
3716+
splitPane.setBorder(null);
3717+
// Non: resizable: reset cursor and vertically center the one-touch buttons along the divider's length
3718+
final javax.swing.plaf.basic.BasicSplitPaneDivider divider =
3719+
((javax.swing.plaf.basic.BasicSplitPaneUI) splitPane.getUI()).getDivider();
3720+
SplitPanes.applyDefaultCursor(divider);
3721+
SplitPanes.centerOneTouchButtons(splitPane, divider);
3722+
return splitPane;
3723+
}
3724+
3725+
public static void applyDefaultCursor(final javax.swing.plaf.basic.BasicSplitPaneDivider divider) {
3726+
divider.setCursor(java.awt.Cursor.getDefaultCursor());
3727+
}
3728+
3729+
public static void centerOneTouchButtons(final javax.swing.JSplitPane splitPane,
3730+
final javax.swing.plaf.basic.BasicSplitPaneDivider divider) {
3731+
divider.setLayout(new java.awt.LayoutManager() {
3732+
@Override
3733+
public void addLayoutComponent(final String name, final Component comp) {
3734+
}
3735+
3736+
@Override
3737+
public void removeLayoutComponent(final Component comp) {
3738+
}
3739+
3740+
@Override
3741+
public java.awt.Dimension preferredLayoutSize(final Container parent) {
3742+
return null;
3743+
}
3744+
3745+
@Override
3746+
public java.awt.Dimension minimumLayoutSize(final Container parent) {
3747+
return null;
3748+
}
3749+
3750+
@Override
3751+
public void layoutContainer(final Container parent) {
3752+
// Replicate FlatDividerLayout's visibility logic: when a side
3753+
// is collapsed, show only the button that can restore it.
3754+
// Otherwise, respect the expandableSide client property.
3755+
final Component[] comps = parent.getComponents();
3756+
final java.awt.Insets insets = splitPane.getInsets();
3757+
final int loc = splitPane.getDividerLocation();
3758+
final boolean horiz = splitPane.getOrientation() == javax.swing.JSplitPane.HORIZONTAL_SPLIT;
3759+
final boolean leftCollapsed = horiz
3760+
? loc <= insets.left
3761+
: loc <= insets.top;
3762+
final boolean rightCollapsed = horiz
3763+
? loc >= splitPane.getWidth() - divider.getWidth() - insets.right
3764+
: loc >= splitPane.getHeight() - divider.getHeight() - insets.bottom;
3765+
for (int i = 0; i < comps.length; i++) {
3766+
if (!(comps[i] instanceof javax.swing.JButton)) continue;
3767+
if (leftCollapsed || rightCollapsed) {
3768+
// Collapsed: show only the button that restores the collapsed side
3769+
comps[i].setVisible(i == 0 ? !leftCollapsed : !rightCollapsed);
3770+
} else {
3771+
final Object expandSide = splitPane.getClientProperty(
3772+
FlatClientProperties.SPLIT_PANE_EXPANDABLE_SIDE);
3773+
if (FlatClientProperties.SPLIT_PANE_EXPANDABLE_SIDE_LEFT.equals(expandSide))
3774+
comps[i].setVisible(i > 0);
3775+
else if (FlatClientProperties.SPLIT_PANE_EXPANDABLE_SIDE_RIGHT.equals(expandSide))
3776+
comps[i].setVisible(i == 0);
3777+
}
3778+
}
3779+
// Collect visible buttons and center them vertically
3780+
final java.util.List<Component> buttons = new java.util.ArrayList<>();
3781+
for (final Component c : comps) {
3782+
if (c instanceof javax.swing.JButton && c.isVisible())
3783+
buttons.add(c);
3784+
}
3785+
if (buttons.isEmpty()) return;
3786+
final int divW = parent.getWidth();
3787+
final int divH = parent.getHeight();
3788+
final int btnW = Math.min(divW, buttons.getFirst().getPreferredSize().width);
3789+
final int btnH = buttons.getFirst().getPreferredSize().height;
3790+
final int totalH = buttons.size() * btnH;
3791+
int y = (divH - totalH) / 2;
3792+
final int x = (divW - btnW) / 2;
3793+
for (final Component btn : buttons) {
3794+
btn.setBounds(x, y, btnW, btnH);
3795+
y += btnH;
3796+
}
3797+
}
3798+
});
3799+
}
3800+
}
36763801
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*-
2+
* #%L
3+
* Fiji distribution of ImageJ for the life sciences.
4+
* %%
5+
* Copyright (C) 2010 - 2026 Fiji developers.
6+
* %%
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as
9+
* published by the Free Software Foundation, either version 3 of the
10+
* License, or (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public
18+
* License along with this program. If not, see
19+
* <http://www.gnu.org/licenses/gpl-3.0.html>.
20+
* #L%
21+
*/
22+
23+
package sc.fiji.snt.gui.cmds;
24+
25+
import org.scijava.ItemIO;
26+
import org.scijava.command.Command;
27+
import org.scijava.command.ContextCommand;
28+
import org.scijava.plugin.Parameter;
29+
import org.scijava.plugin.Plugin;
30+
31+
import java.util.HashMap;
32+
33+
/**
34+
* Command for customizing axis-to-anatomy mappings in Reconstruction Viewer.
35+
* Allows the user to specify which anatomical dimension (Anterior-Posterior,
36+
* Dorsal-Ventral, Left-Right) corresponds to each world axis (X, Y, Z), and
37+
* the direction of the dorsal axis. The mapping determines the camera up
38+
* vector and the axis-swap behavior for constrained 2D views.
39+
*
40+
* @author Tiago Ferreira
41+
*/
42+
@Plugin(type = Command.class, visible = false, label = "Axes Mapping...")
43+
public class CustomizeAxesCmd extends ContextCommand {
44+
45+
/** Anatomical axis choices. */
46+
private static final String AP = "Anterior-Posterior";
47+
private static final String DV = "Dorsal-Ventral";
48+
private static final String LR = "Left-Right (ML)";
49+
private static final String NONE = "None";
50+
51+
private static final String DIR_POS = "Positive (+)";
52+
private static final String DIR_NEG = "Negative (-)";
53+
54+
@Parameter(label = "X axis:", choices = {AP, DV, LR, NONE})
55+
private String xAxisMapping;
56+
57+
@Parameter(label = "Y axis:", choices = {AP, DV, LR, NONE})
58+
private String yAxisMapping;
59+
60+
@Parameter(label = "Z axis:", choices = {AP, DV, LR, NONE})
61+
private String zAxisMapping;
62+
63+
@Parameter(label = "Dorsal direction:", choices = {DIR_POS, DIR_NEG},
64+
description = "The direction along the Dorsal-Ventral axis in which dorsal values increase.\n"
65+
+ "Set to 'Negative' if dorsal is at the origin (e.g. Allen CCF).")
66+
private String dorsalDirection;
67+
68+
@Parameter(type = ItemIO.OUTPUT, required = false, persist = false)
69+
private HashMap<String, String> outMap;
70+
71+
/**
72+
* Returns the axis identifier ("X", "Y", or "Z") assigned to the
73+
* Dorsal-Ventral role, or {@code null} if none is assigned.
74+
*/
75+
public static String getDVAxis(final HashMap<String, String> map) {
76+
if (DV.equals(map.get("xAxis"))) return "X";
77+
if (DV.equals(map.get("yAxis"))) return "Y";
78+
if (DV.equals(map.get("zAxis"))) return "Z";
79+
return null;
80+
}
81+
82+
/**
83+
* Returns the sign of the dorsal direction: {@code -1} for negative,
84+
* {@code +1} for positive.
85+
*/
86+
public static int getDorsalSign(final HashMap<String, String> map) {
87+
return DIR_NEG.equals(map.get("dorsalDir")) ? -1 : 1;
88+
}
89+
90+
/**
91+
* Returns the axis labels derived from the mapping, suitable for
92+
* {@code Viewer3D.setAxesLabels()}.
93+
*/
94+
public static String[] getLabels(final HashMap<String, String> map) {
95+
return new String[]{
96+
NONE.equals(map.get("xAxis")) ? "X" : map.get("xAxis"),
97+
NONE.equals(map.get("yAxis")) ? "Y" : map.get("yAxis"),
98+
NONE.equals(map.get("zAxis")) ? "Z" : map.get("zAxis")
99+
};
100+
}
101+
102+
@Override
103+
public void run() {
104+
outMap = new HashMap<>();
105+
outMap.put("xAxis", xAxisMapping);
106+
outMap.put("yAxis", yAxisMapping);
107+
outMap.put("zAxis", zAxisMapping);
108+
outMap.put("dorsalDir", dorsalDirection);
109+
}
110+
111+
}

‎src/main/java/sc/fiji/snt/gui/cmds/RecViewerPrefsCmd.java‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ public class RecViewerPrefsCmd extends ContextCommand {
5858
public static String DEF_ANIMATION_MODE = "Full Rotation";
5959
/** Default sensitivity setting for 3D viewer controls */
6060
public static String DEF_CONTROLS_SENSITIVITY = "High";
61+
/** Default WYSIWYG snapshot mode (off: use GL readback) */
62+
public static boolean DEF_SNAPSHOT_WYSIWYG = false;
6163
/** Default file extension for scripts */
6264
public static String DEF_SCRIPT_EXTENSION = ".groovy";
6365

@@ -71,17 +73,22 @@ public class RecViewerPrefsCmd extends ContextCommand {
7173
description = "Directory where static snapshots and animated sequences will be saved")
7274
private File snapshotDir;
7375

76+
@Parameter(label = "WYSIWYG snapshots", required = false, persist = true,
77+
description = "Use screen capture for pixel-perfect snapshots matching \n" +
78+
"the on-screen rendering (bypasses GL framebuffer readback)")
79+
private boolean snapshotWYSIWYG;
80+
7481
@Parameter(label = "Animated Rotations:", required = false,
7582
visibility = ItemVisibility.MESSAGE)
7683
private String SUB_HEADER1;
7784

7885
@Parameter(label = "Animation mode", required = false, persist = true,
79-
description = "Full Rotation: continuous 360° sweep. Ping-pong: oscillates back and forth over the specified arc",
86+
description = "Full Rotation: continuous 360° sweep.\nPing-pong: oscillates back and forth over the specified arc",
8087
choices = { "Full Rotation", "Ping-pong" })
8188
private String animationMode;
8289

8390
@Parameter(required = false, persist = true, label = "Rotation degrees",
84-
description = "Full Rotation: total sweep angle. Ping-pong: oscillation arc (camera rocks ±half this angle)",
91+
description = "Full Rotation: total sweep angle.\nPing-pong: oscillation arc (camera rocks ±half this angle)",
8592
style = NumberWidget.SCROLL_BAR_STYLE, stepSize = "10", min = "30",
8693
max = "360d")
8794
private double rotationAngle;
@@ -157,6 +164,7 @@ private void init() {
157164

158165
private void defaults() {
159166
snapshotDir = null;
167+
snapshotWYSIWYG = DEF_SNAPSHOT_WYSIWYG;
160168
animationMode = null;
161169
rotationAngle = 0;
162170
rotationDuration = 0;

‎src/main/java/sc/fiji/snt/util/ImpUtils.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,10 @@ public static ImagePlus getSystemClipboard(final boolean asMultiChannel) {
437437
return null;
438438
}
439439

440+
public static ImagePlus fromBufferedImage(final String title, final BufferedImage image) {
441+
return new ImagePlus(title, new ij.process.ColorProcessor(image));
442+
}
443+
440444
private static boolean isGrayscale(final BufferedImage image, final int width, final int height) {
441445
for (int i = 0; i < width; i++) { // https://stackoverflow.com/a/36173328
442446
for (int j = 0; j < height; j++) {

0 commit comments

Comments
 (0)