-
Notifications
You must be signed in to change notification settings - Fork 583
8389653: Enhance JavaFX with virtual layout containers, and allow ordinary Nodes to participate #2241
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
base: master
Are you sure you want to change the base?
8389653: Enhance JavaFX with virtual layout containers, and allow ordinary Nodes to participate #2241
Changes from 3 commits
ef2df2a
772ebcd
4885655
5735b96
2c57586
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,175 @@ | ||
| /* | ||
| * Copyright (c) 2026, 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 java.util.Map; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
|
|
||
| import javafx.scene.layout.RenderScaleContext; | ||
|
|
||
| /** | ||
| * Turns a {@link RenderScaleContext}'s raw scale factors into the actual snapping | ||
| * operations used by layout math. | ||
| */ | ||
| public interface Snapper { | ||
|
|
||
| /** | ||
| * A default snapper for 1.0 scaling. | ||
| */ | ||
| static final Snapper DEFAULT = new Snapper() { | ||
|
Contributor
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. DEFAULT might be too generic. IDENTITY_SNAPPED or something like that?
Collaborator
Author
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. Yeah, maybe just |
||
|
|
||
| @Override | ||
| public double snapPositionX(double value) { | ||
| return Math.round(value); | ||
| } | ||
|
|
||
| @Override | ||
| public double snapPositionY(double value) { | ||
| return Math.round(value); | ||
| } | ||
|
|
||
| @Override | ||
| public double snapSpaceX(double value) { | ||
| return Math.round(value); | ||
| } | ||
|
|
||
| @Override | ||
| public double snapSpaceY(double value) { | ||
| return Math.round(value); | ||
| } | ||
|
|
||
| @Override | ||
| public double snapSizeX(double value) { | ||
| return Math.ceil(value - Math.ulp(value)); | ||
| } | ||
|
|
||
| @Override | ||
| public double snapSizeY(double value) { | ||
| return Math.ceil(value - Math.ulp(value)); | ||
| } | ||
| }; | ||
|
|
||
| /** | ||
| * A default snapper that does no snapping at all. | ||
| */ | ||
| static final Snapper NO_SNAPPING = new Snapper() { | ||
| @Override | ||
| public double snapPositionX(double value) { | ||
| return value; | ||
| } | ||
|
|
||
| @Override | ||
| public double snapPositionY(double value) { | ||
| return value; | ||
| } | ||
|
|
||
| @Override | ||
| public double snapSpaceX(double value) { | ||
| return value; | ||
| } | ||
|
|
||
| @Override | ||
| public double snapSpaceY(double value) { | ||
| return value; | ||
| } | ||
|
|
||
| @Override | ||
| public double snapSizeX(double value) { | ||
| return value; | ||
| } | ||
|
|
||
| @Override | ||
| public double snapSizeY(double value) { | ||
| return value; | ||
| } | ||
| }; | ||
|
|
||
| class Cache { | ||
| private static final Map<RenderScaleContext, Snapper> INSTANCES = new ConcurrentHashMap<>(); | ||
|
|
||
| { | ||
| INSTANCES.put(RenderScaleContext.DEFAULT, DEFAULT); | ||
| } | ||
| } | ||
|
|
||
| static Snapper createSnapper(RenderScaleContext context) { | ||
| return Cache.INSTANCES.computeIfAbsent(context, _ -> new Snapper() { | ||
| final double ssx = context.snapScaleX(); | ||
| final double ssy = context.snapScaleY(); | ||
| final double rssx = 1.0 / ssx; | ||
| final double rssy = 1.0 / ssy; | ||
|
|
||
| @Override | ||
| public double snapPositionX(double value) { | ||
| return Math.round(value * ssx) * rssx; | ||
| } | ||
|
|
||
| @Override | ||
| public double snapPositionY(double value) { | ||
| return Math.round(value * ssy) * rssy; | ||
| } | ||
|
|
||
| @Override | ||
| public double snapSpaceX(double value) { | ||
| return Math.round(value * ssx) * rssx; | ||
| } | ||
|
|
||
| @Override | ||
| public double snapSpaceY(double value) { | ||
| return Math.round(value * ssy) * rssy; | ||
| } | ||
|
|
||
| @Override | ||
| public double snapSizeX(double value) { | ||
| double d = value * ssx; | ||
|
|
||
| if (Double.isInfinite(d)) { // Avoids returning NaN for high magnitude inputs | ||
| return value; | ||
| } | ||
|
|
||
| return Math.ceil(d - Math.ulp(d)) * rssx; | ||
| } | ||
|
|
||
| @Override | ||
| public double snapSizeY(double value) { | ||
| double d = value * ssy; | ||
|
|
||
| if (Double.isInfinite(d)) { // Avoids returning NaN for high magnitude inputs | ||
| return value; | ||
| } | ||
|
|
||
| return Math.ceil(d - Math.ulp(d)) * rssy; | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| double snapPositionX(double value); | ||
| double snapPositionY(double value); | ||
| double snapSpaceX(double value); | ||
| double snapSpaceY(double value); | ||
|
Comment on lines
+169
to
+172
Member
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. could we perhaps have just
Collaborator
Author
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. It's possible, but they do have distinct purposes:
So the type of snap function you use also tells you something about what you're snapping (and if you're passing a width of a piece of text to position or space, then that's a clear bug). |
||
| double snapSizeX(double value); | ||
| double snapSizeY(double value); | ||
| } | ||
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.
Really like the idea of the
Snapper.What I would really like to see documented is what values developers should snap.
Maybe we could add all the information we gathered over the years here.
So the conclusion of the mailing list entries, #1948, #1111 (maybe even revive this one after) and there are probably more.
Especially: Snap only final values once (before they are returned or used as x/y/w/h (If I understood that right).
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.
Yeah, we can add documentation here, just like how
Measurableexplains the bias system a bit.That's probably best indeed; it depends on what's using those values again whether or not the snapping proved important or not (often the value gets resnapped again, depending on the container, but you shouldn't rely on that).
I also discovered a slight bug in how
ceilworks. We shouldn't subtract 1 ulp from the values, as 1 ulp (at Double.MAX_VALUE) can be a huge number. I was wrong when I implemented that (although it works for most "normal" values).Instead I propose that we subtract 1 millionth of a pixel. At
Double.MAX_VALUEthat rounds to 0, while at more reasonable values it will remove any slight floating point errors that could cause a small 1 pixel misalignment.