Skip to content

Commit

Permalink
implement sheet drawing in JavaFX
Browse files Browse the repository at this point in the history
  • Loading branch information
xzel23 committed Jun 27, 2024
1 parent dce77df commit b4e115f
Show file tree
Hide file tree
Showing 42 changed files with 9,073 additions and 8,712 deletions.
4 changes: 3 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ subprojects {
apply(plugin = "com.dua3.cabe")

java {
toolchain { languageVersion.set(JavaLanguageVersion.of(17)) }
toolchain { languageVersion.set(JavaLanguageVersion.of(21)) }
withJavadocJar()
withSourcesJar()
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

cabe {
Expand Down
288 changes: 264 additions & 24 deletions meja-fx/src/main/java/com/dua3/meja/ui/fx/FxRow.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,296 @@

import com.dua3.cabe.annotations.Nullable;
import com.dua3.meja.model.Row;
import com.dua3.utility.fx.FxUtil;
import com.dua3.meja.ui.CellRenderer;
import com.dua3.meja.ui.SegmentViewDelegate;
import com.dua3.utility.data.Color;
import com.dua3.utility.math.geometry.AffineTransformation2f;
import com.dua3.utility.math.geometry.Rectangle2f;
import com.dua3.utility.math.geometry.Scale2f;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.IndexedCell;
import javafx.stage.Screen;
import javafx.scene.control.Skin;
import javafx.scene.control.Skinnable;

public class FxRow extends IndexedCell<Row> {
public class FxRow extends IndexedCell<RowProxy> {
private final ObservableList<Row> rows;
private final Canvas canvas;

private final FxSheetViewDelegate delegate;
Scale2f displayScale = FxUtil.getDisplayScale(Screen.getPrimary());
private final FxSheetViewDelegate sheetViewDelegate;
private final SegmentViewDelegate segmentViewDelegate;

public FxRow(ObservableList<Row> rows, FxSheetViewDelegate delegate) {
public FxRow(ObservableList<Row> rows, SegmentViewDelegate svDelegate) {
this.rows = rows;
this.delegate = delegate;
this.segmentViewDelegate = svDelegate;
this.sheetViewDelegate = (FxSheetViewDelegate) svDelegate.getSheetViewDelegate();
this.canvas = new Canvas(1,1);

setText(null);
setGraphic(null);
}

public double getRowWidth() {
return displayScale.sx() * delegate.getSheetWidthInPoints();
public float getRowHeightInPixels() {
return sheetViewDelegate.getScale().sy() * getRowHeightInPoints();
}

public double getRowHeight() {
public float getRowHeightInPoints() {
sheetViewDelegate.updateLayout();
int idx = getIndex();
return displayScale.sy() * (idx < 0 || idx >=rows.size()
? delegate.getDefaultRowHeight()
: delegate.getRowHeightInPoints(rows.get(idx).getRowNumber()));

if (getSegmentViewDelegate().isAboveSplit()) {
if (idx <0) {
return getSheetViewDelegate().getDefaultRowHeightInPoints();
} else if (idx==0) {
return sheetViewDelegate.getColumnLabelHeightInPoints();
} else {
idx--;
if (idx<rows.size()) {
return sheetViewDelegate.getRowHeightInPoints(rows.get(idx).getRowNumber());
} else if (idx==rows.size()) {
return sheetViewDelegate.get1PxHeightInPoints();
} else {
return sheetViewDelegate.getDefaultRowHeightInPoints();
}
}
} else {
return (idx < 0 || idx >= rows.size()
? sheetViewDelegate.getDefaultRowHeightInPoints()
: sheetViewDelegate.getRowHeightInPoints(rows.get(idx).getRowNumber()));
}
}

@Override
protected double computePrefHeight(double v) {
return Math.round(getRowHeightInPixels());
}

@Override
protected double computePrefWidth(double v) {
return segmentViewDelegate.getWidthInPixels();
}

@Override
protected double computeMinHeight(double width) {
return Math.max(1, super.computeMinHeight(width));
}

@Override
public void updateIndex(int i) {
super.updateIndex(i);
Row row = i < 0 || i >= rows.size() ? null : rows.get(i);
updateItem(row, row==null);

if (i<0) {
// empty row
updateItem(RowProxy.ROW_PROXY_EMPTY, true);
} else if (segmentViewDelegate.isAboveSplit()) {
// row is above split
if (i==0) {
// row 0 is the column headers
updateItem(RowProxy.ROW_PROXY_CLOLUMN_LABELS, false);
} else {
i--; // adjust i because of inserted column header row
if (i==rows.size()) {
updateItem(RowProxy.ROW_PROXY_SPLIT_LINE, false);
} else {
Row row = i < rows.size() ? rows.get(i) : null;
updateItem(RowProxy.row(row), row == null);
}
}
} else {
// row is below split
Row row = i < rows.size() ? rows.get(i) : null;
updateItem(RowProxy.row(row), row == null);
}
}

@Override
protected void updateItem(@Nullable Row item, boolean empty) {
protected void updateItem(@Nullable RowProxy item, boolean empty) {
super.updateItem(item, empty);
setText(null);
setGraphic(null);
setWidth(getRowWidth());
setHeight(getRowHeight());
sheetViewDelegate.updateLayout();
if (item != null) {
render();
}
}

public FxSheetViewDelegate getSheetViewDelegate() {
return sheetViewDelegate;
}

public SegmentViewDelegate getSegmentViewDelegate() {
return segmentViewDelegate;
}

@Override
protected FxRowSkin createDefaultSkin() {
return new FxRowSkin(this);
protected Skin<?> createDefaultSkin() {
return new Skin<>() {
@Override
public Skinnable getSkinnable() {
return FxRow.this;
}

@Override
public Node getNode() {
return canvas;
}

public void dispose() {
}
};
}

public FxSheetViewDelegate getDelegate() {
return delegate;
private void render() {
RowProxy item = getItem();
switch (item.getType()) {
case ROW -> renderRow(item.getRow());
case EMPTY -> {}
case CLOUMN_LABELS -> renderColumnLabels();
case SPLIT_LINE -> renderSplitLine();
}
}

private void renderRow(Row row) {
float w = segmentViewDelegate.getWidthInPixels();
float h = getRowHeightInPixels();
canvas.setWidth(w);
canvas.setHeight(h);

GraphicsContext gc = canvas.getGraphicsContext2D();
gc.clearRect(0, 0, w, h);

FxSheetViewDelegate sheetViewDelegate = getSheetViewDelegate();
sheetViewDelegate.updateLayout();

Scale2f s = sheetViewDelegate.getScale();

float translateX = s.sx() * getSegmentViewDelegate().getXOffset();
float translateY = segmentViewDelegate.isAboveSplit()
? - s.sy() * sheetViewDelegate.getRowPos(getSegmentViewDelegate().getStartRow())
: 0;
gc.setTransform(s.sx(), 0, 0, s.sy(), translateX, translateY);

CellRenderer cr = new CellRenderer(sheetViewDelegate);

FxGraphics g = new FxGraphics(gc, (float) canvas.getWidth(), (float) canvas.getHeight());

// clear background
g.setFill(sheetViewDelegate.getBackground());
g.fillRect(g.getBounds());

float widthInPoints = segmentViewDelegate.getWidthInPoints();
float rowHeightInPoints = getRowHeightInPoints();

// draw grid lines
g.setStroke(sheetViewDelegate.getGridColor(), sheetViewDelegate.get1PxHeightInPoints());
float x = getSheetViewDelegate().getColumnPos(segmentViewDelegate.getStartColumn());
float y = rowHeightInPoints;
g.strokeLine(x, y, widthInPoints, y);

g.setStroke(sheetViewDelegate.getGridColor(), sheetViewDelegate.get1PxWidthInPoints());
for (int j = segmentViewDelegate.getStartColumn(); j<=segmentViewDelegate.getEndColumn(); j++) {
x = sheetViewDelegate.getColumnPos(j);
g.strokeLine(x, 0, x, h);
}

int i = row.getRowNumber();

g.setTransformation(AffineTransformation2f.translate(0, -sheetViewDelegate.getRowPos(i)));

// draw row label
Rectangle2f r = new Rectangle2f(
-sheetViewDelegate.getRowLabelWidthInPoints(),
sheetViewDelegate.getRowPos(i),
sheetViewDelegate.getRowLabelWidthInPoints(),
sheetViewDelegate.getRowPos(i + 1) - sheetViewDelegate.getRowPos(i)
);

sheetViewDelegate.drawLabel(g, r, sheetViewDelegate.getRowName(i));

// iterate over columns
for (int j = segmentViewDelegate.getStartColumn(); j < segmentViewDelegate.getEndColumn(); j++) {
// draw row label
r = new Rectangle2f(
sheetViewDelegate.getColumnPos(j),
-sheetViewDelegate.getColumnLabelHeightInPoints(),
sheetViewDelegate.getColumnPos(j + 1) - sheetViewDelegate.getColumnPos(j),
sheetViewDelegate.getColumnLabelHeightInPoints()
);
sheetViewDelegate.drawLabel(g, r, sheetViewDelegate.getColumnName(j));

// draw cell
row.getCellIfExists(j).ifPresent(cell -> cr.drawCell(g, cell.getLogicalCell()));
}

if (segmentViewDelegate.hasVLine()) {
g.setStroke(Color.BLACK, sheetViewDelegate.get1PxWidthInPoints());
x = getSheetViewDelegate().getColumnPos(segmentViewDelegate.getEndColumn()) + getSheetViewDelegate().get1PxWidthInPoints();
y = sheetViewDelegate.getRowPos(i);
g.strokeLine(x, y, x, y + getRowHeightInPoints());
}
}

private void renderColumnLabels() {
FxSheetViewDelegate sheetViewDelegate = getSheetViewDelegate();
sheetViewDelegate.updateLayout();

float w = segmentViewDelegate.getWidthInPixels();
float h = sheetViewDelegate.getColumnLabelHeightInPixels();

canvas.setWidth(w);
canvas.setHeight(h);

Scale2f s = sheetViewDelegate.getScale();

GraphicsContext gc = canvas.getGraphicsContext2D();
gc.clearRect(0, 0, w, h);

float translateX = s.sx() * getSegmentViewDelegate().getXOffset();
float translateY = 0;
gc.setTransform(s.sx(), 0, 0, s.sy(), translateX, translateY);

FxGraphics g = new FxGraphics(gc, (float) canvas.getWidth(), (float) canvas.getHeight());

// clear background
g.setFill(sheetViewDelegate.getBackground());
g.fillRect(g.getBounds());

float heightInPoints = sheetViewDelegate.getColumnLabelHeightInPoints();

// iterate over columns
for (int j = segmentViewDelegate.getStartColumn(); j < segmentViewDelegate.getEndColumn(); j++) {
// draw row label
Rectangle2f r = new Rectangle2f(
sheetViewDelegate.getColumnPos(j),
0,
sheetViewDelegate.getColumnPos(j + 1) - sheetViewDelegate.getColumnPos(j),
heightInPoints
);
sheetViewDelegate.drawLabel(g, r, sheetViewDelegate.getColumnName(j));
}

// draw split line
if (segmentViewDelegate.hasVLine()) {
g.setStroke(Color.BLACK, sheetViewDelegate.get1PxWidthInPoints());
float x = getSheetViewDelegate().getColumnPos(segmentViewDelegate.getEndColumn()) + getSheetViewDelegate().get1PxWidthInPoints();
float y = 0;
g.strokeLine(x, y, x, y + heightInPoints);
}
}

private void renderSplitLine() {
float w = segmentViewDelegate.getWidthInPixels();
float h = 1;

canvas.setWidth(w);
canvas.setHeight(h);

GraphicsContext gc = canvas.getGraphicsContext2D();
gc.clearRect(0, 0, w, h);

FxGraphics g = new FxGraphics(gc, (float) canvas.getWidth(), (float) canvas.getHeight());
g.setFill(Color.BLACK);
g.fillRect(g.getBounds());
}

}
Loading

0 comments on commit b4e115f

Please sign in to comment.