org.fxmisc.flowless.TargetPosition Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of richtextfx Show documentation
Show all versions of richtextfx Show documentation
FX-Text-Area for formatted text and other special effects.
package org.fxmisc.flowless;
/**
* Defines where the {@link Navigator} should place the anchor cell's node in the viewport. Its three implementations
* are {@link StartOffStart}, {@link EndOffEnd}, and {@link MinDistanceTo}.
*/
interface TargetPosition {
static TargetPosition BEGINNING = new StartOffStart(0, 0.0);
/**
* When the list of items, those displayed in the viewport, and those that are not, are modified, transforms
* this change to account for those modifications.
*
* @param pos the cell index where the change begins
* @param removedSize the amount of cells that were removed, starting from {@code pos}
* @param addedSize the amount of cells that were added, starting from {@code pos}
*/
TargetPosition transformByChange(int pos, int removedSize, int addedSize);
TargetPosition scrollBy(double delta);
/**
* Visitor Pattern: prevents type-checking the implementation
*/
void accept(TargetPositionVisitor visitor);
/**
* Insures this position's item index is between 0 and {@code size}
*/
TargetPosition clamp(int size);
}
/**
* Uses the Visitor Pattern, so {@link Navigator} does not need to check the type of the {@link TargetPosition}
* before using it to determine how to fill the viewport.
*/
interface TargetPositionVisitor {
void visit(StartOffStart targetPosition);
void visit(EndOffEnd targetPosition);
void visit(MinDistanceTo targetPosition);
}
/**
* A {@link TargetPosition} that instructs its {@link TargetPositionVisitor} to use the cell at {@link #itemIndex}
* as the anchor cell, showing it at the "top" of the viewport and to offset it by {@link #offsetFromStart}.
*/
final class StartOffStart implements TargetPosition {
final int itemIndex;
final double offsetFromStart;
StartOffStart(int itemIndex, double offsetFromStart) {
this.itemIndex = itemIndex;
this.offsetFromStart = offsetFromStart;
}
@Override
public TargetPosition transformByChange(
int pos, int removedSize, int addedSize) {
if(itemIndex >= pos + removedSize) {
// change before the target item, just update item index
return new StartOffStart(itemIndex - removedSize + addedSize, offsetFromStart);
} else if(itemIndex >= pos) {
// target item deleted
if (addedSize == removedSize) {
return this;
} else {
// show the first inserted at the target offset
return new StartOffStart(pos, offsetFromStart);
}
} else {
// change after the target item, target position not affected
return this;
}
}
@Override
public TargetPosition scrollBy(double delta) {
return new StartOffStart(itemIndex, offsetFromStart - delta);
}
@Override
public void accept(TargetPositionVisitor visitor) {
visitor.visit(this);
}
@Override
public TargetPosition clamp(int size) {
return new StartOffStart(clamp(itemIndex, size), offsetFromStart);
}
static int clamp(int idx, int size) {
if(size < 0) {
throw new IllegalArgumentException("size cannot be negative: " + size);
}
if(idx <= 0) {
return 0;
} else if(idx >= size) {
return size - 1;
} else {
return idx;
}
}
}
/**
* A {@link TargetPosition} that instructs its {@link TargetPositionVisitor} to use the cell at {@link #itemIndex}
* as the anchor cell, showing it at the "bottom" of the viewport and to offset it by {@link #offsetFromEnd}.
*/
final class EndOffEnd implements TargetPosition {
final int itemIndex;
final double offsetFromEnd;
EndOffEnd(int itemIndex, double offsetFromEnd) {
this.itemIndex = itemIndex;
this.offsetFromEnd = offsetFromEnd;
}
@Override
public TargetPosition transformByChange(
int pos, int removedSize, int addedSize) {
if(itemIndex >= pos + removedSize) {
// change before the target item, just update item index
return new EndOffEnd(itemIndex - removedSize + addedSize, offsetFromEnd);
} else if(itemIndex >= pos) {
// target item deleted
if (addedSize == removedSize) {
return this;
} else {
// show the last inserted at the target offset
return new EndOffEnd(pos + addedSize - 1, offsetFromEnd);
}
} else {
// change after the target item, target position not affected
return this;
}
}
@Override
public TargetPosition scrollBy(double delta) {
return new EndOffEnd(itemIndex, offsetFromEnd - delta);
}
@Override
public void accept(TargetPositionVisitor visitor) {
visitor.visit(this);
}
@Override
public TargetPosition clamp(int size) {
return new EndOffEnd(StartOffStart.clamp(itemIndex, size), offsetFromEnd);
}
}
final class MinDistanceTo implements TargetPosition {
final int itemIndex;
final Offset minY;
final Offset maxY;
MinDistanceTo(int itemIndex, Offset minY, Offset maxY) {
this.itemIndex = itemIndex;
this.minY = minY;
this.maxY = maxY;
}
public MinDistanceTo(int itemIndex) {
this(itemIndex, Offset.fromStart(0.0), Offset.fromEnd(0.0));
}
@Override
public TargetPosition transformByChange(
int pos, int removedSize, int addedSize) {
if(itemIndex >= pos + removedSize) {
// change before the target item, just update item index
return new MinDistanceTo(itemIndex - removedSize + addedSize, minY, maxY);
} else if(itemIndex >= pos) {
// target item deleted
if (addedSize == removedSize) {
return this;
} else {
// show the first inserted
return new MinDistanceTo(pos, Offset.fromStart(0.0), Offset.fromEnd(0.0));
}
} else {
// change after the target item, target position not affected
return this;
}
}
@Override
public TargetPosition scrollBy(double delta) {
return new MinDistanceTo(itemIndex, minY.add(delta), maxY.add(delta));
}
@Override
public void accept(TargetPositionVisitor visitor) {
visitor.visit(this);
}
@Override
public TargetPosition clamp(int size) {
return new MinDistanceTo(StartOffStart.clamp(itemIndex, size), minY, maxY);
}
}
/**
* Helper class: stores an {@link #offset} value, which should either be offset from the start if {@link #fromStart}
* is true or from the end if false.
*/
class Offset {
public static Offset fromStart(double offset) {
return new Offset(offset, true);
}
public static Offset fromEnd(double offset) {
return new Offset(offset, false);
}
private final double offset;
private final boolean fromStart;
private Offset(double offset, boolean fromStart) {
this.offset = offset;
this.fromStart = fromStart;
}
public double getValue() {
return offset;
}
public boolean isFromStart() {
return fromStart;
}
public boolean isFromEnd() {
return !fromStart;
}
public Offset add(double delta) {
return new Offset(offset + delta, fromStart);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy