Skip to content

Commit

Permalink
[781] Add support for multiline labels
Browse files Browse the repository at this point in the history
The changes are about the node label center position.
There is in fact no visible change but there are technical changes to
prepare the following commits when we will have a label on many lines:

* the label position given by the server is the horizontal center of the
node instead of the left position of the label
* consequently the LabelView is updated to display the text with
'text-anchor': 'middle' instead of the default 'text-anchor': 'start'

The default form description will now use a text area for default form
representation. It will allow multiple line edition. Shift + return in
a text area only adds a line return but does not send the mutation.

Adapt the ELK to consider the multiple line. The ELK label TextBound is
set according the line returns contained in the label text.
This will allow to have the right width of the node and to avoid the
overlap of the label text with the contained nodes.

Add a isMultiLine attribute to Label so that it can be seen as a text area
by sprotty. Adapt the editing bounds and the edited text font to have always
the same editing area whatever the zoom level.

Bug: #781
Signed-off-by: Laurent Fasani <[email protected]>
  • Loading branch information
lfasani authored and sbegaudeau committed Mar 8, 2022
1 parent 893000b commit b4ac4ea
Show file tree
Hide file tree
Showing 23 changed files with 411 additions and 85 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
- https://github.com/eclipse-sirius/sirius-components/issues/1026[#1026] [compatibility] Add support for `OperationAction`. The action are converted to regular tools available in the palette of the frontend
- https://github.com/eclipse-sirius/sirius-components/issues/937[#937] [diagram] Add the ability to export diagram as SVG images
- https://github.com/eclipse-sirius/sirius-components/issues/779[#779] [diagram] Add support for tools preconditions
- https://github.com/eclipse-sirius/sirius-components/issues/781[#781] [diagram] Add support for multiline labels


== v2022.01.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public StringBuilder exportLabel(Label label) {
labelExport.append(this.exportImageElement(style.getIconURL(), -20, -12, Optional.empty()));
}

labelExport.append(this.exportTextElement(label.getText(), style));
labelExport.append(this.exportTextElement(label.getText(), label.getType(), style));

return labelExport.append("</g>"); //$NON-NLS-1$
}
Expand Down Expand Up @@ -109,16 +109,32 @@ private StringBuilder addSizeParam(Size size) {
return sizeParam.append("height=\"" + size.getHeight() + "\" "); //$NON-NLS-1$ //$NON-NLS-2$
}

private StringBuilder exportTextElement(String text, LabelStyle labelStyle) {
private StringBuilder exportTextElement(String text, String type, LabelStyle labelStyle) {
StringBuilder textExport = new StringBuilder();

textExport.append("<text "); //$NON-NLS-1$
textExport.append("style=\""); //$NON-NLS-1$
textExport.append("fill: " + labelStyle.getColor() + "; "); //$NON-NLS-1$ //$NON-NLS-2$
textExport.append(this.exportFont(labelStyle));
if (type.contains("center")) { //$NON-NLS-1$
textExport.append("text-anchor:middle"); //$NON-NLS-1$
}
textExport.append("\">"); //$NON-NLS-1$

textExport.append(text);
String[] lines = text.split("\\n", -1); //$NON-NLS-1$
if (lines.length == 1) {
textExport.append(text);
} else {
textExport.append("<tspan x=\"0\">" + lines[0] + "</tspan>"); //$NON-NLS-1$//$NON-NLS-2$
double fontSize = labelStyle.getFontSize();
for (int i = 1; i < lines.length; i++) {
if (lines[i].isEmpty()) {
// avoid tspan to be ignored if there is only a line return
lines[i] = " "; //$NON-NLS-1$
}
textExport.append("<tspan x=\"0\" dy=\"" + fontSize + "\">" + lines[i] + "</tspan>"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
}
}

return textExport.append("</text>"); //$NON-NLS-1$
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.eclipse.sirius.components.collaborative.forms.handlers;

import java.util.Objects;
import java.util.function.Function;

import org.eclipse.sirius.components.collaborative.api.ChangeDescription;
import org.eclipse.sirius.components.collaborative.api.ChangeKind;
Expand All @@ -26,6 +27,7 @@
import org.eclipse.sirius.components.core.api.ErrorPayload;
import org.eclipse.sirius.components.core.api.IPayload;
import org.eclipse.sirius.components.forms.Form;
import org.eclipse.sirius.components.forms.Textarea;
import org.eclipse.sirius.components.forms.Textfield;
import org.eclipse.sirius.components.representations.Failure;
import org.eclipse.sirius.components.representations.IStatus;
Expand Down Expand Up @@ -78,11 +80,16 @@ public void handle(One<IPayload> payloadSink, Many<ChangeDescription> changeDesc
EditTextfieldInput input = (EditTextfieldInput) formInput;

// @formatter:off
var optionalTextfield = this.formQueryService.findWidget(form, input.getTextfieldId())
.filter(Textfield.class::isInstance)
.map(Textfield.class::cast);

IStatus status = optionalTextfield.map(Textfield::getNewValueHandler)
IStatus status = this.formQueryService.findWidget(form, input.getTextfieldId())
.map(widget -> {
Function<String, IStatus> handlerFunction = null;
if (widget instanceof Textfield) {
handlerFunction = ((Textfield) widget).getNewValueHandler();
} else if (widget instanceof Textarea) {
handlerFunction = ((Textarea) widget).getNewValueHandler();
}
return handlerFunction;
})
.map(handler -> handler.apply(input.getNewValue()))
.orElse(new Failure("")); //$NON-NLS-1$
// @formatter:on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,8 @@ private void convertNode(Node node, ElkNode parent, Map<String, ElkConnectableSh
elkNode.setDimensions(node.getSize().getWidth(), node.getSize().getHeight());
elkNode.setLocation(node.getPosition().getX(), node.getPosition().getY());
} else {
double width = Math.max(textBounds.getSize().getWidth(), node.getSize().getWidth());
double height = Math.max(textBounds.getSize().getHeight(), node.getSize().getHeight());
double width = textBounds.getSize().getWidth();
double height = textBounds.getSize().getHeight();
elkNode.setDimensions(width, height);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,17 @@ private Node getLayoutedNode(Node node, ElkConnectableShape elkConnectableShape,
Size size = Size.of(elkConnectableShape.getWidth(), elkConnectableShape.getHeight());
Position position = Position.at(elkConnectableShape.getX(), elkConnectableShape.getY());

// @formatter:off
Label label = this.getLayoutedLabel(node.getLabel(), id2ElkGraphElements, 0, 0);
double xOffSet = 0;
if (!node.isBorderNode()) {
// The label is positioned at the center of the node and the front-end will apply a "'text-anchor':
// 'middle'" property.
xOffSet = node.getLabel().getSize().getWidth() / 2;
}
Label label = this.getLayoutedLabel(node.getLabel(), id2ElkGraphElements, xOffSet, 0);

List<Node> childNodes = this.getLayoutedNodes(node.getChildNodes(), id2ElkGraphElements);
List<Node> borderNodes = this.getLayoutedNodes(node.getBorderNodes(), id2ElkGraphElements);
// @formatter:off
return Node.newNode(node)
.label(label)
.size(size)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,20 +152,21 @@ private void layoutNode(Optional<IDiagramEvent> optionalDiagramElementEvent, Nod

// update the border node once the current node bounds are updated
Bounds newBounds = Bounds.newBounds().position(node.getPosition()).size(node.getSize()).build();
this.layoutBorderNodes(optionalDiagramElementEvent, node.getBorderNodes(), initialNodeBounds, newBounds, layoutConfigurator);
List<BorderNodesOnSide> borderNodesOnSide = this.layoutBorderNodes(optionalDiagramElementEvent, node.getBorderNodes(), initialNodeBounds, newBounds, layoutConfigurator);

// recompute the label
if (node.getLabel() != null) {
node.getLabel().setPosition(this.nodeLabelPositionProvider.getPosition(node, node.getLabel()));
node.getLabel().setPosition(this.nodeLabelPositionProvider.getPosition(node, node.getLabel(), borderNodesOnSide));
}
}

/**
* Update the border nodes position according to the side length change where it is located.<br>
* The aim is to keep the positioning ratio of the border node on its side.
*/
private void layoutBorderNodes(Optional<IDiagramEvent> optionalDiagramElementEvent, List<NodeLayoutData> borderNodesLayoutData, Bounds initialNodeBounds, Bounds newNodeBounds,
private List<BorderNodesOnSide> layoutBorderNodes(Optional<IDiagramEvent> optionalDiagramElementEvent, List<NodeLayoutData> borderNodesLayoutData, Bounds initialNodeBounds, Bounds newNodeBounds,
ISiriusWebLayoutConfigurator layoutConfigurator) {
List<BorderNodesOnSide> borderNodesPerSide = new ArrayList<>();
if (!borderNodesLayoutData.isEmpty()) {
for (NodeLayoutData nodeLayoutData : borderNodesLayoutData) {
// 1- update the position of the border node if it has been explicitly moved
Expand All @@ -179,14 +180,15 @@ private void layoutBorderNodes(Optional<IDiagramEvent> optionalDiagramElementEve
}

// 2- recompute the border node
List<BorderNodesOnSide> borderNodesPerSide = this.snapBorderNodes(borderNodesLayoutData, initialNodeBounds.getSize(), layoutConfigurator);
borderNodesPerSide = this.snapBorderNodes(borderNodesLayoutData, initialNodeBounds.getSize(), layoutConfigurator);

// 3 - move the border node along the side according to the side change
this.updateBorderNodeAccordingParentResize(optionalDiagramElementEvent, initialNodeBounds, newNodeBounds, borderNodesPerSide, borderNodesLayoutData.get(0).getParent().getId());

// 4- set the label position if the border is newly created
this.updateBorderNodeLabel(optionalDiagramElementEvent, borderNodesPerSide);
}
return borderNodesPerSide;
}

private void updateBorderNodeLabel(Optional<IDiagramEvent> optionalDiagramElementEvent, List<BorderNodesOnSide> borderNodesPerSideList) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021 THALES GLOBAL SERVICES.
* Copyright (c) 2021, 2022 THALES GLOBAL SERVICES.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
Expand All @@ -13,6 +13,7 @@
package org.eclipse.sirius.components.diagrams.layout.incremental.provider;

import java.util.EnumSet;
import java.util.List;
import java.util.Objects;

import org.eclipse.elk.core.math.ElkPadding;
Expand All @@ -21,8 +22,10 @@
import org.eclipse.sirius.components.diagrams.NodeType;
import org.eclipse.sirius.components.diagrams.Position;
import org.eclipse.sirius.components.diagrams.layout.ISiriusWebLayoutConfigurator;
import org.eclipse.sirius.components.diagrams.layout.incremental.BorderNodesOnSide;
import org.eclipse.sirius.components.diagrams.layout.incremental.data.LabelLayoutData;
import org.eclipse.sirius.components.diagrams.layout.incremental.data.NodeLayoutData;
import org.eclipse.sirius.components.diagrams.layout.incremental.utils.RectangleSide;

/**
* Provides the position to apply to a Node Label.
Expand All @@ -37,7 +40,7 @@ public NodeLabelPositionProvider(ISiriusWebLayoutConfigurator layoutConfigurator
this.layoutConfigurator = Objects.requireNonNull(layoutConfigurator);
}

public Position getPosition(NodeLayoutData node, LabelLayoutData label) {
public Position getPosition(NodeLayoutData node, LabelLayoutData label, List<BorderNodesOnSide> borderNodesOnSide) {
double x = 0d;
double y = 0d;

Expand All @@ -49,15 +52,15 @@ public Position getPosition(NodeLayoutData node, LabelLayoutData label) {
}
break;
default:
x = this.getHorizontalPosition(node, label);
x = this.getHorizontalPosition(node, label, borderNodesOnSide);
y = this.getVerticalPosition(node, label);
break;
}

return Position.at(x, y);
}

private double getHorizontalPosition(NodeLayoutData node, LabelLayoutData label) {
private double getHorizontalPosition(NodeLayoutData node, LabelLayoutData label, List<BorderNodesOnSide> borderNodesOnSides) {
double x = 0d;
EnumSet<NodeLabelPlacement> nodeLabelPlacementSet = this.layoutConfigurator.configureByType(node.getNodeType()).getProperty(CoreOptions.NODE_LABELS_PLACEMENT);
ElkPadding nodeLabelsPadding = this.layoutConfigurator.configureByType(node.getNodeType()).getProperty(CoreOptions.NODE_LABELS_PADDING);
Expand All @@ -79,7 +82,21 @@ private double getHorizontalPosition(NodeLayoutData node, LabelLayoutData label)
}
break;
case H_CENTER:
x = (node.getSize().getWidth() - label.getTextBounds().getSize().getWidth()) / 2;
// The label is positioned at the center of the node and the front-end will apply a "'text-anchor':
// 'middle'" property.
int shiftToEast = 0;
int shiftToWest = 0;
for (BorderNodesOnSide borderNodesOnSide : borderNodesOnSides) {
if (RectangleSide.WEST.equals(borderNodesOnSide.getSide())) {
shiftToEast = 1;
} else if (RectangleSide.EAST.equals(borderNodesOnSide.getSide())) {
shiftToWest = 1;
}
}
double portOffset = this.layoutConfigurator.configureByType(node.getNodeType()).getProperty(CoreOptions.PORT_BORDER_OFFSET).doubleValue();
double offSetAccordingToBorderNodes = -portOffset / 2 * (shiftToEast - shiftToWest);

x = node.getSize().getWidth() / 2 + offSetAccordingToBorderNodes;
break;
case H_RIGHT:
if (outside) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021 THALES GLOBAL SERVICES.
* Copyright (c) 2021, 2022 THALES GLOBAL SERVICES.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
Expand All @@ -14,6 +14,7 @@

import static org.assertj.core.api.Assertions.assertThat;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

Expand Down Expand Up @@ -61,8 +62,8 @@ public void testNodeImageLabelBoundsPosition() {
NodeLabelPositionProvider labelBoundsProvider = new NodeLabelPositionProvider(new LayoutConfiguratorRegistry(List.of()).getDefaultLayoutConfigurator());
LabelLayoutData labelLayoutData = this.createLabelLayoutData();

Position position = labelBoundsProvider.getPosition(nodeLayoutData, labelLayoutData);
assertThat(position).extracting(Position::getX).isEqualTo(Double.valueOf(42.5390625));
Position position = labelBoundsProvider.getPosition(nodeLayoutData, labelLayoutData, new ArrayList<>());
assertThat(position).extracting(Position::getX).isEqualTo(Double.valueOf(DEFAULT_NODE_SIZE.getWidth() / 2));
assertThat(position).extracting(Position::getY).isEqualTo(Double.valueOf(-23.3984375));
}

Expand All @@ -72,8 +73,8 @@ public void testNodeRectangleLabelBoundsPosition() {
NodeLayoutData nodeLayoutData = this.createNodeLayoutData(Position.at(0, 0), DEFAULT_NODE_SIZE, createDiagramLayoutData, NodeType.NODE_RECTANGLE);
NodeLabelPositionProvider labelBoundsProvider = new NodeLabelPositionProvider(new LayoutConfiguratorRegistry(List.of()).getDefaultLayoutConfigurator());
LabelLayoutData labelLayoutData = this.createLabelLayoutData();
Position position = labelBoundsProvider.getPosition(nodeLayoutData, labelLayoutData);
assertThat(position).extracting(Position::getX).isEqualTo(Double.valueOf(42.5390625));
Position position = labelBoundsProvider.getPosition(nodeLayoutData, labelLayoutData, new ArrayList<>());
assertThat(position).extracting(Position::getX).isEqualTo(Double.valueOf(DEFAULT_NODE_SIZE.getWidth() / 2));
assertThat(position).extracting(Position::getY).isEqualTo(Double.valueOf(5));
}

Expand Down
Loading

0 comments on commit b4ac4ea

Please sign in to comment.