Skip to content

Commit

Permalink
Feature/finished/iia 1328 read only component set control (ikmdev#258)
Browse files Browse the repository at this point in the history
* Update version to start feature: IIA-1328-Read-Only-Component-Set-Control

* Feature/IIA-1328: Implement Identicons for Read-Only Component Set Control

* Update version to finish feature: IIA-1328-Read-Only-Component-Set-Control
  • Loading branch information
dukke authored Feb 12, 2025
1 parent da2ca7e commit e7565ec
Show file tree
Hide file tree
Showing 7 changed files with 348 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package dev.ikm.komet.kview.controls;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.image.Image;

public class ComponentItem {

public ComponentItem() { }

public ComponentItem(String text, Image icon) {
this.text.set(text);
this.icon.set(icon);
}

// -- text
private StringProperty text = new SimpleStringProperty();
public String getText() { return text.get(); }
public StringProperty textProperty() { return text; }
public void setText(String text) { this.text.set(text); }

// -- icon
private ObjectProperty<Image> icon = new SimpleObjectProperty<>();
public Image getIcon() { return icon.get(); }
public ObjectProperty<Image> iconProperty() { return icon; }
public void setIcon(Image icon) { this.icon.set(icon); }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package dev.ikm.komet.kview.controls;

import dev.ikm.komet.kview.controls.skin.KLReadOnlyComponentSetControlSkin;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;

import java.util.function.Consumer;

public class KLReadOnlyComponentSetControl extends Control {

public KLReadOnlyComponentSetControl() {
getStyleClass().add("read-only-component-set-control");
}

// -- title
private StringProperty title = new SimpleStringProperty();
public String getTitle() { return title.get(); }
public StringProperty titleProperty() { return title; }
public void setTitle(String title) { this.title.set(title); }

// -- prompt text
private StringProperty promptText = new SimpleStringProperty("[Placeholder]");
public String getPromptText() { return promptText.get(); }
public StringProperty promptTextProperty() { return promptText; }
public void setPromptText(String text) { this.promptText.set(text); }

// -- edit mode
private BooleanProperty editMode = new SimpleBooleanProperty();
public boolean isEditMode() { return editMode.get(); }
public BooleanProperty editModeProperty() { return editMode; }
public void setEditMode(boolean editMode) { this.editMode.set(editMode); }

// -- on edit action
private ObjectProperty<Runnable> onEditAction = new SimpleObjectProperty<>();
public Runnable getOnEditAction() { return onEditAction.get(); }
public ObjectProperty<Runnable> onEditActionProperty() { return onEditAction; }
public void setOnEditAction(Runnable onEditAction) { this.onEditAction.set(onEditAction); }

// -- on remove action
private ObjectProperty<Consumer<ComponentItem>> onRemoveAction = new SimpleObjectProperty<>();
public Consumer<ComponentItem> getOnRemoveAction() { return onRemoveAction.get(); }
public ObjectProperty<Consumer<ComponentItem>> onRemoveActionProperty() { return onRemoveAction; }
public void setOnRemoveAction(Consumer<ComponentItem> onEditAction) { this.onRemoveAction.set(onEditAction); }

// items
private ObservableSet<ComponentItem> items = FXCollections.observableSet();
public ObservableSet<ComponentItem> getItems() { return items; }

@Override
protected Skin<?> createDefaultSkin() {
return new KLReadOnlyComponentSetControlSkin(this);
}

@Override
public String getUserAgentStylesheet() {
return KLReadOnlyComponentControl.class.getResource("read-only-component-set-control.css").toExternalForm();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public KLReadOnlyComponentControlSkin(KLReadOnlyComponentControl control) {
textLabel.textProperty().bind(control.textProperty());
iconImageView.imageProperty().bind(control.iconProperty());

textLabel.setMaxWidth(Region.USE_PREF_SIZE);
textLabel.setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(textLabel, Priority.ALWAYS);

iconImageView.setFitWidth(20);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package dev.ikm.komet.kview.controls.skin;

import dev.ikm.komet.kview.controls.ComponentItem;
import dev.ikm.komet.kview.controls.KLReadOnlyComponentSetControl;
import dev.ikm.komet.kview.controls.KometIcon;
import javafx.beans.binding.StringBinding;
import javafx.collections.SetChangeListener;
import javafx.css.PseudoClass;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.SkinBase;
import javafx.scene.image.ImageView;
import javafx.scene.input.ContextMenuEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;

import java.util.HashMap;

public class KLReadOnlyComponentSetControlSkin extends SkinBase<KLReadOnlyComponentSetControl> {
private final VBox mainContainer = new VBox();

private final Label titleLabel = new Label();
private final Label promptTextLabel = new Label();

private final VBox componentsContainer = new VBox();

private final HashMap<ComponentItem, Node> componentUIItems = new HashMap<>();

/**
* @param control The control for which this Skin should attach to.
*/
public KLReadOnlyComponentSetControlSkin(KLReadOnlyComponentSetControl control) {
super(control);

mainContainer.getChildren().addAll(
titleLabel,
componentsContainer
);

getChildren().add(mainContainer);

// title
titleLabel.textProperty().bind(new StringBinding() {
{
super.bind(control.titleProperty());
}
@Override
protected String computeValue() {
String title = control.getTitle();
if (title != null) {
return control.getTitle().toUpperCase();
} else {
return "";
}
}
});

// sync items observableSet
for (ComponentItem componentItem : control.getItems()) {
addNewUIItem(componentItem);
}
control.getItems().addListener(this::itemsChanged);

// CSS
mainContainer.getStyleClass().add("main-container");
componentsContainer.getStyleClass().add("components-container");
titleLabel.getStyleClass().add("title");
promptTextLabel.getStyleClass().add("prompt-text");
}

private void itemsChanged(SetChangeListener.Change<? extends ComponentItem> change) {
if (change.wasAdded()) {
addNewUIItem(change.getElementAdded());
} else if (change.wasRemoved()) {
removeUIItem(change.getElementRemoved());
}
}

private void addNewUIItem(ComponentItem componentItem) {
Node componentUIItem = new ComponentItemNode(this, componentItem);;
componentsContainer.getChildren().add(componentUIItem);
componentUIItems.put(componentItem, componentUIItem);
}

private void removeUIItem(ComponentItem componentItem) {
Node componentUIItem = componentUIItems.get(componentItem);
componentsContainer.getChildren().remove(componentUIItem);
componentUIItems.remove(componentItem);
}

private static MenuItem createMenuItem(String text, KometIcon.IconValue icon, EventHandler<ActionEvent> actionHandler) {
MenuItem menuItem = new MenuItem(text, KometIcon.create(icon, "icon-klcontext-menu"));
menuItem.setOnAction(actionHandler);
return menuItem;
}

protected void fireOnEditAction(ActionEvent actionEvent) {
if (getSkinnable().getOnEditAction() != null) {
getSkinnable().getOnEditAction().run();
}
}

protected void fireOnRmoveAction(ActionEvent actionEvent, ComponentItem componentItem) {
if (getSkinnable().getOnRemoveAction() != null) {
getSkinnable().getOnRemoveAction().accept(componentItem);
}
}

/*******************************************************************************
* *
* Supporting Classes *
* *
******************************************************************************/

public static class ComponentItemNode extends Region {
private static final PseudoClass EDIT_MODE_PSEUDO_CLASS = PseudoClass.getPseudoClass("edit-mode");

private final HBox container = new HBox();
private final ImageView iconImageView = new ImageView();
private final Label textLabel = new Label();

private ContextMenu contextMenu = new ContextMenu();

public ComponentItemNode(KLReadOnlyComponentSetControlSkin componentSetControlSkin, ComponentItem componentItem) {
// Image View
iconImageView.imageProperty().bind(componentItem.iconProperty());
iconImageView.setFitHeight(20);
iconImageView.setFitWidth(20);

// Label (Image View Icon + Text)
textLabel.textProperty().bind(componentItem.textProperty());

container.getChildren().addAll(iconImageView, textLabel);
getChildren().add(container);

textLabel.setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(textLabel, Priority.ALWAYS);


contextMenu.getStyleClass().add("klcontext-menu");
contextMenu.getItems().addAll(
createMenuItem("Edit Set", KometIcon.IconValue.PENCIL, componentSetControlSkin::fireOnEditAction),
new SeparatorMenuItem(),
createMenuItem("Remove", KometIcon.IconValue.TRASH, actionEvent -> componentSetControlSkin.fireOnRmoveAction(actionEvent, componentItem))
);

setOnContextMenuRequested(this::onContextMenuRequested);

// CSS
getStyleClass().add("component-item");
container.getStyleClass().add("container");
}

private void onContextMenuRequested(ContextMenuEvent contextMenuEvent) {
pseudoClassStateChanged(EDIT_MODE_PSEUDO_CLASS, true);

contextMenu.setOnHidden(event -> pseudoClassStateChanged(EDIT_MODE_PSEUDO_CLASS, false));
contextMenu.show(this, contextMenuEvent.getScreenX(), contextMenuEvent.getScreenY());
}

@Override
protected void layoutChildren() {
double leftInsets = snappedLeftInset();
double rightInsets = snappedRightInset();
double topInsets = snappedTopInset();
double bottomInsets = snappedBottomInset();
double width = getWidth();
double height = getHeight();

container.resizeRelocate(leftInsets, topInsets,
width - leftInsets - rightInsets, height - topInsets - bottomInsets);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
import dev.ikm.komet.framework.Identicon;
import dev.ikm.komet.framework.observable.ObservableField;
import dev.ikm.komet.framework.view.ObservableView;
import dev.ikm.komet.kview.controls.ComponentItem;
import dev.ikm.komet.kview.controls.KLComponentSetControl;
import dev.ikm.komet.kview.controls.KLReadOnlyComponentControl;
import dev.ikm.komet.kview.controls.KLReadOnlyComponentSetControl;
import dev.ikm.komet.kview.klfields.BaseDefaultKlField;
import dev.ikm.komet.layout.component.version.field.KlComponentSetField;
import dev.ikm.tinkar.common.id.IntIdSet;
import dev.ikm.tinkar.terms.EntityProxy;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.Parent;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
Expand All @@ -24,21 +27,24 @@ public DefaultKlComponentSetField(ObservableField<IntIdSet> observableComponentS
klComponentSetControl.valueProperty().bindBidirectional(observableComponentSetField.valueProperty());
node = klComponentSetControl;
} else {
VBox vBox = new VBox();
KLReadOnlyComponentSetControl klReadOnlyComponentSetControl = new KLReadOnlyComponentSetControl();

klReadOnlyComponentSetControl.setTitle(getTitle());

observableComponentSetField.valueProperty().subscribe(intIdSet -> {
vBox.getChildren().clear();
vBox.getChildren().add(new Label(getTitle()));
klReadOnlyComponentSetControl.getItems().clear();
intIdSet.forEach(nid -> {
KLReadOnlyComponentControl readOnlyComponentControl = new KLReadOnlyComponentControl();
EntityProxy entityProxy = EntityProxy.make(nid);
readOnlyComponentControl.setText(entityProxy.description());
readOnlyComponentControl.setIcon(Identicon.generateIdenticonImage(entityProxy.publicId()));
vBox.getChildren().add(readOnlyComponentControl);
Image icon = Identicon.generateIdenticonImage(entityProxy.publicId());

ComponentItem componentItem = new ComponentItem(entityProxy.description(), icon);
klReadOnlyComponentSetControl.getItems().add(componentItem);
});
});
node = vBox;

node = klReadOnlyComponentSetControl;
}

setKlWidget(node);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,29 @@
-fx-text-fill: #5d5d5d;
}

.read-only-component-control .main-container .text-container {
-fx-spacing: 4px;
-fx-padding: 4px 0 0 0;

-fx-alignment: center-left;
}

.read-only-component-control .main-container .text-container .text {
-fx-text-fill: #111;
-fx-font-size: 1.166667em;
}

.read-only-component-control .main-container .text-container {
-fx-spacing: 8px;
-fx-border-width: 1px;
-fx-border-color: transparent;

-fx-padding: 0 0 0 1px;
}

.read-only-component-control:hover .main-container .text-container {
.read-only-component-control:hover .main-container .text-container > .text {
-fx-background-color: #f4f4f4;
-fx-border-color: #dfdfdf;
}

.read-only-component-control:edit-mode .main-container .text-container {
.read-only-component-control:edit-mode .main-container .text-container > .text {
-fx-background-color: #f4f9ff;
-fx-border-color: #cfd8e4;

Expand Down
Loading

0 comments on commit e7565ec

Please sign in to comment.