-
Notifications
You must be signed in to change notification settings - Fork 492
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
8313424: JavaFX controls in the title bar #1605
base: master
Are you sure you want to change the base?
Changes from 1 commit
3d19b87
0ddd63d
7969622
a289572
ba02e8f
f973e8c
f02e7e9
fef8cfc
778e6c1
3b468fe
0526edb
95736df
d9c0fe2
c0b588f
d7f88c3
9de4694
cd5d443
bc48ae0
804d0be
f5e3121
1c4ecc1
15dc3ff
9b63892
e7febc5
d1c388b
8c9fbbd
3660a29
8974c14
ca9b325
8e77a22
4336735
a9178b7
6a16536
65f095e
d5afc7d
26b81b8
003e9d5
485a9d9
743626f
d9b82c8
8649f91
af35dce
6523073
8d5d7b8
eaafd9f
cbb3216
63bd058
8c33b3c
49a81d3
356a78e
fcec7c8
2ab1294
8a73f5e
e874d21
3efd782
cadf1c2
600c721
7cb591f
c30eb60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
/* | ||
* Copyright (c) 2025, 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 com.sun.javafx.scene.layout; | ||
|
||
import com.sun.javafx.PlatformUtil; | ||
import javafx.beans.value.ObservableValue; | ||
import javafx.event.EventHandler; | ||
import javafx.scene.Node; | ||
import javafx.scene.Scene; | ||
import javafx.scene.input.MouseEvent; | ||
import javafx.scene.layout.HeaderButtonType; | ||
import javafx.stage.Modality; | ||
import javafx.stage.Stage; | ||
import javafx.util.Subscription; | ||
import java.util.Objects; | ||
import java.util.Optional; | ||
|
||
public final class HeaderButtonBehavior implements EventHandler<MouseEvent> { | ||
|
||
private final Node node; | ||
private final HeaderButtonType type; | ||
private final Subscription subscription; | ||
|
||
public HeaderButtonBehavior(Node node, HeaderButtonType type) { | ||
this.node = Objects.requireNonNull(node); | ||
this.type = Objects.requireNonNull(type); | ||
|
||
ObservableValue<Stage> stage = node.sceneProperty() | ||
.flatMap(Scene::windowProperty) | ||
.map(w -> w instanceof Stage s ? s : null); | ||
|
||
subscription = Subscription.combine( | ||
type == HeaderButtonType.MAXIMIZE | ||
? stage.flatMap(Stage::resizableProperty).subscribe(this::onResizableChanged) | ||
: Subscription.EMPTY, | ||
stage.flatMap(Stage::fullScreenProperty).subscribe(this::onFullScreenChanged), | ||
() -> node.removeEventHandler(MouseEvent.MOUSE_RELEASED, this) | ||
); | ||
|
||
node.addEventHandler(MouseEvent.MOUSE_RELEASED, this); | ||
node.setFocusTraversable(false); | ||
} | ||
|
||
public void dispose() { | ||
subscription.unsubscribe(); | ||
} | ||
|
||
@Override | ||
public void handle(MouseEvent event) { | ||
if (!node.getLayoutBounds().contains(event.getX(), event.getY())) { | ||
return; | ||
} | ||
|
||
switch (type) { | ||
case CLOSE -> getStage().ifPresent(Stage::close); | ||
case ICONIFY -> getStage().ifPresent(stage -> stage.setIconified(true)); | ||
case MAXIMIZE -> getStage().ifPresent(stage -> { | ||
// On macOS, a non-modal window is put into full-screen mode when the maximize button is clicked, | ||
// but enlarged to cover the desktop when the option key is pressed at the same time. | ||
if (PlatformUtil.isMac() && stage.getModality() == Modality.NONE && !event.isAltDown()) { | ||
stage.setFullScreen(!stage.isFullScreen()); | ||
} else { | ||
stage.setMaximized(!stage.isMaximized()); | ||
} | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it might be better to add a default case here, in case this enum evolves. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? This isn't some external enum that might evolve out from under this code, but is part of the same feature. If the enum evolves then the resulting compiler error is a good thing as it alerts whoever added the new enum that they didn't finish implementing it. A default makes sense when the enum might evolve separately from the use of the enum. |
||
} | ||
} | ||
|
||
private Optional<Stage> getStage() { | ||
Scene scene = node.getScene(); | ||
if (scene == null) { | ||
return Optional.empty(); | ||
} | ||
|
||
return scene.getWindow() instanceof Stage stage | ||
? Optional.of(stage) | ||
: Optional.empty(); | ||
} | ||
|
||
private void onResizableChanged(Boolean resizable) { | ||
if (!node.disableProperty().isBound()) { | ||
node.setDisable(resizable == Boolean.FALSE); | ||
} | ||
} | ||
|
||
private void onFullScreenChanged(Boolean fullScreen) { | ||
if (!node.visibleProperty().isBound() && !node.managedProperty().isBound()) { | ||
node.setVisible(fullScreen != Boolean.TRUE); | ||
node.setManaged(fullScreen != Boolean.TRUE); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it might be better to call this class other than "behavior", "handler" perhaps? mouse handler?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit more than just a generic handler, it basically encapsulates the entire behavior of a window button (including setting its visibility and disabled states).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We generally use "Behavior" in the context of controls. Although not wrong, I also think another name might be better. Maybe "Controller"? Or "Manager"? Or ...