diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java index be2d89784..6c3a7f8b2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java @@ -23,7 +23,6 @@ import com.google.gson.stream.JsonWriter; import javafx.beans.binding.Bindings; import javafx.beans.binding.ObjectBinding; import javafx.scene.paint.Color; - import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.ResourceNotFoundError; import org.jackhuang.hmcl.util.io.FileUtils; @@ -50,12 +49,14 @@ public class Theme { Color.web("#B71C1C") // red }; + private final Color paint; private final String color; private final String name; Theme(String name, String color) { this.name = name; this.color = color; + this.paint = Color.web(color); } public String getName() { @@ -84,6 +85,7 @@ public class Theme { File temp = File.createTempFile("hmcl", ".css"); FileUtils.writeText(temp, IOUtils.readFullyAsString(ResourceNotFoundError.getResourceAsStream("/assets/css/custom.css")) .replace("%base-color%", color) + .replace("%base-rippler-color%", String.format("rgba(%d, %d, %d, 0.3)", (int)Math.ceil(paint.getRed() * 256), (int)Math.ceil(paint.getGreen() * 256), (int)Math.ceil(paint.getBlue() * 256))) .replace("%font-color%", getColorDisplayName(getForegroundColor()))); css = temp.toURI().toString(); } catch (IOException | NullPointerException e) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java index eacc93890..cc7713ad2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java @@ -100,7 +100,7 @@ public class AccountListItemSkin extends SkinBase { right.getChildren().add(btnRemove); root.setRight(right); - root.setStyle("-fx-background-color: white; -fx-padding: 8 8 8 0;"); + root.setStyle("-fx-background-color: white; -fx-background-radius: 4; -fx-padding: 8 8 8 0;"); JFXDepthManager.setDepth(root, 1); getChildren().setAll(root); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AuthlibInjectorServersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AuthlibInjectorServersPage.java index d10aa329e..47efb0d69 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AuthlibInjectorServersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AuthlibInjectorServersPage.java @@ -23,6 +23,7 @@ import javafx.collections.ObservableList; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.ListPage; +import org.jackhuang.hmcl.ui.construct.Navigator; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.util.javafx.MappedObservableList; @@ -37,6 +38,7 @@ public class AuthlibInjectorServersPage extends ListPage image = new SimpleObjectProperty<>(this, "image"); private final ObjectProperty rightGraphic = new SimpleObjectProperty<>(this, "rightGraphic"); private final StringProperty title = new SimpleStringProperty(this, "title"); + private final BooleanProperty active = new SimpleBooleanProperty(this, "active"); private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle"); private final BooleanProperty actionButtonVisible = new SimpleBooleanProperty(this, "actionButtonVisible", true); public AdvancedListItem() { + getStyleClass().add("advanced-list-item"); addEventHandler(MouseEvent.MOUSE_CLICKED, e -> fireEvent(new ActionEvent())); } @@ -73,6 +75,18 @@ public class AdvancedListItem extends Control { this.title.set(title); } + public boolean isActive() { + return active.get(); + } + + public BooleanProperty activeProperty() { + return active; + } + + public void setActive(boolean active) { + this.active.set(active); + } + public String getSubtitle() { return subtitle.get(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItemSkin.java index d52803a7d..e15d5bac5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItemSkin.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hmcl.ui.construct; +import javafx.css.PseudoClass; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; @@ -30,13 +31,19 @@ import javafx.scene.text.TextAlignment; import org.jackhuang.hmcl.ui.FXUtils; public class AdvancedListItemSkin extends SkinBase { + private final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected"); public AdvancedListItemSkin(AdvancedListItem skinnable) { super(skinnable); StackPane stackPane = new StackPane(); + stackPane.getStyleClass().add("container"); RipplerContainer container = new RipplerContainer(stackPane); + FXUtils.onChangeAndOperate(skinnable.activeProperty(), active -> { + skinnable.pseudoClassStateChanged(SELECTED, active); + }); + BorderPane root = new BorderPane(); root.setPickOnBounds(false); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java index 01c8e59af..f070fc543 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java @@ -117,9 +117,11 @@ public class ComponentList extends Control { protected static class Skin extends SkinBase { private static final PseudoClass PSEUDO_CLASS_FIRST = PseudoClass.getPseudoClass("first"); + private static final PseudoClass PSEUDO_CLASS_LAST = PseudoClass.getPseudoClass("last"); private final ObservableList list; private final ObjectBinding firstItem; + private final ObjectBinding lastItem; protected Skin(ComponentList control) { super(control); @@ -140,6 +142,16 @@ public class ComponentList extends Control { if (!list.isEmpty()) list.get(0).pseudoClassStateChanged(PSEUDO_CLASS_FIRST, true); + lastItem = Bindings.valueAt(list, Bindings.subtract(Bindings.size(list), 1)); + lastItem.addListener((observable, oldValue, newValue) -> { + if (newValue != null) + newValue.pseudoClassStateChanged(PSEUDO_CLASS_LAST, true); + if (oldValue != null) + oldValue.pseudoClassStateChanged(PSEUDO_CLASS_LAST, false); + }); + if (!list.isEmpty()) + list.get(list.size() - 1).pseudoClassStateChanged(PSEUDO_CLASS_LAST, true); + VBox vbox = new VBox(); Bindings.bindContent(vbox.getChildren(), list); getChildren().setAll(vbox); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FloatScrollBarSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FloatScrollBarSkin.java index 4188fe623..3438fa11f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FloatScrollBarSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FloatScrollBarSkin.java @@ -1,4 +1,4 @@ -/** +/* * Hello Minecraft! Launcher * Copyright (C) 2020 huangyuhui and contributors * diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java index f7dd09d3a..4cdfb567f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/Navigator.java @@ -17,7 +17,9 @@ */ package org.jackhuang.hmcl.ui.construct; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.event.Event; import javafx.event.EventHandler; @@ -37,11 +39,13 @@ import java.util.logging.Level; public class Navigator extends TransitionPane { private static final String PROPERTY_DIALOG_CLOSE_HANDLER = Navigator.class.getName() + ".closeListener"; + private final BooleanProperty backable = new SimpleBooleanProperty(this, "backable"); private final Stack stack = new Stack<>(); private boolean initialized = false; public void init(Node init) { stack.push(init); + backable.set(canGoBack()); getChildren().setAll(init); fireEvent(new NavigationEvent(this, init, NavigationEvent.NAVIGATED)); @@ -62,6 +66,7 @@ public class Navigator extends TransitionPane { Logging.LOG.info("Navigate to " + node); stack.push(node); + backable.set(canGoBack()); NavigationEvent navigating = new NavigationEvent(this, from, NavigationEvent.NAVIGATING); fireEvent(navigating); @@ -103,6 +108,7 @@ public class Navigator extends TransitionPane { Logging.LOG.info("Closed page " + from); stack.pop(); + backable.set(canGoBack()); Node node = stack.peek(); NavigationEvent navigating = new NavigationEvent(this, from, NavigationEvent.NAVIGATING); @@ -131,6 +137,18 @@ public class Navigator extends TransitionPane { return stack.size() > 1; } + public boolean isBackable() { + return backable.get(); + } + + public BooleanProperty backableProperty() { + return backable; + } + + public void setBackable(boolean backable) { + this.backable.set(backable); + } + public int size() { return stack.size(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabControl.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabControl.java new file mode 100644 index 000000000..b5965add9 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabControl.java @@ -0,0 +1,249 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2020 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.construct; + +import javafx.beans.property.*; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.scene.AccessibleAttribute; +import javafx.scene.Node; +import javafx.scene.control.SingleSelectionModel; + +import java.util.function.Supplier; + +public interface TabControl { + ObservableList getTabs(); + + class TabControlSelectionModel extends SingleSelectionModel { + private final TabControl tabHeader; + + public TabControlSelectionModel(final TabControl t) { + if (t == null) { + throw new NullPointerException("TabPane can not be null"); + } + this.tabHeader = t; + + // watching for changes to the items list content + final ListChangeListener itemsContentObserver = c -> { + while (c.next()) { + for (Tab tab : c.getRemoved()) { + if (tab != null && !tabHeader.getTabs().contains(tab)) { + if (tab.isSelected()) { + tab.setSelected(false); + final int tabIndex = c.getFrom(); + + // we always try to select the nearest, non-disabled + // tab from the position of the closed tab. + findNearestAvailableTab(tabIndex, true); + } + } + } + if (c.wasAdded() || c.wasRemoved()) { + // The selected tab index can be out of sync with the list of tab if + // we add or remove tabs before the selected tab. + if (getSelectedIndex() != tabHeader.getTabs().indexOf(getSelectedItem())) { + clearAndSelect(tabHeader.getTabs().indexOf(getSelectedItem())); + } + } + } + if (getSelectedIndex() == -1 && getSelectedItem() == null && tabHeader.getTabs().size() > 0) { + // we go looking for the first non-disabled tab, as opposed to + // just selecting the first tab (fix for RT-36908) + findNearestAvailableTab(0, true); + } else if (tabHeader.getTabs().isEmpty()) { + clearSelection(); + } + }; + if (this.tabHeader.getTabs() != null) { + this.tabHeader.getTabs().addListener(itemsContentObserver); + } + } + + // API Implementation + @Override public void select(int index) { + if (index < 0 || (getItemCount() > 0 && index >= getItemCount()) || + (index == getSelectedIndex() && getModelItem(index).isSelected())) { + return; + } + + // Unselect the old tab + if (getSelectedIndex() >= 0 && getSelectedIndex() < tabHeader.getTabs().size()) { + tabHeader.getTabs().get(getSelectedIndex()).setSelected(false); + } + + setSelectedIndex(index); + + Tab tab = getModelItem(index); + if (tab != null) { + setSelectedItem(tab); + } + + // Select the new tab + if (getSelectedIndex() >= 0 && getSelectedIndex() < tabHeader.getTabs().size()) { + tabHeader.getTabs().get(getSelectedIndex()).setSelected(true); + } + + /* Does this get all the change events */ + ((Node) tabHeader).notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_ITEM); + } + + @Override public void select(Tab tab) { + final int itemCount = getItemCount(); + + for (int i = 0; i < itemCount; i++) { + final Tab value = getModelItem(i); + if (value != null && value.equals(tab)) { + select(i); + return; + } + } + if (tab != null) { + setSelectedItem(tab); + } + } + + @Override protected Tab getModelItem(int index) { + final ObservableList items = tabHeader.getTabs(); + if (items == null) return null; + if (index < 0 || index >= items.size()) return null; + return items.get(index); + } + + @Override protected int getItemCount() { + final ObservableList items = tabHeader.getTabs(); + return items == null ? 0 : items.size(); + } + + private Tab findNearestAvailableTab(int tabIndex, boolean doSelect) { + // we always try to select the nearest, non-disabled + // tab from the position of the closed tab. + final int tabCount = getItemCount(); + int i = 1; + Tab bestTab = null; + while (true) { + // look leftwards + int downPos = tabIndex - i; + if (downPos >= 0) { + Tab _tab = getModelItem(downPos); + if (_tab != null) { + bestTab = _tab; + break; + } + } + + // look rightwards. We subtract one as we need + // to take into account that a tab has been removed + // and if we don't do this we'll miss the tab + // to the right of the tab (as it has moved into + // the removed tabs position). + int upPos = tabIndex + i - 1; + if (upPos < tabCount) { + Tab _tab = getModelItem(upPos); + if (_tab != null) { + bestTab = _tab; + break; + } + } + + if (downPos < 0 && upPos >= tabCount) { + break; + } + i++; + } + + if (doSelect && bestTab != null) { + select(bestTab); + } + + return bestTab; + } + } + + public static class Tab { + private final StringProperty id = new SimpleStringProperty(this, "id"); + private final StringProperty text = new SimpleStringProperty(this, "text"); + private final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper(this, "selected"); + private final ObjectProperty node = new SimpleObjectProperty<>(this, "node"); + private Supplier nodeSupplier; + + public Tab(String id) { + setId(id); + } + + public Tab(String id, String text) { + setId(id); + setText(text); + } + + public Supplier getNodeSupplier() { + return nodeSupplier; + } + + public void setNodeSupplier(Supplier nodeSupplier) { + this.nodeSupplier = nodeSupplier; + } + + public String getId() { + return id.get(); + } + + public StringProperty idProperty() { + return id; + } + + public void setId(String id) { + this.id.set(id); + } + + public String getText() { + return text.get(); + } + + public StringProperty textProperty() { + return text; + } + + public void setText(String text) { + this.text.set(text); + } + + public boolean isSelected() { + return selected.get(); + } + + public ReadOnlyBooleanProperty selectedProperty() { + return selected.getReadOnlyProperty(); + } + + private void setSelected(boolean selected) { + this.selected.set(selected); + } + + public Node getNode() { + return node.get(); + } + + public ObjectProperty nodeProperty() { + return node; + } + + public void setNode(Node node) { + this.node.set(node); + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java index 60298af9e..458fde81b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TabHeader.java @@ -21,14 +21,13 @@ import com.jfoenix.controls.JFXRippler; import javafx.animation.*; import javafx.application.Platform; import javafx.beans.binding.Bindings; -import javafx.beans.property.*; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.css.PseudoClass; import javafx.geometry.Insets; import javafx.geometry.Side; -import javafx.scene.AccessibleAttribute; import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.input.MouseButton; @@ -40,7 +39,7 @@ import javafx.util.Duration; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.javafx.MappedObservableList; -public class TabHeader extends Control { +public class TabHeader extends Control implements TabControl { public TabHeader(Tab... tabs) { getStyleClass().setAll("tab-header"); @@ -51,11 +50,12 @@ public class TabHeader extends Control { private ObservableList tabs = FXCollections.observableArrayList(); + @Override public ObservableList getTabs() { return tabs; } - private final ObjectProperty> selectionModel = new SimpleObjectProperty<>(this, "selectionModel", new TabHeaderSelectionModel(this)); + private final ObjectProperty> selectionModel = new SimpleObjectProperty<>(this, "selectionModel", new TabControlSelectionModel(this)); public SingleSelectionModel getSelectionModel() { return selectionModel.get(); @@ -69,151 +69,6 @@ public class TabHeader extends Control { this.selectionModel.set(selectionModel); } - static class TabHeaderSelectionModel extends SingleSelectionModel { - private final TabHeader tabHeader; - - public TabHeaderSelectionModel(final TabHeader t) { - if (t == null) { - throw new NullPointerException("TabPane can not be null"); - } - this.tabHeader = t; - - // watching for changes to the items list content - final ListChangeListener itemsContentObserver = c -> { - while (c.next()) { - for (Tab tab : c.getRemoved()) { - if (tab != null && !tabHeader.getTabs().contains(tab)) { - if (tab.isSelected()) { - tab.setSelected(false); - final int tabIndex = c.getFrom(); - - // we always try to select the nearest, non-disabled - // tab from the position of the closed tab. - findNearestAvailableTab(tabIndex, true); - } - } - } - if (c.wasAdded() || c.wasRemoved()) { - // The selected tab index can be out of sync with the list of tab if - // we add or remove tabs before the selected tab. - if (getSelectedIndex() != tabHeader.getTabs().indexOf(getSelectedItem())) { - clearAndSelect(tabHeader.getTabs().indexOf(getSelectedItem())); - } - } - } - if (getSelectedIndex() == -1 && getSelectedItem() == null && tabHeader.getTabs().size() > 0) { - // we go looking for the first non-disabled tab, as opposed to - // just selecting the first tab (fix for RT-36908) - findNearestAvailableTab(0, true); - } else if (tabHeader.getTabs().isEmpty()) { - clearSelection(); - } - }; - if (this.tabHeader.getTabs() != null) { - this.tabHeader.getTabs().addListener(itemsContentObserver); - } - } - - // API Implementation - @Override public void select(int index) { - if (index < 0 || (getItemCount() > 0 && index >= getItemCount()) || - (index == getSelectedIndex() && getModelItem(index).isSelected())) { - return; - } - - // Unselect the old tab - if (getSelectedIndex() >= 0 && getSelectedIndex() < tabHeader.getTabs().size()) { - tabHeader.getTabs().get(getSelectedIndex()).setSelected(false); - } - - setSelectedIndex(index); - - Tab tab = getModelItem(index); - if (tab != null) { - setSelectedItem(tab); - } - - // Select the new tab - if (getSelectedIndex() >= 0 && getSelectedIndex() < tabHeader.getTabs().size()) { - tabHeader.getTabs().get(getSelectedIndex()).setSelected(true); - } - - /* Does this get all the change events */ - tabHeader.notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_ITEM); - } - - @Override public void select(Tab tab) { - final int itemCount = getItemCount(); - - for (int i = 0; i < itemCount; i++) { - final Tab value = getModelItem(i); - if (value != null && value.equals(tab)) { - select(i); - return; - } - } - if (tab != null) { - setSelectedItem(tab); - } - } - - @Override protected Tab getModelItem(int index) { - final ObservableList items = tabHeader.getTabs(); - if (items == null) return null; - if (index < 0 || index >= items.size()) return null; - return items.get(index); - } - - @Override protected int getItemCount() { - final ObservableList items = tabHeader.getTabs(); - return items == null ? 0 : items.size(); - } - - private Tab findNearestAvailableTab(int tabIndex, boolean doSelect) { - // we always try to select the nearest, non-disabled - // tab from the position of the closed tab. - final int tabCount = getItemCount(); - int i = 1; - Tab bestTab = null; - while (true) { - // look leftwards - int downPos = tabIndex - i; - if (downPos >= 0) { - Tab _tab = getModelItem(downPos); - if (_tab != null) { - bestTab = _tab; - break; - } - } - - // look rightwards. We subtract one as we need - // to take into account that a tab has been removed - // and if we don't do this we'll miss the tab - // to the right of the tab (as it has moved into - // the removed tabs position). - int upPos = tabIndex + i - 1; - if (upPos < tabCount) { - Tab _tab = getModelItem(upPos); - if (_tab != null) { - bestTab = _tab; - break; - } - } - - if (downPos < 0 && upPos >= tabCount) { - break; - } - i++; - } - - if (doSelect && bestTab != null) { - select(bestTab); - } - - return bestTab; - } - } - @Override protected Skin createDefaultSkin() { return new TabHeaderSkin(this); @@ -450,55 +305,4 @@ public class TabHeader extends Control { } } } - - public static class Tab { - private final StringProperty id = new SimpleStringProperty(this, "id"); - private final StringProperty text = new SimpleStringProperty(this, "text"); - private final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper(this, "selected"); - - public Tab(String id) { - setId(id); - } - - public Tab(String id, String text) { - setId(id); - setText(text); - } - - public String getId() { - return id.get(); - } - - public StringProperty idProperty() { - return id; - } - - public void setId(String id) { - this.id.set(id); - } - - public String getText() { - return text.get(); - } - - public StringProperty textProperty() { - return text; - } - - public void setText(String text) { - this.text.set(text); - } - - public boolean isSelected() { - return selected.get(); - } - - public ReadOnlyBooleanProperty selectedProperty() { - return selected.getReadOnlyProperty(); - } - - private void setSelected(boolean selected) { - this.selected.set(selected); - } - } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorNavigatorPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorNavigatorPage.java index f74f18c5e..7f0c86115 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorNavigatorPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorNavigatorPage.java @@ -17,13 +17,9 @@ */ package org.jackhuang.hmcl.ui.decorator; -import javafx.beans.binding.Bindings; import javafx.scene.Node; -import javafx.scene.layout.Region; -import javafx.scene.layout.StackPane; import org.jackhuang.hmcl.ui.animation.AnimationProducer; import org.jackhuang.hmcl.ui.construct.Navigator; -import org.jackhuang.hmcl.ui.wizard.Refreshable; public abstract class DecoratorNavigatorPage extends DecoratorTransitionPage { protected final Navigator navigator = new Navigator(); @@ -31,6 +27,7 @@ public abstract class DecoratorNavigatorPage extends DecoratorTransitionPage { { this.navigator.setOnNavigating(this::onNavigating); this.navigator.setOnNavigated(this::onNavigated); + backableProperty().bind(navigator.backableProperty()); } @Override @@ -50,39 +47,11 @@ public abstract class DecoratorNavigatorPage extends DecoratorTransitionPage { private void onNavigating(Navigator.NavigationEvent event) { if (event.getSource() != this.navigator) return; - Node from = event.getNode(); - - if (from instanceof DecoratorPage) - ((DecoratorPage) from).back(); + onNavigating(event.getNode()); } private void onNavigated(Navigator.NavigationEvent event) { if (event.getSource() != this.navigator) return; - Node to = event.getNode(); - - if (to instanceof Refreshable) { - refreshableProperty().bind(((Refreshable) to).refreshableProperty()); - } else { - refreshableProperty().unbind(); - refreshableProperty().set(false); - } - - if (to instanceof DecoratorPage) { - state.bind(Bindings.createObjectBinding(() -> { - State state = ((DecoratorPage) to).stateProperty().get(); - return new State(state.getTitle(), state.getTitleNode(), navigator.canGoBack(), state.isRefreshable(), true); - }, ((DecoratorPage) to).stateProperty())); - } else { - state.unbind(); - state.set(new State("", null, navigator.canGoBack(), false, true)); - } - - if (to instanceof Region) { - Region region = (Region) to; - // Let root pane fix window size. - StackPane parent = (StackPane) region.getParent(); - region.prefWidthProperty().bind(parent.widthProperty()); - region.prefHeightProperty().bind(parent.heightProperty()); - } + onNavigated(event.getNode()); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTabPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTabPage.java new file mode 100644 index 000000000..7154d808a --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTabPage.java @@ -0,0 +1,74 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2020 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.decorator; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.control.SingleSelectionModel; +import org.jackhuang.hmcl.ui.animation.ContainerAnimations; +import org.jackhuang.hmcl.ui.construct.Navigator; +import org.jackhuang.hmcl.ui.construct.TabControl; +import org.jackhuang.hmcl.ui.construct.TabHeader; + +public abstract class DecoratorTabPage extends DecoratorTransitionPage implements TabControl { + + public DecoratorTabPage() { + getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> { + if (newValue.getNode() == null && newValue.getNodeSupplier() != null) { + newValue.setNode(newValue.getNodeSupplier().get()); + } + if (newValue.getNode() != null) { + onNavigating(getCurrentPage()); + if (getCurrentPage() != null) getCurrentPage().fireEvent(new Navigator.NavigationEvent(null, getCurrentPage(), Navigator.NavigationEvent.NAVIGATING)); + navigate(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer()); + onNavigated(getCurrentPage()); + if (getCurrentPage() != null) getCurrentPage().fireEvent(new Navigator.NavigationEvent(null, getCurrentPage(), Navigator.NavigationEvent.NAVIGATED)); + } + }); + } + + public DecoratorTabPage(TabHeader.Tab... tabs) { + this(); + if (tabs != null) { + getTabs().addAll(tabs); + } + } + + private ObservableList tabs = FXCollections.observableArrayList(); + + @Override + public ObservableList getTabs() { + return tabs; + } + + private final ObjectProperty> selectionModel = new SimpleObjectProperty<>(this, "selectionModel", new TabControl.TabControlSelectionModel(this)); + + public SingleSelectionModel getSelectionModel() { + return selectionModel.get(); + } + + public ObjectProperty> selectionModelProperty() { + return selectionModel; + } + + public void setSelectionModel(SingleSelectionModel selectionModel) { + this.selectionModel.set(selectionModel); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTransitionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTransitionPage.java index 1b00cc0a1..37efb8c1f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTransitionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTransitionPage.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hmcl.ui.decorator; +import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; @@ -24,6 +25,8 @@ import javafx.beans.property.SimpleBooleanProperty; import javafx.scene.Node; import javafx.scene.control.Control; import javafx.scene.control.Skin; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; import org.jackhuang.hmcl.ui.animation.AnimationProducer; import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.wizard.Refreshable; @@ -32,13 +35,45 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public abstract class DecoratorTransitionPage extends Control implements DecoratorPage { protected final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n(""))); + private final BooleanProperty backable = new SimpleBooleanProperty(false); private final BooleanProperty refreshable = new SimpleBooleanProperty(false); private Node currentPage; protected final TransitionPane transitionPane = new TransitionPane(); protected void navigate(Node page, AnimationProducer animation) { transitionPane.setContent(currentPage = page, animation); - refreshable.setValue(page instanceof Refreshable); + } + + protected void onNavigating(Node from) { + if (from instanceof DecoratorPage) + ((DecoratorPage) from).back(); + } + + protected void onNavigated(Node to) { + if (to instanceof Refreshable) { + refreshableProperty().bind(((Refreshable) to).refreshableProperty()); + } else { + refreshableProperty().unbind(); + refreshableProperty().set(false); + } + + if (to instanceof DecoratorPage) { + state.bind(Bindings.createObjectBinding(() -> { + State state = ((DecoratorPage) to).stateProperty().get(); + return new State(state.getTitle(), state.getTitleNode(), backable.get(), state.isRefreshable(), true); + }, ((DecoratorPage) to).stateProperty())); + } else { + state.unbind(); + state.set(new State("", null, backable.get(), false, true)); + } + + if (to instanceof Region) { + Region region = (Region) to; + // Let root pane fix window size. + StackPane parent = (StackPane) region.getParent(); + region.prefWidthProperty().bind(parent.widthProperty()); + region.prefHeightProperty().bind(parent.heightProperty()); + } } @Override @@ -48,6 +83,18 @@ public abstract class DecoratorTransitionPage extends Control implements Decorat return currentPage; } + public boolean isBackable() { + return backable.get(); + } + + public BooleanProperty backableProperty() { + return backable; + } + + public void setBackable(boolean backable) { + this.backable.set(backable); + } + public boolean isRefreshable() { return refreshable.get(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorWizardDisplayer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorWizardDisplayer.java index dc385eb20..c6bc30477 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorWizardDisplayer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorWizardDisplayer.java @@ -42,7 +42,7 @@ public class DecoratorWizardDisplayer extends DecoratorTransitionPage implements wizardController.setProvider(provider); wizardController.onStart(); - addEventHandler(Navigator.NavigationEvent.NAVIGATING, this::onDecoratorPageNavigating); + addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating); } @Override @@ -77,6 +77,13 @@ public class DecoratorWizardDisplayer extends DecoratorTransitionPage implements else title = ""; state.set(new State(title, null, true, refreshableProperty().get(), true)); + + if (page instanceof Refreshable) { + refreshableProperty().bind(((Refreshable) page).refreshableProperty()); + } else { + refreshableProperty().unbind(); + refreshableProperty().set(false); + } } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index 16c7a13e2..4e2aaee3c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui.main; import javafx.application.Platform; import javafx.geometry.Insets; +import javafx.scene.Node; import javafx.scene.control.SkinBase; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; @@ -37,10 +38,10 @@ import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.account.AccountAdvancedListItem; import org.jackhuang.hmcl.ui.account.AccountList; import org.jackhuang.hmcl.ui.account.AddAccountPane; -import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.construct.AdvancedListBox; import org.jackhuang.hmcl.ui.construct.AdvancedListItem; -import org.jackhuang.hmcl.ui.decorator.DecoratorNavigatorPage; +import org.jackhuang.hmcl.ui.construct.TabHeader; +import org.jackhuang.hmcl.ui.decorator.DecoratorTabPage; import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider; import org.jackhuang.hmcl.ui.profile.ProfileAdvancedListItem; import org.jackhuang.hmcl.ui.profile.ProfileList; @@ -63,19 +64,48 @@ import static org.jackhuang.hmcl.ui.FXUtils.newImage; import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public class RootPage extends DecoratorNavigatorPage { +public class RootPage extends DecoratorTabPage { private MainPage mainPage = null; private SettingsPage settingsPage = null; private GameList gameListPage = null; private AccountList accountListPage = null; private ProfileList profileListPage = null; + private final TabHeader.Tab mainTab = new TabHeader.Tab("main"); + private final TabHeader.Tab settingsTab = new TabHeader.Tab("settings"); + private final TabHeader.Tab gameTab = new TabHeader.Tab("game"); + private final TabHeader.Tab accountTab = new TabHeader.Tab("account"); + private final TabHeader.Tab profileTab = new TabHeader.Tab("profile"); + public RootPage() { EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> onRefreshedVersions((HMCLGameRepository) event.getSource())); Profile profile = Profiles.getSelectedProfile(); if (profile != null && profile.getRepository().isLoaded()) onRefreshedVersions(Profiles.selectedProfileProperty().get().getRepository()); + + mainTab.setNodeSupplier(this::getMainPage); + settingsTab.setNodeSupplier(this::getSettingsPage); + gameTab.setNodeSupplier(this::getGameListPage); + accountTab.setNodeSupplier(this::getAccountListPage); + profileTab.setNodeSupplier(this::getProfileListPage); + getTabs().setAll(mainTab, settingsTab, gameTab, accountTab, profileTab); + } + + @Override + public boolean back() { + if (mainTab.isSelected()) return true; + else { + getSelectionModel().select(mainTab); + return false; + } + } + + @Override + protected void onNavigated(Node to) { + backableProperty().set(!(to instanceof MainPage)); + + super.onNavigated(to); } @Override @@ -156,7 +186,8 @@ public class RootPage extends DecoratorNavigatorPage { // first item in left sidebar AccountAdvancedListItem accountListItem = new AccountAdvancedListItem(); - accountListItem.setOnAction(e -> getSkinnable().navigate(getSkinnable().getAccountListPage(), ContainerAnimations.FADE.getAnimationProducer())); + accountListItem.activeProperty().bind(control.accountTab.selectedProperty()); + accountListItem.setOnAction(e -> control.getSelectionModel().select(control.accountTab)); accountListItem.accountProperty().bind(Accounts.selectedAccountProperty()); // second item in left sidebar @@ -166,7 +197,7 @@ public class RootPage extends DecoratorNavigatorPage { Profile profile = Profiles.getSelectedProfile(); String version = Profiles.getSelectedVersion(); if (version == null) { - getSkinnable().navigate(getSkinnable().getGameListPage(), ContainerAnimations.FADE.getAnimationProducer()); + control.getSelectionModel().select(control.gameTab); } else { Versions.modifyGameSettings(profile, version); } @@ -174,20 +205,23 @@ public class RootPage extends DecoratorNavigatorPage { // third item in left sidebar AdvancedListItem gameItem = new AdvancedListItem(); + gameItem.activeProperty().bind(control.gameTab.selectedProperty()); gameItem.setImage(newImage("/assets/img/bookshelf.png")); gameItem.setTitle(i18n("version.manage")); - gameItem.setOnAction(e -> getSkinnable().navigate(getSkinnable().getGameListPage(), ContainerAnimations.FADE.getAnimationProducer())); + gameItem.setOnAction(e -> control.getSelectionModel().select(control.gameTab)); // forth item in left sidebar ProfileAdvancedListItem profileListItem = new ProfileAdvancedListItem(); - profileListItem.setOnAction(e -> getSkinnable().navigate(getSkinnable().getProfileListPage(), ContainerAnimations.FADE.getAnimationProducer())); + profileListItem.activeProperty().bind(control.profileTab.selectedProperty()); + profileListItem.setOnAction(e -> control.getSelectionModel().select(control.profileTab)); profileListItem.profileProperty().bind(Profiles.selectedProfileProperty()); // fifth item in left sidebar AdvancedListItem launcherSettingsItem = new AdvancedListItem(); + launcherSettingsItem.activeProperty().bind(control.settingsTab.selectedProperty()); launcherSettingsItem.setImage(newImage("/assets/img/command.png")); launcherSettingsItem.setTitle(i18n("settings.launcher")); - launcherSettingsItem.setOnAction(e -> getSkinnable().navigate(getSkinnable().getSettingsPage(), ContainerAnimations.FADE.getAnimationProducer())); + launcherSettingsItem.setOnAction(e -> control.getSelectionModel().select(control.settingsTab)); // the left sidebar AdvancedListBox sideBar = new AdvancedListBox() @@ -217,10 +251,10 @@ public class RootPage extends DecoratorNavigatorPage { } { - control.navigator.getStyleClass().add("jfx-decorator-content-container"); - control.navigator.init(getSkinnable().getMainPage()); - FXUtils.setOverflowHidden(control.navigator, 8); - StackPane wrapper = new StackPane(control.navigator); + control.transitionPane.getStyleClass().add("jfx-decorator-content-container"); + control.transitionPane.getChildren().setAll(getSkinnable().getMainPage()); + FXUtils.setOverflowHidden(control.transitionPane, 8); + StackPane wrapper = new StackPane(control.transitionPane); wrapper.setPadding(new Insets(4)); root.setCenter(wrapper); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java index 211679600..7bdc7e91e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java @@ -73,7 +73,7 @@ public final class SettingsPage extends SettingsView implements DecoratorPage { public SettingsPage() { FXUtils.smoothScrolling(scroll); - addEventHandler(Navigator.NavigationEvent.NAVIGATING, this::onDecoratorPageNavigating); + addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating); // ==== Download sources ==== cboDownloadSource.getItems().setAll(DownloadProviders.providersById.keySet()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java index 1eb51ce04..fd89c032d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileListItemSkin.java @@ -73,7 +73,7 @@ public class ProfileListItemSkin extends SkinBase { right.getChildren().add(btnRemove); root.setRight(right); - root.setStyle("-fx-background-color: white; -fx-padding: 8 8 8 0;"); + root.setStyle("-fx-background-color: white; -fx-background-radius: 4; -fx-padding: 8 8 8 0;"); JFXDepthManager.setDepth(root, 1); item.titleProperty().bind(skinnable.titleProperty()); item.subtitleProperty().bind(skinnable.subtitleProperty()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameList.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameList.java index 14eff58ad..4464051a3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameList.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameList.java @@ -57,7 +57,7 @@ public class GameList extends ListPageBase implements DecoratorPag }); Profiles.registerVersionsListener(this::loadVersions); - addEventHandler(Navigator.NavigationEvent.NAVIGATING, this::onDecoratorPageNavigating); + addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating); } private void loadVersions(Profile profile) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java index 8bfbd1696..fb5a6cf5b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java @@ -80,6 +80,11 @@ public class VersionPage extends Control implements DecoratorPage { loadVersion(newValue, profile); }); + versionSettingsTab.setNode(versionSettingsPage); + modListTab.setNode(modListPage); + installerListTab.setNode(installerListPage); + worldListTab.setNode(worldListPage); + addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated); } @@ -211,15 +216,7 @@ public class VersionPage extends Control implements DecoratorPage { control.worldListTab); control.selectedTab.bind(tabPane.getSelectionModel().selectedItemProperty()); FXUtils.onChangeAndOperate(tabPane.getSelectionModel().selectedItemProperty(), newValue -> { - if (control.versionSettingsTab.equals(newValue)) { - control.transitionPane.setContent(control.versionSettingsPage, ContainerAnimations.FADE.getAnimationProducer()); - } else if (control.modListTab.equals(newValue)) { - control.transitionPane.setContent(control.modListPage, ContainerAnimations.FADE.getAnimationProducer()); - } else if (control.installerListTab.equals(newValue)) { - control.transitionPane.setContent(control.installerListPage, ContainerAnimations.FADE.getAnimationProducer()); - } else if (control.worldListTab.equals(newValue)) { - control.transitionPane.setContent(control.worldListPage, ContainerAnimations.FADE.getAnimationProducer()); - } + control.transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer()); }); HBox toolBar = new HBox(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java index f0258bc67..972f42602 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java @@ -105,7 +105,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag public VersionSettingsPage() { FXUtils.loadFXML(this, "/assets/fxml/version/version-settings.fxml"); - addEventHandler(Navigator.NavigationEvent.NAVIGATING, this::onDecoratorPageNavigating); + addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onDecoratorPageNavigating); cboLauncherVisibility.getItems().setAll(LauncherVisibility.values()); cboLauncherVisibility.setConverter(stringConverter(e -> i18n("settings.advanced.launcher_visibility." + e.name().toLowerCase()))); diff --git a/HMCL/src/main/resources/assets/css/blue.css b/HMCL/src/main/resources/assets/css/blue.css index b89a16e68..4427c574b 100644 --- a/HMCL/src/main/resources/assets/css/blue.css +++ b/HMCL/src/main/resources/assets/css/blue.css @@ -19,5 +19,6 @@ -fx-base-color: #5c6bc0; -fx-base-darker-color: derive(-fx-base-color, -10%); -fx-base-check-color: derive(-fx-base-color, 30%); + -fx-base-rippler-color: rgba(92, 107, 192, 0.3); -fx-base-text-fill: white; } \ No newline at end of file diff --git a/HMCL/src/main/resources/assets/css/custom.css b/HMCL/src/main/resources/assets/css/custom.css index 04a636b7a..bce928f9f 100644 --- a/HMCL/src/main/resources/assets/css/custom.css +++ b/HMCL/src/main/resources/assets/css/custom.css @@ -19,5 +19,6 @@ -fx-base-color: %base-color%; -fx-base-darker-color: derive(-fx-base-color, -10%); -fx-base-check-color: derive(-fx-base-color, 30%); + -fx-base-rippler-color: %base-rippler-color%; -fx-base-text-fill: %font-color%; } \ No newline at end of file diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index 0f0a344b2..3bbd7f3f6 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -71,6 +71,10 @@ -fx-text-fill: -fx-base-text-fill; } +.advanced-list-item:selected .container { + -fx-background-color: -fx-base-rippler-color; +} + .notice-pane > .label { -fx-text-fill: #0079FF; -fx-font-size: 20; @@ -692,6 +696,16 @@ } .options-list-item:first { + -fx-background-radius: 4 4 0 0; + -fx-border-width: 0; +} + +.options-list-item:last { + -fx-background-radius: 0 0 4 4; +} + +.options-list-item:first:last { + -fx-background-radius: 4 4 4 4; -fx-border-width: 0; }