fix image preview and add option for automatically stop auto screenshoting when idle

This commit is contained in:
iceBear67
2026-08-07 19:00:20 +00:00
parent 014b18f1ba
commit 30d124fd8b
22 changed files with 641 additions and 32 deletions
@@ -1,5 +1,6 @@
package dev.photosync.client;
import dev.photosync.core.capture.CameraPose;
import dev.photosync.core.capture.CaptureOrigin;
import dev.photosync.core.capture.CapturedScreenshot;
import dev.photosync.core.config.AutoCaptureSettings;
@@ -9,6 +10,7 @@ import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -23,6 +25,11 @@ import java.util.function.Supplier;
*
* <p>Off by default, and it stays off until the player says otherwise: silently
* filling someone's screenshots folder is not a feature.
*
* <p>When it is on, a cycle is skipped if the camera has not moved for a whole
* interval, because that shot would be the previous one again. The condition is
* "has not moved since the last capture", expressed as a duration so that it
* survives the player wandering off and coming back to the same spot.
*/
@Slf4j
public final class AutoCapture {
@@ -36,6 +43,10 @@ public final class AutoCapture {
private long nextCaptureMillis;
private boolean capturing;
/** The last view that differed from the one before it, and when it arrived. */
private CameraPose restingPose;
private long movedAtMillis;
public AutoCapture(GameContext game, ScreenshotService screenshots,
Supplier<AutoCaptureSettings> settings, Consumer<CapturedScreenshot> sink) {
this.game = game;
@@ -51,11 +62,15 @@ public final class AutoCapture {
// Disarmed, so switching the feature on never fires immediately --
// it always waits a full interval first.
nextCaptureMillis = 0;
restingPose = null;
return;
}
long now = System.currentTimeMillis();
long interval = current.intervalSeconds() * 1000L;
// Every tick, so that stillness is measured over the whole interval
// rather than sampled at its two ends.
observePose(now);
if (nextCaptureMillis == 0) {
nextCaptureMillis = now + interval;
return;
@@ -75,11 +90,47 @@ public final class AutoCapture {
// where it is and the shot happens as soon as it closes.
return;
}
if (current.skipWhenStill() && stillFor(interval, now)) {
// The view is the one the last screenshot already has. Skip this
// cycle and arm the next: the moment the player moves, the timer
// resumes on its own, so nothing has to be re-enabled.
log.debug("Skipping an automatic capture: the view has not changed in {}s",
current.intervalSeconds());
nextCaptureMillis = now + interval;
return;
}
nextCaptureMillis = now + interval;
take(current.fileNameSuffix());
}
/**
* Notes where the camera is, and when it last went somewhere new.
*
* <p>The comparison is against the last pose that <em>differed</em> rather
* than against the previous tick, so a player drifting slowly -- in water, on
* a boat, turning by a pixel at a time -- still counts as moving once the
* movement adds up, instead of being called still forever.
*/
private void observePose(long now) {
Optional<CameraPose> pose = game.cameraPose();
if (pose.isEmpty()) {
// No world, or no player in it yet. Nothing to compare, and stillness
// is not claimed against a view that does not exist.
restingPose = null;
return;
}
if (restingPose == null || pose.get().movedFrom(restingPose)) {
restingPose = pose.get();
movedAtMillis = now;
}
}
/** True once the view has been the same for a full capture cycle. */
private boolean stillFor(long millis, long now) {
return restingPose != null && now - movedAtMillis >= millis;
}
private void take(String suffix) {
capturing = true;
screenshots.capture(suffix).whenComplete((file, failure) -> {
@@ -37,7 +37,9 @@
"photosync.browse.failed": "Could not load the timeline",
"photosync.browse.empty": "Nothing here yet",
"photosync.browse.page_failed": "Could not load these photos",
"photosync.browse.opening": "Loading...",
"photosync.browse.opening": "Loading the full photo...",
"photosync.browse.open_failed": "Could not load this photo",
"photosync.browse.open_failed.hint": "Close and open it again to retry.",
"photosync.browse.close_hint": "Esc to close",
"photosync.browse.status": "%s photos in %s days",
@@ -82,6 +84,8 @@
"photosync.settings.suffix": "Name suffix",
"photosync.settings.only_in_world": "Only while in a world",
"photosync.settings.skip_when_screen_open": "Skip while a screen is open",
"photosync.settings.skip_when_still": "Skip if you have not moved",
"photosync.settings.skip_when_still.detail": "Skips a cycle when the view has not changed since the last automatic screenshot.",
"photosync.settings.notify": "Show messages in the corner",
"photosync.settings.notify.detail": "One line, bottom left, for a moment.",