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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Dated delivery history and release records. Each release is a `## vX.Y.Z — YYY

## Unreleased

- Share Replay's focus reveal scroll updates and finish layout before refreshing pointers and publishing the generation.

- Stage Replay views through layout and paint validation before replacing the current snapshot.

- Restore Replay's pending step inputs together when a step fails before commit.
Expand Down
50 changes: 30 additions & 20 deletions src/replay/replay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,24 +311,17 @@ class ReplayPlayer {
}

// The installed layout commits the update. Recover focus and retarget stationary pointers before recording it.
// Revealing recovered focus can settle another layout, with only the final generation recorded.
// Revealing recovered focus can require a second layout before publishing the single generation.
bool finish_update(UiDrawList paint, std::optional<std::size_t> step, const std::string& at, bool reveal) {
committed_ = true;
auto recovered = focus_.refresh(current_.input());
if (!recovered) {
relocate(errors_, std::move(recovered.diagnostics), at);
return false;
}
focused_ = recovered.value->focused;
if (reveal && focused_) {
auto revealed = scroll_into_view(current_.input(), *focused_);
if (!revealed) {
relocate(errors_, std::move(revealed.diagnostics), at);
return false;
}
if (!revealed.value->empty()) {
for (const auto& update : *revealed.value) state_.offsets[update.container.index] = update.offset;
return settle(step, at);
if (!recover_focus(at)) return false;
if (reveal) {
const auto scrolled = scroll_to_focus(at);
if (!scrolled) return false;
if (*scrolled) {
auto revealed_paint = layout_and_paint(current_, state_.offsets, at);
if (!revealed_paint || !recover_focus(at)) return false;
paint = std::move(*revealed_paint);
}
}
auto refreshed = input_.refresh(current_.input());
Expand All @@ -349,6 +342,16 @@ class ReplayPlayer {
return observe(step, at);
}

bool recover_focus(const std::string& at) {
auto recovered = focus_.refresh(current_.input());
if (!recovered) {
relocate(errors_, std::move(recovered.diagnostics), at);
return false;
}
focused_ = recovered.value->focused;
return true;
}

// Projects the current generation with the current focus.
bool observe(std::optional<std::size_t> step, const std::string& at) {
auto projected = build_semantic_tree({current_.tree.get(), current_.styles, &current_.layout, focused_});
Expand Down Expand Up @@ -486,15 +489,22 @@ class ReplayPlayer {
// Host reveal policy: focus moved to another node scrolls it into view. Changed offsets settle a new
// generation, which observes semantics; otherwise the current generation is observed.
bool reveal(std::optional<NodeHandle> previous, std::size_t step, const std::string& at) {
if (!recording_.policy.reveal_focus || !focused_ || focused_ == previous) return observe(step, at);
if (!recording_.policy.reveal_focus || focused_ == previous) return observe(step, at);
const auto scrolled = scroll_to_focus(at);
if (!scrolled) return false;
return *scrolled ? settle(step, at) : observe(step, at);
}

// Returns whether focus required a scroll offset change, or nullopt on rejection.
std::optional<bool> scroll_to_focus(const std::string& at) {
if (!focused_) return false;
auto revealed = scroll_into_view(current_.input(), *focused_);
if (!revealed) {
relocate(errors_, std::move(revealed.diagnostics), at);
return false;
return std::nullopt;
}
if (revealed.value->empty()) return observe(step, at);
for (const auto& update : *revealed.value) state_.offsets[update.container.index] = update.offset;
return settle(step, at);
return !revealed.value->empty();
}

void record(std::size_t step, std::vector<ActionRequest>& requests) {
Expand Down