Procházet zdrojové kódy

reworked drag'n'drop l&f

Sebastian Stenzel před 5 roky
rodič
revize
1ddfcc3219

+ 59 - 28
main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java

@@ -1,14 +1,15 @@
 package org.cryptomator.ui.mainwindow;
 
 import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
 import javafx.fxml.FXML;
+import javafx.scene.input.DragEvent;
 import javafx.scene.input.TransferMode;
 import javafx.scene.layout.HBox;
-import javafx.scene.layout.Pane;
 import javafx.scene.layout.Region;
 import javafx.scene.layout.VBox;
 import javafx.stage.Stage;
-import org.cryptomator.common.vaults.Vault;
 import org.cryptomator.common.vaults.VaultListManager;
 import org.cryptomator.ui.common.FxController;
 import org.cryptomator.ui.fxapp.FxApplication;
@@ -20,11 +21,11 @@ import org.slf4j.LoggerFactory;
 import javax.inject.Inject;
 import javax.inject.Named;
 import java.io.File;
+import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
-import java.util.Collection;
+import java.util.Set;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 @MainWindowScoped
 public class MainWindowController implements FxController {
@@ -39,9 +40,10 @@ public class MainWindowController implements FxController {
 	private final BooleanBinding updateAvailable;
 	private final VaultListManager vaultListManager;
 	private final WrongFileAlertComponent.Builder wrongFileAlert;
+	private final BooleanProperty draggingOver = new SimpleBooleanProperty();
+	private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
 	public HBox titleBar;
 	public VBox root;
-	public Pane dragAndDropIndicator;
 	public Region resizer;
 	private double xOffset;
 	private double yOffset;
@@ -74,40 +76,53 @@ public class MainWindowController implements FxController {
 			window.setHeight(event.getSceneY());
 		});
 		updateChecker.automaticallyCheckForUpdatesIfEnabled();
-		dragAndDropIndicator.setVisible(false);
-		root.setOnDragOver(event -> {
-			if (event.getGestureSource() != root && event.getDragboard().hasFiles()) {
-				/* allow for both copying and moving, whatever user chooses */
-				event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
-				dragAndDropIndicator.setVisible(true);
-			}
-			event.consume();
-		});
-		root.setOnDragExited(event -> dragAndDropIndicator.setVisible(false));
-		root.setOnDragDropped(event -> {
-			if (event.getGestureSource() != root && event.getDragboard().hasFiles()) {
-				/* allow for both copying and moving, whatever user chooses */
-				event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
-				Collection<Vault> vaultPaths = event.getDragboard().getFiles().stream().map(File::toPath).flatMap(this::addVault).collect(Collectors.toSet());
-				if (vaultPaths.isEmpty()) {
-					wrongFileAlert.build().showWrongFileAlertWindow();
-				}
+		root.setOnDragEntered(this::handleDragEvent);
+		root.setOnDragOver(this::handleDragEvent);
+		root.setOnDragDropped(this::handleDragEvent);
+		root.setOnDragExited(this::handleDragEvent);
+	}
+
+	private void handleDragEvent(DragEvent event) {
+		if (DragEvent.DRAG_ENTERED.equals(event.getEventType()) && event.getGestureSource() == null) {
+			draggingOver.set(true);
+		} else if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
+			event.acceptTransferModes(TransferMode.ANY);
+			draggingVaultOver.set(event.getDragboard().getFiles().stream().map(File::toPath).anyMatch(this::containsVault));
+		} else if (DragEvent.DRAG_DROPPED.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
+			Set<Path> vaultPaths = event.getDragboard().getFiles().stream().map(File::toPath).filter(this::containsVault).collect(Collectors.toSet());
+			if (vaultPaths.isEmpty()) {
+				wrongFileAlert.build().showWrongFileAlertWindow();
+			} else {
+				vaultPaths.forEach(this::addVault);
 			}
+			event.setDropCompleted(!vaultPaths.isEmpty());
 			event.consume();
-		});
+		} else if (DragEvent.DRAG_EXITED.equals(event.getEventType())) {
+			draggingOver.set(false);
+			draggingVaultOver.set(false);
+		}
+	}
+
+	private boolean containsVault(Path path) {
+		if (path.getFileName().toString().equals(MASTERKEY_FILENAME)) {
+			return true;
+		} else if (Files.isDirectory(path) && Files.exists(path.resolve(MASTERKEY_FILENAME))) {
+			return true;
+		} else {
+			return false;
+		}
 	}
 
-	private Stream<Vault> addVault(Path pathToVault) {
+	private void addVault(Path pathToVault) {
 		try {
 			if (pathToVault.getFileName().toString().equals(MASTERKEY_FILENAME)) {
-				return Stream.of(vaultListManager.add(pathToVault.getParent()));
+				vaultListManager.add(pathToVault.getParent());
 			} else {
-				return Stream.of(vaultListManager.add(pathToVault));
+				vaultListManager.add(pathToVault);
 			}
 		} catch (NoSuchFileException e) {
 			LOG.debug("Not a vault: {}", pathToVault);
 		}
-		return Stream.empty();
 	}
 
 	@FXML
@@ -133,4 +148,20 @@ public class MainWindowController implements FxController {
 	public boolean isUpdateAvailable() {
 		return updateAvailable.get();
 	}
+
+	public BooleanProperty draggingOverProperty() {
+		return draggingOver;
+	}
+
+	public boolean isDraggingOver() {
+		return draggingOver.get();
+	}
+
+	public BooleanProperty draggingVaultOverProperty() {
+		return draggingVaultOver;
+	}
+
+	public boolean isDraggingVaultOver() {
+		return draggingVaultOver.get();
+	}
 }

+ 10 - 0
main/ui/src/main/resources/css/dark_theme.css

@@ -201,6 +201,16 @@
 	-fx-translate-y: 1px;
 }
 
+.main-window .drag-n-drop-indicator {
+	-fx-border-color: INDICATOR_BG;
+	-fx-border-width: 3px;
+}
+
+.main-window .drag-n-drop-indicator .drag-n-drop-header {
+	-fx-background-color: INDICATOR_BG;
+	-fx-padding: 3px;
+}
+
 /*******************************************************************************
  *                                                                             *
  * TabPane                                                                     *

+ 10 - 0
main/ui/src/main/resources/css/light_theme.css

@@ -201,6 +201,16 @@
 	-fx-translate-y: 1px;
 }
 
+.main-window .drag-n-drop-indicator {
+	-fx-border-color: INDICATOR_BG;
+	-fx-border-width: 3px;
+}
+
+.main-window .drag-n-drop-indicator .drag-n-drop-header {
+	-fx-background-color: INDICATOR_BG;
+	-fx-padding: 3px;
+}
+
 /*******************************************************************************
  *                                                                             *
  * TabPane                                                                     *

+ 13 - 10
main/ui/src/main/resources/fxml/main_window.fxml

@@ -9,9 +9,7 @@
 <?import javafx.scene.layout.Region?>
 <?import javafx.scene.layout.StackPane?>
 <?import javafx.scene.layout.VBox?>
-<?import javafx.scene.shape.Rectangle?>
 <?import org.cryptomator.ui.controls.FontAwesome5IconView?>
-<?import javafx.scene.layout.Pane?>
 <VBox xmlns="http://javafx.com/javafx"
 	  xmlns:fx="http://javafx.com/fxml"
 	  fx:id="root"
@@ -50,14 +48,19 @@
 			<fx:include source="/fxml/vault_list.fxml" SplitPane.resizableWithParent="false"/>
 			<fx:include source="/fxml/vault_detail.fxml" SplitPane.resizableWithParent="true"/>
 		</SplitPane>
+
 		<Region styleClass="resizer" StackPane.alignment="BOTTOM_RIGHT" fx:id="resizer" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/>
-		<!-- TODO: use css instead of adding a Rectangle: -->
-		<Pane fx:id="dragAndDropIndicator">
-			<StackPane.margin>
-				<Insets topRightBottomLeft="24"/>
-			</StackPane.margin>
-			<Rectangle arcHeight="4" arcWidth="4" fill="gainsboro" strokeType="CENTERED" strokeWidth="3" strokeDashArray="20, 20" strokeLineJoin="ROUND" stroke="black" height="${dragAndDropIndicator.height}"
-					   width="${dragAndDropIndicator.width}"/>
-		</Pane>
+
+		<VBox styleClass="drag-n-drop-indicator" visible="${controller.draggingOver}" alignment="TOP_CENTER">
+			<HBox visible="${!controller.draggingVaultOver}" managed="${!controller.draggingVaultOver}" spacing="6" styleClass="drag-n-drop-header" alignment="CENTER" VBox.vgrow="NEVER">
+				<FontAwesome5IconView glyph="EXCLAMATION_TRIANGLE"/>
+				<Label text="%main.dropZone.unknownDragboardContent"/>
+			</HBox>
+			<HBox visible="${controller.draggingVaultOver}" managed="${controller.draggingVaultOver}" spacing="6" styleClass="drag-n-drop-header" alignment="CENTER" VBox.vgrow="NEVER">
+				<FontAwesome5IconView glyph="CHECK"/>
+				<Label text="%main.dropZone.dropVault"/>
+			</HBox>
+			<Region VBox.vgrow="ALWAYS"/>
+		</VBox>
 	</StackPane>
 </VBox>

+ 3 - 0
main/ui/src/main/resources/i18n/strings.properties

@@ -132,6 +132,9 @@ preferences.donationKey.getDonationKey=Get a donation key
 # Main Window
 main.closeBtn.tooltip=Close
 main.preferencesBtn.tooltip=Preferences
+## Drag 'n' Drop
+main.dropZone.dropVault=Add this vault
+main.dropZone.unknownDragboardContent=If you want to add a vault, drag it to this window
 ## Vault List
 main.vaultlist.emptyList.onboardingInstruction=Click here to add a vault
 main.vaultlist.contextMenu.remove=Remove Vault