diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/DropdownMenu.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/DropdownMenu.java index db7ae8f08b..89f14770dc 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/DropdownMenu.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/DropdownMenu.java @@ -18,14 +18,15 @@ package bisq.desktop.components.controls; import bisq.desktop.common.utils.ImageUtil; +import bisq.desktop.components.containers.Spacer; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.WeakChangeListener; import javafx.collections.ObservableList; import javafx.geometry.Bounds; -import javafx.geometry.Insets; import javafx.geometry.Pos; +import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; @@ -49,7 +50,7 @@ public class DropdownMenu extends HBox { private final BooleanProperty isMenuShowing = new SimpleBooleanProperty(false); private final ContextMenu contextMenu = new ContextMenu(); @Getter - private Label label = new Label(); + private final HBox hBox = new HBox(); private ImageView buttonIcon; private boolean isFirstRun = false; @Setter @@ -71,7 +72,9 @@ public DropdownMenu(String defaultIconId, String activeIconId, boolean useIconOn buttonIcon = defaultIcon; - getChildren().addAll(label, buttonIcon); + getChildren().addAll(hBox, buttonIcon); + hBox.setAlignment(Pos.BASELINE_LEFT); + hBox.getStyleClass().add("dropdown-menu-content-hbox"); getStyleClass().add("dropdown-menu"); contextMenu.getStyleClass().add("dropdown-menu-popup"); @@ -84,8 +87,7 @@ public DropdownMenu(String defaultIconId, String activeIconId, boolean useIconOn setAlignment(Pos.CENTER); } else { setSpacing(5); - setAlignment(Pos.CENTER_RIGHT); - setPadding(new Insets(0, 5, 0, 0)); + setAlignment(Pos.BASELINE_LEFT); } widthPropertyChangeListener = (observable, oldValue, newValue) -> { @@ -111,13 +113,18 @@ public DropdownMenu(String defaultIconId, String activeIconId, boolean useIconOn attachListeners(); } - public void setLabel(String text) { - label.setText(text); + public void setLabelAsContent(String text) { + Label label = new Label(text); + label.setAlignment(Pos.BASELINE_LEFT); + setContent(label); } - public void setLabel(Label label) { - this.label = label; - getChildren().set(0, label); + public void setContent(Node content) { + hBox.getChildren().setAll(content); + } + + public void useSpaceBetweenContentAndIcon() { + getChildren().setAll(hBox, Spacer.fillHBox(), buttonIcon); } private void toggleContextMenu() { diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/DropdownMenuItem.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/DropdownMenuItem.java index 63bff48c39..b3ea6eebc1 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/DropdownMenuItem.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/DropdownMenuItem.java @@ -56,6 +56,10 @@ public DropdownMenuItem(String defaultIconId, String activeIconId, Node node) { setContent(hBox); } + public DropdownMenuItem(Node node) { + this(null, null, node); + } + public void updateWidth(Double width) { hBox.setPrefWidth(width); } diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/table/RichTableView.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/table/RichTableView.java index db8c928947..fd8c81d41b 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/components/table/RichTableView.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/components/table/RichTableView.java @@ -137,15 +137,13 @@ private RichTableView(SortedList sortedList, filterItems.ifPresent(filterMenu::addMenuItems); tooltip = new BisqTooltip(); filterMenu.setTooltip(tooltip); - filterMenu.setAlignment(Pos.BASELINE_LEFT); searchBox = new SearchBox(); searchBox.setManaged(searchTextHandler.isPresent()); searchBox.setVisible(searchBox.isManaged()); searchBox.setPrefWidth(90); searchBox.setAlignment(Pos.BASELINE_LEFT); - HBox.setMargin(filterMenu, new Insets(0, 20, 0, 0)); - HBox filterBox = new HBox(20, searchBox, filterMenu); + HBox filterBox = new HBox(10, searchBox, filterMenu); filterBox.setAlignment(Pos.BASELINE_LEFT); HBox headerBox = new HBox(headlineLabel, Spacer.fillHBox(), filterBox); @@ -163,7 +161,7 @@ private RichTableView(SortedList sortedList, HBox footerHBox = new HBox(numEntriesLabel, Spacer.fillHBox(), exportHyperlink); footerHBox.setAlignment(Pos.BASELINE_LEFT); - VBox.setMargin(headerBox, new Insets(0, 0, 5, 10)); + VBox.setMargin(headerBox, new Insets(0, 10, 5, 10)); VBox.setVgrow(tableView, Priority.ALWAYS); VBox.setMargin(footerHBox, new Insets(-5, 0, 0, 10)); getChildren().addAll(headerBox, tableView, footerHBox); @@ -271,7 +269,7 @@ private void selectedFilterMenuItemChanged() { toggleGroup.flatMap(toggleGroup -> FilterMenuItem.fromToggle(toggleGroup.getSelectedToggle())) .ifPresent(filterMenuItem -> { tooltip.setText(Res.get("component.standardTable.filter.tooltip", filterMenuItem.getTitle())); - filterMenu.setLabel(filterMenuItem.getTitle()); + filterMenu.setLabelAsContent(filterMenuItem.getTitle()); }); } diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/offerbook/offerbook_list/OfferbookListView.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/offerbook/offerbook_list/OfferbookListView.java index af3ff4817e..5b6b6ccfdd 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/offerbook/offerbook_list/OfferbookListView.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/offerbook/offerbook_list/OfferbookListView.java @@ -108,11 +108,11 @@ public class OfferbookListView extends bisq.desktop.common.view.View(model.getSortedOfferbookListItems()); @@ -263,7 +263,7 @@ private DropdownMenu createAndGetOffersDirectionFilterMenu() { menu.getStyleClass().add("dropdown-offer-list-direction-filter-menu"); menu.setOpenToTheRight(true); offerDirectionFilterLabel = new Label(); - menu.setLabel(offerDirectionFilterLabel); + menu.setContent(offerDirectionFilterLabel); buyFromOffers = new DropdownBisqMenuItem(Res.get("bisqEasy.offerbook.offerList.table.filters.offerDirection.buyFrom")); sellToOffers = new DropdownBisqMenuItem(Res.get("bisqEasy.offerbook.offerList.table.filters.offerDirection.sellTo")); menu.addMenuItems(buyFromOffers, sellToOffers); @@ -275,7 +275,7 @@ private DropdownMenu createAndGetPaymentsFilterDropdownMenu() { menu.getStyleClass().add("dropdown-offer-list-payment-filter-menu"); menu.setOpenToTheRight(true); paymentsFilterLabel = new Label(); - menu.setLabel(paymentsFilterLabel); + menu.setContent(paymentsFilterLabel); return menu; } diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/ChatMessageContainerController.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/ChatMessageContainerController.java index 5b7dc2bdd1..49d3ccdb82 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/ChatMessageContainerController.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/ChatMessageContainerController.java @@ -1,7 +1,29 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + package bisq.desktop.main.content.chat.message_container; import bisq.bisq_easy.NavigationTarget; -import bisq.chat.*; +import bisq.chat.ChatChannel; +import bisq.chat.ChatChannelDomain; +import bisq.chat.ChatChannelSelectionService; +import bisq.chat.ChatMessage; +import bisq.chat.ChatService; +import bisq.chat.Citation; import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookChannel; import bisq.chat.bisqeasy.open_trades.BisqEasyOpenTradeChannel; import bisq.chat.common.CommonPublicChatChannel; diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/ChatMessageContainerView.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/ChatMessageContainerView.java index 8f698d3913..e82afb005c 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/ChatMessageContainerView.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/ChatMessageContainerView.java @@ -1,6 +1,22 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + package bisq.desktop.main.content.chat.message_container; -import bisq.common.util.StringUtils; import bisq.desktop.common.threading.UIThread; import bisq.desktop.common.utils.ImageUtil; import bisq.desktop.components.controls.BisqTextArea; @@ -20,7 +36,6 @@ import javafx.scene.layout.Pane; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -import javafx.util.StringConverter; import lombok.extern.slf4j.Slf4j; import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; @@ -157,23 +172,13 @@ private void setUpInputFieldAtMentions() { private void setUpUserProfileSelection(UserProfileSelection userProfileSelection) { userProfileSelection.setMaxComboBoxWidth(165); - userProfileSelection.setConverter(new StringConverter<>() { - @Override - public String toString(UserProfileSelection.ListItem item) { - return item != null ? StringUtils.truncate(item.getUserIdentity().getUserName(), 10) : ""; - } - - @Override - public UserProfileSelection.ListItem fromString(String string) { - return null; - } - }); + userProfileSelection.openMenuUpwards(); + userProfileSelection.openMenuToTheRight(); userProfileSelectionRoot = userProfileSelection.getRoot(); - userProfileSelectionRoot.setMaxHeight(44); + userProfileSelectionRoot.setMaxHeight(45); userProfileSelectionRoot.setMaxWidth(165); userProfileSelectionRoot.setMinWidth(165); - userProfileSelectionRoot.setId("chat-user-profile-bg"); - HBox.setMargin(userProfileSelectionRoot, new Insets(0, -20, 0, -8)); + userProfileSelectionRoot.getStyleClass().add("chat-user-profile-bg"); } private void createChatDialogEnabledSubscription() { diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/components/MaterialUserProfileSelection.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/components/MaterialUserProfileSelection.java index 8456746a0f..ce8b7f7b5b 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/components/MaterialUserProfileSelection.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/components/MaterialUserProfileSelection.java @@ -43,6 +43,7 @@ public class MaterialUserProfileSelection extends Pane { protected final Region bg = new Region(); protected final Region line = new Region(); protected final Region selectionLine = new Region(); + @Getter protected final Label descriptionLabel = new Label(); protected final Pane userProfileSelectionRoot; @Getter @@ -98,7 +99,6 @@ public MaterialUserProfileSelection(UserProfileSelection userProfileSelection, descriptionLabel.setText(description); } - userProfileSelectionRoot = userProfileSelection.getRoot(); userProfileSelectionRoot.setLayoutX(6.5); userProfileSelectionRoot.getStyleClass().add("material-text-field"); @@ -152,10 +152,6 @@ public final StringProperty descriptionProperty() { return descriptionLabel.textProperty(); } - public Label getDescriptionLabel() { - return descriptionLabel; - } - /////////////////////////////////////////////////////////////////////////////////////////////////// // Help diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/components/UserProfileSelection.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/components/UserProfileSelection.java index 6e235fc34d..4245eb11ec 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/components/UserProfileSelection.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/components/UserProfileSelection.java @@ -29,29 +29,22 @@ import bisq.desktop.common.observable.FxBindings; import bisq.desktop.common.threading.UIThread; import bisq.desktop.common.view.Navigation; -import bisq.desktop.components.cathash.CatHash; -import bisq.desktop.components.controls.AutoCompleteComboBox; +import bisq.desktop.components.controls.DropdownMenu; +import bisq.desktop.components.controls.DropdownMenuItem; import bisq.desktop.components.overlay.Popup; import bisq.i18n.Res; import bisq.user.identity.UserIdentity; import bisq.user.identity.UserIdentityService; -import javafx.beans.property.*; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.WeakChangeListener; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Cursor; -import javafx.scene.Node; -import javafx.scene.control.ComboBox; -import javafx.scene.control.Label; -import javafx.scene.control.ListCell; -import javafx.scene.image.ImageView; -import javafx.scene.layout.HBox; +import javafx.css.PseudoClass; import javafx.scene.layout.Pane; -import javafx.util.StringConverter; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -76,16 +69,8 @@ public Pane getRoot() { return controller.view.getRoot(); } - public void setIsLeftAligned(boolean isLeftAligned) { - controller.model.getIsLeftAligned().set(isLeftAligned); - } - public void setMaxComboBoxWidth(int width) { - controller.view.setMaxComboBoxWidth(width); - } - - public void setConverter(StringConverter value) { - controller.view.setConverter(value); + controller.view.setMenuMaxWidth(width); } public boolean isFocused() { @@ -104,6 +89,14 @@ public void setPrefWidth(double value) { controller.setPrefWidth(value); } + public void openMenuUpwards() { + controller.view.getDropdownMenu().setOpenUpwards(true); + } + + public void openMenuToTheRight() { + controller.view.getDropdownMenu().setOpenToTheRight(true); + } + private static class Controller implements bisq.desktop.common.view.Controller { private final Model model; @Getter @@ -111,7 +104,7 @@ private static class Controller implements bisq.desktop.common.view.Controller { private final UserIdentityService userIdentityService; private final Map chatChannelSelectionServices; private Pin selectedUserProfilePin, userProfilesPin, chatChannelSelectionPin, navigationPin, isPrivateChannelPin; - private final ListChangeListener userProfilesListener = change -> updateShouldUseComboBox(); + private final ListChangeListener userProfilesListener = change -> updateShouldShowMenu(); private Controller(ServiceProvider serviceProvider, int iconSize, boolean useMaterialStyle) { this.userIdentityService = serviceProvider.getUserService().getUserIdentityService(); @@ -124,21 +117,26 @@ private Controller(ServiceProvider serviceProvider, int iconSize, boolean useMat @Override public void onActivate() { selectedUserProfilePin = FxBindings.subscribe(userIdentityService.getSelectedUserIdentityObservable(), - userProfile -> UIThread.run(() -> model.getSelectedUserProfile().set(new ListItem(userProfile)))); - userProfilesPin = FxBindings.bind(model.getUserProfiles()) - .map(ListItem::new) + userIdentity -> UIThread.run(() -> model.getSelectedUserIdentity().set(userIdentity))); + userProfilesPin = FxBindings.bind(model.getUserProfiles()) + .map(userIdentity -> { + UserProfileMenuItem userProfileMenuItem = new UserProfileMenuItem(userIdentity); + userProfileMenuItem.setOnAction(e -> onSelected(userProfileMenuItem)); + return userProfileMenuItem; + }) .to(userIdentityService.getUserIdentities()); navigationPin = Navigation.getCurrentNavigationTarget().addObserver(this::navigationTargetChanged); model.getUserProfiles().addListener(userProfilesListener); - isPrivateChannelPin = FxBindings.subscribe(model.getIsPrivateChannel(), isPrivate -> updateShouldUseComboBox()); + isPrivateChannelPin = FxBindings.subscribe(model.getIsPrivateChannel(), isPrivate -> updateShouldShowMenu()); } @Override public void onDeactivate() { // Need to clear list otherwise we get issues with binding when multiple // instances are used. + model.getUserProfiles().forEach(UserProfileMenuItem::dispose); model.getUserProfiles().clear(); model.getUserProfiles().removeListener(userProfilesListener); @@ -151,7 +149,7 @@ public void onDeactivate() { isPrivateChannelPin.unbind(); } - private void onSelected(ListItem selectedItem) { + private void onSelected(UserProfileMenuItem selectedItem) { if (selectedItem != null) { UserIdentity selectedUserIdentity = userIdentityService.getSelectedUserIdentity(); // To make sure a different user is never selected for a private channel it's safest to keep this check @@ -160,30 +158,30 @@ private void onSelected(ListItem selectedItem) { new Popup().warning(Res.get("chat.privateChannel.changeUserProfile.warn", selectedUserIdentity.getUserProfile().getUserName())) .onClose(() -> { - model.getSelectedUserProfile().set(null); - model.getSelectedUserProfile().set(new ListItem(selectedUserIdentity)); + model.getSelectedUserIdentity().set(null); + model.getSelectedUserIdentity().set(selectedUserIdentity); }) .show(); } else { - userIdentityService.selectChatUserIdentity(selectedItem.userIdentity); + userIdentityService.selectChatUserIdentity(selectedItem.getUserIdentity()); } } } private boolean isFocused() { - return view.getComboBox().isFocused(); + return view.getDropdownMenu().isFocused(); } private ReadOnlyBooleanProperty focusedProperty() { - return view.getComboBox().focusedProperty(); + return view.getDropdownMenu().focusedProperty(); } private void requestFocus() { - view.getComboBox().requestFocus(); + view.getDropdownMenu().requestFocus(); } private void setPrefWidth(double value) { - view.getComboBox().setPrefWidth(value); + view.setMenuPrefWidth(value); } private void navigationTargetChanged(NavigationTarget navigationTarget) { @@ -214,350 +212,116 @@ private void selectedChannelChanged(ChatChannel channel) UIThread.run(() -> model.getIsPrivateChannel().set(channel instanceof PrivateChatChannel)); } - private void updateShouldUseComboBox() { - model.getShouldUseComboBox().set(!model.getIsPrivateChannel().get() && model.getUserProfiles().size() > 1); + private void updateShouldShowMenu() { + model.getShouldShowMenu().set(!model.getIsPrivateChannel().get() && !model.getUserProfiles().isEmpty()); } } @Slf4j @Getter private static class Model implements bisq.desktop.common.view.Model { - private final ObjectProperty selectedUserProfile = new SimpleObjectProperty<>(); - private final ObservableList userProfiles = FXCollections.observableArrayList(); - private final DoubleProperty comboBoxWidth = new SimpleDoubleProperty(); - private final BooleanProperty isLeftAligned = new SimpleBooleanProperty(); + private final ObjectProperty selectedUserIdentity = new SimpleObjectProperty<>(); + private final ObservableList userProfiles = FXCollections.observableArrayList(); private final Observable isPrivateChannel = new Observable<>(false); - private final Observable shouldUseComboBox = new Observable<>(false); - + private final Observable shouldShowMenu = new Observable<>(false); + private final DoubleProperty menuWidth = new SimpleDoubleProperty(); } @Slf4j public static class View extends bisq.desktop.common.view.View { + private final static int DEFAULT_MENU_WIDTH = 200; + @Getter - private final UserProfileComboBox comboBox; - private final HBox userNameAndIcon; - private final Label userName; - private final ImageView catHashImageView; - private Subscription selectedUserProfilePin, isLeftAlignedPin, comboBoxWidthPin; - private Pin isComboBoxPin; + private final DropdownMenu dropdownMenu; + private final UserProfileDisplay userProfileDisplay = new UserProfileDisplay(); + private Subscription selectedUserProfilePin, menuWidthPin; + private Pin shouldShowMenuPin; private View(Model model, Controller controller, int iconSize, boolean useMaterialStyle) { super(new Pane(), model, controller); - comboBox = new UserProfileComboBox(model.getUserProfiles(), Res.get("user.userProfile.comboBox.description"), - iconSize, useMaterialStyle); - comboBox.setLayoutY(UserProfileComboBox.Y_OFFSET); - - userName = new Label(); - userName.getStyleClass().add("bisq-text-19"); - catHashImageView = new ImageView(); - catHashImageView.setFitWidth(iconSize); - catHashImageView.setFitHeight(catHashImageView.getFitWidth()); - userNameAndIcon = new HBox(10, userName, catHashImageView); - userNameAndIcon.setLayoutY(8); - userNameAndIcon.setAlignment(Pos.CENTER); - root.getChildren().setAll(comboBox, userNameAndIcon); + dropdownMenu = new DropdownMenu("chevron-drop-menu-grey", "chevron-drop-menu-white", false); + dropdownMenu.setTooltip(Res.get("user.userProfile.comboBox.description")); + dropdownMenu.setContent(userProfileDisplay); + dropdownMenu.useSpaceBetweenContentAndIcon(); + + root.getChildren().setAll(dropdownMenu); root.setPrefHeight(60); + root.getStyleClass().add("user-profile-selection"); } @Override protected void onViewAttached() { - comboBox.setOnChangeConfirmed(e -> controller.onSelected(comboBox.getSelectionModel().getSelectedItem())); - selectedUserProfilePin = EasyBind.subscribe(model.getSelectedUserProfile(), - selected -> { - UIThread.runOnNextRenderFrame(() -> comboBox.getSelectionModel().select(selected)); - if (selected != null) { - UserIdentity userIdentity = selected.userIdentity; - if (userIdentity != null) { - userName.setText(comboBox.getConverter().toString(selected)); - catHashImageView.setImage(CatHash.getImage(userIdentity.getUserProfile(), - catHashImageView.getFitWidth())); - } - } - }); - isLeftAlignedPin = EasyBind.subscribe(model.getIsLeftAligned(), isLeftAligned -> { - comboBox.setIsLeftAligned(isLeftAligned); - if (!isLeftAligned) { - HBox.setMargin(userName, new Insets(-2, 0, 0, 0)); - HBox.setMargin(catHashImageView, new Insets(-1.5, 0, 0, 16)); - userName.toFront(); - } else { - HBox.setMargin(userName, new Insets(-1, 0, 0, 0)); - HBox.setMargin(catHashImageView, new Insets(0, 17, 0, 0)); - userName.toBack(); - } + selectedUserProfilePin = EasyBind.subscribe(model.getSelectedUserIdentity(), selectedUserIdentity -> { + userProfileDisplay.setUserProfile(selectedUserIdentity.getUserProfile()); + model.getUserProfiles().forEach(userProfileMenuItem -> { + userProfileMenuItem.updateSelection(selectedUserIdentity.equals(userProfileMenuItem.getUserIdentity())); + }); }); - comboBoxWidthPin = EasyBind.subscribe(model.getComboBoxWidth(), w -> comboBox.setComboBoxWidth(w.doubleValue())); - - isComboBoxPin = FxBindings.subscribe(model.getShouldUseComboBox(), this::updateShouldUseComboBox); + shouldShowMenuPin = FxBindings.subscribe(model.getShouldShowMenu(), this::shouldShowMenu); + menuWidthPin = EasyBind.subscribe(model.getMenuWidth(), w -> setMenuPrefWidth(w.doubleValue())); + dropdownMenu.addMenuItems(model.getUserProfiles()); } @Override protected void onViewDetached() { selectedUserProfilePin.unsubscribe(); - isLeftAlignedPin.unsubscribe(); - comboBoxWidthPin.unsubscribe(); - isComboBoxPin.unbind(); - catHashImageView.setImage(null); + menuWidthPin.unsubscribe(); + shouldShowMenuPin.unbind(); + dropdownMenu.clearMenuItems(); } - public void setMaxComboBoxWidth(int width) { - comboBox.setComboBoxWidth(width); + private void shouldShowMenu(boolean showMenu) { + dropdownMenu.setManaged(showMenu); + dropdownMenu.setVisible(showMenu); } - public void setConverter(StringConverter value) { - comboBox.setConverter(value); + private void setMenuPrefWidth(double width) { + dropdownMenu.setPrefWidth(width == 0 ? DEFAULT_MENU_WIDTH : width); } - private void updateShouldUseComboBox(boolean isComboBox) { - comboBox.setManaged(isComboBox); - comboBox.setVisible(isComboBox); - userNameAndIcon.setManaged(!isComboBox); - userNameAndIcon.setVisible(!isComboBox); + private void setMenuMaxWidth(double width) { + setMenuPrefWidth(width); + dropdownMenu.setMaxWidth(width == 0 ? DEFAULT_MENU_WIDTH : width); } } - @EqualsAndHashCode(onlyExplicitlyIncluded = true) + @EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true) @Getter - public static class ListItem { + public static final class UserProfileMenuItem extends DropdownMenuItem { + private static final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected"); + @EqualsAndHashCode.Include private final UserIdentity userIdentity; - private ListItem(UserIdentity userIdentity) { - this.userIdentity = userIdentity; - } - - @Override - public String toString() { - return userIdentity != null ? userIdentity.getUserName() : ""; - } - } - - private static class UserProfileComboBox extends AutoCompleteComboBox { - private final static int DEFAULT_COMBO_BOX_WIDTH = 200; - private final static int Y_OFFSET = 30; - private final int iconSize; - - private boolean isLeftAligned; - - public UserProfileComboBox(ObservableList items, - String description, - int iconSize, - boolean useMaterialStyle) { - super(items, description); - - this.iconSize = iconSize; - ((UserProfileSkin) skin).setIconSize(iconSize); - ((UserProfileSkin) skin).setUseMaterialStyle(useMaterialStyle); - - setPrefWidth(DEFAULT_COMBO_BOX_WIDTH); - - setCellFactory(param -> new ListCell<>() { - private ChangeListener labelWidthListener; - private final ImageView catHashImageView = new ImageView(); - private final Label label = new Label(); - private final HBox hBox = new HBox(10); - - { - label.setMouseTransparent(true); - catHashImageView.setFitWidth(iconSize); - catHashImageView.setFitHeight(catHashImageView.getFitWidth()); - setPrefHeight(50); - setPadding(new Insets(10, 0, 0, 10)); - - hBox.setPadding(new Insets(0, 10, 0, 10)); - if (isLeftAligned) { - hBox.setAlignment(Pos.CENTER_RIGHT); - hBox.getChildren().addAll(label, catHashImageView); - } else { - hBox.setAlignment(Pos.CENTER_LEFT); - hBox.getChildren().addAll(catHashImageView, label); - } - - labelWidthListener = (observable, oldValue, newValue) -> { - if (newValue.doubleValue() > 0) { - UserProfileSelection.UserProfileComboBox.this.setComboBoxWidth(calculateWidth(label)); - label.widthProperty().removeListener(labelWidthListener); - } - }; - } - - @Override - protected void updateItem(ListItem item, boolean empty) { - super.updateItem(item, empty); - - if (item != null && !empty) { - catHashImageView.setImage(CatHash.getImage(item.userIdentity.getUserProfile(), - catHashImageView.getFitWidth())); - label.setText(item.userIdentity.getUserName()); - label.widthProperty().addListener(labelWidthListener); - - setGraphic(hBox); - } else { - setGraphic(null); - catHashImageView.setImage(null); - label.widthProperty().removeListener(labelWidthListener); - } - } - }); - } - - private void setIsLeftAligned(boolean isLeftAligned) { - this.isLeftAligned = isLeftAligned; - ((UserProfileSkin) skin).setIsLeftAligned(isLeftAligned); - } - - private void setComboBoxWidth(double width) { - if (width == 0) { - width = DEFAULT_COMBO_BOX_WIDTH; - } - setPrefWidth(width); - ((UserProfileSkin) skin).setComboBoxWidth(width); - } - - private void setMaxComboBoxWidth(double width) { - if (width == 0) { - width = DEFAULT_COMBO_BOX_WIDTH; - } - setPrefWidth(width); - ((UserProfileSkin) skin).setMaxComboBoxWidth(width); - } - - @Override - protected Skin createDefaultSkin() { - if (skin == null) { - skin = new UserProfileSkin(this, description, prompt); - editor = skin.getMaterialTextField().getTextInputControl(); - } - return skin; - } - - private double calculateWidth(Label label) { - double width = label.getWidth() + iconSize + 80; - return Math.max(width, UserProfileComboBox.this.getPrefWidth()); - } - } - - private static class UserProfileSkin extends AutoCompleteComboBox.Skin { - private final static int ICON_PADDING = 17; - private final static int ARROW_WIDTH = 10; - private final static int ARROW_ICON_PADDING = 10; - private final static int TEXT_PADDING = 6; - private final Label userNameLabel = new Label(); - ; - private final ImageView catHashImageView = new ImageView(); - private final UserProfileComboBox userProfileComboBox; - private int iconSize; - private boolean isLeftAligned; - @SuppressWarnings("FieldCanBeLocal") // Need to keep a reference as used in WeakChangeListener - private final ChangeListener selectedItemListener = (observable, oldValue, newValue) -> { - if (newValue != null) { - UserIdentity userIdentity = newValue.getUserIdentity(); - if (userIdentity != null) { - catHashImageView.setImage(CatHash.getImage(userIdentity.getUserProfile(), - catHashImageView.getFitWidth())); - userNameLabel.setText(getUserProfileComboBox().getConverter().toString(newValue)); - buttonPane.layout(); - } - } - }; - @SuppressWarnings("FieldCanBeLocal") // Need to keep a reference as used in WeakChangeListener - ChangeListener userNameLabelWidthListener = (observable, oldValue, newValue) -> { - if (newValue.doubleValue() > 0) { - getUserProfileComboBox().setComboBoxWidth(getUserProfileComboBox().calculateWidth(userNameLabel)); - } - }; - - public UserProfileSkin(ComboBox control, String description, String prompt) { - super(control, description, prompt); - - this.userProfileComboBox = (UserProfileComboBox) control; - - int offset = 5; - arrowX_l = DEFAULT_ARROW_X_L - offset; - arrowX_m = DEFAULT_ARROW_X_M - offset; - arrowX_r = DEFAULT_ARROW_X_R - offset; - - catHashImageView.setLayoutY(7); - - buttonPane.getChildren().setAll(userNameLabel, arrow, catHashImageView); - buttonPane.setCursor(Cursor.HAND); - buttonPane.setLayoutY(-UserProfileComboBox.Y_OFFSET); + private UserProfileMenuItem(UserIdentity userIdentity) { + super(new UserProfileDisplay(userIdentity.getUserProfile())); - control.getSelectionModel().selectedItemProperty().addListener(new WeakChangeListener<>(selectedItemListener)); - userNameLabel.widthProperty().addListener(new WeakChangeListener<>(userNameLabelWidthListener)); - } - - private UserProfileComboBox getUserProfileComboBox() { - return userProfileComboBox; - } + this.userIdentity = userIdentity; - void setIconSize(int iconSize) { - this.iconSize = iconSize; - catHashImageView.setFitWidth(iconSize); - catHashImageView.setFitHeight(catHashImageView.getFitWidth()); + getStyleClass().add("dropdown-menu-item"); + updateSelection(false); + initialize(); } - void setUseMaterialStyle(boolean useMaterialStyle) { - if (useMaterialStyle) { - arrow.setLayoutY(14); - userNameLabel.getStyleClass().add("material-text-field"); - userNameLabel.setLayoutY(5.5); - } else { - arrow.setLayoutY(19); - userNameLabel.getStyleClass().add("bisq-text-19"); - userNameLabel.setLayoutY(14); - } + public void initialize() { } - private void setIsLeftAligned(boolean isLeftAligned) { - this.isLeftAligned = isLeftAligned; + public void dispose() { + setOnAction(null); } - private void setComboBoxWidth(double width) { - buttonPane.setPrefWidth(width); + void updateSelection(boolean isSelected) { + getContent().pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, isSelected); } - private void setMaxComboBoxWidth(double width) { - setComboBoxWidth(width); - userNameLabel.setMaxWidth(width - iconSize - 80); + boolean isSelected() { + return getContent().getPseudoClassStates().contains(SELECTED_PSEUDO_CLASS); } @Override - protected void layoutChildren(final double x, final double y, final double w, final double h) { - if (isLeftAligned) { - double offset = comboBox.getWidth() - 5; - arrowX_l = offset - DEFAULT_ARROW_X_L; - arrowX_m = offset - DEFAULT_ARROW_X_M; - arrowX_r = offset - DEFAULT_ARROW_X_R; - } - super.layoutChildren(x, y, w, h); - - if (isLeftAligned) { - if (userNameLabel.getWidth() > 0) { - double iconX = buttonPane.getPrefWidth() - ICON_PADDING - iconSize; - catHashImageView.setX(iconX); - double arrowX = iconX - ARROW_ICON_PADDING - ARROW_WIDTH; - arrow.setLayoutX(arrowX); - userNameLabel.setLayoutX(arrowX - TEXT_PADDING - userNameLabel.getWidth()); - } - } else { - if (userNameLabel.getWidth() > 0) { - catHashImageView.setX(ICON_PADDING); - arrow.setLayoutX(ICON_PADDING + iconSize + ARROW_ICON_PADDING); - userNameLabel.setLayoutX(ICON_PADDING + iconSize + ARROW_ICON_PADDING + ARROW_WIDTH + TEXT_PADDING); - } - } - } - - @Override - protected int getRowHeight() { - return 50; - } - - @Override - public Node getDisplayNode() { - return null; + public String toString() { + return userIdentity != null ? userIdentity.getUserName() : ""; } } } diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/top/TopPanelView.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/top/TopPanelView.java index 40a844a501..5a9452b94a 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/top/TopPanelView.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/top/TopPanelView.java @@ -46,7 +46,6 @@ public TopPanelView(TopPanelModel model, HBox.setMargin(marketPriceComponent, new Insets(0, 10, 0, 0)); Pane userProfileSelectionRoot = userProfileSelection.getRoot(); - userProfileSelection.setIsLeftAligned(true); HBox.setMargin(userProfileSelectionRoot, new Insets(6.5, 15, 0, 0)); Triple balanceTriple = createBalanceBox(); diff --git a/apps/desktop/desktop/src/main/resources/css/bisq_easy.css b/apps/desktop/desktop/src/main/resources/css/bisq_easy.css index c7ad0befd3..ea1473fcde 100644 --- a/apps/desktop/desktop/src/main/resources/css/bisq_easy.css +++ b/apps/desktop/desktop/src/main/resources/css/bisq_easy.css @@ -323,33 +323,31 @@ -fx-label-padding: 0 7; } +/* OFFER LIST FILTERS */ +.dropdown-offer-list-direction-filter-menu, +.dropdown-offer-list-payment-filter-menu { + -fx-max-height: 20; +} + /* BUY FROM */ .dropdown-offer-list-direction-filter-menu .buy-from-offers { - -fx-label-padding: 0 0 0 5; -fx-text-fill: -bisq2-green; } .dropdown-offer-list-direction-filter-menu:hover .buy-from-offers { - -fx-label-padding: 0 0 0 5; -fx-text-fill: -bisq2-green-lit-20; } /* SELL TO */ .dropdown-offer-list-direction-filter-menu .sell-to-offers { - -fx-label-padding: 0 0 0 5; -fx-text-fill: -bisq2-red-lit-20; } .dropdown-offer-list-direction-filter-menu:hover .sell-to-offers { - -fx-label-padding: 0 0 0 5; -fx-text-fill: -bisq2-red-lit-40; } /* PAYMENT METHODS FILTER */ -.dropdown-offer-list-payment-filter-menu { - -fx-padding: 0 5 0 5; -} - .active-filter { -fx-text-fill: -fx-light-text-color !important; } diff --git a/apps/desktop/desktop/src/main/resources/css/chat.css b/apps/desktop/desktop/src/main/resources/css/chat.css index 7a2e5b6676..4821b2cf3c 100644 --- a/apps/desktop/desktop/src/main/resources/css/chat.css +++ b/apps/desktop/desktop/src/main/resources/css/chat.css @@ -64,9 +64,8 @@ -fx-border-insets: 0 0 -1 0; } -#chat-user-profile-bg { +.chat-user-profile-bg { -fx-background-color: -bisq-dark-grey-40; - -fx-background-insets: 1 20 1 9; -fx-background-radius: 8; } diff --git a/apps/desktop/desktop/src/main/resources/css/controls.css b/apps/desktop/desktop/src/main/resources/css/controls.css index eddc5db396..c72c71fda5 100644 --- a/apps/desktop/desktop/src/main/resources/css/controls.css +++ b/apps/desktop/desktop/src/main/resources/css/controls.css @@ -934,6 +934,7 @@ -fx-background-radius: 6; -fx-border-width: 0; -fx-cursor: hand; + -fx-padding: 0 5 0 5; } .dropdown-menu .label { diff --git a/apps/desktop/desktop/src/main/resources/css/user.css b/apps/desktop/desktop/src/main/resources/css/user.css index 341da89493..f5f2590b1a 100644 --- a/apps/desktop/desktop/src/main/resources/css/user.css +++ b/apps/desktop/desktop/src/main/resources/css/user.css @@ -87,3 +87,16 @@ -fx-background-color: -bisq-dark-grey-20; -fx-background-radius: 8; } + +/******************************************************************************* + * User Profile Controller * + ******************************************************************************/ + +.user-profile-selection .dropdown-menu { + -fx-alignment: center; + -fx-min-height: 45; +} + +.user-profile-selection .dropdown-menu .dropdown-menu-content-hbox { + -fx-alignment: center; +}