diff --git a/desktop/src/main/java/bisq/desktop/bisq.css b/desktop/src/main/java/bisq/desktop/bisq.css index 977d47de0d..d0ae1b06ee 100644 --- a/desktop/src/main/java/bisq/desktop/bisq.css +++ b/desktop/src/main/java/bisq/desktop/bisq.css @@ -523,12 +523,21 @@ bg color of non edit textFields: fafafa -jfx-focus-color: -bs-rd-green; -jfx-unfocus-color: -bs-rd-grey-line; -fx-background-color: -bs-rd-white; + -fx-padding: 0.333333em 0.333333em 0.333333em 0.333333em; } .jfx-text-area:readonly { -fx-background-color: transparent; } +.jfx-text-area > .input-line { + -fx-translate-x: -0.333333em; +} + +.jfx-text-area > .input-focused-line { + -fx-translate-x: -0.333333em; +} + .wallet-seed-words { -fx-font-family: "IBM Plex Mono"; } diff --git a/desktop/src/main/java/bisq/desktop/components/BisqTextArea.java b/desktop/src/main/java/bisq/desktop/components/BisqTextArea.java new file mode 100644 index 0000000000..856f5e791f --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/components/BisqTextArea.java @@ -0,0 +1,12 @@ +package bisq.desktop.components; + +import com.jfoenix.controls.JFXTextArea; + +import javafx.scene.control.Skin; + +public class BisqTextArea extends JFXTextArea { + @Override + protected Skin createDefaultSkin() { + return new JFXTextAreaSkinBisqStyle(this); + } +} diff --git a/desktop/src/main/java/bisq/desktop/components/JFXRadioButtonSkinBisqStyle.java b/desktop/src/main/java/bisq/desktop/components/JFXRadioButtonSkinBisqStyle.java index 6c3c6bd0fd..3ab8cbd587 100644 --- a/desktop/src/main/java/bisq/desktop/components/JFXRadioButtonSkinBisqStyle.java +++ b/desktop/src/main/java/bisq/desktop/components/JFXRadioButtonSkinBisqStyle.java @@ -41,6 +41,10 @@ import javafx.geometry.VPos; import javafx.util.Duration; +/** + * Code copied and adapted from com.jfoenix.skins.JFXRadioButtonSkin + */ + public class JFXRadioButtonSkinBisqStyle extends RadioButtonSkin { private final JFXRippler rippler; private double padding = 12; diff --git a/desktop/src/main/java/bisq/desktop/components/JFXTextAreaSkinBisqStyle.java b/desktop/src/main/java/bisq/desktop/components/JFXTextAreaSkinBisqStyle.java new file mode 100644 index 0000000000..d29063b11a --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/components/JFXTextAreaSkinBisqStyle.java @@ -0,0 +1,121 @@ +package bisq.desktop.components; + +import com.jfoenix.adapters.ReflectionHelper; +import com.jfoenix.controls.JFXTextArea; +import com.jfoenix.skins.PromptLinesWrapper; +import com.jfoenix.skins.ValidationPane; + +import javafx.scene.Node; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.skin.TextAreaSkin; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.Region; +import javafx.scene.paint.Color; +import javafx.scene.text.Text; + +import javafx.geometry.Insets; + +import java.util.Arrays; + +import java.lang.reflect.Field; + +/** + * Code copied and adapted from com.jfoenix.skins.JFXTextAreaSkin + */ + +public class JFXTextAreaSkinBisqStyle extends TextAreaSkin { + + private boolean invalid = true; + + private ScrollPane scrollPane; + private Text promptText; + + private ValidationPane errorContainer; + private PromptLinesWrapper linesWrapper; + + public JFXTextAreaSkinBisqStyle(JFXTextArea textArea) { + super(textArea); + // init text area properties + scrollPane = (ScrollPane) getChildren().get(0); + textArea.setWrapText(true); + + linesWrapper = new PromptLinesWrapper<>( + textArea, + promptTextFillProperty(), + textArea.textProperty(), + textArea.promptTextProperty(), + () -> promptText); + + linesWrapper.init(() -> createPromptNode(), scrollPane); + errorContainer = new ValidationPane<>(textArea); + getChildren().addAll(linesWrapper.line, linesWrapper.focusedLine, linesWrapper.promptContainer, errorContainer); + + registerChangeListener(textArea.disableProperty(), obs -> linesWrapper.updateDisabled()); + registerChangeListener(textArea.focusColorProperty(), obs -> linesWrapper.updateFocusColor()); + registerChangeListener(textArea.unFocusColorProperty(), obs -> linesWrapper.updateUnfocusColor()); + registerChangeListener(textArea.disableAnimationProperty(), obs -> errorContainer.updateClip()); + + } + + + @Override + protected void layoutChildren(final double x, final double y, final double w, final double h) { + super.layoutChildren(x, y, w, h); + + final double height = getSkinnable().getHeight(); + final double width = getSkinnable().getWidth(); + linesWrapper.layoutLines(x, y, width, h, height, promptText == null ? 0 : promptText.getLayoutBounds().getHeight() + 3); + errorContainer.layoutPane(x, height + linesWrapper.focusedLine.getHeight(), width, h); + linesWrapper.updateLabelFloatLayout(); + + if (invalid) { + invalid = false; + // set the default background of text area viewport to white + Region viewPort = (Region) scrollPane.getChildrenUnmodifiable().get(0); + viewPort.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, + CornerRadii.EMPTY, + Insets.EMPTY))); + // reapply css of scroll pane in case set by the user + viewPort.applyCss(); + errorContainer.invalid(w); + // focus + linesWrapper.invalid(); + } + } + + private void createPromptNode() { + if (promptText != null || !linesWrapper.usePromptText.get()) { + return; + } + promptText = new Text(); + promptText.setManaged(false); + promptText.getStyleClass().add("text"); + promptText.setTranslateX(-getSkinnable().getPadding().getLeft() + 1); + promptText.visibleProperty().bind(linesWrapper.usePromptText); + promptText.fontProperty().bind(getSkinnable().fontProperty()); + promptText.textProperty().bind(getSkinnable().promptTextProperty()); + promptText.fillProperty().bind(linesWrapper.animatedPromptTextFill); + promptText.setLayoutX(1); + promptText.getTransforms().add(linesWrapper.promptTextScale); + linesWrapper.promptContainer.getChildren().add(promptText); + if (getSkinnable().isFocused() && ((JFXTextArea) getSkinnable()).isLabelFloat()) { + promptText.setTranslateY(-Math.floor(scrollPane.getHeight())); + linesWrapper.promptTextScale.setX(0.85); + linesWrapper.promptTextScale.setY(0.85); + } + + try { + Field field = ReflectionHelper.getField(TextAreaSkin.class, "promptNode"); + Object oldValue = field.get(this); + if (oldValue != null) { + removeHighlight(Arrays.asList(((Node) oldValue))); + } + field.set(this, promptText); + } catch (Exception e) { + e.printStackTrace(); + } + } +} + diff --git a/desktop/src/main/java/bisq/desktop/components/JFXTextFieldSkinBisqStyle.java b/desktop/src/main/java/bisq/desktop/components/JFXTextFieldSkinBisqStyle.java index 0f5f718694..475a5daf10 100644 --- a/desktop/src/main/java/bisq/desktop/components/JFXTextFieldSkinBisqStyle.java +++ b/desktop/src/main/java/bisq/desktop/components/JFXTextFieldSkinBisqStyle.java @@ -16,6 +16,10 @@ import javafx.beans.value.ObservableDoubleValue; import java.lang.reflect.Field; +/** + * Code copied and adapted from com.jfoenix.skins.JFXTextFieldSkin + */ + public class JFXTextFieldSkinBisqStyle extends TextFieldSkin { private double inputLineExtension; diff --git a/desktop/src/main/java/bisq/desktop/main/disputes/trader/TraderDisputeView.java b/desktop/src/main/java/bisq/desktop/main/disputes/trader/TraderDisputeView.java index 534c9dbbd4..75b907e5ba 100644 --- a/desktop/src/main/java/bisq/desktop/main/disputes/trader/TraderDisputeView.java +++ b/desktop/src/main/java/bisq/desktop/main/disputes/trader/TraderDisputeView.java @@ -22,6 +22,7 @@ import bisq.desktop.common.view.FxmlView; import bisq.desktop.components.AutoTooltipButton; import bisq.desktop.components.AutoTooltipLabel; import bisq.desktop.components.AutoTooltipTableColumn; +import bisq.desktop.components.BisqTextArea; import bisq.desktop.components.BusyAnimation; import bisq.desktop.components.HyperlinkWithIcon; import bisq.desktop.components.InputTextField; @@ -67,8 +68,6 @@ import com.google.common.io.ByteStreams; import de.jensd.fx.fontawesome.AwesomeDude; import de.jensd.fx.fontawesome.AwesomeIcon; -import com.jfoenix.controls.JFXTextArea; - import javafx.stage.FileChooser; import javafx.scene.Scene; @@ -691,7 +690,7 @@ public class TraderDisputeView extends ActivatableView { messagesAnchorPane = new AnchorPane(); VBox.setVgrow(messagesAnchorPane, Priority.ALWAYS); - inputTextArea = new JFXTextArea(); + inputTextArea = new BisqTextArea(); inputTextArea.setPrefHeight(70); inputTextArea.setWrapText(true); if (!(this instanceof ArbitratorDisputeView)) diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java index 49af41ae2b..92f3a8142b 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java @@ -17,6 +17,7 @@ package bisq.desktop.main.overlays.windows; +import bisq.desktop.components.BisqTextArea; import bisq.desktop.main.MainView; import bisq.desktop.main.overlays.Overlay; import bisq.desktop.util.Layout; @@ -194,7 +195,7 @@ public class ContractWindow extends Overlay { Res.get("shared.viewContractAsJson"), 0).second; viewContractButton.setDefaultButton(false); viewContractButton.setOnAction(e -> { - TextArea textArea = new TextArea(); + TextArea textArea = new BisqTextArea(); String contractAsJson = dispute.getContractAsJson(); contractAsJson += "\n\nBuyerMultiSigPubKeyHex: " + Utils.HEX.encode(contract.getBuyerMultiSigPubKey()); contractAsJson += "\nSellerMultiSigPubKeyHex: " + Utils.HEX.encode(contract.getSellerMultiSigPubKey()); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java index 2cdb8844b3..96d1568264 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -19,6 +19,7 @@ package bisq.desktop.main.overlays.windows; import bisq.desktop.components.AutoTooltipCheckBox; import bisq.desktop.components.AutoTooltipRadioButton; +import bisq.desktop.components.BisqTextArea; import bisq.desktop.components.InputTextField; import bisq.desktop.main.overlays.Overlay; import bisq.desktop.main.overlays.popups.Popup; @@ -45,8 +46,6 @@ import org.bitcoinj.core.Coin; import javax.inject.Inject; -import com.jfoenix.controls.JFXTextArea; - import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; @@ -485,7 +484,7 @@ public class DisputeSummaryWindow extends Overlay { private void addSummaryNotes() { - summaryNotesTextArea = new JFXTextArea(); + summaryNotesTextArea = new BisqTextArea(); summaryNotesTextArea.setPromptText(Res.get("disputeSummaryWindow.addSummaryNotes")); summaryNotesTextArea.setWrapText(true); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java index 6ea38d5183..43315c8e74 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java @@ -17,6 +17,7 @@ package bisq.desktop.main.overlays.windows; +import bisq.desktop.components.BisqTextArea; import bisq.desktop.components.TextFieldWithCopyIcon; import bisq.desktop.main.MainView; import bisq.desktop.main.overlays.Overlay; @@ -247,7 +248,7 @@ public class TradeDetailsWindow extends Overlay { Res.get("shared.viewContractAsJson"), 0).second; viewContractButton.setDefaultButton(false); viewContractButton.setOnAction(e -> { - TextArea textArea = new TextArea(); + TextArea textArea = new BisqTextArea(); textArea.setText(trade.getContractAsJson()); String contractAsJson = trade.getContractAsJson(); contractAsJson += "\n\nBuyerMultiSigPubKeyHex: " + Utils.HEX.encode(contract.getBuyerMultiSigPubKey()); diff --git a/desktop/src/main/java/bisq/desktop/util/FormBuilder.java b/desktop/src/main/java/bisq/desktop/util/FormBuilder.java index be5a2cf078..2517d872f5 100644 --- a/desktop/src/main/java/bisq/desktop/util/FormBuilder.java +++ b/desktop/src/main/java/bisq/desktop/util/FormBuilder.java @@ -24,6 +24,7 @@ import bisq.desktop.components.AutoTooltipLabel; import bisq.desktop.components.AutoTooltipRadioButton; import bisq.desktop.components.AutoTooltipSlideToggleButton; import bisq.desktop.components.BalanceTextField; +import bisq.desktop.components.BisqTextArea; import bisq.desktop.components.BsqAddressTextField; import bisq.desktop.components.BusyAnimation; import bisq.desktop.components.FundsTextField; @@ -417,7 +418,7 @@ public class FormBuilder { public static TextArea addTextArea(GridPane gridPane, int rowIndex, String prompt, double top) { - TextArea textArea = new JFXTextArea(); + TextArea textArea = new BisqTextArea(); textArea.setPromptText(prompt); ((JFXTextArea) textArea).setLabelFloat(true); textArea.setWrapText(true); @@ -454,7 +455,7 @@ public class FormBuilder { public static Tuple2 addTopLabelTextArea(GridPane gridPane, int rowIndex, int colIndex, String title, String prompt, double top) { - TextArea textArea = new JFXTextArea(); + TextArea textArea = new BisqTextArea(); textArea.setPromptText(prompt); textArea.setWrapText(true); diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index ada11af7d1..0c9a0caa18 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -19,6 +19,7 @@ package bisq.desktop.util; import bisq.desktop.app.BisqApp; import bisq.desktop.components.AutoTooltipLabel; +import bisq.desktop.components.BisqTextArea; import bisq.desktop.components.indicator.TxConfidenceIndicator; import bisq.desktop.main.overlays.popups.Popup; @@ -740,7 +741,7 @@ public class GUIUtil { } public static void showSelectableTextModal(String title, String text) { - TextArea textArea = new TextArea(); + TextArea textArea = new BisqTextArea(); textArea.setText(text); textArea.setEditable(false); textArea.setWrapText(true);