-
-
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 23 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
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
2 changes: 1 addition & 1 deletion
2
jabgui/src/main/java/org/jabref/gui/preferences/PreferencesDialogView.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
131 changes: 131 additions & 0 deletions
131
jabgui/src/main/java/org/jabref/gui/walkthrough/HighlightManager.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,131 @@ | ||
package org.jabref.gui.walkthrough; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
import javafx.scene.Node; | ||
import javafx.scene.Scene; | ||
import javafx.scene.layout.Pane; | ||
import javafx.stage.Window; | ||
|
||
import org.jabref.gui.walkthrough.components.BackdropHighlight; | ||
import org.jabref.gui.walkthrough.components.FullScreenDarken; | ||
import org.jabref.gui.walkthrough.components.PulseAnimateIndicator; | ||
import org.jabref.gui.walkthrough.declarative.effect.HighlightEffect; | ||
import org.jabref.gui.walkthrough.declarative.effect.MultiWindowHighlight; | ||
|
||
import org.jspecify.annotations.NonNull; | ||
|
||
/** | ||
* Manages highlight effects across multiple windows for walkthrough steps. | ||
*/ | ||
calixtus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
public class HighlightManager { | ||
Yubo-Cao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private final Map<Window, BackdropHighlight> backdropHighlights = new HashMap<>(); | ||
private final Map<Window, PulseAnimateIndicator> pulseIndicators = new HashMap<>(); | ||
private final Map<Window, FullScreenDarken> fullScreenDarkens = new HashMap<>(); | ||
|
||
/** | ||
* Applies the specified highlight configuration. | ||
* | ||
* @param mainScene The primary scene to apply the highlight to. | ||
* @param highlightConfig The optional highlight configuration to apply. Default to | ||
* BackdropHighlight on the primary windows if empty. | ||
* @param fallbackTarget The optional fallback target node to use if no highlight | ||
* configuration is provided. | ||
*/ | ||
calixtus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
public void applyHighlight(@NonNull Scene mainScene, | ||
Optional<MultiWindowHighlight> highlightConfig, | ||
Optional<Node> fallbackTarget) { | ||
detachAll(); | ||
|
||
highlightConfig.ifPresentOrElse( | ||
config -> { | ||
if (config.windowEffects().isEmpty() && config.fallbackEffect().isPresent()) { | ||
applyEffect(mainScene.getWindow(), config.fallbackEffect().get(), fallbackTarget); | ||
return; | ||
} | ||
config.windowEffects().forEach(effect -> { | ||
Window window = effect.windowResolver().resolve().orElse(mainScene.getWindow()); | ||
Optional<Node> targetNode = effect.targetNodeResolver() | ||
.map(resolver -> | ||
resolver.resolve(Optional.ofNullable(window.getScene()).orElse(mainScene))) | ||
.orElse(fallbackTarget); | ||
applyEffect(window, effect.effect(), targetNode); | ||
}); | ||
}, | ||
() -> fallbackTarget.ifPresent(node -> applyBackdropHighlight(mainScene.getWindow(), node)) | ||
); | ||
} | ||
|
||
/** | ||
* Detaches all active highlight effects. | ||
*/ | ||
public void detachAll() { | ||
backdropHighlights.values().forEach(BackdropHighlight::detach); | ||
backdropHighlights.clear(); | ||
|
||
pulseIndicators.values().forEach(PulseAnimateIndicator::detach); | ||
pulseIndicators.clear(); | ||
|
||
fullScreenDarkens.values().forEach(FullScreenDarken::detach); | ||
fullScreenDarkens.clear(); | ||
} | ||
|
||
private void applyEffect(Window window, HighlightEffect effect, Optional<Node> targetNode) { | ||
switch (effect) { | ||
case BACKDROP_HIGHLIGHT -> | ||
targetNode.ifPresent(node -> applyBackdropHighlight(window, node)); | ||
case ANIMATED_PULSE -> | ||
targetNode.ifPresent(node -> applyPulseAnimation(window, node)); | ||
case FULL_SCREEN_DARKEN -> applyFullScreenDarken(window); | ||
case NONE -> { | ||
if (backdropHighlights.containsKey(window)) { | ||
backdropHighlights.get(window).detach(); | ||
backdropHighlights.remove(window); | ||
} | ||
if (pulseIndicators.containsKey(window)) { | ||
pulseIndicators.get(window).detach(); | ||
pulseIndicators.remove(window); | ||
} | ||
if (fullScreenDarkens.containsKey(window)) { | ||
fullScreenDarkens.get(window).detach(); | ||
fullScreenDarkens.remove(window); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private void applyBackdropHighlight(Window window, Node targetNode) { | ||
Scene scene = window.getScene(); | ||
if (scene == null || !(scene.getRoot() instanceof Pane pane)) { | ||
return; | ||
} | ||
|
||
BackdropHighlight backdrop = new BackdropHighlight(pane); | ||
backdrop.attach(targetNode); | ||
backdropHighlights.put(window, backdrop); | ||
} | ||
|
||
private void applyPulseAnimation(Window window, Node targetNode) { | ||
Scene scene = window.getScene(); | ||
if (scene == null || !(scene.getRoot() instanceof Pane pane)) { | ||
return; | ||
} | ||
|
||
PulseAnimateIndicator pulse = new PulseAnimateIndicator(pane); | ||
pulse.attach(targetNode); | ||
pulseIndicators.put(window, pulse); | ||
} | ||
|
||
private void applyFullScreenDarken(Window window) { | ||
Scene scene = window.getScene(); | ||
if (scene == null || !(scene.getRoot() instanceof Pane pane)) { | ||
return; | ||
} | ||
|
||
FullScreenDarken fullDarken = new FullScreenDarken(pane); | ||
fullDarken.attach(); | ||
fullScreenDarkens.put(window, fullDarken); | ||
} | ||
} |
128 changes: 128 additions & 0 deletions
128
jabgui/src/main/java/org/jabref/gui/walkthrough/MultiWindowWalkthroughOverlay.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,128 @@ | ||
package org.jabref.gui.walkthrough; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
import javafx.animation.KeyFrame; | ||
import javafx.animation.Timeline; | ||
import javafx.scene.Node; | ||
import javafx.scene.Scene; | ||
import javafx.stage.Stage; | ||
import javafx.stage.Window; | ||
import javafx.util.Duration; | ||
|
||
import org.jabref.gui.walkthrough.declarative.WindowResolver; | ||
import org.jabref.gui.walkthrough.declarative.step.WalkthroughNode; | ||
|
||
import org.jspecify.annotations.NonNull; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* Manages walkthrough overlays and highlights across multiple windows. | ||
*/ | ||
calixtus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
public class MultiWindowWalkthroughOverlay { | ||
private static final Logger LOGGER = LoggerFactory.getLogger(MultiWindowWalkthroughOverlay.class); | ||
|
||
private final Map<Window, SingleWindowOverlay> overlays = new HashMap<>(); | ||
private final Stage mainStage; | ||
private final HighlightManager highlightManager; | ||
private final Walkthrough walkthrough; | ||
private Timeline nodePollingTimeline; | ||
|
||
public MultiWindowWalkthroughOverlay(Stage mainStage, Walkthrough walkthrough) { | ||
this.mainStage = mainStage; | ||
this.walkthrough = walkthrough; | ||
this.highlightManager = new HighlightManager(); | ||
} | ||
|
||
public void displayStep(@NonNull WalkthroughNode step) { | ||
overlays.values().forEach(SingleWindowOverlay::hide); | ||
Window activeWindow = step.activeWindowResolver().flatMap(WindowResolver::resolve).orElse(mainStage); | ||
Scene scene = activeWindow.getScene(); | ||
|
||
Optional<Node> targetNode = step.resolver().resolve(scene); | ||
if (targetNode.isEmpty()) { | ||
if (step.autoFallback()) { | ||
tryRevertToPreviousResolvableStep(); | ||
} else { | ||
startNodePolling(step, activeWindow); | ||
} | ||
return; | ||
} | ||
|
||
if (step.highlight().isPresent()) { | ||
highlightManager.applyHighlight(scene, step.highlight(), targetNode); | ||
} | ||
|
||
SingleWindowOverlay overlay = getOrCreateOverlay(activeWindow); | ||
overlay.displayStep(step, targetNode.get(), walkthrough); | ||
} | ||
|
||
public void detachAll() { | ||
stopNodePolling(); | ||
highlightManager.detachAll(); | ||
overlays.values().forEach(SingleWindowOverlay::detach); | ||
overlays.clear(); | ||
} | ||
|
||
private void tryRevertToPreviousResolvableStep() { | ||
LOGGER.info("Attempting to revert to previous resolvable step"); | ||
|
||
int currentIndex = walkthrough.currentStepProperty().get(); | ||
for (int i = currentIndex - 1; i >= 0; i--) { | ||
WalkthroughNode previousStep = getStepAtIndex(i); | ||
Window activeWindow = previousStep.activeWindowResolver().flatMap(WindowResolver::resolve).orElse(mainStage); | ||
Scene scene = activeWindow.getScene(); | ||
if (scene != null && previousStep.resolver().resolve(scene).isPresent()) { | ||
LOGGER.info("Reverting to step {} from step {}", i, currentIndex); | ||
walkthrough.goToStep(i); | ||
return; | ||
} | ||
} | ||
|
||
LOGGER.warn("No previous resolvable step found, staying at current step"); | ||
} | ||
|
||
private void startNodePolling(WalkthroughNode step, Window activeWindow) { | ||
LOGGER.info("Auto-fallback disabled for step: {}, starting node polling", step.title()); | ||
stopNodePolling(); | ||
|
||
nodePollingTimeline = new Timeline(new KeyFrame(Duration.millis(100), _ -> { | ||
Scene scene = activeWindow.getScene(); | ||
if (scene != null) { | ||
Optional<Node> targetNode = step.resolver().resolve(scene); | ||
if (targetNode.isPresent()) { | ||
LOGGER.info("Target node found for step: {}, displaying step", step.title()); | ||
stopNodePolling(); | ||
|
||
if (step.highlight().isPresent()) { | ||
highlightManager.applyHighlight(scene, step.highlight(), targetNode); | ||
} | ||
|
||
SingleWindowOverlay overlay = getOrCreateOverlay(activeWindow); | ||
overlay.displayStep(step, targetNode.get(), walkthrough); | ||
} | ||
} | ||
})); | ||
|
||
nodePollingTimeline.setCycleCount(Timeline.INDEFINITE); | ||
nodePollingTimeline.play(); | ||
} | ||
|
||
private void stopNodePolling() { | ||
if (nodePollingTimeline != null) { | ||
nodePollingTimeline.stop(); | ||
nodePollingTimeline = null; | ||
} | ||
} | ||
|
||
private @NonNull WalkthroughNode getStepAtIndex(int index) { | ||
return walkthrough.getSteps().get(index); | ||
} | ||
|
||
private SingleWindowOverlay getOrCreateOverlay(Window window) { | ||
return overlays.computeIfAbsent(window, SingleWindowOverlay::new); | ||
} | ||
} |
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.