From 3f3352302fe745600cef14bec7b24d60595efdb1 Mon Sep 17 00:00:00 2001 From: Ahanu Dewhirst <16126069+Orcjett@users.noreply.github.com> Date: Fri, 19 Jul 2024 09:52:56 +1000 Subject: [PATCH] [#2485] Fix visibility toggling logic and feedback --- .../core/rocketcomponent/RocketComponent.java | 2 + .../main/resources/l10n/messages.properties | 9 +- .../swing/gui/main/RocketActions.java | 117 +++++++++++------- 3 files changed, 81 insertions(+), 47 deletions(-) diff --git a/core/src/main/java/info/openrocket/core/rocketcomponent/RocketComponent.java b/core/src/main/java/info/openrocket/core/rocketcomponent/RocketComponent.java index f3b86d98e..09b72676c 100644 --- a/core/src/main/java/info/openrocket/core/rocketcomponent/RocketComponent.java +++ b/core/src/main/java/info/openrocket/core/rocketcomponent/RocketComponent.java @@ -2716,6 +2716,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab /** * Returns true if this component is visible. * @return True if this component is visible. + * @apiNote The component is rendered if true is returned. */ public boolean isVisible() { return isVisible; @@ -2724,6 +2725,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab /** * Sets the component's visibility to the specified value. * @param value Visibility value + * @apiNote The component is rendered if the specified value is set to true. */ public void setVisible(boolean value) { this.isVisible = value; diff --git a/core/src/main/resources/l10n/messages.properties b/core/src/main/resources/l10n/messages.properties index 36b7c2b8f..c32cba6a5 100644 --- a/core/src/main/resources/l10n/messages.properties +++ b/core/src/main/resources/l10n/messages.properties @@ -49,14 +49,15 @@ RocketActions.MoveDownAct.Movedown = Move down RocketActions.MoveDownAct.ttip.Movedown = Move this component downwards. RocketActions.ExportOBJAct.ExportOBJ = Export as OBJ (.obj) RocketActions.ExportOBJAct.ttip.ExportOBJ = Export the selected components as a Wavefront OBJ 3D file. -RocketActions.VisibilityAct.Hide = Hide -RocketActions.VisibilityAct.ttip.Hide = Hide the selected component. -RocketActions.VisibilityAct.Show = Show -RocketActions.VisibilityAct.ttip.Show = Show the selected component. + RocketActions.VisibilityAct.ShowAll = Show all RocketActions.VisibilityAct.ttip.ShowAll = Show all components. RocketActions.VisibilityAct.HideAll = Hide all RocketActions.VisibilityAct.ttip.HideAll = Hide all components. +RocketActions.VisibilityAct.ShowSelected = Show selected +RocketActions.VisibilityAct.ttip.ShowSelected = Show selected components. +RocketActions.VisibilityAct.HideSelected = Hide selected +RocketActions.VisibilityAct.ttip.HideSelected = Hide selected components. ! RocketPanel RocketPanel.FigTypeAct.SideView = Side view diff --git a/swing/src/main/java/info/openrocket/swing/gui/main/RocketActions.java b/swing/src/main/java/info/openrocket/swing/gui/main/RocketActions.java index a9123efd4..16cf900f7 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/main/RocketActions.java +++ b/swing/src/main/java/info/openrocket/swing/gui/main/RocketActions.java @@ -1259,40 +1259,40 @@ public class RocketActions { /** * Action to toggle the visibility of the selected components. + * @see RocketComponent#isVisible() */ private class ToggleVisibilityAction extends RocketAction { public ToggleVisibilityAction() { - this.putValue(NAME, trans.get("RocketActions.VisibilityAct.Hide")); - this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.VisibilityAct.ttip.Hide")); - this.putValue(SMALL_ICON, GUIUtil.getUITheme().getVisibilityHiddenIcon()); + super.putValue(NAME, trans.get("RocketActions.VisibilityAct.HideSelected")); + super.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.VisibilityAct.ttip.HideSelected")); + super.putValue(SMALL_ICON, GUIUtil.getUITheme().getVisibilityHiddenIcon()); clipboardChanged(); } @Override public void clipboardChanged() { var components = new ArrayList<>(selectionModel.getSelectedComponents()); - super.setEnabled(!components.isEmpty()); - if (!components.isEmpty()) { - var firstComponent = components.get(0); + if (components.isEmpty()) { + return; + } + super.setEnabled(true); - if (components.size() > 1 || isRocketOrStage(firstComponent)) { - if (!firstComponent.isVisible()) { - this.putValue(NAME, trans.get("RocketActions.VisibilityAct.ShowAll")); - this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.VisibilityAct.ttip.ShowAll")); - } else { - this.putValue(NAME, trans.get("RocketActions.VisibilityAct.HideAll")); - this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.VisibilityAct.ttip.HideAll")); - } - } else { - if (!firstComponent.isVisible()) { - this.putValue(NAME, trans.get("RocketActions.VisibilityAct.Show")); - this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.VisibilityAct.ttip.Show")); - } else { - this.putValue(NAME, trans.get("RocketActions.VisibilityAct.Hide")); - this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.VisibilityAct.ttip.Hide")); - } - } + if (isRocketSelected(components)) { + super.putValue(NAME, rocket.isVisible() ? + trans.get("RocketActions.VisibilityAct.HideAll") : + trans.get("RocketActions.VisibilityAct.ShowAll")); + super.putValue(SHORT_DESCRIPTION, rocket.isVisible() ? + trans.get("RocketActions.VisibilityAct.ttip.HideAll") : + trans.get("RocketActions.VisibilityAct.ttip.ShowAll")); + } else { + var visibility = components.stream().anyMatch(RocketComponent::isVisible); + super.putValue(NAME, visibility ? + trans.get("RocketActions.VisibilityAct.HideSelected") : + trans.get("RocketActions.VisibilityAct.ShowSelected")); + super.putValue(SHORT_DESCRIPTION, visibility ? + trans.get("RocketActions.VisibilityAct.ttip.HideSelected") : + trans.get("RocketActions.VisibilityAct.ttip.ShowSelected")); } } @@ -1300,36 +1300,67 @@ public class RocketActions { public void actionPerformed(ActionEvent e) { var components = new ArrayList<>(selectionModel.getSelectedComponents()); - if (!components.isEmpty()) { - var visibility = !components.get(0).isVisible(); - - for (var component : components) { - if (isRocketOrStage(component)) { - getAllDescendants(components).forEach(c -> c.setVisible(visibility)); - continue; - } - component.setVisible(visibility); - } + if (components.isEmpty()) { + return; } + + // Toggle the visibility of the rocket and its descendants + if (isRocketSelected(components)) { + var rocketVisibility = !rocket.isVisible(); + rocket.setVisible(rocketVisibility); + getDescendants(rocket).forEach(descendant -> descendant.setVisible(rocketVisibility)); + return; + } + var visibility = components.stream().noneMatch(RocketComponent::isVisible); + + // Toggle the visibility of all non-stage and non-rocket components + components.stream().filter(c -> !(c instanceof AxialStage || c instanceof Rocket)).forEach(component -> { + component.setVisible(visibility); + + // Update the visibility of this component's stage + var stage = component.getStage(); + stage.setVisible(getDescendants(stage).stream().anyMatch(RocketComponent::isVisible)); + + // Update the visibility of the rocket + rocket.setVisible(getDescendants(rocket).stream().anyMatch(RocketComponent::isVisible)); + }); + + // Toggle the visibility of all stage components and their descendants + components.stream().filter(AxialStage.class::isInstance).forEach(stage -> { + stage.setVisible(visibility); + getDescendants(stage).forEach(descendant -> descendant.setVisible(visibility)); + }); } - private boolean isRocketOrStage(RocketComponent component) { - return component instanceof AxialStage || component instanceof Rocket; + /** + * Returns true if the rocket or all descendant are in the specified list. + * + * @param components Components to query + * @return True if all components are selected + */ + private boolean isRocketSelected(List components) { + var rocketSelected = components.stream().anyMatch(Rocket.class::isInstance); + var allComponentsSelected = getDescendants(rocket).size() == components.size(); + return rocketSelected || allComponentsSelected; } - private Set getAllDescendants(List components) { + /** + * Returns all descendants of the specified component. + * + * @param component Component to query + * @return All descendants + * @apiNote Returns an empty set if the component does not have children. + */ + private Set getDescendants(RocketComponent component) { + Objects.requireNonNull(component); + var result = new LinkedHashSet(); - var queue = new ArrayDeque<>(components); + var queue = new ArrayDeque<>(component.getChildren()); while (!queue.isEmpty()) { var node = queue.pop(); result.add(node); - - for (var child : node.getChildren()) { - if (!result.contains(child)) { - queue.add(child); - } - } + node.getChildren().stream().filter(c -> !result.contains(c)).forEach(queue::add); } return result; }