Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8326619: Stage.sizeToScene() on maximized/fullscreen Stage breaks the Window #1382

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
10 changes: 10 additions & 0 deletions modules/javafx.graphics/src/main/java/javafx/stage/Stage.java
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,16 @@ public final void setMaximized(boolean value) {
}
}

/**
* {@inheritDoc}
* <p>
* If this Stage is {@code maximized} or in {@code fullScreen}, size to scene is not allowed.
*/
@Override
boolean isSizeToSceneAllowed() {
return !isMaximized() && !isFullScreen();
}

public final boolean isMaximized() {
return maximized == null ? false : maximized.get();
}
Expand Down
14 changes: 13 additions & 1 deletion modules/javafx.graphics/src/main/java/javafx/stage/Window.java
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,12 @@ void setPeer(TKStage peer) {
/**
* Set the width and height of this Window to match the size of the content
* of this Window's Scene.
* This request might be ignored if the Window is not allowed to do so, e.g. a {@link Stage}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a <p> tag here?

Also, I recommend spelling outfor example, (and adding a missing comma).

* may be {@code maximized} or in {@code fullScreen} and therefore does not allow this request.
* If that is the case, this request is remembered and reapplied later when allowed.
*/
public void sizeToScene() {
if (getScene() != null && peer != null) {
if (isSizeToSceneAllowed() && getScene() != null && peer != null) {
SceneHelper.preferredSize(getScene());
adjustSize(false);
} else {
Expand All @@ -303,6 +306,15 @@ public void sizeToScene() {
}
}

/**
* Determines whether the {@link #sizeToScene()} request is allowed or not.
*
* @return true if allowed, false otherwise
*/
boolean isSizeToSceneAllowed() {
return true;
}

private void adjustSize(boolean selfSizePriority) {
if (getScene() == null) {
return;
Expand Down
270 changes: 270 additions & 0 deletions tests/system/src/test/java/test/javafx/stage/SizeToSceneTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package test.javafx.stage;

import javafx.application.Platform;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Screen;
import javafx.stage.Stage;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import test.util.Util;

import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;

import static org.junit.jupiter.api.Assertions.assertTrue;

class SizeToSceneTest {

private static final int ROOT_SIZE = 360;

static CountDownLatch startupLatch = new CountDownLatch(1);
static Stage mainStage;

@BeforeAll
static void initFX() throws Exception {
Platform.setImplicitExit(false);
Util.startup(startupLatch, startupLatch::countDown);
}

@AfterAll
static void teardown() {
Util.shutdown();
}

@AfterEach
void afterEach() {
Util.runAndWait(() -> mainStage.hide());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to check for mainStage != null?

}

private static void assertStageScreenBounds() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend adding Util.sleep(500); here.

Rectangle2D bounds = Screen.getPrimary().getVisualBounds();

// There might be small inconsistencies because of decoration, so we expect the bounds to be equal or bigger.
assertTrue(mainStage.getWidth() >= bounds.getWidth(), mainStage.getWidth() + " >= " + bounds.getWidth());
assertTrue(mainStage.getHeight() >= bounds.getHeight(), mainStage.getHeight() + " >= " + bounds.getHeight());
}

private static void assertStageSceneBounds() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend adding Util.sleep(500); here.

// There might be small inconsistencies because of decoration,
// so we expect the size to be between (inclusive) 360 and 410.
assertTrue(mainStage.getWidth() >= ROOT_SIZE, mainStage.getWidth() + " >= " + ROOT_SIZE);
assertTrue(mainStage.getHeight() >= ROOT_SIZE, mainStage.getHeight() + " >= " + ROOT_SIZE);

int maxThreshold = ROOT_SIZE + 50;
assertTrue(mainStage.getWidth() <= maxThreshold, mainStage.getWidth() + " <= " + maxThreshold);
assertTrue(mainStage.getHeight() <= maxThreshold, mainStage.getHeight() + " <= " + maxThreshold);
}

private void createAndShowStage(Consumer<Stage> stageConsumer) {
final CountDownLatch shownLatch = new CountDownLatch(1);

Util.runAndWait(() -> {
mainStage = new Stage();

Button root = new Button();
root.setMinSize(ROOT_SIZE, ROOT_SIZE);
mainStage.setScene(new Scene(root));

stageConsumer.accept(mainStage);

shownLatch.countDown();
});

Util.waitForLatch(shownLatch, 5, "Stage failed to setup and show");
}

@Test
void testInitialSizeOnMaximizedThenSizeToScene() {
createAndShowStage(stage -> {
stage.setMaximized(true);
stage.sizeToScene();
stage.show();
});

assertStageScreenBounds();
}

@Test
void testInitialSizeOnFullscreenThenSizeToScene() {
createAndShowStage(stage -> {
stage.setFullScreen(true);
stage.sizeToScene();
stage.show();
});

assertStageScreenBounds();
}

@Test
void testInitialSizeOnSizeToSceneThenMaximized() {
createAndShowStage(stage -> {
stage.sizeToScene();
stage.setMaximized(true);
stage.show();
});

assertStageScreenBounds();
}

@Test
void testInitialSizeOnSizeToSceneThenFullscreen() {
createAndShowStage(stage -> {
stage.sizeToScene();
stage.setFullScreen(true);
stage.show();
});

assertStageScreenBounds();
}

@Test
void testInitialSizeAfterShowSizeToSceneThenFullscreen() {
createAndShowStage(stage -> {
stage.show();

stage.sizeToScene();
stage.setFullScreen(true);
});

assertStageScreenBounds();
}

@Test
void testInitialSizeAfterShowSizeToSceneThenMaximized() {
createAndShowStage(stage -> {
stage.show();

stage.sizeToScene();
stage.setMaximized(true);
});

assertStageScreenBounds();
}

@Test
void testInitialSizeAfterShowFullscreenThenSizeToScene() {
createAndShowStage(stage -> {
stage.show();

stage.setFullScreen(true);
stage.sizeToScene();
});

assertStageScreenBounds();
}

@Test
void testInitialSizeAfterShowMaximizedThenSizeToScene() {
createAndShowStage(stage -> {
stage.show();

stage.setMaximized(true);
stage.sizeToScene();
});

assertStageScreenBounds();
}

@Test
void testInitialSizeOnSizeToScene() {
createAndShowStage(stage -> {
stage.sizeToScene();
stage.show();
});

assertStageSceneBounds();
}

@Test
void testInitialSizeFullscreenOnOffSizeToScene() {
createAndShowStage(stage -> {
stage.setWidth(100);
stage.setHeight(100);

stage.setFullScreen(true);
stage.sizeToScene();
stage.setFullScreen(false);

stage.show();
});

assertStageSceneBounds();
}

@Test
void testInitialSizeSizeToSceneFullscreenOnOff() {
createAndShowStage(stage -> {
stage.setWidth(100);
stage.setHeight(100);

stage.sizeToScene();
stage.setFullScreen(true);
stage.setFullScreen(false);

stage.show();
});

assertStageSceneBounds();
}

@Test
void testInitialSizeMaximizedOnOffSizeToScene() {
createAndShowStage(stage -> {
stage.setWidth(100);
stage.setHeight(100);

stage.setMaximized(true);
stage.sizeToScene();
stage.setMaximized(false);

stage.show();
});

assertStageSceneBounds();
}

@Test
void testInitialSizeSizeToSceneMaximizedOnOff() {
createAndShowStage(stage -> {
stage.setWidth(100);
stage.setHeight(100);

stage.sizeToScene();
stage.setMaximized(true);
stage.setMaximized(false);

stage.show();
});

assertStageSceneBounds();
}

}