diff --git a/wallettemplate/src/main/java/org/bitcoinj/walletfx/overlay/OverlayWindowController.java b/wallettemplate/src/main/java/org/bitcoinj/walletfx/overlay/OverlayController.java similarity index 82% rename from wallettemplate/src/main/java/org/bitcoinj/walletfx/overlay/OverlayWindowController.java rename to wallettemplate/src/main/java/org/bitcoinj/walletfx/overlay/OverlayController.java index 0a0a0f28b..96a11ffa6 100644 --- a/wallettemplate/src/main/java/org/bitcoinj/walletfx/overlay/OverlayWindowController.java +++ b/wallettemplate/src/main/java/org/bitcoinj/walletfx/overlay/OverlayController.java @@ -15,14 +15,12 @@ */ package org.bitcoinj.walletfx.overlay; -import wallettemplate.MainController; - /** * Interface for controllers displayed via OverlayWindow.OverlayUI */ -public interface OverlayWindowController { +public interface OverlayController { /** * @param ui The overlay UI (node, controller pair) */ - void setOverlayUI(MainController.OverlayUI> ui); + void setOverlayUI(OverlayableStackPaneController.OverlayUI> ui); } diff --git a/wallettemplate/src/main/java/org/bitcoinj/walletfx/overlay/OverlayableStackPaneController.java b/wallettemplate/src/main/java/org/bitcoinj/walletfx/overlay/OverlayableStackPaneController.java new file mode 100644 index 000000000..107867b2e --- /dev/null +++ b/wallettemplate/src/main/java/org/bitcoinj/walletfx/overlay/OverlayableStackPaneController.java @@ -0,0 +1,128 @@ +/* + * Copyright by the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.bitcoinj.walletfx.overlay; + +import javafx.fxml.FXMLLoader; +import javafx.scene.Node; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; +import org.bitcoinj.walletfx.utils.GuiUtils; + +import javax.annotation.Nullable; + +import java.io.IOException; +import java.net.URL; + +import static org.bitcoinj.walletfx.utils.GuiUtils.blurIn; +import static org.bitcoinj.walletfx.utils.GuiUtils.blurOut; +import static org.bitcoinj.walletfx.utils.GuiUtils.checkGuiThread; +import static org.bitcoinj.walletfx.utils.GuiUtils.explodeOut; +import static org.bitcoinj.walletfx.utils.GuiUtils.fadeIn; +import static org.bitcoinj.walletfx.utils.GuiUtils.fadeOutAndRemove; +import static org.bitcoinj.walletfx.utils.GuiUtils.zoomIn; + +/** + * Abstract Controller for a {@link StackPane} that can have other {@link Pane}s displayed on top of it as a modals. + */ +public abstract class OverlayableStackPaneController { + /** + * The {@link StackPane} that contains the {@code mainUI} and any active overlays. Typically, this + * will be the root node of a {@code Scene} contained in a {@code Stage}. + */ + protected final StackPane uiStack = new StackPane(); + /** + * The {@link Pane} containing the bottom-most view, that can be overlaid with other views. + */ + protected Pane mainUI; + private final Node stopClickPane = new Pane(); + @Nullable + private OverlayUI> currentOverlay; + + + public > OverlayUI overlayUI(Node node, T controller) { + checkGuiThread(); + OverlayUI pair = new OverlayUI<>(node, controller); + controller.setOverlayUI(pair); + pair.show(); + return pair; + } + + /** Loads the FXML file with the given name, blurs out the main UI and puts this one on top. */ + public > OverlayUI overlayUI(String name) { + try { + checkGuiThread(); + // Load the UI from disk. + URL location = GuiUtils.getResource(name); + FXMLLoader loader = new FXMLLoader(location); + Pane ui = loader.load(); + T controller = loader.getController(); + OverlayUI pair = new OverlayUI<>(ui, controller); + controller.setOverlayUI(pair); + pair.show(); + return pair; + } catch (IOException e) { + throw new RuntimeException(e); // Can't happen. + } + } + + public class OverlayUI> { + private Node ui; + public T controller; + + public OverlayUI(Node ui, T controller) { + this.ui = ui; + this.controller = controller; + } + + public void show() { + checkGuiThread(); + if (currentOverlay == null) { + uiStack.getChildren().add(stopClickPane); + uiStack.getChildren().add(ui); + blurOut(mainUI); + //darken(mainUI); + fadeIn(ui); + zoomIn(ui); + } else { + // Do a quick transition between the current overlay and the next. + // Bug here: we don't pay attention to changes in outsideClickDismisses. + explodeOut(currentOverlay.ui); + fadeOutAndRemove(uiStack, currentOverlay.ui); + uiStack.getChildren().add(ui); + ui.setOpacity(0.0); + fadeIn(ui, 100); + zoomIn(ui, 100); + } + currentOverlay = this; + } + + public void outsideClickDismisses() { + stopClickPane.setOnMouseClicked((ev) -> done()); + } + + public void done() { + checkGuiThread(); + if (ui == null) return; // In the middle of being dismissed and got an extra click. + explodeOut(ui); + fadeOutAndRemove(uiStack, ui, stopClickPane); + blurIn(mainUI); + //undark(mainUI); + this.ui = null; + this.controller = null; + currentOverlay = null; + } + } +} diff --git a/wallettemplate/src/main/java/wallettemplate/MainController.java b/wallettemplate/src/main/java/wallettemplate/MainController.java index 4a4a36981..3c27fa3a9 100644 --- a/wallettemplate/src/main/java/wallettemplate/MainController.java +++ b/wallettemplate/src/main/java/wallettemplate/MainController.java @@ -19,12 +19,9 @@ package wallettemplate; import javafx.beans.binding.Binding; import javafx.beans.binding.Bindings; import javafx.beans.value.ObservableValue; -import javafx.fxml.FXMLLoader; -import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.input.KeyCombination; import javafx.scene.layout.Pane; -import javafx.scene.layout.StackPane; import org.bitcoinj.core.listeners.DownloadProgressTracker; import org.bitcoinj.core.Coin; import org.bitcoinj.utils.MonetaryFormat; @@ -36,7 +33,7 @@ import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.HBox; import javafx.util.Duration; -import org.bitcoinj.walletfx.overlay.OverlayWindowController; +import org.bitcoinj.walletfx.overlay.OverlayableStackPaneController; import org.bitcoinj.walletfx.utils.GuiUtils; import org.bitcoinj.walletfx.utils.TextFieldValidator; import wallettemplate.controls.ClickableBitcoinAddress; @@ -45,18 +42,13 @@ import org.bitcoinj.walletfx.utils.BitcoinUIModel; import org.bitcoinj.walletfx.utils.easing.EasingMode; import org.bitcoinj.walletfx.utils.easing.ElasticInterpolator; -import javax.annotation.Nullable; -import java.io.IOException; -import java.net.URL; - -import static org.bitcoinj.walletfx.utils.GuiUtils.*; import static wallettemplate.Main.bitcoin; /** * Gets created auto-magically by FXMLLoader via reflection. The widget fields are set to the GUI controls they're named * after. This class handles all the updates and event handling for the main UI. */ -public class MainController { +public class MainController extends OverlayableStackPaneController { public static MainController instance; public HBox controlsBox; public Label balance; @@ -67,10 +59,7 @@ public class MainController { private NotificationBarPane.Item syncItem; private static final MonetaryFormat MONETARY_FORMAT = MonetaryFormat.BTC.noCode(); - private Pane mainUI; - private StackPane uiStack; private NotificationBarPane notificationBar; - private final Node stopClickPane = new Pane(); // Called by FXMLLoader. public void initialize() { @@ -85,7 +74,6 @@ public class MainController { // ordering of the construction and connection matters here, otherwise we get (harmless) CSS error // spew to the logs. notificationBar = new NotificationBarPane(mainUI); - uiStack = new StackPane(); Scene scene = new Scene(uiStack); TextFieldValidator.configureScene(scene); // Add CSS that we need. cssResourceName will be loaded from the same package as this class. @@ -170,80 +158,4 @@ public class MainController { return model.getDownloadProgressTracker(); } - public class OverlayUI> { - public Node ui; - public T controller; - - public OverlayUI(Node ui, T controller) { - this.ui = ui; - this.controller = controller; - } - - public void show() { - checkGuiThread(); - if (currentOverlay == null) { - uiStack.getChildren().add(stopClickPane); - uiStack.getChildren().add(ui); - blurOut(mainUI); - //darken(mainUI); - fadeIn(ui); - zoomIn(ui); - } else { - // Do a quick transition between the current overlay and the next. - // Bug here: we don't pay attention to changes in outsideClickDismisses. - explodeOut(currentOverlay.ui); - fadeOutAndRemove(uiStack, currentOverlay.ui); - uiStack.getChildren().add(ui); - ui.setOpacity(0.0); - fadeIn(ui, 100); - zoomIn(ui, 100); - } - currentOverlay = this; - } - - public void outsideClickDismisses() { - stopClickPane.setOnMouseClicked((ev) -> done()); - } - - public void done() { - checkGuiThread(); - if (ui == null) return; // In the middle of being dismissed and got an extra click. - explodeOut(ui); - fadeOutAndRemove(uiStack, ui, stopClickPane); - blurIn(mainUI); - //undark(mainUI); - this.ui = null; - this.controller = null; - currentOverlay = null; - } - } - - @Nullable - private OverlayUI> currentOverlay; - - public > OverlayUI overlayUI(Node node, T controller) { - checkGuiThread(); - OverlayUI pair = new OverlayUI<>(node, controller); - controller.setOverlayUI(pair); - pair.show(); - return pair; - } - - /** Loads the FXML file with the given name, blurs out the main UI and puts this one on top. */ - public > OverlayUI overlayUI(String name) { - try { - checkGuiThread(); - // Load the UI from disk. - URL location = GuiUtils.getResource(name); - FXMLLoader loader = new FXMLLoader(location); - Pane ui = loader.load(); - T controller = loader.getController(); - OverlayUI pair = new OverlayUI<>(ui, controller); - controller.setOverlayUI(pair); - pair.show(); - return pair; - } catch (IOException e) { - throw new RuntimeException(e); // Can't happen. - } - } } diff --git a/wallettemplate/src/main/java/wallettemplate/SendMoneyController.java b/wallettemplate/src/main/java/wallettemplate/SendMoneyController.java index 88d311a59..2f749c793 100644 --- a/wallettemplate/src/main/java/wallettemplate/SendMoneyController.java +++ b/wallettemplate/src/main/java/wallettemplate/SendMoneyController.java @@ -29,7 +29,8 @@ import javafx.event.ActionEvent; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TextField; -import org.bitcoinj.walletfx.overlay.OverlayWindowController; +import org.bitcoinj.walletfx.overlay.OverlayController; +import org.bitcoinj.walletfx.overlay.OverlayableStackPaneController; import org.bouncycastle.crypto.params.KeyParameter; import wallettemplate.controls.BitcoinAddressValidator; import org.bitcoinj.walletfx.utils.TextFieldValidator; @@ -40,7 +41,7 @@ import static org.bitcoinj.walletfx.utils.GuiUtils.*; import javax.annotation.Nullable; -public class SendMoneyController implements OverlayWindowController { +public class SendMoneyController implements OverlayController { public Button sendBtn; public Button cancelBtn; public TextField address; @@ -48,13 +49,13 @@ public class SendMoneyController implements OverlayWindowController> overlayUI; + private OverlayableStackPaneController.OverlayUI> overlayUI; private Wallet.SendResult sendResult; private KeyParameter aesKey; @Override - public void setOverlayUI(MainController.OverlayUI> ui) { + public void setOverlayUI(OverlayableStackPaneController.OverlayUI> ui) { overlayUI = ui; } @@ -120,14 +121,14 @@ public class SendMoneyController implements OverlayWindowController pwd = MainController.instance.overlayUI("wallet_password.fxml"); + OverlayableStackPaneController.OverlayUI pwd = MainController.instance.overlayUI("wallet_password.fxml"); final String addressStr = address.getText(); final String amountStr = amountEdit.getText(); pwd.controller.aesKeyProperty().addListener((observable, old, cur) -> { // We only get here if the user found the right password. If they don't or they cancel, we end up back on // the main UI screen. By now the send money screen is history so we must recreate it. checkGuiThread(); - MainController.OverlayUI screen = MainController.instance.overlayUI("send_money.fxml"); + OverlayableStackPaneController.OverlayUI screen = MainController.instance.overlayUI("send_money.fxml"); screen.controller.aesKey = cur; screen.controller.address.setText(addressStr); screen.controller.amountEdit.setText(amountStr); diff --git a/wallettemplate/src/main/java/wallettemplate/WalletPasswordController.java b/wallettemplate/src/main/java/wallettemplate/WalletPasswordController.java index cc370a80e..dfef05998 100644 --- a/wallettemplate/src/main/java/wallettemplate/WalletPasswordController.java +++ b/wallettemplate/src/main/java/wallettemplate/WalletPasswordController.java @@ -30,7 +30,8 @@ import javafx.scene.control.ProgressIndicator; import javafx.scene.image.ImageView; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; -import org.bitcoinj.walletfx.overlay.OverlayWindowController; +import org.bitcoinj.walletfx.overlay.OverlayController; +import org.bitcoinj.walletfx.overlay.OverlayableStackPaneController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.bouncycastle.crypto.params.KeyParameter; @@ -45,7 +46,7 @@ import static org.bitcoinj.walletfx.utils.GuiUtils.*; * User interface for entering a password on demand, e.g. to send money. Also used when encrypting a wallet. Shows a * progress meter as we scrypt the password. */ -public class WalletPasswordController implements OverlayWindowController { +public class WalletPasswordController implements OverlayController { private static final Logger log = LoggerFactory.getLogger(WalletPasswordController.class); @FXML HBox buttonsBox; @@ -55,12 +56,12 @@ public class WalletPasswordController implements OverlayWindowController> overlayUI; + private OverlayableStackPaneController.OverlayUI> overlayUI; private SimpleObjectProperty aesKey = new SimpleObjectProperty<>(); @Override - public void setOverlayUI(MainController.OverlayUI> ui) { + public void setOverlayUI(OverlayableStackPaneController.OverlayUI> ui) { overlayUI = ui; } diff --git a/wallettemplate/src/main/java/wallettemplate/WalletSetPasswordController.java b/wallettemplate/src/main/java/wallettemplate/WalletSetPasswordController.java index 9c558587f..bb1549bff 100644 --- a/wallettemplate/src/main/java/wallettemplate/WalletSetPasswordController.java +++ b/wallettemplate/src/main/java/wallettemplate/WalletSetPasswordController.java @@ -23,7 +23,8 @@ import javafx.scene.control.*; import javafx.scene.layout.*; import org.bitcoinj.crypto.*; import org.bitcoinj.wallet.*; -import org.bitcoinj.walletfx.overlay.OverlayWindowController; +import org.bitcoinj.walletfx.overlay.OverlayController; +import org.bitcoinj.walletfx.overlay.OverlayableStackPaneController; import org.slf4j.*; import org.bouncycastle.crypto.params.*; @@ -35,7 +36,7 @@ import java.util.concurrent.*; import org.bitcoinj.walletfx.utils.KeyDerivationTasks; import static org.bitcoinj.walletfx.utils.GuiUtils.*; -public class WalletSetPasswordController implements OverlayWindowController { +public class WalletSetPasswordController implements OverlayController { private static final Logger log = LoggerFactory.getLogger(WalletSetPasswordController.class); public PasswordField pass1, pass2; @@ -44,7 +45,7 @@ public class WalletSetPasswordController implements OverlayWindowController> overlayUI; + private OverlayableStackPaneController.OverlayUI> overlayUI; // These params were determined empirically on a top-range (as of 2014) MacBook Pro with native scrypt support, // using the scryptenc command line tool from the original scrypt distribution, given a memory limit of 40mb. public static final Protos.ScryptParameters SCRYPT_PARAMETERS = Protos.ScryptParameters.newBuilder() @@ -55,7 +56,7 @@ public class WalletSetPasswordController implements OverlayWindowController> ui) { + public void setOverlayUI(OverlayableStackPaneController.OverlayUI> ui) { overlayUI = ui; } diff --git a/wallettemplate/src/main/java/wallettemplate/WalletSettingsController.java b/wallettemplate/src/main/java/wallettemplate/WalletSettingsController.java index cc195477c..764497628 100644 --- a/wallettemplate/src/main/java/wallettemplate/WalletSettingsController.java +++ b/wallettemplate/src/main/java/wallettemplate/WalletSettingsController.java @@ -28,7 +28,8 @@ import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.DatePicker; import javafx.scene.control.TextArea; -import org.bitcoinj.walletfx.overlay.OverlayWindowController; +import org.bitcoinj.walletfx.overlay.OverlayController; +import org.bitcoinj.walletfx.overlay.OverlayableStackPaneController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.bouncycastle.crypto.params.KeyParameter; @@ -48,7 +49,7 @@ import static org.bitcoinj.walletfx.utils.GuiUtils.informationalAlert; import static org.bitcoinj.walletfx.utils.WTUtils.didThrow; import static org.bitcoinj.walletfx.utils.WTUtils.unchecked; -public class WalletSettingsController implements OverlayWindowController { +public class WalletSettingsController implements OverlayController { private static final Logger log = LoggerFactory.getLogger(WalletSettingsController.class); @FXML Button passwordButton; @@ -56,12 +57,12 @@ public class WalletSettingsController implements OverlayWindowController> overlayUI; + private OverlayableStackPaneController.OverlayUI> overlayUI; private KeyParameter aesKey; @Override - public void setOverlayUI(MainController.OverlayUI> ui) { + public void setOverlayUI(OverlayableStackPaneController.OverlayUI> ui) { overlayUI = ui; } @@ -138,12 +139,12 @@ public class WalletSettingsController implements OverlayWindowController pwd = MainController.instance.overlayUI("wallet_password.fxml"); + OverlayableStackPaneController.OverlayUI pwd = MainController.instance.overlayUI("wallet_password.fxml"); pwd.controller.aesKeyProperty().addListener((observable, old, cur) -> { // We only get here if the user found the right password. If they don't or they cancel, we end up back on // the main UI screen. checkGuiThread(); - MainController.OverlayUI screen = MainController.instance.overlayUI("wallet_settings.fxml"); + OverlayableStackPaneController.OverlayUI screen = MainController.instance.overlayUI("wallet_settings.fxml"); screen.controller.initialize(cur); }); } diff --git a/wallettemplate/src/main/java/wallettemplate/controls/ClickableBitcoinAddress.java b/wallettemplate/src/main/java/wallettemplate/controls/ClickableBitcoinAddress.java index e070f8ea4..e186787d9 100644 --- a/wallettemplate/src/main/java/wallettemplate/controls/ClickableBitcoinAddress.java +++ b/wallettemplate/src/main/java/wallettemplate/controls/ClickableBitcoinAddress.java @@ -42,7 +42,8 @@ import javafx.scene.layout.Pane; import org.bitcoinj.core.Address; import org.bitcoinj.uri.BitcoinURI; -import org.bitcoinj.walletfx.overlay.OverlayWindowController; +import org.bitcoinj.walletfx.overlay.OverlayController; +import org.bitcoinj.walletfx.overlay.OverlayableStackPaneController; import wallettemplate.Main; import org.bitcoinj.walletfx.utils.GuiUtils; import org.bitcoinj.walletfx.utils.QRCodeImages; @@ -58,7 +59,7 @@ import static javafx.beans.binding.Bindings.convert; * address looks like a blue hyperlink. Next to it there are two icons, one that copies to the clipboard and another * that shows a QRcode. */ -public class ClickableBitcoinAddress extends AnchorPane implements OverlayWindowController { +public class ClickableBitcoinAddress extends AnchorPane implements OverlayController { @FXML protected Label addressLabel; @FXML protected ContextMenu addressMenu; @FXML protected Label copyWidget; @@ -68,7 +69,7 @@ public class ClickableBitcoinAddress extends AnchorPane implements OverlayWindow private final StringExpression addressStr; @Override - public void setOverlayUI(MainController.OverlayUI> ui) { + public void setOverlayUI(OverlayableStackPaneController.OverlayUI> ui) { } public ClickableBitcoinAddress() { @@ -149,7 +150,7 @@ public class ClickableBitcoinAddress extends AnchorPane implements OverlayWindow // non-centered on the screen. Finally fade/blur it in. Pane pane = new Pane(view); pane.setMaxSize(qrImage.getWidth(), qrImage.getHeight()); - final MainController.OverlayUI overlay = MainController.instance.overlayUI(pane, this); + final OverlayableStackPaneController.OverlayUI overlay = MainController.instance.overlayUI(pane, this); view.setOnMouseClicked(event1 -> overlay.done()); }