Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions src/main/java/com/hfstudio/guidenh/guide/GuidePage.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;

import net.minecraft.util.ResourceLocation;

Expand All @@ -21,6 +24,8 @@ public class GuidePage {
private final ResourceLocation id;
private final LytDocument document;
private final List<LytGuidebookScene> scenes;
private final Set<LytGuidebookScene> registeredScenes;
private long registeredScenesContentRevision;
@Nullable
private final LytHeading titleHeading;
@Nullable
Expand All @@ -42,6 +47,9 @@ public GuidePage(String sourcePack, ResourceLocation id, LytDocument document, @
this.titleHeading = titleHeading;
this.pageMeta = pageMeta;
this.scenes = collectScenes(document);
this.registeredScenes = Collections.newSetFromMap(new IdentityHashMap<LytGuidebookScene, Boolean>());
this.registeredScenes.addAll(scenes);
this.registeredScenesContentRevision = document.getContentRevision();
}

public String sourcePack() {
Expand Down Expand Up @@ -84,7 +92,7 @@ public void prepareForDisplay() {
public void releaseRuntimeScenes() {
// MaterializeTask may append GameScene nodes after compilation. Discover those nodes before
// releasing so asynchronously created preview worlds cannot outlive this page.
registerMaterializedScenes();
registerMaterializedScenes(true);
for (LytGuidebookScene scene : scenes) {
if (scene != null) {
GuidebookLevel level = scene.getLevel();
Expand All @@ -95,23 +103,40 @@ public void releaseRuntimeScenes() {
}
}

/** Adds scenes materialized into the document after the initial page compilation. */
public void registerMaterializedScenes() {
/**
* Adds scenes materialized into the document after the initial page compilation when its
* content changed since the previous scan.
*
* @return the number of newly registered scenes
*/
public int refreshMaterializedScenes() {
return registerMaterializedScenes(false);
}

private int registerMaterializedScenes(boolean force) {
if (document == null) {
return;
return 0;
}
long contentRevision = document.getContentRevision();
if (!force && contentRevision == registeredScenesContentRevision) {
return 0;
}
ArrayDeque<LytNode> pending = new ArrayDeque<>();
pending.add(document);
int added = 0;
while (!pending.isEmpty()) {
LytNode node = pending.removeLast();
if (node instanceof LytGuidebookScene scene && !scenes.contains(scene)) {
if (node instanceof LytGuidebookScene scene && registeredScenes.add(scene)) {
scenes.add(scene);
added++;
}
List<? extends LytNode> children = node.getChildren();
for (int i = children.size() - 1; i >= 0; i--) {
pending.addLast(children.get(i));
}
}
registeredScenesContentRevision = contentRevision;
return added;
}

private static List<LytGuidebookScene> collectScenes(LytDocument document) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ public void replaceChild(LytNode oldChild, LytNode newChild) {
inner.parent = null;
inner = (LytBlock) newChild;
inner.parent = this;
LytDocument doc = getDocument();
if (doc != null) doc.invalidateLayout();
invalidateLayout();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public void removeChild(LytNode node) {
if (isAttached()) LytDocument.notifyDetach(block);
children.remove(block);
block.parent = null;
invalidateLayout();
}
}

Expand All @@ -47,6 +48,7 @@ public void append(LytBlock block) {
block.parent = this;
children.add(block);
if (isAttached()) LytDocument.notifyAttach(block);
invalidateLayout();
}

@Override
Expand All @@ -63,17 +65,15 @@ public void replaceChild(LytNode oldChild, LytNode newChild) {
newBlock.parent = this;
children.set(idx, newBlock);
if (isAttached()) LytDocument.notifyAttach(newBlock);
LytDocument doc = getDocument();
if (doc != null) {
doc.invalidateLayout();
}
invalidateLayout();
}

public void clearContent() {
for (var child : children) {
child.parent = null;
}
children.clear();
invalidateLayout();
}

protected abstract LytRect computeBoxLayout(LayoutContext context, int x, int y, int availableWidth);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ public void addButton(LytButton button) {
button.setColor(toolbarText);
extraButtons.add(button);
append(button);
if (getDocument() != null) getDocument().invalidateLayout();
}

public void setPreferredWidth(int preferredWidth) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,7 @@ public boolean mouseClicked(GuideUiHost screen, int x, int y, int button, boolea
if (tabs.get(index).bounds.contains(x, y)) {
if (selectedIndex != index) {
selectedIndex = index;
if (getDocument() != null) {
getDocument().invalidateLayout();
}
invalidateLayout();
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,7 @@ public void setOpen(boolean open) {
this.open = open;
syncSummaryMarker();
syncContentVisibility();
var document = getDocument();
if (document != null) {
document.invalidateLayout();
}
invalidateLayout();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ public class LytDocument extends LytNode implements LytBlockContainer {
@Nullable
private Layout layout;

/**
* Advances whenever document content invalidates its layout. Consumers can use this to refresh
* derived indexes without rescanning an unchanged document tree every client tick.
*/
@Getter
private long contentRevision;

@Nullable
private DocumentInteractionSnapshot hoveredElement;

Expand Down Expand Up @@ -146,7 +153,9 @@ static void notifyDetach(LytNode node) {
cascadeLive(node, false);
}

@Override
public void invalidateLayout() {
contentRevision++;
layout = null;
invalidateVisibleCache();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ public void replaceChild(LytNode oldChild, LytNode newChild) {
inner.parent = null;
inner = (LytBlock) newChild;
inner.parent = this;
LytDocument doc = getDocument();
if (doc != null) doc.invalidateLayout();
invalidateLayout();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,31 @@ public abstract class LytNode implements Styleable {
@Nullable
private String styleClass;

/**
* Receives layout changes for a detached layout tree, such as rich tooltip content.
*/
@Nullable
private Runnable detachedLayoutInvalidator;

/**
* Invalidates the nearest layout owner. Attached nodes reach their document while detached
* trees can provide an explicit owner through {@link #setDetachedLayoutInvalidator(Runnable)}.
*/
public void invalidateLayout() {
if (parent != null) {
parent.invalidateLayout();
} else if (detachedLayoutInvalidator != null) {
detachedLayoutInvalidator.run();
}
}

/**
* Sets the layout owner used when this node is the root of a detached layout tree.
*/
public void setDetachedLayoutInvalidator(@Nullable Runnable detachedLayoutInvalidator) {
this.detachedLayoutInvalidator = detachedLayoutInvalidator;
}

public void removeChild(LytNode node) {}

public void replaceChild(LytNode oldChild, LytNode newChild) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class LytParagraph extends LytBlock implements LytFlowContainer, DebugFlo
public void append(LytFlowContent child) {
content.append(child);
child.setParent(this);
invalidateLayout();
}

@Override
Expand Down Expand Up @@ -163,6 +164,7 @@ public boolean isEmpty() {

public void clearContent() {
content.clear();
invalidateLayout();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ public LytPlaceholderBlock(CompletableFuture<LytBlock> future) {

private void setCurrent(LytBlock block) {
if (currentBlock != block) {
if (currentBlock != null) {
currentBlock.parent = null;
}
currentChildren.clear();
currentBlock = block;
block.parent = this;
currentChildren.add(block);
var document = getDocument();
if (document != null) {
document.invalidateLayout();
}
invalidateLayout();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import org.jetbrains.annotations.Nullable;

import com.hfstudio.guidenh.guide.document.block.LytNode;
import com.hfstudio.guidenh.guide.document.block.LytVisitor;
import com.hfstudio.guidenh.guide.style.Styleable;
import com.hfstudio.guidenh.guide.style.TextStyle;
Expand Down Expand Up @@ -119,4 +120,15 @@ public void setData(String key, Object value) {
data.put(key, value);
}

/**
* Invalidates the block layout that owns this flow content, if it has one.
*/
public void invalidateLayout() {
if (parent instanceof LytFlowContent flowContent) {
flowContent.invalidateLayout();
} else if (parent instanceof LytNode node) {
node.invalidateLayout();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,27 @@
import lombok.Setter;

@Getter
@Setter
public class LytFlowInlineBlock extends LytFlowContent implements InteractiveElement {

private static final ThreadLocal<LayoutContext> MEASURE_LAYOUT_CONTEXT = ThreadLocal
.withInitial(() -> new LayoutContext(new MinecraftFontMetrics()));

private LytBlock block;

@Setter
private InlineBlockAlignment alignment = InlineBlockAlignment.INLINE;

/**
* Replaces the rendered inline block and invalidates the owning paragraph layout.
*/
public void setBlock(@Nullable LytBlock block) {
if (this.block == block) {
return;
}
this.block = block;
invalidateLayout();
}

public LytSize getPreferredSize(int lineWidth) {
return measurePreferredBounds(lineWidth).size();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public void append(LytFlowContent child) {
}
child.setParent(this);
children.add(child);
invalidateLayout();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.hfstudio.guidenh.guide.document.LytRect;
import com.hfstudio.guidenh.guide.document.block.LytBlock;
import com.hfstudio.guidenh.guide.document.block.LytNode;
import com.hfstudio.guidenh.guide.document.block.LytVisitor;
import com.hfstudio.guidenh.guide.layout.LayoutContext;
import com.hfstudio.guidenh.guide.layout.MinecraftFontMetrics;
import com.hfstudio.guidenh.guide.scene.LytGuidebookScene;
Expand All @@ -19,16 +20,25 @@ public class ContentTooltip implements GuideTooltip {
private int lastMaxWidth = -1;
@Getter
private LytRect layoutBox = LytRect.empty();
/**
* The unnormalized bounds of all rendered tooltip content. This includes floating and inline
* blocks that can extend beyond their paragraph's text bounds.
*/
@Getter
private LytRect contentBounds = LytRect.empty();

public ContentTooltip(LytBlock content) {
this.content = content;
content.setDetachedLayoutInvalidator(this::invalidateLayout);
prepareEmbeddedScenes(content);
}

public LytRect layout(int maxWidth) {
if (maxWidth != lastMaxWidth) {
var ctx = new LayoutContext(new MinecraftFontMetrics());
layoutBox = content.layout(ctx, 0, 0, Math.max(20, maxWidth));
LytRect rootBounds = content.layout(ctx, 0, 0, Math.max(20, maxWidth));
contentBounds = collectContentBounds(rootBounds);
layoutBox = new LytRect(0, 0, contentBounds.width(), contentBounds.height());
lastMaxWidth = maxWidth;
}
return layoutBox;
Expand All @@ -40,6 +50,22 @@ public LytRect layout(int maxWidth) {
public void invalidateLayout() {
lastMaxWidth = -1;
layoutBox = LytRect.empty();
contentBounds = LytRect.empty();
}

private LytRect collectContentBounds(LytRect rootBounds) {
LytRect[] visualBounds = { rootBounds };
content.visit(new LytVisitor() {

@Override
public Result beforeNode(LytNode node) {
if (node instanceof LytBlock block && block.getBounds() != null) {
visualBounds[0] = LytRect.union(visualBounds[0], block.getBounds());
}
return Result.CONTINUE;
}
});
return visualBounds[0];
}

@Override
Expand Down
Loading