Merge pull request #3467 from cryptomator/feature/redesign-mainwindow

Feature: Redesigned MainWindow
This commit is contained in:
mindmonk 2024-10-04 12:13:09 +02:00 committed by GitHub
commit 8d7bf3a370
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 373 additions and 637 deletions

View File

@ -67,8 +67,8 @@ public class Settings {
public final StringProperty quickAccessService; public final StringProperty quickAccessService;
public final ObjectProperty<NodeOrientation> userInterfaceOrientation; public final ObjectProperty<NodeOrientation> userInterfaceOrientation;
public final StringProperty licenseKey; public final StringProperty licenseKey;
public final BooleanProperty showMinimizeButton;
public final BooleanProperty showTrayIcon; public final BooleanProperty showTrayIcon;
public final BooleanProperty compactMode;
public final IntegerProperty windowXPosition; public final IntegerProperty windowXPosition;
public final IntegerProperty windowYPosition; public final IntegerProperty windowYPosition;
public final IntegerProperty windowWidth; public final IntegerProperty windowWidth;
@ -105,8 +105,8 @@ public class Settings {
this.keychainProvider = new SimpleStringProperty(this, "keychainProvider", json.keychainProvider); this.keychainProvider = new SimpleStringProperty(this, "keychainProvider", json.keychainProvider);
this.userInterfaceOrientation = new SimpleObjectProperty<>(this, "userInterfaceOrientation", parseEnum(json.uiOrientation, NodeOrientation.class, NodeOrientation.LEFT_TO_RIGHT)); this.userInterfaceOrientation = new SimpleObjectProperty<>(this, "userInterfaceOrientation", parseEnum(json.uiOrientation, NodeOrientation.class, NodeOrientation.LEFT_TO_RIGHT));
this.licenseKey = new SimpleStringProperty(this, "licenseKey", json.licenseKey); this.licenseKey = new SimpleStringProperty(this, "licenseKey", json.licenseKey);
this.showMinimizeButton = new SimpleBooleanProperty(this, "showMinimizeButton", json.showMinimizeButton);
this.showTrayIcon = new SimpleBooleanProperty(this, "showTrayIcon", json.showTrayIcon); this.showTrayIcon = new SimpleBooleanProperty(this, "showTrayIcon", json.showTrayIcon);
this.compactMode = new SimpleBooleanProperty(this, "compactMode", json.compactMode);
this.windowXPosition = new SimpleIntegerProperty(this, "windowXPosition", json.windowXPosition); this.windowXPosition = new SimpleIntegerProperty(this, "windowXPosition", json.windowXPosition);
this.windowYPosition = new SimpleIntegerProperty(this, "windowYPosition", json.windowYPosition); this.windowYPosition = new SimpleIntegerProperty(this, "windowYPosition", json.windowYPosition);
this.windowWidth = new SimpleIntegerProperty(this, "windowWidth", json.windowWidth); this.windowWidth = new SimpleIntegerProperty(this, "windowWidth", json.windowWidth);
@ -134,8 +134,8 @@ public class Settings {
keychainProvider.addListener(this::somethingChanged); keychainProvider.addListener(this::somethingChanged);
userInterfaceOrientation.addListener(this::somethingChanged); userInterfaceOrientation.addListener(this::somethingChanged);
licenseKey.addListener(this::somethingChanged); licenseKey.addListener(this::somethingChanged);
showMinimizeButton.addListener(this::somethingChanged);
showTrayIcon.addListener(this::somethingChanged); showTrayIcon.addListener(this::somethingChanged);
compactMode.addListener(this::somethingChanged);
windowXPosition.addListener(this::somethingChanged); windowXPosition.addListener(this::somethingChanged);
windowYPosition.addListener(this::somethingChanged); windowYPosition.addListener(this::somethingChanged);
windowWidth.addListener(this::somethingChanged); windowWidth.addListener(this::somethingChanged);
@ -190,8 +190,8 @@ public class Settings {
json.keychainProvider = keychainProvider.get(); json.keychainProvider = keychainProvider.get();
json.uiOrientation = userInterfaceOrientation.get().name(); json.uiOrientation = userInterfaceOrientation.get().name();
json.licenseKey = licenseKey.get(); json.licenseKey = licenseKey.get();
json.showMinimizeButton = showMinimizeButton.get();
json.showTrayIcon = showTrayIcon.get(); json.showTrayIcon = showTrayIcon.get();
json.compactMode = compactMode.get();
json.windowXPosition = windowXPosition.get(); json.windowXPosition = windowXPosition.get();
json.windowYPosition = windowYPosition.get(); json.windowYPosition = windowYPosition.get();
json.windowWidth = windowWidth.get(); json.windowWidth = windowWidth.get();

View File

@ -51,12 +51,12 @@ class SettingsJson {
@JsonProperty("port") @JsonProperty("port")
int port = Settings.DEFAULT_PORT; int port = Settings.DEFAULT_PORT;
@JsonProperty("showMinimizeButton")
boolean showMinimizeButton = Settings.DEFAULT_SHOW_MINIMIZE_BUTTON;
@JsonProperty("showTrayIcon") @JsonProperty("showTrayIcon")
boolean showTrayIcon; boolean showTrayIcon;
@JsonProperty("compactMode")
boolean compactMode;
@JsonProperty("startHidden") @JsonProperty("startHidden")
boolean startHidden = Settings.DEFAULT_START_HIDDEN; boolean startHidden = Settings.DEFAULT_START_HIDDEN;

View File

@ -0,0 +1,96 @@
package org.cryptomator.ui.controls;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
public class NotificationBar extends HBox {
@FXML
private Label notificationLabel;
private final BooleanProperty dismissable = new SimpleBooleanProperty();
private final BooleanProperty notify = new SimpleBooleanProperty();
public NotificationBar() {
setAlignment(Pos.CENTER);
setStyle("-fx-alignment: center;");
Region spacer = new Region();
spacer.setMinWidth(40);
Region leftRegion = new Region();
HBox.setHgrow(leftRegion, javafx.scene.layout.Priority.ALWAYS);
Region rightRegion = new Region();
HBox.setHgrow(rightRegion, javafx.scene.layout.Priority.ALWAYS);
VBox vbox = new VBox();
vbox.setAlignment(Pos.CENTER);
HBox.setHgrow(vbox, javafx.scene.layout.Priority.ALWAYS);
notificationLabel = new Label();
notificationLabel.getStyleClass().add("notification-label");
notificationLabel.setStyle("-fx-alignment: center;");
vbox.getChildren().add(notificationLabel);
Button closeButton = new Button("X");
closeButton.setMinWidth(40);
closeButton.setStyle("-fx-background-color: transparent; -fx-text-fill: white; -fx-font-weight: bold;");
closeButton.visibleProperty().bind(dismissable);
closeButton.setOnAction(_ -> {
visibleProperty().unbind();
managedProperty().unbind();
visibleProperty().set(false);
managedProperty().set(false);
});
closeButton.visibleProperty().bind(dismissable);
getChildren().addAll(spacer, leftRegion, vbox, rightRegion, closeButton);
visibleProperty().bind(notifyProperty());
managedProperty().bind(notifyProperty());
}
public String getText() {
return notificationLabel.getText();
}
public void setText(String text) {
notificationLabel.setText(text);
}
public void setStyleClass(String styleClass) {
getStyleClass().clear();
getStyleClass().add(styleClass);
}
public boolean isDismissable() {
return dismissable.get();
}
public void setDismissable(boolean value) {
dismissable.set(value);
}
public boolean getNotify() {
return notify.get();
}
public void setNotify(boolean value) {
notify.set(value);
}
public BooleanProperty notifyProperty() {
return notify;
}
}

View File

@ -1,19 +1,26 @@
package org.cryptomator.ui.mainwindow; package org.cryptomator.ui.mainwindow;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.FxController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.fxapp.UpdateChecker;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
@MainWindowScoped @MainWindowScoped
public class MainWindowController implements FxController { public class MainWindowController implements FxController {
@ -22,22 +29,61 @@ public class MainWindowController implements FxController {
private final Stage window; private final Stage window;
private final ReadOnlyObjectProperty<Vault> selectedVault; private final ReadOnlyObjectProperty<Vault> selectedVault;
private final Settings settings;
private final FxApplicationWindows appWindows;
private final BooleanBinding updateAvailable;
private final LicenseHolder licenseHolder;
public StackPane root; @FXML
private StackPane root;
@Inject @Inject
public MainWindowController(@MainWindow Stage window, ObjectProperty<Vault> selectedVault) { public MainWindowController(@MainWindow Stage window, //
ObjectProperty<Vault> selectedVault, //
Settings settings, //
FxApplicationWindows appWindows, //
UpdateChecker updateChecker, //
LicenseHolder licenseHolder) {
this.window = window; this.window = window;
this.selectedVault = selectedVault; this.selectedVault = selectedVault;
this.settings = settings;
this.appWindows = appWindows;
this.updateAvailable = updateChecker.updateAvailableProperty();
this.licenseHolder = licenseHolder;
updateChecker.automaticallyCheckForUpdatesIfEnabled();
} }
@FXML @FXML
public void initialize() { public void initialize() {
LOG.trace("init MainWindowController"); LOG.trace("init MainWindowController");
if (SystemUtils.IS_OS_WINDOWS) { if (SystemUtils.IS_OS_WINDOWS) {
root.getStyleClass().add("os-windows"); root.getStyleClass().add("os-windows");
} }
window.focusedProperty().addListener(this::mainWindowFocusChanged); window.focusedProperty().addListener(this::mainWindowFocusChanged);
if (!neverTouched()) {
window.setHeight(settings.windowHeight.get() > window.getMinHeight() ? settings.windowHeight.get() : window.getMinHeight());
window.setWidth(settings.windowWidth.get() > window.getMinWidth() ? settings.windowWidth.get() : window.getMinWidth());
window.setX(settings.windowXPosition.get());
window.setY(settings.windowYPosition.get());
}
window.widthProperty().addListener((_, _, _) -> savePositionalSettings());
window.heightProperty().addListener((_, _, _) -> savePositionalSettings());
window.xProperty().addListener((_, _, _) -> savePositionalSettings());
window.yProperty().addListener((_, _, _) -> savePositionalSettings());
}
private boolean neverTouched() {
return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0);
}
public void savePositionalSettings() {
settings.windowWidth.setValue(window.getWidth());
settings.windowHeight.setValue(window.getHeight());
settings.windowXPosition.setValue(window.getX());
settings.windowYPosition.setValue(window.getY());
} }
private void mainWindowFocusChanged(Observable observable) { private void mainWindowFocusChanged(Observable observable) {
@ -47,4 +93,43 @@ public class MainWindowController implements FxController {
} }
} }
@FXML
public void showGeneralPreferences() {
appWindows.showPreferencesWindow(SelectedPreferencesTab.GENERAL);
}
@FXML
public void showContributePreferences() {
appWindows.showPreferencesWindow(SelectedPreferencesTab.CONTRIBUTE);
}
@FXML
public void showUpdatePreferences() {
appWindows.showPreferencesWindow(SelectedPreferencesTab.UPDATES);
}
public ReadOnlyBooleanProperty debugModeEnabledProperty() {
return settings.debugMode;
}
public boolean getDebugModeEnabled() {
return debugModeEnabledProperty().get();
}
public BooleanBinding updateAvailableProperty() {
return updateAvailable;
}
public boolean getUpdateAvailable() {
return updateAvailable.get();
}
public BooleanBinding licenseValidProperty(){
return licenseHolder.validLicenseProperty();
}
public boolean getLicenseValid() {
return licenseHolder.isValidLicense();
}
} }

View File

@ -6,7 +6,6 @@ import dagger.Provides;
import dagger.multibindings.IntoMap; import dagger.multibindings.IntoMap;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent; import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
import org.cryptomator.ui.error.ErrorComponent;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlFile;
@ -14,8 +13,8 @@ import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory; import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.common.StageInitializer; import org.cryptomator.ui.common.StageInitializer;
import org.cryptomator.ui.error.ErrorComponent;
import org.cryptomator.ui.fxapp.PrimaryStage; import org.cryptomator.ui.fxapp.PrimaryStage;
import org.cryptomator.ui.health.HealthCheckComponent;
import org.cryptomator.ui.migration.MigrationComponent; import org.cryptomator.ui.migration.MigrationComponent;
import org.cryptomator.ui.removevault.RemoveVaultComponent; import org.cryptomator.ui.removevault.RemoveVaultComponent;
import org.cryptomator.ui.stats.VaultStatisticsComponent; import org.cryptomator.ui.stats.VaultStatisticsComponent;
@ -28,7 +27,6 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.StageStyle;
import java.util.Map; import java.util.Map;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@ -41,9 +39,8 @@ abstract class MainWindowModule {
static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer) { static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer) {
initializer.accept(stage); initializer.accept(stage);
stage.setTitle("Cryptomator"); stage.setTitle("Cryptomator");
stage.initStyle(StageStyle.UNDECORATED);
stage.setMinWidth(650); stage.setMinWidth(650);
stage.setMinHeight(440); stage.setMinHeight(498);
return stage; return stage;
} }
@ -85,16 +82,6 @@ abstract class MainWindowModule {
@FxControllerKey(MainWindowController.class) @FxControllerKey(MainWindowController.class)
abstract FxController bindMainWindowController(MainWindowController controller); abstract FxController bindMainWindowController(MainWindowController controller);
@Binds
@IntoMap
@FxControllerKey(MainWindowTitleController.class)
abstract FxController bindMainWindowTitleController(MainWindowTitleController controller);
@Binds
@IntoMap
@FxControllerKey(ResizeController.class)
abstract FxController bindResizeController(ResizeController controller);
@Binds @Binds
@IntoMap @IntoMap
@FxControllerKey(VaultListController.class) @FxControllerKey(VaultListController.class)

View File

@ -18,22 +18,20 @@ public class MainWindowSceneFactory extends DefaultSceneFactory {
protected static final KeyCodeCombination SHORTCUT_N = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN); protected static final KeyCodeCombination SHORTCUT_N = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN);
protected static final KeyCodeCombination SHORTCUT_O = new KeyCodeCombination(KeyCode.O, KeyCombination.SHORTCUT_DOWN); protected static final KeyCodeCombination SHORTCUT_O = new KeyCodeCombination(KeyCode.O, KeyCombination.SHORTCUT_DOWN);
private final Lazy<MainWindowTitleController> mainWindowTitleController; private final Stage window;
private final Lazy<VaultListController> vaultListController; private final Lazy<VaultListController> vaultListController;
@Inject @Inject
public MainWindowSceneFactory(Settings settings, Lazy<MainWindowTitleController> mainWindowTitleController, Lazy<VaultListController> vaultListController) { public MainWindowSceneFactory(Settings settings, @MainWindow Stage window, Lazy<VaultListController> vaultListController) {
super(settings); super(settings);
this.mainWindowTitleController = mainWindowTitleController; this.window = window;
this.vaultListController = vaultListController; this.vaultListController = vaultListController;
} }
@Override @Override
protected void setupDefaultAccelerators(Scene scene, Stage stage) { protected void setupDefaultAccelerators(Scene scene, Stage stage) {
if (SystemUtils.IS_OS_WINDOWS) { if (!SystemUtils.IS_OS_WINDOWS) {
scene.getAccelerators().put(ALT_F4, mainWindowTitleController.get()::close); scene.getAccelerators().put(SHORTCUT_W, window::close);
} else {
scene.getAccelerators().put(SHORTCUT_W, mainWindowTitleController.get()::close);
} }
scene.getAccelerators().put(SHORTCUT_N, vaultListController.get()::didClickAddNewVault); scene.getAccelerators().put(SHORTCUT_N, vaultListController.get()::didClickAddNewVault);
scene.getAccelerators().put(SHORTCUT_O, vaultListController.get()::didClickAddExistingVault); scene.getAccelerators().put(SHORTCUT_O, vaultListController.get()::didClickAddExistingVault);

View File

@ -1,157 +0,0 @@
package org.cryptomator.ui.mainwindow;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplicationTerminator;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.fxapp.UpdateChecker;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.fxml.FXML;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
@MainWindowScoped
public class MainWindowTitleController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(MainWindowTitleController.class);
private final Stage window;
private final FxApplicationTerminator terminator;
private final FxApplicationWindows appWindows;
private final boolean trayMenuInitialized;
private final UpdateChecker updateChecker;
private final BooleanBinding updateAvailable;
private final LicenseHolder licenseHolder;
private final Settings settings;
private final BooleanBinding showMinimizeButton;
public HBox titleBar;
private double xOffset;
private double yOffset;
@Inject
MainWindowTitleController(@MainWindow Stage window, FxApplicationTerminator terminator, FxApplicationWindows appWindows, TrayMenuComponent trayMenu, UpdateChecker updateChecker, LicenseHolder licenseHolder, Settings settings) {
this.window = window;
this.terminator = terminator;
this.appWindows = appWindows;
this.trayMenuInitialized = trayMenu.isInitialized();
this.updateChecker = updateChecker;
this.updateAvailable = updateChecker.updateAvailableProperty();
this.licenseHolder = licenseHolder;
this.settings = settings;
this.showMinimizeButton = Bindings.createBooleanBinding(this::isShowMinimizeButton, settings.showMinimizeButton, settings.showTrayIcon);
}
@FXML
public void initialize() {
LOG.trace("init MainWindowTitleController");
updateChecker.automaticallyCheckForUpdatesIfEnabled();
titleBar.setOnMousePressed(event -> {
xOffset = event.getSceneX();
yOffset = event.getSceneY();
});
titleBar.setOnMouseClicked(event -> {
if (event.getButton().equals(MouseButton.PRIMARY) && event.getClickCount() == 2) {
window.setFullScreen(!window.isFullScreen());
}
});
titleBar.setOnMouseDragged(event -> {
if (window.isFullScreen()) return;
window.setX(event.getScreenX() - xOffset);
window.setY(event.getScreenY() - yOffset);
});
titleBar.setOnDragDetected(mouseDragEvent -> {
titleBar.startFullDrag();
});
titleBar.setOnMouseDragReleased(mouseDragEvent -> {
saveWindowSettings();
});
window.setOnCloseRequest(event -> {
close();
event.consume();
});
}
private void saveWindowSettings() {
settings.windowXPosition.setValue(window.getX());
settings.windowYPosition.setValue(window.getY());
settings.windowWidth.setValue(window.getWidth());
settings.windowHeight.setValue(window.getHeight());
}
@FXML
public void close() {
if (trayMenuInitialized) {
window.close();
} else {
terminator.terminate();
}
}
@FXML
public void minimize() {
window.setIconified(true);
}
@FXML
public void showPreferences() {
appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY);
}
@FXML
public void showGeneralPreferences() {
appWindows.showPreferencesWindow(SelectedPreferencesTab.GENERAL);
}
@FXML
public void showContributePreferences() {
appWindows.showPreferencesWindow(SelectedPreferencesTab.CONTRIBUTE);
}
/* Getter/Setter */
public LicenseHolder getLicenseHolder() {
return licenseHolder;
}
public BooleanBinding updateAvailableProperty() {
return updateAvailable;
}
public boolean isUpdateAvailable() {
return updateAvailable.get();
}
public boolean isTrayIconPresent() {
return trayMenuInitialized;
}
public ReadOnlyBooleanProperty debugModeEnabledProperty() {
return settings.debugMode;
}
public boolean isDebugModeEnabled() {
return debugModeEnabledProperty().get();
}
public BooleanBinding showMinimizeButtonProperty() {
return showMinimizeButton;
}
public boolean isShowMinimizeButton() {
// always show the minimize button if no tray icon is present OR it is explicitly enabled
return !trayMenuInitialized || settings.showMinimizeButton.get();
}
}

View File

@ -1,194 +0,0 @@
package org.cryptomator.ui.mainwindow;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.common.FxController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.binding.BooleanBinding;
import javafx.fxml.FXML;
import javafx.geometry.Rectangle2D;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
@MainWindow
public class ResizeController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(ResizeController.class);
private final Stage window;
public Region tlResizer;
public Region trResizer;
public Region blResizer;
public Region brResizer;
public Region tResizer;
public Region rResizer;
public Region bResizer;
public Region lResizer;
public Region lDefaultRegion;
public Region tDefaultRegion;
public Region rDefaultRegion;
public Region bDefaultRegion;
private double origX, origY, origW, origH;
private final Settings settings;
private final BooleanBinding showResizingArrows;
@Inject
ResizeController(@MainWindow Stage window, Settings settings) {
this.window = window;
this.settings = settings;
this.showResizingArrows = window.fullScreenProperty().not();
}
@FXML
public void initialize() {
LOG.trace("init ResizeController");
if (!neverTouched()) {
window.setHeight(settings.windowHeight.get() > window.getMinHeight() ? settings.windowHeight.get() : window.getMinHeight());
window.setWidth(settings.windowWidth.get() > window.getMinWidth() ? settings.windowWidth.get() : window.getMinWidth());
window.setX(settings.windowXPosition.get());
window.setY(settings.windowYPosition.get());
}
window.setOnShowing(this::checkDisplayBounds);
}
private boolean neverTouched() {
return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0);
}
private void checkDisplayBounds(WindowEvent evt) {
// Minimizing a window in Windows and closing it could result in an out of bounds position at (x, y) = (-32000, -32000)
// See https://devblogs.microsoft.com/oldnewthing/20041028-00/?p=37453
// If the position is (-32000, -32000), restore to the last saved position
if (window.getX() == -32000 && window.getY() == -32000) {
window.setX(settings.windowXPosition.get());
window.setY(settings.windowYPosition.get());
window.setWidth(settings.windowWidth.get());
window.setHeight(settings.windowHeight.get());
}
if (isOutOfDisplayBounds()) {
// If the position is illegal, then the window appears on the main screen in the middle of the window.
LOG.debug("Resetting window position due to insufficient screen overlap");
Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds();
window.setX((primaryScreenBounds.getWidth() - window.getMinWidth()) / 2);
window.setY((primaryScreenBounds.getHeight() - window.getMinHeight()) / 2);
window.setWidth(window.getMinWidth());
window.setHeight(window.getMinHeight());
savePositionalSettings();
}
}
private boolean isOutOfDisplayBounds() {
// define a rect which is inset on all sides from the window's rect:
final double x = window.getX() + 20; // 20px left
final double y = window.getY() + 5; // 5px top
final double w = window.getWidth() - 40; // 20px left + 20px right
final double h = window.getHeight() - 25; // 5px top + 20px bottom
return isRectangleOutOfScreen(x, y, 0, h) // Left pixel column
|| isRectangleOutOfScreen(x + w, y, 0, h) // Right pixel column
|| isRectangleOutOfScreen(x, y, w, 0) // Top pixel row
|| isRectangleOutOfScreen(x, y + h, w, 0); // Bottom pixel row
}
private boolean isRectangleOutOfScreen(double x, double y, double width, double height) {
return Screen.getScreensForRectangle(x, y, width, height).isEmpty();
}
private void startResize(MouseEvent evt) {
origX = window.getX();
origY = window.getY();
origW = window.getWidth();
origH = window.getHeight();
}
@FXML
private void resizeTopLeft(MouseEvent evt) {
resizeTop(evt);
resizeLeft(evt);
}
@FXML
private void resizeTopRight(MouseEvent evt) {
resizeTop(evt);
resizeRight(evt);
}
@FXML
private void resizeBottomLeft(MouseEvent evt) {
resizeBottom(evt);
resizeLeft(evt);
}
@FXML
private void resizeBottomRight(MouseEvent evt) {
resizeBottom(evt);
resizeRight(evt);
}
@FXML
private void resizeTop(MouseEvent evt) {
startResize(evt);
double newY = evt.getScreenY();
double dy = newY - origY;
double newH = origH - dy;
if (newH < window.getMaxHeight() && newH > window.getMinHeight()) {
window.setY(newY);
window.setHeight(newH);
}
}
@FXML
private void resizeLeft(MouseEvent evt) {
startResize(evt);
double newX = evt.getScreenX();
double dx = newX - origX;
double newW = origW - dx;
if (newW < window.getMaxWidth() && newW > window.getMinWidth()) {
window.setX(newX);
window.setWidth(newW);
}
}
@FXML
private void resizeBottom(MouseEvent evt) {
double newH = evt.getSceneY();
if (newH < window.getMaxHeight() && newH > window.getMinHeight()) {
window.setHeight(newH);
}
}
@FXML
private void resizeRight(MouseEvent evt) {
double newW = evt.getSceneX();
if (newW < window.getMaxWidth() && newW > window.getMinWidth()) {
window.setWidth(newW);
}
}
@FXML
public void savePositionalSettings() {
settings.windowWidth.setValue(window.getWidth());
settings.windowHeight.setValue(window.getHeight());
settings.windowXPosition.setValue(window.getX());
settings.windowYPosition.setValue(window.getY());
}
public BooleanBinding showResizingArrowsProperty() {
return showResizingArrows;
}
public boolean isShowResizingArrows() {
return showResizingArrows.get();
}
}

View File

@ -1,5 +1,6 @@
package org.cryptomator.ui.mainwindow; package org.cryptomator.ui.mainwindow;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState; import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.common.Animations; import org.cryptomator.ui.common.Animations;
@ -18,6 +19,7 @@ public class VaultListCellController implements FxController {
private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>(); private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
private final ObservableValue<FontAwesome5Icon> glyph; private final ObservableValue<FontAwesome5Icon> glyph;
private final ObservableValue<Boolean> compactMode;
private AutoAnimator spinAnimation; private AutoAnimator spinAnimation;
@ -25,8 +27,9 @@ public class VaultListCellController implements FxController {
public FontAwesome5IconView vaultStateView; public FontAwesome5IconView vaultStateView;
@Inject @Inject
VaultListCellController() { VaultListCellController(Settings settings) {
this.glyph = vault.flatMap(Vault::stateProperty).map(this::getGlyphForVaultState); this.glyph = vault.flatMap(Vault::stateProperty).map(this::getGlyphForVaultState);
this.compactMode = settings.compactMode;
} }
public void initialize() { public void initialize() {
@ -68,6 +71,14 @@ public class VaultListCellController implements FxController {
return vault.get(); return vault.get();
} }
public ObservableValue<Boolean> compactModeProperty() {
return compactMode;
}
public boolean getCompactMode() {
return compactMode.getValue();
}
public void setVault(Vault value) { public void setVault(Vault value) {
vault.set(value); vault.set(value);
} }

View File

@ -1,6 +1,7 @@
package org.cryptomator.ui.mainwindow; package org.cryptomator.ui.mainwindow;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptofs.CryptoFileSystemProvider; import org.cryptomator.cryptofs.CryptoFileSystemProvider;
@ -9,6 +10,7 @@ import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.VaultService; import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.removevault.RemoveVaultComponent; import org.cryptomator.ui.removevault.RemoveVaultComponent;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -24,7 +26,6 @@ import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Side; import javafx.geometry.Side;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu; import javafx.scene.control.ContextMenu;
import javafx.scene.control.ListView; import javafx.scene.control.ListView;
import javafx.scene.input.ContextMenuEvent; import javafx.scene.input.ContextMenuEvent;
@ -33,6 +34,7 @@ import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent; import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode; import javafx.scene.input.TransferMode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.io.File; import java.io.File;
@ -69,10 +71,11 @@ public class VaultListController implements FxController {
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty(); private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
private final ResourceBundle resourceBundle; private final ResourceBundle resourceBundle;
private final FxApplicationWindows appWindows; private final FxApplicationWindows appWindows;
private final ObservableValue<Double> cellSize;
public ListView<Vault> vaultList; public ListView<Vault> vaultList;
public StackPane root; public StackPane root;
public Button addVaultBtn; @FXML
private HBox addVaultButton;
@FXML @FXML
private ContextMenu addVaultContextMenu; private ContextMenu addVaultContextMenu;
@ -86,7 +89,8 @@ public class VaultListController implements FxController {
RemoveVaultComponent.Builder removeVaultDialogue, // RemoveVaultComponent.Builder removeVaultDialogue, //
VaultListManager vaultListManager, // VaultListManager vaultListManager, //
ResourceBundle resourceBundle, // ResourceBundle resourceBundle, //
FxApplicationWindows appWindows) { FxApplicationWindows appWindows, //
Settings settings) {
this.mainWindow = mainWindow; this.mainWindow = mainWindow;
this.vaults = vaults; this.vaults = vaults;
this.selectedVault = selectedVault; this.selectedVault = selectedVault;
@ -101,11 +105,13 @@ public class VaultListController implements FxController {
this.emptyVaultList = Bindings.isEmpty(vaults); this.emptyVaultList = Bindings.isEmpty(vaults);
selectedVault.addListener(this::selectedVaultDidChange); selectedVault.addListener(this::selectedVaultDidChange);
cellSize = settings.compactMode.map(compact -> compact ? 30.0 : 60.0);
} }
public void initialize() { public void initialize() {
vaultList.setItems(vaults); vaultList.setItems(vaults);
vaultList.setCellFactory(cellFactory); vaultList.setCellFactory(cellFactory);
selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty()); selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty());
vaults.addListener((ListChangeListener.Change<? extends Vault> c) -> { vaults.addListener((ListChangeListener.Change<? extends Vault> c) -> {
while (c.next()) { while (c.next()) {
@ -171,7 +177,7 @@ public class VaultListController implements FxController {
if (addVaultContextMenu.isShowing()) { if (addVaultContextMenu.isShowing()) {
addVaultContextMenu.hide(); addVaultContextMenu.hide();
} else { } else {
addVaultContextMenu.show(addVaultBtn, Side.BOTTOM, 0.0, 0.0); addVaultContextMenu.show(addVaultButton, Side.BOTTOM, 0.0, 0.0);
} }
} }
@ -247,6 +253,11 @@ public class VaultListController implements FxController {
} }
} }
@FXML
public void showPreferences() {
appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY);
}
// Getter and Setter // Getter and Setter
public BooleanBinding emptyVaultListProperty() { public BooleanBinding emptyVaultListProperty() {
@ -265,5 +276,12 @@ public class VaultListController implements FxController {
return draggingVaultOver.get(); return draggingVaultOver.get();
} }
public ObservableValue<Double> cellSizeProperty() {
return cellSize;
}
public Double getCellSize() {
return cellSize.getValue();
}
} }

View File

@ -36,8 +36,8 @@ public class InterfacePreferencesController implements FxController {
private final ResourceBundle resourceBundle; private final ResourceBundle resourceBundle;
private final SupportedLanguages supportedLanguages; private final SupportedLanguages supportedLanguages;
public ChoiceBox<UiTheme> themeChoiceBox; public ChoiceBox<UiTheme> themeChoiceBox;
public CheckBox showMinimizeButtonCheckbox;
public CheckBox showTrayIconCheckbox; public CheckBox showTrayIconCheckbox;
public CheckBox compactModeCheckbox;
public ChoiceBox<String> preferredLanguageChoiceBox; public ChoiceBox<String> preferredLanguageChoiceBox;
public ToggleGroup nodeOrientation; public ToggleGroup nodeOrientation;
public RadioButton nodeOrientationLtr; public RadioButton nodeOrientationLtr;
@ -63,9 +63,8 @@ public class InterfacePreferencesController implements FxController {
themeChoiceBox.valueProperty().bindBidirectional(settings.theme); themeChoiceBox.valueProperty().bindBidirectional(settings.theme);
themeChoiceBox.setConverter(new UiThemeConverter(resourceBundle)); themeChoiceBox.setConverter(new UiThemeConverter(resourceBundle));
showMinimizeButtonCheckbox.selectedProperty().bindBidirectional(settings.showMinimizeButton);
showTrayIconCheckbox.selectedProperty().bindBidirectional(settings.showTrayIcon); showTrayIconCheckbox.selectedProperty().bindBidirectional(settings.showTrayIcon);
compactModeCheckbox.selectedProperty().bindBidirectional(settings.compactMode);
preferredLanguageChoiceBox.getItems().addAll(supportedLanguages.getLanguageTags()); preferredLanguageChoiceBox.getItems().addAll(supportedLanguages.getLanguageTags());
preferredLanguageChoiceBox.valueProperty().bindBidirectional(settings.language); preferredLanguageChoiceBox.valueProperty().bindBidirectional(settings.language);

View File

@ -181,31 +181,20 @@
-fx-border-width: 1px; -fx-border-width: 1px;
} }
.main-window .title { .main-window .button-bar {
-fx-background-color: CONTROL_BORDER_NORMAL, TITLE_BG; -fx-background-color: MAIN_BG;
-fx-background-insets: 0, 0 0 1px 0; -fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent;
-fx-border-width: 1px 0 0 0;
} }
.main-window .title .button { .main-window .button-left {
-fx-pref-height: 30px; -fx-border-color: CONTROL_BORDER_NORMAL;
-fx-pref-width: 30px; -fx-border-width: 0 1px 0 0;
-fx-background-color: none;
-fx-padding: 0;
} }
.main-window .title .button .glyph-icon { .main-window .button-right {
-fx-fill: white; -fx-border-color: CONTROL_BORDER_NORMAL;
} -fx-border-width: 0 0 0 1px;
.main-window .title .button:armed .glyph-icon {
-fx-fill: GRAY_8;
}
.main-window .update-indicator {
-fx-background-color: white, RED_5;
-fx-background-insets: 1px, 2px;
-fx-background-radius: 6px, 5px;
-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 2, 0, 0, 0);
} }
/******************************************************************************* /*******************************************************************************
@ -322,23 +311,33 @@
-fx-fill: transparent; -fx-fill: transparent;
} }
.button.toolbar-button { /*******************************************************************************
-fx-min-height: 40px; * *
-fx-background-color: transparent; * NotificationBar *
-fx-background-insets: 0; * *
-fx-background-radius: 0; ******************************************************************************/
-fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent;
-fx-border-width: 1px 0 0 0; .notification-label {
-fx-padding: 0; -fx-text-fill: white;
-fx-font-weight: bold;
} }
.button.toolbar-button:focused { .notification-debug {
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG; -fx-min-height:24px;
-fx-background-insets: 0, 2px 1px 1px 1px; -fx-max-height:24px;
-fx-background-color: RED_5;
} }
.button.toolbar-button:armed { .notification-update {
-fx-background-color: CONTROL_BG_ARMED; -fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: YELLOW_5;
}
.notification-support {
-fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: PRIMARY;
} }
/******************************************************************************* /*******************************************************************************
@ -394,16 +393,6 @@
-fx-background-color: MUTED_BG; -fx-background-color: MUTED_BG;
} }
/* Note: These values below are kinda random such that it looks ok. I'm pretty sure there is room for improvement. Additionally, fx-text-fill does not work*/
.badge-debug {
-fx-font-family: 'Open Sans Bold';
-fx-font-size: 1.0em;
-fx-background-radius: 8px;
-fx-padding: 0.3em 0.55em 0.3em 0.55em;
-fx-background-color: RED_5;
-fx-background-radius: 2em;
}
/******************************************************************************* /*******************************************************************************
* * * *
* Password Strength Indicator * * Password Strength Indicator *

View File

@ -177,34 +177,24 @@
/* windows needs an explicit border: */ /* windows needs an explicit border: */
.main-window.os-windows { .main-window.os-windows {
-fx-border-color: TITLE_BG; -fx-border-color: CONTROL_BORDER_NORMAL;
-fx-border-width: 1px; -fx-border-width: 1px 0 0 0;
} }
.main-window .title { .main-window .button-bar {
-fx-background-color: TITLE_BG; -fx-background-color: MAIN_BG;
-fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent;
-fx-border-width: 1px 0 0 0;
} }
.main-window .title .button { .main-window .button-bar .button-left {
-fx-pref-height: 30px; -fx-border-color: CONTROL_BORDER_NORMAL;
-fx-pref-width: 30px; -fx-border-width: 0 1px 0 0;
-fx-background-color: none;
-fx-padding: 0;
} }
.main-window .title .button .glyph-icon { .main-window .button-bar .button-right {
-fx-fill: white; -fx-border-color: CONTROL_BORDER_NORMAL;
} -fx-border-width: 0 0 0 1px;
.main-window .title .button:armed .glyph-icon {
-fx-fill: GRAY_8;
}
.main-window .update-indicator {
-fx-background-color: white, RED_5;
-fx-background-insets: 1px, 2px;
-fx-background-radius: 6px, 5px;
-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 2, 0, 0, 0);
} }
/******************************************************************************* /*******************************************************************************
@ -321,23 +311,33 @@
-fx-fill: transparent; -fx-fill: transparent;
} }
.button.toolbar-button { /*******************************************************************************
-fx-min-height: 40px; * *
-fx-background-color: transparent; * NotificationBar *
-fx-background-insets: 0; * *
-fx-background-radius: 0; ******************************************************************************/
-fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent;
-fx-border-width: 1px 0 0 0; .notification-label {
-fx-padding: 0; -fx-text-fill: white;
-fx-font-weight: bold;
} }
.button.toolbar-button:focused { .notification-debug {
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG; -fx-min-height:24px;
-fx-background-insets: 0, 2px 1px 1px 1px; -fx-max-height:24px;
-fx-background-color: RED_5;
} }
.button.toolbar-button:armed { .notification-update {
-fx-background-color: CONTROL_BG_ARMED; -fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: YELLOW_5;
}
.notification-support {
-fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: PRIMARY;
} }
/******************************************************************************* /*******************************************************************************
@ -393,16 +393,6 @@
-fx-background-color: MUTED_BG; -fx-background-color: MUTED_BG;
} }
/* Note: These values below are kinda random such that it looks ok. I'm pretty sure there is room for improvement. Additionally, fx-text-fill does not work*/
.badge-debug {
-fx-font-family: 'Open Sans Bold';
-fx-font-size: 1.0em;
-fx-background-radius: 8px;
-fx-padding: 0.3em 0.55em 0.3em 0.55em;
-fx-background-color: RED_5;
-fx-background-radius: 2em;
}
/******************************************************************************* /*******************************************************************************
* * * *
* Password Strength Indicator * * Password Strength Indicator *

View File

@ -3,17 +3,31 @@
<?import javafx.scene.control.SplitPane?> <?import javafx.scene.control.SplitPane?>
<?import javafx.scene.layout.StackPane?> <?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import org.cryptomator.ui.controls.NotificationBar?>
<StackPane xmlns:fx="http://javafx.com/fxml" <StackPane xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx" xmlns="http://javafx.com/javafx"
fx:id="root" fx:id="root"
fx:controller="org.cryptomator.ui.mainwindow.MainWindowController" fx:controller="org.cryptomator.ui.mainwindow.MainWindowController"
styleClass="main-window"> styleClass="main-window">
<VBox minWidth="650"> <VBox minWidth="600">
<fx:include source="main_window_title.fxml" VBox.vgrow="NEVER"/> <NotificationBar onMouseClicked="#showUpdatePreferences"
text="%main.notification.updateAvailable"
dismissable="true"
notify="${controller.updateAvailable}"
styleClass="notification-update"/>
<NotificationBar onMouseClicked="#showContributePreferences"
text="%main.notification.support"
dismissable="true"
notify="${!controller.licenseValid}"
styleClass="notification-support"/>
<SplitPane dividerPositions="0.33" orientation="HORIZONTAL" VBox.vgrow="ALWAYS"> <SplitPane dividerPositions="0.33" orientation="HORIZONTAL" VBox.vgrow="ALWAYS">
<fx:include source="vault_list.fxml" SplitPane.resizableWithParent="false"/> <fx:include source="vault_list.fxml" SplitPane.resizableWithParent="false"/>
<fx:include source="vault_detail.fxml" SplitPane.resizableWithParent="true"/> <fx:include source="vault_detail.fxml" SplitPane.resizableWithParent="true"/>
</SplitPane> </SplitPane>
<NotificationBar onMouseClicked="#showGeneralPreferences"
text="DEBUG MODE"
styleClass="notification-debug"
notify="${controller.debugModeEnabled}"/>
</VBox> </VBox>
<fx:include source="main_window_resize.fxml"/>
</StackPane> </StackPane>

View File

@ -1,30 +0,0 @@
<?import javafx.scene.Cursor?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.Region?>
<AnchorPane xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.mainwindow.ResizeController"
nodeOrientation="LEFT_TO_RIGHT"
pickOnBounds="false">
<fx:define>
<Cursor fx:id="nwResize" fx:constant="NW_RESIZE"/>
<Cursor fx:id="neResize" fx:constant="NE_RESIZE"/>
<Cursor fx:id="nsResize" fx:constant="N_RESIZE"/>
<Cursor fx:id="ewResize" fx:constant="E_RESIZE"/>
<Cursor fx:id="default" fx:constant="DEFAULT"/>
</fx:define>
<Region fx:id="tlResizer" cursor="${nwResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" visible="${controller.showResizingArrows}" managed="${controller.showResizingArrows}" onMouseDragged="#resizeTopLeft" onMouseReleased="#savePositionalSettings" AnchorPane.topAnchor="0" AnchorPane.leftAnchor="0"/>
<Region fx:id="trResizer" cursor="${neResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" visible="${controller.showResizingArrows}" managed="${controller.showResizingArrows}" onMouseDragged="#resizeTopRight" onMouseReleased="#savePositionalSettings" AnchorPane.topAnchor="0" AnchorPane.rightAnchor="0"/>
<Region fx:id="blResizer" cursor="${neResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" visible="${controller.showResizingArrows}" managed="${controller.showResizingArrows}" onMouseDragged="#resizeBottomLeft" onMouseReleased="#savePositionalSettings" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0"/>
<Region fx:id="brResizer" cursor="${nwResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" visible="${controller.showResizingArrows}" managed="${controller.showResizingArrows}" onMouseDragged="#resizeBottomRight" onMouseReleased="#savePositionalSettings" AnchorPane.bottomAnchor="0" AnchorPane.rightAnchor="0"/>
<Region fx:id="tResizer" cursor="${nsResize}" prefWidth="0" prefHeight="6" maxWidth="-Infinity" maxHeight="-Infinity" visible="${controller.showResizingArrows}" managed="${controller.showResizingArrows}" onMouseDragged="#resizeTop" onMouseReleased="#savePositionalSettings" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="0.0"/>
<Region fx:id="rResizer" cursor="${ewResize}" prefWidth="6" prefHeight="0" maxWidth="-Infinity" maxHeight="-Infinity" visible="${controller.showResizingArrows}" managed="${controller.showResizingArrows}" onMouseDragged="#resizeRight" onMouseReleased="#savePositionalSettings" AnchorPane.bottomAnchor="10.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="10.0"/>
<Region fx:id="bResizer" cursor="${nsResize}" prefWidth="6" prefHeight="6" maxWidth="-Infinity" maxHeight="-Infinity" visible="${controller.showResizingArrows}" managed="${controller.showResizingArrows}" onMouseDragged="#resizeBottom" onMouseReleased="#savePositionalSettings" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0"/>
<Region fx:id="lResizer" cursor="${ewResize}" prefWidth="6" prefHeight="6" maxWidth="-Infinity" maxHeight="-Infinity" visible="${controller.showResizingArrows}" managed="${controller.showResizingArrows}" onMouseDragged="#resizeLeft" onMouseReleased="#savePositionalSettings" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="10.0"/>
<Region fx:id="lDefaultRegion" cursor="${default}" prefWidth="1" prefHeight="1" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.bottomAnchor="-1" AnchorPane.leftAnchor="-1" AnchorPane.topAnchor="-1"/>
<Region fx:id="tDefaultRegion" cursor="${default}" prefWidth="1" prefHeight="1" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.leftAnchor="-1" AnchorPane.topAnchor="-1" AnchorPane.rightAnchor="-1"/>
<Region fx:id="rDefaultRegion" cursor="${default}" prefWidth="1" prefHeight="1" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.topAnchor="-1" AnchorPane.rightAnchor="-1" AnchorPane.bottomAnchor="-1"/>
<Region fx:id="bDefaultRegion" cursor="${default}" prefWidth="1" prefHeight="1" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.rightAnchor="-1" AnchorPane.bottomAnchor="-1" AnchorPane.leftAnchor="-1"/>
</AnchorPane>

View File

@ -1,78 +0,0 @@
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:id="titleBar"
fx:controller="org.cryptomator.ui.mainwindow.MainWindowTitleController"
styleClass="title"
alignment="CENTER"
minHeight="50"
maxHeight="50"
spacing="6">
<padding>
<Insets bottom="6" left="12" right="12" top="6"/>
</padding>
<children>
<ImageView HBox.hgrow="ALWAYS" fitHeight="14" preserveRatio="true" cache="true">
<Image url="@../img/title-logo.png"/>
</ImageView>
<Region HBox.hgrow="ALWAYS"/>
<Hyperlink onAction="#showGeneralPreferences" focusTraversable="false" visible="${controller.debugModeEnabled}" styleClass="badge-debug" text="DEBUG MODE" textFill="white">
<tooltip>
<Tooltip text="%main.debugModeEnabled.tooltip"/>
</tooltip>
</Hyperlink>
<Region HBox.hgrow="ALWAYS"/>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showContributePreferences" focusTraversable="false" visible="${!controller.licenseHolder.validLicense}">
<graphic>
<StackPane>
<FontAwesome5IconView glyph="EXCLAMATION_CIRCLE" glyphSize="16"/>
<Region styleClass="update-indicator" StackPane.alignment="TOP_RIGHT" prefWidth="12" prefHeight="12" maxWidth="-Infinity" maxHeight="-Infinity"/>
</StackPane>
</graphic>
<tooltip>
<Tooltip text="%main.supporterCertificateMissing.tooltip"/>
</tooltip>
</Button>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showPreferences" focusTraversable="false">
<graphic>
<StackPane>
<FontAwesome5IconView glyph="COGS" glyphSize="16"/>
<Region styleClass="update-indicator" visible="${controller.updateAvailable}" StackPane.alignment="TOP_RIGHT" prefWidth="12" prefHeight="12" maxWidth="-Infinity" maxHeight="-Infinity"/>
</StackPane>
</graphic>
<tooltip>
<Tooltip text="%main.preferencesBtn.tooltip"/>
</tooltip>
</Button>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#minimize" focusTraversable="false" visible="${controller.showMinimizeButton}" managed="${controller.showMinimizeButton}">
<graphic>
<FontAwesome5IconView glyph="WINDOW_MINIMIZE" glyphSize="12"/>
</graphic>
<tooltip>
<Tooltip text="%main.minimizeBtn.tooltip"/>
</tooltip>
</Button>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#close" focusTraversable="false">
<graphic>
<FontAwesome5IconView glyph="TIMES" glyphSize="16"/>
</graphic>
<tooltip>
<Tooltip text="%main.closeBtn.tooltip"/>
</tooltip>
</Button>
</children>
</HBox>

View File

@ -37,9 +37,7 @@
<RadioButton fx:id="nodeOrientationRtl" text="%preferences.interface.interfaceOrientation.rtl" alignment="CENTER_RIGHT" toggleGroup="${nodeOrientation}"/> <RadioButton fx:id="nodeOrientationRtl" text="%preferences.interface.interfaceOrientation.rtl" alignment="CENTER_RIGHT" toggleGroup="${nodeOrientation}"/>
</HBox> </HBox>
<CheckBox fx:id="showMinimizeButtonCheckbox" text="%preferences.interface.showMinimizeButton" visible="${controller.trayMenuInitialized}" managed="${controller.trayMenuInitialized}"/>
<CheckBox fx:id="showTrayIconCheckbox" text="%preferences.interface.showTrayIcon" visible="${controller.trayMenuSupported}" managed="${controller.trayMenuSupported}"/> <CheckBox fx:id="showTrayIconCheckbox" text="%preferences.interface.showTrayIcon" visible="${controller.trayMenuSupported}" managed="${controller.trayMenuSupported}"/>
<CheckBox fx:id="compactModeCheckbox" text="%preferences.interface.compactMode"/>
</children> </children>
</VBox> </VBox>

View File

@ -1,15 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?> <?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?> <?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.Region?> <?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?> <?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Arc?>
<?import javafx.scene.control.ContextMenu?> <?import javafx.scene.control.ContextMenu?>
<?import javafx.scene.control.MenuItem?> <?import javafx.scene.control.MenuItem?>
<?import javafx.scene.layout.HBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.shape.Arc?>
<StackPane xmlns:fx="http://javafx.com/fxml" <StackPane xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx" xmlns="http://javafx.com/javafx"
fx:id="root" fx:id="root"
@ -17,7 +18,7 @@
minWidth="206"> minWidth="206">
<VBox> <VBox>
<StackPane VBox.vgrow="ALWAYS"> <StackPane VBox.vgrow="ALWAYS">
<ListView fx:id="vaultList" editable="true" fixedCellSize="60"> <ListView fx:id="vaultList" editable="true" fixedCellSize="${controller.cellSize}">
<contextMenu> <contextMenu>
<fx:include source="vault_list_contextmenu.fxml"/> <fx:include source="vault_list_contextmenu.fxml"/>
</contextMenu> </contextMenu>
@ -25,30 +26,42 @@
<VBox visible="${controller.emptyVaultList}" spacing="6" alignment="CENTER"> <VBox visible="${controller.emptyVaultList}" spacing="6" alignment="CENTER">
<Region VBox.vgrow="ALWAYS"/> <Region VBox.vgrow="ALWAYS"/>
<Label VBox.vgrow="NEVER" text="%main.vaultlist.emptyList.onboardingInstruction" textAlignment="CENTER" wrapText="true"/> <Label VBox.vgrow="NEVER" text="%main.vaultlist.emptyList.onboardingInstruction" textAlignment="CENTER" wrapText="true"/>
<Arc VBox.vgrow="NEVER" styleClass="onboarding-overlay-arc" type="OPEN" centerX="50" centerY="0" radiusY="100" radiusX="50" startAngle="0" length="-60" strokeWidth="1"/> <HBox>
<Arc styleClass="onboarding-overlay-arc" translateX="20" radiusY="100" radiusX="50" length="-60"/>
</HBox>
</VBox> </VBox>
</StackPane> </StackPane>
<Button fx:id="addVaultBtn" onAction="#toggleMenu" styleClass="toolbar-button" text="%main.vaultlist.addVaultBtn" alignment="BASELINE_CENTER" maxWidth="Infinity" contentDisplay="RIGHT"> <HBox styleClass="button-bar">
<graphic> <HBox fx:id="addVaultButton" onMouseClicked="#toggleMenu" styleClass="button-left" alignment="CENTER" minWidth="20">
<FontAwesome5IconView glyph="CARET_DOWN"/> <padding>
</graphic> <Insets topRightBottomLeft="12"/>
</Button> </padding>
<fx:define> <FontAwesome5IconView glyph="PLUS" HBox.hgrow="NEVER" glyphSize="16"/>
<ContextMenu fx:id="addVaultContextMenu"> </HBox>
<items> <Region HBox.hgrow="ALWAYS"/>
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemNew" onAction="#didClickAddNewVault" > <HBox onMouseClicked="#showPreferences" styleClass="button-right" alignment="CENTER" minWidth="20">
<graphic> <padding>
<FontAwesome5IconView glyph="PLUS" textAlignment="CENTER" wrappingWidth="14" /> <Insets topRightBottomLeft="12"/>
</graphic> </padding>
</MenuItem> <FontAwesome5IconView glyph="COG" HBox.hgrow="NEVER" glyphSize="16"/>
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemExisting" onAction="#didClickAddExistingVault" > </HBox>
<graphic> </HBox>
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14" />
</graphic>
</MenuItem>
</items>
</ContextMenu>
</fx:define>
</VBox> </VBox>
<Region styleClass="drag-n-drop-border" visible="${controller.draggingVaultOver}"/> <Region styleClass="drag-n-drop-border" visible="${controller.draggingVaultOver}"/>
<fx:define>
<ContextMenu fx:id="addVaultContextMenu">
<items>
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemNew" onAction="#didClickAddNewVault" >
<graphic>
<FontAwesome5IconView glyph="PLUS" textAlignment="CENTER" wrappingWidth="14" />
</graphic>
</MenuItem>
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemExisting" onAction="#didClickAddExistingVault" >
<graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14" />
</graphic>
</MenuItem>
</items>
</ContextMenu>
</fx:define>
</StackPane> </StackPane>

View File

@ -23,7 +23,7 @@
</VBox> </VBox>
<VBox spacing="4" HBox.hgrow="ALWAYS"> <VBox spacing="4" HBox.hgrow="ALWAYS">
<Label styleClass="header-label" text="${controller.vault.displayName}"/> <Label styleClass="header-label" text="${controller.vault.displayName}"/>
<Label styleClass="detail-label" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS"> <Label styleClass="detail-label" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS" visible="${!controller.compactMode}" managed="${!controller.compactMode}">
<tooltip> <tooltip>
<Tooltip text="${controller.vault.displayablePath}"/> <Tooltip text="${controller.vault.displayablePath}"/>
</tooltip> </tooltip>

View File

@ -301,8 +301,8 @@ preferences.interface.language.auto=System Default
preferences.interface.interfaceOrientation=Interface Orientation preferences.interface.interfaceOrientation=Interface Orientation
preferences.interface.interfaceOrientation.ltr=Left to Right preferences.interface.interfaceOrientation.ltr=Left to Right
preferences.interface.interfaceOrientation.rtl=Right to Left preferences.interface.interfaceOrientation.rtl=Right to Left
preferences.interface.showMinimizeButton=Show minimize button
preferences.interface.showTrayIcon=Show tray icon (requires restart) preferences.interface.showTrayIcon=Show tray icon (requires restart)
preferences.interface.compactMode=Enable compact vault list
## Volume ## Volume
preferences.volume=Virtual Drive preferences.volume=Virtual Drive
preferences.volume.type=Default Volume Type preferences.volume.type=Default Volume Type
@ -377,11 +377,6 @@ stats.access.total=Total accesses: %d
# Main Window # Main Window
main.closeBtn.tooltip=Close
main.minimizeBtn.tooltip=Minimize
main.preferencesBtn.tooltip=Preferences
main.debugModeEnabled.tooltip=Debug mode is enabled
main.supporterCertificateMissing.tooltip=Please consider donating
## Vault List ## Vault List
main.vaultlist.emptyList.onboardingInstruction=Click here to add a vault main.vaultlist.emptyList.onboardingInstruction=Click here to add a vault
main.vaultlist.contextMenu.remove=Remove… main.vaultlist.contextMenu.remove=Remove…
@ -390,9 +385,11 @@ main.vaultlist.contextMenu.unlock=Unlock…
main.vaultlist.contextMenu.unlockNow=Unlock Now main.vaultlist.contextMenu.unlockNow=Unlock Now
main.vaultlist.contextMenu.vaultoptions=Show Vault Options main.vaultlist.contextMenu.vaultoptions=Show Vault Options
main.vaultlist.contextMenu.reveal=Reveal Drive main.vaultlist.contextMenu.reveal=Reveal Drive
main.vaultlist.addVaultBtn=Add main.vaultlist.addVaultBtn.menuItemNew=Create New Vault...
main.vaultlist.addVaultBtn.menuItemNew=New Vault... main.vaultlist.addVaultBtn.menuItemExisting=Open Existing Vault...
main.vaultlist.addVaultBtn.menuItemExisting=Existing Vault... ##Notificaition
main.notification.updateAvailable=Update is available.
main.notification.support=Support Cryptomator.
## Vault Detail ## Vault Detail
### Welcome ### Welcome
main.vaultDetail.welcomeOnboarding=Thanks for choosing Cryptomator to protect your files. If you need any assistance, check out our getting started guides: main.vaultDetail.welcomeOnboarding=Thanks for choosing Cryptomator to protect your files. If you need any assistance, check out our getting started guides: