-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Implementation of walkthrough infrastructure #13182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Yubo-Cao
wants to merge
57
commits into
JabRef:main
Choose a base branch
from
Yubo-Cao:walkthrough
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 44 commits
Commits
Show all changes
57 commits
Select commit
Hold shift + click to select a range
5babec2
feat: add initial implementation of walkthrough
Yubo-Cao fdd6f13
Merge branch 'JabRef:main' into walkthrough
Yubo-Cao 1230126
Fix issues mentioned in the PR
f30c5be
Merge branch 'main' into walkthrough
koppor 7dec5e5
Merge remote-tracking branch 'upstream/main' into walkthrough
dafac44
Merge branch 'walkthrough' of https://github.com/Yubo-Cao/jabref into…
1b2af57
Tweak Base.css
6e21170
Implement backdrop highlight effect.
bf87a52
Use record classes
2039dfc
Fix backdrop highlight showing on invisible node
264c3c4
Builder refactor and adding paper directory setup
6f80afe
Resolve PR comments
f2c4add
Update JabRef_en keys
da595c4
Update JabRef_en keys and remove title case
b43936f
Fix paper directory picker
cc8a553
Fix modernizer
197f6d8
Add edit entry step (and bugs on IndexOutOfBound
d944011
Add edit entry step
50dedc2
Merge branch 'walkthrough' of https://github.com/Yubo-Cao/jabref into…
Yubo-Cao 61b5963
Remove change to localization
Yubo-Cao 878d22b
Preliminary implementation of main file directory walkthrough
9aab167
Implement fix according to subhramit
Yubo-Cao 405cf0e
Resolve warnings from Intellij
Yubo-Cao ec2a036
Remove whitespace change to JabRefGUI
Yubo-Cao eb8d4fb
Remove whitespace changes to JabRefFrame
Yubo-Cao 547c819
Remove whitespace change to PreferencesDialogView
Yubo-Cao 770258b
Rename the Manager to WalkthroughHighlighter
Yubo-Cao d32c14b
Use PopOver class, rather than custom arrow position algorithm
Yubo-Cao ca33ea6
Change excess usage of Optional to Nullable
Yubo-Cao 570bfab
Refactor highlight effect
Yubo-Cao d5eb6fb
Add before navigate hook to prevent unwanted revert
Yubo-Cao b5a10c1
Fix Checkstyle and Intellij
Yubo-Cao d589f9d
Update the width and height
Yubo-Cao 487cc60
Internationalize WalkthroughAction
Yubo-Cao 323b353
Fix according to Trag bot.
Yubo-Cao 40a22ff
Fix according to Trag bot.
Yubo-Cao da7e8a5
Fix according to Trag bot.
Yubo-Cao a184af6
Fix improper PopOver display
Yubo-Cao cb1d9a1
Fix bug for scrolling and window effect
Yubo-Cao bfe9be7
Fix according to Trag
Yubo-Cao 4979337
Additional fix per Trag
Yubo-Cao 57353e4
Fix backdrop highlight NPE
Yubo-Cao dd41500
Merge remote-tracking branch 'upstream/main' into walkthrough
Yubo-Cao da50fcd
Fix localization issue
Yubo-Cao 7b284be
Fix according to Trag bot
Yubo-Cao 66e85af
Fix WalkthroughStep3's LinkedFiles issue
Yubo-Cao b02ef51
Merge branch 'main' into walkthrough
Yubo-Cao c635e33
Fix the issue where PopOver rendered unstyled
Yubo-Cao 62147cf
Update Walkthrough comments
Yubo-Cao 3f77583
Comment on the walkthrough and fix the switch expression
Yubo-Cao 4a1ad10
Properly refactor the displayStep code
Yubo-Cao 325f822
Provide overload for displayStep
Yubo-Cao 001f1f8
Lazy loading for WalkthroughAction registry
Yubo-Cao 39c6259
Make the walkthrough more robust in the checking the null arguments
Yubo-Cao 6dbecfc
Slightly reformatted the walkthrough updater.
Yubo-Cao b845b32
Introduce fix to the WindowResolver's hard coding problem and inversi…
Yubo-Cao a7e40c7
Update jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java
Yubo-Cao File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
258 changes: 258 additions & 0 deletions
258
jabgui/src/main/java/org/jabref/gui/walkthrough/SingleWindowWalkthroughOverlay.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,258 @@ | ||
package org.jabref.gui.walkthrough; | ||
|
||
import java.util.Optional; | ||
|
||
import javafx.beans.value.ChangeListener; | ||
import javafx.geometry.Bounds; | ||
import javafx.geometry.Pos; | ||
import javafx.scene.Node; | ||
import javafx.scene.Scene; | ||
import javafx.scene.layout.ColumnConstraints; | ||
import javafx.scene.layout.GridPane; | ||
import javafx.scene.layout.Pane; | ||
import javafx.scene.layout.Priority; | ||
import javafx.scene.layout.RowConstraints; | ||
import javafx.scene.layout.StackPane; | ||
import javafx.scene.shape.Rectangle; | ||
import javafx.stage.Window; | ||
|
||
import org.jabref.gui.walkthrough.declarative.step.PanelPosition; | ||
import org.jabref.gui.walkthrough.declarative.step.PanelStep; | ||
import org.jabref.gui.walkthrough.declarative.step.TooltipPosition; | ||
import org.jabref.gui.walkthrough.declarative.step.TooltipStep; | ||
import org.jabref.gui.walkthrough.declarative.step.WalkthroughStep; | ||
|
||
import org.controlsfx.control.PopOver; | ||
import org.jspecify.annotations.Nullable; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* Manages the overlay for displaying walkthrough steps in a single window. | ||
*/ | ||
public class SingleWindowWalkthroughOverlay { | ||
private static final Logger LOGGER = LoggerFactory.getLogger(SingleWindowWalkthroughOverlay.class); | ||
|
||
private final Window window; | ||
private final GridPane overlayPane; | ||
private final Pane originalRoot; | ||
private final StackPane stackPane; | ||
private final WalkthroughRenderer renderer; | ||
private final WalkthroughUpdater updater = new WalkthroughUpdater(); | ||
|
||
public SingleWindowWalkthroughOverlay(Window window) { | ||
this.window = window; | ||
this.renderer = new WalkthroughRenderer(); | ||
|
||
overlayPane = new GridPane(); | ||
overlayPane.getStyleClass().add("walkthrough-overlay"); | ||
overlayPane.setPickOnBounds(false); | ||
overlayPane.setMaxWidth(Double.MAX_VALUE); | ||
overlayPane.setMaxHeight(Double.MAX_VALUE); | ||
|
||
Scene scene = window.getScene(); | ||
// This basically never happens, so only a development time check is needed | ||
assert scene != null; | ||
calixtus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
originalRoot = (Pane) scene.getRoot(); | ||
stackPane = new StackPane(); | ||
|
||
stackPane.getChildren().add(originalRoot); | ||
stackPane.getChildren().add(overlayPane); | ||
|
||
scene.setRoot(stackPane); | ||
} | ||
|
||
/** | ||
* Displays a walkthrough step with a target node. | ||
*/ | ||
public void displayStepWithTarget(WalkthroughStep step, | ||
Node targetNode, | ||
Runnable beforeNavigate, | ||
Walkthrough walkthrough) { | ||
displayStep(step, targetNode, beforeNavigate, walkthrough); | ||
} | ||
|
||
/** | ||
* Displays a walkthrough step without a target node. | ||
*/ | ||
public void displayStepWithoutTarget(WalkthroughStep step, | ||
Runnable beforeNavigate, | ||
Walkthrough walkthrough) { | ||
displayStep(step, null, beforeNavigate, walkthrough); | ||
} | ||
|
||
private void displayStep(WalkthroughStep step, | ||
@Nullable Node targetNode, | ||
Runnable beforeNavigate, | ||
Walkthrough walkthrough) { | ||
hide(); | ||
|
||
switch (step) { | ||
case TooltipStep tooltipStep -> { | ||
Node content = renderer.render(tooltipStep, walkthrough, beforeNavigate); | ||
displayTooltipStep(content, targetNode, tooltipStep); | ||
hideOverlayPane(); | ||
} | ||
case PanelStep panelStep -> { | ||
Node content = renderer.render(panelStep, walkthrough, beforeNavigate); | ||
displayPanelStep(content, panelStep); | ||
setupClipping(content); | ||
overlayPane.toFront(); | ||
} | ||
} | ||
|
||
if (targetNode == null) { | ||
return; | ||
} | ||
|
||
step.navigationPredicate().ifPresent(predicate -> updater | ||
.addCleanupTask(predicate.attachListeners(targetNode, beforeNavigate, walkthrough::nextStep))); | ||
} | ||
Yubo-Cao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* Hide the overlay and clean up any resources. | ||
*/ | ||
public void hide() { | ||
overlayPane.getChildren().clear(); | ||
overlayPane.setClip(null); | ||
overlayPane.setVisible(true); | ||
updater.cleanup(); | ||
} | ||
|
||
/** | ||
* Detaches the overlay and restores the original scene root. | ||
*/ | ||
public void detach() { | ||
hide(); | ||
|
||
Scene scene = window.getScene(); | ||
if (scene != null && originalRoot != null) { | ||
stackPane.getChildren().remove(originalRoot); | ||
scene.setRoot(originalRoot); | ||
LOGGER.debug("Restored original scene root: {}", originalRoot.getClass().getName()); | ||
} | ||
} | ||
|
||
private void displayTooltipStep(Node content, @Nullable Node targetNode, TooltipStep step) { | ||
PopOver popover = new PopOver(); | ||
popover.setContentNode(content); | ||
popover.setDetachable(false); | ||
popover.setCloseButtonEnabled(false); | ||
popover.setHeaderAlwaysVisible(false); | ||
mapToArrowLocation(step.position()).ifPresent(popover::setArrowLocation); | ||
popover.setAutoHide(false); | ||
popover.setAutoFix(true); | ||
|
||
if (targetNode == null) { | ||
popover.show(window); | ||
return; | ||
} | ||
|
||
popover.show(targetNode); | ||
updater.addCleanupTask(popover::hide); | ||
Runnable showPopover = () -> { | ||
if (WalkthroughUpdater.cannotPositionNode(targetNode)) { | ||
return; | ||
} | ||
popover.show(targetNode); | ||
}; | ||
updater.setupScrollContainerListeners(targetNode, showPopover); | ||
} | ||
|
||
private void displayPanelStep(Node content, PanelStep step) { | ||
overlayPane.getChildren().clear(); | ||
overlayPane.getRowConstraints().clear(); | ||
overlayPane.getColumnConstraints().clear(); | ||
|
||
configurePanelLayout(step.position()); | ||
|
||
overlayPane.getChildren().add(content); | ||
GridPane.setHgrow(content, Priority.NEVER); | ||
GridPane.setVgrow(content, Priority.NEVER); | ||
|
||
switch (step.position()) { | ||
case LEFT -> { | ||
overlayPane.setAlignment(Pos.CENTER_LEFT); | ||
GridPane.setVgrow(content, Priority.ALWAYS); | ||
GridPane.setFillHeight(content, true); | ||
} | ||
case RIGHT -> { | ||
overlayPane.setAlignment(Pos.CENTER_RIGHT); | ||
GridPane.setVgrow(content, Priority.ALWAYS); | ||
GridPane.setFillHeight(content, true); | ||
} | ||
case TOP -> { | ||
overlayPane.setAlignment(Pos.TOP_CENTER); | ||
GridPane.setHgrow(content, Priority.ALWAYS); | ||
GridPane.setFillWidth(content, true); | ||
} | ||
case BOTTOM -> { | ||
overlayPane.setAlignment(Pos.BOTTOM_CENTER); | ||
GridPane.setHgrow(content, Priority.ALWAYS); | ||
GridPane.setFillWidth(content, true); | ||
} | ||
default -> { | ||
LOGGER.warn("Unsupported position for panel step: {}", step.position()); | ||
overlayPane.setAlignment(Pos.CENTER); | ||
} | ||
} | ||
} | ||
|
||
private void configurePanelLayout(PanelPosition position) { | ||
RowConstraints rowConstraints = new RowConstraints(); | ||
ColumnConstraints columnConstraints = new ColumnConstraints(); | ||
|
||
switch (position) { | ||
case LEFT, | ||
RIGHT -> { | ||
rowConstraints.setVgrow(Priority.ALWAYS); | ||
columnConstraints.setHgrow(Priority.NEVER); | ||
} | ||
case TOP, | ||
BOTTOM -> { | ||
columnConstraints.setHgrow(Priority.ALWAYS); | ||
rowConstraints.setVgrow(Priority.NEVER); | ||
} | ||
default -> { | ||
rowConstraints.setVgrow(Priority.NEVER); | ||
columnConstraints.setHgrow(Priority.NEVER); | ||
} | ||
} | ||
Yubo-Cao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
overlayPane.getRowConstraints().add(rowConstraints); | ||
overlayPane.getColumnConstraints().add(columnConstraints); | ||
} | ||
|
||
private Optional<PopOver.ArrowLocation> mapToArrowLocation(TooltipPosition position) { | ||
return Optional.ofNullable(switch (position) { | ||
case TOP -> | ||
PopOver.ArrowLocation.BOTTOM_CENTER; | ||
case BOTTOM -> | ||
PopOver.ArrowLocation.TOP_CENTER; | ||
case LEFT -> | ||
PopOver.ArrowLocation.RIGHT_CENTER; | ||
case RIGHT -> | ||
PopOver.ArrowLocation.LEFT_CENTER; | ||
case AUTO -> | ||
null; | ||
}); | ||
} | ||
|
||
private void hideOverlayPane() { | ||
overlayPane.setVisible(false); | ||
updater.addCleanupTask(() -> overlayPane.setVisible(true)); | ||
} | ||
|
||
private void setupClipping(Node node) { | ||
ChangeListener<Bounds> listener = (_, _, bounds) -> { | ||
if (bounds != null && bounds.getWidth() > 0 && bounds.getHeight() > 0) { | ||
Rectangle clip = new Rectangle(bounds.getMinX(), bounds.getMinY(), | ||
bounds.getWidth(), bounds.getHeight()); | ||
overlayPane.setClip(clip); | ||
} | ||
}; | ||
updater.listen(node.boundsInLocalProperty(), listener); | ||
listener.changed(null, null, node.getBoundsInParent()); | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.