Support icon color and add icons page

This commit is contained in:
mkpaz 2023-02-08 18:02:21 +04:00
parent c547b7231b
commit 0eee144d26
6 changed files with 347 additions and 2 deletions

@ -5,6 +5,7 @@ import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import atlantafx.sampler.page.Page;
import atlantafx.sampler.page.components.*;
import atlantafx.sampler.page.general.IconsPage;
import atlantafx.sampler.page.general.ThemePage;
import atlantafx.sampler.page.general.TypographyPage;
import atlantafx.sampler.page.showcase.filemanager.FileManagerPage;
@ -170,6 +171,7 @@ class Sidebar extends StackPane {
caption("GENERAL"),
navLink(ThemePage.NAME, ThemePage.class),
navLink(TypographyPage.NAME, TypographyPage.class),
navLink(IconsPage.NAME, IconsPage.class),
caption("COMPONENTS"),
navLink(OverviewPage.NAME, OverviewPage.class),
navLink(InputGroupPage.NAME, InputGroupPage.class),

@ -0,0 +1,128 @@
package atlantafx.sampler.page.general;
import atlantafx.base.theme.Tweaks;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.control.*;
import org.kordamp.ikonli.Ikon;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import static atlantafx.base.theme.Styles.TEXT_SMALL;
public class IconBrowser extends TableView<List<Ikon>> {
static final int FILTER_LEN = 2;
private final int colNum;
private final List<Ikon> icons;
private final SimpleStringProperty filter = new SimpleStringProperty();
public IconBrowser(int colNum, List<Ikon> icons) {
super();
if (colNum <= 0) {
throw new IllegalArgumentException("Column count must be greater than 0.");
}
if (icons == null || icons.isEmpty()) {
throw new IllegalArgumentException("Icon list cannot be null or empty.");
}
this.colNum = colNum;
this.icons = icons;
filterProperty().addListener((obs, old, val) -> updateData(val));
initTable();
updateData(null);
}
public SimpleStringProperty filterProperty() {
return filter;
}
private void initTable() {
for (int i = 0; i < colNum; i++) {
var col = new TableColumn<List<Ikon>, Ikon>("col" + i);
final int colIndex = i;
col.setCellValueFactory(cb -> {
var row = cb.getValue();
var item = row.size() > colIndex ? row.get(colIndex) : null;
return new SimpleObjectProperty<>(item);
});
col.setCellFactory(cb -> new FontIconCell());
col.getStyleClass().add(Tweaks.ALIGN_CENTER);
getColumns().add(col);
}
setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
getSelectionModel().setCellSelectionEnabled(true);
getStyleClass().add("icon-browser");
}
private void updateData(String filterString) {
var displayedIcons = filterString == null || filterString.isBlank() || filterString.length() < FILTER_LEN
? icons
: icons.stream().filter(icon -> containsString(icon.getDescription(), filterString)).toList();
var data = partitionList(displayedIcons, colNum);
getItems().setAll(data);
}
private <T> Collection<List<T>> partitionList(List<T> list, int size) {
List<List<T>> partitions = new ArrayList<>();
if (list.size() == 0) {
return partitions;
}
int length = list.size();
int numOfPartitions = length / size + ((length % size == 0) ? 0 : 1);
for (int i = 0; i < numOfPartitions; i++) {
int from = i * size;
int to = Math.min((i * size + size), length);
partitions.add(list.subList(from, to));
}
return partitions;
}
private boolean containsString(String s1, String s2) {
return s1.toLowerCase(Locale.ROOT).contains(s2.toLowerCase(Locale.ROOT));
}
///////////////////////////////////////////////////////////////////////////
public static class FontIconCell extends TableCell<List<Ikon>, Ikon> {
private final Label root = new Label();
private final FontIcon fontIcon = new FontIcon();
public FontIconCell() {
super();
root.setContentDisplay(ContentDisplay.TOP);
root.setGraphic(fontIcon);
root.setGraphicTextGap(10);
root.getStyleClass().addAll("icon-label", TEXT_SMALL);
}
@Override
protected void updateItem(Ikon icon, boolean empty) {
super.updateItem(icon, empty);
if (icon == null) {
setGraphic(null);
return;
}
root.setText(icon.getDescription());
fontIcon.setIconCode(icon);
setGraphic(root);
}
}
}

@ -0,0 +1,143 @@
/* SPDX-License-Identifier: MIT */
package atlantafx.sampler.page.general;
import atlantafx.base.controls.CustomTextField;
import atlantafx.base.theme.Styles;
import atlantafx.sampler.page.AbstractPage;
import atlantafx.sampler.page.SampleBlock;
import atlantafx.sampler.theme.CSSFragment;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import org.kordamp.ikonli.Ikon;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.javafx.StackedFontIcon;
import org.kordamp.ikonli.material2.Material2AL;
import org.kordamp.ikonli.material2.Material2MZ;
import org.kordamp.ikonli.material2.Material2OutlinedAL;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP;
import static atlantafx.sampler.page.SampleBlock.BLOCK_VGAP;
import static atlantafx.sampler.util.Controls.hyperlink;
public class IconsPage extends AbstractPage {
public static final String NAME = "Icons";
@Override
public String getName() { return NAME; }
public IconsPage() {
super();
createView();
}
private void createView() {
var headerText = new TextFlow(
new Text("AtlantaFX supports "),
hyperlink("Ikonli", URI.create("https://kordamp.org/ikonli")),
new Text(" iconic fonts that can be used together with some JavaFX components.")
);
var browserText = new TextFlow(
new Text("There's a variety of icon packs. Sampler app uses "),
hyperlink("Material Icons", URI.create("https://kordamp.org/ikonli/cheat-sheet-material2.html")),
new Text(" you can preview below.")
);
var filterText = new CustomTextField();
filterText.setLeft(new FontIcon(Material2MZ.SEARCH));
filterText.setPrefWidth(300);
var filterBox = new HBox(filterText);
filterBox.setAlignment(Pos.CENTER);
var icons = new ArrayList<Ikon>();
icons.addAll(Arrays.asList(Material2AL.values()));
icons.addAll(Arrays.asList(Material2MZ.values()));
var iconBrowser = new IconBrowser(5, icons);
VBox.setVgrow(iconBrowser, Priority.ALWAYS);
iconBrowser.filterProperty().bind(filterText.textProperty());
setUserContent(new VBox(
PAGE_VGAP,
headerText,
expandingHBox(colorSample(), stackingSample()),
browserText,
filterBox,
iconBrowser
));
}
private SampleBlock colorSample() {
var accentIcon = new FontIcon(Material2MZ.THUMB_UP);
accentIcon.getStyleClass().add(Styles.ACCENT);
var successIcon = new FontIcon(Material2MZ.THUMB_UP);
successIcon.getStyleClass().add(Styles.SUCCESS);
var warningIcon = new FontIcon(Material2MZ.THUMB_UP);
warningIcon.getStyleClass().add(Styles.WARNING);
var dangerIcon = new FontIcon(Material2MZ.THUMB_UP);
dangerIcon.getStyleClass().add(Styles.DANGER);
var content = new VBox(
BLOCK_VGAP,
new Label("You can also use pseudo-classes to set icon color."),
new HBox(BLOCK_HGAP, accentIcon, successIcon, warningIcon, dangerIcon)
);
return new SampleBlock("Colors", content);
}
private SampleBlock stackingSample() {
var outerIcon1 = new FontIcon(Material2OutlinedAL.BLOCK);
outerIcon1.getStyleClass().add("outer-icon");
var innerIcon1 = new FontIcon(Material2MZ.PHOTO_CAMERA);
innerIcon1.getStyleClass().add("inner-icon");
var stackIcon1 = new StackedFontIcon();
stackIcon1.getChildren().addAll(innerIcon1, outerIcon1);
new CSSFragment("""
.stacked-ikonli-font-icon > .outer-icon {
-fx-icon-size: 48px;
-fx-icon-color: -color-danger-emphasis;
}
.stacked-ikonli-font-icon > .inner-icon {
-fx-icon-size: 24px;
}
""").addTo(stackIcon1);
var outerIcon2 = new FontIcon(Material2OutlinedAL.CHECK_BOX_OUTLINE_BLANK);
outerIcon2.getStyleClass().add("outer-icon");
var innerIcon2 = new FontIcon(Material2AL.LOCK);
innerIcon2.getStyleClass().add("inner-icon");
var stackIcon2 = new StackedFontIcon();
stackIcon2.getChildren().addAll(outerIcon2, innerIcon2);
new CSSFragment("""
.stacked-ikonli-font-icon > .outer-icon {
-fx-icon-size: 48px;
}
.stacked-ikonli-font-icon > .inner-icon {
-fx-icon-size: 24px;
}
""").addTo(stackIcon2);
var content = new HBox(BLOCK_HGAP, stackIcon1, stackIcon2);
return new SampleBlock("Stacking Icons", content);
}
}

@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
.icon-browser {
-fx-border-color: transparent;
-color-cell-border: transparent;
>.column-header-background {
-fx-max-height: 0;
-fx-pref-height: 0;
-fx-min-height: 0;
}
.table-row-cell {
-fx-cell-size: 80px;
.icon-label {
-fx-padding: 10px;
-fx-cursor: hand;
}
}
.ikonli-font-icon {
-fx-icon-size: 36px;
}
}

@ -1,3 +1,4 @@
// SPDX-License-Identifier: MIT
@use "html-editor-fix"
@use "html-editor-fix";
@use "icon-browser";

@ -3,7 +3,8 @@
@use "../settings/config" as cfg;
@use "../settings/effects";
@each $level, $radius in cfg.$elevation {
@each $level,
$radius in cfg.$elevation {
.elevated-#{$level} {
@include effects.shadow(cfg.$elevation-color, $radius);
}
@ -12,3 +13,47 @@
.interactive:hover {
@include effects.shadow(cfg.$elevation-color, cfg.$elevation-interactive);
}
///////////////////////////////////////////////////////////////////////////////
// Ikonli //
///////////////////////////////////////////////////////////////////////////////
.ikonli-font-icon.accent {
-fx-fill: -color-accent-emphasis;
-fx-icon-color: -color-accent-emphasis;
}
.ikonli-font-icon.success {
-fx-fill: -color-success-emphasis;
-fx-icon-color: -color-success-emphasis;
}
.ikonli-font-icon.warning {
-fx-fill: -color-warning-emphasis;
-fx-icon-color: -color-warning-emphasis;
}
.ikonli-font-icon.danger {
-fx-fill: -color-danger-emphasis;
-fx-icon-color: -color-danger-emphasis;
}
.ikonli-font-icon:accent {
-fx-fill: -color-accent-emphasis;
-fx-icon-color: -color-accent-emphasis;
}
.ikonli-font-icon:success {
-fx-fill: -color-success-emphasis;
-fx-icon-color: -color-success-emphasis;
}
.ikonli-font-icon:warning {
-fx-fill: -color-warning-emphasis;
-fx-icon-color: -color-warning-emphasis;
}
.ikonli-font-icon:danger {
-fx-fill: -color-danger-emphasis;
-fx-icon-color: -color-danger-emphasis;
}