diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 020e8be7b10..84a6b1e92a4 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -35,6 +35,9 @@ on:
- 'scripts/check-native-warnings.py'
- 'scripts/check-native-warnings.sh'
- 'scripts/native-warnings/**'
+ # Same reason: the C++ linkage gate runs from this workflow and nowhere else, so
+ # ignoring scripts/** would let a change that weakens it merge unexercised.
+ - 'scripts/check-native-cpp-linkage.py'
# The build hint gates are run from this workflow and nowhere else, and one
# of them holds an empty baseline. Ignoring the whole directory meant a
# change that breaks a gate, or that adds a line to the baseline, could
@@ -94,6 +97,9 @@ on:
- 'scripts/check-native-warnings.py'
- 'scripts/check-native-warnings.sh'
- 'scripts/native-warnings/**'
+ # Same reason: the C++ linkage gate runs from this workflow and nowhere else, so
+ # ignoring scripts/** would let a change that weakens it merge unexercised.
+ - 'scripts/check-native-cpp-linkage.py'
# The build hint gates are run from this workflow and nowhere else, and one
# of them holds an empty baseline. Ignoring the whole directory meant a
# change that breaks a gate, or that adds a line to the baseline, could
@@ -566,6 +572,15 @@ jobs:
mvn -B -q -f maven/pom.xml -pl windows,linux -am -DskipTests \
-Dcn1.binaries="${CN1_BINARIES}" compile
scripts/check-native-signatures.sh --require-all
+ # Linkage, which the signature gate above cannot see: it verifies that a
+ # native's NAME matches its Java method, and a mangled C++ symbol has the
+ # right name too. Needs no compiler and no build output, so it sits on its
+ # own step rather than behind the compile above.
+ - name: Check native C++ linkage
+ if: ${{ matrix.java-version == 8 }}
+ run: |
+ python3 scripts/check-native-cpp-linkage.py --self-test
+ python3 scripts/check-native-cpp-linkage.py
# The native warning gate itself runs on the macOS/Windows/Linux legs that
# actually compile C; what runs HERE is its parser, against a hand-authored
# fixture, plus a structural check on the checked-in baselines. Both need no
diff --git a/.github/workflows/scripts-android.yml b/.github/workflows/scripts-android.yml
index f88e828f8ea..479d67ef522 100644
--- a/.github/workflows/scripts-android.yml
+++ b/.github/workflows/scripts-android.yml
@@ -317,7 +317,14 @@ jobs:
uses: actions/upload-artifact@v7
with:
name: android-instrumentation-logs
- path: artifacts/connectedAndroidTest*.log
+ # The comparison also writes a PNG for every screenshot that did not match a
+ # stored golden, including one that has no golden at all. Uploading only the log
+ # left those on the runner, so there was no way to seed an Android golden from
+ # CI -- a new suite test could be added, captured and reported as missing here
+ # forever, and the only route to a golden was an emulator on somebody's desk.
+ path: |
+ artifacts/connectedAndroidTest*.log
+ artifacts/*.png
if-no-files-found: warn
retention-days: 14
compression-level: 6
@@ -326,7 +333,10 @@ jobs:
uses: actions/upload-artifact@v7
with:
name: android-instrumentation-logs-${{ matrix.id }}
- path: artifacts/connectedAndroidTest*.log
+ # Same reason as the default leg above.
+ path: |
+ artifacts/connectedAndroidTest*.log
+ artifacts/*.png
if-no-files-found: warn
retention-days: 14
compression-level: 6
diff --git a/CLAUDE.md b/CLAUDE.md
index 0d1fb40312f..87fd1b4b181 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -482,6 +482,33 @@ calls -- is a warning: it is dead code, not a broken build. Note the offline gat
reads `target/classes`, so **a stale port build reports natives that no longer
exist**; rebuild the module before believing a finding.
+#### The right name is not enough in C++
+
+A native defined in a `.cpp` or `.mm` file gets its name **mangled** unless it is
+declared `extern "C"`. The file compiles, the symbol it exports is not the one the
+generated code calls, and the link fails on the device in a file nobody touched --
+naming a symbol that is visibly right there in the source.
+
+`NativeSignatureVerifier` cannot see it. It checks that a native's *name* matches
+its Java method, and a mangled function has the right name too; linkage is not part
+of a name. #5845 shipped exactly this in `cn1_windows_window.cpp` and only a real
+Windows build caught it.
+
+`scripts/check-native-cpp-linkage.py` closes that, over every tracked `.cpp`, `.cc`,
+`.cxx` and `.mm`. It needs no compiler and no build output. Like the control-character
+gate it has **no baseline and no exclusions**: a native without C linkage is never
+intentional, and the fix is always one line. The script is re-included in `pr.yml`'s
+`paths` (both triggers) so a change that weakens the gate cannot merge unexercised.
+
+```bash
+scripts/check-native-cpp-linkage.py # every tracked C++ translation unit
+scripts/check-native-cpp-linkage.py PATH ... # just these
+```
+
+Note what it deliberately does not report: a port-internal C++ helper, whose mangling
+is correct, and a prototype, which needs no linkage of its own -- only a *definition*
+whose symbol the generated code will call.
+
#### A framework an app gets by accident is a framework it can lose
The iOS port referenced the `UTType` class while `ByteCodeTranslator` linked
diff --git a/CodenameOne/src/com/codename1/components/FloatingActionButton.java b/CodenameOne/src/com/codename1/components/FloatingActionButton.java
index c4c6a432cb5..8b3cf71e1d0 100644
--- a/CodenameOne/src/com/codename1/components/FloatingActionButton.java
+++ b/CodenameOne/src/com/codename1/components/FloatingActionButton.java
@@ -461,6 +461,11 @@ public void released(int x, int y) {
}
final Container con = createPopupContent(subMenu);
Dialog d = new Dialog();
+ // Framework chrome, never an operating system window: this popup is POSITIONED by the
+ // framework, and native window mode documents those margins as ignored, so in a window
+ // it comes out centred and loses the placement that is its whole point. See
+ // TooltipManager for the full note.
+ d.setNativeWindowMode(false);
d.setDialogUIID("Container");
d.getContentPane().setUIID("Container");
d.setLayout(new BorderLayout());
diff --git a/CodenameOne/src/com/codename1/components/GroupBox.java b/CodenameOne/src/com/codename1/components/GroupBox.java
new file mode 100644
index 00000000000..d69016285b4
--- /dev/null
+++ b/CodenameOne/src/com/codename1/components/GroupBox.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
+package com.codename1.components;
+
+import com.codename1.ui.Container;
+import com.codename1.ui.Label;
+import com.codename1.ui.layouts.BorderLayout;
+import com.codename1.ui.layouts.Layout;
+
+/// A titled frame around a group of related controls.
+///
+/// The desktop equivalent of a section header, and the one piece of grouping chrome all
+/// three desktop toolkits agree on: `NSBox` with a title, `GtkFrame` with a label widget,
+/// and WinUI's headered content. On a phone the same grouping is expressed by a gap and a
+/// heading, which is why Codename One never had this -- and why a desktop form built out of
+/// plain containers reads as one undifferentiated column of controls.
+///
+/// Two UIIDs: `GroupBox` styles the frame (its border is the box) and `GroupBoxTitle`
+/// styles the caption. A theme that wants the caption to sit *in* the top edge rather than
+/// above it does that with a negative top margin on `GroupBoxTitle`; nothing here hard-codes
+/// a position, because the three platforms disagree about it.
+///
+/// ```java
+/// GroupBox appearance = new GroupBox("Appearance");
+/// appearance.add(new CheckBox("Use the system accent colour"))
+/// .add(new CheckBox("Reduce transparency"));
+/// ```
+///
+/// The content is an ordinary `Container`, so `add`, `remove` and the layout all behave as
+/// they would anywhere else -- `#getContentPane()` is there for the rare caller that wants
+/// the inner container itself.
+public class GroupBox extends Container {
+ private final Label title = new Label("", "GroupBoxTitle");
+ private final Container content;
+
+ /// An untitled group whose content stacks vertically.
+ public GroupBox() {
+ this("", com.codename1.ui.layouts.BoxLayout.y());
+ }
+
+ /// A titled group whose content stacks vertically.
+ ///
+ /// #### Parameters
+ ///
+ /// - `titleText`: the caption
+ public GroupBox(String titleText) {
+ this(titleText, com.codename1.ui.layouts.BoxLayout.y());
+ }
+
+ /// A titled group with a layout of its own.
+ ///
+ /// #### Parameters
+ ///
+ /// - `titleText`: the caption
+ ///
+ /// - `contentLayout`: the layout for the grouped controls
+ public GroupBox(String titleText, Layout contentLayout) {
+ super(new BorderLayout());
+ setUIID("GroupBox");
+ content = new Container(contentLayout);
+ content.setUIID("Container");
+ title.setText(titleText);
+ // An empty caption must not reserve a strip: an untitled group is a plain box, and a
+ // blank label with the title style's padding would leave a gap nothing explains.
+ title.setHidden(titleText == null || titleText.length() == 0);
+ // super, explicitly: the overrides below route an ordinary add into the content pane,
+ // which is what every caller means -- and would put the caption and the content pane
+ // itself inside the content pane if these two went through them.
+ super.addComponent(BorderLayout.NORTH, title);
+ super.addComponent(BorderLayout.CENTER, content);
+ }
+
+ /// The caption.
+ ///
+ /// #### Returns
+ ///
+ /// the title text, never null
+ public String getTitle() {
+ return title.getText();
+ }
+
+ /// Sets the caption. Setting it empty removes the caption strip entirely.
+ ///
+ /// #### Parameters
+ ///
+ /// - `titleText`: the caption
+ public void setTitle(String titleText) {
+ title.setText(titleText == null ? "" : titleText);
+ title.setHidden(title.getText().length() == 0);
+ }
+
+ /// The caption component, for a caller that needs to style or replace its icon.
+ ///
+ /// #### Returns
+ ///
+ /// the title label
+ public Label getTitleComponent() {
+ return title;
+ }
+
+ /// The container the grouped controls live in.
+ ///
+ /// #### Returns
+ ///
+ /// the content container
+ public Container getContentPane() {
+ return content;
+ }
+
+ /// @inheritDoc
+ ///
+ /// Routed into the content pane, so an ordinary add means "add to the group" rather than
+ /// "add beside the caption". `Container#add(Component)` is final and delegates here, so
+ /// overriding this covers the chaining form too.
+ @Override
+ public void addComponent(com.codename1.ui.Component cmp) {
+ if (content == null) {
+ // During the super constructor, before the content pane exists.
+ super.addComponent(cmp);
+ return;
+ }
+ content.addComponent(cmp);
+ }
+
+ /// @inheritDoc
+ ///
+ /// The constraint belongs to the CONTENT layout, not to the BorderLayout that positions
+ /// the caption. That layout is an implementation detail, and a caller passing
+ /// `BorderLayout.SOUTH` means "below the other controls in this group", never "outside the
+ /// box, under the frame".
+ @Override
+ public void addComponent(Object constraints, com.codename1.ui.Component cmp) {
+ if (content == null) {
+ super.addComponent(constraints, cmp);
+ return;
+ }
+ content.addComponent(constraints, cmp);
+ }
+
+ /// @inheritDoc
+ @Override
+ public void removeComponent(com.codename1.ui.Component cmp) {
+ if (cmp == title || cmp == content) { //NOPMD CompareObjectsWithEquals
+ super.removeComponent(cmp);
+ return;
+ }
+ content.removeComponent(cmp);
+ }
+
+ /// @inheritDoc
+ @Override
+ public void removeAll() {
+ content.removeAll();
+ }
+}
diff --git a/CodenameOne/src/com/codename1/components/Separator.java b/CodenameOne/src/com/codename1/components/Separator.java
new file mode 100644
index 00000000000..fb3549ca97a
--- /dev/null
+++ b/CodenameOne/src/com/codename1/components/Separator.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
+package com.codename1.components;
+
+import com.codename1.ui.Component;
+import com.codename1.ui.Display;
+import com.codename1.ui.Graphics;
+import com.codename1.ui.geom.Dimension;
+import com.codename1.ui.plaf.Style;
+
+/// A rule that divides one group of controls from the next.
+///
+/// Every desktop toolkit draws one -- `NSBox` in separator mode, WinUI's
+/// `MenuFlyoutSeparator` and navigation separators, `GtkSeparator` -- and Codename One had
+/// no component for it, so applications drew their own out of a `Label` with a bottom
+/// border, or a `Container` with a fixed height and a background colour. Those look
+/// approximately right on one platform and wrong everywhere else, because the thing that
+/// differs between platforms is exactly the part they hard-code: the thickness, the
+/// colour, and how much air sits either side of it.
+///
+/// All three come from the `Separator` UIID. The rule itself is the style's **border**
+/// where there is one and its background colour otherwise, the air either side is the
+/// style's margin, and the thickness is the `separatorThicknessMM` theme constant. A
+/// theme that says nothing gets a one-pixel line in the foreground colour.
+///
+/// ```java
+/// Container settings = new Container(BoxLayout.y());
+/// settings.add(new Label("Appearance"))
+/// .add(new Separator())
+/// .add(new Label("Privacy"));
+/// ```
+///
+/// A separator is not focusable and is skipped by keyboard traversal: it is decoration,
+/// and stopping on it with Tab would be a bug on every platform.
+public class Separator extends Component {
+
+ /// A rule that runs left to right, dividing the rows of a vertical stack.
+ public static final int HORIZONTAL = 0;
+
+ /// A rule that runs top to bottom, dividing the columns of a horizontal row.
+ public static final int VERTICAL = 1;
+
+ private int orientation = HORIZONTAL;
+
+ /// A horizontal separator.
+ public Separator() {
+ this(HORIZONTAL);
+ }
+
+ /// A separator running in the given direction.
+ ///
+ /// #### Parameters
+ ///
+ /// - `orientation`: `#HORIZONTAL` or `#VERTICAL`
+ public Separator(int orientation) {
+ this.orientation = orientation;
+ setUIID("Separator");
+ setFocusable(false);
+ }
+
+ /// The direction this rule runs in.
+ ///
+ /// #### Returns
+ ///
+ /// `#HORIZONTAL` or `#VERTICAL`
+ public int getOrientation() {
+ return orientation;
+ }
+
+ /// Sets the direction this rule runs in.
+ ///
+ /// #### Parameters
+ ///
+ /// - `orientation`: `#HORIZONTAL` or `#VERTICAL`
+ public void setOrientation(int orientation) {
+ if (this.orientation != orientation) {
+ this.orientation = orientation;
+ setShouldCalcPreferredSize(true);
+ }
+ }
+
+ /// The rule's thickness in pixels, from the `separatorThicknessMM` theme constant.
+ ///
+ /// Never zero. A theme is free to ask for a hairline, and a hairline on a high density
+ /// screen rounds to zero millimetres worth of pixels -- which would make the separator
+ /// invisible rather than thin, and invisible is the one thing it must not be.
+ ///
+ /// #### Returns
+ ///
+ /// the thickness in pixels, at least 1
+ protected int getThickness() {
+ // A string, like every other *MM constant here (see Slider's track and thumb): the
+ // theme format has no float accessor, and parsing a malformed one has to fall back
+ // rather than throw out of a paint.
+ String mm = getUIManager().getThemeConstant("separatorThicknessMM", null);
+ if (mm == null || mm.trim().length() == 0) {
+ return 1;
+ }
+ try {
+ return Math.max(1, Display.getInstance().convertToPixels(Float.parseFloat(mm.trim())));
+ } catch (NumberFormatException notANumber) {
+ return 1;
+ }
+ }
+
+ @Override
+ protected Dimension calcPreferredSize() {
+ Style s = getStyle();
+ int thickness = getThickness();
+ if (orientation == VERTICAL) {
+ return new Dimension(thickness + s.getHorizontalPadding(), s.getVerticalPadding());
+ }
+ return new Dimension(s.getHorizontalPadding(), thickness + s.getVerticalPadding());
+ }
+
+ @Override
+ public void paint(Graphics g) {
+ Style s = getStyle();
+ if (s.getBorder() != null) {
+ // A themed border already draws the rule, including the per-side colours a
+ // platform that wants a two-tone bevel needs. Painting over it would double it.
+ return;
+ }
+ int thickness = getThickness();
+ g.setColor(s.getFgColor());
+ int alpha = g.concatenateAlpha(s.getFgAlpha());
+ if (orientation == VERTICAL) {
+ g.fillRect(getX() + s.getPaddingLeft(isRTL()) + (getWidth() - s.getHorizontalPadding()
+ - thickness) / 2,
+ getY() + s.getPaddingTop(),
+ thickness,
+ getHeight() - s.getVerticalPadding());
+ } else {
+ g.fillRect(getX() + s.getPaddingLeft(isRTL()),
+ getY() + s.getPaddingTop() + (getHeight() - s.getVerticalPadding()
+ - thickness) / 2,
+ getWidth() - s.getHorizontalPadding(),
+ thickness);
+ }
+ g.setAlpha(alpha);
+ }
+}
diff --git a/CodenameOne/src/com/codename1/components/Stepper.java b/CodenameOne/src/com/codename1/components/Stepper.java
new file mode 100644
index 00000000000..5b583885fdd
--- /dev/null
+++ b/CodenameOne/src/com/codename1/components/Stepper.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
+package com.codename1.components;
+
+import com.codename1.ui.Button;
+import com.codename1.ui.Container;
+import com.codename1.ui.TextField;
+import com.codename1.ui.events.ActionEvent;
+import com.codename1.ui.events.ActionListener;
+import com.codename1.ui.layouts.BorderLayout;
+import com.codename1.ui.layouts.BoxLayout;
+import com.codename1.ui.util.EventDispatcher;
+
+/// A numeric field with increment and decrement controls: `NSStepper`, WinUI's `NumberBox`
+/// with its spin buttons, `GtkSpinButton`.
+///
+/// Codename One had no equivalent. The nearest thing is `Slider`, which is a different
+/// control for a different job -- a slider is for a value whose exact number does not
+/// matter, and a stepper is for one where it does. An application that needed a bounded
+/// number on the desktop had to build the composite by hand, and every hand-built one got
+/// the same two things wrong: it let the field hold text that is not a number, and it let
+/// the buttons walk past the bounds.
+///
+/// ```java
+/// Stepper copies = new Stepper(1, 1, 99);
+/// copies.addActionListener(e -> print(copies.getValue()));
+/// ```
+///
+/// Three UIIDs: `Stepper` for the composite, `StepperField` for the text field and
+/// `StepperButton` for the two buttons. The buttons carry `-` and `+` by default; a theme
+/// or an application that wants arrows sets icons on `#getDecrementButton()` and
+/// `#getIncrementButton()`.
+///
+/// The value is an int. A stepper over a fractional quantity is a real control on some
+/// platforms, and it is deliberately not this one: doing it properly means a format, a
+/// locale and a parse policy, and guessing those is worse than not offering them.
+public class Stepper extends Container {
+ private final TextField field = new TextField();
+ private final Button decrement = new Button("-", "StepperButton");
+ private final Button increment = new Button("+", "StepperButton");
+ private final EventDispatcher listeners = new EventDispatcher();
+
+ private int value;
+ private int minValue;
+ private int maxValue;
+ private int step = 1;
+
+ /// A stepper over 0..100 starting at 0.
+ public Stepper() {
+ this(0, 0, 100);
+ }
+
+ /// A stepper over the given range.
+ ///
+ /// #### Parameters
+ ///
+ /// - `value`: the initial value, clamped into the range
+ ///
+ /// - `minValue`: the lowest value the control will produce
+ ///
+ /// - `maxValue`: the highest value the control will produce
+ public Stepper(int value, int minValue, int maxValue) {
+ super(new BorderLayout());
+ setUIID("Stepper");
+ if (maxValue < minValue) {
+ throw new IllegalArgumentException("maxValue " + maxValue
+ + " is below minValue " + minValue);
+ }
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ this.value = clamp(value);
+
+ field.setUIID("StepperField");
+ field.setConstraint(TextField.NUMERIC);
+ field.setText(String.valueOf(this.value));
+ field.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ commitTypedText();
+ }
+ });
+ field.addDataChangedListener(new com.codename1.ui.events.DataChangedListener() {
+ @Override
+ public void dataChanged(int type, int index) {
+ commitTypedText();
+ }
+ });
+
+ decrement.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ setValue(Stepper.this.value - step);
+ }
+ });
+ increment.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ setValue(Stepper.this.value + step);
+ }
+ });
+
+ Container buttons = new Container(BoxLayout.x());
+ buttons.setUIID("Container");
+ buttons.add(decrement).add(increment);
+ add(BorderLayout.CENTER, field);
+ add(BorderLayout.EAST, buttons);
+ updateButtonState();
+ }
+
+ /// The current value.
+ ///
+ /// #### Returns
+ ///
+ /// the value, always within `#getMinValue()`..`#getMaxValue()`
+ public int getValue() {
+ return value;
+ }
+
+ /// Sets the value, clamped into the range. Fires the action listeners only when the
+ /// value actually moved, so a caller that sets the same value twice does not report a
+ /// change that did not happen.
+ ///
+ /// #### Parameters
+ ///
+ /// - `newValue`: the requested value
+ public void setValue(int newValue) {
+ int clamped = clamp(newValue);
+ if (clamped == value) {
+ // Still refresh the text: the user may have typed something out of range, in
+ // which case the field and the value disagree and the field is the wrong one.
+ syncField();
+ updateButtonState();
+ return;
+ }
+ value = clamped;
+ syncField();
+ updateButtonState();
+ listeners.fireActionEvent(new ActionEvent(this));
+ }
+
+ /// The lowest value this control will produce.
+ ///
+ /// #### Returns
+ ///
+ /// the minimum
+ public int getMinValue() {
+ return minValue;
+ }
+
+ /// The highest value this control will produce.
+ ///
+ /// #### Returns
+ ///
+ /// the maximum
+ public int getMaxValue() {
+ return maxValue;
+ }
+
+ /// Sets the range. The current value is clamped into the new range, which fires the
+ /// listeners when that moves it.
+ ///
+ /// #### Parameters
+ ///
+ /// - `minValue`: the lowest value
+ ///
+ /// - `maxValue`: the highest value
+ public void setRange(int minValue, int maxValue) {
+ if (maxValue < minValue) {
+ throw new IllegalArgumentException("maxValue " + maxValue
+ + " is below minValue " + minValue);
+ }
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ setValue(value);
+ }
+
+ /// How far one press of a button moves the value.
+ ///
+ /// #### Returns
+ ///
+ /// the step, at least 1
+ public int getStep() {
+ return step;
+ }
+
+ /// Sets how far one press of a button moves the value.
+ ///
+ /// #### Parameters
+ ///
+ /// - `step`: the step, which must be positive
+ public void setStep(int step) {
+ if (step < 1) {
+ throw new IllegalArgumentException("step must be positive: " + step);
+ }
+ this.step = step;
+ }
+
+ /// The decrement button, for a caller that wants to give it an icon.
+ ///
+ /// #### Returns
+ ///
+ /// the decrement button
+ public Button getDecrementButton() {
+ return decrement;
+ }
+
+ /// The increment button, for a caller that wants to give it an icon.
+ ///
+ /// #### Returns
+ ///
+ /// the increment button
+ public Button getIncrementButton() {
+ return increment;
+ }
+
+ /// The editable field, for a caller that wants to make it read-only.
+ ///
+ /// #### Returns
+ ///
+ /// the text field
+ public TextField getField() {
+ return field;
+ }
+
+ /// Notified when the value changes, however it changed.
+ ///
+ /// #### Parameters
+ ///
+ /// - `l`: the listener
+ public void addActionListener(ActionListener l) {
+ listeners.addListener(l);
+ }
+
+ /// Stops notifying a listener.
+ ///
+ /// #### Parameters
+ ///
+ /// - `l`: the listener
+ public void removeActionListener(ActionListener l) {
+ listeners.removeListener(l);
+ }
+
+ /// Takes whatever the user typed and turns it into a value.
+ ///
+ /// An empty field is left alone rather than treated as zero: a user clearing the field
+ /// to retype it would otherwise watch it fill itself in under the caret. Anything that
+ /// is not a number is ignored the same way -- the field is corrected when the value is
+ /// next set, which is what leaving the field does.
+ private void commitTypedText() {
+ String text = field.getText();
+ if (text == null || text.length() == 0) {
+ return;
+ }
+ int typed;
+ try {
+ typed = Integer.parseInt(text.trim());
+ } catch (NumberFormatException err) {
+ return;
+ }
+ if (typed == value) {
+ return;
+ }
+ int clamped = clamp(typed);
+ value = clamped;
+ updateButtonState();
+ if (clamped != typed) {
+ // Out of range. Correct the field now rather than at focus loss, because the
+ // number under the caret is not one this control can produce.
+ syncField();
+ }
+ listeners.fireActionEvent(new ActionEvent(this));
+ }
+
+ private void syncField() {
+ String text = String.valueOf(value);
+ if (!text.equals(field.getText())) {
+ field.setText(text);
+ }
+ }
+
+ /// Disables the button that cannot move: a desktop stepper at its bound greys out the
+ /// half that would step past it rather than accepting a press that does nothing.
+ private void updateButtonState() {
+ decrement.setEnabled(value > minValue);
+ increment.setEnabled(value < maxValue);
+ }
+
+ private int clamp(int v) {
+ if (v < minValue) {
+ return minValue;
+ }
+ if (v > maxValue) {
+ return maxValue;
+ }
+ return v;
+ }
+}
diff --git a/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java b/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java
index 412ed92cf35..c495d85f811 100644
--- a/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java
+++ b/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java
@@ -4992,6 +4992,31 @@ public void showNativeScreen(Object nativeFullScreenPeer) {
public void setNativeCommands(Vector commands) {
}
+ /// Whether this platform actually has a native menu system for
+ /// `#setNativeCommands(Vector)` to put commands on.
+ ///
+ /// False here, and the default matters: `setNativeCommands` is a no-op on a platform
+ /// that has no menu bar, and `MenuBar.updateCommands` calls it and RETURNS -- it draws
+ /// no soft buttons, because on a platform with a real menu bar drawing them too would
+ /// duplicate every command. So on a platform without one, asking for
+ /// `Display#COMMAND_BEHAVIOR_NATIVE` did not fall back to anything: the commands went to
+ /// a method that discards them and were never drawn at all. Silently, since nothing in
+ /// that path can tell "handled natively" from "dropped".
+ ///
+ /// That was latent until a theme asked for it. The desktop native themes declare
+ /// `commandBehavior: Native`, which is right for the platforms they model and which two
+ /// of the ports that will install them -- Windows and Linux -- cannot honour yet.
+ ///
+ /// A port overrides this to true when it really puts the commands somewhere the user can
+ /// reach them. Everything else keeps whatever Codename One draws itself.
+ ///
+ /// #### Returns
+ ///
+ /// true if this platform has a native menu bar
+ public boolean isNativeCommandsSupported() {
+ return false;
+ }
+
/// Returns the desktop title-bar mode for this platform: one of {@code "native"} (OS title
/// bar + native menu bar), {@code "custom"} (undecorated window where the CN1 Toolbar acts as
/// the title bar) or {@code "toolbar"} (legacy in-app CN1 Toolbar). Returns {@code "toolbar"}
@@ -5006,6 +5031,24 @@ public String getDesktopTitleBarMode() {
return "toolbar";
}
+ /// The desktop title-bar mode this platform was explicitly asked for, or null when nobody
+ /// asked. Distinct from {@link #getDesktopTitleBarMode()}, which is documented to answer a
+ /// usable mode and therefore cannot express "unset" - it answers {@code "toolbar"} both for
+ /// a port with no opinion and for a project that deliberately chose the legacy look.
+ ///
+ /// That distinction is the whole point: it is what lets a native theme carry a
+ /// {@code desktopTitleBarMode} constant (Windows and macOS keep a system title bar and want
+ /// {@code native}; GNOME's HeaderBar IS the title bar and wants {@code custom}) without
+ /// overriding a project that set the build hint by hand. A build hint answers here; a theme
+ /// constant only gets consulted when this returns null.
+ ///
+ /// #### Returns
+ ///
+ /// the explicitly configured mode, or null when nothing configured one
+ public String getConfiguredDesktopTitleBarMode() {
+ return null;
+ }
+
/// Minimizes the native desktop window when the application draws its own (custom mode)
/// window chrome on an undecorated window. No-op on platforms without a native window.
public void minimizeNativeWindow() {
@@ -10782,6 +10825,23 @@ public void setCommandBehavior(int commandBehavior) {
commandBehavior = Display.COMMAND_BEHAVIOR_SOFTKEY;
}
}
+ if (commandBehavior == Display.COMMAND_BEHAVIOR_NATIVE && !isNativeCommandsSupported()) {
+ // Normalised here for the same reason BUTTON_BAR is normalised above: this is
+ // where a behaviour the platform cannot honour gets turned into one it can, and
+ // doing it here fixes every reader at once rather than each in turn.
+ //
+ // NATIVE is the one behaviour whose unsupported case is silent. MenuBar.
+ // updateCommands hands the commands to setNativeCommands and returns without
+ // drawing soft buttons -- correct where there is a real menu bar, since drawing
+ // them too would duplicate every command, and on a platform without one it means
+ // the commands go to a method that discards them and are never drawn at all.
+ // Nothing downstream can tell that from "the platform handled it".
+ //
+ // Latent until a theme asked for it, which the desktop native themes now do:
+ // they declare commandBehavior: Native, which is right for the platforms they
+ // model and which the Windows and Linux ports cannot honour yet.
+ commandBehavior = Display.COMMAND_BEHAVIOR_DEFAULT;
+ }
this.commandBehavior = commandBehavior;
notifyCommandBehavior(commandBehavior);
}
diff --git a/CodenameOne/src/com/codename1/ui/Component.java b/CodenameOne/src/com/codename1/ui/Component.java
index e07c16af9b9..a2393c11491 100644
--- a/CodenameOne/src/com/codename1/ui/Component.java
+++ b/CodenameOne/src/com/codename1/ui/Component.java
@@ -7202,11 +7202,86 @@ boolean fireContextMenu(int x, int y) {
return true;
}
}
+ // A listener that did not consume the event has not handled it, so the component's
+ // own commands still get to answer. Resolved in the same walk rather than in a
+ // second one, so the nearest ancestor with EITHER wins -- a row inside a table that
+ // has its own commands must not be overruled by the table's listener declining.
+ if (c.contextMenuCommands != null && c.contextMenuCommands.length > 0) {
+ ContextMenu.show(c, x, y, c.contextMenuCommands);
+ return true;
+ }
c = c.getParent();
}
return false;
}
+ /// Which component's commands a context-menu request at this point would open, or null
+ /// when it would open nothing.
+ ///
+ /// The same walk `#fireContextMenu(int, int)` performs, minus the showing. Split out
+ /// because the showing is a modal popup that parks the caller until the user dismisses
+ /// it: the routing is the part worth asserting, and asserting it through the showing
+ /// means a test with no user to dismiss the menu simply hangs.
+ ///
+ /// #### Returns
+ ///
+ /// the component whose commands would open, or null
+ Component resolveContextMenuOwner() {
+ Component c = this;
+ while (c != null) {
+ if (c.contextMenuCommands != null && c.contextMenuCommands.length > 0) {
+ return c;
+ }
+ c = c.getParent();
+ }
+ return null;
+ }
+
+ /// The commands a right click on this component offers.
+ ///
+ /// #### Returns
+ ///
+ /// the commands, or null when this component has no menu of its own
+ public Command[] getContextMenuCommands() {
+ if (contextMenuCommands == null) {
+ return null;
+ }
+ Command[] copy = new Command[contextMenuCommands.length];
+ System.arraycopy(contextMenuCommands, 0, copy, 0, contextMenuCommands.length);
+ return copy;
+ }
+
+ /// Gives this component a right-click menu.
+ ///
+ /// The menu opens by itself on a secondary mouse button, a stylus barrel button or a
+ /// long press, so nothing else is needed:
+ ///
+ /// ```java
+ /// label.setContextMenuCommands(cut, copy, paste);
+ /// ```
+ ///
+ /// The commands are fixed. When they depend on what was clicked -- which row, which
+ /// cell -- register a `#addContextMenuListener(ActionListener)` instead and call
+ /// `ContextMenu#show(Component, int, int, Command...)` from it.
+ ///
+ /// Passing null or an empty array removes the menu. It does not leave an empty one
+ /// behind: a menu with no items is a rectangle the user has to dismiss to learn it was
+ /// empty.
+ ///
+ /// #### Parameters
+ ///
+ /// - `commands`: the menu items in order, or null for none
+ public void setContextMenuCommands(Command... commands) {
+ if (commands == null || commands.length == 0) {
+ contextMenuCommands = null;
+ return;
+ }
+ contextMenuCommands = new Command[commands.length];
+ System.arraycopy(commands, 0, contextMenuCommands, 0, commands.length);
+ }
+
+ private Command[] contextMenuCommands;
+
/// Dispatches a mouse wheel event to the registered listeners walking up the component
/// hierarchy until a listener consumes the event. Returns true if a listener consumed it,
/// in which case the default scrolling behavior should be skipped.
diff --git a/CodenameOne/src/com/codename1/ui/ContextMenu.java b/CodenameOne/src/com/codename1/ui/ContextMenu.java
new file mode 100644
index 00000000000..7cd0a45a07d
--- /dev/null
+++ b/CodenameOne/src/com/codename1/ui/ContextMenu.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
+package com.codename1.ui;
+
+import com.codename1.ui.events.ActionEvent;
+import com.codename1.ui.events.ActionListener;
+import com.codename1.ui.geom.Rectangle;
+import com.codename1.ui.layouts.BorderLayout;
+import com.codename1.ui.layouts.BoxLayout;
+
+/// The right-click menu.
+///
+/// Codename One has carried the *event* for a long time --
+/// `Component#addContextMenuListener(ActionListener)` fires on a secondary mouse button, a
+/// stylus barrel button or a long press -- and has never had anything that turns it into a
+/// menu. Every application that wanted one built its own popup, which is why none of them
+/// looked like the platform.
+///
+/// Two ways in. Give a component its commands and the menu appears by itself:
+///
+/// ```java
+/// label.setContextMenuCommands(cut, copy, paste);
+/// ```
+///
+/// Or open it from a listener, when the commands depend on what was clicked:
+///
+/// ```java
+/// table.addContextMenuListener(e -> {
+/// ContextMenu.show(table, e.getX(), e.getY(), commandsFor(rowAt(e.getY())));
+/// e.consume();
+/// });
+/// ```
+///
+/// The menu is an anchored popup, which means it is drawn inside the application's surface
+/// rather than in a window of its own -- deliberately, and for the same reasons
+/// `Dialog#showPopupDialog(Rectangle)` gives: the rectangle it points at is in its host's
+/// coordinate space, a separate window would never receive the click meant to dismiss it,
+/// and it would steal focus from its opener every time it appeared. It is styled through
+/// `PopupContentPane` and `Command`, which the desktop native themes define.
+///
+/// A null or empty command array opens nothing. That is not a special case to work around:
+/// a menu with no items is a rectangle the user has to dismiss to learn it was empty.
+public final class ContextMenu {
+
+ private ContextMenu() {
+ }
+
+ /// Opens the menu at a point, in the coordinate space of the anchor's top level.
+ ///
+ /// #### Parameters
+ ///
+ /// - `anchor`: the component the menu belongs to; its top level hosts the popup
+ ///
+ /// - `x`: the pointer x coordinate
+ ///
+ /// - `y`: the pointer y coordinate
+ ///
+ /// - `commands`: the menu items, in order
+ ///
+ /// #### Returns
+ ///
+ /// the command the user chose, or null if the menu was dismissed or never opened
+ public static Command show(Component anchor, int x, int y, Command... commands) {
+ if (anchor == null || commands == null || commands.length == 0) {
+ return null;
+ }
+ Command[] chosen = new Command[1];
+ Dialog menu = build(commands, chosen);
+ TopLevelContainer host = anchor.getTopLevelContainer();
+ if (host != null) {
+ menu.setTopLevelHost(host);
+ }
+ // A one-pixel rectangle at the pointer: the popup points at where the user clicked,
+ // not at the middle of whatever they clicked on. Anchoring to the component would put
+ // a menu for a full-width row in the centre of the screen.
+ menu.showPopupDialog(new Rectangle(x, y, 1, 1));
+ return chosen[0];
+ }
+
+ /// Opens the menu over a component rather than at a point, for a caller that has no
+ /// pointer position -- a keyboard menu key, or a disclosure button that opens the same
+ /// menu a right click would.
+ ///
+ /// #### Parameters
+ ///
+ /// - `anchor`: the component to point at
+ ///
+ /// - `commands`: the menu items, in order
+ ///
+ /// #### Returns
+ ///
+ /// the command the user chose, or null
+ public static Command show(Component anchor, Command... commands) {
+ if (anchor == null || commands == null || commands.length == 0) {
+ return null;
+ }
+ Command[] chosen = new Command[1];
+ build(commands, chosen).showPopupDialog(anchor);
+ return chosen[0];
+ }
+
+ /// Builds the popup: one left-aligned button per command, stacked, in a dialog with no
+ /// title and no chrome of its own.
+ ///
+ /// The buttons carry the command's NAME rather than the command itself. A
+ /// `Button(Command)` fires the command from inside its own action event, which would run
+ /// it while the menu is still showing -- so a command that opens a form or another dialog
+ /// would put it underneath a menu still holding the popup layer, and the menu would
+ /// outlive the screen it belonged to. Here the menu is disposed first and the command
+ /// dispatched after, which is the order `Dialog` uses for its own command buttons.
+ ///
+ /// #### Parameters
+ ///
+ /// - `commands`: the menu items
+ ///
+ /// - `chosen`: a one-element box the pressed command is written into
+ ///
+ /// #### Returns
+ ///
+ /// the popup, not yet shown
+ private static Dialog build(Command[] commands, final Command[] chosen) {
+ final Dialog menu = new Dialog();
+ menu.setDisposeWhenPointerOutOfBounds(true);
+ menu.setLayout(new BorderLayout());
+ menu.setAutoDispose(true);
+ // A context menu never becomes an operating system window even where the theme asks
+ // for one by default -- see the class note. setNativeWindowMode is per instance and
+ // outranks both the static default and the theme constant.
+ menu.setNativeWindowMode(false);
+
+ Container items = new Container(BoxLayout.y());
+ items.setUIID("CommandList");
+ items.setScrollableY(true);
+ // `final` on the loop variable, not merely convention: the core compiles at Java 5
+ // source level, where a foreach variable is NOT implicitly final and so cannot be
+ // captured by the listener below without it.
+ for (final Command cmd : commands) {
+ if (cmd == null) {
+ continue;
+ }
+ Button b = new Button(cmd.getCommandName(), cmd.getIcon());
+ b.setUIID("Command");
+ b.setEnabled(cmd.isEnabled());
+ b.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ chosen[0] = cmd;
+ menu.dispose();
+ cmd.actionPerformed(new ActionEvent(cmd, ActionEvent.Type.Command));
+ }
+ });
+ items.add(b);
+ }
+ menu.add(BorderLayout.CENTER, items);
+ return menu;
+ }
+}
diff --git a/CodenameOne/src/com/codename1/ui/Dialog.java b/CodenameOne/src/com/codename1/ui/Dialog.java
index 86fbf13f67e..3e775f79a71 100644
--- a/CodenameOne/src/com/codename1/ui/Dialog.java
+++ b/CodenameOne/src/com/codename1/ui/Dialog.java
@@ -2705,6 +2705,16 @@ void nativeCloseRequested(ActionEvent evt) {
// Vetoed so the dialog owns the teardown. Letting the window dispose itself
// first would leave the dialog believing it was still showing.
evt.consume();
+ cancel();
+ }
+
+ /// Every way a user says "not this one" -- the window's close control, the platform back
+ /// gesture, and Escape on a desktop keyboard -- means the same thing, so they resolve it in
+ /// one place: the back command when there is one, and disposal when there isn't.
+ ///
+ /// Written three times before this existed, which is how the three drifted apart: add a
+ /// fourth entry point and the next one is written a fourth time.
+ void cancel() {
Command back = getBackCommand();
if (back != null) {
dispatchCommand(back, new ActionEvent(back, ActionEvent.Type.Command));
@@ -2713,6 +2723,17 @@ void nativeCloseRequested(ActionEvent evt) {
dispose();
}
+ /// @inheritDoc
+ ///
+ /// Escape closes a dialog, which is what its window's close control already means. An
+ /// anchored popup is included: it is the one surface where Escape is the ONLY way out that
+ /// does not also click something underneath.
+ @Override
+ boolean escapePressed() {
+ cancel();
+ return true;
+ }
+
/// Takes this dialog back out of its window. Idempotent, and marshalled onto the
/// event dispatch thread.
///
@@ -3164,12 +3185,7 @@ void hostBackPressed(ActionEvent evt) {
return;
}
evt.consume();
- Command back = getBackCommand();
- if (back != null) {
- dispatchCommand(back, new ActionEvent(back, ActionEvent.Type.Command));
- return;
- }
- dispose();
+ cancel();
}
/// Whether this is the last dialog added to its host's shared layer, which is the
diff --git a/CodenameOne/src/com/codename1/ui/Form.java b/CodenameOne/src/com/codename1/ui/Form.java
index fce503b772f..8a6d9eee373 100644
--- a/CodenameOne/src/com/codename1/ui/Form.java
+++ b/CodenameOne/src/com/codename1/ui/Form.java
@@ -899,9 +899,29 @@ public Container getTitleArea() {
}
/// Returns the configured desktop title-bar mode ({@code native}, {@code custom} or
- /// {@code toolbar}). Sourced from the implementation (desktop ports report the real mode;
- /// everything else returns {@code toolbar}).
+ /// {@code toolbar}).
+ ///
+ /// Three sources in order. The build hint wins: a project that spelled out
+ /// {@code desktop.titleBar} means it, and a theme must not talk it out of that. Then the
+ /// installed theme's {@code desktopTitleBarMode} constant, which is how the desktop native
+ /// themes express the convention of the platform they model -- Windows and macOS keep a
+ /// system title bar and ask for {@code native}, GNOME's HeaderBar is the title bar and asks
+ /// for {@code custom}. Then whatever the port reports, which is {@code toolbar} everywhere
+ /// that is not a desktop.
+ ///
+ /// The theme step is gated on {@link Display#isDesktop()} so a mobile port that somehow
+ /// loaded a desktop theme still renders its ordinary chrome.
String getDesktopTitleBarMode() {
+ String configured = Display.impl.getConfiguredDesktopTitleBarMode();
+ if (configured != null && configured.length() > 0) {
+ return configured;
+ }
+ if (Display.getInstance().isDesktop()) {
+ String themed = getUIManager().getThemeConstant("desktopTitleBarMode", null);
+ if (themed != null && themed.length() > 0) {
+ return themed;
+ }
+ }
return Display.impl.getDesktopTitleBarMode();
}
@@ -919,8 +939,17 @@ boolean isDesktopNativeChrome() {
/// Indicates the {@code native} desktop title-bar mode, where the CN1 Toolbar is hidden entirely:
/// the form title goes into the real OS window title bar and the commands are bridged to a native
/// menu bar. Inert (false) on mobile.
+ ///
+ /// Conditional on the platform actually HAVING a native menu bar. Hiding the Toolbar takes
+ /// away the side menu, which is the only place the commands are drawn, so doing it on a
+ /// port whose `setNativeCommands` discards them removes every command from the
+ /// application. The title still goes to the OS title bar on such a port -- that part
+ /// works everywhere -- and the Toolbar stays, which is the legacy look rather than a
+ /// broken one.
boolean isDesktopHideToolbar() {
- return Display.getInstance().isDesktop() && "native".equals(getDesktopTitleBarMode());
+ return Display.getInstance().isDesktop()
+ && "native".equals(getDesktopTitleBarMode())
+ && Display.impl.isNativeCommandsSupported();
}
/// Indicates the {@code custom} desktop title-bar mode, where the CN1 Toolbar stays visible and
@@ -3842,10 +3871,45 @@ static TabIterator buildTabIterator(Container root, Component start) {
return new TabIterator(out, start);
}
+ /// The traversal order a desktop keyboard walks with Tab.
+ ///
+ /// Deliberately NOT `#buildTabIterator(Container, Component)`, whose filter is opt-in:
+ /// `Component#getPreferredTabIndex()` defaults to -1 and `TextArea` is the only class in the
+ /// framework that ever calls `setPreferredTabIndex(0)`, so that iterator holds text areas and
+ /// nothing else. That is right for what built it -- "next field while editing", which is what
+ /// `TextEditUtil` and `Picker` use it for -- and wrong for Tab, which would then walk between
+ /// a form's text fields and skip every button, checkbox and slider between them.
+ ///
+ /// A desktop keyboard should reach whatever the pointer reaches, so the filter here is
+ /// focusability itself. An explicit `preferredTabIndex` still wins: those components sort to
+ /// the front in the order they were numbered, which is the whole point of setting one.
+ /// Everything else keeps document order, because `Collections#sort` is stable and
+ /// `ComponentSelector` walks the tree in the order the form reads.
+ ///
+ /// #### Parameters
+ ///
+ /// - `root`: the top level to walk
+ ///
+ /// - `start`: the component to start from
+ ///
+ /// #### Returns
+ ///
+ /// the desktop traversal iterator
+ static TabIterator buildDesktopTabIterator(Container root, Component start) {
+ root.updateTabIndices(0);
+ java.util.List out = new ArrayList();
+ out.addAll(ComponentSelector.select("*", root).filter(new DesktopTabIteratorFilter()));
+ Collections.sort(out, new DesktopTabIteratorComparator());
+ return new TabIterator(out, start);
+ }
+
/// {@inheritDoc}
@Override
public void keyPressed(int keyCode) {
int game = Display.getInstance().getGameAction(keyCode);
+ if (desktopKeyPressed(keyCode)) {
+ return;
+ }
if (menuBar.handlesKeycode(keyCode) && !focusedHandlesInput(keyCode)) {
menuBar.keyPressed(keyCode);
return;
@@ -3941,6 +4005,97 @@ private boolean focusedHandlesInput(int keyCode) {
&& focused.getComponentForm() == this; //NOPMD CompareObjectsWithEquals
}
+ /// Horizontal tab. Ports deliver Tab as its character code, which is what
+ /// `JavaSEPort.C.getCode` returns for a key event whose `getKeyChar()` is defined -- Tab's
+ /// is, so it arrives here as 9 rather than as an AWT virtual key.
+ private static final int KEY_TAB = 9;
+
+ /// Escape, likewise delivered as its character code.
+ private static final int KEY_ESCAPE = 27;
+
+ /// The two keyboard conventions every desktop toolkit has and Codename One never had.
+ ///
+ /// **Tab / Shift-Tab moves focus.** The traversal order itself is not new -- `TabIterator`,
+ /// `getNextComponent` and `preferredTabIndex` have been here for years -- but nothing was
+ /// ever wired to the key, so the only consumers were "next field while editing" paths in
+ /// the ports. On a desktop a form that cannot be operated from the keyboard is not a
+ /// desktop form.
+ ///
+ /// **Escape means cancel.** On a `Dialog` it does what the window's own close control does,
+ /// which is already written as "the back command, or dispose when there isn't one". On a
+ /// plain form it fires the back command and does nothing at all when there is none -- Escape
+ /// must never be able to exit an application.
+ ///
+ /// Enter needs nothing here: ports already map it to `GAME_KEY_CODE_FIRE` and
+ /// `keyReleased` already fires `getDefaultCommand()` on `GAME_FIRE`.
+ ///
+ /// The whole method is gated on `Display#isDesktop()`, so no mobile key dispatch and no
+ /// mobile screenshot baseline moves. Returning true means the key was consumed.
+ ///
+ /// #### Parameters
+ ///
+ /// - `keyCode`: the code being dispatched
+ ///
+ /// #### Returns
+ ///
+ /// true when this form handled the key and dispatch should stop
+ private boolean desktopKeyPressed(int keyCode) {
+ if (!Display.getInstance().isDesktop()) {
+ return false;
+ }
+ if (keyCode == KEY_TAB) {
+ return moveFocusByTab(Display.getInstance().isShiftKeyDown());
+ }
+ if (keyCode == KEY_ESCAPE) {
+ return escapePressed();
+ }
+ return false;
+ }
+
+ /// Moves focus one step along the tab order, wrapping at either end so the keyboard can
+ /// never strand itself. Answers false when there is nothing else focusable, leaving the key
+ /// to ordinary dispatch.
+ ///
+ /// #### Parameters
+ ///
+ /// - `backwards`: true for Shift-Tab
+ boolean moveFocusByTab(boolean backwards) {
+ Component from = focused;
+ if (from == null || from.getComponentForm() != this) { //NOPMD CompareObjectsWithEquals
+ initFocused();
+ from = focused;
+ }
+ TabIterator order = buildDesktopTabIterator(this, from);
+ Component next = backwards ? order.getPrevious() : order.getNext();
+ if (next == null) {
+ // Ran off the end. Wrap, so the keyboard can never strand itself at the last
+ // control of a form with no way back except the pointer.
+ java.util.List all = order.getComponents();
+ if (all.isEmpty()) {
+ return false;
+ }
+ next = backwards ? all.get(all.size() - 1) : all.get(0);
+ }
+ if (next == from) { //NOPMD CompareObjectsWithEquals
+ return false;
+ }
+ setFocused(next);
+ next.scrollRectToVisible(0, 0, next.getWidth(), next.getHeight(), next);
+ return true;
+ }
+
+ /// Escape on an ordinary form: the back command when there is one, nothing otherwise.
+ /// `Dialog` overrides this to close itself.
+ boolean escapePressed() {
+ Command back = getBackCommand();
+ if (back == null) {
+ return false;
+ }
+ back.actionPerformed(new ActionEvent(back, ActionEvent.Type.Command));
+ actionCommandImpl(back);
+ return true;
+ }
+
/// Space, the lowest code that stands for a character a text component can receive.
private static final int FIRST_PRINTABLE_KEY_CODE = 32;
@@ -5594,6 +5749,18 @@ public Component getCurrent() {
return current;
}
+ /// The components in traversal order, unmodifiable.
+ ///
+ /// Exposed so a caller that ran off either end can wrap round to the other one without
+ /// rebuilding the order it just walked.
+ ///
+ /// #### Returns
+ ///
+ /// the traversal order
+ public java.util.List getComponents() {
+ return Collections.unmodifiableList(components);
+ }
+
/// Sets the current component in the iterator. This reposition the iterator
/// to the given component.
///
@@ -5797,4 +5964,35 @@ public boolean filter(Component c) {
return c.getTabIndex() >= 0 && c.isVisible() && c.isFocusable() && c.isEnabled() && !c.isHidden(true);
}
}
+
+ /// Everything a pointer could reach, which is what a desktop keyboard must reach too.
+ /// Note the absence of the `getTabIndex() >= 0` test the mobile filter opens with: that test
+ /// is what makes the ordinary iterator opt-in, and opting in is exactly what nothing except
+ /// `TextArea` does.
+ private static class DesktopTabIteratorFilter implements Filter {
+ @Override
+ public boolean filter(Component c) {
+ return c.isVisible() && c.isFocusable() && c.isEnabled() && !c.isHidden(true);
+ }
+ }
+
+ /// Orders the desktop traversal: an explicitly numbered component first, in its number's
+ /// order, and everything else in document order behind it.
+ ///
+ /// Zero counts as unnumbered, not as first. `setPreferredTabIndex(0)` is how
+ /// `Component#setTraversable(boolean)` says "join the order", never "be the first" --
+ /// reading it as a position would put every `TextArea` ahead of the label above it.
+ private static class DesktopTabIteratorComparator implements Comparator {
+ @Override
+ public int compare(Component o1, Component o2) {
+ int i1 = positionOf(o1);
+ int i2 = positionOf(o2);
+ return i1 < i2 ? -1 : i2 < i1 ? 1 : 0;
+ }
+
+ private int positionOf(Component c) {
+ int idx = c.getPreferredTabIndex();
+ return idx > 0 ? idx : Integer.MAX_VALUE;
+ }
+ }
}
diff --git a/CodenameOne/src/com/codename1/ui/MenuBar.java b/CodenameOne/src/com/codename1/ui/MenuBar.java
index b7f0c7ad26b..aea23be510b 100644
--- a/CodenameOne/src/com/codename1/ui/MenuBar.java
+++ b/CodenameOne/src/com/codename1/ui/MenuBar.java
@@ -489,6 +489,10 @@ public void setSelectCommand(Command selectCommand) {
private void updateCommands() {
int commandBehavior = getCommandBehavior();
if (commandBehavior == Display.COMMAND_BEHAVIOR_NATIVE) {
+ // Reachable only where the platform really has a menu bar:
+ // CodenameOneImplementation.setCommandBehavior normalises NATIVE away on a
+ // platform that does not, because returning here without one drops every
+ // command silently.
Display.getInstance().getImplementation().setNativeCommands(commands);
return;
}
diff --git a/CodenameOne/src/com/codename1/ui/Toolbar.java b/CodenameOne/src/com/codename1/ui/Toolbar.java
index 4cb1985bcd2..1a8e3434690 100644
--- a/CodenameOne/src/com/codename1/ui/Toolbar.java
+++ b/CodenameOne/src/com/codename1/ui/Toolbar.java
@@ -1785,6 +1785,11 @@ public void actionPerformed(ActionEvent evt) {
if (isLeft) {
sidemenuDialog = new InteractionDialog(new BorderLayout());
+ // Framework chrome, never an operating system window: this popup is POSITIONED by the
+ // framework, and native window mode documents those margins as ignored, so in a window
+ // it comes out centred and loses the placement that is its whole point. See
+ // TooltipManager for the full note.
+ sidemenuDialog.setNativeWindowMode(false);
sidemenuDialog.setFormMode(true);
sidemenuDialog.setUIID("Container");
@@ -1833,6 +1838,11 @@ public void actionPerformed(ActionEvent evt) {
}
} else {
rightSidemenuDialog = new InteractionDialog(new BorderLayout());
+ // Framework chrome, never an operating system window: this popup is POSITIONED by the
+ // framework, and native window mode documents those margins as ignored, so in a window
+ // it comes out centred and loses the placement that is its whole point. See
+ // TooltipManager for the full note.
+ rightSidemenuDialog.setNativeWindowMode(false);
rightSidemenuDialog.setFormMode(true);
rightSidemenuDialog.setUIID("Container");
diff --git a/CodenameOne/src/com/codename1/ui/TooltipManager.java b/CodenameOne/src/com/codename1/ui/TooltipManager.java
index 79ee4e337e5..6862b91723f 100644
--- a/CodenameOne/src/com/codename1/ui/TooltipManager.java
+++ b/CodenameOne/src/com/codename1/ui/TooltipManager.java
@@ -141,6 +141,13 @@ protected void showTooltip(final String tip, final Component cmp) {
return;
}
currentTooltip = new InteractionDialog(new BorderLayout());
+ // Framework chrome, never an operating system window. This popup is POSITIONED by
+ // the framework -- it is placed relative to the surface it belongs to -- and native
+ // window mode documents those margins as ignored, so in a window it would come out
+ // centred and lose the placement that is its whole point. Dialog.usesNativeWindow
+ // already exempts menus and anchored popups for the same reason; AbstractDialog is a
+ // closed interface (see the comment at its end), so each of these says so itself.
+ currentTooltip.setNativeWindowMode(false);
// showPopupDialog infers the host from the anchor anyway, but saying it is
// free and records which surface this tooltip belongs to.
currentTooltip.setTopLevelHost(f);
diff --git a/CodenameOne/src/com/codename1/ui/Window.java b/CodenameOne/src/com/codename1/ui/Window.java
index 10f3ae55655..8a1bc628d27 100644
--- a/CodenameOne/src/com/codename1/ui/Window.java
+++ b/CodenameOne/src/com/codename1/ui/Window.java
@@ -1011,6 +1011,78 @@ public Form.TabIterator getTabIterator(Component start) {
return Form.buildTabIterator(this, start);
}
+ /// Horizontal tab, as a port delivers it. Same value `Form` uses; see its note.
+ private static final int KEY_TAB = 9;
+
+ /// Escape, likewise.
+ private static final int KEY_ESCAPE = 27;
+
+ /// Tab traversal and Escape inside a window, the same two conventions `Form` gained.
+ ///
+ /// A window needs its own copy rather than inheriting one because it is not a `Form` -- it
+ /// extends `Container` -- and because it owns a keyboard scope that a form does not. Both
+ /// keys are therefore resolved after `focusWithinKeyScope()`, so an overlay that has claimed
+ /// the keyboard keeps it.
+ ///
+ /// Escape closes the window when its close operation allows, which is what the platform's own
+ /// close control does. A window whose close operation is DO_NOTHING_ON_CLOSE ignores it, for
+ /// the same reason it ignores the close button.
+ ///
+ /// #### Parameters
+ ///
+ /// - `keyCode`: the code being dispatched
+ ///
+ /// #### Returns
+ ///
+ /// true when the window handled the key and dispatch should stop
+ private boolean desktopKeyPressed(int keyCode) {
+ if (!Display.getInstance().isDesktop()) {
+ return false;
+ }
+ if (keyCode == KEY_TAB) {
+ return moveFocusByTab(Display.getInstance().isShiftKeyDown());
+ }
+ if (keyCode == KEY_ESCAPE) {
+ return escapePressed();
+ }
+ return false;
+ }
+
+ /// Moves focus one step along this window's desktop traversal order, wrapping at either end.
+ ///
+ /// #### Parameters
+ ///
+ /// - `backwards`: true for Shift-Tab
+ boolean moveFocusByTab(boolean backwards) {
+ Component from = getFocused();
+ Form.TabIterator order = Form.buildDesktopTabIterator(this, from);
+ Component next = backwards ? order.getPrevious() : order.getNext();
+ if (next == null) {
+ java.util.List all = order.getComponents();
+ if (all.isEmpty()) {
+ return false;
+ }
+ next = backwards ? all.get(all.size() - 1) : all.get(0);
+ }
+ if (next == from) { //NOPMD CompareObjectsWithEquals
+ return false;
+ }
+ setFocused(next);
+ next.scrollRectToVisible(0, 0, next.getWidth(), next.getHeight(), next);
+ return true;
+ }
+
+ /// Escape asks the window to close, which is what its own close control asks. Honours the
+ /// close operation, so a window that refuses the close button refuses this too.
+ boolean escapePressed() {
+ if (getCloseOperation() == DO_NOTHING_ON_CLOSE) {
+ return false;
+ }
+ closeRequested();
+ return true;
+ }
+
+
/// {@inheritDoc}
@Override
public void scrollComponentToVisible(Component c) {
@@ -4504,6 +4576,9 @@ public void keyPressed(int keyCode) {
if (!focusWithinKeyScope()) {
return;
}
+ if (desktopKeyPressed(keyCode)) {
+ return;
+ }
int game = Display.getInstance().getGameAction(keyCode);
if (focused != null) {
// Everything below is asked of the component that took the press, not of
diff --git a/CodenameOne/src/com/codename1/ui/plaf/UIManager.java b/CodenameOne/src/com/codename1/ui/plaf/UIManager.java
index 5e65f57ab27..c4502148d95 100644
--- a/CodenameOne/src/com/codename1/ui/plaf/UIManager.java
+++ b/CodenameOne/src/com/codename1/ui/plaf/UIManager.java
@@ -1281,6 +1281,46 @@ private void resetThemeProps(Hashtable installedTheme) {
themeProps.put("ScrollThumb.bgColor", foreground);
}
+ // The interactive desktop scrollbar draws through its own four UIIDs
+ // (LookAndFeel.initScroll picks them when interactiveScrollBool is on) so that turning
+ // it on never restyles the mobile bar. Nothing seeded them, which meant a theme that
+ // enabled the constant without also defining all four got a track and a thumb built
+ // from the blank default style: an invisible scrollbar, drawn, reserving a gutter, with
+ // nothing reporting a problem. The seeds below are the mobile ones plus the two things
+ // the desktop bar needs and the mobile one does not -- a gutter wide enough to grab
+ // (the track UIID's horizontal padding is what reserves it) and thumb hover/pressed
+ // states, so the highlight exists even before a theme styles it.
+ //
+ // Guarded like every other seed here, so a theme that defines these suppresses them
+ // rather than fighting them. All three desktop native themes do.
+ if (installedTheme == null || !installedTheme.containsKey("DesktopScroll.derive")) {
+ themeProps.put("DesktopScroll.margin", "0,0,0,0");
+ int gutter = Math.max(2, Display.getInstance().convertToPixels(3, true) / 2);
+ themeProps.put("DesktopScroll.padding", "0," + gutter + ",0," + gutter);
+ themeProps.put("DesktopScroll.transparency", "0");
+ }
+ if (installedTheme == null || !installedTheme.containsKey("DesktopScrollThumb.derive")) {
+ themeProps.put("DesktopScrollThumb.padding", "0,0,0,0");
+ themeProps.put("DesktopScrollThumb.margin", "0,0,0,0");
+ themeProps.put("DesktopScrollThumb.bgColor", foreground);
+ themeProps.put("DesktopScrollThumb.sel#derive", "DesktopScrollThumb");
+ themeProps.put("DesktopScrollThumb.press#derive", "DesktopScrollThumb");
+ }
+ if (installedTheme == null || !installedTheme.containsKey("DesktopHorizontalScroll.derive")) {
+ themeProps.put("DesktopHorizontalScroll.margin", "0,0,0,0");
+ int gutter = Math.max(2, Display.getInstance().convertToPixels(3, true) / 2);
+ themeProps.put("DesktopHorizontalScroll.padding", gutter + ",0," + gutter + ",0");
+ themeProps.put("DesktopHorizontalScroll.transparency", "0");
+ }
+ if (installedTheme == null
+ || !installedTheme.containsKey("DesktopHorizontalScrollThumb.derive")) {
+ themeProps.put("DesktopHorizontalScrollThumb.padding", "0,0,0,0");
+ themeProps.put("DesktopHorizontalScrollThumb.margin", "0,0,0,0");
+ themeProps.put("DesktopHorizontalScrollThumb.bgColor", foreground);
+ themeProps.put("DesktopHorizontalScrollThumb.sel#derive", "DesktopHorizontalScrollThumb");
+ themeProps.put("DesktopHorizontalScrollThumb.press#derive", "DesktopHorizontalScrollThumb");
+ }
+
if (installedTheme == null || !installedTheme.containsKey("SliderFull.derive")) {
themeProps.put("SliderFull.bgColor", foreground);
}
diff --git a/CodenameOne/src/com/codename1/ui/spinner/Picker.java b/CodenameOne/src/com/codename1/ui/spinner/Picker.java
index 2e1935a1f61..23da1ecf560 100644
--- a/CodenameOne/src/com/codename1/ui/spinner/Picker.java
+++ b/CodenameOne/src/com/codename1/ui/spinner/Picker.java
@@ -350,6 +350,11 @@ && isLightweightModeSupportedForType(type)) {
setEnabled(true);
} else {
Dialog pickerDlg = new Dialog();
+ // Framework chrome, never an operating system window: this popup is POSITIONED by the
+ // framework, and native window mode documents those margins as ignored, so in a window
+ // it comes out centred and loses the placement that is its whole point. See
+ // TooltipManager for the full note.
+ pickerDlg.setNativeWindowMode(false);
pickerDlg.setDisposeWhenPointerOutOfBounds(true);
pickerDlg.setLayout(new BorderLayout());
Calendar cld = Calendar.getInstance();
@@ -768,6 +773,15 @@ protected void deinitialize() {
};
+ // Framework chrome, never an operating system window. This one is placed by
+ // hand -- setX/setY/setWidth/setHeight below, then show(top, bottom, left,
+ // right) -- and native window mode documents exactly those margins as
+ // ignored, so in a window the popup comes out centred and every placement
+ // variant collapses onto the same picture. Measured on the Windows port
+ // before this line existed: LightweightPickerButtons captured two of its
+ // four placements, the two it did capture were byte-identical, and the
+ // suite then timed out waiting for the other two.
+ dlg.setNativeWindowMode(false);
dlg.setOwner(Picker.this);
//dlg.setFormMode(!isTablet);
ComponentSelector.select("DialogTitle", dlg).getParent().setPadding(0).setMargin(0).setBorder(Border.createEmpty());
diff --git a/CodenameOne/src/com/codename1/ui/validation/Validator.java b/CodenameOne/src/com/codename1/ui/validation/Validator.java
index fbaaebd63ab..e8f9ad79cee 100644
--- a/CodenameOne/src/com/codename1/ui/validation/Validator.java
+++ b/CodenameOne/src/com/codename1/ui/validation/Validator.java
@@ -102,7 +102,19 @@ public class Validator {
private static boolean validateOnEveryKey = false;
private final HashMap constraintList = new HashMap();
private final ArrayList submitButtons = new ArrayList();
- private InteractionDialog message = new InteractionDialog();
+ // Framework chrome, never an operating system window: this bubble is placed against the
+ // field it belongs to, and native window mode documents those margins as ignored. The
+ // field initialiser needs it as much as the reassignment below does -- it is the instance
+ // that is used until a constraint supplies a message of its own, and missing it is what
+ // FrameworkChromeNeverWindowsTest caught by counting constructions against opt-outs.
+ private InteractionDialog message = newChromePopup();
+
+ /// An InteractionDialog that will not become a window whatever the theme default is.
+ private static InteractionDialog newChromePopup() {
+ InteractionDialog d = new InteractionDialog();
+ d.setNativeWindowMode(false);
+ return d;
+ }
/// Error message UIID defaults to DialogBody. Allows customizing the look of the message
private String errorMessageUIID = "DialogBody";
/// Indicates the mode in which validation failures are expressed
@@ -569,6 +581,11 @@ public void focusGained(Component cmp) {
String err = getErrorMessage(cmp);
if (err != null && err.length() > 0) {
message = new InteractionDialog(err);
+ // Framework chrome, never an operating system window: this popup is POSITIONED by the
+ // framework, and native window mode documents those margins as ignored, so in a window
+ // it comes out centred and loses the placement that is its whole point. See
+ // TooltipManager for the full note.
+ message.setNativeWindowMode(false);
// The emblem path below shows by rectangle, which has
// no anchor component to resolve a host from.
message.setTopLevelHost(p);
diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java b/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java
index a60f1c19002..671bc754ebe 100644
--- a/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java
+++ b/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java
@@ -1149,7 +1149,15 @@ public static void setShowEDTViolationStacks(boolean aShowEDTViolationStacks) {
private static Resources nativeThemeRes;
// Desktop window-chrome configuration. Defaults preserve the legacy behavior (CN1 Toolbar,
// no interactive scrollbars); the generated desktop Stub opts a new app in.
- private static String desktopTitleBarMode = "toolbar";
+ //
+ // null means "nobody asked", NOT "toolbar". The two have to be distinguishable because a
+ // desktop native theme carries its own desktopTitleBarMode constant, and that constant may
+ // only speak when the project did not. Every reader below coalesces null to "toolbar" for
+ // its own answer, so nothing outside this class sees the sentinel -- except
+ // getConfiguredDesktopTitleBarMode, which exists to report it, and
+ // injectDesktopThemeConstants, which must not inject a mode nobody chose or it would
+ // overwrite the theme's own constant with this default.
+ private static String desktopTitleBarMode;
private static boolean desktopInteractiveScrollbars = false;
// Caches the last command-name signature pushed to the native menu bar to avoid rebuilding
// (and flickering the macOS screen menu) when an unchanged form is re-shown.
@@ -2729,6 +2737,17 @@ public void run() {
}
}
+ /// @inheritDoc
+ ///
+ /// True on the desktop, where setNativeCommands below builds a real Swing JMenuBar
+ /// (which becomes the macOS screen menu). False elsewhere, including the phone-skinned
+ /// simulator, where there is no menu bar to put anything on and the commands belong in
+ /// whatever Codename One draws.
+ @Override
+ public boolean isNativeCommandsSupported() {
+ return isDesktop();
+ }
+
@Override
public void setNativeCommands(Vector commands) {
if (!isDesktopNativeChromeMode()) {
@@ -3062,7 +3081,7 @@ public static void setDesktopTitleBarMode(String mode) {
/// @return the configured desktop title-bar mode (defaults to {@code "toolbar"}).
public static String getDesktopTitleBarModeSetting() {
- return desktopTitleBarMode;
+ return desktopTitleBarMode == null ? "toolbar" : desktopTitleBarMode;
}
/// The desktop title-bar mode core consults to decide whether to suppress the CN1 Toolbar.
@@ -3090,9 +3109,26 @@ public static boolean isDesktopInteractiveScrollbars() {
/// Resolves the effective desktop title-bar mode, honoring the
/// {@code codename1.arg.desktop.titleBar} system property fallback.
private String resolveDesktopTitleBarMode() {
+ String mode = configuredDesktopTitleBarMode();
+ return mode == null ? "toolbar" : mode;
+ }
+
+ /// The mode the project actually asked for, or null when it asked for nothing. Kept
+ /// separate from {@link #resolveDesktopTitleBarMode()} so the "unset" case survives all the
+ /// way to Form, where a desktop native theme's own constant gets to answer instead.
+ private static String configuredDesktopTitleBarMode() {
return System.getProperty("codename1.arg.desktop.titleBar", desktopTitleBarMode);
}
+ /// @inheritDoc
+ @Override
+ public String getConfiguredDesktopTitleBarMode() {
+ if (!isDesktop()) {
+ return null;
+ }
+ return configuredDesktopTitleBarMode();
+ }
+
/// @return true when running on the desktop with a title-bar mode that hides the CN1
/// Toolbar in favor of native chrome (native or custom).
boolean isDesktopNativeChromeMode() {
@@ -3166,8 +3202,11 @@ private void injectDesktopThemeConstants(Hashtable h) {
if (h == null || !isDesktop()) {
return;
}
- String mode = System.getProperty("codename1.arg.desktop.titleBar", desktopTitleBarMode);
+ String mode = configuredDesktopTitleBarMode();
if (mode != null && mode.length() > 0) {
+ // Only when the project asked. Injecting the default here would write
+ // "toolbar" over a desktop native theme's own constant and put the in-app
+ // Toolbar back on every screen the theme meant to hand to the window.
h.put("@desktopTitleBarMode", mode);
}
boolean interactive = desktopInteractiveScrollbars
diff --git a/Ports/LinuxPort/nativeSources/cn1_linux.h b/Ports/LinuxPort/nativeSources/cn1_linux.h
index fcb88a5d1f5..dad60492a33 100644
--- a/Ports/LinuxPort/nativeSources/cn1_linux.h
+++ b/Ports/LinuxPort/nativeSources/cn1_linux.h
@@ -95,7 +95,12 @@ typedef enum {
* under the cursor and the theme's hover rules are dead entries in the .res.
* Deliberately the same number the Windows port uses, so the two desktop wire
* protocols do not drift apart. */
- CN1_EVENT_POINTER_HOVER = 22
+ CN1_EVENT_POINTER_HOVER = 22,
+ /* A native menu bar item was chosen. keyCode carries the Codename One command id the
+ * Java side handed out in setNativeCommands. Queued like every other input so the
+ * command runs on the EDT rather than on the GTK thread. Same number as the Windows
+ * port's, so the two desktop wire protocols do not drift apart. */
+ CN1_EVENT_MENU_COMMAND = 23
} CN1EventType;
/* Fixed-point scale for the gesture keyCode field (see CN1_EVENT_PINCH). */
diff --git a/Ports/LinuxPort/nativeSources/cn1_linux_window.c b/Ports/LinuxPort/nativeSources/cn1_linux_window.c
index a81ac209b50..23f86db08ec 100644
--- a/Ports/LinuxPort/nativeSources/cn1_linux_window.c
+++ b/Ports/LinuxPort/nativeSources/cn1_linux_window.c
@@ -274,6 +274,12 @@ int cn1LinuxPopEvent(int* out) {
static GtkWidget* cn1Window = 0;
static GtkWidget* cn1DrawingArea = 0;
static GtkWidget* cn1Overlay = 0; /* GtkOverlay: drawing area + native widget layer */
+static GtkWidget* cn1RootBox = 0; /* GtkBox: optional menu bar above the overlay */
+static GtkWidget* cn1MenuBar = 0; /* the native menu bar, when commands published one */
+/* Created ONCE and reused. A fresh group per rebuild would leak one per published form and
+ * leave the window holding every group it had ever been given -- the menu items go away
+ * with the bar, but the groups themselves do not. */
+static GtkAccelGroup* cn1MenuAccels = 0;
static GtkWidget* cn1Fixed = 0; /* GtkFixed overlay hosting positioned native peers */
static GtkWidget* cn1AccessibilityFixed = 0; /* transparent GTK/ATK semantic hierarchy */
static CN1Graphics cn1WindowG; /* the on-screen / headless back buffer */
@@ -1014,7 +1020,14 @@ JAVA_VOID com_codename1_impl_linux_LinuxNative_initDisplay___java_lang_String_in
gtk_widget_set_opacity(cn1AccessibilityFixed, 0.01);
gtk_overlay_add_overlay(GTK_OVERLAY(cn1Overlay), cn1AccessibilityFixed);
gtk_overlay_set_overlay_pass_through(GTK_OVERLAY(cn1Overlay), cn1AccessibilityFixed, TRUE);
- gtk_container_add(GTK_CONTAINER(cn1Window), cn1Overlay);
+ /* A vertical box between the window and the overlay, so a menu bar has somewhere to go.
+ * It is created unconditionally and stays EMPTY until commands arrive: an application
+ * that publishes none packs nothing above the overlay, and a GtkBox with one child
+ * that expands is laid out exactly as the overlay was when it was the window's direct
+ * child. That is what keeps every existing screenshot byte-identical. */
+ cn1RootBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_box_pack_start(GTK_BOX(cn1RootBox), cn1Overlay, TRUE, TRUE, 0);
+ gtk_container_add(GTK_CONTAINER(cn1Window), cn1RootBox);
g_signal_connect(cn1DrawingArea, "draw", G_CALLBACK(cn1OnDraw), 0);
g_signal_connect(cn1DrawingArea, "configure-event", G_CALLBACK(cn1OnConfigure), 0);
@@ -1035,6 +1048,224 @@ JAVA_VOID com_codename1_impl_linux_LinuxNative_initDisplay___java_lang_String_in
cn1WindowOpen = 1;
}
+/* ------------------------------------------------------------- menu bar */
+
+/*
+ * The native menu bar.
+ *
+ * Commands arrive as one encoded row each, the format
+ * IOSImplementation.setNativeCommands writes for the macOS menu and
+ * WindowsImplementation writes for the Win32 one:
+ *
+ * "\t
*
*
{@code macos.themeMode} names it directly, the legacy macNative.
* spelling is accepted like every other setting here, and the cross platform
@@ -741,13 +741,15 @@ public String getThemeMode() {
// asked for the modern iOS look and still gets it.
mode = shared;
} else {
- // NOT aqua yet, and that is the same sequencing as the Windows and Linux
- // poms: making Aqua the default restyles every screen and reseeds this
- // port's committed screenshot baselines, which deserves its own review and
- // wants doing once, after the theme reaches its fidelity target. Until
- // then macos.themeMode=aqua selects it explicitly, which is what the
- // whitelist above is for and what the review asked for.
- mode = "modern";
+ // Aqua. This port had been defaulting to the modern iOS theme, which is an
+ // iPhone design language on a Mac -- and which it only ever did because the
+ // flip restyles every screen and reseeds this port's committed screenshot
+ // baselines. Those are reseeded in this same change, from the CI runner that
+ // captures them, which is what the deferral was waiting for.
+ //
+ // macos.themeMode still names any of the others explicitly, including
+ // "modern" for an application that wants the iOS look on a Mac.
+ mode = "aqua";
}
}
// Interpolated into generated Java source, so it is constrained to the
diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/builders/MacOSBuildHintsTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/builders/MacOSBuildHintsTest.java
index 8aea9e40c11..0ebfe6b71ba 100644
--- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/builders/MacOSBuildHintsTest.java
+++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/builders/MacOSBuildHintsTest.java
@@ -598,12 +598,20 @@ public void theBuildNumberHonoursTheLegacySpellingBetweenTheOtherTwo() {
/// so defaulting to it gave a new macOS port an iPhone 7 look and no dark
/// mode whatsoever, however carefully the application asked for one. That is
/// what made every *_dark screenshot come out light.
- @Test
- public void theNativeThemeDefaultsToModernAndIsConstrainedToKnownModes() {
- assertEquals("modern", parse(raw(), "p").getThemeMode());
+ ///
+ /// The unset default is now aqua rather than modern. "modern" was never right for a
+ /// Mac either -- it is the iOS 26 Liquid Glass theme, an iPhone design language on a
+ /// desktop -- and was only the default because flipping it reseeds this port's
+ /// committed screenshot baselines, which now happens alongside it.
+ @Test
+ public void theNativeThemeDefaultsToAquaAndIsConstrainedToKnownModes() {
+ // A macOS application gets the macOS design language unless it asks otherwise.
+ assertEquals("aqua", parse(raw(), "p").getThemeMode());
// aqua and native have to survive the whitelist or the hint cannot select the
// theme it names.
assertEquals("aqua", parse(raw("macos.themeMode", "aqua"), "p").getThemeMode());
+ // And the iOS looks are still reachable by name, for an application that wants one.
+ assertEquals("modern", parse(raw("macos.themeMode", "modern"), "p").getThemeMode());
assertEquals("native", parse(raw("macos.themeMode", "native"), "p").getThemeMode());
assertEquals("ios7", parse(raw("macos.themeMode", "ios7"), "p").getThemeMode());
assertEquals("ios7", parse(raw("macNative.themeMode", "ios7"), "p").getThemeMode());
diff --git a/maven/codenameone-maven-plugin/src/test/java/com/codename1/builders/MacOSStubThemeTest.java b/maven/codenameone-maven-plugin/src/test/java/com/codename1/builders/MacOSStubThemeTest.java
index 8db618a8629..11276d6d75f 100644
--- a/maven/codenameone-maven-plugin/src/test/java/com/codename1/builders/MacOSStubThemeTest.java
+++ b/maven/codenameone-maven-plugin/src/test/java/com/codename1/builders/MacOSStubThemeTest.java
@@ -54,10 +54,28 @@
class MacOSStubThemeTest {
@Test
- void theGeneratedStubInstallsTheModernThemeByDefault(@TempDir Path tmp) throws Exception {
+ void theGeneratedStubInstallsTheAquaThemeByDefault(@TempDir Path tmp) throws Exception {
String stub = generateStub(tmp, new HashMap());
- assertTrue(stub.contains("setIosMode(\"modern\")"),
- "the stub must select the only theme that declares @darkModeBool; got:\n" + stub);
+ assertTrue(stub.contains("setIosMode(\"aqua\")"),
+ "a macOS application gets the macOS design language unless it asks otherwise;"
+ + " got:\n" + stub);
+ }
+
+ /// The property the default is really guarding, asserted separately from the value.
+ ///
+ /// This test used to require "modern", and the reason was never that modern was right
+ /// for a Mac -- it is the iOS 26 theme -- but that iOS7Theme.res declares no
+ /// @darkModeBool, so a stub defaulting to it left the application with no dark mode
+ /// whatever it asked for. Aqua declares the constant too (asserted against the compiled
+ /// resource by DesktopNativeThemeContentTest), so the property survives the change of
+ /// value. Naming it here means the next person to move this default is told what it has
+ /// to keep, rather than reading a literal and guessing.
+ @Test
+ void theDefaultIsNeverTheThemeWithNoDarkMode(@TempDir Path tmp) throws Exception {
+ String stub = generateStub(tmp, new HashMap());
+ assertFalse(stub.contains("setIosMode(\"ios7\")"),
+ "iOS7Theme.res declares no @darkModeBool, so defaulting to it takes dark mode"
+ + " away from every application on this port");
}
@Test
@@ -94,6 +112,32 @@ void theThemeResourceReachesTheApplicationResources(@TempDir Path tmp) throws Ex
"and it stays staged for the signature gate, which reads that set");
}
+ /// The theme the port now DEFAULTS to has to reach the application too.
+ ///
+ /// installNativeTheme() asks for its resource by name at run time and falls back to the
+ /// legacy theme WITHOUT SAYING SO when the lookup returns null, so a default that is not
+ /// staged is a silent revert to the iOS 7 look. That was latent while the default was
+ /// modern and iOSModernTheme.res was the only theme anyone staged; it became load
+ /// bearing when the default moved to aqua.
+ @Test
+ void theAquaThemeReachesTheApplicationResourcesToo(@TempDir Path tmp) throws Exception {
+ File nativeSources = new File(tmp.toFile(), "nativeSources");
+ File buildinRes = new File(tmp.toFile(), "btres");
+ assertTrue(nativeSources.mkdirs() && buildinRes.mkdirs());
+ Files.write(new File(nativeSources, "MacOSAquaTheme.res").toPath(),
+ "aqua".getBytes(StandardCharsets.UTF_8));
+ Files.write(new File(nativeSources, "iOSModernTheme.res").toPath(),
+ "modern".getBytes(StandardCharsets.UTF_8));
+
+ MacOSNativeBuilder.stageThemeResources(nativeSources, buildinRes);
+
+ assertTrue(new File(buildinRes, "MacOSAquaTheme.res").isFile(),
+ "the default theme has to be an application resource or the port silently"
+ + " falls back to the legacy one");
+ assertTrue(new File(buildinRes, "iOSModernTheme.res").isFile(),
+ "and the theme macos.themeMode=modern names still has to be there");
+ }
+
/// An application shipping its own theme of the same name keeps it.
@Test
void anApplicationsOwnResourceIsNotOverwritten(@TempDir Path tmp) throws Exception {
diff --git a/maven/core-unittests/src/test/java/com/codename1/components/DesktopComponentsTest.java b/maven/core-unittests/src/test/java/com/codename1/components/DesktopComponentsTest.java
new file mode 100644
index 00000000000..4f551c765eb
--- /dev/null
+++ b/maven/core-unittests/src/test/java/com/codename1/components/DesktopComponentsTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
+package com.codename1.components;
+
+import com.codename1.junit.FormTest;
+import com.codename1.junit.UITestBase;
+import com.codename1.ui.Button;
+import com.codename1.ui.CheckBox;
+import com.codename1.ui.Component;
+import com.codename1.ui.Display;
+import com.codename1.ui.DisplayTest;
+import com.codename1.ui.Form;
+import com.codename1.ui.Label;
+import com.codename1.ui.events.ActionEvent;
+import com.codename1.ui.events.ActionListener;
+import com.codename1.ui.layouts.BorderLayout;
+import com.codename1.ui.layouts.BoxLayout;
+import com.codename1.ui.plaf.UIManager;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The three controls the desktop design languages have and Codename One did not:
+ * {@link Separator}, {@link GroupBox} and {@link Stepper}.
+ */
+class DesktopComponentsTest extends UITestBase {
+
+ // ---- Separator ---------------------------------------------------------------
+
+ @FormTest
+ void separatorIsNeverThinnerThanAPixel() {
+ // A theme is free to ask for a hairline, and a hairline rounds to zero millimetres
+ // worth of pixels on a dense screen. Zero would make the rule invisible rather than
+ // thin, and invisible is the one thing a separator must not be.
+ Hashtable props = new Hashtable();
+ props.put("@separatorThicknessMM", "0.0001");
+ UIManager.getInstance().addThemeProps(props);
+
+ Separator s = new Separator();
+ assertTrue(s.getThickness() >= 1, "a hairline must still occupy a pixel");
+ }
+
+ @FormTest
+ void separatorFallsBackWhenTheConstantIsMalformed() {
+ Hashtable props = new Hashtable();
+ props.put("@separatorThicknessMM", "not a number");
+ UIManager.getInstance().addThemeProps(props);
+
+ // Must not throw: this is read from paint, where an exception takes the whole form
+ // down rather than one rule.
+ assertEquals(1, new Separator().getThickness(), "a malformed constant falls back to 1px");
+ }
+
+ @FormTest
+ void separatorHonoursTheThemeConstant() {
+ Hashtable props = new Hashtable();
+ props.put("@separatorThicknessMM", "2");
+ UIManager.getInstance().addThemeProps(props);
+
+ int expected = Display.getInstance().convertToPixels(2f);
+ assertEquals(Math.max(1, expected), new Separator().getThickness());
+ }
+
+ @FormTest
+ void separatorIsNotFocusable() {
+ // Decoration. Stopping on it with Tab would be a bug on every platform, and the
+ // desktop traversal filter is focusability, so this is what keeps it out.
+ assertFalse(new Separator().isFocusable(), "a rule must not take focus");
+ }
+
+ @FormTest
+ void separatorPrefersItsThicknessOnTheRightAxis() {
+ Hashtable props = new Hashtable();
+ props.put("@separatorThicknessMM", "2");
+ UIManager.getInstance().addThemeProps(props);
+
+ Separator horizontal = new Separator(Separator.HORIZONTAL);
+ Separator vertical = new Separator(Separator.VERTICAL);
+ Form f = new Form("Rules", BoxLayout.y());
+ f.add(horizontal).add(vertical);
+ f.show();
+ DisplayTest.flushEdt();
+
+ assertTrue(horizontal.getPreferredH() >= horizontal.getThickness(),
+ "a horizontal rule is as tall as it is thick");
+ assertTrue(vertical.getPreferredW() >= vertical.getThickness(),
+ "a vertical rule is as wide as it is thick");
+ }
+
+ // ---- GroupBox ----------------------------------------------------------------
+
+ @FormTest
+ void groupBoxAddsIntoItsContentPane() {
+ // Container.add(Component) is final and delegates to addComponent, which is what the
+ // routing overrides. If that ever stops being true this is the test that says so.
+ GroupBox g = new GroupBox("Appearance");
+ CheckBox accent = new CheckBox("Use the system accent colour");
+ g.add(accent);
+
+ assertSame(g.getContentPane(), accent.getParent(),
+ "an ordinary add belongs to the group, not beside the caption");
+ }
+
+ @FormTest
+ void groupBoxRoutesAConstrainedAddIntoTheContentPane() {
+ GroupBox g = new GroupBox("Layout", new BorderLayout());
+ Label south = new Label("bottom");
+ g.add(BorderLayout.SOUTH, south);
+
+ assertSame(g.getContentPane(), south.getParent(),
+ "BorderLayout.SOUTH means 'below the other controls in this group'");
+ }
+
+ @FormTest
+ void groupBoxWithNoCaptionReservesNoStrip() {
+ GroupBox g = new GroupBox();
+ assertTrue(g.getTitleComponent().isHidden(),
+ "an untitled group is a plain box, not a box with a blank strip");
+
+ g.setTitle("Now titled");
+ assertFalse(g.getTitleComponent().isHidden(), "setting a caption brings the strip back");
+
+ g.setTitle("");
+ assertTrue(g.getTitleComponent().isHidden(), "and clearing it takes the strip away again");
+ }
+
+ @FormTest
+ void groupBoxRemoveAllKeepsItsOwnStructure() {
+ GroupBox g = new GroupBox("Privacy");
+ g.add(new CheckBox("One")).add(new CheckBox("Two"));
+ g.removeAll();
+
+ assertEquals(0, g.getContentPane().getComponentCount(), "the grouped controls are gone");
+ assertSame(g, g.getTitleComponent().getParent(), "the caption is not one of them");
+ assertSame(g, g.getContentPane().getParent(), "and neither is the content pane");
+ }
+
+ // ---- Stepper -----------------------------------------------------------------
+
+ @FormTest
+ void stepperClampsToItsRange() {
+ Stepper s = new Stepper(5, 1, 10);
+ s.setValue(99);
+ assertEquals(10, s.getValue(), "a value above the range clamps to the maximum");
+ s.setValue(-99);
+ assertEquals(1, s.getValue(), "and below it clamps to the minimum");
+ }
+
+ @FormTest
+ void stepperConstructorClampsTheInitialValue() {
+ assertEquals(10, new Stepper(50, 1, 10).getValue());
+ assertEquals(1, new Stepper(-50, 1, 10).getValue());
+ }
+
+ @FormTest
+ void stepperFiresOnlyWhenTheValueMoved() {
+ final List seen = new ArrayList();
+ final Stepper s = new Stepper(5, 1, 10);
+ s.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ seen.add(Integer.valueOf(s.getValue()));
+ }
+ });
+
+ s.setValue(6);
+ s.setValue(6);
+ s.setValue(10);
+ s.setValue(99);
+
+ assertEquals(2, seen.size(), "setting the same value twice is not a change, and neither"
+ + " is clamping to a bound the value already sat on");
+ assertEquals(Integer.valueOf(6), seen.get(0));
+ assertEquals(Integer.valueOf(10), seen.get(1));
+ }
+
+ @FormTest
+ void stepperButtonsStepAndGreyOutAtTheBounds() {
+ Stepper s = new Stepper(2, 1, 3);
+ Button down = s.getDecrementButton();
+ Button up = s.getIncrementButton();
+ assertTrue(down.isEnabled() && up.isEnabled(), "mid-range both halves work");
+
+ up.released();
+ assertEquals(3, s.getValue());
+ assertFalse(up.isEnabled(), "at the maximum the half that would step past it greys out");
+ assertTrue(down.isEnabled());
+
+ down.released();
+ down.released();
+ assertEquals(1, s.getValue());
+ assertFalse(down.isEnabled(), "and likewise at the minimum");
+ assertTrue(up.isEnabled());
+ }
+
+ @FormTest
+ void stepperHonoursItsStep() {
+ Stepper s = new Stepper(0, 0, 100);
+ s.setStep(25);
+ s.getIncrementButton().released();
+ assertEquals(25, s.getValue());
+ assertThrows(IllegalArgumentException.class, () -> s.setStep(0),
+ "a zero step is a button that does nothing, which is a bug not a configuration");
+ }
+
+ @FormTest
+ void stepperRejectsAnInvertedRange() {
+ assertThrows(IllegalArgumentException.class, () -> new Stepper(0, 10, 1));
+ assertThrows(IllegalArgumentException.class, () -> new Stepper(0, 0, 10).setRange(10, 1));
+ }
+
+ @FormTest
+ void stepperTakesTypedTextAndCorrectsWhatItCannotProduce() {
+ Stepper s = new Stepper(5, 1, 10);
+ s.getField().setText("7");
+ assertEquals(7, s.getValue(), "a typed number in range becomes the value");
+ assertEquals("7", s.getField().getText(), "and the field is left as typed");
+
+ s.getField().setText("99");
+ assertEquals(10, s.getValue(), "out of range clamps");
+ assertEquals("10", s.getField().getText(),
+ "and the field is corrected, because the number under the caret is not one"
+ + " this control can produce");
+ }
+
+ @FormTest
+ void stepperLeavesAnEmptyOrUnparseableFieldAlone() {
+ Stepper s = new Stepper(5, 1, 10);
+ s.getField().setText("");
+ assertEquals(5, s.getValue(), "clearing the field to retype it must not reset the value");
+ assertEquals("", s.getField().getText(), "and must not fill itself in under the caret");
+
+ s.getField().setText("abc");
+ assertEquals(5, s.getValue(), "nor does text that is not a number");
+ }
+
+ @FormTest
+ void stepperPartsAreReachableByTheDesktopKeyboard() {
+ // The composite is three focusable controls, which is what a desktop user tabs
+ // through. Asserted because the parts are built internally: nothing else would
+ // notice if one of them stopped being focusable.
+ Stepper s = new Stepper(5, 1, 10);
+ Form f = new Form("Stepper", BoxLayout.y());
+ f.add(s);
+ f.show();
+ DisplayTest.flushEdt();
+
+ assertTrue(s.getField().isFocusable(), "the field takes focus");
+ assertTrue(s.getDecrementButton().isFocusable(), "so does the decrement button");
+ assertTrue(s.getIncrementButton().isFocusable(), "and the increment button");
+ }
+}
diff --git a/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java b/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java
index b40a40b1895..673e0d337a6 100644
--- a/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java
+++ b/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java
@@ -612,11 +612,51 @@ public void setDesktopTitleBarMode(String mode) {
this.desktopTitleBarMode = mode;
}
+ /// Mirrors the real ports' split between "the project asked for this" and "this is what the
+ /// platform answers when nobody asked", which is the distinction a theme constant is allowed
+ /// to fill. Null unless a test sets it, so every existing test keeps reaching
+ /// getDesktopTitleBarMode as before.
+ @Override
+ public String getConfiguredDesktopTitleBarMode() {
+ return configuredDesktopTitleBarMode;
+ }
+
+ public void setConfiguredDesktopTitleBarMode(String mode) {
+ this.configuredDesktopTitleBarMode = mode;
+ }
+
+ private String configuredDesktopTitleBarMode;
+
+ @Override
+ public boolean isShiftKeyDown() {
+ return shiftKeyDown;
+ }
+
+ public void setShiftKeyDown(boolean shiftKeyDown) {
+ this.shiftKeyDown = shiftKeyDown;
+ }
+
+ private boolean shiftKeyDown;
+
@Override
public void setNativeCommands(java.util.Vector commands) {
this.lastNativeCommands = commands;
}
+ /// Defaults TRUE so the existing desktop-chrome tests, which were written when every
+ /// implementation was assumed to have a menu bar, keep asserting what they always did.
+ /// The fallback tests set it false, which is what a port with no native menu reports.
+ @Override
+ public boolean isNativeCommandsSupported() {
+ return nativeCommandsSupported;
+ }
+
+ public void setNativeCommandsSupported(boolean nativeCommandsSupported) {
+ this.nativeCommandsSupported = nativeCommandsSupported;
+ }
+
+ private boolean nativeCommandsSupported = true;
+
/** @return the commands last pushed via setNativeCommands, for desktop-chrome assertions. */
public java.util.Vector getLastNativeCommands() {
return lastNativeCommands;
@@ -1453,6 +1493,9 @@ public void reset() {
desktop = false;
nativeTitle = false;
desktopTitleBarMode = "toolbar";
+ configuredDesktopTitleBarMode = null;
+ shiftKeyDown = false;
+ nativeCommandsSupported = true;
lastNativeCommands = null;
clearFileSystem();
clearSockets();
diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/ContextMenuTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/ContextMenuTest.java
new file mode 100644
index 00000000000..0aaebb3a635
--- /dev/null
+++ b/maven/core-unittests/src/test/java/com/codename1/ui/ContextMenuTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
+package com.codename1.ui;
+
+import com.codename1.junit.FormTest;
+import com.codename1.junit.UITestBase;
+import com.codename1.ui.events.ActionEvent;
+import com.codename1.ui.events.ActionListener;
+import com.codename1.ui.layouts.BoxLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The right-click menu. Codename One has fired the context-menu EVENT for a long time and
+ * has never had anything that turns it into a menu.
+ */
+class ContextMenuTest extends UITestBase {
+
+ private Command named(String name, final List log) {
+ return new Command(name) {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ log.add(getCommandName());
+ }
+ };
+ }
+
+ @FormTest
+ void componentCommandsOpenTheMenuWithNoListener() {
+ List log = new ArrayList();
+ Label target = new Label("Right click me");
+ target.setContextMenuCommands(named("Cut", log), named("Copy", log));
+
+ Form f = new Form("Menu", BoxLayout.y());
+ f.add(target);
+ f.show();
+ DisplayTest.flushEdt();
+
+ assertSame(target, target.resolveContextMenuOwner(),
+ "a component with commands handles its own context menu request");
+ }
+
+ @FormTest
+ void aComponentWithNoCommandsAndNoListenerHandlesNothing() {
+ Label target = new Label("Plain");
+ Form f = new Form("Menu", BoxLayout.y());
+ f.add(target);
+ f.show();
+ DisplayTest.flushEdt();
+
+ assertNull(target.resolveContextMenuOwner(),
+ "nothing to show means the request is not handled, so a long press still"
+ + " reaches the component underneath");
+ assertFalse(target.fireContextMenu(10, 10),
+ "and fireContextMenu says so, which is what leaves the long press to the"
+ + " component underneath");
+ }
+
+ @FormTest
+ void aConsumingListenerWinsOverTheCommands() {
+ List log = new ArrayList();
+ final boolean[] listenerRan = new boolean[1];
+ Label target = new Label("Both");
+ target.setContextMenuCommands(named("Cut", log));
+ target.addContextMenuListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ listenerRan[0] = true;
+ evt.consume();
+ }
+ });
+
+ Form f = new Form("Menu", BoxLayout.y());
+ f.add(target);
+ f.show();
+ DisplayTest.flushEdt();
+
+ assertTrue(target.fireContextMenu(10, 10));
+ assertTrue(listenerRan[0], "the listener is asked first");
+ }
+
+ @FormTest
+ void theNearestAncestorWithEitherAnswers() {
+ // A row inside a table that has its own commands must not be overruled by the
+ // table's listener declining to consume -- both are resolved in one walk.
+ List log = new ArrayList();
+ final boolean[] outerListenerRan = new boolean[1];
+
+ Container outer = new Container(BoxLayout.y());
+ outer.addContextMenuListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ outerListenerRan[0] = true;
+ // deliberately does not consume
+ }
+ });
+ Label inner = new Label("Row");
+ inner.setContextMenuCommands(named("Delete row", log));
+ outer.add(inner);
+
+ Form f = new Form("Menu", BoxLayout.y());
+ f.add(outer);
+ f.show();
+ DisplayTest.flushEdt();
+
+ assertSame(inner, inner.resolveContextMenuOwner(), "the row's own commands answer");
+ assertFalse(outerListenerRan[0],
+ "and nothing has climbed to the container to ask it");
+ }
+
+ @FormTest
+ void theWalkClimbsToAnAncestorWhenTheComponentHasNoMenuOfItsOwn() {
+ List log = new ArrayList();
+ Container outer = new Container(BoxLayout.y());
+ outer.setContextMenuCommands(named("Paste", log));
+ Label inner = new Label("No menu of its own");
+ outer.add(inner);
+
+ Form f = new Form("Menu", BoxLayout.y());
+ f.add(outer);
+ f.show();
+ DisplayTest.flushEdt();
+
+ assertSame(outer, inner.resolveContextMenuOwner(),
+ "a right click on a plain child opens the container's menu");
+ }
+
+ @FormTest
+ void settingCommandsToNullRemovesTheMenu() {
+ List log = new ArrayList();
+ Label target = new Label("Toggle");
+ target.setContextMenuCommands(named("Cut", log));
+ assertNotNull(target.getContextMenuCommands());
+
+ target.setContextMenuCommands((Command[]) null);
+ assertNull(target.getContextMenuCommands(), "null removes the menu");
+
+ target.setContextMenuCommands(new Command[0]);
+ assertNull(target.getContextMenuCommands(),
+ "and so does an empty array -- an empty menu is a rectangle the user has to"
+ + " dismiss to learn it was empty");
+ }
+
+ @FormTest
+ void theCommandArrayIsCopiedInBothDirections() {
+ List log = new ArrayList();
+ Command cut = named("Cut", log);
+ Command[] given = {cut};
+ Label target = new Label("Copy me");
+ target.setContextMenuCommands(given);
+
+ given[0] = named("Replaced", log);
+ assertSame(cut, target.getContextMenuCommands()[0],
+ "mutating the caller's array must not rewrite the menu");
+
+ Command[] read = target.getContextMenuCommands();
+ read[0] = named("Replaced again", log);
+ assertSame(cut, target.getContextMenuCommands()[0],
+ "and mutating what getContextMenuCommands returned must not either");
+ }
+
+ @FormTest
+ void showRefusesToOpenAnEmptyMenu() {
+ Label target = new Label("Anchor");
+ Form f = new Form("Menu", BoxLayout.y());
+ f.add(target);
+ f.show();
+ DisplayTest.flushEdt();
+
+ assertNull(ContextMenu.show(target, 1, 1), "no commands opens nothing");
+ assertNull(ContextMenu.show(target, 1, 1, (Command[]) null));
+ assertNull(ContextMenu.show(null, 1, 1, new Command("X")), "and neither does no anchor");
+ }
+}
diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/DesktopChromeTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/DesktopChromeTest.java
index 9aa65df6e3a..fe20d80d3c7 100644
--- a/maven/core-unittests/src/test/java/com/codename1/ui/DesktopChromeTest.java
+++ b/maven/core-unittests/src/test/java/com/codename1/ui/DesktopChromeTest.java
@@ -1,3 +1,25 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
package com.codename1.ui;
import com.codename1.junit.FormTest;
@@ -62,6 +84,92 @@ void customModeKeepsToolbarAsTitleBarAndBridgesCommands() {
assertTrue(bridged.contains(save), "the side-menu command must be in the native menu set");
}
+ /**
+ * A port whose {@code setNativeCommands} discards them must keep drawing the Toolbar.
+ *
+ *
{@code MenuBar.updateCommands} used to call {@code setNativeCommands} and RETURN
+ * whenever the behavior was NATIVE -- drawing no soft buttons, because on a platform with
+ * a real menu bar drawing them too would duplicate every command. On a platform without
+ * one, that meant the commands went to a method that discards them and were never drawn
+ * at all. Silently: nothing in that path can tell "handled natively" from "dropped".
+ *
+ *
Latent until a theme asked for it, which the desktop native themes now do --
+ * {@code commandBehavior: Native}, right for the platforms they model and not yet
+ * honourable by the Windows and Linux ports.
+ */
+ @FormTest
+ void aPortWithNoNativeMenuBarKeepsTheToolbar() {
+ desktopMode("native");
+ implementation.setNativeCommandsSupported(false);
+
+ Form f = new Form("No native menu");
+ Command save = new Command("Save");
+ f.addCommand(save);
+ f.show();
+ DisplayTest.flushEdt();
+
+ assertNotNull(f.getToolbar().getParent(),
+ "hiding the toolbar would take away the only place these commands are drawn");
+ assertTrue(f.getToolbar().getAllNativeMenuCommands().contains(save),
+ "and the command is still in it");
+ }
+
+ @FormTest
+ void aPortWithANativeMenuBarStillHidesTheToolbar() {
+ // The other direction, so the guard above cannot silently disable native mode
+ // everywhere: this is the case DesktopChromeTest's first test already covers, kept
+ // beside its opposite.
+ desktopMode("native");
+ implementation.setNativeCommandsSupported(true);
+
+ Form f = new Form("Native menu");
+ f.addCommand(new Command("Save"));
+ f.show();
+ DisplayTest.flushEdt();
+
+ assertNull(f.getToolbar().getParent(),
+ "with somewhere for the commands to go, the toolbar is hidden as before");
+ }
+
+ /**
+ * The same guard one level down, in {@code MenuBar.updateCommands}, which is the path a
+ * form takes when the application sets {@code Display.COMMAND_BEHAVIOR_NATIVE} directly
+ * rather than through the desktop title-bar mode.
+ */
+ @FormTest
+ void nativeCommandBehaviourStillDrawsSoftButtonsWithNoNativeMenuBar() {
+ implementation.setDesktop(true);
+ implementation.setNativeCommandsSupported(false);
+ Toolbar.setGlobalToolbar(false);
+ Display.getInstance().setCommandBehavior(Display.COMMAND_BEHAVIOR_NATIVE);
+ try {
+ Form f = new Form("Soft buttons");
+ Command save = new Command("Save");
+ f.addCommand(save);
+ f.show();
+ DisplayTest.flushEdt();
+
+ assertTrue(hasButtonLabelled(f, "Save"),
+ "with nowhere native to put it, the command has to be drawn");
+ } finally {
+ Display.getInstance().setCommandBehavior(Display.COMMAND_BEHAVIOR_DEFAULT);
+ Toolbar.setGlobalToolbar(true);
+ }
+ }
+
+ private boolean hasButtonLabelled(Container root, String text) {
+ for (int iter = 0; iter < root.getComponentCount(); iter++) {
+ Component c = root.getComponentAt(iter);
+ if (c instanceof Button && text.equals(((Button) c).getText())) {
+ return true;
+ }
+ if (c instanceof Container && hasButtonLabelled((Container) c, text)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@FormTest
void desktopShortcutHintRoundTrips() {
Command save = new Command("Save");
diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/DesktopKeyboardConventionsTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/DesktopKeyboardConventionsTest.java
new file mode 100644
index 00000000000..da57f946953
--- /dev/null
+++ b/maven/core-unittests/src/test/java/com/codename1/ui/DesktopKeyboardConventionsTest.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
+package com.codename1.ui;
+
+import com.codename1.junit.FormTest;
+import com.codename1.junit.UITestBase;
+import com.codename1.ui.events.ActionEvent;
+import com.codename1.ui.layouts.BoxLayout;
+import com.codename1.ui.plaf.UIManager;
+
+import java.util.Hashtable;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The keyboard conventions a desktop toolkit has and Codename One did not: Tab and Shift-Tab
+ * move focus, and Escape cancels. The traversal order itself is old ({@code TabIterator},
+ * {@code getNextComponent}); what is new is that a key is wired to it.
+ *
+ *
Every case here is also asserted in its mobile form, because the whole feature is gated on
+ * {@code isDesktop()} and a gate nobody tests in both directions is not a gate.
+ */
+class DesktopKeyboardConventionsTest extends UITestBase {
+
+ /** Tab, as a port delivers it: the character code, not an AWT virtual key. */
+ private static final int KEY_TAB = 9;
+
+ /** Escape, likewise. */
+ private static final int KEY_ESCAPE = 27;
+
+ private Form threeButtonForm() {
+ Form f = new Form("Keys", new BoxLayout(BoxLayout.Y_AXIS));
+ f.add(new Button("One")).add(new Button("Two")).add(new Button("Three"));
+ return f;
+ }
+
+ private Button button(Form f, int index) {
+ return (Button) f.getContentPane().getComponentAt(index);
+ }
+
+ @FormTest
+ void tabMovesFocusForwardOnDesktop() {
+ implementation.setDesktop(true);
+ Form f = threeButtonForm();
+ f.show();
+ DisplayTest.flushEdt();
+
+ f.setFocused(button(f, 0));
+ f.keyPressed(KEY_TAB);
+ DisplayTest.flushEdt();
+
+ assertSame(button(f, 1), f.getFocused(), "Tab must advance focus to the next component");
+ }
+
+ @FormTest
+ void shiftTabMovesFocusBackwardOnDesktop() {
+ implementation.setDesktop(true);
+ implementation.setShiftKeyDown(true);
+ Form f = threeButtonForm();
+ f.show();
+ DisplayTest.flushEdt();
+
+ f.setFocused(button(f, 2));
+ f.keyPressed(KEY_TAB);
+ DisplayTest.flushEdt();
+
+ assertSame(button(f, 1), f.getFocused(), "Shift-Tab must walk focus backwards");
+ }
+
+ @FormTest
+ void tabIsInertOnMobile() {
+ // The mobile branch is the one that must not move, because every existing screenshot
+ // baseline on every phone port was captured with Tab doing nothing.
+ implementation.setDesktop(false);
+ Form f = threeButtonForm();
+ f.show();
+ DisplayTest.flushEdt();
+
+ Button first = button(f, 0);
+ f.setFocused(first);
+ f.keyPressed(KEY_TAB);
+ DisplayTest.flushEdt();
+
+ assertSame(first, f.getFocused(), "Tab must not traverse focus on a phone");
+ }
+
+ @FormTest
+ void escapeFiresTheFormsBackCommand() {
+ implementation.setDesktop(true);
+ Form f = threeButtonForm();
+ final boolean[] fired = new boolean[1];
+ Command back = new Command("Back") {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ fired[0] = true;
+ }
+ };
+ f.setBackCommand(back);
+ f.show();
+ DisplayTest.flushEdt();
+
+ f.keyPressed(KEY_ESCAPE);
+ DisplayTest.flushEdt();
+
+ assertTrue(fired[0], "Escape must fire the back command on the desktop");
+ }
+
+ @FormTest
+ void escapeWithoutABackCommandDoesNothing() {
+ // Deliberate: Escape must never be able to exit an application, so a form with nothing
+ // to cancel simply ignores it rather than falling through to any exit path.
+ implementation.setDesktop(true);
+ Form f = threeButtonForm();
+ f.show();
+ DisplayTest.flushEdt();
+
+ Button first = button(f, 0);
+ f.setFocused(first);
+ f.keyPressed(KEY_ESCAPE);
+ DisplayTest.flushEdt();
+
+ assertSame(f, Display.getInstance().getCurrent(), "the form must still be showing");
+ assertSame(first, f.getFocused(), "and focus must be where it was");
+ }
+
+ @FormTest
+ void escapeIsInertOnMobile() {
+ implementation.setDesktop(false);
+ Form f = threeButtonForm();
+ final boolean[] fired = new boolean[1];
+ Command back = new Command("Back") {
+ @Override
+ public void actionPerformed(ActionEvent evt) {
+ fired[0] = true;
+ }
+ };
+ f.setBackCommand(back);
+ f.show();
+ DisplayTest.flushEdt();
+
+ f.keyPressed(KEY_ESCAPE);
+ DisplayTest.flushEdt();
+
+ assertFalse(fired[0], "Escape is a desktop convention and must stay inert on a phone");
+ }
+
+ @FormTest
+ void escapeDisposesADialogWithNoBackCommand() {
+ implementation.setDesktop(true);
+ Form host = threeButtonForm();
+ host.show();
+ DisplayTest.flushEdt();
+
+ Dialog d = new Dialog("Confirm");
+ d.add(new Label("Body"));
+ d.setDisposeWhenPointerOutOfBounds(false);
+ d.showModeless();
+ DisplayTest.flushEdt();
+
+ d.keyPressed(KEY_ESCAPE);
+ DisplayTest.flushEdt();
+
+ assertFalse(d.isVisible() && d.getParent() != null,
+ "Escape must close a dialog that has nothing else to cancel");
+ }
+
+ /**
+ * The theme constant only speaks when the project did not. Windows and macOS themes carry
+ * {@code native}, GNOME carries {@code custom}, and a project that spelled out
+ * {@code desktop.titleBar} must still win.
+ */
+ @FormTest
+ void themeConstantSuppliesTheTitleBarModeWhenNothingElseDid() {
+ implementation.setDesktop(true);
+ Hashtable props = new Hashtable();
+ props.put("@desktopTitleBarMode", "native");
+ UIManager.getInstance().addThemeProps(props);
+ Toolbar.setGlobalToolbar(true);
+
+ Form f = new Form("Themed");
+ f.show();
+ DisplayTest.flushEdt();
+
+ assertEquals("native", f.getDesktopTitleBarMode(),
+ "with no build hint the installed theme's constant must be read");
+ }
+
+ @FormTest
+ void aConfiguredModeOutranksTheThemeConstant() {
+ implementation.setDesktop(true);
+ implementation.setConfiguredDesktopTitleBarMode("toolbar");
+ Hashtable props = new Hashtable();
+ props.put("@desktopTitleBarMode", "native");
+ UIManager.getInstance().addThemeProps(props);
+ Toolbar.setGlobalToolbar(true);
+
+ Form f = new Form("Themed");
+ f.show();
+ DisplayTest.flushEdt();
+
+ assertEquals("toolbar", f.getDesktopTitleBarMode(),
+ "a project that asked for the legacy look must keep it");
+ }
+
+ @FormTest
+ void themeConstantIsIgnoredOffTheDesktop() {
+ implementation.setDesktop(false);
+ Hashtable props = new Hashtable();
+ props.put("@desktopTitleBarMode", "native");
+ UIManager.getInstance().addThemeProps(props);
+
+ Form f = new Form("Themed");
+ f.show();
+ DisplayTest.flushEdt();
+
+ assertEquals("toolbar", f.getDesktopTitleBarMode(),
+ "a phone that somehow loaded a desktop theme still draws its own chrome");
+ }
+
+ /**
+ * The four interactive-scrollbar UIIDs are picked by {@code LookAndFeel.initScroll} but were
+ * seeded by nothing, so a theme that turned the constant on without defining all four drew a
+ * track and a thumb out of the blank default style -- an invisible scrollbar that still
+ * reserved its gutter, with nothing reporting a problem.
+ */
+ @FormTest
+ void theInteractiveScrollbarUiidsAreSeeded() {
+ UIManager m = UIManager.getInstance();
+ assertNotNull(m.getComponentStyle("DesktopScroll"), "DesktopScroll must have a style");
+ assertNotNull(m.getComponentStyle("DesktopScrollThumb"), "DesktopScrollThumb must have a style");
+ assertNotNull(m.getComponentStyle("DesktopHorizontalScroll"),
+ "DesktopHorizontalScroll must have a style");
+ assertNotNull(m.getComponentStyle("DesktopHorizontalScrollThumb"),
+ "DesktopHorizontalScrollThumb must have a style");
+
+ assertTrue(m.getComponentStyle("DesktopScroll").getPaddingRight(false) > 0,
+ "the vertical track needs a gutter wide enough to grab");
+ assertTrue(m.getComponentStyle("DesktopHorizontalScroll").getPaddingTop() > 0,
+ "the horizontal track needs one too");
+ assertNotNull(m.getComponentSelectedStyle("DesktopScrollThumb"),
+ "the thumb needs a hover (selected) style to highlight with");
+ assertNotNull(m.getComponentCustomStyle("DesktopScrollThumb", "press"),
+ "and a pressed style to highlight with while dragged");
+ }
+}
diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/FrameworkChromeNeverWindowsTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/FrameworkChromeNeverWindowsTest.java
new file mode 100644
index 00000000000..b7e3f000e29
--- /dev/null
+++ b/maven/core-unittests/src/test/java/com/codename1/ui/FrameworkChromeNeverWindowsTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
+package com.codename1.ui;
+
+import com.codename1.junit.UITestBase;
+import com.codename1.ui.plaf.UIManager;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Hashtable;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Framework chrome must never become an operating system window, however the application or
+ * its theme has set the default.
+ *
+ *
These popups are POSITIONED by the framework -- placed relative to the surface they
+ * belong to -- and native window mode documents exactly those margins as ignored. In a
+ * window they come out centred and lose the placement that is their whole point.
+ *
+ *
Not hypothetical. The desktop themes set {@code defaultNativeWindowModeBool}, and the
+ * Windows port's screenshot suite then captured two of {@code LightweightPickerButtons}'
+ * four placements, found the two it did capture byte-identical, and timed out waiting for
+ * the rest. {@code ComboBox} and {@code InfiniteProgress} had already opted out by hand, so
+ * the hazard was known -- it had simply never been reachable, because nothing defaulted the
+ * mode to true.
+ *
+ *
Why this reads source. The obvious runtime test does not work and it is worth
+ * saying why, because it looked like it did: reaching one of these popups means pressing the
+ * control that owns it, which parks the caller, so the popup does not exist while the test
+ * can look at the hierarchy. A first version walked the current form and passed with every
+ * fix removed -- a probe that cannot fail. {@code AbstractDialog} is a closed interface whose
+ * own comment forbids new members, so there is no shared choke point to assert on either.
+ * What is left is the invariant itself: a framework class that builds a positioned popup says
+ * so at the point it builds it.
+ */
+public class FrameworkChromeNeverWindowsTest extends UITestBase {
+
+ /**
+ * Framework sources that build a popup they position themselves, and must therefore opt
+ * out of native window mode.
+ *
+ *
Deliberately a list rather than a scan of the whole tree: a dialog the APPLICATION
+ * shows should take the theme default, and {@code Dialog.show}, {@code MasterDetail} and
+ * {@code Oauth2} are exactly that. The distinction is whether the framework places it.
+ */
+ private static final String[] POSITIONED_POPUP_SOURCES = {
+ "com/codename1/ui/ComboBox.java",
+ "com/codename1/ui/ContextMenu.java",
+ "com/codename1/ui/TooltipManager.java",
+ "com/codename1/ui/Toolbar.java",
+ "com/codename1/ui/spinner/Picker.java",
+ "com/codename1/ui/validation/Validator.java",
+ "com/codename1/components/FloatingActionButton.java",
+ "com/codename1/components/InfiniteProgress.java",
+ };
+
+ /** Every construction of a dialog that would take the default if left alone. */
+ private static final Pattern CONSTRUCTS =
+ Pattern.compile("new\\s+(?:InteractionDialog|Dialog)\\s*\\(");
+
+ /**
+ * Comments, which are stripped before anything is counted.
+ *
+ *
Not defensive tidiness: {@code BubbleTransition} shows a dialog in a {@code ///}
+ * javadoc EXAMPLE, and counting that reported a source with no dialogs in it at all as
+ * an unclassified builder. A check whose findings a reader has to filter by hand stops
+ * being read.
+ */
+ private static final Pattern COMMENTS =
+ Pattern.compile("//[^\\n]*|/\\*.*?\\*/", Pattern.DOTALL);
+
+ private static String withoutComments(String src) {
+ return COMMENTS.matcher(src).replaceAll("");
+ }
+
+ private static final Pattern OPTS_OUT =
+ Pattern.compile("setNativeWindowMode\\(\\s*false\\s*\\)");
+
+ /**
+ * The setting has to actually be reachable, or every assertion here is vacuous.
+ */
+ @Test
+ public void theThemeConstantReallyTurnsTheDefaultOn() {
+ Hashtable props = new Hashtable();
+ props.put("@defaultNativeWindowModeBool", "true");
+ UIManager.getInstance().addThemeProps(props);
+ Dialog.setDefaultNativeWindowMode(true);
+ try {
+ assertTrue(new Dialog().isNativeWindowMode(),
+ "an ordinary dialog must take the default, or nothing below is a test");
+ Dialog opted = new Dialog();
+ opted.setNativeWindowMode(false);
+ assertFalse(opted.isNativeWindowMode(),
+ "and the per-instance opt-out must outrank it, which is what the framework"
+ + " popups rely on");
+ } finally {
+ Dialog.setDefaultNativeWindowMode(false);
+ }
+ }
+
+ /**
+ * Every framework source that builds a positioned popup opts out at least as many times
+ * as it builds one.
+ *
+ *
Counting rather than merely requiring one occurrence: {@code Toolbar} builds two
+ * side menus and {@code Picker} builds both a modal dialog and the lightweight popup, and
+ * the bug that started this was the SECOND of a pair being missed.
+ */
+ @Test
+ public void everyFrameworkPositionedPopupOptsOut() throws IOException {
+ File root = locateCoreSources();
+ if (root == null) {
+ // The sources are not beside this checkout; nothing to assert rather than a
+ // failure a developer cannot act on.
+ return;
+ }
+ List problems = new ArrayList();
+ for (String rel : POSITIONED_POPUP_SOURCES) {
+ File f = new File(root, rel);
+ if (!f.isFile()) {
+ problems.add(rel + ": missing; the list is stale");
+ continue;
+ }
+ String src = withoutComments(
+ new String(Files.readAllBytes(f.toPath()), StandardCharsets.UTF_8));
+ int built = count(CONSTRUCTS, src);
+ int optedOut = count(OPTS_OUT, src);
+ if (built > optedOut) {
+ problems.add(rel + ": builds " + built + " dialog(s) and opts out " + optedOut
+ + " time(s); a framework-positioned popup that takes the theme default"
+ + " comes out centred in a window of its own");
+ }
+ }
+ if (!problems.isEmpty()) {
+ fail("framework chrome would become an operating system window:\n "
+ + String.join("\n ", problems));
+ }
+ }
+
+ /**
+ * The list above has to stay honest as the framework grows, so this fails when a NEW
+ * framework source starts building one of these and is not classified either way.
+ */
+ @Test
+ public void newFrameworkPopupsHaveToBeClassified() throws IOException {
+ File root = locateCoreSources();
+ if (root == null) {
+ return;
+ }
+ // Sources that build a dialog the APPLICATION owns, which correctly takes the
+ // default. Listed so that "not in either list" is a real answer rather than silence.
+ Set applicationOwned = new HashSet(Arrays.asList(
+ "com/codename1/ui/Dialog.java",
+ "com/codename1/ui/AbstractDialog.java",
+ "com/codename1/components/InteractionDialog.java",
+ "com/codename1/components/MasterDetail.java",
+ "com/codename1/io/Oauth2.java",
+ // Modal choosers and prompts the USER operates. Each is a thing a desktop
+ // would reasonably show in a window of its own, so taking the default is
+ // right: a file chooser, a crash report, a signature pad, a share sheet and
+ // a country list are dialogs, not chrome placed against a control.
+ "com/codename1/impl/CodenameOneImplementation.java",
+ "com/codename1/system/DefaultCrashReporter.java",
+ "com/codename1/components/SignatureComponent.java",
+ "com/codename1/components/ShareButton.java",
+ "com/codename1/components/PhoneNumberField.java"));
+
+ // Exempt by MECHANISM rather than by a call at the construction site. Dialog's own
+ // usesNativeWindow() already refuses a window for a menu, so MenuBar's popup is
+ // covered without saying so -- and listing it as application-owned would be a lie
+ // that the next reader would have to disprove.
+ Set exemptByMechanism = new HashSet(Arrays.asList(
+ "com/codename1/ui/MenuBar.java"));
+ applicationOwned.addAll(exemptByMechanism);
+ Set positioned = new HashSet(Arrays.asList(POSITIONED_POPUP_SOURCES));
+
+ List unclassified = new ArrayList();
+ collectDialogBuilders(root, root, positioned, applicationOwned, unclassified);
+ if (!unclassified.isEmpty()) {
+ fail("these framework sources build a Dialog or InteractionDialog and are in"
+ + " neither list; decide whether the framework positions it (opt out) or"
+ + " the application owns it (take the default):\n "
+ + String.join("\n ", unclassified));
+ }
+ }
+
+ private void collectDialogBuilders(File root, File dir, Set positioned,
+ Set applicationOwned, List out)
+ throws IOException {
+ File[] entries = dir.listFiles();
+ if (entries == null) {
+ return;
+ }
+ for (File f : entries) {
+ if (f.isDirectory()) {
+ collectDialogBuilders(root, f, positioned, applicationOwned, out);
+ continue;
+ }
+ if (!f.getName().endsWith(".java")) {
+ continue;
+ }
+ String rel = root.toPath().relativize(f.toPath()).toString().replace('\\', '/');
+ if (positioned.contains(rel) || applicationOwned.contains(rel)) {
+ continue;
+ }
+ String src = withoutComments(
+ new String(Files.readAllBytes(f.toPath()), StandardCharsets.UTF_8));
+ if (count(CONSTRUCTS, src) > 0) {
+ out.add(rel);
+ }
+ }
+ }
+
+ private static int count(Pattern p, String src) {
+ Matcher m = p.matcher(src);
+ int n = 0;
+ while (m.find()) {
+ n++;
+ }
+ return n;
+ }
+
+ /** Walks up for CodenameOne/src, the way the native-theme tests locate Themes/. */
+ private static File locateCoreSources() {
+ File cwd = new File(".").getAbsoluteFile();
+ for (int i = 0; i < 6 && cwd != null; i++) {
+ File candidate = new File(cwd, "CodenameOne/src");
+ if (candidate.isDirectory()) {
+ return candidate;
+ }
+ cwd = cwd.getParentFile();
+ }
+ return null;
+ }
+}
diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/OffscreenFormPaintTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/OffscreenFormPaintTest.java
new file mode 100644
index 00000000000..7773d65dfad
--- /dev/null
+++ b/maven/core-unittests/src/test/java/com/codename1/ui/OffscreenFormPaintTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
+package com.codename1.ui;
+
+import com.codename1.junit.FormTest;
+import com.codename1.junit.UITestBase;
+import com.codename1.ui.layouts.BorderLayout;
+import com.codename1.ui.layouts.BoxLayout;
+import com.codename1.ui.plaf.Style;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ *
A Form that is built off-screen and painted into an Image must paint its children, not
+ * just its own background.
+ *
+ *
This is how every animation filmstrip in the screenshot suite is captured: a Form is
+ * constructed, given a size, made visible, laid out and painted into an offscreen Image
+ * six times at different animation progresses. It is never shown. Ten such tests exist,
+ * and when this breaks all ten produce a grid of empty cells in the Form's background
+ * colour -- which is a picture, so the capture succeeds and only a human looking at it can
+ * tell that the content is gone.
+ *
+ *
The regression that prompted this was specific to desktop "native" title bar mode,
+ * where the Toolbar is deliberately never attached to the form. That path runs only when
+ * the platform reports a native menu bar, so the three knobs below are the condition --
+ * without them the default toolbar mode is exercised and nothing is proven.
+ */
+public class OffscreenFormPaintTest extends UITestBase {
+
+ private static final int TILE_COLOR = 0xef476f;
+
+ @FormTest
+ void aFormBuiltOffScreenPaintsItsChildrenInDesktopNativeTitleBarMode() {
+ implementation.setDesktop(true);
+ implementation.setNativeCommandsSupported(true);
+ implementation.setDesktopTitleBarMode("native");
+ assertTrue(paintOffScreenFormAndLookForTheTile(),
+ "A Form painted off-screen in desktop 'native' title bar mode painted its "
+ + "background but none of its children. Every animation filmstrip in the "
+ + "screenshot suite captures exactly this way, so the goldens become grids "
+ + "of empty cells that still look like valid screenshots.");
+ }
+
+ @FormTest
+ void aFormBuiltOffScreenPaintsItsChildrenInToolbarMode() {
+ // The control. If this one ever fails too, the fault is in off-screen painting
+ // generally rather than in the native title bar path, and the test above would
+ // otherwise point at the wrong thing.
+ implementation.setDesktop(true);
+ implementation.setNativeCommandsSupported(true);
+ implementation.setDesktopTitleBarMode("toolbar");
+ assertTrue(paintOffScreenFormAndLookForTheTile(),
+ "A Form painted off-screen in the default toolbar mode painted its "
+ + "background but none of its children.");
+ }
+
+ /**
+ * Builds and paints a Form the way AbstractContainerAnimationScreenshotTest does, and
+ * reports whether the child's colour reached the image.
+ *
+ * @return true when the tile was painted
+ */
+ private boolean paintOffScreenFormAndLookForTheTile() {
+ int width = 400;
+ int height = 300;
+
+ // Without this the Form has no Toolbar at all, Toolbar.initMenuBar never runs, and
+ // the desktop "native" branch under test is simply not reached -- the first version
+ // of this test passed in both modes for exactly that reason, which is a test that
+ // cannot fail rather than a behaviour that works. The screenshot suite's app turns
+ // the global toolbar on, so this also matches how those captures are produced.
+ Toolbar.setGlobalToolbar(true);
+
+ Form host = new Form("Off-screen host");
+ assertNotNull(host.getToolbar(),
+ "The Form under test has no Toolbar, so the title bar mode cannot matter and "
+ + "this test would pass whatever the mode does.");
+ host.setWidth(width);
+ host.setHeight(height);
+ // A Form is invisible until shown, and paintComponent is a no-op while it is. The
+ // filmstrip tests flip this for the same reason.
+ host.setVisible(true);
+ host.setLayout(new BorderLayout());
+
+ Container content = new Container(BoxLayout.y());
+ Style contentStyle = content.getAllStyles();
+ contentStyle.setBgColor(0xfafafa);
+ contentStyle.setBgTransparency(255);
+
+ Label tile = new Label("Tile");
+ Style tileStyle = tile.getAllStyles();
+ // Explicit and fully opaque, so the assertion is about whether the child was
+ // painted at all and never about what a theme would have coloured it.
+ tileStyle.setBgColor(TILE_COLOR);
+ tileStyle.setFgColor(0xffffff);
+ tileStyle.setBgTransparency(255);
+ tileStyle.setPaddingUnit(Style.UNIT_TYPE_PIXELS);
+ tileStyle.setPadding(12, 12, 12, 12);
+ content.add(tile);
+
+ host.add(BorderLayout.CENTER, content);
+ host.forceRevalidate();
+
+ Image frame = Image.createImage(width, height, 0xffffffff);
+ Graphics g = frame.getGraphics();
+ host.paintComponent(g, true);
+
+ int[] pixels = new int[width * height];
+ frame.getRGB(pixels, 0, 0, 0, width, height);
+ for (int i = 0; i < pixels.length; i++) {
+ if ((pixels[i] & 0xffffff) == TILE_COLOR) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/plaf/DesktopNativeThemeContentTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/plaf/DesktopNativeThemeContentTest.java
new file mode 100644
index 00000000000..7c3cbd081ad
--- /dev/null
+++ b/maven/core-unittests/src/test/java/com/codename1/ui/plaf/DesktopNativeThemeContentTest.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
+package com.codename1.ui.plaf;
+
+import com.codename1.junit.UITestBase;
+import com.codename1.ui.util.Resources;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.Hashtable;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Asserts that the three desktop native themes really carry the desktop chrome, by reading
+ * the compiled {@code .res} rather than the CSS.
+ *
+ *
This exists because of a failure mode this work already hit once: a theme edit that
+ * scored identically to no edit, because the class loader reached a stale copy. A test that
+ * reads the CSS would have been green for that too. Reading the built resource is the only
+ * check that says the edit survived the compiler AND landed in the file every consumer
+ * stages.
+ *
+ *
Skipped, not failed, when {@code Themes/} has not been built -- the same policy
+ * {@code NativeThemeBindingsTest} uses, so a checkout that has not run
+ * {@code scripts/build-native-themes.sh} does not report a failure it cannot fix. CI runs
+ * that script before the desktop legs.
+ */
+public class DesktopNativeThemeContentTest extends UITestBase {
+
+ private static final String[] DESKTOP_THEMES = {
+ "WindowsFluentTheme.res", "MacOSAquaTheme.res", "GnomeAdwaitaTheme.res",
+ };
+
+ /** The two behaviours that used to need a port-side hook and are now the theme's own. */
+ @Test
+ public void everyDesktopThemeTurnsOnTheDesktopBehaviours() throws Exception {
+ for (String name : DESKTOP_THEMES) {
+ Hashtable theme = loadTheme(name);
+ if (theme == null) {
+ return;
+ }
+ assertEquals("true", theme.get("@interactiveScrollBool"),
+ name + " must turn the fading touch indicator into a real scrollbar");
+ assertEquals("true", theme.get("@defaultNativeWindowModeBool"),
+ name + " must open dialogs as real operating system windows");
+ assertEquals("24", theme.get("@scrollThumbMinSizeInt"),
+ name + " must keep the thumb grabbable on long content");
+ assertNotNull(theme.get("@separatorThicknessMM"),
+ name + " must size the Separator rule");
+ }
+ }
+
+ /**
+ * The scrollbar the desktop actually draws. Until this change all three themes carried
+ * {@code DesktopScrollThumb { cn1-derive: ScrollThumb; }} and nothing else, which is a
+ * desktop scrollbar with no gutter and no highlight.
+ */
+ @Test
+ public void everyDesktopThemeStylesTheInteractiveScrollbar() throws Exception {
+ for (String name : DESKTOP_THEMES) {
+ Hashtable theme = loadTheme(name);
+ if (theme == null) {
+ return;
+ }
+ assertNotNull(theme.get("DesktopScroll.padding"),
+ name + ": the gutter width IS DesktopScroll's horizontal padding");
+ assertNotNull(theme.get("DesktopHorizontalScroll.padding"),
+ name + ": and the horizontal gutter is its vertical padding");
+
+ // The highlight is asserted as a DIFFERENCE, not as a key. cn1-derive emits the
+ // whole state family -- sel#, press#, dis# -- by copying the base, so
+ // `DesktopScrollThumb { cn1-derive: ScrollThumb; }` produces a sel#bgColor that
+ // is present, identical to the base, and therefore an invisible highlight. A
+ // non-null assertion passes on exactly the defect this replaces. Measured: the
+ // reverted theme emitted sel#bgColor = 8a8a8a against bgColor = 8a8a8a.
+ //
+ // Note also which states: sel# and press#, never hover#. LookAndFeel's
+ // InteractiveScrollThumb returns getSelectedStyle() under the pointer and
+ // getPressedStyle() while dragged.
+ assertNotNull(theme.get("DesktopScrollThumb.bgColor"),
+ name + ": the thumb needs a colour of its own, not the mobile one");
+ assertDiffers(theme, name, "DesktopScrollThumb.bgColor",
+ "DesktopScrollThumb.sel#bgColor", "the thumb must visibly highlight"
+ + " under the pointer");
+ assertDiffers(theme, name, "DesktopScrollThumb.bgColor",
+ "DesktopScrollThumb.press#bgColor", "and visibly again while dragged");
+
+ assertNotNull(theme.get("DesktopScrollThumb.margin"),
+ name + ": the thumb is inset from the track by its own margin");
+
+ // Same in dark, and additionally NOT the light colour. cn1-derive is flattened
+ // against the light parent at compile time, so the reverted theme's dark thumb
+ // came out 8a8a8a -- the LIGHT mobile grey -- rather than the dark one.
+ assertDiffers(theme, name, "$DarkDesktopScrollThumb.bgColor",
+ "$DarkDesktopScrollThumb.sel#bgColor",
+ "the dark thumb must highlight too");
+ assertDiffers(theme, name, "DesktopScrollThumb.bgColor",
+ "$DarkDesktopScrollThumb.bgColor",
+ "and must not be the light colour flattened through a derive");
+ }
+ }
+
+ /**
+ * The surfaces Codename One still draws itself on a desktop: the context menu, the
+ * overflow menu, the tooltip and the dialog's command area. None was defined by any
+ * desktop theme, so each fell through to UIManager's blank default -- black on white,
+ * on a dark window.
+ */
+ @Test
+ public void everyDesktopThemeStylesTheSurfacesCn1StillDraws() throws Exception {
+ String[] required = {
+ "PopupContentPane.bgColor", "CommandList.margin", "Command.fgColor",
+ "Command.sel#bgColor", "TooltipDialog.bgColor", "Tooltip.fgColor",
+ "DialogCommandArea.padding",
+ "Separator.fgColor", "GroupBox.border", "GroupBoxTitle.fgColor",
+ "Link.fgColor", "Stepper.margin", "StepperField.bgColor", "StepperButton.bgColor",
+ "ToolbarSearch.bgColor", "AccordionHeader.padding", "AccordionItem.padding",
+ // Tab, not SelectedTab: Tabs writes the `Tab` UIID onto every tab button and marks
+ // the open one with that button's own selected style. The themes carried
+ // SelectedTab/UnselectedTab rules that nothing in the framework writes, so the
+ // strip fell through to UIManager's `Tab.sel#derive: Tab` seed and the selected tab
+ // was pixel-identical to the others -- no indication at all of which was open.
+ "Tab.fgColor", "TabbedPane.margin", "TabsContainer.bgColor",
+ "TabsContainerHost.bgColor",
+ };
+ for (String name : DESKTOP_THEMES) {
+ Hashtable theme = loadTheme(name);
+ if (theme == null) {
+ return;
+ }
+ for (String key : required) {
+ assertNotNull(theme.get(key), name + " is missing " + key);
+ }
+ }
+ }
+
+ /**
+ * Every colour-bearing addition needs a dark counterpart. cn1-derive is flattened
+ * against the LIGHT parent at compile time, so a derived UIID gets a concrete copy of
+ * the light colours and no $Dark entry at all -- the trap the existing themes already
+ * carry a comment about, and the one this change had to avoid fourteen more times.
+ */
+ @Test
+ public void everyColouredAdditionHasADarkCounterpart() throws Exception {
+ String[] required = {
+ "$DarkPopupContentPane.bgColor", "$DarkCommand.fgColor", "$DarkTooltip.fgColor",
+ "$DarkTooltipDialog.bgColor", "$DarkSeparator.fgColor", "$DarkGroupBoxTitle.fgColor",
+ "$DarkLink.fgColor", "$DarkStepperField.bgColor", "$DarkStepperButton.bgColor",
+ "$DarkToolbarSearch.bgColor", "$DarkTab.fgColor", "$DarkTabsContainer.bgColor",
+ };
+ for (String name : DESKTOP_THEMES) {
+ Hashtable theme = loadTheme(name);
+ if (theme == null) {
+ return;
+ }
+ for (String key : required) {
+ assertNotNull(theme.get(key), name + " is missing " + key);
+ }
+ }
+ }
+
+ /**
+ * macOS restyles none of these controls on rollover -- the captured AppKit reference
+ * says so, and eighteen {@code .hover} rules were removed from the Aqua theme for that
+ * reason. Adding hover rules back in a later sweep is an easy mistake to make, so it is
+ * asserted rather than remembered.
+ *
+ *
The scrollbar knob is the deliberate exception: {@code NSScroller} does darken under
+ * the pointer, and it expresses that through sel#/press#, not hover#.
+ */
+ /// The open tab has to be VISIBLY open.
+ ///
+ /// Asserted as a difference rather than as a key, for the same reason the scrollbar
+ /// highlight is: `UIManager.resetThemeProps` seeds `Tab.sel#derive: Tab`, so the selected
+ /// entry is present in every compiled theme whether or not anything styled it. Present and
+ /// equal is exactly the defect -- a tab strip with no indication of which tab is open.
+ @Test
+ public void everyDesktopThemeMarksTheOpenTab() throws Exception {
+ for (String name : DESKTOP_THEMES) {
+ Hashtable theme = loadTheme(name);
+ if (theme == null) {
+ return;
+ }
+ boolean fillDiffers = differs(theme, "Tab.bgColor", "Tab.sel#bgColor");
+ boolean textDiffers = differs(theme, "Tab.fgColor", "Tab.sel#fgColor");
+ boolean borderDiffers = theme.get("Tab.sel#border") != null
+ && !String.valueOf(theme.get("Tab.sel#border"))
+ .equals(String.valueOf(theme.get("Tab.border")));
+ assertTrue(fillDiffers || textDiffers || borderDiffers,
+ name + ": the selected tab is indistinguishable from an unselected one --"
+ + " it needs a different fill, text colour or border");
+ }
+ }
+
+ private static boolean differs(Hashtable theme, String a, String b) {
+ Object va = theme.get(a);
+ Object vb = theme.get(b);
+ if (vb == null) {
+ return false;
+ }
+ return !String.valueOf(va).equals(String.valueOf(vb));
+ }
+
+ @Test
+ public void aquaAddsNoHoverRules() throws Exception {
+ Hashtable theme = loadTheme("MacOSAquaTheme.res");
+ if (theme == null) {
+ return;
+ }
+ String[] mustNotHover = {
+ "Command.hover#bgColor", "StepperButton.hover#bgColor", "Link.hover#fgColor",
+ "AccordionHeader.hover#bgColor", "UnselectedTab.hover#bgColor",
+ };
+ for (String key : mustNotHover) {
+ assertNull(theme.get(key), "Aqua must not restyle on rollover: " + key);
+ }
+ }
+
+ /** Windows and GNOME do restyle on rollover, and must say so. */
+ @Test
+ public void fluentAndAdwaitaDoAddHoverRules() throws Exception {
+ String[] withHover = {"WindowsFluentTheme.res", "GnomeAdwaitaTheme.res"};
+ for (String name : withHover) {
+ Hashtable theme = loadTheme(name);
+ if (theme == null) {
+ return;
+ }
+ assertNotNull(theme.get("Command.hover#bgColor"),
+ name + ": a menu item must light up under the pointer");
+ assertNotNull(theme.get("StepperButton.hover#bgColor"),
+ name + ": so must a stepper button");
+ }
+ }
+
+ /**
+ * Asserts two theme entries are both present and hold different values.
+ *
+ *
Present-and-equal is the failure this exists to catch: it is what a
+ * {@code cn1-derive} produces, and it renders as a control that does not react.
+ */
+ private static void assertDiffers(Hashtable theme, String themeName, String baseKey,
+ String variantKey, String why) {
+ Object base = theme.get(baseKey);
+ Object variant = theme.get(variantKey);
+ assertNotNull(base, themeName + " is missing " + baseKey);
+ assertNotNull(variant, themeName + " is missing " + variantKey);
+ assertNotEquals(String.valueOf(base), String.valueOf(variant),
+ themeName + ": " + why + " (" + baseKey + " and " + variantKey
+ + " are both " + base + ")");
+ }
+
+ private static Hashtable loadTheme(String fileName) throws Exception {
+ File themeFile = locateNativeTheme(fileName);
+ if (themeFile == null) {
+ return null;
+ }
+ Resources res;
+ InputStream stream = new FileInputStream(themeFile);
+ try {
+ res = Resources.open(stream);
+ } finally {
+ stream.close();
+ }
+ String[] themeNames = res.getThemeResourceNames();
+ if (themeNames == null || themeNames.length == 0) {
+ fail(fileName + " carries no theme");
+ }
+ Hashtable theme = res.getTheme(themeNames[0]);
+ assertNotNull(theme, fileName + " theme is empty");
+ assertTrue(theme.size() > 0, fileName + " theme has no entries");
+ return theme;
+ }
+
+ private static File locateNativeTheme(String fileName) {
+ File cwd = new File(".").getAbsoluteFile();
+ for (int i = 0; i < 6 && cwd != null; i++) {
+ File candidate = new File(cwd, "Themes/" + fileName);
+ if (candidate.isFile()) {
+ return candidate;
+ }
+ cwd = cwd.getParentFile();
+ }
+ return null;
+ }
+}
diff --git a/maven/linux/pom.xml b/maven/linux/pom.xml
index c6f689d66c8..0dad93c4612 100644
--- a/maven/linux/pom.xml
+++ b/maven/linux/pom.xml
@@ -83,17 +83,7 @@
maven-antrun-plugin
-
+ which reads target/classes directly.
+
+ The theme is this port's platform theme, not the Material
+ placeholder it staged before. AndroidMaterialTheme put a
+ phone design language on a GNOME desktop: Material ripples, a
+ hamburger side menu and a fading touch scrollbar.
+
+ Flipping it restyles every screen and reseeds this port's committed
+ screenshot baselines, which is why it was deferred when the theme
+ landed rather than riding along inside the change that introduced
+ it. The baselines are reseeded in this same change, from the CI
+ runner that scores them. -->
stage-native-themeprepare-package
@@ -110,7 +111,7 @@
-
diff --git a/maven/windows/pom.xml b/maven/windows/pom.xml
index 32c88479cd3..7e196dfcf03 100644
--- a/maven/windows/pom.xml
+++ b/maven/windows/pom.xml
@@ -83,17 +83,7 @@
maven-antrun-plugin
-
+ reads target/classes directly.
+
+ The theme is this port's platform theme, not the Material
+ placeholder it staged before. AndroidMaterialTheme put a
+ phone design language on a Windows desktop: Material ripples, a
+ hamburger side menu and a fading touch scrollbar.
+
+ Flipping it restyles every screen and reseeds this port's committed
+ screenshot baselines, which is why it was deferred when the theme
+ landed rather than riding along inside the change that introduced
+ it. The baselines are reseeded in this same change, from the CI
+ runner that scores them. -->
stage-native-themeprepare-package
@@ -110,7 +111,7 @@
-
diff --git a/native-themes/COVERAGE.md b/native-themes/COVERAGE.md
index 3c97c11db5a..a036e7cd423 100644
--- a/native-themes/COVERAGE.md
+++ b/native-themes/COVERAGE.md
@@ -157,33 +157,68 @@ First measured scores, against golden sets captured on hosted runners:
| Theme | Golden set | Pairs | Mean | Gating |
|---|---|---:|---:|---|
-| Windows Fluent | `windows-11-fluent` | 60 | 82.1% | yes, on master |
-| GNOME Adwaita | `gnome-adwaita` | 60 | 85.9% | yes, on master |
-| macOS Aqua | `macos-aqua` | 60 | 84.6% (local) | yes, on master |
+| Windows Fluent | `windows-11-fluent` | 98 | 85.9% | yes, on master |
+| GNOME Adwaita | `gnome-adwaita` | 102 | 84.2% | yes, on master |
+| macOS Aqua | `macos-aqua` | 86 | 84.2% | yes, on master |
These are starting points, not results. All three themes were written without a
-reference to check them against, so this is the first time any of them has been
-measured, and the ratchet moves them up from here.
+reference to check them against, and the ratchet moves them up from here.
-### No port installs one by default yet
+The pair counts differ by platform because three rows are only scorable where the
+reference can be rendered and put into the state; see "Rows that are not scored on
+every platform" below. The means barely moved when the matrix grew from 9 rows to 21
+(82.1 / 85.9 / 84.6 before), so the new rows sit in the same band as the old ones
+rather than dragging the set down -- but four of them are well below it:
-The themes are built, measured and selectable, and every desktop port still
-installs what it installed before: Windows and Linux stage the Material
-placeholder, macOS installs the iOS theme unless `macos.themeMode=aqua` asks for
-Aqua.
+| Row | Score | What the gap is |
+|---|---:|---|
+| `DesktopTabs` | dark: 87.4 Fluent, 71.0 Adwaita, 49.0 Aqua | Two separate problems, and only the first was a defect. **The defect:** the themes styled `SelectedTab` and `UnselectedTab`, and `Tabs` writes neither -- it writes `Tab` and marks the open one with that button's own selected style. The rules were dead, the strip fell through to `UIManager`'s `Tab.sel#derive: Tab` seed, and the selected tab was pixel-identical to the others; a captured Linux screenshot showed three plain boxes with no indication of which was open. Fixed: the dark row went 35.3 -> 87.4 on Fluent and 35.7 -> 71.0 on Adwaita. Adding the divider those two platforms draw under the strip took GNOME's geometry regressions to zero and Fluent's width ratio from 0.64 to 0.96. **What remains is Aqua**, at 49.0: `NSTabView` is a centred rounded pill on the bare window background and CN1 draws a left-aligned row, so the shapes genuinely differ and the narrower bounding box there is CORRECT rather than a regression. That one wants a per-platform tab shape, not more colour tuning, and it is the reason Aqua deliberately has no divider rule. |
+| `DesktopMenuBar` | 34-50% | CN1's `CommandList` strip against a real menu bar. Only meaningful where CN1 still draws its own menu, which is GNOME's headerbar mode. |
+| `DesktopListRow` | 68-90% | Row height and the selected fill; the CN1 row is taller than a native one on all three. |
+| `DesktopSlider` dark | 73% | Pre-existing, macOS only, and unchanged by this work. |
-That is sequencing, not an oversight. Flipping a port's theme restyles every
-screen and reseeds its committed screenshot baselines -- about 154 on the Windows
-port alone -- which deserves its own review rather than riding along inside the
-change that introduces the theme, and wants doing once the themes reach their
-fidelity targets rather than now and again later.
+### Every desktop port installs one
-Each flip is one line: the ` 75%; slider gained a round knob on a continuous track. |
-The macOS hover rows were the exception that proves the reference is worth having:
-they were the six worst tiles in the set, and the manifest already said why --
-AppKit restyles none of those controls on hover, so eighteen `.hover` rules were
-removed rather than tuned.
+The macOS hover rows were the exception that proves the reference is worth having: they
+were the six worst tiles in the set, and the manifest already said why -- AppKit restyles
+none of those controls on hover, so eighteen `.hover` rules were removed rather than tuned.
+That property is now asserted rather than remembered
+(`DesktopNativeThemeContentTest.aquaAddsNoHoverRules`).
### Covered components
-| Native control | CN1 building block | Fidelity test | Score (min-max) | Notes |
-|---|---|---|---:|---|
-| UIButton .glass | `Button` | Button | 90.9-93.4 | frosted capsule, backdrop-filter glass |
-| UIButton .prominentGlass | `RaisedButton` UIID | RaisedButton | 87.8-92.5 | geometry: ~10% wider than native (tracked) |
-| UIButton .plain | `FlatButton` UIID | FlatButton | 86.7-88.2 | geometry: native pill radius 92px vs CN1 44px (tracked) |
-| UITextField | `TextField` | TextField | 97.3-97.6 | |
-| Check glyph (Reminders style) | `CheckBox` | CheckBox | 92.2-97.5 | SF Symbol glyphs (iosSFStateIconsBool); iOS has no native checkbox |
-| Radio glyph | `RadioButton` | RadioButton | 92.2-95.5 | SF largecircle.fill.circle glyph |
-| UISwitch | `Switch` | Switch | 92.1-96.8 | + liquid droplet thumb morph (frame-validated) |
-| UISlider | `Slider` | Slider | 92.4-95.1 | |
-| UIProgressView | `Slider` (ProgressBar UIID) | ProgressBar | 94.4-95.4 | |
-| UITabBar (floating pill) | `Tabs` | Tabs | 84.8-86.4 | + selection-lens morph (frame-validated); residual = frost texture, worst iOS rows |
-| UINavigationBar | Toolbar UIID bar | Toolbar | 87.6-87.7 | residual = frost texture; still bottom-quartile |
-| UIAlertController (alert) | Dialog UIID card | Dialog | 97.0-97.1 | |
-| UIPickerView | `GenericSpinner` | Spinner | 91.5-91.8 | whole-row perspective; CN1 wheel wraps short models (native does not); dark off-row contrast tracked |
-| UIVisualEffectView / UIGlassEffect | GlassPanel UIID | GlassPanel{Grey,Red,Grad,Photo} | 96.1-98.6 | glass-blend isolation over 4 backdrops (see scope note above) |
-
-Isolation/ladder cases (not user-facing components): TabOne 95.6-96.1
-(geometry OFF: w 0.75 / h 0.54 -- see scope note), TabsGeom 93.0-93.5,
-GlassText/GlassIcon 98.6-98.7.
-
-Animated glass (validated per-frame at fixed progress, no native golden):
-TabsMorph (selection lens: travel, overshoot, lens size, tint timing),
-SwitchMorph (droplet stretch/squash).
-
-### Missing components (to reach a complete theme)
-
-| Native control | Suggested CN1 building block | Status |
+| Fidelity test | WinUI 3 | AppKit | GTK4 / libadwaita |
+|---|---|---|---|
+| DesktopButton | Button | NSButton (rounded) | GtkButton |
+| DesktopAccentButton | Button + AccentButtonStyle | NSButton (default) | GtkButton `.suggested-action` |
+| DesktopTextField | TextBox | NSTextField | GtkEntry |
+| DesktopCheckBox | CheckBox | NSButton (checkbox) | GtkCheckButton |
+| DesktopRadioButton | RadioButton | NSButton (radio) | GtkCheckButton in a group |
+| DesktopSwitch | ToggleSwitch | NSSwitch | GtkSwitch |
+| DesktopSlider | Slider | NSSlider | GtkScale |
+| DesktopProgressBar | ProgressBar | NSProgressIndicator | GtkProgressBar |
+| DesktopComboBox | ComboBox | NSPopUpButton | GtkDropDown |
+| DesktopSeparator | Border in `DividerStrokeColorDefaultBrush` | NSBox (separator) | GtkSeparator |
+| DesktopGroupBox | headered Border | NSBox (titled) | GtkFrame |
+| DesktopStepper | NumberBox (inline spin) | NSTextField + NSStepper | GtkSpinButton |
+| DesktopLinkButton | HyperlinkButton | NSButton (link) | GtkLinkButton |
+| DesktopSearchField | AutoSuggestBox | NSSearchField | GtkSearchEntry |
+| DesktopListRow | ListViewItem | NSTableRowView | GtkListBoxRow |
+| DesktopTabs | TabView | NSTabView | GtkNotebook |
+| DesktopToolbar | CommandBar | title-bar strip | AdwHeaderBar |
+| DesktopDisclosure | Expander | disclosure triangle + label | GtkExpander |
+| DesktopScrollBar | ScrollBar | -- | GtkScrollbar |
+| DesktopScrollBarHighlight | -- | -- | GtkScrollbar (PRELIGHT / ACTIVE) |
+| DesktopMenuBar | MenuBar | -- | GtkPopoverMenuBar |
+| DesktopMenuItem | MenuFlyoutItem | -- | menu row (`.model` button) |
+| DesktopTooltip | ToolTip | -- | -- |
+
+States: normal, hover, pressed, selected and disabled as each control supports them, in
+both appearances.
+
+### Rows that are not scored on every platform
+
+A reference has to be RENDERABLE into a view, and three of these are not everywhere. Saying
+where the reference exists is the honest answer: a blank golden scores 0% forever and reads
+as a theme bug.
+
+| Row | Missing on | Why |
|---|---|---|
-| UISegmentedControl | ButtonGroup / Tabs pill variant | `ToggleButton` themed (capsule track); not in the fidelity suite |
-| UIStepper | Stepper composite (2 glass buttons) | not started |
-| UISearchBar / searchable nav | Toolbar search mode | not started |
-| UIActivityIndicatorView | InfiniteProgress | not started (UIID exists, untested) |
-| UIPageControl | Tabs page indicator | not started |
-| UIDatePicker (wheels) | Picker (date/time spinner) | partially themed (DateSpinner UIIDs), not in suite |
-| UIDatePicker (calendar) | Calendar | not started |
-| UIMenu / context menu | ActionSheet / Command menu | not started |
-| Action sheet (bottom) | Sheet / ActionSheet | not started |
-| Bottom sheet (detents) | Sheet | not started |
-| UITableView cell chrome | MultiButton / list rows | not started |
-| Toast / HUD | ToastBar | not started |
-| Pull-to-refresh spinner | pull-to-refresh (themed) | not started |
-| Large-title navigation bar | Toolbar large-title mode | not started |
-| Tab bar badge | Tabs badge | not started |
-| UISlider liquid thumb morph | Slider droplet (reuse SwitchThumbDroplet) | planned (task tracked) |
+| DesktopScrollBar | macOS | Measured, not assumed. An `NSScroller` reports `usableParts=allScrollerParts`, `knobProportion` 0.4, `isHidden=false` and a 17x56 frame -- and renders nothing through `NSView.cacheDisplay`. Tried detached and inside a real `NSScrollView`, in both `.legacy` and `.overlay` styles, with `AppleShowScrollBars=Always` already set by the capture script. The tile comes back holding one colour, the backdrop, every time. Same class of limitation as Aqua vibrancy. |
+| DesktopScrollBarHighlight | macOS, Windows | The scrollbar's hover and drag states. Measured on a capture run: none of `PointerOver`, `UncheckedPointerOver`, `CheckedPointerOver` or `MouseOver` is a visual state of a WinUI `ScrollBar`, and neither is `Pressed` or `Dragging`. GTK can state it -- `PRELIGHT` and `ACTIVE` are what the CSS pseudo-classes resolve from -- and its captured tiles genuinely differ from normal, so the row scores there and nowhere else rather than not existing. |
+| DesktopListRow hover | all three | A WinUI `ListViewItem` draws through `ListViewItemPresenter`, which paints its own pointer-over chrome rather than exposing a state `GoToState` can reach. Dropped from the row rather than scored on two platforms and blocked on the third; `selected` is a real property everywhere and is scored. |
+| DesktopMenuBar, DesktopMenuItem | macOS | An `NSMenu` belongs to the window server, not to a view. |
+| DesktopTooltip | macOS, GNOME | Both platforms' tooltips are separate windows. A WinUI `ToolTip` is an ordinary `Control`, which is why the row exists at all. |
+
+Three things the second wave found in the references themselves, each caught by the capture
+apps' own blockers rather than by eye:
+
+- An `NSTableRowView` has no intrinsic size in either axis and laid out to 240x0, producing
+ no image. Given the standard 24pt row height a table would have given it.
+- An `NSStackView`'s `fittingSize` came back with no width, so the stepper tile showed the
+ chevrons and no field -- half a control.
+- A `CGColor` read from a dynamic `NSColor` freezes at whatever appearance was in force, so
+ the toolbar's light tile was painted with the dark window background. Drawn rather than
+ layer-backed now, which is why `TileView` draws its own fill too.
### Known visual gaps (tracked, honest list)
-- iOS `Tabs`/`Toolbar` frost texture: the two worst iOS families; theme knobs
- are at measured optima and two material-level tweaks (saturation, edge
- feather) measured flat -- closing this requires a closer reproduction of the
- native Liquid Glass material in the Metal patch, not tuning.
-- `TabOne` geometry (w 0.75 / h 0.54 vs native) despite its high overlay score.
-- iOS `Spinner` dark: off-row text contrast is low (uniform ~0.32 fade matches
- the native tone but the dark-sheet contrast is tracked for another pass).
-- Android `ProgressBar`: ~1.5x native track height (geometry-tracked).
-- Android disabled dark `Button`: lower contrast than native.
-- Android FAB/switch small geometry deltas (geometry-tracked).
-
-### Feature-level gaps
-
-- Live glass while scrolling: composed-patch cache recomposes per frame when
- the backdrop moves (policy documented in `Component.internalPaintImpl` and
- the METALView glass patch cache); a pure-GPU two-pass material (like the
- selection lens shader) is the tracked follow-up.
-- Tab icons: CN1 renders Apple SF Symbols on iOS (`FontImage.createSFOrMaterial`);
- a handful of glyphs still differ from the exact native weights.
-- RTL mirroring of the glass morphs is untested.
+| Gap | Why it is open |
+|---|---|
+| Fluent reveal highlight | The gradient that follows the cursor across a control. Needs per-pixel pointer position at paint time; no CN1 primitive expresses it. |
+| Mica / Acrylic | A WINDOW attribute (`DwmSetWindowAttribute`), not a region operation, so it is not a theme rule at all. The right shape is a `desktopWindowBackdrop` theme constant read at window creation. |
+| Aqua vibrancy | `NSVisualEffectView` is composited by the window server and is invisible to `NSView.cacheDisplay`, which is the capture path that needs no Screen Recording consent. A missing golden is honest; a blank one scores 0% forever and reads as a theme bug. |
+| macOS hover | AppKit draws no rollover state for any control in this matrix. The Aqua theme leaves hover equal to normal, the captured reference says the same, and the gate holds it there. Not a gap in the theme -- a property of the platform. |
+| Adwaita has no Mica analogue | By design. Recorded so nobody goes looking for one. |
+| Window chrome | The tile contract is a widget in a tile. `DesktopToolbar` now scores the title-bar strip, but the rest -- borders, shadows, corner radii, the traffic lights -- is not scored. |
+| Dialog | Not scored: an alert needs a bigger tile than 240x56, and the tile size is a constant in each of the three standalone capture apps rather than a per-row value. Teaching all three per-row tiles is the prerequisite. |
+| Fluent `ScrollBar` visual-state names | The WinUI `ScrollBar` template predates the `PointerOver` vocabulary, so `MouseOver` and `Dragging` are tried after the modern names. A capture where none of them matched reports a blocker rather than writing a tile identical to normal. |
+
+### Fonts are the honest ceiling
+
+Segoe UI Variable and SF Pro are system-only and not redistributable, so the Windows and
+macOS sets can never be reproduced away from those platforms. Only GNOME can be made fully
+honest, Cantarell being redistributable and pinnable. Where a face cannot be matched the
+residual is named rather than dismissed as anti-aliasing.
+
+### Golden sets
+
+All three are captured, reviewed, committed and gating on master. The second-wave rows have
+no goldens yet: they are captured by dispatching
+`fidelity-desktop-native-ref.yml -f targets=all -f mode=capture`, reviewed frame by frame,
+committed in one commit naming the run, then re-dispatched and required to come back
+byte-identical. Until that happens the desktop fidelity legs report those pairs as
+`missing_expected` and fail, which is the correct behaviour -- a new row is not silently
+skipped.
+
+A baseline is recorded from the runner that SCORES it, never locally. The CN1 side renders
+on the leg's own OS, and a Mac-recorded baseline failed the gnome gate on eighteen pairs --
+the slider comes out one pixel taller on Linux. That is the "measured on its own OS runner"
+rule applying to the baseline as well as the reference, and it is easy to miss because a
+locally recorded baseline passes locally forever.
+
+The protocol, including the measured reproducibility residual on the Windows set, is in
+`scripts/fidelity-app/goldens/README.md`.
## UIIDs the framework assigns
diff --git a/native-themes/README.md b/native-themes/README.md
index 360df176500..71b5e25fbf3 100644
--- a/native-themes/README.md
+++ b/native-themes/README.md
@@ -9,9 +9,12 @@ repo's `Themes/` directory, alongside the legacy hand-authored themes.
```
native-themes/
- base/ shared tokens, @constants, @font-face (future)
- ios-modern/theme.css iOS liquid-glass theme
+ base/ shared tokens, @constants, @font-face (future)
+ ios-modern/theme.css iOS liquid-glass theme
android-material/theme.css Android Material 3 theme
+ windows-fluent/theme.css Windows 11 Fluent (WinUI 3)
+ macos-aqua/theme.css macOS Aqua (AppKit)
+ gnome-adwaita/theme.css GNOME Adwaita (GTK4 + libadwaita)
```
Each `theme.css` is fed directly to the compiler. Until `@import` support is
@@ -104,6 +107,33 @@ Each theme must declare these in `#Constants`:
which is populated from the theme's `@media (prefers-color-scheme: dark)`
blocks.
+## Desktop themes declare their own behaviour
+
+A desktop native theme also turns on the behaviours that make a desktop application feel
+like one. These are theme constants rather than port hooks on purpose: the three desktop
+`theme.css` files install only on a desktop, so an application still on the legacy theme is
+untouched and nothing needs an `isDesktop()` gate.
+
+- `interactiveScrollBool: true` -- a grab-able thumb, a track that pages on click, a
+ reserved gutter, no fade. The bar draws through `DesktopScroll` / `DesktopScrollThumb`
+ and the horizontal pair, which are separate UIIDs from the mobile `Scroll` / `ScrollThumb`
+ precisely so turning this on never restyles the mobile bar.
+- `scrollThumbMinSizeInt` -- the thumb's minimum length in pixels.
+- `defaultNativeWindowModeBool: true` -- a `Dialog` opens as a real operating system window.
+ Anchored popups (`ComboBox`, `Picker`, the context menu) never do.
+- `desktopTitleBarMode` -- `native`, `custom` or `toolbar`. A `desktop.titleBar` build hint
+ outranks it; the constant is what speaks when the project said nothing.
+- `commandBehavior: Native` -- commands go to the platform's menu. Safe on a port that has
+ none: `setCommandBehavior` normalises it away there.
+- `separatorThicknessMM` -- the `Separator` rule.
+
+Note the highlight states on `DesktopScrollThumb` are `.selected` (pointer over) and
+`.pressed` (dragging), NOT `.hover`. `LookAndFeel`'s interactive thumb reads
+`getSelectedStyle()` and `getPressedStyle()`; a `.hover` rule there compiles and is never
+painted. And do not write these four as `cn1-derive: ScrollThumb` -- a derive emits the
+whole state family by copying the base, so the highlight comes out identical to the resting
+colour, present in the `.res` and invisible on screen.
+
## cn1-derive inheritance rule
`cn1-derive` only works reliably when the derived UIID is a straightforward
@@ -146,3 +176,6 @@ Outputs:
- `Themes/iOSModernTheme.res`
- `Themes/AndroidMaterialTheme.res`
+- `Themes/WindowsFluentTheme.res`
+- `Themes/MacOSAquaTheme.res`
+- `Themes/GnomeAdwaitaTheme.res`
diff --git a/native-themes/gnome-adwaita/theme.css b/native-themes/gnome-adwaita/theme.css
index 8bf63b1564b..c0cff8e00c5 100644
--- a/native-themes/gnome-adwaita/theme.css
+++ b/native-themes/gnome-adwaita/theme.css
@@ -116,6 +116,28 @@
them. Without it a progress bar paints the legacy full-height fill, 19px against
this reference. */
progressTrackThicknessMM: "2.12";
+
+ /* ---- Desktop behaviour the ports used to have to be told about separately ----
+ Both of these are behaviours a native desktop theme IS, so the theme is where they
+ belong: these three files install only on the desktop, which means an application
+ still on the legacy theme is untouched and no port-side isDesktop() gate is needed.
+
+ interactiveScrollBool turns the fading touch indicator into a real scrollbar -- a
+ thumb that can be grabbed, a track that pages on click, a reserved gutter, and no
+ fade. scrollThumbMinSizeInt keeps that thumb grabbable on content far taller than
+ the viewport; 24px is the floor all three toolkits settle around.
+
+ defaultNativeWindowModeBool opens a Dialog as a real operating system window rather
+ than drawing it inside the application's own surface. Anchored popups (ComboBox,
+ Picker) never take it, and the constant is ignored wherever there is no windowing
+ system, so shared code needs no guard. */
+ interactiveScrollBool: true;
+ scrollThumbMinSizeInt: 24;
+ defaultNativeWindowModeBool: true;
+
+ /* Separator's rule thickness. A 1px hairline, which is what all three draw. */
+ separatorThicknessMM: "0.26";
+
}
/* --- Window and text ------------------------------------------------------------------- */
@@ -534,10 +556,34 @@ ScrollThumb.hover { background-color: #9a9996; cn1-background-type: cn1-pill-bor
ScrollThumb.pressed { background-color: #77767b; cn1-background-type: cn1-pill-border; }
HorizontalScroll { background-color: transparent; }
HorizontalScrollThumb { cn1-derive: ScrollThumb; }
-DesktopScroll { background-color: transparent; }
-DesktopScrollThumb { cn1-derive: ScrollThumb; }
-DesktopHorizontalScroll { background-color: transparent; }
-DesktopHorizontalScrollThumb { cn1-derive: ScrollThumb; }
+
+/* --- The interactive desktop scrollbar ------------------------------------------------- */
+/*
+ * These four are NOT the mobile Scroll/ScrollThumb above. LookAndFeel.initScroll swaps to
+ * them when interactiveScrollBool is on, and until now this theme derived the thumb from the
+ * mobile one -- which meant a desktop scrollbar with no gutter, no minimum length and no
+ * highlight, on a theme whose whole job is to look like the platform.
+ *
+ * The gutter width is DesktopScroll's horizontal padding plus its margin
+ * (LookAndFeel.getVerticalScrollWidth sums exactly those), and the thumb is inset from it by
+ * its own margin. That is how a thin thumb sits in a wider track.
+ *
+ * The highlight states are .selected and .pressed, NOT .hover: LookAndFeel's
+ * InteractiveScrollThumb returns getSelectedStyle() while the pointer is over the thumb and
+ * getPressedStyle() while it is being dragged. A .hover rule here would compile, and would
+ * never be painted.
+ *
+ * Properties are spelled out rather than cn1-derive'd from ScrollThumb, because deriving is
+ * what left them wrong.
+ */
+DesktopScroll { background-color: transparent; padding: 0 1.72mm 0 1.72mm; margin: 0; }
+DesktopScrollThumb { background-color: #b8b4b0; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 0.66mm 0 0.66mm; }
+DesktopScrollThumb.selected { background-color: #918d88; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 0.66mm 0 0.66mm; }
+DesktopScrollThumb.pressed { background-color: #6f6b66; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 0.66mm 0 0.66mm; }
+DesktopHorizontalScroll { background-color: transparent; padding: 1.72mm 0 1.72mm 0; margin: 0; }
+DesktopHorizontalScrollThumb { background-color: #b8b4b0; cn1-background-type: cn1-pill-border; padding: 0; margin: 0.66mm 0 0.66mm 0; }
+DesktopHorizontalScrollThumb.selected { background-color: #918d88; cn1-background-type: cn1-pill-border; padding: 0; margin: 0.66mm 0 0.66mm 0; }
+DesktopHorizontalScrollThumb.pressed { background-color: #6f6b66; cn1-background-type: cn1-pill-border; padding: 0; margin: 0.66mm 0 0.66mm 0; }
ListRenderer {
color: var(--text-color, #2e3436);
@@ -570,6 +616,99 @@ ListRendererFocus {
/* --- Dark ------------------------------------------------------------------------------ */
+
+/* --- Grouping, rules and links --------------------------------------------------------- */
+/*
+ * Separator is the component's own UIID: its foreground colour is the rule and its margin is
+ * the air either side. GroupBox is the frame and GroupBoxTitle the caption -- nothing here
+ * positions the caption relative to the top edge, because the three platforms disagree about
+ * it and a theme that wants it inset says so with a negative top margin.
+ */
+Separator { color: #d8d4d0; background-color: transparent; margin: 1.6mm 0 1.6mm 0; padding: 0; }
+GroupBox { background-color: transparent; border: 0.26mm solid #cdc7c2; border-radius: 1.59mm; padding: 1.6mm; margin: 1.6mm 0 1.6mm 0; }
+GroupBoxTitle { color: #5e5c64; background-color: transparent; padding: 0 0 1.6mm 0; margin: 0; }
+Link { color: #3584e4; background-color: transparent; text-decoration: underline; padding: 0; margin: 0; }
+Link.hover { color: #3584e4; background-color: transparent; text-decoration: underline; padding: 0; margin: 0; }
+
+/* --- Stepper (NSStepper / NumberBox / GtkSpinButton) ----------------------------------- */
+Stepper { background-color: transparent; padding: 0; margin: 0; }
+StepperField { color: #2e3436; background-color: #ffffff; border: 0.26mm solid #cdc7c2; border-radius: 1.59mm; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+StepperButton { color: #2e3436; background-color: #fafafa; border: 0.26mm solid #cdc7c2; border-radius: 1.59mm; padding: 1.6mm 1.6mm 1.6mm 1.6mm; margin: 0; }
+StepperButton.disabled { color: #5e5c64; background-color: #fafafa; border: 0.26mm solid #cdc7c2; border-radius: 1.59mm; padding: 1.6mm 1.6mm 1.6mm 1.6mm; margin: 0; }
+StepperButton.hover { color: #2e3436; background-color: #ededed; border: 0.26mm solid #cdc7c2; border-radius: 1.59mm; padding: 1.6mm 1.6mm 1.6mm 1.6mm; margin: 0; }
+
+/* --- Menus, tooltips and the rest of the popup surfaces -------------------------------- */
+/*
+ * On Windows and macOS the menu BAR is the platform's own and never reaches these rules. What
+ * does reach them is everything Codename One still draws itself: the right-click context menu,
+ * the overflow menu, the tooltip, and the whole of the GNOME headerbar mode. None of the four
+ * was defined by any desktop theme, so each of them fell through to UIManager's blank default
+ * -- which on a dark window is black text on white.
+ */
+PopupContentPane { background-color: #ffffff; border: 0.26mm solid #cdc7c2; border-radius: 3.18mm; padding: 1.6mm 0 1.6mm 0; margin: 0; }
+CommandList { background-color: transparent; padding: 0; margin: 0; }
+Command { color: #2e3436; background-color: transparent; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; text-align: left; }
+Command.selected { color: #ffffff; background-color: #3584e4; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; text-align: left; }
+Command.disabled { color: #5e5c64; background-color: transparent; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; text-align: left; }
+TouchCommand { cn1-derive: Command; }
+TooltipDialog { background-color: #ffffff; border: 0.26mm solid #cdc7c2; border-radius: 1.59mm; padding: 0; margin: 0; }
+Tooltip { color: #2e3436; background-color: transparent; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+Command.hover { color: #2e3436; background-color: #ededed; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; text-align: left; }
+
+/* --- Dialog command area ---------------------------------------------------------------- */
+/*
+ * Dialog, DialogTitle and DialogBody were already here; the buttons along the bottom were not,
+ * and they are most of what a desktop alert looks like. DialogButtonDefault is the one the
+ * platform emphasises -- the accented button on Windows and GNOME, the key-equivalent button
+ * on macOS.
+ */
+DialogCommandArea { background-color: transparent; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+DialogButton { cn1-derive: Button; }
+DialogButtonDefault { cn1-derive: RaisedButton; }
+
+/* --- Search field, accordion and tabs ---------------------------------------------------- */
+/*
+ * ToolbarSearch is written by SearchBar, and the Accordion pair is seeded by
+ * UIManager.resetThemeProps with a plain line border and phone metrics. Both looked like a
+ * mobile control on a desktop window; defining them here suppresses the framework's seed.
+ */
+ToolbarSearch { color: #2e3436; background-color: #ffffff; border: 0.26mm solid #cdc7c2; border-radius: 1.59mm; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+AccordionHeader { color: #2e3436; background-color: transparent; border-radius: 1.59mm; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+AccordionItem { background-color: transparent; border: none; padding: 0 3.2mm 1.6mm 3.2mm; margin: 0; }
+Tabs { background-color: transparent; padding: 0; margin: 0; }
+AccordionHeader.hover { color: #2e3436; background-color: #ededed; border-radius: 1.59mm; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+
+
+/* --- Tabs ------------------------------------------------------------------------------- */
+/*
+ * The UIID a tab button actually gets is `Tab`, and its selected state is that button's own
+ * selected style. This theme previously defined SelectedTab and UnselectedTab, which nothing in
+ * the framework writes -- so the rules were dead and the tab strip fell through to
+ * UIManager.resetThemeProps, which seeds `Tab.sel#derive: Tab`. That seed makes the selected tab
+ * IDENTICAL to an unselected one: the captured Linux screenshot showed three plain boxes with no
+ * indication of which was open, and the DesktopTabs fidelity row scored 35-68%.
+ *
+ * TabbedPane is the content pane below the strip, TabsContainer the strip itself and
+ * TabsContainerHost its wrapper; all three are named by Tabs and none was defined here.
+ */
+TabbedPane { background-color: transparent; padding: 0; margin: 0; }
+/* The divider under the tab strip. GtkNotebook draws one and so does a WinUI TabView;
+ NSTabView does not, which is why the Aqua theme has no such rule -- its pill sits on
+ the bare window background.
+
+ It is also what makes the row MEASURE like the native one. A Tab is transparent until
+ it is selected, so without the divider the only content in the tile is the selected
+ tab's fill and the two labels: the comparator's bbox came out 64%% of the native
+ width where it had been 100%%. Drawing the line the platform actually draws fixes the
+ look and the measurement together, which is the only kind of fix worth making to a
+ geometry number. */
+TabsContainer { background-color: #fafafa; border-bottom: 0.26mm solid #d8d4d0; padding: 0; margin: 0; }
+TabsContainerHost { background-color: #fafafa; padding: 0; margin: 0; }
+Tab { color: #5e5c64; background-color: transparent; border: none; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+Tab.selected { color: #2e3436; background-color: transparent; border-bottom: 0.79mm solid #3584e4; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+Tab.pressed { color: #2e3436; background-color: #ededed; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+Tab.hover { color: #2e3436; background-color: #ededed; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+
@media (prefers-color-scheme: dark) {
Form { background-color: #242424; color: #ffffff; }
Label { color: #ffffff; }
@@ -645,6 +784,48 @@ ListRendererFocus {
keeps TextField's near-white background on a dark form. Repeat every derive. */
TextArea { cn1-derive: TextField; }
HorizontalScrollThumb { cn1-derive: ScrollThumb; }
- DesktopScrollThumb { cn1-derive: ScrollThumb; }
- DesktopHorizontalScrollThumb { cn1-derive: ScrollThumb; }
+ DesktopScroll { background-color: transparent; padding: 0 1.72mm 0 1.72mm; margin: 0; }
+ DesktopScrollThumb { background-color: #6e6a66; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 0.66mm 0 0.66mm; }
+ DesktopScrollThumb.selected { background-color: #948f8a; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 0.66mm 0 0.66mm; }
+ DesktopScrollThumb.pressed { background-color: #b5b0ab; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 0.66mm 0 0.66mm; }
+ DesktopHorizontalScroll { background-color: transparent; padding: 1.72mm 0 1.72mm 0; margin: 0; }
+ DesktopHorizontalScrollThumb { background-color: #6e6a66; cn1-background-type: cn1-pill-border; padding: 0; margin: 0.66mm 0 0.66mm 0; }
+ DesktopHorizontalScrollThumb.selected { background-color: #948f8a; cn1-background-type: cn1-pill-border; padding: 0; margin: 0.66mm 0 0.66mm 0; }
+ DesktopHorizontalScrollThumb.pressed { background-color: #b5b0ab; cn1-background-type: cn1-pill-border; padding: 0; margin: 0.66mm 0 0.66mm 0; }
+ Separator { color: #3d3846; background-color: transparent; margin: 1.6mm 0 1.6mm 0; padding: 0; }
+ GroupBox { background-color: transparent; border: 0.26mm solid #3d3846; border-radius: 1.59mm; padding: 1.6mm; margin: 1.6mm 0 1.6mm 0; }
+ GroupBoxTitle { color: #c0bfbc; background-color: transparent; padding: 0 0 1.6mm 0; margin: 0; }
+ Link { color: #3584e4; background-color: transparent; text-decoration: underline; padding: 0; margin: 0; }
+ Link.hover { color: #3584e4; background-color: transparent; text-decoration: underline; padding: 0; margin: 0; }
+ Stepper { background-color: transparent; padding: 0; margin: 0; }
+ StepperField { color: #ffffff; background-color: #1e1e1e; border: 0.26mm solid #3d3846; border-radius: 1.59mm; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+ StepperButton { color: #ffffff; background-color: #242424; border: 0.26mm solid #3d3846; border-radius: 1.59mm; padding: 1.6mm 1.6mm 1.6mm 1.6mm; margin: 0; }
+ StepperButton.disabled { color: #c0bfbc; background-color: #242424; border: 0.26mm solid #3d3846; border-radius: 1.59mm; padding: 1.6mm 1.6mm 1.6mm 1.6mm; margin: 0; }
+ StepperButton.hover { color: #ffffff; background-color: #353535; border: 0.26mm solid #3d3846; border-radius: 1.59mm; padding: 1.6mm 1.6mm 1.6mm 1.6mm; margin: 0; }
+ PopupContentPane { background-color: #2a2a2a; border: 0.26mm solid #3d3846; border-radius: 3.18mm; padding: 1.6mm 0 1.6mm 0; margin: 0; }
+ CommandList { background-color: transparent; padding: 0; margin: 0; }
+ Command { color: #ffffff; background-color: transparent; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; text-align: left; }
+ Command.selected { color: #ffffff; background-color: #3584e4; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; text-align: left; }
+ Command.disabled { color: #c0bfbc; background-color: transparent; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; text-align: left; }
+ TouchCommand { cn1-derive: Command; }
+ TooltipDialog { background-color: #2a2a2a; border: 0.26mm solid #3d3846; border-radius: 1.59mm; padding: 0; margin: 0; }
+ Tooltip { color: #ffffff; background-color: transparent; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+ Command.hover { color: #ffffff; background-color: #353535; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; text-align: left; }
+ DialogCommandArea { background-color: transparent; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+ DialogButton { cn1-derive: Button; }
+ DialogButtonDefault { cn1-derive: RaisedButton; }
+ ToolbarSearch { color: #ffffff; background-color: #1e1e1e; border: 0.26mm solid #3d3846; border-radius: 1.59mm; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+ AccordionHeader { color: #ffffff; background-color: transparent; border-radius: 1.59mm; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+ AccordionItem { background-color: transparent; border: none; padding: 0 3.2mm 1.6mm 3.2mm; margin: 0; }
+ Tabs { background-color: transparent; padding: 0; margin: 0; }
+ AccordionHeader.hover { color: #ffffff; background-color: #353535; border-radius: 1.59mm; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+
+ TabbedPane { background-color: transparent; padding: 0; margin: 0; }
+ TabsContainer { background-color: #242424; border-bottom: 0.26mm solid #3d3846; padding: 0; margin: 0; }
+ TabsContainerHost { background-color: #242424; padding: 0; margin: 0; }
+ Tab { color: #c0bfbc; background-color: transparent; border: none; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+ Tab.selected { color: #ffffff; background-color: transparent; border-bottom: 0.79mm solid #3584e4; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+ Tab.pressed { color: #ffffff; background-color: #353535; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+ Tab.hover { color: #ffffff; background-color: #353535; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+
}
diff --git a/native-themes/macos-aqua/theme.css b/native-themes/macos-aqua/theme.css
index 949578f2a78..20612a56ae8 100644
--- a/native-themes/macos-aqua/theme.css
+++ b/native-themes/macos-aqua/theme.css
@@ -107,6 +107,28 @@
them. Without it a progress bar paints the legacy full-height fill, 19px against
this reference. */
progressTrackThicknessMM: "2.12";
+
+ /* ---- Desktop behaviour the ports used to have to be told about separately ----
+ Both of these are behaviours a native desktop theme IS, so the theme is where they
+ belong: these three files install only on the desktop, which means an application
+ still on the legacy theme is untouched and no port-side isDesktop() gate is needed.
+
+ interactiveScrollBool turns the fading touch indicator into a real scrollbar -- a
+ thumb that can be grabbed, a track that pages on click, a reserved gutter, and no
+ fade. scrollThumbMinSizeInt keeps that thumb grabbable on content far taller than
+ the viewport; 24px is the floor all three toolkits settle around.
+
+ defaultNativeWindowModeBool opens a Dialog as a real operating system window rather
+ than drawing it inside the application's own surface. Anchored popups (ComboBox,
+ Picker) never take it, and the constant is ignored wherever there is no windowing
+ system, so shared code needs no guard. */
+ interactiveScrollBool: true;
+ scrollThumbMinSizeInt: 24;
+ defaultNativeWindowModeBool: true;
+
+ /* Separator's rule thickness. A 1px hairline, which is what all three draw. */
+ separatorThicknessMM: "0.26";
+
}
/* --- Window and text ------------------------------------------------------------------- */
@@ -461,10 +483,34 @@ ScrollThumb.hover { background-color: #a8a8a8; cn1-background-type: cn1-pill-bor
ScrollThumb.pressed { background-color: #8e8e8e; cn1-background-type: cn1-pill-border; }
HorizontalScroll { background-color: transparent; }
HorizontalScrollThumb { cn1-derive: ScrollThumb; }
-DesktopScroll { background-color: transparent; }
-DesktopScrollThumb { cn1-derive: ScrollThumb; }
-DesktopHorizontalScroll { background-color: transparent; }
-DesktopHorizontalScrollThumb { cn1-derive: ScrollThumb; }
+
+/* --- The interactive desktop scrollbar ------------------------------------------------- */
+/*
+ * These four are NOT the mobile Scroll/ScrollThumb above. LookAndFeel.initScroll swaps to
+ * them when interactiveScrollBool is on, and until now this theme derived the thumb from the
+ * mobile one -- which meant a desktop scrollbar with no gutter, no minimum length and no
+ * highlight, on a theme whose whole job is to look like the platform.
+ *
+ * The gutter width is DesktopScroll's horizontal padding plus its margin
+ * (LookAndFeel.getVerticalScrollWidth sums exactly those), and the thumb is inset from it by
+ * its own margin. That is how a thin thumb sits in a wider track.
+ *
+ * The highlight states are .selected and .pressed, NOT .hover: LookAndFeel's
+ * InteractiveScrollThumb returns getSelectedStyle() while the pointer is over the thumb and
+ * getPressedStyle() while it is being dragged. A .hover rule here would compile, and would
+ * never be painted.
+ *
+ * Properties are spelled out rather than cn1-derive'd from ScrollThumb, because deriving is
+ * what left them wrong.
+ */
+DesktopScroll { background-color: transparent; padding: 0 1.98mm 0 1.98mm; margin: 0; }
+DesktopScrollThumb { background-color: #a8a8ad; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 1.06mm 0 1.06mm; }
+DesktopScrollThumb.selected { background-color: #8e8e93; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 1.06mm 0 1.06mm; }
+DesktopScrollThumb.pressed { background-color: #636366; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 1.06mm 0 1.06mm; }
+DesktopHorizontalScroll { background-color: transparent; padding: 1.98mm 0 1.98mm 0; margin: 0; }
+DesktopHorizontalScrollThumb { background-color: #a8a8ad; cn1-background-type: cn1-pill-border; padding: 0; margin: 1.06mm 0 1.06mm 0; }
+DesktopHorizontalScrollThumb.selected { background-color: #8e8e93; cn1-background-type: cn1-pill-border; padding: 0; margin: 1.06mm 0 1.06mm 0; }
+DesktopHorizontalScrollThumb.pressed { background-color: #636366; cn1-background-type: cn1-pill-border; padding: 0; margin: 1.06mm 0 1.06mm 0; }
/* The sidebar capsule: macOS rounds the whole selected row. */
ListRenderer {
@@ -496,6 +542,84 @@ ListRendererFocus {
/* --- Dark ------------------------------------------------------------------------------ */
+
+/* --- Grouping, rules and links --------------------------------------------------------- */
+/*
+ * Separator is the component's own UIID: its foreground colour is the rule and its margin is
+ * the air either side. GroupBox is the frame and GroupBoxTitle the caption -- nothing here
+ * positions the caption relative to the top edge, because the three platforms disagree about
+ * it and a theme that wants it inset says so with a negative top margin.
+ */
+Separator { color: #dcdcdc; background-color: transparent; margin: 1.1mm 0 1.1mm 0; padding: 0; }
+GroupBox { background-color: transparent; border: 0.26mm solid #c6c6c8; border-radius: 1.59mm; padding: 1.1mm; margin: 1.1mm 0 1.1mm 0; }
+GroupBoxTitle { color: #7f7f7f; background-color: transparent; padding: 0 0 1.1mm 0; margin: 0; }
+Link { color: #007aff; background-color: transparent; text-decoration: underline; padding: 0; margin: 0; }
+
+/* --- Stepper (NSStepper / NumberBox / GtkSpinButton) ----------------------------------- */
+Stepper { background-color: transparent; padding: 0; margin: 0; }
+StepperField { color: #000000; background-color: #ffffff; border: 0.26mm solid #c6c6c8; border-radius: 1.59mm; padding: 1.1mm 2.6mm 1.1mm 2.6mm; margin: 0; }
+StepperButton { color: #000000; background-color: #ececec; border: 0.26mm solid #c6c6c8; border-radius: 1.59mm; padding: 1.1mm 1.1mm 1.1mm 1.1mm; margin: 0; }
+StepperButton.disabled { color: #7f7f7f; background-color: #ececec; border: 0.26mm solid #c6c6c8; border-radius: 1.59mm; padding: 1.1mm 1.1mm 1.1mm 1.1mm; margin: 0; }
+
+/* --- Menus, tooltips and the rest of the popup surfaces -------------------------------- */
+/*
+ * On Windows and macOS the menu BAR is the platform's own and never reaches these rules. What
+ * does reach them is everything Codename One still draws itself: the right-click context menu,
+ * the overflow menu, the tooltip, and the whole of the GNOME headerbar mode. None of the four
+ * was defined by any desktop theme, so each of them fell through to UIManager's blank default
+ * -- which on a dark window is black text on white.
+ */
+PopupContentPane { background-color: #ffffff; border: 0.26mm solid #c6c6c8; border-radius: 1.59mm; padding: 1.1mm 0 1.1mm 0; margin: 0; }
+CommandList { background-color: transparent; padding: 0; margin: 0; }
+Command { color: #000000; background-color: transparent; padding: 1.1mm 2.6mm 1.1mm 2.6mm; margin: 0; text-align: left; }
+Command.selected { color: #ffffff; background-color: #007aff; padding: 1.1mm 2.6mm 1.1mm 2.6mm; margin: 0; text-align: left; }
+Command.disabled { color: #7f7f7f; background-color: transparent; padding: 1.1mm 2.6mm 1.1mm 2.6mm; margin: 0; text-align: left; }
+TouchCommand { cn1-derive: Command; }
+TooltipDialog { background-color: #ffffff; border: 0.26mm solid #c6c6c8; border-radius: 1.59mm; padding: 0; margin: 0; }
+Tooltip { color: #000000; background-color: transparent; padding: 1.1mm 2.6mm 1.1mm 2.6mm; margin: 0; }
+
+/* --- Dialog command area ---------------------------------------------------------------- */
+/*
+ * Dialog, DialogTitle and DialogBody were already here; the buttons along the bottom were not,
+ * and they are most of what a desktop alert looks like. DialogButtonDefault is the one the
+ * platform emphasises -- the accented button on Windows and GNOME, the key-equivalent button
+ * on macOS.
+ */
+DialogCommandArea { background-color: transparent; padding: 1.1mm 2.6mm 1.1mm 2.6mm; margin: 0; }
+DialogButton { cn1-derive: Button; }
+DialogButtonDefault { cn1-derive: RaisedButton; }
+
+/* --- Search field, accordion and tabs ---------------------------------------------------- */
+/*
+ * ToolbarSearch is written by SearchBar, and the Accordion pair is seeded by
+ * UIManager.resetThemeProps with a plain line border and phone metrics. Both looked like a
+ * mobile control on a desktop window; defining them here suppresses the framework's seed.
+ */
+ToolbarSearch { color: #000000; background-color: #ffffff; border: 0.26mm solid #c6c6c8; border-radius: 1.59mm; padding: 1.1mm 2.6mm 1.1mm 2.6mm; margin: 0; }
+AccordionHeader { color: #000000; background-color: transparent; border-radius: 1.59mm; padding: 1.1mm 2.6mm 1.1mm 2.6mm; margin: 0; }
+AccordionItem { background-color: transparent; border: none; padding: 0 2.6mm 1.1mm 2.6mm; margin: 0; }
+Tabs { background-color: transparent; padding: 0; margin: 0; }
+
+
+/* --- Tabs ------------------------------------------------------------------------------- */
+/*
+ * The UIID a tab button actually gets is `Tab`, and its selected state is that button's own
+ * selected style. This theme previously defined SelectedTab and UnselectedTab, which nothing in
+ * the framework writes -- so the rules were dead and the tab strip fell through to
+ * UIManager.resetThemeProps, which seeds `Tab.sel#derive: Tab`. That seed makes the selected tab
+ * IDENTICAL to an unselected one: the captured Linux screenshot showed three plain boxes with no
+ * indication of which was open, and the DesktopTabs fidelity row scored 35-68%.
+ *
+ * TabbedPane is the content pane below the strip, TabsContainer the strip itself and
+ * TabsContainerHost its wrapper; all three are named by Tabs and none was defined here.
+ */
+TabbedPane { background-color: transparent; padding: 0; margin: 0; }
+TabsContainer { background-color: #ececec; padding: 0; margin: 0; }
+TabsContainerHost { background-color: #ececec; padding: 0; margin: 0; }
+Tab { color: #7f7f7f; background-color: transparent; border: none; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+Tab.selected { color: #ffffff; background-color: #007aff; border-radius: 1.59mm; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+Tab.pressed { color: #ffffff; background-color: #e8e8e8; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+
@media (prefers-color-scheme: dark) {
Form { background-color: #323232; color: #ffffff; }
Label { color: #ffffff; }
@@ -559,6 +683,43 @@ ListRendererFocus {
keeps TextField's near-white background on a dark form. Repeat every derive. */
TextArea { cn1-derive: TextField; }
HorizontalScrollThumb { cn1-derive: ScrollThumb; }
- DesktopScrollThumb { cn1-derive: ScrollThumb; }
- DesktopHorizontalScrollThumb { cn1-derive: ScrollThumb; }
+ DesktopScroll { background-color: transparent; padding: 0 1.98mm 0 1.98mm; margin: 0; }
+ DesktopScrollThumb { background-color: #5a5a5e; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 1.06mm 0 1.06mm; }
+ DesktopScrollThumb.selected { background-color: #8e8e93; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 1.06mm 0 1.06mm; }
+ DesktopScrollThumb.pressed { background-color: #aeaeb2; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 1.06mm 0 1.06mm; }
+ DesktopHorizontalScroll { background-color: transparent; padding: 1.98mm 0 1.98mm 0; margin: 0; }
+ DesktopHorizontalScrollThumb { background-color: #5a5a5e; cn1-background-type: cn1-pill-border; padding: 0; margin: 1.06mm 0 1.06mm 0; }
+ DesktopHorizontalScrollThumb.selected { background-color: #8e8e93; cn1-background-type: cn1-pill-border; padding: 0; margin: 1.06mm 0 1.06mm 0; }
+ DesktopHorizontalScrollThumb.pressed { background-color: #aeaeb2; cn1-background-type: cn1-pill-border; padding: 0; margin: 1.06mm 0 1.06mm 0; }
+ Separator { color: #48484a; background-color: transparent; margin: 1.1mm 0 1.1mm 0; padding: 0; }
+ GroupBox { background-color: transparent; border: 0.26mm solid #545456; border-radius: 1.59mm; padding: 1.1mm; margin: 1.1mm 0 1.1mm 0; }
+ GroupBoxTitle { color: #98989d; background-color: transparent; padding: 0 0 1.1mm 0; margin: 0; }
+ Link { color: #007aff; background-color: transparent; text-decoration: underline; padding: 0; margin: 0; }
+ Stepper { background-color: transparent; padding: 0; margin: 0; }
+ StepperField { color: #ffffff; background-color: #1e1e1e; border: 0.26mm solid #545456; border-radius: 1.59mm; padding: 1.1mm 2.6mm 1.1mm 2.6mm; margin: 0; }
+ StepperButton { color: #ffffff; background-color: #323232; border: 0.26mm solid #545456; border-radius: 1.59mm; padding: 1.1mm 1.1mm 1.1mm 1.1mm; margin: 0; }
+ StepperButton.disabled { color: #98989d; background-color: #323232; border: 0.26mm solid #545456; border-radius: 1.59mm; padding: 1.1mm 1.1mm 1.1mm 1.1mm; margin: 0; }
+ PopupContentPane { background-color: #2a2a2a; border: 0.26mm solid #545456; border-radius: 1.59mm; padding: 1.1mm 0 1.1mm 0; margin: 0; }
+ CommandList { background-color: transparent; padding: 0; margin: 0; }
+ Command { color: #ffffff; background-color: transparent; padding: 1.1mm 2.6mm 1.1mm 2.6mm; margin: 0; text-align: left; }
+ Command.selected { color: #ffffff; background-color: #007aff; padding: 1.1mm 2.6mm 1.1mm 2.6mm; margin: 0; text-align: left; }
+ Command.disabled { color: #98989d; background-color: transparent; padding: 1.1mm 2.6mm 1.1mm 2.6mm; margin: 0; text-align: left; }
+ TouchCommand { cn1-derive: Command; }
+ TooltipDialog { background-color: #2a2a2a; border: 0.26mm solid #545456; border-radius: 1.59mm; padding: 0; margin: 0; }
+ Tooltip { color: #ffffff; background-color: transparent; padding: 1.1mm 2.6mm 1.1mm 2.6mm; margin: 0; }
+ DialogCommandArea { background-color: transparent; padding: 1.1mm 2.6mm 1.1mm 2.6mm; margin: 0; }
+ DialogButton { cn1-derive: Button; }
+ DialogButtonDefault { cn1-derive: RaisedButton; }
+ ToolbarSearch { color: #ffffff; background-color: #1e1e1e; border: 0.26mm solid #545456; border-radius: 1.59mm; padding: 1.1mm 2.6mm 1.1mm 2.6mm; margin: 0; }
+ AccordionHeader { color: #ffffff; background-color: transparent; border-radius: 1.59mm; padding: 1.1mm 2.6mm 1.1mm 2.6mm; margin: 0; }
+ AccordionItem { background-color: transparent; border: none; padding: 0 2.6mm 1.1mm 2.6mm; margin: 0; }
+ Tabs { background-color: transparent; padding: 0; margin: 0; }
+
+ TabbedPane { background-color: transparent; padding: 0; margin: 0; }
+ TabsContainer { background-color: #323232; padding: 0; margin: 0; }
+ TabsContainerHost { background-color: #323232; padding: 0; margin: 0; }
+ Tab { color: #98989d; background-color: transparent; border: none; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+ Tab.selected { color: #ffffff; background-color: #007aff; border-radius: 1.59mm; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+ Tab.pressed { color: #ffffff; background-color: #3a3a3a; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+
}
diff --git a/native-themes/windows-fluent/theme.css b/native-themes/windows-fluent/theme.css
index c6f134a54df..15661d6d464 100644
--- a/native-themes/windows-fluent/theme.css
+++ b/native-themes/windows-fluent/theme.css
@@ -119,6 +119,28 @@
them. Without it a progress bar paints the legacy full-height fill, 19px against
this reference. */
progressTrackThicknessMM: "0.79";
+
+ /* ---- Desktop behaviour the ports used to have to be told about separately ----
+ Both of these are behaviours a native desktop theme IS, so the theme is where they
+ belong: these three files install only on the desktop, which means an application
+ still on the legacy theme is untouched and no port-side isDesktop() gate is needed.
+
+ interactiveScrollBool turns the fading touch indicator into a real scrollbar -- a
+ thumb that can be grabbed, a track that pages on click, a reserved gutter, and no
+ fade. scrollThumbMinSizeInt keeps that thumb grabbable on content far taller than
+ the viewport; 24px is the floor all three toolkits settle around.
+
+ defaultNativeWindowModeBool opens a Dialog as a real operating system window rather
+ than drawing it inside the application's own surface. Anchored popups (ComboBox,
+ Picker) never take it, and the constant is ignored wherever there is no windowing
+ system, so shared code needs no guard. */
+ interactiveScrollBool: true;
+ scrollThumbMinSizeInt: 24;
+ defaultNativeWindowModeBool: true;
+
+ /* Separator's rule thickness. A 1px hairline, which is what all three draw. */
+ separatorThicknessMM: "0.26";
+
}
/* --- Window and text ------------------------------------------------------------------- */
@@ -559,10 +581,34 @@ ScrollThumb.hover { background-color: #767676; cn1-background-type: cn1-pill-bor
ScrollThumb.pressed { background-color: #5d5d5d; cn1-background-type: cn1-pill-border; }
HorizontalScroll { background-color: transparent; }
HorizontalScrollThumb { cn1-derive: ScrollThumb; }
-DesktopScroll { background-color: transparent; }
-DesktopScrollThumb { cn1-derive: ScrollThumb; }
-DesktopHorizontalScroll { background-color: transparent; }
-DesktopHorizontalScrollThumb { cn1-derive: ScrollThumb; }
+
+/* --- The interactive desktop scrollbar ------------------------------------------------- */
+/*
+ * These four are NOT the mobile Scroll/ScrollThumb above. LookAndFeel.initScroll swaps to
+ * them when interactiveScrollBool is on, and until now this theme derived the thumb from the
+ * mobile one -- which meant a desktop scrollbar with no gutter, no minimum length and no
+ * highlight, on a theme whose whole job is to look like the platform.
+ *
+ * The gutter width is DesktopScroll's horizontal padding plus its margin
+ * (LookAndFeel.getVerticalScrollWidth sums exactly those), and the thumb is inset from it by
+ * its own margin. That is how a thin thumb sits in a wider track.
+ *
+ * The highlight states are .selected and .pressed, NOT .hover: LookAndFeel's
+ * InteractiveScrollThumb returns getSelectedStyle() while the pointer is over the thumb and
+ * getPressedStyle() while it is being dragged. A .hover rule here would compile, and would
+ * never be painted.
+ *
+ * Properties are spelled out rather than cn1-derive'd from ScrollThumb, because deriving is
+ * what left them wrong.
+ */
+DesktopScroll { background-color: transparent; padding: 0 1.59mm 0 1.59mm; margin: 0; }
+DesktopScrollThumb { background-color: #8a8a8a; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 0.79mm 0 0.79mm; }
+DesktopScrollThumb.selected { background-color: #767676; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 0.79mm 0 0.79mm; }
+DesktopScrollThumb.pressed { background-color: #5d5d5d; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 0.79mm 0 0.79mm; }
+DesktopHorizontalScroll { background-color: transparent; padding: 1.59mm 0 1.59mm 0; margin: 0; }
+DesktopHorizontalScrollThumb { background-color: #8a8a8a; cn1-background-type: cn1-pill-border; padding: 0; margin: 0.79mm 0 0.79mm 0; }
+DesktopHorizontalScrollThumb.selected { background-color: #767676; cn1-background-type: cn1-pill-border; padding: 0; margin: 0.79mm 0 0.79mm 0; }
+DesktopHorizontalScrollThumb.pressed { background-color: #5d5d5d; cn1-background-type: cn1-pill-border; padding: 0; margin: 0.79mm 0 0.79mm 0; }
/* NavigationView's selected row: a layer fill with an accent bar, approximated here by the
fill; the bar needs a per-side border and is tracked rather than faked. */
@@ -595,6 +641,99 @@ ListRendererFocus {
/* --- Dark ------------------------------------------------------------------------------ */
+
+/* --- Grouping, rules and links --------------------------------------------------------- */
+/*
+ * Separator is the component's own UIID: its foreground colour is the rule and its margin is
+ * the air either side. GroupBox is the frame and GroupBoxTitle the caption -- nothing here
+ * positions the caption relative to the top edge, because the three platforms disagree about
+ * it and a theme that wants it inset says so with a negative top margin.
+ */
+Separator { color: #e5e5e5; background-color: transparent; margin: 1.6mm 0 1.6mm 0; padding: 0; }
+GroupBox { background-color: transparent; border: 0.26mm solid #e9e9e9; border-radius: 1.06mm; padding: 1.6mm; margin: 1.6mm 0 1.6mm 0; }
+GroupBoxTitle { color: #5d5d5d; background-color: transparent; padding: 0 0 1.6mm 0; margin: 0; }
+Link { color: #0078d4; background-color: transparent; text-decoration: underline; padding: 0; margin: 0; }
+Link.hover { color: #0078d4; background-color: transparent; text-decoration: underline; padding: 0; margin: 0; }
+
+/* --- Stepper (NSStepper / NumberBox / GtkSpinButton) ----------------------------------- */
+Stepper { background-color: transparent; padding: 0; margin: 0; }
+StepperField { color: #1a1a1a; background-color: #ffffff; border: 0.26mm solid #e9e9e9; border-radius: 1.06mm; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; }
+StepperButton { color: #1a1a1a; background-color: #f3f3f3; border: 0.26mm solid #e9e9e9; border-radius: 1.06mm; padding: 1.6mm 1.6mm 1.6mm 1.6mm; margin: 0; }
+StepperButton.disabled { color: #5d5d5d; background-color: #f3f3f3; border: 0.26mm solid #e9e9e9; border-radius: 1.06mm; padding: 1.6mm 1.6mm 1.6mm 1.6mm; margin: 0; }
+StepperButton.hover { color: #1a1a1a; background-color: #f0f0f0; border: 0.26mm solid #e9e9e9; border-radius: 1.06mm; padding: 1.6mm 1.6mm 1.6mm 1.6mm; margin: 0; }
+
+/* --- Menus, tooltips and the rest of the popup surfaces -------------------------------- */
+/*
+ * On Windows and macOS the menu BAR is the platform's own and never reaches these rules. What
+ * does reach them is everything Codename One still draws itself: the right-click context menu,
+ * the overflow menu, the tooltip, and the whole of the GNOME headerbar mode. None of the four
+ * was defined by any desktop theme, so each of them fell through to UIManager's blank default
+ * -- which on a dark window is black text on white.
+ */
+PopupContentPane { background-color: #fbfbfb; border: 0.26mm solid #e9e9e9; border-radius: 2.12mm; padding: 1.6mm 0 1.6mm 0; margin: 0; }
+CommandList { background-color: transparent; padding: 0; margin: 0; }
+Command { color: #1a1a1a; background-color: transparent; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; text-align: left; }
+Command.selected { color: #ffffff; background-color: #0078d4; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; text-align: left; }
+Command.disabled { color: #5d5d5d; background-color: transparent; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; text-align: left; }
+TouchCommand { cn1-derive: Command; }
+TooltipDialog { background-color: #fbfbfb; border: 0.26mm solid #e9e9e9; border-radius: 1.06mm; padding: 0; margin: 0; }
+Tooltip { color: #1a1a1a; background-color: transparent; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; }
+Command.hover { color: #1a1a1a; background-color: #f0f0f0; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; text-align: left; }
+
+/* --- Dialog command area ---------------------------------------------------------------- */
+/*
+ * Dialog, DialogTitle and DialogBody were already here; the buttons along the bottom were not,
+ * and they are most of what a desktop alert looks like. DialogButtonDefault is the one the
+ * platform emphasises -- the accented button on Windows and GNOME, the key-equivalent button
+ * on macOS.
+ */
+DialogCommandArea { background-color: transparent; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; }
+DialogButton { cn1-derive: Button; }
+DialogButtonDefault { cn1-derive: RaisedButton; }
+
+/* --- Search field, accordion and tabs ---------------------------------------------------- */
+/*
+ * ToolbarSearch is written by SearchBar, and the Accordion pair is seeded by
+ * UIManager.resetThemeProps with a plain line border and phone metrics. Both looked like a
+ * mobile control on a desktop window; defining them here suppresses the framework's seed.
+ */
+ToolbarSearch { color: #1a1a1a; background-color: #ffffff; border: 0.26mm solid #e9e9e9; border-radius: 1.06mm; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; }
+AccordionHeader { color: #1a1a1a; background-color: transparent; border-radius: 1.06mm; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; }
+AccordionItem { background-color: transparent; border: none; padding: 0 2.6mm 1.6mm 2.6mm; margin: 0; }
+Tabs { background-color: transparent; padding: 0; margin: 0; }
+AccordionHeader.hover { color: #1a1a1a; background-color: #f0f0f0; border-radius: 1.06mm; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; }
+
+
+/* --- Tabs ------------------------------------------------------------------------------- */
+/*
+ * The UIID a tab button actually gets is `Tab`, and its selected state is that button's own
+ * selected style. This theme previously defined SelectedTab and UnselectedTab, which nothing in
+ * the framework writes -- so the rules were dead and the tab strip fell through to
+ * UIManager.resetThemeProps, which seeds `Tab.sel#derive: Tab`. That seed makes the selected tab
+ * IDENTICAL to an unselected one: the captured Linux screenshot showed three plain boxes with no
+ * indication of which was open, and the DesktopTabs fidelity row scored 35-68%.
+ *
+ * TabbedPane is the content pane below the strip, TabsContainer the strip itself and
+ * TabsContainerHost its wrapper; all three are named by Tabs and none was defined here.
+ */
+TabbedPane { background-color: transparent; padding: 0; margin: 0; }
+/* The divider under the tab strip. GtkNotebook draws one and so does a WinUI TabView;
+ NSTabView does not, which is why the Aqua theme has no such rule -- its pill sits on
+ the bare window background.
+
+ It is also what makes the row MEASURE like the native one. A Tab is transparent until
+ it is selected, so without the divider the only content in the tile is the selected
+ tab's fill and the two labels: the comparator's bbox came out 64%% of the native
+ width where it had been 100%%. Drawing the line the platform actually draws fixes the
+ look and the measurement together, which is the only kind of fix worth making to a
+ geometry number. */
+TabsContainer { background-color: #f3f3f3; border-bottom: 0.26mm solid #e5e5e5; padding: 0; margin: 0; }
+TabsContainerHost { background-color: #f3f3f3; padding: 0; margin: 0; }
+Tab { color: #5d5d5d; background-color: transparent; border: none; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+Tab.selected { color: #1a1a1a; background-color: #fbfbfb; border-radius: 1.06mm; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+Tab.pressed { color: #1a1a1a; background-color: #f0f0f0; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+Tab.hover { color: #1a1a1a; background-color: #f0f0f0; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+
@media (prefers-color-scheme: dark) {
Form { background-color: #202020; color: #ffffff; }
Label { color: #ffffff; }
@@ -685,6 +824,48 @@ ListRendererFocus {
keeps TextField's near-white background on a dark form. Repeat every derive. */
TextArea { cn1-derive: TextField; }
HorizontalScrollThumb { cn1-derive: ScrollThumb; }
- DesktopScrollThumb { cn1-derive: ScrollThumb; }
- DesktopHorizontalScrollThumb { cn1-derive: ScrollThumb; }
+ DesktopScroll { background-color: transparent; padding: 0 1.59mm 0 1.59mm; margin: 0; }
+ DesktopScrollThumb { background-color: #9a9a9a; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 0.79mm 0 0.79mm; }
+ DesktopScrollThumb.selected { background-color: #aaaaaa; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 0.79mm 0 0.79mm; }
+ DesktopScrollThumb.pressed { background-color: #cfcfcf; cn1-background-type: cn1-pill-border; padding: 0; margin: 0 0.79mm 0 0.79mm; }
+ DesktopHorizontalScroll { background-color: transparent; padding: 1.59mm 0 1.59mm 0; margin: 0; }
+ DesktopHorizontalScrollThumb { background-color: #9a9a9a; cn1-background-type: cn1-pill-border; padding: 0; margin: 0.79mm 0 0.79mm 0; }
+ DesktopHorizontalScrollThumb.selected { background-color: #aaaaaa; cn1-background-type: cn1-pill-border; padding: 0; margin: 0.79mm 0 0.79mm 0; }
+ DesktopHorizontalScrollThumb.pressed { background-color: #cfcfcf; cn1-background-type: cn1-pill-border; padding: 0; margin: 0.79mm 0 0.79mm 0; }
+ Separator { color: #2d2d2d; background-color: transparent; margin: 1.6mm 0 1.6mm 0; padding: 0; }
+ GroupBox { background-color: transparent; border: 0.26mm solid #363636; border-radius: 1.06mm; padding: 1.6mm; margin: 1.6mm 0 1.6mm 0; }
+ GroupBoxTitle { color: #cfcfcf; background-color: transparent; padding: 0 0 1.6mm 0; margin: 0; }
+ Link { color: #4cc2ff; background-color: transparent; text-decoration: underline; padding: 0; margin: 0; }
+ Link.hover { color: #4cc2ff; background-color: transparent; text-decoration: underline; padding: 0; margin: 0; }
+ Stepper { background-color: transparent; padding: 0; margin: 0; }
+ StepperField { color: #ffffff; background-color: #1f1f1f; border: 0.26mm solid #363636; border-radius: 1.06mm; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; }
+ StepperButton { color: #ffffff; background-color: #202020; border: 0.26mm solid #363636; border-radius: 1.06mm; padding: 1.6mm 1.6mm 1.6mm 1.6mm; margin: 0; }
+ StepperButton.disabled { color: #cfcfcf; background-color: #202020; border: 0.26mm solid #363636; border-radius: 1.06mm; padding: 1.6mm 1.6mm 1.6mm 1.6mm; margin: 0; }
+ StepperButton.hover { color: #ffffff; background-color: #323232; border: 0.26mm solid #363636; border-radius: 1.06mm; padding: 1.6mm 1.6mm 1.6mm 1.6mm; margin: 0; }
+ PopupContentPane { background-color: #2c2c2c; border: 0.26mm solid #363636; border-radius: 2.12mm; padding: 1.6mm 0 1.6mm 0; margin: 0; }
+ CommandList { background-color: transparent; padding: 0; margin: 0; }
+ Command { color: #ffffff; background-color: transparent; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; text-align: left; }
+ Command.selected { color: #000000; background-color: #4cc2ff; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; text-align: left; }
+ Command.disabled { color: #cfcfcf; background-color: transparent; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; text-align: left; }
+ TouchCommand { cn1-derive: Command; }
+ TooltipDialog { background-color: #2c2c2c; border: 0.26mm solid #363636; border-radius: 1.06mm; padding: 0; margin: 0; }
+ Tooltip { color: #ffffff; background-color: transparent; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; }
+ Command.hover { color: #ffffff; background-color: #323232; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; text-align: left; }
+ DialogCommandArea { background-color: transparent; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; }
+ DialogButton { cn1-derive: Button; }
+ DialogButtonDefault { cn1-derive: RaisedButton; }
+ ToolbarSearch { color: #ffffff; background-color: #1f1f1f; border: 0.26mm solid #363636; border-radius: 1.06mm; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; }
+ AccordionHeader { color: #ffffff; background-color: transparent; border-radius: 1.06mm; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; }
+ AccordionItem { background-color: transparent; border: none; padding: 0 2.6mm 1.6mm 2.6mm; margin: 0; }
+ Tabs { background-color: transparent; padding: 0; margin: 0; }
+ AccordionHeader.hover { color: #ffffff; background-color: #323232; border-radius: 1.06mm; padding: 1.6mm 2.6mm 1.6mm 2.6mm; margin: 0; }
+
+ TabbedPane { background-color: transparent; padding: 0; margin: 0; }
+ TabsContainer { background-color: #202020; border-bottom: 0.26mm solid #2d2d2d; padding: 0; margin: 0; }
+ TabsContainerHost { background-color: #202020; padding: 0; margin: 0; }
+ Tab { color: #cfcfcf; background-color: transparent; border: none; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+ Tab.selected { color: #ffffff; background-color: #2c2c2c; border-radius: 1.06mm; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+ Tab.pressed { color: #ffffff; background-color: #323232; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+ Tab.hover { color: #ffffff; background-color: #323232; padding: 1.6mm 3.2mm 1.6mm 3.2mm; margin: 0; }
+
}
diff --git a/scripts/android/screenshots/DesktopChromeTheme_dark.png b/scripts/android/screenshots/DesktopChromeTheme_dark.png
new file mode 100644
index 00000000000..f7aba5cf8ef
Binary files /dev/null and b/scripts/android/screenshots/DesktopChromeTheme_dark.png differ
diff --git a/scripts/android/screenshots/DesktopChromeTheme_light.png b/scripts/android/screenshots/DesktopChromeTheme_light.png
new file mode 100644
index 00000000000..544c42bf333
Binary files /dev/null and b/scripts/android/screenshots/DesktopChromeTheme_light.png differ
diff --git a/scripts/android/screenshots/DesktopScrollbarTheme_dark.png b/scripts/android/screenshots/DesktopScrollbarTheme_dark.png
new file mode 100644
index 00000000000..aae71a8006d
Binary files /dev/null and b/scripts/android/screenshots/DesktopScrollbarTheme_dark.png differ
diff --git a/scripts/android/screenshots/DesktopScrollbarTheme_light.png b/scripts/android/screenshots/DesktopScrollbarTheme_light.png
new file mode 100644
index 00000000000..29272653f8f
Binary files /dev/null and b/scripts/android/screenshots/DesktopScrollbarTheme_light.png differ
diff --git a/scripts/android/screenshots/DesktopWidgetsTheme_dark.png b/scripts/android/screenshots/DesktopWidgetsTheme_dark.png
new file mode 100644
index 00000000000..e9b52f4b3e9
Binary files /dev/null and b/scripts/android/screenshots/DesktopWidgetsTheme_dark.png differ
diff --git a/scripts/android/screenshots/DesktopWidgetsTheme_light.png b/scripts/android/screenshots/DesktopWidgetsTheme_light.png
new file mode 100644
index 00000000000..3b59e176bb7
Binary files /dev/null and b/scripts/android/screenshots/DesktopWidgetsTheme_light.png differ
diff --git a/scripts/build-gnome-native-ref.sh b/scripts/build-gnome-native-ref.sh
index 603d4e9afee..bce7ed56c1c 100755
--- a/scripts/build-gnome-native-ref.sh
+++ b/scripts/build-gnome-native-ref.sh
@@ -23,7 +23,11 @@ OUT_DIR="$REPO_ROOT/artifacts/desktop-native-ref/gnome"
mkdir -p "$OUT_DIR"
log "Compiling $SRC"
-cc -O1 -std=c11 -Wall -o "$BUILD/native-ref" "$SRC" \
+# -g and -rdynamic are for the fatal-signal handler in native-ref.c: without the dynamic
+# symbol table backtrace_symbols_fd prints bare addresses, which turns a crash report into
+# a hex dump nobody can act on. They cost build time and binary size in a tool that is
+# thrown away after one capture.
+cc -O1 -g -rdynamic -std=c11 -Wall -o "$BUILD/native-ref" "$SRC" \
$(pkg-config --cflags --libs gtk4 libadwaita-1)
# A bare Xvfb has no window manager, so nothing ever takes focus and every GTK toplevel
diff --git a/scripts/check-fidelity-spec.py b/scripts/check-fidelity-spec.py
index 2ddd4d24c5f..3a1251f1dd0 100755
--- a/scripts/check-fidelity-spec.py
+++ b/scripts/check-fidelity-spec.py
@@ -286,6 +286,17 @@ def main():
"DesktopCheckBox": "Check",
"DesktopRadioButton": "Radio",
"DesktopComboBox": "Option",
+ # Second wave. Every text-bearing row belongs here: this table is what caught all six of
+ # the first wave rendering different strings on the two sides, which capped the text
+ # field at 65% until it was found.
+ "DesktopGroupBox": "Group",
+ "DesktopLinkButton": "Link",
+ "DesktopSearchField": "Search",
+ "DesktopListRow": "Row",
+ "DesktopDisclosure": "Details",
+ "DesktopMenuBar": "File",
+ "DesktopMenuItem": "Open",
+ "DesktopTooltip": "Tooltip",
}
@@ -333,31 +344,58 @@ def matched(pattern, source, group=1):
return match.group(group) if match else None
+def first_nonempty(pattern, source, group=1):
+ """The first NON-EMPTY capture, not simply the first.
+
+ A composite reference builds more than one labelled thing, and the one that carries the
+ text is not always first: the macOS disclosure is an empty-titled NSButton for the
+ triangle followed by the label that actually says "Details". Taking match one there
+ reads the empty string and reports drift that is not there.
+ """
+ for match in re.finditer(pattern, source, re.S | re.M):
+ if match.group(group):
+ return match.group(group)
+ return None
+
+
def native_label(platform, src, rid, kind):
"""Read the deliberately small reference-app constructor tables, failing closed on drift."""
rid, kind = re.escape(rid), re.escape(kind)
if platform == "windows":
mapping = matched(r'new\(\s*"' + rid + r'"\s*,\s*"([^"\n]+)"', src)
body = matched(r'^\s*"' + kind + r'"\s*=>\s*(.*?)(?=^\s*(?:"\w+"|_)\s*=>)', src) or ""
- if re.fullmatch(r'MakeComboBox\(\),\s*', body):
- body = matched(r'ComboBox MakeComboBox\(\)\s*\{(.*?)^\s*\}', src) or ""
- label = matched(r'\.Items\.Add\(\s*"([^"\n]*)"\s*\)', body)
+ # A kind whose arm is just a factory call: follow it into that method's body, so the
+ # literal is still read from the one place that builds this kind and not from a
+ # neighbouring arm.
+ factory = matched(r'^\s*(Make\w+)\(\),\s*$', body)
+ if factory:
+ body = matched(r'\b' + factory + r'\(\)\s*\{(.*?)^\s*\}', src) or ""
+ label = first_nonempty(
+ r'(?:\.Items\.Add\(\s*|\b(?:Content|Text|Header|Title)\s*=\s*)"([^"\n]*)"',
+ body)
else:
- label = matched(r'\b(?:Content|Text)\s*=\s*"([^"\n]*)"', body)
+ label = first_nonempty(r'\b(?:Content|Text|Header|Title)\s*=\s*"([^"\n]*)"', body)
elif platform == "macos":
mapping = matched(r'Spec\(id:\s*"' + rid + r'",\s*kind:\s*"([^"\n]+)"', src)
body = matched(r'case "' + kind + r'":(.*?)(?=^\s*(?:case |default:))', src) or ""
- label = matched(r'(?:NSButton\((?:title|checkboxWithTitle|radioButtonWithTitle):|'
- r'NSTextField\(string:|\.addItem\(withTitle:)\s*"([^"\n]*)"', body)
+ label = first_nonempty(
+ r'(?:NS(?:Button|SearchField|TextField)\((?:title|checkboxWithTitle|'
+ r'radioButtonWithTitle|string|labelWithString):|\.addItem\(withTitle:|'
+ r'\w+\.title\s*=)\s*"([^"\n]*)"', body)
else:
mapping = matched(r'\{\s*"' + rid + r'",\s*"([^"\n]+)"', src)
# Scope to make_widget: other functions also branch on these kind strings.
factory = src.split('static GtkWidget *make_widget(', 1)[-1]
body = matched(r'if \(strcmp\(kind, "' + kind + r'"\) == 0\) \{(.*?)'
r'(?=^ if \(strcmp\(kind,|^ blocker\()', factory) or ""
- label = matched(r'(?:gtk_(?:button|check_button)_new_with_label\(|'
- r'gtk_editable_set_text\(GTK_EDITABLE\(\w+\),|'
- r'const char \*items\[\]\s*=\s*\{)\s*"([^"\n]*)"', body)
+ label = first_nonempty(
+ r'(?:gtk_(?:button|check_button)_new_with_label\(|'
+ r'gtk_editable_set_text\(GTK_EDITABLE\(\w+\),|'
+ r'gtk_link_button_new_with_label\("[^"\n]*",\s*|'
+ r'gtk_(?:frame|expander|label)_new\(|'
+ r'g_menu_append_submenu\(\w+,\s*|'
+ r'adw_window_title_new\(|'
+ r'const char \*items\[\]\s*=\s*\{)\s*"([^"\n]*)"', body)
return mapping, label
diff --git a/scripts/check-native-cpp-linkage.py b/scripts/check-native-cpp-linkage.py
new file mode 100755
index 00000000000..41ab2f7e8a4
--- /dev/null
+++ b/scripts/check-native-cpp-linkage.py
@@ -0,0 +1,269 @@
+#!/usr/bin/env python3
+"""Every ParparVM native defined in a C++ translation unit must have C linkage.
+
+ParparVM generates C that calls a native by its exact symbol name. A C++ compiler
+mangles a function's name unless it is declared `extern "C"`, so a native defined in a
+.cpp or .mm file without that declaration compiles cleanly, exports a mangled symbol,
+and leaves the name the generated code calls undefined -- a link error on the device,
+in a file nobody touched, naming a symbol that is visibly right there in the source.
+
+This is not hypothetical. PR #5845 shipped exactly that in cn1_windows_window.cpp and
+only a real Windows build caught it, because the two checks that look at natives cannot
+see it: check-native-signatures.sh verifies that the NAME matches the Java method, and
+it does -- linkage is not part of a name. Nothing else reads these files at all.
+
+The check is deliberately absolute, with no baseline: a native without C linkage is
+never intentional, and the fix is always the same one line.
+"""
+
+import re
+import subprocess
+import sys
+
+# The generated code calls natives by these prefixes. A function whose name begins with a
+# Java package path is a native entry point; anything else in these files is port-internal
+# C++ that is supposed to be mangled.
+NATIVE_PREFIX = re.compile(r'\b((?:com|net|org|java)_[A-Za-z0-9_]*_[A-Za-z0-9_]+)\s*\(')
+
+DEFINITION = re.compile(
+ r'(?:^|\n)[ \t]*(?:[A-Za-z_][A-Za-z0-9_]*[ \t\r\n*&]+)+'
+ r'((?:com|net|org|java)_[A-Za-z0-9_]+)[ \t\r\n]*\(')
+
+
+def blank_comments_and_literals(text):
+ """Replace comments and string/char literals with spaces, preserving line structure.
+
+ Offsets and line numbers have to survive: findings are reported by line, and the
+ brace scan below would otherwise count a brace inside a string literal.
+ """
+ out = []
+ i = 0
+ n = len(text)
+ while i < n:
+ c = text[i]
+ two = text[i:i + 2]
+ if two == '//':
+ while i < n and text[i] != '\n':
+ out.append(' ')
+ i += 1
+ elif two == '/*':
+ while i < n and text[i:i + 2] != '*/':
+ out.append('\n' if text[i] == '\n' else ' ')
+ i += 1
+ out.append(' ')
+ i += 2
+ elif c in '"\'':
+ quote = c
+ out.append(' ')
+ i += 1
+ while i < n:
+ if text[i] == '\\':
+ out.append(' ')
+ i += 2
+ continue
+ if text[i] == quote:
+ out.append(' ')
+ i += 1
+ break
+ out.append('\n' if text[i] == '\n' else ' ')
+ i += 1
+ else:
+ out.append(c)
+ i += 1
+ return ''.join(out)
+
+
+def extern_c_spans(raw, clean):
+ """Offset ranges covered by an `extern "C" { ... }` block.
+
+ The literal has to be found in the RAW text -- blanking removes the "C" -- while the
+ braces are counted in the CLEANED text, where a brace inside a literal cannot lie.
+ """
+ spans = []
+ for m in re.finditer(r'extern[ \t\r\n]*"C"[ \t\r\n]*\{', raw):
+ depth = 0
+ i = m.end() - 1
+ while i < len(clean):
+ if clean[i] == '{':
+ depth += 1
+ elif clean[i] == '}':
+ depth -= 1
+ if depth == 0:
+ spans.append((m.start(), i))
+ break
+ i += 1
+ else:
+ spans.append((m.start(), len(clean)))
+ return spans
+
+
+def declared_extern_c(raw, decl_start, name_start):
+ """True for `extern "C" JAVA_VOID foo(...)` -- the single-declaration form.
+
+ Anchored to THIS declaration, between its first token and the symbol name, rather
+ than to a window of preceding text. A loose lookback would accept
+
+ extern "C" void somethingElse(void);
+ JAVA_VOID com_codename1_...(...) { }
+
+ where the second function has no C linkage at all and the `extern "C"` belongs to the
+ line above it. Offsets line up because blanking comments and literals preserves length.
+ """
+ return re.match(
+ r'[ \t\r\n]*extern[ \t\r\n]*"C"[ \t\r\n]*(?:[A-Za-z_][A-Za-z0-9_]*[ \t\r\n*&]+)*$',
+ raw[decl_start:name_start]) is not None
+
+
+def check(path):
+ with open(path, 'r', encoding='utf-8', errors='replace') as fh:
+ return check_text(fh.read())
+
+
+def check_text(raw):
+ clean = blank_comments_and_literals(raw)
+ spans = extern_c_spans(raw, clean)
+ findings = []
+ for m in DEFINITION.finditer(clean):
+ start = m.start(1)
+ # A declaration (ends in ';') is not a definition and needs no linkage of its own,
+ # but it is also harmless to require it -- what matters is that the DEFINITION has
+ # it, so look ahead for the body.
+ tail = clean[m.end():m.end() + 4000]
+ closing = tail.find(')')
+ if closing < 0:
+ continue
+ after = tail[closing + 1:closing + 40].lstrip()
+ if not after.startswith('{'):
+ continue
+ if any(lo <= start <= hi for lo, hi in spans):
+ continue
+ if declared_extern_c(raw, m.start(), start):
+ continue
+ findings.append((clean.count('\n', 0, start) + 1, m.group(1)))
+ return findings
+
+
+
+# Each fixture is (source, expected symbols reported). They encode the cases this gate has
+# to get right, and they exist because the way a gate like this fails is silent: a parser
+# that stops recognising a definition reports zero findings, which is indistinguishable
+# from a clean tree. --self-test is wired into the same CI step as the gate itself.
+SELF_TEST_CASES = (
+ (
+ 'native inside an extern "C" block is fine',
+ 'extern "C" {\n'
+ 'JAVA_VOID com_codename1_impl_windows_WindowsNative_wrapped___int(void *t, int a) {\n'
+ '}\n'
+ '}\n',
+ [],
+ ),
+ (
+ 'native declared extern "C" on its own is fine',
+ 'extern "C" JAVA_VOID com_codename1_impl_windows_WindowsNative_single___int(void *t, int a) {\n'
+ '}\n',
+ [],
+ ),
+ (
+ 'a prototype is not a definition',
+ 'JAVA_VOID com_codename1_impl_windows_WindowsNative_proto___int(void *t, int a);\n',
+ [],
+ ),
+ (
+ 'a port-internal C++ helper is supposed to be mangled',
+ 'static int helperNotANative(int x) { return x + 1; }\n',
+ [],
+ ),
+ (
+ 'a native in a comment is not a definition',
+ '/*\nJAVA_VOID com_codename1_impl_windows_WindowsNative_commented___int(void *t, int a) {\n}\n*/\n',
+ [],
+ ),
+ (
+ 'a brace inside a string literal does not close the block',
+ 'extern "C" {\n'
+ 'JAVA_VOID com_codename1_impl_windows_WindowsNative_literal___int(void *t, int a) {\n'
+ ' printf("}");\n'
+ '}\n'
+ 'JAVA_VOID com_codename1_impl_windows_WindowsNative_after___int(void *t, int a) {\n'
+ '}\n'
+ '}\n',
+ [],
+ ),
+ (
+ 'a native at file scope is reported',
+ 'JAVA_VOID com_codename1_impl_windows_WindowsNative_unwrapped___int(void *t, int a) {\n'
+ '}\n',
+ ['com_codename1_impl_windows_WindowsNative_unwrapped___int'],
+ ),
+ (
+ 'an extern "C" belonging to the line above does not cover the next definition',
+ 'extern "C" void somethingElse(void);\n'
+ 'JAVA_VOID com_codename1_impl_windows_WindowsNative_adjacent___int(void *t, int a) {\n'
+ '}\n',
+ ['com_codename1_impl_windows_WindowsNative_adjacent___int'],
+ ),
+ (
+ 'a native inside a namespace is reported -- a namespace mangles too',
+ 'namespace cn1 {\n'
+ 'JAVA_VOID com_codename1_impl_windows_WindowsNative_namespaced___int(void *t, int a) {\n'
+ '}\n'
+ '}\n',
+ ['com_codename1_impl_windows_WindowsNative_namespaced___int'],
+ ),
+)
+
+
+def self_test():
+ failures = 0
+ for name, source, expected in SELF_TEST_CASES:
+ got = [sym for _, sym in check_text(source)]
+ if got != expected:
+ failures += 1
+ print('SELF-TEST FAIL: %s' % name)
+ print(' expected: %s' % (expected,))
+ print(' got: %s' % (got,))
+ if failures:
+ print('\ncheck-native-cpp-linkage: %d of %d self-test case(s) failed.'
+ % (failures, len(SELF_TEST_CASES)), file=sys.stderr)
+ return 1
+ print('check-native-cpp-linkage: %d self-test case(s) passed.' % len(SELF_TEST_CASES))
+ return 0
+
+
+def main(argv):
+ if '--self-test' in argv:
+ return self_test()
+ if argv:
+ paths = argv
+ else:
+ out = subprocess.run(['git', 'ls-files', '*.cpp', '*.cc', '*.cxx', '*.mm'],
+ capture_output=True, text=True, check=True).stdout
+ paths = [p for p in out.split() if p]
+ total = 0
+ bad = 0
+ for path in paths:
+ try:
+ findings = check(path)
+ except IOError:
+ continue
+ total += 1
+ for line, name in findings:
+ bad += 1
+ print('%s:%d: ParparVM native %s is defined without C linkage.' % (path, line, name))
+ print(' The C++ compiler will mangle it and the generated code will not link.')
+ print(' Move it inside the file\'s extern "C" block, or mark it extern "C".')
+ if total == 0:
+ # A check that examined nothing must never report success: an empty file list means
+ # the glob or the invocation is wrong, not that the tree is clean.
+ print('check-native-cpp-linkage: FATAL: no C++ files examined.', file=sys.stderr)
+ return 2
+ if bad:
+ print('\ncheck-native-cpp-linkage: %d native(s) without C linkage in %d file(s).'
+ % (bad, total), file=sys.stderr)
+ return 1
+ print('check-native-cpp-linkage: %d file(s) clean.' % total)
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/scripts/fidelity-app/baseline/gnome-adwaita-fidelity-baseline.json b/scripts/fidelity-app/baseline/gnome-adwaita-fidelity-baseline.json
index 3dcacd5e03c..d0781ed59ea 100644
--- a/scripts/fidelity-app/baseline/gnome-adwaita-fidelity-baseline.json
+++ b/scripts/fidelity-app/baseline/gnome-adwaita-fidelity-baseline.json
@@ -150,6 +150,116 @@
"height_ratio": 0.8438,
"width_ratio": 0.8409
},
+ "DesktopDisclosure_normal_dark": {
+ "center_offset": 3.61,
+ "height_ratio": 0.6923,
+ "width_ratio": 0.6491
+ },
+ "DesktopDisclosure_normal_light": {
+ "center_offset": 3.61,
+ "height_ratio": 0.6923,
+ "width_ratio": 0.6491
+ },
+ "DesktopGroupBox_normal_dark": {
+ "center_offset": 0.0,
+ "height_ratio": 1.0,
+ "width_ratio": 1.0
+ },
+ "DesktopGroupBox_normal_light": {
+ "center_offset": 0.0,
+ "height_ratio": 1.0,
+ "width_ratio": 1.0
+ },
+ "DesktopLinkButton_disabled_dark": {
+ "center_offset": 20.35,
+ "height_ratio": 0.9231,
+ "width_ratio": 0.7692
+ },
+ "DesktopLinkButton_disabled_light": {
+ "center_offset": 20.35,
+ "height_ratio": 0.9231,
+ "width_ratio": 0.7692
+ },
+ "DesktopLinkButton_hover_dark": {
+ "center_offset": 20.35,
+ "height_ratio": 0.9231,
+ "width_ratio": 0.7692
+ },
+ "DesktopLinkButton_hover_light": {
+ "center_offset": 20.35,
+ "height_ratio": 0.9231,
+ "width_ratio": 0.7692
+ },
+ "DesktopLinkButton_normal_dark": {
+ "center_offset": 20.35,
+ "height_ratio": 0.9231,
+ "width_ratio": 0.7692
+ },
+ "DesktopLinkButton_normal_light": {
+ "center_offset": 20.35,
+ "height_ratio": 0.9231,
+ "width_ratio": 0.7692
+ },
+ "DesktopListRow_normal_dark": {
+ "center_offset": 3.91,
+ "height_ratio": 0.7273,
+ "width_ratio": 0.7857
+ },
+ "DesktopListRow_normal_light": {
+ "center_offset": 3.91,
+ "height_ratio": 0.7273,
+ "width_ratio": 0.7857
+ },
+ "DesktopListRow_selected_dark": {
+ "center_offset": 2.5,
+ "height_ratio": 0.8276,
+ "width_ratio": 1.0
+ },
+ "DesktopListRow_selected_light": {
+ "center_offset": 2.5,
+ "height_ratio": 0.8276,
+ "width_ratio": 1.0
+ },
+ "DesktopMenuBar_normal_dark": {
+ "center_offset": 1.0,
+ "height_ratio": 0.9,
+ "width_ratio": 1.0
+ },
+ "DesktopMenuBar_normal_light": {
+ "center_offset": 1.0,
+ "height_ratio": 0.9,
+ "width_ratio": 1.0
+ },
+ "DesktopMenuItem_disabled_dark": {
+ "center_offset": 9.18,
+ "height_ratio": 0.6667,
+ "width_ratio": 0.7778
+ },
+ "DesktopMenuItem_disabled_light": {
+ "center_offset": 9.18,
+ "height_ratio": 0.6667,
+ "width_ratio": 0.7778
+ },
+ "DesktopMenuItem_hover_dark": {
+ "center_offset": 9.39,
+ "height_ratio": 0.75,
+ "width_ratio": 0.7536
+ },
+ "DesktopMenuItem_hover_light": {
+ "center_offset": 9.39,
+ "height_ratio": 0.75,
+ "width_ratio": 0.7536
+ },
+ "DesktopMenuItem_normal_dark": {
+ "center_offset": 9.18,
+ "height_ratio": 0.6667,
+ "width_ratio": 0.7778
+ },
+ "DesktopMenuItem_normal_light": {
+ "center_offset": 9.18,
+ "height_ratio": 0.6667,
+ "width_ratio": 0.7778
+ },
"DesktopProgressBar_normal_dark": {
"center_offset": 2.0,
"height_ratio": 1.3333,
@@ -200,6 +310,66 @@
"height_ratio": 0.8,
"width_ratio": 0.918
},
+ "DesktopScrollBarHighlight_hover_dark": {
+ "center_offset": 14.32,
+ "height_ratio": 0.55,
+ "width_ratio": 1.25
+ },
+ "DesktopScrollBarHighlight_hover_light": {
+ "center_offset": 14.32,
+ "height_ratio": 0.55,
+ "width_ratio": 1.25
+ },
+ "DesktopScrollBarHighlight_normal_dark": {
+ "center_offset": 14.32,
+ "height_ratio": 0.55,
+ "width_ratio": 1.25
+ },
+ "DesktopScrollBarHighlight_normal_light": {
+ "center_offset": 14.32,
+ "height_ratio": 0.55,
+ "width_ratio": 1.25
+ },
+ "DesktopScrollBarHighlight_pressed_dark": {
+ "center_offset": 14.32,
+ "height_ratio": 0.55,
+ "width_ratio": 1.25
+ },
+ "DesktopScrollBarHighlight_pressed_light": {
+ "center_offset": 14.32,
+ "height_ratio": 0.55,
+ "width_ratio": 1.25
+ },
+ "DesktopScrollBar_normal_dark": {
+ "center_offset": 14.32,
+ "height_ratio": 0.55,
+ "width_ratio": 1.25
+ },
+ "DesktopScrollBar_normal_light": {
+ "center_offset": 14.32,
+ "height_ratio": 0.55,
+ "width_ratio": 1.25
+ },
+ "DesktopSearchField_disabled_dark": {
+ "center_offset": 3.0,
+ "height_ratio": 0.8125,
+ "width_ratio": 1.0
+ },
+ "DesktopSearchField_disabled_light": {
+ "center_offset": 1.5,
+ "height_ratio": 1.7333,
+ "width_ratio": 1.0631
+ },
+ "DesktopSearchField_normal_dark": {
+ "center_offset": 3.0,
+ "height_ratio": 0.8125,
+ "width_ratio": 1.0
+ },
+ "DesktopSearchField_normal_light": {
+ "center_offset": 3.0,
+ "height_ratio": 0.8125,
+ "width_ratio": 1.0
+ },
"DesktopSlider_disabled_dark": {
"center_offset": 55.0,
"height_ratio": 0.75,
@@ -230,6 +400,26 @@
"height_ratio": 0.1667,
"width_ratio": 1.0926
},
+ "DesktopStepper_disabled_dark": {
+ "center_offset": 66.07,
+ "height_ratio": 0.8125,
+ "width_ratio": 2.2692
+ },
+ "DesktopStepper_disabled_light": {
+ "center_offset": 69.56,
+ "height_ratio": 0.8125,
+ "width_ratio": 2.6517
+ },
+ "DesktopStepper_normal_dark": {
+ "center_offset": 66.07,
+ "height_ratio": 0.8125,
+ "width_ratio": 2.2692
+ },
+ "DesktopStepper_normal_light": {
+ "center_offset": 66.07,
+ "height_ratio": 0.8125,
+ "width_ratio": 2.2692
+ },
"DesktopSwitch_disabled_dark": {
"center_offset": 9.62,
"height_ratio": 0.9583,
@@ -270,6 +460,16 @@
"height_ratio": 0.9583,
"width_ratio": 1.0
},
+ "DesktopTabs_normal_dark": {
+ "center_offset": 11.5,
+ "height_ratio": 0.96,
+ "width_ratio": 1.0
+ },
+ "DesktopTabs_normal_light": {
+ "center_offset": 11.5,
+ "height_ratio": 0.96,
+ "width_ratio": 1.0
+ },
"DesktopTextField_disabled_dark": {
"center_offset": 1.5,
"height_ratio": 0.9063,
@@ -299,6 +499,16 @@
"center_offset": 1.5,
"height_ratio": 0.9063,
"width_ratio": 1.0
+ },
+ "DesktopToolbar_normal_dark": {
+ "center_offset": 11.0,
+ "height_ratio": 0.5,
+ "width_ratio": 1.0
+ },
+ "DesktopToolbar_normal_light": {
+ "center_offset": 15.5,
+ "height_ratio": 0.6389,
+ "width_ratio": 1.0
}
},
"pairs": {
@@ -332,6 +542,28 @@
"DesktopComboBox_hover_light": 86.59,
"DesktopComboBox_normal_dark": 89.09,
"DesktopComboBox_normal_light": 87.03,
+ "DesktopDisclosure_normal_dark": 79.74,
+ "DesktopDisclosure_normal_light": 81.15,
+ "DesktopGroupBox_normal_dark": 80.03,
+ "DesktopGroupBox_normal_light": 76.73,
+ "DesktopLinkButton_disabled_dark": 87.87,
+ "DesktopLinkButton_disabled_light": 87.42,
+ "DesktopLinkButton_hover_dark": 76.82,
+ "DesktopLinkButton_hover_light": 80.07,
+ "DesktopLinkButton_normal_dark": 78.63,
+ "DesktopLinkButton_normal_light": 79.02,
+ "DesktopListRow_normal_dark": 86.99,
+ "DesktopListRow_normal_light": 88.32,
+ "DesktopListRow_selected_dark": 72.2,
+ "DesktopListRow_selected_light": 72.76,
+ "DesktopMenuBar_normal_dark": 80.0,
+ "DesktopMenuBar_normal_light": 50.03,
+ "DesktopMenuItem_disabled_dark": 86.17,
+ "DesktopMenuItem_disabled_light": 86.44,
+ "DesktopMenuItem_hover_dark": 91.65,
+ "DesktopMenuItem_hover_light": 92.36,
+ "DesktopMenuItem_normal_dark": 78.51,
+ "DesktopMenuItem_normal_light": 80.06,
"DesktopProgressBar_normal_dark": 79.98,
"DesktopProgressBar_normal_light": 78.94,
"DesktopRadioButton_disabled_dark": 90.28,
@@ -342,12 +574,28 @@
"DesktopRadioButton_normal_light": 86.44,
"DesktopRadioButton_selected_dark": 86.21,
"DesktopRadioButton_selected_light": 88.38,
+ "DesktopScrollBarHighlight_hover_dark": 82.63,
+ "DesktopScrollBarHighlight_hover_light": 82.53,
+ "DesktopScrollBarHighlight_normal_dark": 88.3,
+ "DesktopScrollBarHighlight_normal_light": 88.53,
+ "DesktopScrollBarHighlight_pressed_dark": 77.37,
+ "DesktopScrollBarHighlight_pressed_light": 77.23,
+ "DesktopScrollBar_normal_dark": 88.3,
+ "DesktopScrollBar_normal_light": 88.53,
+ "DesktopSearchField_disabled_dark": 77.87,
+ "DesktopSearchField_disabled_light": 70.3,
+ "DesktopSearchField_normal_dark": 71.72,
+ "DesktopSearchField_normal_light": 69.96,
"DesktopSlider_disabled_dark": 89.64,
"DesktopSlider_disabled_light": 88.2,
"DesktopSlider_hover_dark": 90.82,
"DesktopSlider_hover_light": 94.37,
"DesktopSlider_normal_dark": 94.35,
"DesktopSlider_normal_light": 94.73,
+ "DesktopStepper_disabled_dark": 82.43,
+ "DesktopStepper_disabled_light": 74.26,
+ "DesktopStepper_normal_dark": 77.25,
+ "DesktopStepper_normal_light": 73.8,
"DesktopSwitch_disabled_dark": 86.95,
"DesktopSwitch_disabled_light": 95.24,
"DesktopSwitch_hover_dark": 92.65,
@@ -356,11 +604,15 @@
"DesktopSwitch_normal_light": 93.49,
"DesktopSwitch_selected_dark": 86.99,
"DesktopSwitch_selected_light": 82.79,
+ "DesktopTabs_normal_dark": 71.05,
+ "DesktopTabs_normal_light": 72.76,
"DesktopTextField_disabled_dark": 90.19,
"DesktopTextField_disabled_light": 80.5,
"DesktopTextField_hover_dark": 72.05,
"DesktopTextField_hover_light": 73.86,
"DesktopTextField_normal_dark": 75.68,
- "DesktopTextField_normal_light": 75.36
+ "DesktopTextField_normal_light": 75.36,
+ "DesktopToolbar_normal_dark": 86.32,
+ "DesktopToolbar_normal_light": 80.11
}
}
diff --git a/scripts/fidelity-app/baseline/macos-aqua-fidelity-baseline.json b/scripts/fidelity-app/baseline/macos-aqua-fidelity-baseline.json
index 78e6888356d..9776d3c4f3c 100644
--- a/scripts/fidelity-app/baseline/macos-aqua-fidelity-baseline.json
+++ b/scripts/fidelity-app/baseline/macos-aqua-fidelity-baseline.json
@@ -150,6 +150,76 @@
"height_ratio": 1.0,
"width_ratio": 0.8933
},
+ "DesktopDisclosure_normal_dark": {
+ "center_offset": 6.32,
+ "height_ratio": 0.8182,
+ "width_ratio": 0.6
+ },
+ "DesktopDisclosure_normal_light": {
+ "center_offset": 6.32,
+ "height_ratio": 0.8182,
+ "width_ratio": 0.6
+ },
+ "DesktopGroupBox_normal_dark": {
+ "center_offset": 1.0,
+ "height_ratio": 1.04,
+ "width_ratio": 1.0085
+ },
+ "DesktopGroupBox_normal_light": {
+ "center_offset": 1.0,
+ "height_ratio": 1.04,
+ "width_ratio": 1.0085
+ },
+ "DesktopLinkButton_disabled_dark": {
+ "center_offset": 3.81,
+ "height_ratio": 0.9167,
+ "width_ratio": 0.72
+ },
+ "DesktopLinkButton_disabled_light": {
+ "center_offset": 3.81,
+ "height_ratio": 0.9167,
+ "width_ratio": 0.72
+ },
+ "DesktopLinkButton_hover_dark": {
+ "center_offset": 3.81,
+ "height_ratio": 0.9167,
+ "width_ratio": 0.72
+ },
+ "DesktopLinkButton_hover_light": {
+ "center_offset": 3.81,
+ "height_ratio": 0.9167,
+ "width_ratio": 0.72
+ },
+ "DesktopLinkButton_normal_dark": {
+ "center_offset": 3.81,
+ "height_ratio": 0.9167,
+ "width_ratio": 0.72
+ },
+ "DesktopLinkButton_normal_light": {
+ "center_offset": 3.81,
+ "height_ratio": 0.9167,
+ "width_ratio": 0.72
+ },
+ "DesktopListRow_normal_dark": {
+ "center_offset": 2.24,
+ "height_ratio": 0.8,
+ "width_ratio": 0.9167
+ },
+ "DesktopListRow_normal_light": {
+ "center_offset": 2.24,
+ "height_ratio": 0.8,
+ "width_ratio": 0.9167
+ },
+ "DesktopListRow_selected_dark": {
+ "center_offset": 2.12,
+ "height_ratio": 0.8636,
+ "width_ratio": 1.0583
+ },
+ "DesktopListRow_selected_light": {
+ "center_offset": 2.12,
+ "height_ratio": 0.8636,
+ "width_ratio": 1.0583
+ },
"DesktopProgressBar_normal_dark": {
"center_offset": 3.0,
"height_ratio": 1.3333,
@@ -200,6 +270,26 @@
"height_ratio": 1.1333,
"width_ratio": 1.0556
},
+ "DesktopSearchField_disabled_dark": {
+ "center_offset": 0.5,
+ "height_ratio": 1.3125,
+ "width_ratio": 1.0
+ },
+ "DesktopSearchField_disabled_light": {
+ "center_offset": 1.0,
+ "height_ratio": 1.1053,
+ "width_ratio": 1.0
+ },
+ "DesktopSearchField_normal_dark": {
+ "center_offset": 0.5,
+ "height_ratio": 1.3125,
+ "width_ratio": 1.0
+ },
+ "DesktopSearchField_normal_light": {
+ "center_offset": 1.0,
+ "height_ratio": 1.1053,
+ "width_ratio": 1.0
+ },
"DesktopSlider_disabled_dark": {
"center_offset": 56.14,
"height_ratio": 0.6,
@@ -230,6 +320,26 @@
"height_ratio": 0.5714,
"width_ratio": 1.0
},
+ "DesktopStepper_disabled_dark": {
+ "center_offset": 86.01,
+ "height_ratio": 1.05,
+ "width_ratio": 3.6875
+ },
+ "DesktopStepper_disabled_light": {
+ "center_offset": 85.52,
+ "height_ratio": 1.0,
+ "width_ratio": 3.6308
+ },
+ "DesktopStepper_normal_dark": {
+ "center_offset": 86.01,
+ "height_ratio": 1.05,
+ "width_ratio": 3.6875
+ },
+ "DesktopStepper_normal_light": {
+ "center_offset": 85.51,
+ "height_ratio": 0.9545,
+ "width_ratio": 3.6308
+ },
"DesktopSwitch_disabled_dark": {
"center_offset": 7.76,
"height_ratio": 1.0,
@@ -270,6 +380,16 @@
"height_ratio": 1.0,
"width_ratio": 1.0789
},
+ "DesktopTabs_normal_dark": {
+ "center_offset": 40.03,
+ "height_ratio": 0.25,
+ "width_ratio": 0.6239
+ },
+ "DesktopTabs_normal_light": {
+ "center_offset": 39.56,
+ "height_ratio": 0.2,
+ "width_ratio": 0.6195
+ },
"DesktopTextField_disabled_dark": {
"center_offset": 98.02,
"height_ratio": 0.6,
@@ -299,6 +419,16 @@
"center_offset": 1.0,
"height_ratio": 1.1,
"width_ratio": 1.0
+ },
+ "DesktopToolbar_normal_dark": {
+ "center_offset": 15.01,
+ "height_ratio": 1.5455,
+ "width_ratio": 8.1379
+ },
+ "DesktopToolbar_normal_light": {
+ "center_offset": 15.01,
+ "height_ratio": 1.5455,
+ "width_ratio": 8.1379
}
},
"pairs": {
@@ -332,6 +462,20 @@
"DesktopComboBox_hover_light": 89.46,
"DesktopComboBox_normal_dark": 89.25,
"DesktopComboBox_normal_light": 89.46,
+ "DesktopDisclosure_normal_dark": 85.37,
+ "DesktopDisclosure_normal_light": 83.72,
+ "DesktopGroupBox_normal_dark": 73.62,
+ "DesktopGroupBox_normal_light": 73.04,
+ "DesktopLinkButton_disabled_dark": 90.56,
+ "DesktopLinkButton_disabled_light": 88.56,
+ "DesktopLinkButton_hover_dark": 91.06,
+ "DesktopLinkButton_hover_light": 88.92,
+ "DesktopLinkButton_normal_dark": 91.06,
+ "DesktopLinkButton_normal_light": 88.92,
+ "DesktopListRow_normal_dark": 89.91,
+ "DesktopListRow_normal_light": 88.99,
+ "DesktopListRow_selected_dark": 85.53,
+ "DesktopListRow_selected_light": 84.26,
"DesktopProgressBar_normal_dark": 81.01,
"DesktopProgressBar_normal_light": 84.46,
"DesktopRadioButton_disabled_dark": 90.87,
@@ -342,12 +486,20 @@
"DesktopRadioButton_normal_light": 79.53,
"DesktopRadioButton_selected_dark": 82.82,
"DesktopRadioButton_selected_light": 83.33,
+ "DesktopSearchField_disabled_dark": 78.6,
+ "DesktopSearchField_disabled_light": 81.19,
+ "DesktopSearchField_normal_dark": 75.79,
+ "DesktopSearchField_normal_light": 85.92,
"DesktopSlider_disabled_dark": 77.86,
"DesktopSlider_disabled_light": 77.38,
"DesktopSlider_hover_dark": 72.94,
"DesktopSlider_hover_light": 76.14,
"DesktopSlider_normal_dark": 72.94,
"DesktopSlider_normal_light": 76.14,
+ "DesktopStepper_disabled_dark": 81.68,
+ "DesktopStepper_disabled_light": 80.02,
+ "DesktopStepper_normal_dark": 80.85,
+ "DesktopStepper_normal_light": 79.36,
"DesktopSwitch_disabled_dark": 76.21,
"DesktopSwitch_disabled_light": 95.33,
"DesktopSwitch_hover_dark": 91.59,
@@ -356,11 +508,15 @@
"DesktopSwitch_normal_light": 95.03,
"DesktopSwitch_selected_dark": 84.53,
"DesktopSwitch_selected_light": 87.21,
+ "DesktopTabs_normal_dark": 48.96,
+ "DesktopTabs_normal_light": 81.97,
"DesktopTextField_disabled_dark": 89.29,
"DesktopTextField_disabled_light": 91.18,
"DesktopTextField_hover_dark": 81.08,
"DesktopTextField_hover_light": 90.91,
"DesktopTextField_normal_dark": 81.08,
- "DesktopTextField_normal_light": 90.91
+ "DesktopTextField_normal_light": 90.91,
+ "DesktopToolbar_normal_dark": 74.99,
+ "DesktopToolbar_normal_light": 76.14
}
}
diff --git a/scripts/fidelity-app/baseline/windows-11-fluent-fidelity-baseline.json b/scripts/fidelity-app/baseline/windows-11-fluent-fidelity-baseline.json
index d22819278d5..fff3c34bcc5 100644
--- a/scripts/fidelity-app/baseline/windows-11-fluent-fidelity-baseline.json
+++ b/scripts/fidelity-app/baseline/windows-11-fluent-fidelity-baseline.json
@@ -150,6 +150,116 @@
"height_ratio": 0.4828,
"width_ratio": 0.4565
},
+ "DesktopDisclosure_normal_dark": {
+ "center_offset": 37.17,
+ "height_ratio": 0.1778,
+ "width_ratio": 0.265
+ },
+ "DesktopDisclosure_normal_light": {
+ "center_offset": 39.09,
+ "height_ratio": 0.1818,
+ "width_ratio": 0.2672
+ },
+ "DesktopGroupBox_normal_dark": {
+ "center_offset": 3.81,
+ "height_ratio": 0.9231,
+ "width_ratio": 0.9706
+ },
+ "DesktopGroupBox_normal_light": {
+ "center_offset": 96.51,
+ "height_ratio": 0.9231,
+ "width_ratio": 0.141
+ },
+ "DesktopLinkButton_disabled_dark": {
+ "center_offset": 16.56,
+ "height_ratio": 1.0,
+ "width_ratio": 0.7083
+ },
+ "DesktopLinkButton_disabled_light": {
+ "center_offset": 16.56,
+ "height_ratio": 1.0,
+ "width_ratio": 0.7083
+ },
+ "DesktopLinkButton_hover_dark": {
+ "center_offset": 17.76,
+ "height_ratio": 0.3667,
+ "width_ratio": 0.3617
+ },
+ "DesktopLinkButton_hover_light": {
+ "center_offset": 16.56,
+ "height_ratio": 1.0,
+ "width_ratio": 0.7083
+ },
+ "DesktopLinkButton_normal_dark": {
+ "center_offset": 16.56,
+ "height_ratio": 1.0,
+ "width_ratio": 0.7083
+ },
+ "DesktopLinkButton_normal_light": {
+ "center_offset": 16.56,
+ "height_ratio": 1.0,
+ "width_ratio": 0.7083
+ },
+ "DesktopListRow_normal_dark": {
+ "center_offset": 12.73,
+ "height_ratio": 0.8,
+ "width_ratio": 0.8462
+ },
+ "DesktopListRow_normal_light": {
+ "center_offset": 12.73,
+ "height_ratio": 0.8,
+ "width_ratio": 0.8462
+ },
+ "DesktopListRow_selected_dark": {
+ "center_offset": 6.5,
+ "height_ratio": 0.6389,
+ "width_ratio": 1.0172
+ },
+ "DesktopListRow_selected_light": {
+ "center_offset": 12.73,
+ "height_ratio": 0.8,
+ "width_ratio": 0.8462
+ },
+ "DesktopMenuBar_normal_dark": {
+ "center_offset": 95.08,
+ "height_ratio": 1.5455,
+ "width_ratio": 11.8
+ },
+ "DesktopMenuBar_normal_light": {
+ "center_offset": 95.08,
+ "height_ratio": 1.5455,
+ "width_ratio": 11.8
+ },
+ "DesktopMenuItem_disabled_dark": {
+ "center_offset": 12.38,
+ "height_ratio": 0.7692,
+ "width_ratio": 0.7576
+ },
+ "DesktopMenuItem_disabled_light": {
+ "center_offset": 12.38,
+ "height_ratio": 0.7692,
+ "width_ratio": 0.7576
+ },
+ "DesktopMenuItem_hover_dark": {
+ "center_offset": 10.7,
+ "height_ratio": 0.6389,
+ "width_ratio": 0.7719
+ },
+ "DesktopMenuItem_hover_light": {
+ "center_offset": 12.38,
+ "height_ratio": 0.7692,
+ "width_ratio": 0.7576
+ },
+ "DesktopMenuItem_normal_dark": {
+ "center_offset": 12.38,
+ "height_ratio": 0.7692,
+ "width_ratio": 0.7576
+ },
+ "DesktopMenuItem_normal_light": {
+ "center_offset": 12.38,
+ "height_ratio": 0.7692,
+ "width_ratio": 0.7576
+ },
"DesktopProgressBar_normal_dark": {
"center_offset": 47.04,
"height_ratio": 3.0,
@@ -200,6 +310,36 @@
"height_ratio": 0.85,
"width_ratio": 0.9016
},
+ "DesktopScrollBar_normal_dark": {
+ "center_offset": 14.14,
+ "height_ratio": 0.9167,
+ "width_ratio": 3.0
+ },
+ "DesktopScrollBar_normal_light": {
+ "center_offset": 14.14,
+ "height_ratio": 0.9167,
+ "width_ratio": 3.0
+ },
+ "DesktopSearchField_disabled_dark": {
+ "center_offset": 4.0,
+ "height_ratio": 0.9048,
+ "width_ratio": 1.0
+ },
+ "DesktopSearchField_disabled_light": {
+ "center_offset": 7.5,
+ "height_ratio": 1.1429,
+ "width_ratio": 1.0085
+ },
+ "DesktopSearchField_normal_dark": {
+ "center_offset": 0.5,
+ "height_ratio": 0.6333,
+ "width_ratio": 1.0
+ },
+ "DesktopSearchField_normal_light": {
+ "center_offset": 7.0,
+ "height_ratio": 1.0909,
+ "width_ratio": 1.0
+ },
"DesktopSlider_disabled_dark": {
"center_offset": 54.5,
"height_ratio": 0.7727,
@@ -230,6 +370,26 @@
"height_ratio": 0.7727,
"width_ratio": 1.0
},
+ "DesktopStepper_disabled_dark": {
+ "center_offset": 53.06,
+ "height_ratio": 0.8333,
+ "width_ratio": 1.8154
+ },
+ "DesktopStepper_disabled_light": {
+ "center_offset": 50.12,
+ "height_ratio": 0.8276,
+ "width_ratio": 1.7907
+ },
+ "DesktopStepper_normal_dark": {
+ "center_offset": 53.06,
+ "height_ratio": 0.8333,
+ "width_ratio": 1.8154
+ },
+ "DesktopStepper_normal_light": {
+ "center_offset": 50.62,
+ "height_ratio": 0.8276,
+ "width_ratio": 1.7769
+ },
"DesktopSwitch_disabled_dark": {
"center_offset": 14.58,
"height_ratio": 1.05,
@@ -270,6 +430,16 @@
"height_ratio": 1.05,
"width_ratio": 0.6029
},
+ "DesktopTabs_normal_dark": {
+ "center_offset": 12.9,
+ "height_ratio": 1.4375,
+ "width_ratio": 1.0679
+ },
+ "DesktopTabs_normal_light": {
+ "center_offset": 10.74,
+ "height_ratio": 0.6071,
+ "width_ratio": 0.9576
+ },
"DesktopTextField_disabled_dark": {
"center_offset": 0.0,
"height_ratio": 1.0,
@@ -299,6 +469,26 @@
"center_offset": 0.0,
"height_ratio": 1.0,
"width_ratio": 1.0
+ },
+ "DesktopToolbar_normal_dark": {
+ "center_offset": 14.08,
+ "height_ratio": 1.0,
+ "width_ratio": 0.9615
+ },
+ "DesktopToolbar_normal_light": {
+ "center_offset": 14.08,
+ "height_ratio": 1.0,
+ "width_ratio": 0.9615
+ },
+ "DesktopTooltip_normal_dark": {
+ "center_offset": 2.55,
+ "height_ratio": 1.0345,
+ "width_ratio": 1.0909
+ },
+ "DesktopTooltip_normal_light": {
+ "center_offset": 0.0,
+ "height_ratio": 0.5,
+ "width_ratio": 0.7778
}
},
"pairs": {
@@ -332,6 +522,28 @@
"DesktopComboBox_hover_light": 92.66,
"DesktopComboBox_normal_dark": 95.79,
"DesktopComboBox_normal_light": 92.46,
+ "DesktopDisclosure_normal_dark": 87.93,
+ "DesktopDisclosure_normal_light": 74.29,
+ "DesktopGroupBox_normal_dark": 81.85,
+ "DesktopGroupBox_normal_light": 86.26,
+ "DesktopLinkButton_disabled_dark": 79.84,
+ "DesktopLinkButton_disabled_light": 81.69,
+ "DesktopLinkButton_hover_dark": 91.91,
+ "DesktopLinkButton_hover_light": 73.36,
+ "DesktopLinkButton_normal_dark": 72.36,
+ "DesktopLinkButton_normal_light": 74.92,
+ "DesktopListRow_normal_dark": 67.57,
+ "DesktopListRow_normal_light": 72.57,
+ "DesktopListRow_selected_dark": 88.08,
+ "DesktopListRow_selected_light": 67.96,
+ "DesktopMenuBar_normal_dark": 76.42,
+ "DesktopMenuBar_normal_light": 33.65,
+ "DesktopMenuItem_disabled_dark": 81.51,
+ "DesktopMenuItem_disabled_light": 83.72,
+ "DesktopMenuItem_hover_dark": 90.04,
+ "DesktopMenuItem_hover_light": 72.57,
+ "DesktopMenuItem_normal_dark": 68.19,
+ "DesktopMenuItem_normal_light": 72.81,
"DesktopProgressBar_normal_dark": 77.89,
"DesktopProgressBar_normal_light": 77.36,
"DesktopRadioButton_disabled_dark": 91.37,
@@ -342,12 +554,22 @@
"DesktopRadioButton_normal_light": 86.38,
"DesktopRadioButton_selected_dark": 87.05,
"DesktopRadioButton_selected_light": 87.25,
+ "DesktopScrollBar_normal_dark": 75.6,
+ "DesktopScrollBar_normal_light": 78.9,
+ "DesktopSearchField_disabled_dark": 84.84,
+ "DesktopSearchField_disabled_light": 90.82,
+ "DesktopSearchField_normal_dark": 80.46,
+ "DesktopSearchField_normal_light": 86.99,
"DesktopSlider_disabled_dark": 82.87,
"DesktopSlider_disabled_light": 78.79,
"DesktopSlider_hover_dark": 96.35,
"DesktopSlider_hover_light": 95.14,
"DesktopSlider_normal_dark": 96.68,
"DesktopSlider_normal_light": 95.29,
+ "DesktopStepper_disabled_dark": 84.36,
+ "DesktopStepper_disabled_light": 89.73,
+ "DesktopStepper_normal_dark": 82.28,
+ "DesktopStepper_normal_light": 87.74,
"DesktopSwitch_disabled_dark": 74.47,
"DesktopSwitch_disabled_light": 87.57,
"DesktopSwitch_hover_dark": 70.61,
@@ -356,11 +578,17 @@
"DesktopSwitch_normal_light": 79.87,
"DesktopSwitch_selected_dark": 70.71,
"DesktopSwitch_selected_light": 79.72,
+ "DesktopTabs_normal_dark": 87.36,
+ "DesktopTabs_normal_light": 78.77,
"DesktopTextField_disabled_dark": 98.21,
"DesktopTextField_disabled_light": 96.2,
"DesktopTextField_hover_dark": 95.66,
"DesktopTextField_hover_light": 92.69,
"DesktopTextField_normal_dark": 94.57,
- "DesktopTextField_normal_light": 92.35
+ "DesktopTextField_normal_light": 92.35,
+ "DesktopToolbar_normal_dark": 71.0,
+ "DesktopToolbar_normal_light": 74.47,
+ "DesktopTooltip_normal_dark": 92.77,
+ "DesktopTooltip_normal_light": 91.97
}
}
diff --git a/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/render/Cn1WidgetRenderer.java b/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/render/Cn1WidgetRenderer.java
index a27c5cc7220..50b5ed2e581 100644
--- a/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/render/Cn1WidgetRenderer.java
+++ b/scripts/fidelity-app/common/src/main/java/com/codenameone/fidelity/render/Cn1WidgetRenderer.java
@@ -56,6 +56,15 @@ public final class Cn1WidgetRenderer {
private Cn1WidgetRenderer() {
}
+ /// Desktop rows that build something of their own rather than reusing a mobile branch.
+ private static final java.util.Set SECOND_WAVE =
+ new java.util.HashSet(java.util.Arrays.asList(
+ "DesktopScrollBar", "DesktopScrollBarHighlight", "DesktopSeparator",
+ "DesktopGroupBox", "DesktopStepper",
+ "DesktopLinkButton", "DesktopSearchField", "DesktopListRow", "DesktopTabs",
+ "DesktopToolbar", "DesktopDisclosure", "DesktopMenuBar", "DesktopMenuItem",
+ "DesktopTooltip"));
+
/// Maps a desktop row id onto the component kind that builds it.
///
/// A desktop Button is still a Button; what makes the row different is its UIID, its tile
@@ -67,6 +76,14 @@ private static String desktopToMobileId(String id) {
return id;
}
String rest = id.substring("Desktop".length());
+ // The second-wave rows keep their own ids: each has a branch of its own below, and
+ // stripping the prefix would send several of them somewhere wrong. DesktopTabs would
+ // land in the iOS Tabs branch, which builds a Liquid Glass floating pill; DesktopToolbar
+ // in the one that builds a navigation bar with a title and a back chevron. Neither is
+ // the desktop control.
+ if (SECOND_WAVE.contains(id)) {
+ return id;
+ }
if ("AccentButton".equals(rest)) {
return "RaisedButton";
}
@@ -107,7 +124,16 @@ public static boolean isSupported(String id) {
|| "DesktopTextField".equals(id) || "DesktopCheckBox".equals(id)
|| "DesktopRadioButton".equals(id) || "DesktopSwitch".equals(id)
|| "DesktopSlider".equals(id) || "DesktopProgressBar".equals(id)
- || "DesktopComboBox".equals(id);
+ || "DesktopComboBox".equals(id)
+ // The second wave: the chrome and the controls the first nine did not reach.
+ || "DesktopScrollBar".equals(id) || "DesktopScrollBarHighlight".equals(id)
+ || "DesktopSeparator".equals(id)
+ || "DesktopGroupBox".equals(id) || "DesktopStepper".equals(id)
+ || "DesktopLinkButton".equals(id) || "DesktopSearchField".equals(id)
+ || "DesktopListRow".equals(id) || "DesktopTabs".equals(id)
+ || "DesktopToolbar".equals(id) || "DesktopDisclosure".equals(id)
+ || "DesktopMenuBar".equals(id) || "DesktopMenuItem".equals(id)
+ || "DesktopTooltip".equals(id);
}
/**
@@ -515,12 +541,174 @@ private static Component buildImpl(ComponentSpec spec, String state, String appe
spinner.setRenderingPrototype("Value 0");
spinner.setValue("Value 3");
c = spinner;
+ } else if ("DesktopScrollBar".equals(id) || "DesktopScrollBarHighlight".equals(id)) {
+ // The bar itself, not a scrolling container. LookAndFeel.drawVerticalScroll takes
+ // any component and paints the theme's track and thumb across it, which is exactly
+ // the bare NSScroller / GtkScrollbar / WinUI ScrollBar the reference apps build --
+ // a scrolling container would put its CONTENT in the comparison too.
+ //
+ // The hover and drag states are expressed by OVERRIDING the two public methods the
+ // look and feel asks, rather than by faking a pointer. Those methods are what
+ // drawScroll reads to pick the thumb's selected or pressed style, so this renders
+ // the same pixels a real hover does, and it needs no test-only hook in the product.
+ c = new ScrollBarProbe("hover".equals(state), "pressed".equals(state));
+ } else if ("DesktopSeparator".equals(id)) {
+ com.codename1.components.Separator sep = new com.codename1.components.Separator();
+ sep.setUIID(uiid);
+ c = sep;
+ } else if ("DesktopGroupBox".equals(id)) {
+ com.codename1.components.GroupBox box =
+ new com.codename1.components.GroupBox(text);
+ box.setUIID(uiid);
+ // One short row of content, so the frame has something to enclose. A native
+ // NSBox / GtkFrame with an empty body collapses to its own insets, which is not a
+ // control anyone would recognise -- the same reason the text field is given a
+ // width rather than allowed to measure to its placeholder.
+ Label body = new Label("Item");
+ body.setUIID("Label");
+ box.add(body);
+ c = box;
+ } else if ("DesktopStepper".equals(id)) {
+ com.codename1.components.Stepper st = new com.codename1.components.Stepper(1, 0, 10);
+ st.setUIID(uiid);
+ if ("disabled".equals(state)) {
+ st.setEnabled(false);
+ st.getField().setEnabled(false);
+ st.getDecrementButton().setEnabled(false);
+ st.getIncrementButton().setEnabled(false);
+ }
+ c = st;
+ } else if ("DesktopLinkButton".equals(id)) {
+ Button link = new Button(text);
+ link.setUIID(uiid);
+ link.getAllStyles().setMargin(0, 0, 0, 0);
+ applyButtonState(link, state);
+ c = link;
+ } else if ("DesktopSearchField".equals(id)) {
+ TextField search = new TextField(text);
+ search.setUIID(uiid);
+ search.setEditable(false);
+ search.getAllStyles().setMargin(0, 0, 0, 0);
+ search.setColumns(1);
+ search.setGrowByContent(true);
+ if ("disabled".equals(state)) {
+ search.setEnabled(false);
+ }
+ c = search;
+ } else if ("DesktopListRow".equals(id)) {
+ // A row is a Label under the ListRenderer UIID, which is what a CN1 list paints
+ // for each entry. Selected and hover are ordinary style states here rather than
+ // list-model selection, because the tile is one row with no list around it.
+ Label row = new Label(text);
+ row.setUIID(uiid);
+ row.getAllStyles().setMargin(0, 0, 0, 0);
+ if ("selected".equals(state)) {
+ row.setFocus(true);
+ }
+ c = row;
+ } else if ("DesktopTabs".equals(id)) {
+ Tabs tabs = new Tabs();
+ tabs.setUIID(uiid);
+ tabs.addTab("One", new Label(""));
+ tabs.addTab("Two", new Label(""));
+ c = tabs;
+ } else if ("DesktopToolbar".equals(id)) {
+ // The strip, built directly rather than through Form.setToolbar: the tile has no
+ // form chrome around it, and a Toolbar taken off a Form brings its title area's
+ // layout with it.
+ Container bar = new Container(new BorderLayout());
+ bar.setUIID(uiid);
+ Label title = new Label("Title");
+ title.setUIID("Title");
+ bar.add(BorderLayout.CENTER, title);
+ c = bar;
+ } else if ("DesktopDisclosure".equals(id)) {
+ Button disclosure = new Button(text);
+ disclosure.setUIID(uiid);
+ disclosure.getAllStyles().setMargin(0, 0, 0, 0);
+ c = disclosure;
+ } else if ("DesktopMenuBar".equals(id)) {
+ Container menuBar = new Container(new FlowLayout());
+ menuBar.setUIID(uiid);
+ Button item = new Button(text);
+ item.setUIID("Command");
+ menuBar.add(item);
+ c = menuBar;
+ } else if ("DesktopMenuItem".equals(id)) {
+ Button item = new Button(text);
+ item.setUIID(uiid);
+ item.getAllStyles().setMargin(0, 0, 0, 0);
+ applyButtonState(item, state);
+ if ("disabled".equals(state)) {
+ item.setEnabled(false);
+ }
+ c = item;
+ } else if ("DesktopTooltip".equals(id)) {
+ Container tip = new Container(new BorderLayout());
+ tip.setUIID("TooltipDialog");
+ Label label = new Label(text);
+ label.setUIID(uiid);
+ tip.add(BorderLayout.CENTER, label);
+ c = tip;
} else {
return null;
}
return c;
}
+ /// Paints the theme's interactive scrollbar and nothing else.
+ ///
+ /// `LookAndFeel.drawVerticalScroll` takes any component and paints the track and thumb
+ /// across it, so this is the bar on its own -- the same thing the reference apps build,
+ /// rather than a scrolling container whose content would join the comparison.
+ ///
+ /// The two state overrides are the whole trick. `drawScroll` asks the component it is
+ /// painting for `isVScrollThumbHover()` and `isVScrollThumbGrabbed()` to choose between
+ /// the thumb's unselected, selected and pressed styles. Both are public, so answering
+ /// them directly renders exactly the pixels a real hover or drag produces, with no
+ /// test-only hook added to the framework and no synthetic pointer to get wrong.
+ private static final class ScrollBarProbe extends Container {
+ private final boolean hover;
+ private final boolean grabbed;
+
+ ScrollBarProbe(boolean hover, boolean grabbed) {
+ this.hover = hover;
+ this.grabbed = grabbed;
+ setUIID("Container");
+ getAllStyles().setMargin(0, 0, 0, 0);
+ getAllStyles().setPadding(0, 0, 0, 0);
+ getAllStyles().setBgTransparency(0);
+ }
+
+ @Override
+ public boolean isVScrollThumbHover() {
+ return hover;
+ }
+
+ @Override
+ public boolean isVScrollThumbGrabbed() {
+ return grabbed;
+ }
+
+ @Override
+ protected com.codename1.ui.geom.Dimension calcPreferredSize() {
+ // As wide as the theme's gutter and as tall as it is given. The gutter width is
+ // the measurement -- it is DesktopScroll's own padding plus margin -- so it must
+ // come from the look and feel rather than from a number written here.
+ return new com.codename1.ui.geom.Dimension(
+ getUIManager().getLookAndFeel().getVerticalScrollWidth(), 1);
+ }
+
+ @Override
+ public void paint(com.codename1.ui.Graphics g) {
+ // offsetRatio 0, blockSizeRatio 0.4: a thumb at the top covering about two fifths
+ // of the track. Fixed rather than derived, because the reference apps set the same
+ // proportion by hand and the two sides have to agree about where the thumb is
+ // before anything about its colour or shape can be compared.
+ getUIManager().getLookAndFeel().drawVerticalScroll(g, this, 0f, 0.4f);
+ }
+ }
+
private static void applyButtonState(Button b, String state) {
if ("disabled".equals(state)) {
b.setEnabled(false);
diff --git a/scripts/fidelity-app/common/src/main/resources/fidelity-tests.yaml b/scripts/fidelity-app/common/src/main/resources/fidelity-tests.yaml
index 9a3b34eb0e0..63b79741510 100644
--- a/scripts/fidelity-app/common/src/main/resources/fidelity-tests.yaml
+++ b/scripts/fidelity-app/common/src/main/resources/fidelity-tests.yaml
@@ -405,3 +405,171 @@ components:
native_gnome: gtk_dropdown
text: Option
states: normal,hover,disabled
+
+ # --- Desktop, second wave -----------------------------------------------------
+ # The chrome and the controls the first nine rows did not reach. Same 240x56 tile,
+ # deliberately: a per-row tile size would have to be taught to three standalone capture
+ # apps that each hard-code one, and nothing here needs a bigger canvas.
+ #
+ # Three rows carry a platforms: list rather than all three native keys, and the reason is
+ # the same one that keeps Aqua vibrancy out of the suite: the reference has to be
+ # RENDERABLE into a view. A macOS NSMenu and a GTK or AppKit tooltip are window-server
+ # surfaces, invisible to NSView.cacheDisplay and to GtkWidgetPaintable, which are the
+ # capture paths these apps use. A blank golden scores 0% forever and reads as a theme bug,
+ # so the honest answer is to say where the reference exists and score it there.
+
+ # Two rows, because the three platforms can express different amounts of one control and
+ # every boundary below was MEASURED on a capture run rather than guessed.
+ #
+ # macOS cannot render a scrollbar into a view at all: an NSScroller reports
+ # usableParts=allScrollerParts, knobProportion 0.4, isHidden=false and a 17x56 frame, and
+ # renders NOTHING through NSView.cacheDisplay. Tried detached and inside a real NSScrollView,
+ # in both .legacy and .overlay styles, with AppleShowScrollBars=Always already set by the
+ # capture script; the tile comes back holding one colour, the backdrop, every time. Same
+ # class of limitation as Aqua vibrancy, and cacheDisplay is the path that needs no Screen
+ # Recording consent, which a hosted runner cannot grant.
+ #
+ # Windows renders the bar but cannot express its highlight: none of PointerOver,
+ # UncheckedPointerOver, CheckedPointerOver or MouseOver is a visual state of a WinUI
+ # ScrollBar, and neither is Pressed or Dragging. GTK can -- PRELIGHT and ACTIVE are what the
+ # CSS pseudo-classes resolve from -- and its captured hover and pressed tiles are genuinely
+ # different from normal, so the highlight is scored where the reference can state it.
+ - id: DesktopScrollBar
+ cn1_uiid: DesktopScrollThumb
+ native_win: winui_scrollbar
+ native_gnome: gtk_scrollbar
+ platforms: windows,gnome
+ states: normal
+
+ - id: DesktopScrollBarHighlight
+ cn1_uiid: DesktopScrollThumb
+ native_gnome: gtk_scrollbar
+ platforms: gnome
+ states: normal,hover,pressed
+
+ # No DesktopSeparator row, and the reason is a measurement rather than an omission.
+ #
+ # A separator is a 1px hairline a few levels off the surface, on all three platforms. The
+ # comparator's content mask measures distance from the backdrop, and that contrast does not
+ # clear it: the captured tile is 98.2% backdrop and holds exactly TWO colours, with a
+ # maximum deviation of 14 (Fluent), 23 (Aqua) and 29 (Adwaita) -- against 36 colours and a
+ # deviation of 216 for a Button tile. So the mask comes back empty on both sides, geometry
+ # reports {"empty": true}, and shape and size agreement come out at 1.0 because two empty
+ # masks agree perfectly. Windows light scored 96.45% with both sub-scores at exactly 1.0.
+ #
+ # A row that reports a near-perfect number about something the metric never looked at is
+ # worse than no row. FidelityGate already refuses to baseline it, which is the suite working
+ # as designed. Lowering the content threshold would make it measurable and is not worth
+ # doing for one row: that threshold is shared by every row on every platform, and dropping
+ # it starts admitting anti-aliasing as content.
+ #
+ # The Separator component is still themed by all three desktop themes and is asserted by
+ # DesktopNativeThemeContentTest; it is the overlay COMPARISON that cannot see it.
+
+ - id: DesktopSeparator
+ cn1_uiid: Separator
+ # The thinnest row in the suite, and the one most worth measuring: a separator is
+ # nothing but a colour and a thickness, so a theme that gets either wrong has no other
+ # symptom.
+ #
+ # It keeps the default 240x56 tile even though a shorter one would put proportionally
+ # more of the rule in frame. tile_height_px is documented as a per-component override
+ # and the CN1 side honours it, but the three reference apps mirror only the DEFAULTS --
+ # TILE_W and TILE_H are constants in each of them -- so a per-component override moves
+ # one side of the comparison and not the other. No desktop row has ever used one. The
+ # measured proof is this row's own first capture: the yaml asked for 24 and all three
+ # references produced 240x56 regardless.
+ native_win: winui_separator
+ native_mac: appkit_box_separator
+ native_gnome: gtk_separator
+ states: normal
+
+ - id: DesktopGroupBox
+ cn1_uiid: GroupBox
+ native_win: winui_groupbox
+ native_mac: appkit_box_titled
+ native_gnome: gtk_frame
+ text: Group
+ states: normal
+
+ - id: DesktopStepper
+ cn1_uiid: Stepper
+ native_win: winui_numberbox
+ native_mac: appkit_stepper
+ native_gnome: gtk_spin_button
+ states: normal,disabled
+
+ - id: DesktopLinkButton
+ cn1_uiid: Link
+ native_win: winui_hyperlinkbutton
+ native_mac: appkit_link_button
+ native_gnome: gtk_link_button
+ text: Link
+ states: normal,hover,disabled
+
+ - id: DesktopSearchField
+ cn1_uiid: ToolbarSearch
+ native_win: winui_autosuggestbox
+ native_mac: appkit_searchfield
+ native_gnome: gtk_search_entry
+ text: Search
+ states: normal,disabled
+
+ - id: DesktopListRow
+ cn1_uiid: ListRenderer
+ native_win: winui_listviewitem
+ native_mac: appkit_tableview_row
+ native_gnome: gtk_listbox_row
+ text: Row
+ # No hover: a WinUI ListViewItem draws through ListViewItemPresenter, which paints its
+ # own pointer-over chrome rather than exposing a visual state GoToState can reach --
+ # measured, the capture blocked on it. Selected is a real property on all three.
+ states: normal,selected
+
+ - id: DesktopTabs
+ cn1_uiid: Tabs
+ native_win: winui_tabview
+ native_mac: appkit_tabview
+ native_gnome: gtk_notebook
+ states: normal
+
+ - id: DesktopToolbar
+ cn1_uiid: Toolbar
+ native_win: winui_commandbar
+ native_mac: appkit_toolbar
+ native_gnome: adw_header_bar
+ states: normal
+
+ - id: DesktopDisclosure
+ cn1_uiid: AccordionHeader
+ native_win: winui_expander
+ native_mac: appkit_disclosure
+ native_gnome: gtk_expander
+ text: Details
+ states: normal
+
+ # Windows and GNOME only: AppKit's menu bar is an NSMenu owned by the window server.
+ - id: DesktopMenuBar
+ cn1_uiid: CommandList
+ native_win: winui_menubar
+ native_gnome: gtk_popover_menubar
+ platforms: windows,gnome
+ text: File
+ states: normal
+
+ - id: DesktopMenuItem
+ cn1_uiid: Command
+ native_win: winui_menuflyoutitem
+ native_gnome: gtk_popover_menu_item
+ platforms: windows,gnome
+ text: Open
+ states: normal,hover,disabled
+
+ # Windows only: a WinUI ToolTip is an ordinary Control and renders into a view. The GTK
+ # and AppKit tooltips are separate windows the capture path cannot see.
+ - id: DesktopTooltip
+ cn1_uiid: Tooltip
+ native_win: winui_tooltip
+ platforms: windows
+ text: Tooltip
+ states: normal
diff --git a/scripts/fidelity-app/desktop-runner/src/main/java/com/codenameone/fidelity/DesktopTileRunner.java b/scripts/fidelity-app/desktop-runner/src/main/java/com/codenameone/fidelity/DesktopTileRunner.java
index 9ce1869482b..2d19fb0acfc 100644
--- a/scripts/fidelity-app/desktop-runner/src/main/java/com/codenameone/fidelity/DesktopTileRunner.java
+++ b/scripts/fidelity-app/desktop-runner/src/main/java/com/codenameone/fidelity/DesktopTileRunner.java
@@ -210,7 +210,25 @@ private static int renderAll(String platform, String themeRes, File outDir) thro
/// comparison is between two different geometries and the score means nothing.
private static final java.util.Set FULL_WIDTH_IDS =
new java.util.HashSet(java.util.Arrays.asList(
- "DesktopSlider", "DesktopProgressBar", "DesktopTextField"));
+ "DesktopSlider", "DesktopProgressBar", "DesktopTextField",
+ // Second wave, same rule: a search field measures to its placeholder, and
+ // a row, a box, a tab strip, a toolbar and a menu bar are containers that
+ // take the width they are given.
+ "DesktopSearchField", "DesktopListRow",
+ "DesktopGroupBox", "DesktopTabs", "DesktopToolbar", "DesktopMenuBar",
+ // A separator is a rule across whatever it divides; it has no width of
+ // its own on either side.
+ "DesktopSeparator"));
+
+ /// Controls with no natural HEIGHT, the same rule on the other axis.
+ ///
+ /// The group box is a frame around other things: left to measure itself it collapses onto
+ /// its own title and draws no frame, which is a heading rather than a group box. The
+ /// scrollbar is defined by its length. Both native reference apps apply the same rule
+ /// through their own FULL_HEIGHT lists, kept in step by hand exactly as the width ones are.
+ private static final java.util.Set FULL_HEIGHT_IDS =
+ new java.util.HashSet(java.util.Arrays.asList(
+ "DesktopGroupBox", "DesktopScrollBar", "DesktopScrollBarHighlight"));
private static int tileBackground() {
return UIManager.getInstance().getComponentStyle("Form").getBgColor();
@@ -264,6 +282,9 @@ private static boolean renderTile(ComponentSpec c, String state, String appearan
if (FULL_WIDTH_IDS.contains(c.getId())) {
comp.setPreferredW(w);
}
+ if (FULL_HEIGHT_IDS.contains(c.getId())) {
+ comp.setPreferredH(h);
+ }
comp.getAllStyles().setMargin(0, 0, 0, 0);
// getAllStyles() deliberately EXCLUDES the hover style, so it is zeroed here as well
// -- and here rather than inside the renderer, because this clear runs AFTER build()
diff --git a/scripts/fidelity-app/gnome-native-ref/native-ref.c b/scripts/fidelity-app/gnome-native-ref/native-ref.c
index 28e46ba1159..20ac3530099 100644
--- a/scripts/fidelity-app/gnome-native-ref/native-ref.c
+++ b/scripts/fidelity-app/gnome-native-ref/native-ref.c
@@ -47,6 +47,9 @@
#include
#include
#include
+#include
+#include
+#include
static const char *out_dir = NULL;
static int is_probe = 1;
@@ -154,6 +157,23 @@ static const Spec SPECS[] = {
{"DesktopSlider", "gtk_scale", {"normal", "hover", "disabled", NULL}},
{"DesktopProgressBar", "gtk_progressbar", {"normal", NULL}},
{"DesktopComboBox", "gtk_dropdown", {"normal", "hover", "disabled", NULL}},
+
+ /* Second wave. GNOME carries the scrollbar, the menu bar and the menu item that AppKit
+ * cannot -- a GtkScrollbar is an ordinary widget and GtkPopoverMenuBar renders into the
+ * paintable -- but not the tooltip, which is a surface of its own like AppKit's. */
+ {"DesktopScrollBar", "gtk_scrollbar", {"normal", NULL}},
+ {"DesktopScrollBarHighlight", "gtk_scrollbar", {"normal", "hover", "pressed", NULL}},
+ {"DesktopSeparator", "gtk_separator", {"normal", NULL}},
+ {"DesktopGroupBox", "gtk_frame", {"normal", NULL}},
+ {"DesktopStepper", "gtk_spin_button", {"normal", "disabled", NULL}},
+ {"DesktopLinkButton", "gtk_link_button", {"normal", "hover", "disabled", NULL}},
+ {"DesktopSearchField", "gtk_search_entry", {"normal", "disabled", NULL}},
+ {"DesktopListRow", "gtk_listbox_row", {"normal", "selected", NULL}},
+ {"DesktopTabs", "gtk_notebook", {"normal", NULL}},
+ {"DesktopToolbar", "adw_header_bar", {"normal", NULL}},
+ {"DesktopDisclosure", "gtk_expander", {"normal", NULL}},
+ {"DesktopMenuBar", "gtk_popover_menubar", {"normal", NULL}},
+ {"DesktopMenuItem", "gtk_popover_menu_item", {"normal", "hover", "disabled", NULL}},
};
#define SPEC_COUNT ((int) (sizeof(SPECS) / sizeof(SPECS[0])))
@@ -164,7 +184,27 @@ static const Spec SPECS[] = {
static int is_full_width(const char *kind) {
return strcmp(kind, "gtk_scale") == 0
|| strcmp(kind, "gtk_progressbar") == 0
- || strcmp(kind, "gtk_entry") == 0;
+ || strcmp(kind, "gtk_entry") == 0
+ /* Second wave, same rule: a search entry measures to its placeholder, and a row, a
+ * frame, a notebook, a header bar and a menu bar are containers that take the width
+ * they are given. */
+ || strcmp(kind, "gtk_search_entry") == 0
+ || strcmp(kind, "gtk_listbox_row") == 0
+ || strcmp(kind, "gtk_separator") == 0
+ || strcmp(kind, "gtk_frame") == 0
+ || strcmp(kind, "gtk_notebook") == 0
+ || strcmp(kind, "adw_header_bar") == 0
+ || strcmp(kind, "gtk_popover_menubar") == 0;
+}
+
+/* Controls with no natural HEIGHT, the same rule on the other axis. A frame is a border
+ * around other things -- left to measure itself it collapses onto its own label and draws
+ * no border, which is a heading rather than a group box -- and a vertical scrollbar is
+ * defined by its length. Kept in step BY HAND with FULL_HEIGHT_KINDS in the macOS
+ * reference, IsFullHeight in the Windows one and FULL_HEIGHT_IDS in DesktopTileRunner. */
+static int is_full_height(const char *kind) {
+ return strcmp(kind, "gtk_frame") == 0
+ || strcmp(kind, "gtk_scrollbar") == 0;
}
static int exit_code = 0;
@@ -454,6 +494,117 @@ static GtkWidget *make_widget(const char *kind) {
const char *items[] = {"Option", NULL};
return gtk_drop_down_new_from_strings(items);
}
+ if (strcmp(kind, "gtk_scrollbar") == 0) {
+ /* The adjustment is what gives the slider a size and a position: page_size over
+ * upper is the proportion of the trough it covers, so 40 of 100 is the two fifths
+ * the CN1 side is drawn at, at value 0 -- the top. Both sides have to agree about
+ * where the thumb is before anything about its colour or shape can be compared. */
+ GtkAdjustment *adj = gtk_adjustment_new(0.0, 0.0, 100.0, 1.0, 10.0, 40.0);
+ return gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, adj);
+ }
+ if (strcmp(kind, "gtk_separator") == 0) {
+ /* GtkSeparator is the real thing rather than a drawn line: its thickness and colour
+ come from the Adwaita stylesheet, which is exactly what the Separator UIID has to
+ match. It has no natural width, so it is in the full-width set. */
+ return gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
+ }
+
+ if (strcmp(kind, "gtk_frame") == 0) {
+ /* GtkFrame with a label IS the GNOME group box; there is no separate widget. */
+ GtkWidget *frame = gtk_frame_new("Group");
+ GtkWidget *body = gtk_label_new("Item");
+ gtk_widget_set_margin_start(body, 8);
+ gtk_widget_set_margin_end(body, 8);
+ gtk_widget_set_margin_top(body, 8);
+ gtk_widget_set_margin_bottom(body, 8);
+ gtk_frame_set_child(GTK_FRAME(frame), body);
+ return frame;
+ }
+ if (strcmp(kind, "gtk_spin_button") == 0) {
+ /* One widget here, unlike AppKit's field-plus-stepper pair: a GtkSpinButton already
+ * IS the entry with its two buttons, which is the same control the CN1 Stepper
+ * composes. */
+ GtkWidget *sp = gtk_spin_button_new_with_range(0.0, 10.0, 1.0);
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(sp), 1.0);
+ return sp;
+ }
+ if (strcmp(kind, "gtk_link_button") == 0) {
+ return gtk_link_button_new_with_label("https://www.codenameone.com/", "Link");
+ }
+ if (strcmp(kind, "gtk_search_entry") == 0) {
+ GtkWidget *e = gtk_search_entry_new();
+ gtk_editable_set_text(GTK_EDITABLE(e), "Search");
+ return e;
+ }
+ if (strcmp(kind, "gtk_listbox_row") == 0) {
+ /* Inside a GtkListBox, not detached: Adwaita styles a row through the list's own
+ * CSS node, so a bare GtkListBoxRow draws none of the padding, background or
+ * selection the platform gives it. */
+ GtkWidget *list = gtk_list_box_new();
+ GtkWidget *row = gtk_list_box_row_new();
+ GtkWidget *label = gtk_label_new("Row");
+ gtk_widget_set_halign(label, GTK_ALIGN_START);
+ gtk_widget_set_margin_start(label, 8);
+ gtk_widget_set_margin_end(label, 8);
+ gtk_widget_set_margin_top(label, 4);
+ gtk_widget_set_margin_bottom(label, 4);
+ gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), label);
+ gtk_list_box_append(GTK_LIST_BOX(list), row);
+ return list;
+ }
+ if (strcmp(kind, "gtk_notebook") == 0) {
+ GtkWidget *nb = gtk_notebook_new();
+ gtk_notebook_append_page(GTK_NOTEBOOK(nb), gtk_label_new(""), gtk_label_new("One"));
+ gtk_notebook_append_page(GTK_NOTEBOOK(nb), gtk_label_new(""), gtk_label_new("Two"));
+ return nb;
+ }
+ if (strcmp(kind, "adw_header_bar") == 0) {
+ /* AdwHeaderBar is the GNOME title bar, and on GNOME the title bar IS the toolbar --
+ * which is exactly why the Adwaita theme asks for desktopTitleBarMode: custom. */
+ GtkWidget *bar = adw_header_bar_new();
+ adw_header_bar_set_title_widget(ADW_HEADER_BAR(bar), adw_window_title_new("Title", NULL));
+ return bar;
+ }
+ if (strcmp(kind, "gtk_expander") == 0) {
+ return gtk_expander_new("Details");
+ }
+ if (strcmp(kind, "gtk_popover_menubar") == 0) {
+ /* The models are held for the life of the process, deliberately.
+ *
+ * GtkPopoverMenuBar keeps a reference to the model it was built from and rebuilds
+ * its items from it while it lives, and the popovers it creates hold on to the
+ * submenu. Dropping our references here made the lifetime depend on GTK's teardown
+ * order rather than on ours -- and the capture then segfaulted partway through the
+ * DARK pass, several widgets after this one had been written, which is exactly the
+ * shape of a deferred free. Two earlier runs of the same binary completed, so it is
+ * intermittent, which is the other half of that shape.
+ *
+ * Holding them costs two objects in a tool that writes a hundred PNGs and exits. A
+ * capture that crashes one run in three costs a run. */
+ static GMenu *model = NULL;
+ static GMenu *file = NULL;
+ if (model == NULL) {
+ model = g_menu_new();
+ file = g_menu_new();
+ g_menu_append(file, "Open", "app.open");
+ g_menu_append_submenu(model, "File", G_MENU_MODEL(file));
+ }
+ return gtk_popover_menu_bar_new_from_model(G_MENU_MODEL(model));
+ }
+ if (strcmp(kind, "gtk_popover_menu_item") == 0) {
+ /* A menu item is a GtkButton with the "model" style class inside a popover menu:
+ * that class is what Adwaita styles a menu row with, and a plain button would be
+ * measured against the theme's menu row rather than against a menu row. */
+ GtkWidget *item = gtk_button_new_with_label("Open");
+ gtk_button_set_has_frame(GTK_BUTTON(item), FALSE);
+ gtk_widget_add_css_class(item, "model");
+ gtk_widget_add_css_class(item, "flat");
+ GtkWidget *child = gtk_button_get_child(GTK_BUTTON(item));
+ if (GTK_IS_LABEL(child)) {
+ gtk_widget_set_halign(child, GTK_ALIGN_START);
+ }
+ return item;
+ }
blocker("unknown native_gnome kind '%s'", kind);
return NULL;
}
@@ -506,6 +657,13 @@ static int apply_state(GtkWidget *w, const char *state, const char *kind) {
gtk_check_button_set_active(GTK_CHECK_BUTTON(w), TRUE);
return 1;
}
+ if (GTK_IS_LIST_BOX(w)) {
+ GtkListBoxRow *row = gtk_list_box_get_row_at_index(GTK_LIST_BOX(w), 0);
+ if (row) {
+ gtk_list_box_select_row(GTK_LIST_BOX(w), row);
+ return 1;
+ }
+ }
return 0;
}
blocker("unknown state '%s'", state);
@@ -530,11 +688,15 @@ static GtkWidget *build_tile(const Spec *spec, const char *state) {
return NULL;
}
gtk_widget_set_halign(w, is_full_width(spec->kind) ? GTK_ALIGN_FILL : GTK_ALIGN_START);
- gtk_widget_set_valign(w, GTK_ALIGN_START);
+ gtk_widget_set_valign(w, is_full_height(spec->kind) ? GTK_ALIGN_FILL : GTK_ALIGN_START);
if (is_full_width(spec->kind)) {
gtk_widget_set_size_request(w, TILE_W, -1);
gtk_widget_set_hexpand(w, TRUE);
}
+ if (is_full_height(spec->kind)) {
+ gtk_widget_set_size_request(w, is_full_width(spec->kind) ? TILE_W : -1, TILE_H);
+ gtk_widget_set_vexpand(w, TRUE);
+ }
GtkWidget *tile = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_add_css_class(tile, "background");
@@ -637,7 +799,20 @@ static gboolean on_ready(gpointer data) {
}
}
- write_manifest(win, probe_widget ? probe_widget : GTK_WIDGET(win));
+ /* Re-fetch rather than reuse `probe_widget`. That pointer was taken before the capture
+ * loop, and the loop builds a tile into `content` and DESTROYS it for every spec and
+ * state, so by here it names freed memory. write_manifest calls
+ * gtk_widget_get_pango_context on it, which is a read straight through the dangling
+ * pointer -- the NULL check above cannot see that, because the pointer is not null, it
+ * is stale.
+ *
+ * This is the intermittent GNOME capture segfault: it depended on what the allocator
+ * had put back in that memory, which is why it reproduced roughly once in three runs
+ * and looked like flakiness. Adding a spec changed the allocation pattern enough to
+ * make it fire every time, which is the only reason it stopped being intermittent and
+ * became findable. */
+ GtkWidget *manifest_probe = content ? gtk_widget_get_first_child(content) : NULL;
+ write_manifest(win, manifest_probe ? manifest_probe : GTK_WIDGET(win));
for (int i = 0; i < blocker_count; i++) {
fprintf(stderr, "NATIVEREF:BLOCKER %s\n", blockers[i]);
@@ -647,7 +822,32 @@ static gboolean on_ready(gpointer data) {
return G_SOURCE_REMOVE;
}
+/* Turns a crash into something diagnosable.
+ *
+ * A capture that dies mid-set reports exit 139 and nothing else, and a segfault inside a
+ * toolkit teardown is the kind that appears one run in three -- so the run that shows it is
+ * not necessarily the run anyone is watching. The handler prints the frames and re-raises,
+ * so the shell still sees the real signal and the job still fails.
+ *
+ * async-signal-safe: backtrace_symbols_fd writes straight to the fd and allocates nothing,
+ * unlike backtrace_symbols.
+ */
+static void on_fatal_signal(int sig) {
+ void *frames[64];
+ int n = backtrace(frames, 64);
+ const char *msg = "NATIVEREF:BLOCKER fatal signal, backtrace follows\n";
+ ssize_t ignored = write(STDERR_FILENO, msg, strlen(msg));
+ (void) ignored;
+ backtrace_symbols_fd(frames, n, STDERR_FILENO);
+ signal(sig, SIG_DFL);
+ raise(sig);
+}
+
int main(int argc, char **argv) {
+ signal(SIGSEGV, on_fatal_signal);
+ signal(SIGABRT, on_fatal_signal);
+ signal(SIGBUS, on_fatal_signal);
+
out_dir = g_getenv("NATIVEREF_OUT");
if (!out_dir) {
fprintf(stderr, "NATIVEREF:ERR NATIVEREF_OUT is not set\n");
diff --git a/scripts/fidelity-app/goldens/README.md b/scripts/fidelity-app/goldens/README.md
index afbd77aa138..d6146d41767 100644
--- a/scripts/fidelity-app/goldens/README.md
+++ b/scripts/fidelity-app/goldens/README.md
@@ -74,12 +74,28 @@ change into a green build.
two consecutive grabs agree.
**The measured residual, recorded rather than tolerated:** the Windows set
- reproduces byte-for-byte except for 2-3 pixels on the slider thumb's
- anti-aliased edge in dark mode, which differ by +/-1 in a channel between runs.
- That is GPU rasterizer rounding; nothing in the app or the environment pins it.
- It is far below the comparator's content threshold and does not move a score.
- It is written down here so the next person does not spend a run discovering it,
- and it is NOT a licence to accept a larger one.
+ reproduces byte-for-byte except for 2-3 pixels on an anti-aliased EDGE, which
+ differ by +/-1 in a channel between runs. That is GPU rasterizer rounding;
+ nothing in the app or the environment pins it. It is far below the comparator's
+ content threshold and does not move a score. It is written down here so the next
+ person does not spend a run discovering it, and it is NOT a licence to accept a
+ larger one.
+
+ Measured again when the second wave of rows landed, and the shape held: two runs
+ of identical code differed on `DesktopSlider_normal_dark` (2px),
+ `DesktopSlider_hover_dark` (3px) and `DesktopTooltip_normal_light` (2px), every
+ one of them +/-1 in a channel on a rounded border or a thumb edge. So it is a
+ property of anti-aliased edges on this runner rather than of the slider, which is
+ the only control the first measurement happened to have.
+
+ GNOME, by contrast, reproduced **byte-for-byte across two runs, all 104 tiles**,
+ including the manifest -- so the residual is not a property of the suite.
+
+ The Windows set also carries one PNG that is NOT a tile: `Button_normal_light.png`,
+ the self-check the app BitBlts to prove the Mica backdrop reached its window. It is
+ excluded from `tiles_written` and must be excluded from the committed set too --
+ it has no CN1 counterpart, so leaving it in makes the golden count disagree with
+ the number of pairs the gate can score.
5. **Record the first baseline separately**, with `FIDELITY_UPDATE_BASELINE=1`, so
the commit that defines the goldens and the commit that defines the ratchet are
two reviewable changes rather than one.
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopDisclosure_normal_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopDisclosure_normal_dark.png
new file mode 100644
index 00000000000..d382144e1ac
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopDisclosure_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopDisclosure_normal_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopDisclosure_normal_light.png
new file mode 100644
index 00000000000..15cf31616ce
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopDisclosure_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopGroupBox_normal_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopGroupBox_normal_dark.png
new file mode 100644
index 00000000000..0b0953135e7
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopGroupBox_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopGroupBox_normal_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopGroupBox_normal_light.png
new file mode 100644
index 00000000000..adba5b9c81a
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopGroupBox_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_disabled_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_disabled_dark.png
new file mode 100644
index 00000000000..757eafb43a6
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_disabled_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_disabled_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_disabled_light.png
new file mode 100644
index 00000000000..95ba6c7a572
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_disabled_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_hover_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_hover_dark.png
new file mode 100644
index 00000000000..59f17c2c421
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_hover_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_hover_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_hover_light.png
new file mode 100644
index 00000000000..910ba79ad0a
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_hover_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_normal_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_normal_dark.png
new file mode 100644
index 00000000000..7b8e6e57aff
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_normal_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_normal_light.png
new file mode 100644
index 00000000000..6cbc0562016
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopLinkButton_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopListRow_normal_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopListRow_normal_dark.png
new file mode 100644
index 00000000000..af087eb7058
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopListRow_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopListRow_normal_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopListRow_normal_light.png
new file mode 100644
index 00000000000..23b4526427c
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopListRow_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopListRow_selected_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopListRow_selected_dark.png
new file mode 100644
index 00000000000..53e494dc503
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopListRow_selected_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopListRow_selected_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopListRow_selected_light.png
new file mode 100644
index 00000000000..89c2793813e
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopListRow_selected_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuBar_normal_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuBar_normal_dark.png
new file mode 100644
index 00000000000..4a4cb2db9eb
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuBar_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuBar_normal_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuBar_normal_light.png
new file mode 100644
index 00000000000..7ea031b96c8
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuBar_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_disabled_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_disabled_dark.png
new file mode 100644
index 00000000000..e658876f94d
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_disabled_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_disabled_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_disabled_light.png
new file mode 100644
index 00000000000..9ec21e64649
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_disabled_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_hover_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_hover_dark.png
new file mode 100644
index 00000000000..fcafb22751e
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_hover_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_hover_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_hover_light.png
new file mode 100644
index 00000000000..e5a11db21e7
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_hover_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_normal_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_normal_dark.png
new file mode 100644
index 00000000000..37492e0cb2e
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_normal_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_normal_light.png
new file mode 100644
index 00000000000..c4ac7a1bf16
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopMenuItem_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_hover_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_hover_dark.png
new file mode 100644
index 00000000000..6c693bd63f2
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_hover_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_hover_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_hover_light.png
new file mode 100644
index 00000000000..12a2ff4b1f1
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_hover_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_normal_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_normal_dark.png
new file mode 100644
index 00000000000..731fb30bdd9
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_normal_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_normal_light.png
new file mode 100644
index 00000000000..16744e83743
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_pressed_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_pressed_dark.png
new file mode 100644
index 00000000000..de32cc48637
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_pressed_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_pressed_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_pressed_light.png
new file mode 100644
index 00000000000..ce2780009a0
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBarHighlight_pressed_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBar_normal_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBar_normal_dark.png
new file mode 100644
index 00000000000..731fb30bdd9
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBar_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBar_normal_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBar_normal_light.png
new file mode 100644
index 00000000000..16744e83743
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopScrollBar_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSearchField_disabled_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSearchField_disabled_dark.png
new file mode 100644
index 00000000000..324c93d5465
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSearchField_disabled_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSearchField_disabled_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSearchField_disabled_light.png
new file mode 100644
index 00000000000..dff48eb383e
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSearchField_disabled_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSearchField_normal_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSearchField_normal_dark.png
new file mode 100644
index 00000000000..1c46c5ea388
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSearchField_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSearchField_normal_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSearchField_normal_light.png
new file mode 100644
index 00000000000..eff7852c974
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSearchField_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSeparator_normal_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSeparator_normal_dark.png
new file mode 100644
index 00000000000..ded4fbd34d7
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSeparator_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSeparator_normal_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSeparator_normal_light.png
new file mode 100644
index 00000000000..64b9a29f69f
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopSeparator_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopStepper_disabled_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopStepper_disabled_dark.png
new file mode 100644
index 00000000000..da43095a214
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopStepper_disabled_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopStepper_disabled_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopStepper_disabled_light.png
new file mode 100644
index 00000000000..457d8fc0ac6
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopStepper_disabled_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopStepper_normal_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopStepper_normal_dark.png
new file mode 100644
index 00000000000..98b387a0d28
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopStepper_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopStepper_normal_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopStepper_normal_light.png
new file mode 100644
index 00000000000..6eeb77bffe2
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopStepper_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopTabs_normal_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopTabs_normal_dark.png
new file mode 100644
index 00000000000..6fd145c1be9
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopTabs_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopTabs_normal_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopTabs_normal_light.png
new file mode 100644
index 00000000000..dcead7cd117
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopTabs_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopToolbar_normal_dark.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopToolbar_normal_dark.png
new file mode 100644
index 00000000000..d5ced9b7aea
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopToolbar_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/DesktopToolbar_normal_light.png b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopToolbar_normal_light.png
new file mode 100644
index 00000000000..98b05d0fe91
Binary files /dev/null and b/scripts/fidelity-app/goldens/gnome-adwaita/DesktopToolbar_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/gnome-adwaita/capture-manifest.json b/scripts/fidelity-app/goldens/gnome-adwaita/capture-manifest.json
index 65967990227..67aa0145ae3 100644
--- a/scripts/fidelity-app/goldens/gnome-adwaita/capture-manifest.json
+++ b/scripts/fidelity-app/goldens/gnome-adwaita/capture-manifest.json
@@ -27,7 +27,7 @@
"window": {
"active": true
},
- "tiles_written": 60,
+ "tiles_written": 104,
"backdrop_by_appearance": {"light": "#FAFAFA", "dark": "#242424"},
"states_identical_to_normal": ["DesktopTextField_hover_light", "DesktopTextField_hover_dark"],
"blockers": []
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopDisclosure_normal_dark.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopDisclosure_normal_dark.png
new file mode 100644
index 00000000000..ea16d8a7bd3
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopDisclosure_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopDisclosure_normal_light.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopDisclosure_normal_light.png
new file mode 100644
index 00000000000..cfae31f87c4
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopDisclosure_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopGroupBox_normal_dark.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopGroupBox_normal_dark.png
new file mode 100644
index 00000000000..33ba3084046
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopGroupBox_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopGroupBox_normal_light.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopGroupBox_normal_light.png
new file mode 100644
index 00000000000..db720a62159
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopGroupBox_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_disabled_dark.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_disabled_dark.png
new file mode 100644
index 00000000000..6b9bcbb6c50
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_disabled_dark.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_disabled_light.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_disabled_light.png
new file mode 100644
index 00000000000..f523416c29b
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_disabled_light.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_hover_dark.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_hover_dark.png
new file mode 100644
index 00000000000..369037bf7c1
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_hover_dark.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_hover_light.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_hover_light.png
new file mode 100644
index 00000000000..244617ff6c5
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_hover_light.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_normal_dark.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_normal_dark.png
new file mode 100644
index 00000000000..369037bf7c1
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_normal_light.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_normal_light.png
new file mode 100644
index 00000000000..244617ff6c5
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopLinkButton_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopListRow_normal_dark.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopListRow_normal_dark.png
new file mode 100644
index 00000000000..07e06035efe
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopListRow_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopListRow_normal_light.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopListRow_normal_light.png
new file mode 100644
index 00000000000..0fe28041820
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopListRow_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopListRow_selected_dark.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopListRow_selected_dark.png
new file mode 100644
index 00000000000..374620abf87
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopListRow_selected_dark.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopListRow_selected_light.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopListRow_selected_light.png
new file mode 100644
index 00000000000..1eea67f5013
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopListRow_selected_light.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopSearchField_disabled_dark.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopSearchField_disabled_dark.png
new file mode 100644
index 00000000000..f7b8dc2f905
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopSearchField_disabled_dark.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopSearchField_disabled_light.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopSearchField_disabled_light.png
new file mode 100644
index 00000000000..2aa4f9e9cb6
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopSearchField_disabled_light.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopSearchField_normal_dark.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopSearchField_normal_dark.png
new file mode 100644
index 00000000000..e76cc55ad05
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopSearchField_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopSearchField_normal_light.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopSearchField_normal_light.png
new file mode 100644
index 00000000000..62e28824a35
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopSearchField_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopSeparator_normal_dark.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopSeparator_normal_dark.png
new file mode 100644
index 00000000000..9466eadc23b
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopSeparator_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopSeparator_normal_light.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopSeparator_normal_light.png
new file mode 100644
index 00000000000..547839cae58
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopSeparator_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopStepper_disabled_dark.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopStepper_disabled_dark.png
new file mode 100644
index 00000000000..abf6fa652b2
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopStepper_disabled_dark.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopStepper_disabled_light.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopStepper_disabled_light.png
new file mode 100644
index 00000000000..2d0513a8b1e
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopStepper_disabled_light.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopStepper_normal_dark.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopStepper_normal_dark.png
new file mode 100644
index 00000000000..2f0772dfeed
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopStepper_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopStepper_normal_light.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopStepper_normal_light.png
new file mode 100644
index 00000000000..5f937252643
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopStepper_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopTabs_normal_dark.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopTabs_normal_dark.png
new file mode 100644
index 00000000000..2035940ab43
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopTabs_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopTabs_normal_light.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopTabs_normal_light.png
new file mode 100644
index 00000000000..1a0838f7109
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopTabs_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopToolbar_normal_dark.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopToolbar_normal_dark.png
new file mode 100644
index 00000000000..5e71c30962b
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopToolbar_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/DesktopToolbar_normal_light.png b/scripts/fidelity-app/goldens/macos-aqua/DesktopToolbar_normal_light.png
new file mode 100644
index 00000000000..d40dcab01b5
Binary files /dev/null and b/scripts/fidelity-app/goldens/macos-aqua/DesktopToolbar_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/macos-aqua/capture-manifest.json b/scripts/fidelity-app/goldens/macos-aqua/capture-manifest.json
index 06f7c2e6d90..036d76d6a87 100644
--- a/scripts/fidelity-app/goldens/macos-aqua/capture-manifest.json
+++ b/scripts/fidelity-app/goldens/macos-aqua/capture-manifest.json
@@ -3,7 +3,7 @@
"platform": "macos",
"golden_set": "macos-aqua",
"mode": "capture",
- "tiles_written": 60,
+ "tiles_written": 88,
"backdrop_by_appearance": {"dark": "#262626", "light": "#E7E7E7"},
"os": {
"version": "15.7.9",
@@ -32,7 +32,7 @@
"reduce_transparency": false,
"increase_contrast": false
},
- "states_identical_to_normal": ["DesktopButton_hover_light", "DesktopAccentButton_hover_light", "DesktopTextField_hover_light", "DesktopCheckBox_hover_light", "DesktopRadioButton_hover_light", "DesktopSwitch_hover_light", "DesktopSlider_hover_light", "DesktopComboBox_hover_light", "DesktopButton_hover_dark", "DesktopAccentButton_hover_dark", "DesktopTextField_hover_dark", "DesktopCheckBox_hover_dark", "DesktopRadioButton_hover_dark", "DesktopSwitch_hover_dark", "DesktopSlider_hover_dark", "DesktopComboBox_hover_dark"],
+ "states_identical_to_normal": ["DesktopButton_hover_light", "DesktopAccentButton_hover_light", "DesktopTextField_hover_light", "DesktopCheckBox_hover_light", "DesktopRadioButton_hover_light", "DesktopSwitch_hover_light", "DesktopSlider_hover_light", "DesktopComboBox_hover_light", "DesktopLinkButton_hover_light", "DesktopButton_hover_dark", "DesktopAccentButton_hover_dark", "DesktopTextField_hover_dark", "DesktopCheckBox_hover_dark", "DesktopRadioButton_hover_dark", "DesktopSwitch_hover_dark", "DesktopSlider_hover_dark", "DesktopComboBox_hover_dark", "DesktopLinkButton_hover_dark"],
"capture": {
"method": "cachedisplay",
"vibrancy_capturable": false,
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopDisclosure_normal_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopDisclosure_normal_dark.png
new file mode 100644
index 00000000000..47fbc106817
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopDisclosure_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopDisclosure_normal_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopDisclosure_normal_light.png
new file mode 100644
index 00000000000..3f7a27d35c8
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopDisclosure_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopGroupBox_normal_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopGroupBox_normal_dark.png
new file mode 100644
index 00000000000..c1ee6cc87b3
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopGroupBox_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopGroupBox_normal_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopGroupBox_normal_light.png
new file mode 100644
index 00000000000..1ed0721be57
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopGroupBox_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_disabled_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_disabled_dark.png
new file mode 100644
index 00000000000..9c56511e58a
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_disabled_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_disabled_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_disabled_light.png
new file mode 100644
index 00000000000..ac1b522cf20
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_disabled_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_hover_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_hover_dark.png
new file mode 100644
index 00000000000..60b6c55fe77
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_hover_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_hover_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_hover_light.png
new file mode 100644
index 00000000000..fc2b5b3a8d8
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_hover_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_normal_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_normal_dark.png
new file mode 100644
index 00000000000..9f25e12b965
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_normal_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_normal_light.png
new file mode 100644
index 00000000000..2428f58d0be
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopLinkButton_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopListRow_normal_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopListRow_normal_dark.png
new file mode 100644
index 00000000000..4c2e9add99f
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopListRow_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopListRow_normal_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopListRow_normal_light.png
new file mode 100644
index 00000000000..ff67d35bb02
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopListRow_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopListRow_selected_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopListRow_selected_dark.png
new file mode 100644
index 00000000000..9f4f9e7bf8c
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopListRow_selected_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopListRow_selected_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopListRow_selected_light.png
new file mode 100644
index 00000000000..87b0e88741b
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopListRow_selected_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuBar_normal_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuBar_normal_dark.png
new file mode 100644
index 00000000000..2d7b7b7e1a9
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuBar_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuBar_normal_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuBar_normal_light.png
new file mode 100644
index 00000000000..e212d1516e3
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuBar_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_disabled_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_disabled_dark.png
new file mode 100644
index 00000000000..6c11f74c93b
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_disabled_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_disabled_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_disabled_light.png
new file mode 100644
index 00000000000..80fa7e4301e
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_disabled_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_hover_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_hover_dark.png
new file mode 100644
index 00000000000..28e770198ec
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_hover_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_hover_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_hover_light.png
new file mode 100644
index 00000000000..f84ef8501c5
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_hover_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_normal_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_normal_dark.png
new file mode 100644
index 00000000000..ee2ae8fd403
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_normal_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_normal_light.png
new file mode 100644
index 00000000000..afaaf1f7dce
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopMenuItem_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopScrollBar_normal_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopScrollBar_normal_dark.png
new file mode 100644
index 00000000000..310eaee6c89
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopScrollBar_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopScrollBar_normal_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopScrollBar_normal_light.png
new file mode 100644
index 00000000000..b1c02559d8f
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopScrollBar_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSearchField_disabled_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSearchField_disabled_dark.png
new file mode 100644
index 00000000000..2479ae6d948
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSearchField_disabled_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSearchField_disabled_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSearchField_disabled_light.png
new file mode 100644
index 00000000000..9364a20db2f
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSearchField_disabled_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSearchField_normal_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSearchField_normal_dark.png
new file mode 100644
index 00000000000..c2d99d03b4d
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSearchField_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSearchField_normal_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSearchField_normal_light.png
new file mode 100644
index 00000000000..12cb42b2522
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSearchField_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSeparator_normal_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSeparator_normal_dark.png
new file mode 100644
index 00000000000..b5c8481d196
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSeparator_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSeparator_normal_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSeparator_normal_light.png
new file mode 100644
index 00000000000..52483e49f0b
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSeparator_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSlider_disabled_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSlider_disabled_dark.png
index 05131a2ba20..0e6d3ad2b71 100644
Binary files a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSlider_disabled_dark.png and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSlider_disabled_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSlider_normal_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSlider_normal_dark.png
index 3f3e7cafc14..dc46be2b34f 100644
Binary files a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSlider_normal_dark.png and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopSlider_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopStepper_disabled_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopStepper_disabled_dark.png
new file mode 100644
index 00000000000..322fc0e497d
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopStepper_disabled_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopStepper_disabled_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopStepper_disabled_light.png
new file mode 100644
index 00000000000..f69a72008a3
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopStepper_disabled_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopStepper_normal_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopStepper_normal_dark.png
new file mode 100644
index 00000000000..ca5ebac8481
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopStepper_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopStepper_normal_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopStepper_normal_light.png
new file mode 100644
index 00000000000..9dac230bc75
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopStepper_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopTabs_normal_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopTabs_normal_dark.png
new file mode 100644
index 00000000000..0d77a618fbe
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopTabs_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopTabs_normal_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopTabs_normal_light.png
new file mode 100644
index 00000000000..be7c4692333
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopTabs_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopToolbar_normal_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopToolbar_normal_dark.png
new file mode 100644
index 00000000000..b1c79717aae
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopToolbar_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopToolbar_normal_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopToolbar_normal_light.png
new file mode 100644
index 00000000000..820e4ffc031
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopToolbar_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopTooltip_normal_dark.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopTooltip_normal_dark.png
new file mode 100644
index 00000000000..dc6603922d3
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopTooltip_normal_dark.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/DesktopTooltip_normal_light.png b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopTooltip_normal_light.png
new file mode 100644
index 00000000000..a2ecdd8fa85
Binary files /dev/null and b/scripts/fidelity-app/goldens/windows-11-fluent/DesktopTooltip_normal_light.png differ
diff --git a/scripts/fidelity-app/goldens/windows-11-fluent/capture-manifest.json b/scripts/fidelity-app/goldens/windows-11-fluent/capture-manifest.json
index 67f42e77dcd..d4afd0258ca 100644
--- a/scripts/fidelity-app/goldens/windows-11-fluent/capture-manifest.json
+++ b/scripts/fidelity-app/goldens/windows-11-fluent/capture-manifest.json
@@ -35,7 +35,7 @@
"control_family": "Segoe UI Variable",
"segoe_ui_variable_installed": true
},
- "tiles_written": 60,
+ "tiles_written": 100,
"animations_disabled_by_app": true,
"backdrop_by_appearance": {"light": "#F3F3F3", "dark": "#202020"},
"states_identical_to_normal": [],
diff --git a/scripts/fidelity-app/macos-native-ref/NativeRef.swift b/scripts/fidelity-app/macos-native-ref/NativeRef.swift
index 11c9f564498..53bc16a6116 100644
--- a/scripts/fidelity-app/macos-native-ref/NativeRef.swift
+++ b/scripts/fidelity-app/macos-native-ref/NativeRef.swift
@@ -83,6 +83,22 @@ final class TileView: NSView {
}
}
+/// The strip a window shows where its title bar is, for the DesktopToolbar row.
+///
+/// NSToolbar belongs to a window and cannot be rendered into a view, so the reference is the
+/// surface the toolbar sits on plus the window title -- which is what the CN1 Toolbar UIID
+/// draws, and what comparing against a detached NSToolbar would NOT be.
+///
+/// Drawn rather than layer-backed, like TileView and for the same reason: a CGColor taken
+/// from a dynamic NSColor freezes at the appearance it was read in.
+final class TitleBarStripView: NSView {
+ override var isFlipped: Bool { true }
+ override func draw(_ dirtyRect: NSRect) {
+ NSColor.windowBackgroundColor.setFill()
+ dirtyRect.fill()
+ }
+}
+
/// One row of the desktop matrix. `kind` is the native_mac key in fidelity-tests.yaml, and
/// the ids and states are that file's too: the two lists must agree or the comparator pairs
/// a CN1 render against nothing.
@@ -102,6 +118,21 @@ let SPECS: [Spec] = [
Spec(id: "DesktopSlider", kind: "appkit_slider", states: ["normal", "hover", "disabled"]),
Spec(id: "DesktopProgressBar", kind: "appkit_progress", states: ["normal"]),
Spec(id: "DesktopComboBox", kind: "appkit_popupbutton", states: ["normal", "hover", "disabled"]),
+
+ // Second wave. No menu bar and no tooltip row here: NSMenu and an AppKit tooltip are
+ // window-server surfaces, invisible to cacheDisplay, which is the capture path that needs
+ // no Screen Recording consent. Those rows carry a platforms: list in the spec rather than
+ // a blank golden that would score 0% forever and read as a theme bug -- the same call
+ // already made for Aqua vibrancy.
+ Spec(id: "DesktopSeparator", kind: "appkit_box_separator", states: ["normal"]),
+ Spec(id: "DesktopGroupBox", kind: "appkit_box_titled", states: ["normal"]),
+ Spec(id: "DesktopStepper", kind: "appkit_stepper", states: ["normal", "disabled"]),
+ Spec(id: "DesktopLinkButton", kind: "appkit_link_button", states: ["normal", "hover", "disabled"]),
+ Spec(id: "DesktopSearchField", kind: "appkit_searchfield", states: ["normal", "disabled"]),
+ Spec(id: "DesktopListRow", kind: "appkit_tableview_row", states: ["normal", "selected"]),
+ Spec(id: "DesktopTabs", kind: "appkit_tabview", states: ["normal"]),
+ Spec(id: "DesktopToolbar", kind: "appkit_toolbar", states: ["normal"]),
+ Spec(id: "DesktopDisclosure", kind: "appkit_disclosure", states: ["normal"]),
]
/// Controls that own the full tile width rather than sizing to their content. A slider, a
@@ -109,7 +140,24 @@ let SPECS: [Spec] = [
/// asked for -- so the tile width is the honest answer, and it is the same rule the CN1
/// renderer applies. Left to size themselves, a text field measures to its placeholder
/// (39px for "Text"), which is not a control anyone would recognise or ship.
-let FULL_WIDTH_KINDS: Set = ["appkit_slider", "appkit_progress", "appkit_textfield"]
+let FULL_WIDTH_KINDS: Set = [
+ "appkit_slider", "appkit_progress", "appkit_textfield",
+ // Second wave, same rule: none of these has a natural width either. A search field
+ // measures to its placeholder, and a row, a box, a tab view and a toolbar are all
+ // containers that take the width they are given.
+ "appkit_searchfield", "appkit_tableview_row",
+ "appkit_box_titled", "appkit_tabview", "appkit_toolbar",
+ "appkit_box_separator",
+]
+
+/// Controls that own the full tile HEIGHT rather than sizing to their content.
+///
+/// A group box is a frame around other things, so its height is whatever it is given -- left
+/// to measure itself it collapses onto its own title and draws no frame at all, which is a
+/// heading, not a group box. The CN1 side applies the same rule through
+/// DesktopTileRunner.FULL_HEIGHT_IDS, and the two lists are kept in step by hand exactly as
+/// the full-width ones are.
+let FULL_HEIGHT_KINDS: Set = ["appkit_box_titled", "appkit_tabview"]
final class RefApp: NSObject, NSApplicationDelegate {
var window: NSWindow!
@@ -204,6 +252,147 @@ final class RefApp: NSObject, NSApplicationDelegate {
let pop = NSPopUpButton(frame: .zero, pullsDown: false)
pop.addItem(withTitle: "Option")
return pop
+ case "appkit_box_separator":
+ // NSBox in .separator mode IS AppKit's horizontal rule -- the same object as the
+ // titled box above, which is why both are NSBox here rather than one of them being
+ // a hand-drawn line. It has no natural width, so it is in FULL_WIDTH_KINDS.
+ let sep = NSBox(frame: NSRect(x: 0, y: 0, width: TILE_W, height: 1))
+ sep.boxType = .separator
+ return sep
+ case "appkit_box_titled":
+ // The label goes INSIDE the default content view. Assigning it AS the content view
+ // replaces the view the box draws its frame around, so the frame disappeared and
+ // the label was clipped by a box that had sized itself to nothing.
+ let box = NSBox(frame: NSRect(x: 0, y: 0, width: TILE_W, height: TILE_H))
+ box.title = "Group"
+ box.titlePosition = .atTop
+ box.boxType = .primary
+ let body = NSTextField(labelWithString: "Item")
+ body.sizeToFit()
+ body.setFrameOrigin(NSPoint(x: 4, y: 4))
+ box.contentView?.addSubview(body)
+ return box
+ case "appkit_stepper":
+ // The NSStepper alone is the two chevrons; the number beside it is a separate
+ // field, and the CN1 Stepper is the pair. Built as the pair so the two sides
+ // compare the same control rather than half of one.
+ //
+ // A plain container with explicit frames, not an NSStackView: a stack view's
+ // fittingSize came back with no width, so the tile showed the chevrons and no
+ // field at all -- half a control, which is exactly what this pairing exists to
+ // avoid.
+ let field = NSTextField(string: "1")
+ field.isBezeled = true
+ field.bezelStyle = .roundedBezel
+ field.sizeToFit()
+ field.setFrameSize(NSSize(width: max(field.frame.width, 48),
+ height: field.frame.height))
+ let stepper = NSStepper()
+ stepper.minValue = 0
+ stepper.maxValue = 10
+ stepper.doubleValue = 1
+ stepper.sizeToFit()
+ let h = max(field.frame.height, stepper.frame.height)
+ let row = NSView(frame: NSRect(x: 0, y: 0,
+ width: field.frame.width + 2 + stepper.frame.width,
+ height: h))
+ field.setFrameOrigin(NSPoint(x: 0, y: (h - field.frame.height) / 2))
+ stepper.setFrameOrigin(NSPoint(x: field.frame.width + 2,
+ y: (h - stepper.frame.height) / 2))
+ row.addSubview(field)
+ row.addSubview(stepper)
+ return row
+ case "appkit_link_button":
+ // NSButton's own link style, not a text field with an attributed string: the
+ // latter is what an application writes when the platform has no link control,
+ // and AppKit has one.
+ let b = NSButton(title: "Link", target: nil, action: nil)
+ b.isBordered = false
+ b.contentTintColor = .linkColor
+ b.attributedTitle = NSAttributedString(
+ string: "Link",
+ attributes: [.foregroundColor: NSColor.linkColor,
+ .underlineStyle: NSUnderlineStyle.single.rawValue])
+ return b
+ case "appkit_searchfield":
+ let f = NSSearchField(string: "Search")
+ f.isEditable = true
+ return f
+ case "appkit_tableview_row":
+ // A row view with a cell in it, which is what a single NSTableView row draws.
+ //
+ // The frame is explicit because NSTableRowView has no intrinsic size in either
+ // axis -- measured: it laid out to 240x0 and produced no image at all, which the
+ // zero-size blocker caught. 24pt is the standard NSTableView row height, which is
+ // what a table would have given it.
+ let rowHeight: CGFloat = 24
+ let row = NSTableRowView(frame: NSRect(x: 0, y: 0, width: TILE_W, height: rowHeight))
+ // Emphasized, so a selected row draws the ACCENT fill rather than the grey one.
+ // An NSTableRowView outside a focused table is unemphasized by default, and grey
+ // is what macOS shows for a selection in a window the user is not working in --
+ // not what a selected row looks like while they are. Measured: the unemphasized
+ // reference scored the CN1 row at 67%, against a CN1 style that is correctly
+ // accent-filled.
+ row.isEmphasized = true
+ let label = NSTextField(labelWithString: "Row")
+ label.sizeToFit()
+ label.setFrameOrigin(NSPoint(x: 4, y: (rowHeight - label.frame.height) / 2))
+ row.addSubview(label)
+ return row
+ case "appkit_tabview":
+ // Full height as well as full width (see FULL_HEIGHT_KINDS). Left to its fitting
+ // size an NSTabView is taller than the tile and its tab strip came out clipped
+ // through its own top edge -- a reference that is cut in half measures nothing,
+ // whatever the number underneath says.
+ let tv = NSTabView()
+ let one = NSTabViewItem(identifier: "one")
+ one.label = "One"
+ let two = NSTabViewItem(identifier: "two")
+ two.label = "Two"
+ tv.addTabViewItem(one)
+ tv.addTabViewItem(two)
+ return tv
+ case "appkit_toolbar":
+ // NSToolbar belongs to a window and cannot be rendered into a view, so the
+ // reference is the strip a window shows in its place: the title bar's own
+ // background with the window title on it. That is what the CN1 Toolbar UIID
+ // draws, and comparing it against a detached NSToolbar would compare two
+ // different things.
+ // The fill is DRAWN, not assigned to a layer. A CGColor taken from a dynamic
+ // NSColor is resolved once, at whatever appearance was in force when it was read,
+ // so the light tile came out with the dark window background painted across it.
+ // TileView draws its own fill for exactly this reason.
+ let strip = TitleBarStripView(frame: NSRect(x: 0, y: 0, width: TILE_W, height: TILE_H))
+ let title = NSTextField(labelWithString: "Title")
+ title.font = NSFont.titleBarFont(ofSize: NSFont.systemFontSize)
+ title.sizeToFit()
+ title.setFrameOrigin(NSPoint(
+ x: (TILE_W - title.frame.width) / 2,
+ y: (TILE_H - title.frame.height) / 2))
+ strip.addSubview(title)
+ return strip
+ case "appkit_disclosure":
+ // The triangle AND its label. AppKit's .disclosure bezel draws the triangle only
+ // and ignores the title outright -- measured: the tile came out as a bare chevron
+ // with no text, against a CN1 accordion header that is a labelled row. A titled
+ // disclosure on macOS is the triangle with a label beside it, which is what a
+ // sidebar or an inspector section actually shows.
+ let triangle = NSButton(title: "", target: nil, action: nil)
+ triangle.setButtonType(.pushOnPushOff)
+ triangle.bezelStyle = .disclosure
+ triangle.sizeToFit()
+ let label = NSTextField(labelWithString: "Details")
+ label.sizeToFit()
+ let h = max(triangle.frame.height, label.frame.height)
+ let row = NSView(frame: NSRect(x: 0, y: 0,
+ width: triangle.frame.width + 4 + label.frame.width,
+ height: h))
+ triangle.setFrameOrigin(NSPoint(x: 0, y: (h - triangle.frame.height) / 2))
+ label.setFrameOrigin(NSPoint(x: triangle.frame.width + 4,
+ y: (h - label.frame.height) / 2))
+ row.addSubview(triangle)
+ row.addSubview(label)
+ return row
case "appkit_progress":
let p = NSProgressIndicator()
p.style = .bar
@@ -238,11 +427,26 @@ final class RefApp: NSObject, NSApplicationDelegate {
return true
case "selected":
if let sw = view as? NSSwitch { sw.state = .on; return true }
+ if let row = view as? NSTableRowView { row.isSelected = true; return true }
if let b = view as? NSButton { b.state = .on; return true }
+ // A composite: the disclosure is a triangle plus a label, and the state belongs
+ // to the triangle. Recursed rather than special-cased by kind, because the state
+ // is always a property of one control inside the composite and the alternative is
+ // a second table mapping kinds to which subview to reach for.
+ for sub in view.subviews where applyState(sub, state, kind) {
+ _ = sub
+ return true
+ }
return false
case "disabled":
if let c = view as? NSControl { c.isEnabled = false; return true }
- return false
+ // The stepper is a field plus a stepper and BOTH halves have to grey out; unlike
+ // selected, this is not one control's state, so it does not stop at the first.
+ var reached = false
+ for sub in view.subviews {
+ if applyState(sub, state, kind) { reached = true }
+ }
+ return reached
default:
blocker("unknown state '\(state)'")
return false
@@ -284,6 +488,9 @@ final class RefApp: NSObject, NSApplicationDelegate {
var size = widget.fittingSize
if size.height <= 0 { size.height = widget.intrinsicContentSize.height }
if size.height <= 0 { size.height = widget.frame.height }
+ if FULL_HEIGHT_KINDS.contains(spec.kind) {
+ size.height = TILE_H
+ }
if FULL_WIDTH_KINDS.contains(spec.kind) {
size.width = TILE_W
} else {
diff --git a/scripts/fidelity-app/windows-native-ref/Program.cs b/scripts/fidelity-app/windows-native-ref/Program.cs
index 560a299dd97..b93b6f58bc1 100644
--- a/scripts/fidelity-app/windows-native-ref/Program.cs
+++ b/scripts/fidelity-app/windows-native-ref/Program.cs
@@ -63,6 +63,7 @@
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI;
using Microsoft.UI.Windowing;
@@ -158,6 +159,25 @@ private sealed record Spec(string Id, string Kind, string[] States);
new("DesktopSlider", "winui_slider", new[] { "normal", "hover", "disabled" }),
new("DesktopProgressBar", "winui_progressbar", new[] { "normal" }),
new("DesktopComboBox", "winui_combobox", new[] { "normal", "hover", "disabled" }),
+
+ // Second wave. Windows carries three rows the other two references cannot: its menu
+ // bar and menu item are ordinary Controls that render into a view, and so is its
+ // ToolTip, where the AppKit and GTK equivalents are window-server surfaces. The
+ // scrollbar is here and not on macOS for the same reason -- see the DesktopScrollBar
+ // note in fidelity-tests.yaml for what was measured.
+ new("DesktopScrollBar", "winui_scrollbar", new[] { "normal" }),
+ new("DesktopSeparator", "winui_separator", new[] { "normal" }),
+ new("DesktopGroupBox", "winui_groupbox", new[] { "normal" }),
+ new("DesktopStepper", "winui_numberbox", new[] { "normal", "disabled" }),
+ new("DesktopLinkButton", "winui_hyperlinkbutton", new[] { "normal", "hover", "disabled" }),
+ new("DesktopSearchField", "winui_autosuggestbox", new[] { "normal", "disabled" }),
+ new("DesktopListRow", "winui_listviewitem", new[] { "normal", "selected" }),
+ new("DesktopTabs", "winui_tabview", new[] { "normal" }),
+ new("DesktopToolbar", "winui_commandbar", new[] { "normal" }),
+ new("DesktopDisclosure", "winui_expander", new[] { "normal" }),
+ new("DesktopMenuBar", "winui_menubar", new[] { "normal" }),
+ new("DesktopMenuItem", "winui_menuflyoutitem", new[] { "normal", "hover", "disabled" }),
+ new("DesktopTooltip", "winui_tooltip", new[] { "normal" }),
};
/// Controls with no natural width: layout always assigns one, so the tile width is the
@@ -165,7 +185,21 @@ private sealed record Spec(string Id, string Kind, string[] States);
/// and FULL_WIDTH_IDS in DesktopTileRunner. If one side stretches a control and the
/// other does not, the comparison is between two geometries and the score means nothing.
private static bool IsFullWidth(string kind) =>
- kind is "winui_slider" or "winui_progressbar" or "winui_textbox";
+ kind is "winui_slider" or "winui_progressbar" or "winui_textbox"
+ // Second wave, same rule: a search field measures to its placeholder, and a row,
+ // a box, a tab strip, a command bar and a menu bar are containers that take the
+ // width they are given.
+ or "winui_autosuggestbox" or "winui_listviewitem"
+ or "winui_groupbox" or "winui_tabview" or "winui_commandbar" or "winui_menubar"
+ or "winui_separator";
+
+ /// Controls with no natural HEIGHT, the same rule on the other axis. A group box is a
+ /// frame around other things -- left to measure itself it collapses onto its own header
+ /// and draws no frame, which is a heading rather than a group box -- and a vertical
+ /// scrollbar is defined by its length. Kept in step BY HAND with FULL_HEIGHT_KINDS in the
+ /// macOS and GNOME references and FULL_HEIGHT_IDS in DesktopTileRunner.
+ private static bool IsFullHeight(string kind) =>
+ kind is "winui_groupbox" or "winui_scrollbar";
private static FrameworkElement MakeWidget(string kind) => kind switch
{
@@ -185,9 +219,126 @@ private static bool IsFullWidth(string kind) =>
"winui_slider" => new Slider { Minimum = 0, Maximum = 1, Value = 0.5, StepFrequency = 0.01 },
"winui_progressbar" => new ProgressBar { Minimum = 0, Maximum = 1, Value = 0.6 },
"winui_combobox" => MakeComboBox(),
+
+ // A ScrollBar in its always-visible form with the thumb at the top covering two
+ // fifths of the track. Both sides have to agree about where the thumb is before
+ // anything about its colour or shape can be compared, and the CN1 side is drawn at
+ // the same proportion and offset.
+ "winui_scrollbar" => new ScrollBar
+ {
+ Orientation = Orientation.Vertical,
+ Minimum = 0,
+ Maximum = 100,
+ Value = 0,
+ ViewportSize = 40,
+ IndicatorMode = ScrollingIndicatorMode.MouseIndicator,
+ Visibility = Visibility.Visible,
+ },
+ "winui_separator" => MakeSeparator(),
+ "winui_groupbox" => MakeGroupBox(),
+ // NumberBox with its spin buttons shown inline, which is the WinUI stepper. Without
+ // SpinButtonPlacementMode it is a plain number field and the control under
+ // comparison would be missing half of itself.
+ "winui_numberbox" => new NumberBox
+ {
+ Value = 1,
+ Minimum = 0,
+ Maximum = 10,
+ SpinButtonPlacementMode = NumberBoxSpinButtonPlacementMode.Inline,
+ },
+ "winui_hyperlinkbutton" => new HyperlinkButton { Content = "Link" },
+ "winui_autosuggestbox" => new AutoSuggestBox
+ {
+ Text = "Search",
+ QueryIcon = new SymbolIcon(Symbol.Find),
+ },
+ "winui_listviewitem" => new ListViewItem { Content = "Row" },
+ "winui_tabview" => MakeTabView(),
+ "winui_commandbar" => MakeCommandBar(),
+ "winui_expander" => new Expander { Header = "Details", Content = new TextBlock { Text = "Item" } },
+ "winui_menubar" => MakeMenuBar(),
+ "winui_menuflyoutitem" => new MenuFlyoutItem { Text = "Open" },
+ // A WinUI ToolTip is an ordinary Control and renders into a view, which is why this
+ // row exists here and nowhere else: the AppKit and GTK tooltips are separate windows
+ // the capture path cannot see.
+ "winui_tooltip" => new ToolTip { Content = "Tooltip" },
_ => null,
};
+ private static FrameworkElement MakeSeparator()
+ {
+ // WinUI has no Separator control for content: the platform draws a horizontal rule as a
+ // one-pixel Border in DividerStrokeColorDefaultBrush, which is what its own settings
+ // pages use between groups. MenuFlyoutSeparator exists but is a menu primitive with
+ // menu insets, so it would be measuring the wrong thing.
+ return new Border
+ {
+ Height = 1,
+ HorizontalAlignment = HorizontalAlignment.Stretch,
+ VerticalAlignment = VerticalAlignment.Center,
+ Background = (Brush)Application.Current.Resources["DividerStrokeColorDefaultBrush"],
+ };
+ }
+
+ private static FrameworkElement MakeGroupBox()
+ {
+ // WinUI has no GroupBox control. Its headered-content convention is a Border with a
+ // caption above it, which is what the platform's own settings pages draw and what the
+ // GroupBox UIID has to match -- so that is built here rather than a control being
+ // substituted from another toolkit's vocabulary.
+ var caption = new TextBlock
+ {
+ Text = "Group",
+ Style = (Style)Application.Current.Resources["CaptionTextBlockStyle"],
+ };
+ var body = new Border
+ {
+ BorderThickness = new Thickness(1),
+ BorderBrush = (Brush)Application.Current.Resources["CardStrokeColorDefaultBrush"],
+ CornerRadius = new CornerRadius(4),
+ Padding = new Thickness(8),
+ Child = new TextBlock { Text = "Item" },
+ };
+ var panel = new StackPanel { Orientation = Orientation.Vertical, Spacing = 4 };
+ panel.Children.Add(caption);
+ panel.Children.Add(body);
+ return panel;
+ }
+
+ private static TabView MakeTabView()
+ {
+ // Not closable, and no add button. A WinUI TabView is a document-tab control and
+ // shows a close affordance on every tab by default; a Codename One Tabs has no such
+ // thing, so leaving them on compares two tabs against two tabs plus two buttons and
+ // charges the difference to the theme.
+ var tv = new TabView { IsAddTabButtonVisible = false };
+ tv.TabItems.Add(new TabViewItem { Header = "One", IsClosable = false });
+ tv.TabItems.Add(new TabViewItem { Header = "Two", IsClosable = false });
+ tv.SelectedIndex = 0;
+ return tv;
+ }
+
+ private static CommandBar MakeCommandBar()
+ {
+ var bar = new CommandBar { DefaultLabelPosition = CommandBarDefaultLabelPosition.Right };
+ bar.Content = new TextBlock
+ {
+ Text = "Title",
+ Margin = new Thickness(12, 0, 0, 0),
+ VerticalAlignment = VerticalAlignment.Center,
+ };
+ return bar;
+ }
+
+ private static MenuBar MakeMenuBar()
+ {
+ var bar = new MenuBar();
+ var file = new MenuBarItem { Title = "File" };
+ file.Items.Add(new MenuFlyoutItem { Text = "Open" });
+ bar.Items.Add(file);
+ return bar;
+ }
+
private static ComboBox MakeComboBox()
{
var c = new ComboBox();
@@ -219,6 +370,7 @@ private bool ApplyState(FrameworkElement widget, string state, string kind, stri
if (widget is ToggleSwitch ts) { ts.IsOn = true; return true; }
if (widget is CheckBox cb) { cb.IsChecked = true; return true; }
if (widget is RadioButton rb) { rb.IsChecked = true; return true; }
+ if (widget is ListViewItem lvi) { lvi.IsSelected = true; return true; }
_blockers.Add($"{tileName}: {kind} has no selected state");
return false;
case "disabled":
@@ -233,9 +385,15 @@ private bool ApplyState(FrameworkElement widget, string state, string kind, stri
_blockers.Add($"{tileName}: {kind} is not a Control, so it has no visual states");
return false;
}
+ // "MouseOver" and "Dragging" are the ScrollBar template's own names: that
+ // control's CommonStates predate the PointerOver vocabulary and were never
+ // renamed. Tried after the modern names rather than instead of them, and a
+ // name that does not exist simply returns false and falls through to the
+ // next -- the blocker below is what fires when none of them matched.
string[] candidates = state == "hover"
- ? new[] { "PointerOver", "UncheckedPointerOver", "CheckedPointerOver" }
- : new[] { "Pressed", "UncheckedPressed", "CheckedPressed" };
+ ? new[] { "PointerOver", "UncheckedPointerOver", "CheckedPointerOver",
+ "MouseOver" }
+ : new[] { "Pressed", "UncheckedPressed", "CheckedPressed", "Dragging" };
foreach (var name in candidates)
{
if (VisualStateManager.GoToState(control, name, false))
@@ -549,7 +707,9 @@ await OnUiAsync(() =>
w.HorizontalAlignment = IsFullWidth(spec.Kind)
? HorizontalAlignment.Stretch
: HorizontalAlignment.Left;
- w.VerticalAlignment = VerticalAlignment.Top;
+ w.VerticalAlignment = IsFullHeight(spec.Kind)
+ ? VerticalAlignment.Stretch
+ : VerticalAlignment.Top;
w.Margin = new Thickness(0);
var host = new Grid
diff --git a/scripts/hellocodenameone/common/codenameone_settings.properties b/scripts/hellocodenameone/common/codenameone_settings.properties
index c66a9fd4eeb..7ff4237d7b4 100644
--- a/scripts/hellocodenameone/common/codenameone_settings.properties
+++ b/scripts/hellocodenameone/common/codenameone_settings.properties
@@ -11,6 +11,13 @@ codename1.arg.ios.carplay.audio=true
codename1.arg.ios.maps.provider=apple
codename1.arg.java.version=17
codename1.cssTheme=true
+# Desktop conventions, matching what the Maven archetype and the initializr already
+# ship to every new project. Inert on the phone ports (everything they turn on is
+# gated on CN.isDesktop()), so only the Windows, Linux and macOS screenshot suites
+# see them -- which is the point: those suites are the only coverage the desktop
+# chrome has.
+codename1.arg.desktop.titleBar=native
+codename1.arg.desktop.interactiveScrollbars=true
codename1.arg.ios.documentProvider.appGroup=group.com.codenameone.examples.hellocodenameone
codename1.arg.ios.documentProvider.enabled=true
codename1.displayName=HelloCodenameOne
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractAnimationScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractAnimationScreenshotTest.java
index 71e8c21fe32..5e51da1bcd0 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractAnimationScreenshotTest.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractAnimationScreenshotTest.java
@@ -1,3 +1,25 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
package com.codenameone.examples.hellocodenameone.tests;
import com.codename1.ui.Form;
@@ -81,7 +103,17 @@ public void run() {
AnimationTime.reset();
}
markCaptureStarted();
- Cn1ssDeviceRunnerHelper.emitImage(grid, getImageName(), this::done);
+ Cn1ssDeviceRunnerHelper.emitImage(grid, getImageName(), new Runnable() {
+ @Override
+ public void run() {
+ if (blankFilmstripMessage != null) {
+ // fail() calls done(), so this finalises the test exactly once.
+ fail(blankFilmstripMessage);
+ return;
+ }
+ done();
+ }
+ });
}
/// Build the final screenshot Image. The default implementation runs the
@@ -93,6 +125,14 @@ protected Image buildScreenshot(int width, int height) {
return buildGrid(width, height);
}
+ /// Set when every frame came out a single flat colour. Recorded rather than failed on
+ /// the spot: BaseTest.fail calls done(), and finalising the test from inside the compose
+ /// would end it before the image is emitted -- the runner would then advance and the
+ /// late emit would land on whatever screen came next, which is the exact failure the
+ /// DualAppearance gate was written for. The image is worth having either way; it is the
+ /// evidence.
+ private String blankFilmstripMessage;
+
private Image buildGrid(int width, int height) {
int cellW = width / GRID_COLS;
int cellH = height / GRID_ROWS;
@@ -107,6 +147,7 @@ private Image buildGrid(int width, int height) {
cg.setColor(0x101010);
cg.fillRect(0, 0, width, height);
prepareCapture(frameWidth, frameHeight);
+ int blankFrames = 0;
try {
for (int i = 0; i < FRAME_COUNT; i++) {
double progress = (double) i / (double) (FRAME_COUNT - 1);
@@ -122,6 +163,9 @@ private Image buildGrid(int width, int height) {
} else {
scaled = frame.scaled(cellW, cellH);
}
+ if (isSingleColour(frame, frameWidth, frameHeight)) {
+ blankFrames++;
+ }
int row = i / GRID_COLS;
int col = i % GRID_COLS;
cg.drawImage(scaled, col * cellW, row * cellH);
@@ -134,10 +178,61 @@ private Image buildGrid(int width, int height) {
} finally {
finishCapture();
}
+ if (blankFrames == FRAME_COUNT) {
+ // Every frame is one flat colour, so the filmstrip has no content in it at all.
+ //
+ // This is a picture, which is the whole problem: the capture succeeds, the
+ // comparison runs, and the only thing that can tell a blank filmstrip from a
+ // real one is a person looking at it. That is how ten of these emitted six
+ // empty cells in the host Form's background colour -- a layout invalidation
+ // that these captures had been getting by accident stopped happening -- and
+ // the goldens would have recorded the blank as the new truth.
+ //
+ // The condition is deliberately all six rather than any: a single flat frame
+ // can be legitimate at one end of an animation, six cannot.
+ blankFilmstripMessage = getImageName() + " produced " + FRAME_COUNT
+ + " frames and every one of them is a single flat colour."
+ + " The animation host painted its background and none of its children;"
+ + " see BaseTest.layoutOffScreen.";
+ System.out.println("CN1SS:ERR:test=" + getImageName()
+ + " blank_filmstrip=" + blankFilmstripMessage);
+ }
drawGridLines(cg, width, height, cellW, cellH);
return composite;
}
+ /// True when every pixel of the image is the same colour.
+ ///
+ /// #### Parameters
+ ///
+ /// - `img`: the frame to inspect
+ ///
+ /// - `w`: its width
+ ///
+ /// - `h`: its height
+ ///
+ /// #### Returns
+ ///
+ /// true when the frame carries exactly one colour
+ private static boolean isSingleColour(Image img, int w, int h) {
+ if (w <= 0 || h <= 0) {
+ return true;
+ }
+ // getRGB() rather than the region overload, which is not public outside the
+ // com.codename1.ui package.
+ int[] pixels = img.getRGB();
+ if (pixels == null || pixels.length == 0) {
+ return true;
+ }
+ int first = pixels[0];
+ for (int i = 1; i < pixels.length; i++) {
+ if (pixels[i] != first) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private void drawGridLines(Graphics g, int width, int height, int cellW, int cellH) {
g.setColor(0x303030);
for (int c = 1; c < GRID_COLS; c++) {
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractComponentReplaceScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractComponentReplaceScreenshotTest.java
index 1ca1f0b49e1..6c58e7add03 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractComponentReplaceScreenshotTest.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractComponentReplaceScreenshotTest.java
@@ -1,3 +1,25 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
package com.codenameone.examples.hellocodenameone.tests;
import com.codename1.ui.Component;
@@ -113,7 +135,7 @@ protected Image buildScreenshot(int width, int height) {
}
replaceHost.add(slots[i]);
}
- replaceHost.layoutContainer();
+ layoutOffScreen(replaceHost);
int duration = getAnimationDurationMillis();
long endTime = getAnimationStartTime() + duration;
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractContainerAnimationScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractContainerAnimationScreenshotTest.java
index 1fce721f22a..8df6cdcac88 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractContainerAnimationScreenshotTest.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractContainerAnimationScreenshotTest.java
@@ -1,3 +1,25 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
package com.codenameone.examples.hellocodenameone.tests;
import com.codename1.ui.Container;
@@ -38,7 +60,7 @@ protected void prepareCapture(int frameWidth, int frameHeight) {
animatedContainer = buildContainer(frameWidth, frameHeight);
animationHost.add(BorderLayout.CENTER, animatedContainer);
- animationHost.layoutContainer();
+ layoutOffScreen(animationHost);
animation = startAnimation(animatedContainer, getAnimationDurationMillis());
}
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractStickyHeaderScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractStickyHeaderScreenshotTest.java
index 3363fb23455..5bc884661a3 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractStickyHeaderScreenshotTest.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractStickyHeaderScreenshotTest.java
@@ -1,3 +1,25 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
package com.codenameone.examples.hellocodenameone.tests;
import com.codename1.components.StickyHeaderContainer;
@@ -69,7 +91,7 @@ protected void prepareCapture(int frameWidth, int frameHeight) {
}
host.add(BorderLayout.CENTER, sticky);
- host.layoutContainer();
+ layoutOffScreen(host);
sticky.layoutContainer();
sticky.getScrollContainer().layoutContainer();
}
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractTransitionScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractTransitionScreenshotTest.java
index 99074d6b483..c77c9e0cf47 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractTransitionScreenshotTest.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/AbstractTransitionScreenshotTest.java
@@ -1,3 +1,25 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
package com.codenameone.examples.hellocodenameone.tests;
import com.codename1.ui.Button;
@@ -67,12 +89,19 @@ protected void prepareCapture(int frameWidth, int frameHeight) {
// is a no-op, leaving every transition frame empty.
sourceForm.setVisible(true);
buildSourceForm(sourceForm);
+ // Both forms are sized with the raw setters, which invalidate nothing, so neither
+ // would lay out -- see BaseTest.layoutOffScreen. This class never called
+ // layoutContainer at all and got its layout entirely by accident, which is why it
+ // was the one transition base the first sweep missed: that sweep looked for files
+ // calling BOTH setWidth and layoutContainer.
+ layoutOffScreen(sourceForm);
destForm = new Form(getDestTitle());
destForm.setWidth(frameWidth);
destForm.setHeight(frameHeight);
destForm.setVisible(true);
buildDestForm(destForm);
+ layoutOffScreen(destForm);
transition = createTransition(getAnimationDurationMillis());
transition.init(sourceForm, destForm);
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/BaseTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/BaseTest.java
index 16fcf790a9c..4c7460e117a 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/BaseTest.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/BaseTest.java
@@ -312,4 +312,40 @@ public synchronized void resetForRetry() {
captureStarted = false;
captureStage = "retry-created";
}
+
+ /**
+ *
Lays out a Form that was built off-screen and sized with the raw setters, for
+ * painting into an Image.
+ *
+ *
{@code layoutContainer()} is NOT enough and must not be used here.
+ * {@link com.codename1.ui.Container#layoutContainer()} lays out only when the container
+ * is already marked dirty -- it is {@code if (shouldLayout)} and nothing else -- and
+ * {@code setWidth}/{@code setHeight} are raw setters that mark nothing. A Form resized
+ * that way is therefore still "laid out", at whatever size it had when it was built,
+ * which is the display size.
+ *
+ *
These captures used to survive that by accident. Adding a child marks the CONTENT
+ * PANE dirty and the flag propagates to the parent, but only on a transition -- see
+ * {@code Container.setShouldLayout}, which returns early when the value is unchanged --
+ * so whether the Form itself got marked depended on something else having touched it.
+ * Attaching the Toolbar was that something else.
+ *
+ *
Desktop "native" title bar mode never attaches the Toolbar: the title goes to the
+ * OS window and the commands to the native menu bar. One invalidation disappeared with
+ * it, every off-screen host Form silently kept the display size, its children were laid
+ * out at 0x0, and ten animation filmstrips captured six empty cells in the Form's
+ * background colour. Empty cells are still a picture, so every one of those captures
+ * succeeded and the goldens would have recorded the blank.
+ *
+ *
{@code forceRevalidate()} is the public API for "things changed underneath, lay
+ * this out again", and it does not depend on anything else having marked the tree.
+ *
+ * @param host the off-screen Form or Container to lay out
+ */
+ protected static void layoutOffScreen(com.codename1.ui.Container host) {
+ if (host == null) {
+ return;
+ }
+ host.forceRevalidate();
+ }
}
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java
index 8fe9a8c2676..246abed9839 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java
@@ -405,6 +405,12 @@ private static int testTimeoutMs(BaseTest testClass) {
new PaletteOverrideThemeScreenshotTest(),
new CssGradientsScreenshotTest(),
new CssFilterBlurScreenshotTest(),
+ // The desktop surface this release turned on. Every port that stages a desktop
+ // native theme renders these, so the three of them are what stands between a
+ // theme rule going missing and nobody noticing until a user reports it.
+ new DesktopWidgetsThemeScreenshotTest(),
+ new DesktopChromeThemeScreenshotTest(),
+ new DesktopScrollbarThemeScreenshotTest(),
// External surfaces (com.codename1.surfaces): a deterministic widget descriptor
// rendered through the shared SurfaceRasterizer (the JavaSE/Windows/Linux desktop
// widget renderer) with a pinned clock, so the node-tree -> wire-JSON -> pixels
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DesktopChromeThemeScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DesktopChromeThemeScreenshotTest.java
new file mode 100644
index 00000000000..215a352531f
--- /dev/null
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DesktopChromeThemeScreenshotTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
+package com.codenameone.examples.hellocodenameone.tests;
+
+import com.codename1.ui.Button;
+import com.codename1.ui.Component;
+import com.codename1.ui.Container;
+import com.codename1.ui.Form;
+import com.codename1.ui.Label;
+import com.codename1.ui.layouts.BorderLayout;
+import com.codename1.ui.layouts.BoxLayout;
+import com.codename1.ui.layouts.FlowLayout;
+import com.codename1.ui.layouts.Layout;
+
+/**
+ *
The chrome Codename One draws for itself: the menu a right click opens, the tooltip,
+ * and a dialog's command area. These are the surfaces the three desktop themes gained
+ * rules for in this change, and until now no capture on any platform contained them.
+ *
+ *
Rendered as styled containers rather than by opening the real popups, for the reason
+ * {@code DialogThemeScreenshotTest} already gives: a modal popup parks the caller, and the
+ * caller here is the harness. {@link com.codename1.ui.ContextMenu#show} takes the modal
+ * path deliberately -- it returns the chosen command -- so driving it from a screenshot
+ * test would mean either a second thread or a modeless entry point that exists only for
+ * the test. Both buy a worse test than this one.
+ *
+ *
What that trade costs is worth stating plainly: this proves the UIIDs
+ * ({@code PopupContentPane}, {@code CommandList}, {@code Command}, {@code TooltipDialog},
+ * {@code Tooltip}, {@code DialogCommandArea}, {@code DialogButton},
+ * {@code DialogButtonDefault}) are defined and legible in both appearances. It does not
+ * prove the menu opens. {@code ContextMenuTest} does that, and the two are complementary
+ * rather than overlapping -- the bug this PR fixed in the tab strip was a theme naming a
+ * UIID the code never writes, which only a pairing like this can catch from both ends.
+ *
+ *
Hover is absent on purpose. {@code Command.hover} cannot be forced without a live
+ * pointer, and the desktop fidelity suite already scores hover against the real WinUI,
+ * AppKit and GTK references, which is a stronger check than a screenshot of a state this
+ * harness would have to fake.
+ */
+public class DesktopChromeThemeScreenshotTest extends DualAppearanceBaseTest {
+
+ @Override
+ protected String baseName() {
+ return "DesktopChromeTheme";
+ }
+
+ @Override
+ protected Layout newLayout() {
+ return BoxLayout.y();
+ }
+
+ @Override
+ protected boolean useTexturedBackdrop() {
+ // A popup pane and a tooltip are exactly the surfaces that are supposed to be
+ // opaque. Over a flat form background a missing background colour is invisible;
+ // over the texture it is the first thing a reviewer sees.
+ return true;
+ }
+
+ @Override
+ protected void populate(Form form, String suffix) {
+ form.add(new Label("Context menu"));
+ Container popup = new Container(new BorderLayout());
+ popup.setUIID("PopupContentPane");
+ Container items = new Container(BoxLayout.y());
+ items.setUIID("CommandList");
+ items.add(command("Cut", false));
+ items.add(command("Copy", false));
+ items.add(command("Paste", false));
+ // Disabled is the one command state with a rule of its own in all three themes,
+ // and a paste with nothing on the clipboard is where a real menu shows it.
+ items.add(command("Paste special", true));
+ popup.add(BorderLayout.CENTER, items);
+ // Left-aligned and narrow: a menu is as wide as its widest item, and stretching it
+ // across the form would hide a Command rule that sets its own text alignment.
+ Container popupRow = new Container(new FlowLayout(Component.LEFT));
+ popupRow.add(popup);
+ form.add(popupRow);
+
+ form.add(new Label("Tooltip"));
+ Container tooltip = new Container(new BorderLayout());
+ tooltip.setUIID("TooltipDialog");
+ Label tip = new Label("Saves the current document");
+ tip.setUIID("Tooltip");
+ tooltip.add(BorderLayout.CENTER, tip);
+ Container tooltipRow = new Container(new FlowLayout(Component.LEFT));
+ tooltipRow.add(tooltip);
+ form.add(tooltipRow);
+
+ form.add(new Label("Dialog command area"));
+ Container commands = new Container(new FlowLayout(Component.RIGHT));
+ commands.setUIID("DialogCommandArea");
+ Button cancel = new Button("Cancel");
+ cancel.setUIID("DialogButton");
+ Button ok = new Button("Save");
+ ok.setUIID("DialogButtonDefault");
+ commands.add(cancel);
+ commands.add(ok);
+ form.add(commands);
+ annotateComponent(commands, "DialogCommandArea: default action distinguished from the rest");
+ }
+
+ private static Button command(String text, boolean disabledItem) {
+ Button b = new Button(text);
+ b.setUIID("Command");
+ if (disabledItem) {
+ b.setEnabled(false);
+ }
+ return b;
+ }
+}
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DesktopModeScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DesktopModeScreenshotTest.java
index 7b2e5c566a7..7cac947a304 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DesktopModeScreenshotTest.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DesktopModeScreenshotTest.java
@@ -1,21 +1,39 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
package com.codenameone.examples.hellocodenameone.tests;
import com.codename1.ui.CN;
import com.codename1.ui.Command;
import com.codename1.ui.Container;
-import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.Label;
import com.codename1.ui.Toolbar;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.plaf.Style;
-import com.codename1.ui.plaf.UIManager;
-import java.util.Hashtable;
-
-/// Shows that the desktop integration features are inert on the phone/tablet ports but reshape the
-/// UI on the Mac native (Catalyst) desktop build, where {@code CN.isDesktop()} is true.
+/// Shows that the desktop integration features are inert on the phone/tablet ports and reshape the
+/// UI on every desktop port, where {@code CN.isDesktop()} is true.
///
/// The exact same code runs on every port:
///
@@ -23,36 +41,37 @@
/// renders an ordinary mobile screen - a CN1 {@code Toolbar} with a hamburger side-menu button
/// and the usual fading touch scrollbar, which has settled to invisible by the time the
/// screenshot is taken. So the desktop features have no visible impact there.
-/// * On the Mac native build ({@code CN.isDesktop() == true}) the test opts into desktop mode
-/// ({@code desktop.titleBar=native} plus interactive scrollbars). The screenshot then looks
-/// different: the in-app Toolbar and its hamburger are gone (the commands move to the native
-/// macOS menu bar, which isn't part of the form raster), and the scrollbar shows an
-/// always-visible, draggable thumb that the mobile ports never display.
+/// * On a desktop port ({@code CN.isDesktop() == true}) the screenshot looks different: the
+/// commands are in the platform's menu rather than a hamburger, and the scrollbar shows an
+/// always-visible, draggable thumb with a reserved gutter that the mobile ports never display.
+///
+/// The commands are not in the raster on any desktop port, and that is the point of capturing
+/// this screen on all of them. Every one has a real menu bar now -- a Swing {@code JMenuBar} on
+/// the Java SE build, an {@code NSMenu} on macOS, a Win32 {@code HMENU} on Windows and a
+/// {@code GtkMenuBar} on Linux -- so the {@code native} title-bar mode hides the in-app Toolbar
+/// and the commands move into chrome the screenshot does not cover.
///
-/// The command keyboard accelerators are exercised on the desktop too (they become Mac
-/// {@code UIKeyCommand}s), though a still screenshot can't show them.
+/// A port that had none would keep its Toolbar instead: {@code Form.isDesktopHideToolbar()}
+/// will not hide the only place the commands are drawn. That branch is asserted by
+/// {@code DesktopChromeTest} rather than by this screenshot, because no port takes it today.
///
-/// The desktop-mode toggles are global, so the test reverts them in {@link #done()} - which runs
-/// only after the screenshot has been captured - keeping every other test's baseline (on every
-/// port) untouched.
+/// The command keyboard accelerators are exercised on the desktop too, though a still screenshot
+/// cannot show them.
public class DesktopModeScreenshotTest extends BaseTest {
- private boolean desktopEnabled;
@Override
public boolean runTest() throws Exception {
- if (CN.isDesktop()) {
- desktopEnabled = true;
- // Read live by the toolbar at show time; hides the Toolbar and bridges commands to the
- // native menu bar. Inert on the mobile ports (gated on isDesktop()).
- Display.getInstance().setProperty("desktop.titleBar", "native");
- // Turn on the always-visible interactive scrollbar (the macOS-style thumb). Injected
- // directly here (rather than via the isDesktop-gated port hook) so it only happens on
- // the desktop branch; reverted in done().
- Hashtable interactive = new Hashtable();
- interactive.put("@interactiveScrollBool", "true");
- UIManager.getInstance().addThemeProps(interactive);
- }
-
+ // This test used to switch desktop mode on for itself and switch it back off in
+ // done(), because it was the only screen in the suite that ran with the desktop
+ // chrome. It is not any more: codenameone_settings.properties sets
+ // desktop.titleBar=native and desktop.interactiveScrollbars=true for the whole
+ // application, and the desktop ports install their platform's native theme, which
+ // turns interactiveScrollBool on through the theme rather than through a hook here.
+ //
+ // Keeping the local opt-in would now hide a regression rather than demonstrate a
+ // feature: whatever this test switched on for itself would look right even if the
+ // suite-wide settings had stopped working. What it demonstrates instead is the
+ // chrome the whole suite renders in.
Form form = createForm("Desktop Mode", new BorderLayout(), "DesktopMode");
Toolbar toolbar = new Toolbar();
form.setToolbar(toolbar);
@@ -115,24 +134,6 @@ protected long extraSettleBeforeCaptureMillis() {
return 700;
}
- @Override
- protected synchronized void done() {
- // Revert the global desktop-mode toggles now that the screenshot has been captured, so the
- // rest of the suite (and every other port's baseline) is unaffected by this test.
- if (desktopEnabled) {
- desktopEnabled = false;
- try {
- Display.getInstance().setProperty("desktop.titleBar", "toolbar");
- Hashtable revert = new Hashtable();
- revert.put("@interactiveScrollBool", "false");
- UIManager.getInstance().addThemeProps(revert);
- } catch (Throwable ignored) {
- // best-effort restore; never let teardown fail the test
- }
- }
- super.done();
- }
-
private static int rowColor(int i) {
int[] palette = {0x118ab2, 0x06d6a0, 0xffd166, 0xef476f, 0x8338ec, 0x073b4c};
return palette[i % palette.length];
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DesktopScrollbarThemeScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DesktopScrollbarThemeScreenshotTest.java
new file mode 100644
index 00000000000..772cc12c42a
--- /dev/null
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DesktopScrollbarThemeScreenshotTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
+package com.codenameone.examples.hellocodenameone.tests;
+
+import com.codename1.ui.Container;
+import com.codename1.ui.Form;
+import com.codename1.ui.Graphics;
+import com.codename1.ui.Label;
+import com.codename1.ui.geom.Dimension;
+import com.codename1.ui.layouts.BoxLayout;
+import com.codename1.ui.layouts.Layout;
+
+/**
+ *
The desktop scrollbar in the three states that distinguish it from the mobile one:
+ * idle, pointer over the thumb, and thumb being dragged.
+ *
+ *
This is the change's headline behaviour and it had no capture anywhere. Before this
+ * PR the three desktop themes derived their scrollbar from the mobile theme -- a thumb
+ * that fades in, fades out, and cannot be grabbed -- while the mobile themes carried the
+ * full desktop treatment. The rules are now the right way round, and a scrollbar with a
+ * reserved gutter and a hover highlight is what proves it.
+ *
+ *
The states are painted rather than performed. {@code isVScrollThumbHover} and
+ * {@code isVScrollThumbGrabbed} are what the look and feel consults, so overriding them is
+ * not a shortcut around the real code path -- it IS the real code path, entered at the
+ * only point a screenshot harness can reach without a live pointer. The same probe scores
+ * the {@code DesktopScrollBar} rows in the fidelity suite against the real WinUI, AppKit
+ * and GTK scrollbars, so the two suites are asking one question in two ways: fidelity
+ * asks whether it looks like the platform's, this asks whether it still looks like itself
+ * after a change.
+ *
+ *
The thumb proportions are fixed at the top four tenths of the track, matching the
+ * fidelity probe, because a thumb whose size follows some scrollable content would move
+ * whenever that content changed and every golden would churn for a reason unrelated to
+ * the scrollbar.
+ */
+public class DesktopScrollbarThemeScreenshotTest extends DualAppearanceBaseTest {
+
+ @Override
+ protected String baseName() {
+ return "DesktopScrollbarTheme";
+ }
+
+ @Override
+ protected Layout newLayout() {
+ return BoxLayout.y();
+ }
+
+ @Override
+ protected void populate(Form form, String suffix) {
+ form.add(new Label("Idle"));
+ form.add(track(false, false));
+ form.add(new Label("Pointer over the thumb"));
+ form.add(track(true, false));
+ form.add(new Label("Thumb dragged"));
+ com.codename1.ui.Component grabbed = track(false, true);
+ form.add(grabbed);
+ annotateComponent(grabbed, "DesktopScrollThumb.pressed inside the DesktopScroll gutter");
+ }
+
+ private static com.codename1.ui.Component track(boolean hover, boolean grabbed) {
+ return new ScrollBarProbe(hover, grabbed);
+ }
+
+ /**
+ * Paints the vertical scrollbar the look and feel would paint for a component in the
+ * given thumb state, at its themed gutter width.
+ */
+ private static final class ScrollBarProbe extends Container {
+ private final boolean hover;
+ private final boolean grabbed;
+
+ ScrollBarProbe(boolean hover, boolean grabbed) {
+ this.hover = hover;
+ this.grabbed = grabbed;
+ setUIID("Container");
+ getAllStyles().setMargin(0, 0, 0, 0);
+ getAllStyles().setPadding(0, 0, 0, 0);
+ getAllStyles().setBgTransparency(0);
+ }
+
+ @Override
+ public boolean isVScrollThumbHover() {
+ return hover;
+ }
+
+ @Override
+ public boolean isVScrollThumbGrabbed() {
+ return grabbed;
+ }
+
+ @Override
+ protected Dimension calcPreferredSize() {
+ // The gutter width is itself part of what is being captured -- it is
+ // DesktopScroll's padding plus margin -- so it has to be asked for rather than
+ // written here. The height is a plain strip tall enough to show a thumb that
+ // covers part of a track.
+ return new Dimension(
+ getUIManager().getLookAndFeel().getVerticalScrollWidth(),
+ com.codename1.ui.Display.getInstance().convertToPixels(18, false));
+ }
+
+ @Override
+ public void paint(Graphics g) {
+ getUIManager().getLookAndFeel().drawVerticalScroll(g, this, 0f, 0.4f);
+ }
+ }
+}
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DesktopWidgetsThemeScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DesktopWidgetsThemeScreenshotTest.java
new file mode 100644
index 00000000000..8d600a37ca8
--- /dev/null
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DesktopWidgetsThemeScreenshotTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
+package com.codenameone.examples.hellocodenameone.tests;
+
+import com.codename1.components.GroupBox;
+import com.codename1.components.Separator;
+import com.codename1.components.Stepper;
+import com.codename1.ui.Button;
+import com.codename1.ui.CheckBox;
+import com.codename1.ui.Form;
+import com.codename1.ui.Label;
+import com.codename1.ui.TextField;
+import com.codename1.ui.layouts.BoxLayout;
+import com.codename1.ui.layouts.Layout;
+
+/**
+ *
The desktop controls Codename One did not have until this change, plus the two
+ * framework UIIDs the desktop themes had been skipping.
+ *
+ *
{@link Separator}, {@link GroupBox} and {@link Stepper} are new classes, and
+ * {@code Link}, {@code ToolbarSearch} and {@code AccordionHeader} are styles the three
+ * desktop themes gained here. All six are theme surface: the classes carry almost no
+ * behaviour, and what can go wrong with them is that a rule is missing, names a UIID
+ * nothing writes, or reads acceptably in light and vanishes in dark.
+ *
+ *
That is a thing a screenshot can prove and a unit test cannot, which is why this
+ * exists alongside {@code DesktopComponentsTest} rather than instead of it: that one
+ * asserts the clamping and the content-pane routing, this one asserts they can be seen.
+ * Both are needed -- the dark-mode misses this catches are invisible to an assertion
+ * about a value, and the tab-strip bug this PR fixed (two UIIDs nobody writes) was
+ * exactly that shape.
+ *
+ *
The stepper appears twice on purpose. A stepper sitting at its minimum must show a
+ * disabled decrement button, which is the one piece of behaviour here with a visual
+ * consequence, and rendering only the middle of the range would never show it.
+ */
+public class DesktopWidgetsThemeScreenshotTest extends DualAppearanceBaseTest {
+
+ @Override
+ protected String baseName() {
+ return "DesktopWidgetsTheme";
+ }
+
+ @Override
+ protected Layout newLayout() {
+ return BoxLayout.y();
+ }
+
+ @Override
+ protected void populate(Form form, String suffix) {
+ GroupBox group = new GroupBox("Appearance");
+ group.add(new CheckBox("Follow the system theme"));
+ group.add(new Label("Grouped controls sit inside the content pane"));
+ form.add(group);
+ annotateComponent(group, "GroupBox: titled frame, caption above a bordered content pane");
+
+ form.add(new Separator());
+
+ Stepper midRange = new Stepper(3, 1, 10);
+ form.add(new Label("Stepper, mid range"));
+ form.add(midRange);
+ annotateComponent(midRange, "Stepper: field flanked by increment / decrement");
+
+ form.add(new Label("Stepper, clamped at the minimum"));
+ // Both buttons are styled the same until one of them cannot act. At the floor the
+ // decrement button is disabled, so this row is the only place StepperButton.disabled
+ // is rendered -- and a theme that forgot that rule looks identical to one that has it
+ // in every other capture.
+ form.add(new Stepper(1, 1, 10));
+
+ form.add(new Separator());
+
+ Button link = new Button("A hyperlink button");
+ link.setUIID("Link");
+ form.add(link);
+
+ TextField search = new TextField("", "Search", 20, TextField.ANY);
+ search.setUIID("ToolbarSearch");
+ form.add(search);
+
+ Label accordion = new Label("Accordion header");
+ accordion.setUIID("AccordionHeader");
+ form.add(accordion);
+ }
+}
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/PullToRefreshSpinnerScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/PullToRefreshSpinnerScreenshotTest.java
index 0b39362472b..332fcbfb872 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/PullToRefreshSpinnerScreenshotTest.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/PullToRefreshSpinnerScreenshotTest.java
@@ -1,3 +1,25 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
package com.codenameone.examples.hellocodenameone.tests;
import com.codename1.ui.Container;
@@ -60,7 +82,7 @@ public void run() {
scrollHost.add(new Label("Row " + i));
}
host.add(BorderLayout.CENTER, scrollHost);
- host.layoutContainer();
+ layoutOffScreen(host);
// Pin the container in the "task running" state so the painter
// draws the continuous-spin arc.
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/SmoothScrollScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/SmoothScrollScreenshotTest.java
index 87d245c4b17..a197759844f 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/SmoothScrollScreenshotTest.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/SmoothScrollScreenshotTest.java
@@ -1,3 +1,25 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
package com.codenameone.examples.hellocodenameone.tests;
import com.codename1.ui.Container;
@@ -61,7 +83,7 @@ protected void prepareCapture(int frameWidth, int frameHeight) {
scrollContainer.add(tile);
}
scrollHost.add(BorderLayout.CENTER, scrollContainer);
- scrollHost.layoutContainer();
+ layoutOffScreen(scrollHost);
int contentHeight = scrollContainer.getScrollDimension().getHeight();
int maxScroll = Math.max(0, contentHeight - scrollContainer.getHeight());
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/StatusBarTapDiagnosticScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/StatusBarTapDiagnosticScreenshotTest.java
index dec1b85c039..1712665cc7b 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/StatusBarTapDiagnosticScreenshotTest.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/StatusBarTapDiagnosticScreenshotTest.java
@@ -1,3 +1,25 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
package com.codenameone.examples.hellocodenameone.tests;
import com.codename1.system.NativeLookup;
@@ -83,7 +105,7 @@ public boolean runTest() throws Exception {
scrollContainer.add(tile);
}
form.add(BorderLayout.CENTER, scrollContainer);
- form.layoutContainer();
+ layoutOffScreen(form);
int contentHeight = scrollContainer.getScrollDimension().getHeight();
int maxScroll = Math.max(0, contentHeight - scrollContainer.getHeight());
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/TabsAnimatedIndicatorScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/TabsAnimatedIndicatorScreenshotTest.java
index b9cd83af3f0..cc5ce38391a 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/TabsAnimatedIndicatorScreenshotTest.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/TabsAnimatedIndicatorScreenshotTest.java
@@ -1,3 +1,25 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
package com.codenameone.examples.hellocodenameone.tests;
import com.codename1.ui.Button;
@@ -38,7 +60,7 @@ protected void prepareCapture(int frameWidth, int frameHeight) {
tabs.addTab("Search", new Button("Search content"));
tabs.addTab("Profile", new Button("Profile content"));
host.add(BorderLayout.CENTER, tabs);
- host.layoutContainer();
+ layoutOffScreen(host);
// Kick off the indicator slide -- the Motion this starts reads
// AnimationTime which the harness advances per frame.
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/TabsLiquidGlassAnimationScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/TabsLiquidGlassAnimationScreenshotTest.java
index 28414fce920..305d4a7dd4c 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/TabsLiquidGlassAnimationScreenshotTest.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/TabsLiquidGlassAnimationScreenshotTest.java
@@ -164,7 +164,7 @@ private void buildTile(boolean dark) {
// width, which lands the themed pill at the native y=0 position.
tile.add(BorderLayout.NORTH, tabs);
layoutHost.add(BorderLayout.CENTER, tile);
- layoutHost.layoutContainer();
+ layoutOffScreen(layoutHost);
tile.layoutContainer();
tabs.layoutContainer();
tabs.getTabsContainer().layoutContainer();
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/TensileBounceScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/TensileBounceScreenshotTest.java
index fd0e2531543..36ae4255482 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/TensileBounceScreenshotTest.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/TensileBounceScreenshotTest.java
@@ -1,3 +1,25 @@
+/*
+ * Copyright (c) 2026, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
+ * need additional information or have any questions.
+ */
package com.codenameone.examples.hellocodenameone.tests;
import com.codename1.ui.Container;
@@ -69,7 +91,7 @@ protected void prepareCapture(int frameWidth, int frameHeight) {
scrollContainer.add(tile);
}
scrollHost.add(BorderLayout.CENTER, scrollContainer);
- scrollHost.layoutContainer();
+ layoutOffScreen(scrollHost);
// Pull a third of the visible viewport - matching what a user can drag
// past the top edge before lifting off in iOS. Anything smaller (<10%)
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/WindowDialogTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/WindowDialogTest.java
index b593b6d7f85..3b6c1c530d2 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/WindowDialogTest.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/WindowDialogTest.java
@@ -73,6 +73,16 @@ protected void initComponent() {
d.setLayout(new BorderLayout());
d.add(BorderLayout.CENTER, new Label("Delete the document?"));
d.setTopLevelHost(top);
+ // Pinned to the HOSTED path, which is the path this test documents: the
+ // dialog on the window's own surface with the content dimmed behind it. The
+ // desktop native themes set defaultNativeWindowModeBool, so without this the
+ // dialog opens as a separate operating system window, leaves the raster
+ // entirely, and the golden becomes an empty host window -- correct behaviour
+ // for that mode, and no longer a test of the layered pane and the scrim.
+ //
+ // Measured on the reseed captures: Window-Dialog-900x700 went from a dialog
+ // over dimmed content to 99.7% a single colour.
+ d.setNativeWindowMode(false);
// Gets the dialog a backdrop without parking anything: the scrim is
// installed for either modality or outside-press dismissal, so asking
// for the second one renders the tint that a modal dialog would show.
diff --git a/scripts/ios/screenshots-metal/CoverHorizontalTransitionTest.png b/scripts/ios/screenshots-metal/CoverHorizontalTransitionTest.png
index 122851a0080..656eb56aa83 100644
Binary files a/scripts/ios/screenshots-metal/CoverHorizontalTransitionTest.png and b/scripts/ios/screenshots-metal/CoverHorizontalTransitionTest.png differ
diff --git a/scripts/ios/screenshots-metal/DesktopChromeTheme_dark.png b/scripts/ios/screenshots-metal/DesktopChromeTheme_dark.png
new file mode 100644
index 00000000000..f388e82e3cb
Binary files /dev/null and b/scripts/ios/screenshots-metal/DesktopChromeTheme_dark.png differ
diff --git a/scripts/ios/screenshots-metal/DesktopChromeTheme_light.png b/scripts/ios/screenshots-metal/DesktopChromeTheme_light.png
new file mode 100644
index 00000000000..7ddea2c7408
Binary files /dev/null and b/scripts/ios/screenshots-metal/DesktopChromeTheme_light.png differ
diff --git a/scripts/ios/screenshots-metal/DesktopScrollbarTheme_dark.png b/scripts/ios/screenshots-metal/DesktopScrollbarTheme_dark.png
new file mode 100644
index 00000000000..032861cb06d
Binary files /dev/null and b/scripts/ios/screenshots-metal/DesktopScrollbarTheme_dark.png differ
diff --git a/scripts/ios/screenshots-metal/DesktopScrollbarTheme_light.png b/scripts/ios/screenshots-metal/DesktopScrollbarTheme_light.png
new file mode 100644
index 00000000000..144a73e0fa9
Binary files /dev/null and b/scripts/ios/screenshots-metal/DesktopScrollbarTheme_light.png differ
diff --git a/scripts/ios/screenshots-metal/DesktopWidgetsTheme_dark.png b/scripts/ios/screenshots-metal/DesktopWidgetsTheme_dark.png
new file mode 100644
index 00000000000..27953a9c3c8
Binary files /dev/null and b/scripts/ios/screenshots-metal/DesktopWidgetsTheme_dark.png differ
diff --git a/scripts/ios/screenshots-metal/DesktopWidgetsTheme_light.png b/scripts/ios/screenshots-metal/DesktopWidgetsTheme_light.png
new file mode 100644
index 00000000000..89f7cb56413
Binary files /dev/null and b/scripts/ios/screenshots-metal/DesktopWidgetsTheme_light.png differ
diff --git a/scripts/ios/screenshots-metal/FadeTransitionTest.png b/scripts/ios/screenshots-metal/FadeTransitionTest.png
index 9cf460fadb8..69d5e98dd27 100644
Binary files a/scripts/ios/screenshots-metal/FadeTransitionTest.png and b/scripts/ios/screenshots-metal/FadeTransitionTest.png differ
diff --git a/scripts/ios/screenshots-metal/FlipTransitionTest.png b/scripts/ios/screenshots-metal/FlipTransitionTest.png
index d81286badca..e82191411a8 100644
Binary files a/scripts/ios/screenshots-metal/FlipTransitionTest.png and b/scripts/ios/screenshots-metal/FlipTransitionTest.png differ
diff --git a/scripts/ios/screenshots-metal/MorphElementMorphScreenshotTest.png b/scripts/ios/screenshots-metal/MorphElementMorphScreenshotTest.png
index 5d7a2cab25c..871b31dbbab 100644
Binary files a/scripts/ios/screenshots-metal/MorphElementMorphScreenshotTest.png and b/scripts/ios/screenshots-metal/MorphElementMorphScreenshotTest.png differ
diff --git a/scripts/ios/screenshots-metal/MorphTransitionScrubScreenshotTest.png b/scripts/ios/screenshots-metal/MorphTransitionScrubScreenshotTest.png
index 34061c17016..1286eefb0af 100644
Binary files a/scripts/ios/screenshots-metal/MorphTransitionScrubScreenshotTest.png and b/scripts/ios/screenshots-metal/MorphTransitionScrubScreenshotTest.png differ
diff --git a/scripts/ios/screenshots-metal/MorphTransitionSnapshotTest.png b/scripts/ios/screenshots-metal/MorphTransitionSnapshotTest.png
index 6734a06c81f..d97b4c8edad 100644
Binary files a/scripts/ios/screenshots-metal/MorphTransitionSnapshotTest.png and b/scripts/ios/screenshots-metal/MorphTransitionSnapshotTest.png differ
diff --git a/scripts/ios/screenshots-metal/MorphTransitionTest.png b/scripts/ios/screenshots-metal/MorphTransitionTest.png
index 1dbd420e467..2fe6bdfd205 100644
Binary files a/scripts/ios/screenshots-metal/MorphTransitionTest.png and b/scripts/ios/screenshots-metal/MorphTransitionTest.png differ
diff --git a/scripts/ios/screenshots-metal/SlideFadeTitleTransitionTest.png b/scripts/ios/screenshots-metal/SlideFadeTitleTransitionTest.png
index 2cb06fc946d..761f8877759 100644
Binary files a/scripts/ios/screenshots-metal/SlideFadeTitleTransitionTest.png and b/scripts/ios/screenshots-metal/SlideFadeTitleTransitionTest.png differ
diff --git a/scripts/ios/screenshots-metal/SlideHorizontalBackTransitionTest.png b/scripts/ios/screenshots-metal/SlideHorizontalBackTransitionTest.png
index 42604f35d15..8586b3c4bda 100644
Binary files a/scripts/ios/screenshots-metal/SlideHorizontalBackTransitionTest.png and b/scripts/ios/screenshots-metal/SlideHorizontalBackTransitionTest.png differ
diff --git a/scripts/ios/screenshots-metal/SlideHorizontalTransitionTest.png b/scripts/ios/screenshots-metal/SlideHorizontalTransitionTest.png
index 92d968d099e..a6c9c3f94c2 100644
Binary files a/scripts/ios/screenshots-metal/SlideHorizontalTransitionTest.png and b/scripts/ios/screenshots-metal/SlideHorizontalTransitionTest.png differ
diff --git a/scripts/ios/screenshots-metal/SlideVerticalTransitionTest.png b/scripts/ios/screenshots-metal/SlideVerticalTransitionTest.png
index dffbce9f9aa..a49cc725333 100644
Binary files a/scripts/ios/screenshots-metal/SlideVerticalTransitionTest.png and b/scripts/ios/screenshots-metal/SlideVerticalTransitionTest.png differ
diff --git a/scripts/ios/screenshots-metal/UncoverHorizontalTransitionTest.png b/scripts/ios/screenshots-metal/UncoverHorizontalTransitionTest.png
index 7ec7a5cf38b..4cd00865b7b 100644
Binary files a/scripts/ios/screenshots-metal/UncoverHorizontalTransitionTest.png and b/scripts/ios/screenshots-metal/UncoverHorizontalTransitionTest.png differ
diff --git a/scripts/ios/screenshots-tv/CoverHorizontalTransitionTest.png b/scripts/ios/screenshots-tv/CoverHorizontalTransitionTest.png
index 4db92e40ba4..be64612250c 100644
Binary files a/scripts/ios/screenshots-tv/CoverHorizontalTransitionTest.png and b/scripts/ios/screenshots-tv/CoverHorizontalTransitionTest.png differ
diff --git a/scripts/ios/screenshots-tv/DesktopChromeTheme_dark.png b/scripts/ios/screenshots-tv/DesktopChromeTheme_dark.png
new file mode 100644
index 00000000000..df1998168b9
Binary files /dev/null and b/scripts/ios/screenshots-tv/DesktopChromeTheme_dark.png differ
diff --git a/scripts/ios/screenshots-tv/DesktopChromeTheme_light.png b/scripts/ios/screenshots-tv/DesktopChromeTheme_light.png
new file mode 100644
index 00000000000..12374c1e8eb
Binary files /dev/null and b/scripts/ios/screenshots-tv/DesktopChromeTheme_light.png differ
diff --git a/scripts/ios/screenshots-tv/DesktopScrollbarTheme_dark.png b/scripts/ios/screenshots-tv/DesktopScrollbarTheme_dark.png
new file mode 100644
index 00000000000..b4e6b9b9778
Binary files /dev/null and b/scripts/ios/screenshots-tv/DesktopScrollbarTheme_dark.png differ
diff --git a/scripts/ios/screenshots-tv/DesktopScrollbarTheme_light.png b/scripts/ios/screenshots-tv/DesktopScrollbarTheme_light.png
new file mode 100644
index 00000000000..48cf09c5fd1
Binary files /dev/null and b/scripts/ios/screenshots-tv/DesktopScrollbarTheme_light.png differ
diff --git a/scripts/ios/screenshots-tv/DesktopWidgetsTheme_dark.png b/scripts/ios/screenshots-tv/DesktopWidgetsTheme_dark.png
new file mode 100644
index 00000000000..6722eb00881
Binary files /dev/null and b/scripts/ios/screenshots-tv/DesktopWidgetsTheme_dark.png differ
diff --git a/scripts/ios/screenshots-tv/DesktopWidgetsTheme_light.png b/scripts/ios/screenshots-tv/DesktopWidgetsTheme_light.png
new file mode 100644
index 00000000000..b82849bc13c
Binary files /dev/null and b/scripts/ios/screenshots-tv/DesktopWidgetsTheme_light.png differ
diff --git a/scripts/ios/screenshots-tv/FadeTransitionTest.png b/scripts/ios/screenshots-tv/FadeTransitionTest.png
index aba4a42f28f..1ff4a4f9f8c 100644
Binary files a/scripts/ios/screenshots-tv/FadeTransitionTest.png and b/scripts/ios/screenshots-tv/FadeTransitionTest.png differ
diff --git a/scripts/ios/screenshots-tv/FlipTransitionTest.png b/scripts/ios/screenshots-tv/FlipTransitionTest.png
index d54e03e73b1..b29ec1ce3bd 100644
Binary files a/scripts/ios/screenshots-tv/FlipTransitionTest.png and b/scripts/ios/screenshots-tv/FlipTransitionTest.png differ
diff --git a/scripts/ios/screenshots-tv/MorphElementMorphScreenshotTest.png b/scripts/ios/screenshots-tv/MorphElementMorphScreenshotTest.png
index bacb9899df6..b08be87a802 100644
Binary files a/scripts/ios/screenshots-tv/MorphElementMorphScreenshotTest.png and b/scripts/ios/screenshots-tv/MorphElementMorphScreenshotTest.png differ
diff --git a/scripts/ios/screenshots-tv/MorphTransitionScrubScreenshotTest.png b/scripts/ios/screenshots-tv/MorphTransitionScrubScreenshotTest.png
index e3480fe8cee..0a89318b763 100644
Binary files a/scripts/ios/screenshots-tv/MorphTransitionScrubScreenshotTest.png and b/scripts/ios/screenshots-tv/MorphTransitionScrubScreenshotTest.png differ
diff --git a/scripts/ios/screenshots-tv/MorphTransitionSnapshotTest.png b/scripts/ios/screenshots-tv/MorphTransitionSnapshotTest.png
index 6dee9b05697..c9776bc1179 100644
Binary files a/scripts/ios/screenshots-tv/MorphTransitionSnapshotTest.png and b/scripts/ios/screenshots-tv/MorphTransitionSnapshotTest.png differ
diff --git a/scripts/ios/screenshots-tv/MorphTransitionTest.png b/scripts/ios/screenshots-tv/MorphTransitionTest.png
index 57a9fe33170..e77553f86d0 100644
Binary files a/scripts/ios/screenshots-tv/MorphTransitionTest.png and b/scripts/ios/screenshots-tv/MorphTransitionTest.png differ
diff --git a/scripts/ios/screenshots-tv/SlideFadeTitleTransitionTest.png b/scripts/ios/screenshots-tv/SlideFadeTitleTransitionTest.png
index e0f3b7721df..27edae85346 100644
Binary files a/scripts/ios/screenshots-tv/SlideFadeTitleTransitionTest.png and b/scripts/ios/screenshots-tv/SlideFadeTitleTransitionTest.png differ
diff --git a/scripts/ios/screenshots-tv/SlideHorizontalBackTransitionTest.png b/scripts/ios/screenshots-tv/SlideHorizontalBackTransitionTest.png
index d9f49e15278..68abeeb430e 100644
Binary files a/scripts/ios/screenshots-tv/SlideHorizontalBackTransitionTest.png and b/scripts/ios/screenshots-tv/SlideHorizontalBackTransitionTest.png differ
diff --git a/scripts/ios/screenshots-tv/SlideHorizontalTransitionTest.png b/scripts/ios/screenshots-tv/SlideHorizontalTransitionTest.png
index 469b4c62ef8..a04aa2058ca 100644
Binary files a/scripts/ios/screenshots-tv/SlideHorizontalTransitionTest.png and b/scripts/ios/screenshots-tv/SlideHorizontalTransitionTest.png differ
diff --git a/scripts/ios/screenshots-tv/SlideVerticalTransitionTest.png b/scripts/ios/screenshots-tv/SlideVerticalTransitionTest.png
index 8d323b17ae1..eb1ff961afe 100644
Binary files a/scripts/ios/screenshots-tv/SlideVerticalTransitionTest.png and b/scripts/ios/screenshots-tv/SlideVerticalTransitionTest.png differ
diff --git a/scripts/ios/screenshots-tv/UncoverHorizontalTransitionTest.png b/scripts/ios/screenshots-tv/UncoverHorizontalTransitionTest.png
index 21024d421f6..df2de00e5c2 100644
Binary files a/scripts/ios/screenshots-tv/UncoverHorizontalTransitionTest.png and b/scripts/ios/screenshots-tv/UncoverHorizontalTransitionTest.png differ
diff --git a/scripts/ios/screenshots-watch/DesktopChromeTheme_dark.png b/scripts/ios/screenshots-watch/DesktopChromeTheme_dark.png
new file mode 100644
index 00000000000..f40c7aa782c
Binary files /dev/null and b/scripts/ios/screenshots-watch/DesktopChromeTheme_dark.png differ
diff --git a/scripts/ios/screenshots-watch/DesktopChromeTheme_light.png b/scripts/ios/screenshots-watch/DesktopChromeTheme_light.png
new file mode 100644
index 00000000000..5d7ac2bb678
Binary files /dev/null and b/scripts/ios/screenshots-watch/DesktopChromeTheme_light.png differ
diff --git a/scripts/ios/screenshots-watch/DesktopScrollbarTheme_dark.png b/scripts/ios/screenshots-watch/DesktopScrollbarTheme_dark.png
new file mode 100644
index 00000000000..c803b5cc3b7
Binary files /dev/null and b/scripts/ios/screenshots-watch/DesktopScrollbarTheme_dark.png differ
diff --git a/scripts/ios/screenshots-watch/DesktopScrollbarTheme_light.png b/scripts/ios/screenshots-watch/DesktopScrollbarTheme_light.png
new file mode 100644
index 00000000000..d3d1cad37d8
Binary files /dev/null and b/scripts/ios/screenshots-watch/DesktopScrollbarTheme_light.png differ
diff --git a/scripts/ios/screenshots-watch/DesktopWidgetsTheme_dark.png b/scripts/ios/screenshots-watch/DesktopWidgetsTheme_dark.png
new file mode 100644
index 00000000000..bc55da6d6f1
Binary files /dev/null and b/scripts/ios/screenshots-watch/DesktopWidgetsTheme_dark.png differ
diff --git a/scripts/ios/screenshots-watch/DesktopWidgetsTheme_light.png b/scripts/ios/screenshots-watch/DesktopWidgetsTheme_light.png
new file mode 100644
index 00000000000..a3de147272b
Binary files /dev/null and b/scripts/ios/screenshots-watch/DesktopWidgetsTheme_light.png differ
diff --git a/scripts/ios/screenshots/CoverHorizontalTransitionTest.png b/scripts/ios/screenshots/CoverHorizontalTransitionTest.png
index 30b15ddf91d..b3103a8eb92 100644
Binary files a/scripts/ios/screenshots/CoverHorizontalTransitionTest.png and b/scripts/ios/screenshots/CoverHorizontalTransitionTest.png differ
diff --git a/scripts/ios/screenshots/DesktopChromeTheme_dark.png b/scripts/ios/screenshots/DesktopChromeTheme_dark.png
new file mode 100644
index 00000000000..69e0e1c3270
Binary files /dev/null and b/scripts/ios/screenshots/DesktopChromeTheme_dark.png differ
diff --git a/scripts/ios/screenshots/DesktopChromeTheme_light.png b/scripts/ios/screenshots/DesktopChromeTheme_light.png
new file mode 100644
index 00000000000..38eea792873
Binary files /dev/null and b/scripts/ios/screenshots/DesktopChromeTheme_light.png differ
diff --git a/scripts/ios/screenshots/DesktopScrollbarTheme_dark.png b/scripts/ios/screenshots/DesktopScrollbarTheme_dark.png
new file mode 100644
index 00000000000..1d6f0cc0c47
Binary files /dev/null and b/scripts/ios/screenshots/DesktopScrollbarTheme_dark.png differ
diff --git a/scripts/ios/screenshots/DesktopScrollbarTheme_light.png b/scripts/ios/screenshots/DesktopScrollbarTheme_light.png
new file mode 100644
index 00000000000..aa5cd84565a
Binary files /dev/null and b/scripts/ios/screenshots/DesktopScrollbarTheme_light.png differ
diff --git a/scripts/ios/screenshots/DesktopWidgetsTheme_dark.png b/scripts/ios/screenshots/DesktopWidgetsTheme_dark.png
new file mode 100644
index 00000000000..944e120bf90
Binary files /dev/null and b/scripts/ios/screenshots/DesktopWidgetsTheme_dark.png differ
diff --git a/scripts/ios/screenshots/DesktopWidgetsTheme_light.png b/scripts/ios/screenshots/DesktopWidgetsTheme_light.png
new file mode 100644
index 00000000000..30928650168
Binary files /dev/null and b/scripts/ios/screenshots/DesktopWidgetsTheme_light.png differ
diff --git a/scripts/ios/screenshots/FadeTransitionTest.png b/scripts/ios/screenshots/FadeTransitionTest.png
index ec9d29e63ec..ffbd8e96c5e 100644
Binary files a/scripts/ios/screenshots/FadeTransitionTest.png and b/scripts/ios/screenshots/FadeTransitionTest.png differ
diff --git a/scripts/ios/screenshots/FlipTransitionTest.png b/scripts/ios/screenshots/FlipTransitionTest.png
index e20bc339391..af27fa3d706 100644
Binary files a/scripts/ios/screenshots/FlipTransitionTest.png and b/scripts/ios/screenshots/FlipTransitionTest.png differ
diff --git a/scripts/ios/screenshots/MorphElementMorphScreenshotTest.png b/scripts/ios/screenshots/MorphElementMorphScreenshotTest.png
index 2b5cb08619b..4f97444e554 100644
Binary files a/scripts/ios/screenshots/MorphElementMorphScreenshotTest.png and b/scripts/ios/screenshots/MorphElementMorphScreenshotTest.png differ
diff --git a/scripts/ios/screenshots/MorphTransitionScrubScreenshotTest.png b/scripts/ios/screenshots/MorphTransitionScrubScreenshotTest.png
index 81b3492d6e1..be92f0a49e1 100644
Binary files a/scripts/ios/screenshots/MorphTransitionScrubScreenshotTest.png and b/scripts/ios/screenshots/MorphTransitionScrubScreenshotTest.png differ
diff --git a/scripts/ios/screenshots/MorphTransitionSnapshotTest.png b/scripts/ios/screenshots/MorphTransitionSnapshotTest.png
index 49044a2f14f..2ffcc57a395 100644
Binary files a/scripts/ios/screenshots/MorphTransitionSnapshotTest.png and b/scripts/ios/screenshots/MorphTransitionSnapshotTest.png differ
diff --git a/scripts/ios/screenshots/MorphTransitionTest.png b/scripts/ios/screenshots/MorphTransitionTest.png
index 0711e129a33..8a19dcd5a53 100644
Binary files a/scripts/ios/screenshots/MorphTransitionTest.png and b/scripts/ios/screenshots/MorphTransitionTest.png differ
diff --git a/scripts/ios/screenshots/SlideFadeTitleTransitionTest.png b/scripts/ios/screenshots/SlideFadeTitleTransitionTest.png
index 6c0a99cfeba..ebcc2d9d382 100644
Binary files a/scripts/ios/screenshots/SlideFadeTitleTransitionTest.png and b/scripts/ios/screenshots/SlideFadeTitleTransitionTest.png differ
diff --git a/scripts/ios/screenshots/SlideHorizontalBackTransitionTest.png b/scripts/ios/screenshots/SlideHorizontalBackTransitionTest.png
index 6f17a2fefb2..9b3a1170c7c 100644
Binary files a/scripts/ios/screenshots/SlideHorizontalBackTransitionTest.png and b/scripts/ios/screenshots/SlideHorizontalBackTransitionTest.png differ
diff --git a/scripts/ios/screenshots/SlideHorizontalTransitionTest.png b/scripts/ios/screenshots/SlideHorizontalTransitionTest.png
index 31d40bc1646..a3e4360fe16 100644
Binary files a/scripts/ios/screenshots/SlideHorizontalTransitionTest.png and b/scripts/ios/screenshots/SlideHorizontalTransitionTest.png differ
diff --git a/scripts/ios/screenshots/SlideVerticalTransitionTest.png b/scripts/ios/screenshots/SlideVerticalTransitionTest.png
index 156e73fd970..f663f6105d9 100644
Binary files a/scripts/ios/screenshots/SlideVerticalTransitionTest.png and b/scripts/ios/screenshots/SlideVerticalTransitionTest.png differ
diff --git a/scripts/ios/screenshots/UncoverHorizontalTransitionTest.png b/scripts/ios/screenshots/UncoverHorizontalTransitionTest.png
index 5ac6a01e222..8236894c5b4 100644
Binary files a/scripts/ios/screenshots/UncoverHorizontalTransitionTest.png and b/scripts/ios/screenshots/UncoverHorizontalTransitionTest.png differ
diff --git a/scripts/javascript/screenshots/DesktopChromeTheme_dark.png b/scripts/javascript/screenshots/DesktopChromeTheme_dark.png
new file mode 100644
index 00000000000..03929050531
Binary files /dev/null and b/scripts/javascript/screenshots/DesktopChromeTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/DesktopChromeTheme_ios_dark.png b/scripts/javascript/screenshots/DesktopChromeTheme_ios_dark.png
new file mode 100644
index 00000000000..7335e04c754
Binary files /dev/null and b/scripts/javascript/screenshots/DesktopChromeTheme_ios_dark.png differ
diff --git a/scripts/javascript/screenshots/DesktopChromeTheme_ios_light.png b/scripts/javascript/screenshots/DesktopChromeTheme_ios_light.png
new file mode 100644
index 00000000000..0e03522af75
Binary files /dev/null and b/scripts/javascript/screenshots/DesktopChromeTheme_ios_light.png differ
diff --git a/scripts/javascript/screenshots/DesktopChromeTheme_light.png b/scripts/javascript/screenshots/DesktopChromeTheme_light.png
new file mode 100644
index 00000000000..e32ee367ba9
Binary files /dev/null and b/scripts/javascript/screenshots/DesktopChromeTheme_light.png differ
diff --git a/scripts/javascript/screenshots/DesktopMode.png b/scripts/javascript/screenshots/DesktopMode.png
index 26881db3cbf..f051190e655 100644
Binary files a/scripts/javascript/screenshots/DesktopMode.png and b/scripts/javascript/screenshots/DesktopMode.png differ
diff --git a/scripts/javascript/screenshots/DesktopScrollbarTheme_dark.png b/scripts/javascript/screenshots/DesktopScrollbarTheme_dark.png
new file mode 100644
index 00000000000..27021cacdb2
Binary files /dev/null and b/scripts/javascript/screenshots/DesktopScrollbarTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/DesktopScrollbarTheme_ios_dark.png b/scripts/javascript/screenshots/DesktopScrollbarTheme_ios_dark.png
new file mode 100644
index 00000000000..45777b67983
Binary files /dev/null and b/scripts/javascript/screenshots/DesktopScrollbarTheme_ios_dark.png differ
diff --git a/scripts/javascript/screenshots/DesktopScrollbarTheme_ios_light.png b/scripts/javascript/screenshots/DesktopScrollbarTheme_ios_light.png
new file mode 100644
index 00000000000..010021ad2e6
Binary files /dev/null and b/scripts/javascript/screenshots/DesktopScrollbarTheme_ios_light.png differ
diff --git a/scripts/javascript/screenshots/DesktopScrollbarTheme_light.png b/scripts/javascript/screenshots/DesktopScrollbarTheme_light.png
new file mode 100644
index 00000000000..da2579e0596
Binary files /dev/null and b/scripts/javascript/screenshots/DesktopScrollbarTheme_light.png differ
diff --git a/scripts/javascript/screenshots/DesktopWidgetsTheme_dark.png b/scripts/javascript/screenshots/DesktopWidgetsTheme_dark.png
new file mode 100644
index 00000000000..66148bc2b07
Binary files /dev/null and b/scripts/javascript/screenshots/DesktopWidgetsTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/DesktopWidgetsTheme_ios_dark.png b/scripts/javascript/screenshots/DesktopWidgetsTheme_ios_dark.png
new file mode 100644
index 00000000000..c5735f0e9d1
Binary files /dev/null and b/scripts/javascript/screenshots/DesktopWidgetsTheme_ios_dark.png differ
diff --git a/scripts/javascript/screenshots/DesktopWidgetsTheme_ios_light.png b/scripts/javascript/screenshots/DesktopWidgetsTheme_ios_light.png
new file mode 100644
index 00000000000..9f3a4aa555b
Binary files /dev/null and b/scripts/javascript/screenshots/DesktopWidgetsTheme_ios_light.png differ
diff --git a/scripts/javascript/screenshots/DesktopWidgetsTheme_light.png b/scripts/javascript/screenshots/DesktopWidgetsTheme_light.png
new file mode 100644
index 00000000000..2b015efddcf
Binary files /dev/null and b/scripts/javascript/screenshots/DesktopWidgetsTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/AdsScreen.png b/scripts/linux/screenshots-arm/AdsScreen.png
index 1695a15ec61..b851e69ab61 100644
Binary files a/scripts/linux/screenshots-arm/AdsScreen.png and b/scripts/linux/screenshots-arm/AdsScreen.png differ
diff --git a/scripts/linux/screenshots-arm/AnimateHierarchyScreenshotTest.png b/scripts/linux/screenshots-arm/AnimateHierarchyScreenshotTest.png
index 8a9ce03c8c8..5796be4c0a7 100644
Binary files a/scripts/linux/screenshots-arm/AnimateHierarchyScreenshotTest.png and b/scripts/linux/screenshots-arm/AnimateHierarchyScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/AnimateLayoutScreenshotTest.png b/scripts/linux/screenshots-arm/AnimateLayoutScreenshotTest.png
index 464b0d55c45..aa3d28a3622 100644
Binary files a/scripts/linux/screenshots-arm/AnimateLayoutScreenshotTest.png and b/scripts/linux/screenshots-arm/AnimateLayoutScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/AnimateUnlayoutScreenshotTest.png b/scripts/linux/screenshots-arm/AnimateUnlayoutScreenshotTest.png
index c66faed6f9f..d923b6e9c7f 100644
Binary files a/scripts/linux/screenshots-arm/AnimateUnlayoutScreenshotTest.png and b/scripts/linux/screenshots-arm/AnimateUnlayoutScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/AppReviewDialog.png b/scripts/linux/screenshots-arm/AppReviewDialog.png
index 76dfbc00041..6c0f5f331e3 100644
Binary files a/scripts/linux/screenshots-arm/AppReviewDialog.png and b/scripts/linux/screenshots-arm/AppReviewDialog.png differ
diff --git a/scripts/linux/screenshots-arm/BrowserComponent.png b/scripts/linux/screenshots-arm/BrowserComponent.png
index ae2ed972338..a9bd7e8a95a 100644
Binary files a/scripts/linux/screenshots-arm/BrowserComponent.png and b/scripts/linux/screenshots-arm/BrowserComponent.png differ
diff --git a/scripts/linux/screenshots-arm/ButtonTheme_dark.png b/scripts/linux/screenshots-arm/ButtonTheme_dark.png
index bb390f7c5e7..51b044f9cc1 100644
Binary files a/scripts/linux/screenshots-arm/ButtonTheme_dark.png and b/scripts/linux/screenshots-arm/ButtonTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/ButtonTheme_light.png b/scripts/linux/screenshots-arm/ButtonTheme_light.png
index 83c9c255954..25cfc5b22fb 100644
Binary files a/scripts/linux/screenshots-arm/ButtonTheme_light.png and b/scripts/linux/screenshots-arm/ButtonTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/CenteredDialogTitle_dark.png b/scripts/linux/screenshots-arm/CenteredDialogTitle_dark.png
index 41e6bbde831..00f69a60f71 100644
Binary files a/scripts/linux/screenshots-arm/CenteredDialogTitle_dark.png and b/scripts/linux/screenshots-arm/CenteredDialogTitle_dark.png differ
diff --git a/scripts/linux/screenshots-arm/CenteredDialogTitle_light.png b/scripts/linux/screenshots-arm/CenteredDialogTitle_light.png
index 4090a503250..fb59facf52e 100644
Binary files a/scripts/linux/screenshots-arm/CenteredDialogTitle_light.png and b/scripts/linux/screenshots-arm/CenteredDialogTitle_light.png differ
diff --git a/scripts/linux/screenshots-arm/CenteredInteractionDialogTitle_dark.png b/scripts/linux/screenshots-arm/CenteredInteractionDialogTitle_dark.png
index 0514c7659da..e391dcf2881 100644
Binary files a/scripts/linux/screenshots-arm/CenteredInteractionDialogTitle_dark.png and b/scripts/linux/screenshots-arm/CenteredInteractionDialogTitle_dark.png differ
diff --git a/scripts/linux/screenshots-arm/CenteredInteractionDialogTitle_light.png b/scripts/linux/screenshots-arm/CenteredInteractionDialogTitle_light.png
index 195b7441e2c..f1e574cff28 100644
Binary files a/scripts/linux/screenshots-arm/CenteredInteractionDialogTitle_light.png and b/scripts/linux/screenshots-arm/CenteredInteractionDialogTitle_light.png differ
diff --git a/scripts/linux/screenshots-arm/ChatInput_dark.png b/scripts/linux/screenshots-arm/ChatInput_dark.png
index ea4123d0b27..074d73e5e7b 100644
Binary files a/scripts/linux/screenshots-arm/ChatInput_dark.png and b/scripts/linux/screenshots-arm/ChatInput_dark.png differ
diff --git a/scripts/linux/screenshots-arm/ChatInput_light.png b/scripts/linux/screenshots-arm/ChatInput_light.png
index 3e29156a8ec..830aa685907 100644
Binary files a/scripts/linux/screenshots-arm/ChatInput_light.png and b/scripts/linux/screenshots-arm/ChatInput_light.png differ
diff --git a/scripts/linux/screenshots-arm/ChatView_dark.png b/scripts/linux/screenshots-arm/ChatView_dark.png
index 55f6a36e9bf..da54b18458c 100644
Binary files a/scripts/linux/screenshots-arm/ChatView_dark.png and b/scripts/linux/screenshots-arm/ChatView_dark.png differ
diff --git a/scripts/linux/screenshots-arm/ChatView_light.png b/scripts/linux/screenshots-arm/ChatView_light.png
index 2701dbb67de..35548ab92fa 100644
Binary files a/scripts/linux/screenshots-arm/ChatView_light.png and b/scripts/linux/screenshots-arm/ChatView_light.png differ
diff --git a/scripts/linux/screenshots-arm/CheckBoxRadioTheme_dark.png b/scripts/linux/screenshots-arm/CheckBoxRadioTheme_dark.png
index b279ea8bfad..2f9d3b3e700 100644
Binary files a/scripts/linux/screenshots-arm/CheckBoxRadioTheme_dark.png and b/scripts/linux/screenshots-arm/CheckBoxRadioTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/CheckBoxRadioTheme_light.png b/scripts/linux/screenshots-arm/CheckBoxRadioTheme_light.png
index 11143caacf9..34554c51e19 100644
Binary files a/scripts/linux/screenshots-arm/CheckBoxRadioTheme_light.png and b/scripts/linux/screenshots-arm/CheckBoxRadioTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/CodeEditor.png b/scripts/linux/screenshots-arm/CodeEditor.png
index a925ffc98ae..8efa91dd1ac 100644
Binary files a/scripts/linux/screenshots-arm/CodeEditor.png and b/scripts/linux/screenshots-arm/CodeEditor.png differ
diff --git a/scripts/linux/screenshots-arm/ComponentReplaceFadeScreenshotTest.png b/scripts/linux/screenshots-arm/ComponentReplaceFadeScreenshotTest.png
index 410a3a9949b..28fd7fda472 100644
Binary files a/scripts/linux/screenshots-arm/ComponentReplaceFadeScreenshotTest.png and b/scripts/linux/screenshots-arm/ComponentReplaceFadeScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/ComponentReplaceFlipScreenshotTest.png b/scripts/linux/screenshots-arm/ComponentReplaceFlipScreenshotTest.png
index 6769300b8a3..e7335579f6f 100644
Binary files a/scripts/linux/screenshots-arm/ComponentReplaceFlipScreenshotTest.png and b/scripts/linux/screenshots-arm/ComponentReplaceFlipScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/ComponentReplaceSlideScreenshotTest.png b/scripts/linux/screenshots-arm/ComponentReplaceSlideScreenshotTest.png
index 3886b92613d..84fae211596 100644
Binary files a/scripts/linux/screenshots-arm/ComponentReplaceSlideScreenshotTest.png and b/scripts/linux/screenshots-arm/ComponentReplaceSlideScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/CoverHorizontalTransitionTest.png b/scripts/linux/screenshots-arm/CoverHorizontalTransitionTest.png
index 570c97925c4..d662ce34838 100644
Binary files a/scripts/linux/screenshots-arm/CoverHorizontalTransitionTest.png and b/scripts/linux/screenshots-arm/CoverHorizontalTransitionTest.png differ
diff --git a/scripts/linux/screenshots-arm/DesktopChromeTheme_dark.png b/scripts/linux/screenshots-arm/DesktopChromeTheme_dark.png
new file mode 100644
index 00000000000..ff0971083d8
Binary files /dev/null and b/scripts/linux/screenshots-arm/DesktopChromeTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/DesktopChromeTheme_light.png b/scripts/linux/screenshots-arm/DesktopChromeTheme_light.png
new file mode 100644
index 00000000000..5c093becc96
Binary files /dev/null and b/scripts/linux/screenshots-arm/DesktopChromeTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/DesktopMode.png b/scripts/linux/screenshots-arm/DesktopMode.png
index 80de41abb5c..24e20f4d8ab 100644
Binary files a/scripts/linux/screenshots-arm/DesktopMode.png and b/scripts/linux/screenshots-arm/DesktopMode.png differ
diff --git a/scripts/linux/screenshots-arm/DesktopScrollbarTheme_dark.png b/scripts/linux/screenshots-arm/DesktopScrollbarTheme_dark.png
new file mode 100644
index 00000000000..1f6d474b2eb
Binary files /dev/null and b/scripts/linux/screenshots-arm/DesktopScrollbarTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/DesktopScrollbarTheme_light.png b/scripts/linux/screenshots-arm/DesktopScrollbarTheme_light.png
new file mode 100644
index 00000000000..6287e13c63b
Binary files /dev/null and b/scripts/linux/screenshots-arm/DesktopScrollbarTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/DesktopWidgetsTheme_dark.png b/scripts/linux/screenshots-arm/DesktopWidgetsTheme_dark.png
new file mode 100644
index 00000000000..dfa1a37e8cf
Binary files /dev/null and b/scripts/linux/screenshots-arm/DesktopWidgetsTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/DesktopWidgetsTheme_light.png b/scripts/linux/screenshots-arm/DesktopWidgetsTheme_light.png
new file mode 100644
index 00000000000..17a318249f7
Binary files /dev/null and b/scripts/linux/screenshots-arm/DesktopWidgetsTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/DialogTheme_dark.png b/scripts/linux/screenshots-arm/DialogTheme_dark.png
index 396d29bc899..b7ce57c801b 100644
Binary files a/scripts/linux/screenshots-arm/DialogTheme_dark.png and b/scripts/linux/screenshots-arm/DialogTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/DialogTheme_light.png b/scripts/linux/screenshots-arm/DialogTheme_light.png
index 0028650c211..e8445729709 100644
Binary files a/scripts/linux/screenshots-arm/DialogTheme_light.png and b/scripts/linux/screenshots-arm/DialogTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/FadeTransitionTest.png b/scripts/linux/screenshots-arm/FadeTransitionTest.png
index 93bd6529b76..fac0f7bc4e7 100644
Binary files a/scripts/linux/screenshots-arm/FadeTransitionTest.png and b/scripts/linux/screenshots-arm/FadeTransitionTest.png differ
diff --git a/scripts/linux/screenshots-arm/FlipTransitionTest.png b/scripts/linux/screenshots-arm/FlipTransitionTest.png
index ee4a4e58c74..7b2c0332691 100644
Binary files a/scripts/linux/screenshots-arm/FlipTransitionTest.png and b/scripts/linux/screenshots-arm/FlipTransitionTest.png differ
diff --git a/scripts/linux/screenshots-arm/FloatingActionButtonTheme_dark.png b/scripts/linux/screenshots-arm/FloatingActionButtonTheme_dark.png
index b591b96b3f9..225e2ab2afe 100644
Binary files a/scripts/linux/screenshots-arm/FloatingActionButtonTheme_dark.png and b/scripts/linux/screenshots-arm/FloatingActionButtonTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/FloatingActionButtonTheme_light.png b/scripts/linux/screenshots-arm/FloatingActionButtonTheme_light.png
index 688dbbaba39..3ca92da79ac 100644
Binary files a/scripts/linux/screenshots-arm/FloatingActionButtonTheme_light.png and b/scripts/linux/screenshots-arm/FloatingActionButtonTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/Gpu3DAnimation.png b/scripts/linux/screenshots-arm/Gpu3DAnimation.png
index 3b1985c5425..fc74f7b9c79 100644
Binary files a/scripts/linux/screenshots-arm/Gpu3DAnimation.png and b/scripts/linux/screenshots-arm/Gpu3DAnimation.png differ
diff --git a/scripts/linux/screenshots-arm/Gpu3DCube.png b/scripts/linux/screenshots-arm/Gpu3DCube.png
index 368dd151791..3ee9a456285 100644
Binary files a/scripts/linux/screenshots-arm/Gpu3DCube.png and b/scripts/linux/screenshots-arm/Gpu3DCube.png differ
diff --git a/scripts/linux/screenshots-arm/Gpu3DModel.png b/scripts/linux/screenshots-arm/Gpu3DModel.png
index d57618054c5..e10c7b6da24 100644
Binary files a/scripts/linux/screenshots-arm/Gpu3DModel.png and b/scripts/linux/screenshots-arm/Gpu3DModel.png differ
diff --git a/scripts/linux/screenshots-arm/Gpu3DTexturedCube.png b/scripts/linux/screenshots-arm/Gpu3DTexturedCube.png
index b861b698e47..035311fccf8 100644
Binary files a/scripts/linux/screenshots-arm/Gpu3DTexturedCube.png and b/scripts/linux/screenshots-arm/Gpu3DTexturedCube.png differ
diff --git a/scripts/linux/screenshots-arm/ImageViewerNavigationModes.png b/scripts/linux/screenshots-arm/ImageViewerNavigationModes.png
index b4ba09ead65..e708a0daebb 100644
Binary files a/scripts/linux/screenshots-arm/ImageViewerNavigationModes.png and b/scripts/linux/screenshots-arm/ImageViewerNavigationModes.png differ
diff --git a/scripts/linux/screenshots-arm/LightweightPickerButtons.png b/scripts/linux/screenshots-arm/LightweightPickerButtons.png
index ddd0150606c..b8c07274cf0 100644
Binary files a/scripts/linux/screenshots-arm/LightweightPickerButtons.png and b/scripts/linux/screenshots-arm/LightweightPickerButtons.png differ
diff --git a/scripts/linux/screenshots-arm/LightweightPickerButtons_above_center.png b/scripts/linux/screenshots-arm/LightweightPickerButtons_above_center.png
index 5c36c07d1be..4c2a63b0af7 100644
Binary files a/scripts/linux/screenshots-arm/LightweightPickerButtons_above_center.png and b/scripts/linux/screenshots-arm/LightweightPickerButtons_above_center.png differ
diff --git a/scripts/linux/screenshots-arm/LightweightPickerButtons_below_right.png b/scripts/linux/screenshots-arm/LightweightPickerButtons_below_right.png
index c8fec83079d..6280a3e030f 100644
Binary files a/scripts/linux/screenshots-arm/LightweightPickerButtons_below_right.png and b/scripts/linux/screenshots-arm/LightweightPickerButtons_below_right.png differ
diff --git a/scripts/linux/screenshots-arm/LightweightPickerButtons_between_mixed.png b/scripts/linux/screenshots-arm/LightweightPickerButtons_between_mixed.png
index c7e55871cba..a028a08663f 100644
Binary files a/scripts/linux/screenshots-arm/LightweightPickerButtons_between_mixed.png and b/scripts/linux/screenshots-arm/LightweightPickerButtons_between_mixed.png differ
diff --git a/scripts/linux/screenshots-arm/ListTheme_dark.png b/scripts/linux/screenshots-arm/ListTheme_dark.png
index c2fc2d8c49f..81a016b806b 100644
Binary files a/scripts/linux/screenshots-arm/ListTheme_dark.png and b/scripts/linux/screenshots-arm/ListTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/ListTheme_light.png b/scripts/linux/screenshots-arm/ListTheme_light.png
index b5a7eb2daf9..03f69a9efd1 100644
Binary files a/scripts/linux/screenshots-arm/ListTheme_light.png and b/scripts/linux/screenshots-arm/ListTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/MainActivity.png b/scripts/linux/screenshots-arm/MainActivity.png
index b1768dff946..9878f2bbf9e 100644
Binary files a/scripts/linux/screenshots-arm/MainActivity.png and b/scripts/linux/screenshots-arm/MainActivity.png differ
diff --git a/scripts/linux/screenshots-arm/Media360Panorama.png b/scripts/linux/screenshots-arm/Media360Panorama.png
index 54f14917724..3c857eb638b 100644
Binary files a/scripts/linux/screenshots-arm/Media360Panorama.png and b/scripts/linux/screenshots-arm/Media360Panorama.png differ
diff --git a/scripts/linux/screenshots-arm/MediaPlayback.png b/scripts/linux/screenshots-arm/MediaPlayback.png
index 352c16e0fe9..32c2ef9f809 100644
Binary files a/scripts/linux/screenshots-arm/MediaPlayback.png and b/scripts/linux/screenshots-arm/MediaPlayback.png differ
diff --git a/scripts/linux/screenshots-arm/MorphElementMorphScreenshotTest.png b/scripts/linux/screenshots-arm/MorphElementMorphScreenshotTest.png
index 75859654396..60cc6b0dcb7 100644
Binary files a/scripts/linux/screenshots-arm/MorphElementMorphScreenshotTest.png and b/scripts/linux/screenshots-arm/MorphElementMorphScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/MorphTransitionScrolledSourceTest.png b/scripts/linux/screenshots-arm/MorphTransitionScrolledSourceTest.png
index 5136d1e9695..545de89cf7c 100644
Binary files a/scripts/linux/screenshots-arm/MorphTransitionScrolledSourceTest.png and b/scripts/linux/screenshots-arm/MorphTransitionScrolledSourceTest.png differ
diff --git a/scripts/linux/screenshots-arm/MorphTransitionScrubScreenshotTest.png b/scripts/linux/screenshots-arm/MorphTransitionScrubScreenshotTest.png
index 0ce34fbfa36..84555b5f58b 100644
Binary files a/scripts/linux/screenshots-arm/MorphTransitionScrubScreenshotTest.png and b/scripts/linux/screenshots-arm/MorphTransitionScrubScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/MorphTransitionSnapshotTest.png b/scripts/linux/screenshots-arm/MorphTransitionSnapshotTest.png
index 938b97be008..1aae256bb4f 100644
Binary files a/scripts/linux/screenshots-arm/MorphTransitionSnapshotTest.png and b/scripts/linux/screenshots-arm/MorphTransitionSnapshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/MorphTransitionTest.png b/scripts/linux/screenshots-arm/MorphTransitionTest.png
index f516ba73ea3..b630e74705a 100644
Binary files a/scripts/linux/screenshots-arm/MorphTransitionTest.png and b/scripts/linux/screenshots-arm/MorphTransitionTest.png differ
diff --git a/scripts/linux/screenshots-arm/MultiButtonTheme_dark.png b/scripts/linux/screenshots-arm/MultiButtonTheme_dark.png
index 5e56a3d0338..8129b3e6165 100644
Binary files a/scripts/linux/screenshots-arm/MultiButtonTheme_dark.png and b/scripts/linux/screenshots-arm/MultiButtonTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/MultiButtonTheme_light.png b/scripts/linux/screenshots-arm/MultiButtonTheme_light.png
index 575e25bc9e7..c6edd72ea0f 100644
Binary files a/scripts/linux/screenshots-arm/MultiButtonTheme_light.png and b/scripts/linux/screenshots-arm/MultiButtonTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/NativeMapFallback.png b/scripts/linux/screenshots-arm/NativeMapFallback.png
index 97bce6f9035..d6e841f9156 100644
Binary files a/scripts/linux/screenshots-arm/NativeMapFallback.png and b/scripts/linux/screenshots-arm/NativeMapFallback.png differ
diff --git a/scripts/linux/screenshots-arm/PaletteOverrideTheme_dark.png b/scripts/linux/screenshots-arm/PaletteOverrideTheme_dark.png
index b3655ff54ed..c6bb9b1f6d7 100644
Binary files a/scripts/linux/screenshots-arm/PaletteOverrideTheme_dark.png and b/scripts/linux/screenshots-arm/PaletteOverrideTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/PaletteOverrideTheme_light.png b/scripts/linux/screenshots-arm/PaletteOverrideTheme_light.png
index e6f3b025a39..3b33ed6aa9e 100644
Binary files a/scripts/linux/screenshots-arm/PaletteOverrideTheme_light.png and b/scripts/linux/screenshots-arm/PaletteOverrideTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/PickerTheme_dark.png b/scripts/linux/screenshots-arm/PickerTheme_dark.png
index d11e486ed8c..d4638e785e2 100644
Binary files a/scripts/linux/screenshots-arm/PickerTheme_dark.png and b/scripts/linux/screenshots-arm/PickerTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/PickerTheme_light.png b/scripts/linux/screenshots-arm/PickerTheme_light.png
index ccb184e468b..28a8dd03e02 100644
Binary files a/scripts/linux/screenshots-arm/PickerTheme_light.png and b/scripts/linux/screenshots-arm/PickerTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/PullToRefreshSpinnerScreenshotTest.png b/scripts/linux/screenshots-arm/PullToRefreshSpinnerScreenshotTest.png
index 73572440bf0..5199f2b712f 100644
Binary files a/scripts/linux/screenshots-arm/PullToRefreshSpinnerScreenshotTest.png and b/scripts/linux/screenshots-arm/PullToRefreshSpinnerScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/PureEditors.png b/scripts/linux/screenshots-arm/PureEditors.png
index 253b46841a7..06568a21a73 100644
Binary files a/scripts/linux/screenshots-arm/PureEditors.png and b/scripts/linux/screenshots-arm/PureEditors.png differ
diff --git a/scripts/linux/screenshots-arm/RealOsmVector.png b/scripts/linux/screenshots-arm/RealOsmVector.png
index 10de17b76e5..1e0d47c59d1 100644
Binary files a/scripts/linux/screenshots-arm/RealOsmVector.png and b/scripts/linux/screenshots-arm/RealOsmVector.png differ
diff --git a/scripts/linux/screenshots-arm/RichTextArea.png b/scripts/linux/screenshots-arm/RichTextArea.png
index 3ff29bfdc61..fbbecc00c51 100644
Binary files a/scripts/linux/screenshots-arm/RichTextArea.png and b/scripts/linux/screenshots-arm/RichTextArea.png differ
diff --git a/scripts/linux/screenshots-arm/SVGStatic.png b/scripts/linux/screenshots-arm/SVGStatic.png
index 200c513a26b..fe7d0a7300b 100644
Binary files a/scripts/linux/screenshots-arm/SVGStatic.png and b/scripts/linux/screenshots-arm/SVGStatic.png differ
diff --git a/scripts/linux/screenshots-arm/Sheet.png b/scripts/linux/screenshots-arm/Sheet.png
index e37da0c58d5..776cac56b60 100644
Binary files a/scripts/linux/screenshots-arm/Sheet.png and b/scripts/linux/screenshots-arm/Sheet.png differ
diff --git a/scripts/linux/screenshots-arm/SheetSlideUpAnimationScreenshotTest.png b/scripts/linux/screenshots-arm/SheetSlideUpAnimationScreenshotTest.png
index a50aea1b9d3..8324331f2cf 100644
Binary files a/scripts/linux/screenshots-arm/SheetSlideUpAnimationScreenshotTest.png and b/scripts/linux/screenshots-arm/SheetSlideUpAnimationScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/ShowcaseTheme_dark.png b/scripts/linux/screenshots-arm/ShowcaseTheme_dark.png
index ab121be0901..525fac70b67 100644
Binary files a/scripts/linux/screenshots-arm/ShowcaseTheme_dark.png and b/scripts/linux/screenshots-arm/ShowcaseTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/ShowcaseTheme_light.png b/scripts/linux/screenshots-arm/ShowcaseTheme_light.png
index 4e66f57d6fb..11e2b20c96c 100644
Binary files a/scripts/linux/screenshots-arm/ShowcaseTheme_light.png and b/scripts/linux/screenshots-arm/ShowcaseTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/SlideFadeTitleTransitionTest.png b/scripts/linux/screenshots-arm/SlideFadeTitleTransitionTest.png
index 6ccc3ea8f0d..5c5348c60f2 100644
Binary files a/scripts/linux/screenshots-arm/SlideFadeTitleTransitionTest.png and b/scripts/linux/screenshots-arm/SlideFadeTitleTransitionTest.png differ
diff --git a/scripts/linux/screenshots-arm/SlideHorizontalBackTransitionTest.png b/scripts/linux/screenshots-arm/SlideHorizontalBackTransitionTest.png
index f69ac0abf5d..87bfbb6edb1 100644
Binary files a/scripts/linux/screenshots-arm/SlideHorizontalBackTransitionTest.png and b/scripts/linux/screenshots-arm/SlideHorizontalBackTransitionTest.png differ
diff --git a/scripts/linux/screenshots-arm/SlideHorizontalTransitionTest.png b/scripts/linux/screenshots-arm/SlideHorizontalTransitionTest.png
index a9bdec50e4a..d40e200ad4e 100644
Binary files a/scripts/linux/screenshots-arm/SlideHorizontalTransitionTest.png and b/scripts/linux/screenshots-arm/SlideHorizontalTransitionTest.png differ
diff --git a/scripts/linux/screenshots-arm/SlideVerticalTransitionTest.png b/scripts/linux/screenshots-arm/SlideVerticalTransitionTest.png
index 0448196eb79..eaed501bf08 100644
Binary files a/scripts/linux/screenshots-arm/SlideVerticalTransitionTest.png and b/scripts/linux/screenshots-arm/SlideVerticalTransitionTest.png differ
diff --git a/scripts/linux/screenshots-arm/SmoothScrollScreenshotTest.png b/scripts/linux/screenshots-arm/SmoothScrollScreenshotTest.png
index 259ab0ff131..0b4caba0d79 100644
Binary files a/scripts/linux/screenshots-arm/SmoothScrollScreenshotTest.png and b/scripts/linux/screenshots-arm/SmoothScrollScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/SpanLabelTheme_dark.png b/scripts/linux/screenshots-arm/SpanLabelTheme_dark.png
index f67a2c2ab2c..c367a1ef1a0 100644
Binary files a/scripts/linux/screenshots-arm/SpanLabelTheme_dark.png and b/scripts/linux/screenshots-arm/SpanLabelTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/SpanLabelTheme_light.png b/scripts/linux/screenshots-arm/SpanLabelTheme_light.png
index b6910f1182e..ff4484ad2f1 100644
Binary files a/scripts/linux/screenshots-arm/SpanLabelTheme_light.png and b/scripts/linux/screenshots-arm/SpanLabelTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/StatusBarTapDiagnosticScreenshotTest.png b/scripts/linux/screenshots-arm/StatusBarTapDiagnosticScreenshotTest.png
index d7c7f8181c6..9598bc783a4 100644
Binary files a/scripts/linux/screenshots-arm/StatusBarTapDiagnosticScreenshotTest.png and b/scripts/linux/screenshots-arm/StatusBarTapDiagnosticScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/StickyHeaderFadeTransitionScreenshotTest.png b/scripts/linux/screenshots-arm/StickyHeaderFadeTransitionScreenshotTest.png
index 4fdd0534b5c..497f2546f92 100644
Binary files a/scripts/linux/screenshots-arm/StickyHeaderFadeTransitionScreenshotTest.png and b/scripts/linux/screenshots-arm/StickyHeaderFadeTransitionScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/StickyHeaderScreenshotTest.png b/scripts/linux/screenshots-arm/StickyHeaderScreenshotTest.png
index 9f4dd396d1c..6b94f2a8b02 100644
Binary files a/scripts/linux/screenshots-arm/StickyHeaderScreenshotTest.png and b/scripts/linux/screenshots-arm/StickyHeaderScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/StickyHeaderSlideTransitionScreenshotTest.png b/scripts/linux/screenshots-arm/StickyHeaderSlideTransitionScreenshotTest.png
index ed1fdab9d1d..cca0abbbed1 100644
Binary files a/scripts/linux/screenshots-arm/StickyHeaderSlideTransitionScreenshotTest.png and b/scripts/linux/screenshots-arm/StickyHeaderSlideTransitionScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/SurfacesRasterizer.png b/scripts/linux/screenshots-arm/SurfacesRasterizer.png
index e40cc72a28a..a36c11e02d9 100644
Binary files a/scripts/linux/screenshots-arm/SurfacesRasterizer.png and b/scripts/linux/screenshots-arm/SurfacesRasterizer.png differ
diff --git a/scripts/linux/screenshots-arm/SwitchTheme_dark.png b/scripts/linux/screenshots-arm/SwitchTheme_dark.png
index f272c2af4c0..16b4d9a150e 100644
Binary files a/scripts/linux/screenshots-arm/SwitchTheme_dark.png and b/scripts/linux/screenshots-arm/SwitchTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/SwitchTheme_light.png b/scripts/linux/screenshots-arm/SwitchTheme_light.png
index 99cf1fd94f5..2bdecee3e2a 100644
Binary files a/scripts/linux/screenshots-arm/SwitchTheme_light.png and b/scripts/linux/screenshots-arm/SwitchTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/TabsAnimatedIndicatorScreenshotTest.png b/scripts/linux/screenshots-arm/TabsAnimatedIndicatorScreenshotTest.png
index 3730d694fac..ebc05b7fd24 100644
Binary files a/scripts/linux/screenshots-arm/TabsAnimatedIndicatorScreenshotTest.png and b/scripts/linux/screenshots-arm/TabsAnimatedIndicatorScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/TabsBehavior.png b/scripts/linux/screenshots-arm/TabsBehavior.png
index 8d6ff2c9465..d88fde9d262 100644
Binary files a/scripts/linux/screenshots-arm/TabsBehavior.png and b/scripts/linux/screenshots-arm/TabsBehavior.png differ
diff --git a/scripts/linux/screenshots-arm/TabsTheme_dark.png b/scripts/linux/screenshots-arm/TabsTheme_dark.png
index cd243ceabf3..51c63bb1040 100644
Binary files a/scripts/linux/screenshots-arm/TabsTheme_dark.png and b/scripts/linux/screenshots-arm/TabsTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/TabsTheme_light.png b/scripts/linux/screenshots-arm/TabsTheme_light.png
index c5b51b10989..037607e3ce3 100644
Binary files a/scripts/linux/screenshots-arm/TabsTheme_light.png and b/scripts/linux/screenshots-arm/TabsTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/TensileBounceScreenshotTest.png b/scripts/linux/screenshots-arm/TensileBounceScreenshotTest.png
index 42532fc0623..fa99d9076a6 100644
Binary files a/scripts/linux/screenshots-arm/TensileBounceScreenshotTest.png and b/scripts/linux/screenshots-arm/TensileBounceScreenshotTest.png differ
diff --git a/scripts/linux/screenshots-arm/TextAreaAlignmentStates.png b/scripts/linux/screenshots-arm/TextAreaAlignmentStates.png
index d3f63336c25..5606ffa9cd3 100644
Binary files a/scripts/linux/screenshots-arm/TextAreaAlignmentStates.png and b/scripts/linux/screenshots-arm/TextAreaAlignmentStates.png differ
diff --git a/scripts/linux/screenshots-arm/TextFieldTheme_dark.png b/scripts/linux/screenshots-arm/TextFieldTheme_dark.png
index fd9dbbd6f94..c759c44de3e 100644
Binary files a/scripts/linux/screenshots-arm/TextFieldTheme_dark.png and b/scripts/linux/screenshots-arm/TextFieldTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/TextFieldTheme_light.png b/scripts/linux/screenshots-arm/TextFieldTheme_light.png
index bd15ec555f8..a81074e3eb3 100644
Binary files a/scripts/linux/screenshots-arm/TextFieldTheme_light.png and b/scripts/linux/screenshots-arm/TextFieldTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/ToastBarTopPosition.png b/scripts/linux/screenshots-arm/ToastBarTopPosition.png
index bb986498684..ae3acadee0b 100644
Binary files a/scripts/linux/screenshots-arm/ToastBarTopPosition.png and b/scripts/linux/screenshots-arm/ToastBarTopPosition.png differ
diff --git a/scripts/linux/screenshots-arm/ToolbarTheme_dark.png b/scripts/linux/screenshots-arm/ToolbarTheme_dark.png
index 5739c63b8a0..a8003cfd6b7 100644
Binary files a/scripts/linux/screenshots-arm/ToolbarTheme_dark.png and b/scripts/linux/screenshots-arm/ToolbarTheme_dark.png differ
diff --git a/scripts/linux/screenshots-arm/ToolbarTheme_light.png b/scripts/linux/screenshots-arm/ToolbarTheme_light.png
index 0541392c1d2..0c803c81e98 100644
Binary files a/scripts/linux/screenshots-arm/ToolbarTheme_light.png and b/scripts/linux/screenshots-arm/ToolbarTheme_light.png differ
diff --git a/scripts/linux/screenshots-arm/UncoverHorizontalTransitionTest.png b/scripts/linux/screenshots-arm/UncoverHorizontalTransitionTest.png
index 363250015ce..2dae992a488 100644
Binary files a/scripts/linux/screenshots-arm/UncoverHorizontalTransitionTest.png and b/scripts/linux/screenshots-arm/UncoverHorizontalTransitionTest.png differ
diff --git a/scripts/linux/screenshots-arm/VRStereoScene.png b/scripts/linux/screenshots-arm/VRStereoScene.png
index 0f0b02c0462..212ff7d5102 100644
Binary files a/scripts/linux/screenshots-arm/VRStereoScene.png and b/scripts/linux/screenshots-arm/VRStereoScene.png differ
diff --git a/scripts/linux/screenshots-arm/ValidatorLightweightPicker.png b/scripts/linux/screenshots-arm/ValidatorLightweightPicker.png
index 13787e143d7..116c6323f43 100644
Binary files a/scripts/linux/screenshots-arm/ValidatorLightweightPicker.png and b/scripts/linux/screenshots-arm/ValidatorLightweightPicker.png differ
diff --git a/scripts/linux/screenshots-arm/VectorMapDarkStyle.png b/scripts/linux/screenshots-arm/VectorMapDarkStyle.png
index b3894a8b514..128bf7862a4 100644
Binary files a/scripts/linux/screenshots-arm/VectorMapDarkStyle.png and b/scripts/linux/screenshots-arm/VectorMapDarkStyle.png differ
diff --git a/scripts/linux/screenshots-arm/VectorMapMarkers.png b/scripts/linux/screenshots-arm/VectorMapMarkers.png
index 1de805e3717..07df2cc6db4 100644
Binary files a/scripts/linux/screenshots-arm/VectorMapMarkers.png and b/scripts/linux/screenshots-arm/VectorMapMarkers.png differ
diff --git a/scripts/linux/screenshots-arm/VectorMapShapes.png b/scripts/linux/screenshots-arm/VectorMapShapes.png
index ea885ad371e..be693523fe7 100644
Binary files a/scripts/linux/screenshots-arm/VectorMapShapes.png and b/scripts/linux/screenshots-arm/VectorMapShapes.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Dialog-1000x400.png b/scripts/linux/screenshots-arm/Window-Dialog-1000x400.png
index 8cffac9bf3e..f52adee6a9e 100644
Binary files a/scripts/linux/screenshots-arm/Window-Dialog-1000x400.png and b/scripts/linux/screenshots-arm/Window-Dialog-1000x400.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Dialog-400x300.png b/scripts/linux/screenshots-arm/Window-Dialog-400x300.png
index c45f579cedd..8fbd037f328 100644
Binary files a/scripts/linux/screenshots-arm/Window-Dialog-400x300.png and b/scripts/linux/screenshots-arm/Window-Dialog-400x300.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Dialog-900x700.png b/scripts/linux/screenshots-arm/Window-Dialog-900x700.png
index fa496bc0d22..c520187fad5 100644
Binary files a/scripts/linux/screenshots-arm/Window-Dialog-900x700.png and b/scripts/linux/screenshots-arm/Window-Dialog-900x700.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Editing-1000x400.png b/scripts/linux/screenshots-arm/Window-Editing-1000x400.png
index 6829fee4c5f..80c18e4b88f 100644
Binary files a/scripts/linux/screenshots-arm/Window-Editing-1000x400.png and b/scripts/linux/screenshots-arm/Window-Editing-1000x400.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Editing-400x300.png b/scripts/linux/screenshots-arm/Window-Editing-400x300.png
index 110bd7bdb09..d4f007a0208 100644
Binary files a/scripts/linux/screenshots-arm/Window-Editing-400x300.png and b/scripts/linux/screenshots-arm/Window-Editing-400x300.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Editing-900x700.png b/scripts/linux/screenshots-arm/Window-Editing-900x700.png
index 998c9e033f6..e38de325fda 100644
Binary files a/scripts/linux/screenshots-arm/Window-Editing-900x700.png and b/scripts/linux/screenshots-arm/Window-Editing-900x700.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Graphics-1000x400.png b/scripts/linux/screenshots-arm/Window-Graphics-1000x400.png
index d9ca21c7707..05e161ffb57 100644
Binary files a/scripts/linux/screenshots-arm/Window-Graphics-1000x400.png and b/scripts/linux/screenshots-arm/Window-Graphics-1000x400.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Graphics-400x300.png b/scripts/linux/screenshots-arm/Window-Graphics-400x300.png
index 239629b938a..ad13fbbb7bc 100644
Binary files a/scripts/linux/screenshots-arm/Window-Graphics-400x300.png and b/scripts/linux/screenshots-arm/Window-Graphics-400x300.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Graphics-900x700.png b/scripts/linux/screenshots-arm/Window-Graphics-900x700.png
index a2f0d8c13de..80bbfba6d04 100644
Binary files a/scripts/linux/screenshots-arm/Window-Graphics-900x700.png and b/scripts/linux/screenshots-arm/Window-Graphics-900x700.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Layout-1000x400.png b/scripts/linux/screenshots-arm/Window-Layout-1000x400.png
index 666adc85253..486a9f14866 100644
Binary files a/scripts/linux/screenshots-arm/Window-Layout-1000x400.png and b/scripts/linux/screenshots-arm/Window-Layout-1000x400.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Layout-400x300.png b/scripts/linux/screenshots-arm/Window-Layout-400x300.png
index 518d7788ff6..58d97bb884b 100644
Binary files a/scripts/linux/screenshots-arm/Window-Layout-400x300.png and b/scripts/linux/screenshots-arm/Window-Layout-400x300.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Layout-900x700.png b/scripts/linux/screenshots-arm/Window-Layout-900x700.png
index de6bff86530..b2a1b5bac9e 100644
Binary files a/scripts/linux/screenshots-arm/Window-Layout-900x700.png and b/scripts/linux/screenshots-arm/Window-Layout-900x700.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Modal-background.png b/scripts/linux/screenshots-arm/Window-Modal-background.png
index cb8bfc3d899..7ba9c5119f1 100644
Binary files a/scripts/linux/screenshots-arm/Window-Modal-background.png and b/scripts/linux/screenshots-arm/Window-Modal-background.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Overlay-600x450.png b/scripts/linux/screenshots-arm/Window-Overlay-600x450.png
index 5d9f7c82fe4..eb9635dfc2e 100644
Binary files a/scripts/linux/screenshots-arm/Window-Overlay-600x450.png and b/scripts/linux/screenshots-arm/Window-Overlay-600x450.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Scroll-1000x400.png b/scripts/linux/screenshots-arm/Window-Scroll-1000x400.png
index a6ad7a0b2b9..3c33787174f 100644
Binary files a/scripts/linux/screenshots-arm/Window-Scroll-1000x400.png and b/scripts/linux/screenshots-arm/Window-Scroll-1000x400.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Scroll-400x300.png b/scripts/linux/screenshots-arm/Window-Scroll-400x300.png
index 49c2b58a893..fe85af37a2a 100644
Binary files a/scripts/linux/screenshots-arm/Window-Scroll-400x300.png and b/scripts/linux/screenshots-arm/Window-Scroll-400x300.png differ
diff --git a/scripts/linux/screenshots-arm/Window-Scroll-900x700.png b/scripts/linux/screenshots-arm/Window-Scroll-900x700.png
index 51412c91d0e..61ea0d4afc8 100644
Binary files a/scripts/linux/screenshots-arm/Window-Scroll-900x700.png and b/scripts/linux/screenshots-arm/Window-Scroll-900x700.png differ
diff --git a/scripts/linux/screenshots-arm/chart-bar-stacked.png b/scripts/linux/screenshots-arm/chart-bar-stacked.png
index 4865f7f9f5c..12200036335 100644
Binary files a/scripts/linux/screenshots-arm/chart-bar-stacked.png and b/scripts/linux/screenshots-arm/chart-bar-stacked.png differ
diff --git a/scripts/linux/screenshots-arm/chart-bar.png b/scripts/linux/screenshots-arm/chart-bar.png
index 04b6ddd24b6..072d544484e 100644
Binary files a/scripts/linux/screenshots-arm/chart-bar.png and b/scripts/linux/screenshots-arm/chart-bar.png differ
diff --git a/scripts/linux/screenshots-arm/chart-bubble.png b/scripts/linux/screenshots-arm/chart-bubble.png
index 39119420cb5..8f48bf837e2 100644
Binary files a/scripts/linux/screenshots-arm/chart-bubble.png and b/scripts/linux/screenshots-arm/chart-bubble.png differ
diff --git a/scripts/linux/screenshots-arm/chart-combined-xy.png b/scripts/linux/screenshots-arm/chart-combined-xy.png
index 8084dbbe62b..4155e8d8a6e 100644
Binary files a/scripts/linux/screenshots-arm/chart-combined-xy.png and b/scripts/linux/screenshots-arm/chart-combined-xy.png differ
diff --git a/scripts/linux/screenshots-arm/chart-cubic-line.png b/scripts/linux/screenshots-arm/chart-cubic-line.png
index eb348b6c615..8fda6321718 100644
Binary files a/scripts/linux/screenshots-arm/chart-cubic-line.png and b/scripts/linux/screenshots-arm/chart-cubic-line.png differ
diff --git a/scripts/linux/screenshots-arm/chart-doughnut.png b/scripts/linux/screenshots-arm/chart-doughnut.png
index b81bce19b06..06461fe2593 100644
Binary files a/scripts/linux/screenshots-arm/chart-doughnut.png and b/scripts/linux/screenshots-arm/chart-doughnut.png differ
diff --git a/scripts/linux/screenshots-arm/chart-line.png b/scripts/linux/screenshots-arm/chart-line.png
index ba4df24c155..3dcacc29c9a 100644
Binary files a/scripts/linux/screenshots-arm/chart-line.png and b/scripts/linux/screenshots-arm/chart-line.png differ
diff --git a/scripts/linux/screenshots-arm/chart-pie.png b/scripts/linux/screenshots-arm/chart-pie.png
index 45079071073..1205719c5aa 100644
Binary files a/scripts/linux/screenshots-arm/chart-pie.png and b/scripts/linux/screenshots-arm/chart-pie.png differ
diff --git a/scripts/linux/screenshots-arm/chart-radar.png b/scripts/linux/screenshots-arm/chart-radar.png
index 3637aa1fb5c..9047243f995 100644
Binary files a/scripts/linux/screenshots-arm/chart-radar.png and b/scripts/linux/screenshots-arm/chart-radar.png differ
diff --git a/scripts/linux/screenshots-arm/chart-range-bar.png b/scripts/linux/screenshots-arm/chart-range-bar.png
index abd9732d0c8..96155b2a0a5 100644
Binary files a/scripts/linux/screenshots-arm/chart-range-bar.png and b/scripts/linux/screenshots-arm/chart-range-bar.png differ
diff --git a/scripts/linux/screenshots-arm/chart-rotated-pie.png b/scripts/linux/screenshots-arm/chart-rotated-pie.png
index 6c299f87bc7..0ecb0ea39a6 100644
Binary files a/scripts/linux/screenshots-arm/chart-rotated-pie.png and b/scripts/linux/screenshots-arm/chart-rotated-pie.png differ
diff --git a/scripts/linux/screenshots-arm/chart-scatter.png b/scripts/linux/screenshots-arm/chart-scatter.png
index c469bcf0c0f..e566573c3a0 100644
Binary files a/scripts/linux/screenshots-arm/chart-scatter.png and b/scripts/linux/screenshots-arm/chart-scatter.png differ
diff --git a/scripts/linux/screenshots-arm/chart-time.png b/scripts/linux/screenshots-arm/chart-time.png
index 2c5022daa94..4f82f1159f4 100644
Binary files a/scripts/linux/screenshots-arm/chart-time.png and b/scripts/linux/screenshots-arm/chart-time.png differ
diff --git a/scripts/linux/screenshots-arm/chart-transform.png b/scripts/linux/screenshots-arm/chart-transform.png
index 7e02828c1d6..705837fa8ca 100644
Binary files a/scripts/linux/screenshots-arm/chart-transform.png and b/scripts/linux/screenshots-arm/chart-transform.png differ
diff --git a/scripts/linux/screenshots-arm/css-gradients.png b/scripts/linux/screenshots-arm/css-gradients.png
index 8501c1b7ab0..34d95462d04 100644
Binary files a/scripts/linux/screenshots-arm/css-gradients.png and b/scripts/linux/screenshots-arm/css-gradients.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-affine-scale.png b/scripts/linux/screenshots-arm/graphics-affine-scale.png
index f6278ce5e9b..0a0b571c74f 100644
Binary files a/scripts/linux/screenshots-arm/graphics-affine-scale.png and b/scripts/linux/screenshots-arm/graphics-affine-scale.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-clip-under-rotation.png b/scripts/linux/screenshots-arm/graphics-clip-under-rotation.png
index c4f69af73a0..ab7285b9e17 100644
Binary files a/scripts/linux/screenshots-arm/graphics-clip-under-rotation.png and b/scripts/linux/screenshots-arm/graphics-clip-under-rotation.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-clip.png b/scripts/linux/screenshots-arm/graphics-clip.png
index b459244603f..c1fda205aff 100644
Binary files a/scripts/linux/screenshots-arm/graphics-clip.png and b/scripts/linux/screenshots-arm/graphics-clip.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-draw-arc.png b/scripts/linux/screenshots-arm/graphics-draw-arc.png
index 80f535e093d..e040b283efe 100644
Binary files a/scripts/linux/screenshots-arm/graphics-draw-arc.png and b/scripts/linux/screenshots-arm/graphics-draw-arc.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-draw-gradient-stops.png b/scripts/linux/screenshots-arm/graphics-draw-gradient-stops.png
index f893769f5bd..c26c7d5d536 100644
Binary files a/scripts/linux/screenshots-arm/graphics-draw-gradient-stops.png and b/scripts/linux/screenshots-arm/graphics-draw-gradient-stops.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-draw-gradient.png b/scripts/linux/screenshots-arm/graphics-draw-gradient.png
index 6774b1aeec3..34945e0fbf2 100644
Binary files a/scripts/linux/screenshots-arm/graphics-draw-gradient.png and b/scripts/linux/screenshots-arm/graphics-draw-gradient.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-draw-image-rect.png b/scripts/linux/screenshots-arm/graphics-draw-image-rect.png
index 3be1cba2c4d..62090cff610 100644
Binary files a/scripts/linux/screenshots-arm/graphics-draw-image-rect.png and b/scripts/linux/screenshots-arm/graphics-draw-image-rect.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-draw-line.png b/scripts/linux/screenshots-arm/graphics-draw-line.png
index 61909b0b21c..74b18df1a32 100644
Binary files a/scripts/linux/screenshots-arm/graphics-draw-line.png and b/scripts/linux/screenshots-arm/graphics-draw-line.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-draw-rect.png b/scripts/linux/screenshots-arm/graphics-draw-rect.png
index 0a1e7474c99..4c2a59c16e7 100644
Binary files a/scripts/linux/screenshots-arm/graphics-draw-rect.png and b/scripts/linux/screenshots-arm/graphics-draw-rect.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-draw-round-rect.png b/scripts/linux/screenshots-arm/graphics-draw-round-rect.png
index 39b5ce5315c..d0e37a24a6a 100644
Binary files a/scripts/linux/screenshots-arm/graphics-draw-round-rect.png and b/scripts/linux/screenshots-arm/graphics-draw-round-rect.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-draw-shape.png b/scripts/linux/screenshots-arm/graphics-draw-shape.png
index 1dd0016f6aa..743e0290ee2 100644
Binary files a/scripts/linux/screenshots-arm/graphics-draw-shape.png and b/scripts/linux/screenshots-arm/graphics-draw-shape.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-draw-string-decorated.png b/scripts/linux/screenshots-arm/graphics-draw-string-decorated.png
index 8c1ce429e57..cd02e702b9f 100644
Binary files a/scripts/linux/screenshots-arm/graphics-draw-string-decorated.png and b/scripts/linux/screenshots-arm/graphics-draw-string-decorated.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-draw-string.png b/scripts/linux/screenshots-arm/graphics-draw-string.png
index 3f955c6bdcc..622ac317ac3 100644
Binary files a/scripts/linux/screenshots-arm/graphics-draw-string.png and b/scripts/linux/screenshots-arm/graphics-draw-string.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-empty-clip.png b/scripts/linux/screenshots-arm/graphics-empty-clip.png
index 8134902d1f0..342ac72d92b 100644
Binary files a/scripts/linux/screenshots-arm/graphics-empty-clip.png and b/scripts/linux/screenshots-arm/graphics-empty-clip.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-fill-arc.png b/scripts/linux/screenshots-arm/graphics-fill-arc.png
index 64f66ba194b..fbff5963f4b 100644
Binary files a/scripts/linux/screenshots-arm/graphics-fill-arc.png and b/scripts/linux/screenshots-arm/graphics-fill-arc.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-fill-polygon.png b/scripts/linux/screenshots-arm/graphics-fill-polygon.png
index 47e79b7b6bc..fb1f48f3e68 100644
Binary files a/scripts/linux/screenshots-arm/graphics-fill-polygon.png and b/scripts/linux/screenshots-arm/graphics-fill-polygon.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-fill-rect.png b/scripts/linux/screenshots-arm/graphics-fill-rect.png
index 4cf6e7c4007..a7ac5121ed2 100644
Binary files a/scripts/linux/screenshots-arm/graphics-fill-rect.png and b/scripts/linux/screenshots-arm/graphics-fill-rect.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-fill-round-rect.png b/scripts/linux/screenshots-arm/graphics-fill-round-rect.png
index 5e5f9884d7f..eddfb7a91c0 100644
Binary files a/scripts/linux/screenshots-arm/graphics-fill-round-rect.png and b/scripts/linux/screenshots-arm/graphics-fill-round-rect.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-fill-shape.png b/scripts/linux/screenshots-arm/graphics-fill-shape.png
index f150b2bf7f8..81dd3cacdb9 100644
Binary files a/scripts/linux/screenshots-arm/graphics-fill-shape.png and b/scripts/linux/screenshots-arm/graphics-fill-shape.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-fill-triangle.png b/scripts/linux/screenshots-arm/graphics-fill-triangle.png
index 8d5dc56ea00..92721e00f9f 100644
Binary files a/scripts/linux/screenshots-arm/graphics-fill-triangle.png and b/scripts/linux/screenshots-arm/graphics-fill-triangle.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-gaussian-blur.png b/scripts/linux/screenshots-arm/graphics-gaussian-blur.png
index 80e24a83f61..a7f2d01f014 100644
Binary files a/scripts/linux/screenshots-arm/graphics-gaussian-blur.png and b/scripts/linux/screenshots-arm/graphics-gaussian-blur.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-inscribed-triangle-grid.png b/scripts/linux/screenshots-arm/graphics-inscribed-triangle-grid.png
index 926b0c43208..550a8aabaa2 100644
Binary files a/scripts/linux/screenshots-arm/graphics-inscribed-triangle-grid.png and b/scripts/linux/screenshots-arm/graphics-inscribed-triangle-grid.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-large-stroke-dirty-clip.png b/scripts/linux/screenshots-arm/graphics-large-stroke-dirty-clip.png
index d735a5dcde7..a211137fb5a 100644
Binary files a/scripts/linux/screenshots-arm/graphics-large-stroke-dirty-clip.png and b/scripts/linux/screenshots-arm/graphics-large-stroke-dirty-clip.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-partial-flush-clip-escape.png b/scripts/linux/screenshots-arm/graphics-partial-flush-clip-escape.png
index b429276a374..5da60584147 100644
Binary files a/scripts/linux/screenshots-arm/graphics-partial-flush-clip-escape.png and b/scripts/linux/screenshots-arm/graphics-partial-flush-clip-escape.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-rotate.png b/scripts/linux/screenshots-arm/graphics-rotate.png
index 3ea1f59e6c6..ca74621d88f 100644
Binary files a/scripts/linux/screenshots-arm/graphics-rotate.png and b/scripts/linux/screenshots-arm/graphics-rotate.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-scale.png b/scripts/linux/screenshots-arm/graphics-scale.png
index 1b6026969f2..631a07fc533 100644
Binary files a/scripts/linux/screenshots-arm/graphics-scale.png and b/scripts/linux/screenshots-arm/graphics-scale.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-stroke-test.png b/scripts/linux/screenshots-arm/graphics-stroke-test.png
index f5ff34b3e66..21e281d5cd7 100644
Binary files a/scripts/linux/screenshots-arm/graphics-stroke-test.png and b/scripts/linux/screenshots-arm/graphics-stroke-test.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-tile-image.png b/scripts/linux/screenshots-arm/graphics-tile-image.png
index e22afbb2d74..b5a57fa5a70 100644
Binary files a/scripts/linux/screenshots-arm/graphics-tile-image.png and b/scripts/linux/screenshots-arm/graphics-tile-image.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-transform-camera.png b/scripts/linux/screenshots-arm/graphics-transform-camera.png
index 71c08495468..410afdb0ac2 100644
Binary files a/scripts/linux/screenshots-arm/graphics-transform-camera.png and b/scripts/linux/screenshots-arm/graphics-transform-camera.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-transform-perspective.png b/scripts/linux/screenshots-arm/graphics-transform-perspective.png
index 1f9440a8fa0..4e53f8085a5 100644
Binary files a/scripts/linux/screenshots-arm/graphics-transform-perspective.png and b/scripts/linux/screenshots-arm/graphics-transform-perspective.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-transform-rotation.png b/scripts/linux/screenshots-arm/graphics-transform-rotation.png
index bc24eaccd3b..e662922f6ec 100644
Binary files a/scripts/linux/screenshots-arm/graphics-transform-rotation.png and b/scripts/linux/screenshots-arm/graphics-transform-rotation.png differ
diff --git a/scripts/linux/screenshots-arm/graphics-transform-translation.png b/scripts/linux/screenshots-arm/graphics-transform-translation.png
index edc7b651c34..f107a45e8f4 100644
Binary files a/scripts/linux/screenshots-arm/graphics-transform-translation.png and b/scripts/linux/screenshots-arm/graphics-transform-translation.png differ
diff --git a/scripts/linux/screenshots-arm/kotlin.png b/scripts/linux/screenshots-arm/kotlin.png
index 05879314463..b0156d58402 100644
Binary files a/scripts/linux/screenshots-arm/kotlin.png and b/scripts/linux/screenshots-arm/kotlin.png differ
diff --git a/scripts/linux/screenshots-arm/landscape.png b/scripts/linux/screenshots-arm/landscape.png
index 4c2f7c7a026..bd240669f77 100644
Binary files a/scripts/linux/screenshots-arm/landscape.png and b/scripts/linux/screenshots-arm/landscape.png differ
diff --git a/scripts/linux/screenshots/AdsScreen.png b/scripts/linux/screenshots/AdsScreen.png
index 1695a15ec61..b851e69ab61 100644
Binary files a/scripts/linux/screenshots/AdsScreen.png and b/scripts/linux/screenshots/AdsScreen.png differ
diff --git a/scripts/linux/screenshots/AnimateHierarchyScreenshotTest.png b/scripts/linux/screenshots/AnimateHierarchyScreenshotTest.png
index 8a9ce03c8c8..5796be4c0a7 100644
Binary files a/scripts/linux/screenshots/AnimateHierarchyScreenshotTest.png and b/scripts/linux/screenshots/AnimateHierarchyScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/AnimateLayoutScreenshotTest.png b/scripts/linux/screenshots/AnimateLayoutScreenshotTest.png
index 464b0d55c45..aa3d28a3622 100644
Binary files a/scripts/linux/screenshots/AnimateLayoutScreenshotTest.png and b/scripts/linux/screenshots/AnimateLayoutScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/AnimateUnlayoutScreenshotTest.png b/scripts/linux/screenshots/AnimateUnlayoutScreenshotTest.png
index c66faed6f9f..d923b6e9c7f 100644
Binary files a/scripts/linux/screenshots/AnimateUnlayoutScreenshotTest.png and b/scripts/linux/screenshots/AnimateUnlayoutScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/AppReviewDialog.png b/scripts/linux/screenshots/AppReviewDialog.png
index 76dfbc00041..6c0f5f331e3 100644
Binary files a/scripts/linux/screenshots/AppReviewDialog.png and b/scripts/linux/screenshots/AppReviewDialog.png differ
diff --git a/scripts/linux/screenshots/BrowserComponent.png b/scripts/linux/screenshots/BrowserComponent.png
index ae2ed972338..a9bd7e8a95a 100644
Binary files a/scripts/linux/screenshots/BrowserComponent.png and b/scripts/linux/screenshots/BrowserComponent.png differ
diff --git a/scripts/linux/screenshots/ButtonTheme_dark.png b/scripts/linux/screenshots/ButtonTheme_dark.png
index bb390f7c5e7..51b044f9cc1 100644
Binary files a/scripts/linux/screenshots/ButtonTheme_dark.png and b/scripts/linux/screenshots/ButtonTheme_dark.png differ
diff --git a/scripts/linux/screenshots/ButtonTheme_light.png b/scripts/linux/screenshots/ButtonTheme_light.png
index 83c9c255954..25cfc5b22fb 100644
Binary files a/scripts/linux/screenshots/ButtonTheme_light.png and b/scripts/linux/screenshots/ButtonTheme_light.png differ
diff --git a/scripts/linux/screenshots/CenteredDialogTitle_dark.png b/scripts/linux/screenshots/CenteredDialogTitle_dark.png
index 41e6bbde831..00f69a60f71 100644
Binary files a/scripts/linux/screenshots/CenteredDialogTitle_dark.png and b/scripts/linux/screenshots/CenteredDialogTitle_dark.png differ
diff --git a/scripts/linux/screenshots/CenteredDialogTitle_light.png b/scripts/linux/screenshots/CenteredDialogTitle_light.png
index 4090a503250..fb59facf52e 100644
Binary files a/scripts/linux/screenshots/CenteredDialogTitle_light.png and b/scripts/linux/screenshots/CenteredDialogTitle_light.png differ
diff --git a/scripts/linux/screenshots/CenteredInteractionDialogTitle_dark.png b/scripts/linux/screenshots/CenteredInteractionDialogTitle_dark.png
index 0514c7659da..e391dcf2881 100644
Binary files a/scripts/linux/screenshots/CenteredInteractionDialogTitle_dark.png and b/scripts/linux/screenshots/CenteredInteractionDialogTitle_dark.png differ
diff --git a/scripts/linux/screenshots/CenteredInteractionDialogTitle_light.png b/scripts/linux/screenshots/CenteredInteractionDialogTitle_light.png
index 195b7441e2c..f1e574cff28 100644
Binary files a/scripts/linux/screenshots/CenteredInteractionDialogTitle_light.png and b/scripts/linux/screenshots/CenteredInteractionDialogTitle_light.png differ
diff --git a/scripts/linux/screenshots/ChatInput_dark.png b/scripts/linux/screenshots/ChatInput_dark.png
index ea4123d0b27..074d73e5e7b 100644
Binary files a/scripts/linux/screenshots/ChatInput_dark.png and b/scripts/linux/screenshots/ChatInput_dark.png differ
diff --git a/scripts/linux/screenshots/ChatInput_light.png b/scripts/linux/screenshots/ChatInput_light.png
index 3e29156a8ec..830aa685907 100644
Binary files a/scripts/linux/screenshots/ChatInput_light.png and b/scripts/linux/screenshots/ChatInput_light.png differ
diff --git a/scripts/linux/screenshots/ChatView_dark.png b/scripts/linux/screenshots/ChatView_dark.png
index 55f6a36e9bf..da54b18458c 100644
Binary files a/scripts/linux/screenshots/ChatView_dark.png and b/scripts/linux/screenshots/ChatView_dark.png differ
diff --git a/scripts/linux/screenshots/ChatView_light.png b/scripts/linux/screenshots/ChatView_light.png
index 2701dbb67de..35548ab92fa 100644
Binary files a/scripts/linux/screenshots/ChatView_light.png and b/scripts/linux/screenshots/ChatView_light.png differ
diff --git a/scripts/linux/screenshots/CheckBoxRadioTheme_dark.png b/scripts/linux/screenshots/CheckBoxRadioTheme_dark.png
index b279ea8bfad..2f9d3b3e700 100644
Binary files a/scripts/linux/screenshots/CheckBoxRadioTheme_dark.png and b/scripts/linux/screenshots/CheckBoxRadioTheme_dark.png differ
diff --git a/scripts/linux/screenshots/CheckBoxRadioTheme_light.png b/scripts/linux/screenshots/CheckBoxRadioTheme_light.png
index 11143caacf9..34554c51e19 100644
Binary files a/scripts/linux/screenshots/CheckBoxRadioTheme_light.png and b/scripts/linux/screenshots/CheckBoxRadioTheme_light.png differ
diff --git a/scripts/linux/screenshots/CodeEditor.png b/scripts/linux/screenshots/CodeEditor.png
index a925ffc98ae..8efa91dd1ac 100644
Binary files a/scripts/linux/screenshots/CodeEditor.png and b/scripts/linux/screenshots/CodeEditor.png differ
diff --git a/scripts/linux/screenshots/ComponentReplaceFadeScreenshotTest.png b/scripts/linux/screenshots/ComponentReplaceFadeScreenshotTest.png
index 410a3a9949b..28fd7fda472 100644
Binary files a/scripts/linux/screenshots/ComponentReplaceFadeScreenshotTest.png and b/scripts/linux/screenshots/ComponentReplaceFadeScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/ComponentReplaceFlipScreenshotTest.png b/scripts/linux/screenshots/ComponentReplaceFlipScreenshotTest.png
index 6769300b8a3..e7335579f6f 100644
Binary files a/scripts/linux/screenshots/ComponentReplaceFlipScreenshotTest.png and b/scripts/linux/screenshots/ComponentReplaceFlipScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/ComponentReplaceSlideScreenshotTest.png b/scripts/linux/screenshots/ComponentReplaceSlideScreenshotTest.png
index 3886b92613d..84fae211596 100644
Binary files a/scripts/linux/screenshots/ComponentReplaceSlideScreenshotTest.png and b/scripts/linux/screenshots/ComponentReplaceSlideScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/CoverHorizontalTransitionTest.png b/scripts/linux/screenshots/CoverHorizontalTransitionTest.png
index 570c97925c4..d662ce34838 100644
Binary files a/scripts/linux/screenshots/CoverHorizontalTransitionTest.png and b/scripts/linux/screenshots/CoverHorizontalTransitionTest.png differ
diff --git a/scripts/linux/screenshots/DesktopChromeTheme_dark.png b/scripts/linux/screenshots/DesktopChromeTheme_dark.png
new file mode 100644
index 00000000000..ff0971083d8
Binary files /dev/null and b/scripts/linux/screenshots/DesktopChromeTheme_dark.png differ
diff --git a/scripts/linux/screenshots/DesktopChromeTheme_light.png b/scripts/linux/screenshots/DesktopChromeTheme_light.png
new file mode 100644
index 00000000000..5c093becc96
Binary files /dev/null and b/scripts/linux/screenshots/DesktopChromeTheme_light.png differ
diff --git a/scripts/linux/screenshots/DesktopMode.png b/scripts/linux/screenshots/DesktopMode.png
index 80de41abb5c..24e20f4d8ab 100644
Binary files a/scripts/linux/screenshots/DesktopMode.png and b/scripts/linux/screenshots/DesktopMode.png differ
diff --git a/scripts/linux/screenshots/DesktopScrollbarTheme_dark.png b/scripts/linux/screenshots/DesktopScrollbarTheme_dark.png
new file mode 100644
index 00000000000..1f6d474b2eb
Binary files /dev/null and b/scripts/linux/screenshots/DesktopScrollbarTheme_dark.png differ
diff --git a/scripts/linux/screenshots/DesktopScrollbarTheme_light.png b/scripts/linux/screenshots/DesktopScrollbarTheme_light.png
new file mode 100644
index 00000000000..6287e13c63b
Binary files /dev/null and b/scripts/linux/screenshots/DesktopScrollbarTheme_light.png differ
diff --git a/scripts/linux/screenshots/DesktopWidgetsTheme_dark.png b/scripts/linux/screenshots/DesktopWidgetsTheme_dark.png
new file mode 100644
index 00000000000..dfa1a37e8cf
Binary files /dev/null and b/scripts/linux/screenshots/DesktopWidgetsTheme_dark.png differ
diff --git a/scripts/linux/screenshots/DesktopWidgetsTheme_light.png b/scripts/linux/screenshots/DesktopWidgetsTheme_light.png
new file mode 100644
index 00000000000..17a318249f7
Binary files /dev/null and b/scripts/linux/screenshots/DesktopWidgetsTheme_light.png differ
diff --git a/scripts/linux/screenshots/DialogTheme_dark.png b/scripts/linux/screenshots/DialogTheme_dark.png
index 396d29bc899..b7ce57c801b 100644
Binary files a/scripts/linux/screenshots/DialogTheme_dark.png and b/scripts/linux/screenshots/DialogTheme_dark.png differ
diff --git a/scripts/linux/screenshots/DialogTheme_light.png b/scripts/linux/screenshots/DialogTheme_light.png
index 0028650c211..e8445729709 100644
Binary files a/scripts/linux/screenshots/DialogTheme_light.png and b/scripts/linux/screenshots/DialogTheme_light.png differ
diff --git a/scripts/linux/screenshots/FadeTransitionTest.png b/scripts/linux/screenshots/FadeTransitionTest.png
index 93bd6529b76..fac0f7bc4e7 100644
Binary files a/scripts/linux/screenshots/FadeTransitionTest.png and b/scripts/linux/screenshots/FadeTransitionTest.png differ
diff --git a/scripts/linux/screenshots/FlipTransitionTest.png b/scripts/linux/screenshots/FlipTransitionTest.png
index ee4a4e58c74..7b2c0332691 100644
Binary files a/scripts/linux/screenshots/FlipTransitionTest.png and b/scripts/linux/screenshots/FlipTransitionTest.png differ
diff --git a/scripts/linux/screenshots/FloatingActionButtonTheme_dark.png b/scripts/linux/screenshots/FloatingActionButtonTheme_dark.png
index b591b96b3f9..225e2ab2afe 100644
Binary files a/scripts/linux/screenshots/FloatingActionButtonTheme_dark.png and b/scripts/linux/screenshots/FloatingActionButtonTheme_dark.png differ
diff --git a/scripts/linux/screenshots/FloatingActionButtonTheme_light.png b/scripts/linux/screenshots/FloatingActionButtonTheme_light.png
index 688dbbaba39..3ca92da79ac 100644
Binary files a/scripts/linux/screenshots/FloatingActionButtonTheme_light.png and b/scripts/linux/screenshots/FloatingActionButtonTheme_light.png differ
diff --git a/scripts/linux/screenshots/Gpu3DAnimation.png b/scripts/linux/screenshots/Gpu3DAnimation.png
index b3462d165cf..fc74f7b9c79 100644
Binary files a/scripts/linux/screenshots/Gpu3DAnimation.png and b/scripts/linux/screenshots/Gpu3DAnimation.png differ
diff --git a/scripts/linux/screenshots/Gpu3DCube.png b/scripts/linux/screenshots/Gpu3DCube.png
index 368dd151791..3ee9a456285 100644
Binary files a/scripts/linux/screenshots/Gpu3DCube.png and b/scripts/linux/screenshots/Gpu3DCube.png differ
diff --git a/scripts/linux/screenshots/Gpu3DModel.png b/scripts/linux/screenshots/Gpu3DModel.png
index 503a7e641ce..5547f4f3d64 100644
Binary files a/scripts/linux/screenshots/Gpu3DModel.png and b/scripts/linux/screenshots/Gpu3DModel.png differ
diff --git a/scripts/linux/screenshots/Gpu3DTexturedCube.png b/scripts/linux/screenshots/Gpu3DTexturedCube.png
index b861b698e47..035311fccf8 100644
Binary files a/scripts/linux/screenshots/Gpu3DTexturedCube.png and b/scripts/linux/screenshots/Gpu3DTexturedCube.png differ
diff --git a/scripts/linux/screenshots/ImageViewerNavigationModes.png b/scripts/linux/screenshots/ImageViewerNavigationModes.png
index b4ba09ead65..e708a0daebb 100644
Binary files a/scripts/linux/screenshots/ImageViewerNavigationModes.png and b/scripts/linux/screenshots/ImageViewerNavigationModes.png differ
diff --git a/scripts/linux/screenshots/LightweightPickerButtons.png b/scripts/linux/screenshots/LightweightPickerButtons.png
index ddd0150606c..b8c07274cf0 100644
Binary files a/scripts/linux/screenshots/LightweightPickerButtons.png and b/scripts/linux/screenshots/LightweightPickerButtons.png differ
diff --git a/scripts/linux/screenshots/LightweightPickerButtons_above_center.png b/scripts/linux/screenshots/LightweightPickerButtons_above_center.png
index 5c36c07d1be..4c2a63b0af7 100644
Binary files a/scripts/linux/screenshots/LightweightPickerButtons_above_center.png and b/scripts/linux/screenshots/LightweightPickerButtons_above_center.png differ
diff --git a/scripts/linux/screenshots/LightweightPickerButtons_below_right.png b/scripts/linux/screenshots/LightweightPickerButtons_below_right.png
index c8fec83079d..6280a3e030f 100644
Binary files a/scripts/linux/screenshots/LightweightPickerButtons_below_right.png and b/scripts/linux/screenshots/LightweightPickerButtons_below_right.png differ
diff --git a/scripts/linux/screenshots/LightweightPickerButtons_between_mixed.png b/scripts/linux/screenshots/LightweightPickerButtons_between_mixed.png
index c7e55871cba..a028a08663f 100644
Binary files a/scripts/linux/screenshots/LightweightPickerButtons_between_mixed.png and b/scripts/linux/screenshots/LightweightPickerButtons_between_mixed.png differ
diff --git a/scripts/linux/screenshots/ListTheme_dark.png b/scripts/linux/screenshots/ListTheme_dark.png
index c2fc2d8c49f..81a016b806b 100644
Binary files a/scripts/linux/screenshots/ListTheme_dark.png and b/scripts/linux/screenshots/ListTheme_dark.png differ
diff --git a/scripts/linux/screenshots/ListTheme_light.png b/scripts/linux/screenshots/ListTheme_light.png
index b5a7eb2daf9..03f69a9efd1 100644
Binary files a/scripts/linux/screenshots/ListTheme_light.png and b/scripts/linux/screenshots/ListTheme_light.png differ
diff --git a/scripts/linux/screenshots/MainActivity.png b/scripts/linux/screenshots/MainActivity.png
index b1768dff946..9878f2bbf9e 100644
Binary files a/scripts/linux/screenshots/MainActivity.png and b/scripts/linux/screenshots/MainActivity.png differ
diff --git a/scripts/linux/screenshots/Media360Panorama.png b/scripts/linux/screenshots/Media360Panorama.png
index 982fff0ebdb..2d41facfad6 100644
Binary files a/scripts/linux/screenshots/Media360Panorama.png and b/scripts/linux/screenshots/Media360Panorama.png differ
diff --git a/scripts/linux/screenshots/MediaPlayback.png b/scripts/linux/screenshots/MediaPlayback.png
index 352c16e0fe9..32c2ef9f809 100644
Binary files a/scripts/linux/screenshots/MediaPlayback.png and b/scripts/linux/screenshots/MediaPlayback.png differ
diff --git a/scripts/linux/screenshots/MorphElementMorphScreenshotTest.png b/scripts/linux/screenshots/MorphElementMorphScreenshotTest.png
index ffe77e2890e..8048cadf0f7 100644
Binary files a/scripts/linux/screenshots/MorphElementMorphScreenshotTest.png and b/scripts/linux/screenshots/MorphElementMorphScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/MorphTransitionScrolledSourceTest.png b/scripts/linux/screenshots/MorphTransitionScrolledSourceTest.png
index 5136d1e9695..545de89cf7c 100644
Binary files a/scripts/linux/screenshots/MorphTransitionScrolledSourceTest.png and b/scripts/linux/screenshots/MorphTransitionScrolledSourceTest.png differ
diff --git a/scripts/linux/screenshots/MorphTransitionScrubScreenshotTest.png b/scripts/linux/screenshots/MorphTransitionScrubScreenshotTest.png
index 0ce34fbfa36..84555b5f58b 100644
Binary files a/scripts/linux/screenshots/MorphTransitionScrubScreenshotTest.png and b/scripts/linux/screenshots/MorphTransitionScrubScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/MorphTransitionSnapshotTest.png b/scripts/linux/screenshots/MorphTransitionSnapshotTest.png
index 938b97be008..1aae256bb4f 100644
Binary files a/scripts/linux/screenshots/MorphTransitionSnapshotTest.png and b/scripts/linux/screenshots/MorphTransitionSnapshotTest.png differ
diff --git a/scripts/linux/screenshots/MorphTransitionTest.png b/scripts/linux/screenshots/MorphTransitionTest.png
index f516ba73ea3..b630e74705a 100644
Binary files a/scripts/linux/screenshots/MorphTransitionTest.png and b/scripts/linux/screenshots/MorphTransitionTest.png differ
diff --git a/scripts/linux/screenshots/MultiButtonTheme_dark.png b/scripts/linux/screenshots/MultiButtonTheme_dark.png
index 5e56a3d0338..8129b3e6165 100644
Binary files a/scripts/linux/screenshots/MultiButtonTheme_dark.png and b/scripts/linux/screenshots/MultiButtonTheme_dark.png differ
diff --git a/scripts/linux/screenshots/MultiButtonTheme_light.png b/scripts/linux/screenshots/MultiButtonTheme_light.png
index 575e25bc9e7..c6edd72ea0f 100644
Binary files a/scripts/linux/screenshots/MultiButtonTheme_light.png and b/scripts/linux/screenshots/MultiButtonTheme_light.png differ
diff --git a/scripts/linux/screenshots/NativeMapFallback.png b/scripts/linux/screenshots/NativeMapFallback.png
index 05ff8bb4352..e3f77019074 100644
Binary files a/scripts/linux/screenshots/NativeMapFallback.png and b/scripts/linux/screenshots/NativeMapFallback.png differ
diff --git a/scripts/linux/screenshots/PaletteOverrideTheme_dark.png b/scripts/linux/screenshots/PaletteOverrideTheme_dark.png
index b3655ff54ed..c6bb9b1f6d7 100644
Binary files a/scripts/linux/screenshots/PaletteOverrideTheme_dark.png and b/scripts/linux/screenshots/PaletteOverrideTheme_dark.png differ
diff --git a/scripts/linux/screenshots/PaletteOverrideTheme_light.png b/scripts/linux/screenshots/PaletteOverrideTheme_light.png
index e6f3b025a39..3b33ed6aa9e 100644
Binary files a/scripts/linux/screenshots/PaletteOverrideTheme_light.png and b/scripts/linux/screenshots/PaletteOverrideTheme_light.png differ
diff --git a/scripts/linux/screenshots/PickerTheme_dark.png b/scripts/linux/screenshots/PickerTheme_dark.png
index d11e486ed8c..d4638e785e2 100644
Binary files a/scripts/linux/screenshots/PickerTheme_dark.png and b/scripts/linux/screenshots/PickerTheme_dark.png differ
diff --git a/scripts/linux/screenshots/PickerTheme_light.png b/scripts/linux/screenshots/PickerTheme_light.png
index ccb184e468b..28a8dd03e02 100644
Binary files a/scripts/linux/screenshots/PickerTheme_light.png and b/scripts/linux/screenshots/PickerTheme_light.png differ
diff --git a/scripts/linux/screenshots/PullToRefreshSpinnerScreenshotTest.png b/scripts/linux/screenshots/PullToRefreshSpinnerScreenshotTest.png
index 73572440bf0..5199f2b712f 100644
Binary files a/scripts/linux/screenshots/PullToRefreshSpinnerScreenshotTest.png and b/scripts/linux/screenshots/PullToRefreshSpinnerScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/PureEditors.png b/scripts/linux/screenshots/PureEditors.png
index 253b46841a7..06568a21a73 100644
Binary files a/scripts/linux/screenshots/PureEditors.png and b/scripts/linux/screenshots/PureEditors.png differ
diff --git a/scripts/linux/screenshots/RealOsmVector.png b/scripts/linux/screenshots/RealOsmVector.png
index 6811e714b88..8c803a53375 100644
Binary files a/scripts/linux/screenshots/RealOsmVector.png and b/scripts/linux/screenshots/RealOsmVector.png differ
diff --git a/scripts/linux/screenshots/RichTextArea.png b/scripts/linux/screenshots/RichTextArea.png
index 3ff29bfdc61..fbbecc00c51 100644
Binary files a/scripts/linux/screenshots/RichTextArea.png and b/scripts/linux/screenshots/RichTextArea.png differ
diff --git a/scripts/linux/screenshots/SVGStatic.png b/scripts/linux/screenshots/SVGStatic.png
index 200c513a26b..fe7d0a7300b 100644
Binary files a/scripts/linux/screenshots/SVGStatic.png and b/scripts/linux/screenshots/SVGStatic.png differ
diff --git a/scripts/linux/screenshots/Sheet.png b/scripts/linux/screenshots/Sheet.png
index e37da0c58d5..776cac56b60 100644
Binary files a/scripts/linux/screenshots/Sheet.png and b/scripts/linux/screenshots/Sheet.png differ
diff --git a/scripts/linux/screenshots/SheetSlideUpAnimationScreenshotTest.png b/scripts/linux/screenshots/SheetSlideUpAnimationScreenshotTest.png
index a50aea1b9d3..8324331f2cf 100644
Binary files a/scripts/linux/screenshots/SheetSlideUpAnimationScreenshotTest.png and b/scripts/linux/screenshots/SheetSlideUpAnimationScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/ShowcaseTheme_dark.png b/scripts/linux/screenshots/ShowcaseTheme_dark.png
index ab121be0901..525fac70b67 100644
Binary files a/scripts/linux/screenshots/ShowcaseTheme_dark.png and b/scripts/linux/screenshots/ShowcaseTheme_dark.png differ
diff --git a/scripts/linux/screenshots/ShowcaseTheme_light.png b/scripts/linux/screenshots/ShowcaseTheme_light.png
index 4e66f57d6fb..11e2b20c96c 100644
Binary files a/scripts/linux/screenshots/ShowcaseTheme_light.png and b/scripts/linux/screenshots/ShowcaseTheme_light.png differ
diff --git a/scripts/linux/screenshots/SlideFadeTitleTransitionTest.png b/scripts/linux/screenshots/SlideFadeTitleTransitionTest.png
index 6ccc3ea8f0d..5c5348c60f2 100644
Binary files a/scripts/linux/screenshots/SlideFadeTitleTransitionTest.png and b/scripts/linux/screenshots/SlideFadeTitleTransitionTest.png differ
diff --git a/scripts/linux/screenshots/SlideHorizontalBackTransitionTest.png b/scripts/linux/screenshots/SlideHorizontalBackTransitionTest.png
index f69ac0abf5d..87bfbb6edb1 100644
Binary files a/scripts/linux/screenshots/SlideHorizontalBackTransitionTest.png and b/scripts/linux/screenshots/SlideHorizontalBackTransitionTest.png differ
diff --git a/scripts/linux/screenshots/SlideHorizontalTransitionTest.png b/scripts/linux/screenshots/SlideHorizontalTransitionTest.png
index a9bdec50e4a..d40e200ad4e 100644
Binary files a/scripts/linux/screenshots/SlideHorizontalTransitionTest.png and b/scripts/linux/screenshots/SlideHorizontalTransitionTest.png differ
diff --git a/scripts/linux/screenshots/SlideVerticalTransitionTest.png b/scripts/linux/screenshots/SlideVerticalTransitionTest.png
index 0448196eb79..eaed501bf08 100644
Binary files a/scripts/linux/screenshots/SlideVerticalTransitionTest.png and b/scripts/linux/screenshots/SlideVerticalTransitionTest.png differ
diff --git a/scripts/linux/screenshots/SmoothScrollScreenshotTest.png b/scripts/linux/screenshots/SmoothScrollScreenshotTest.png
index 259ab0ff131..0b4caba0d79 100644
Binary files a/scripts/linux/screenshots/SmoothScrollScreenshotTest.png and b/scripts/linux/screenshots/SmoothScrollScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/SpanLabelTheme_dark.png b/scripts/linux/screenshots/SpanLabelTheme_dark.png
index f67a2c2ab2c..c367a1ef1a0 100644
Binary files a/scripts/linux/screenshots/SpanLabelTheme_dark.png and b/scripts/linux/screenshots/SpanLabelTheme_dark.png differ
diff --git a/scripts/linux/screenshots/SpanLabelTheme_light.png b/scripts/linux/screenshots/SpanLabelTheme_light.png
index b6910f1182e..ff4484ad2f1 100644
Binary files a/scripts/linux/screenshots/SpanLabelTheme_light.png and b/scripts/linux/screenshots/SpanLabelTheme_light.png differ
diff --git a/scripts/linux/screenshots/StatusBarTapDiagnosticScreenshotTest.png b/scripts/linux/screenshots/StatusBarTapDiagnosticScreenshotTest.png
index d7c7f8181c6..9598bc783a4 100644
Binary files a/scripts/linux/screenshots/StatusBarTapDiagnosticScreenshotTest.png and b/scripts/linux/screenshots/StatusBarTapDiagnosticScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/StickyHeaderFadeTransitionScreenshotTest.png b/scripts/linux/screenshots/StickyHeaderFadeTransitionScreenshotTest.png
index 4fdd0534b5c..497f2546f92 100644
Binary files a/scripts/linux/screenshots/StickyHeaderFadeTransitionScreenshotTest.png and b/scripts/linux/screenshots/StickyHeaderFadeTransitionScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/StickyHeaderScreenshotTest.png b/scripts/linux/screenshots/StickyHeaderScreenshotTest.png
index 9f4dd396d1c..6b94f2a8b02 100644
Binary files a/scripts/linux/screenshots/StickyHeaderScreenshotTest.png and b/scripts/linux/screenshots/StickyHeaderScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/StickyHeaderSlideTransitionScreenshotTest.png b/scripts/linux/screenshots/StickyHeaderSlideTransitionScreenshotTest.png
index ed1fdab9d1d..cca0abbbed1 100644
Binary files a/scripts/linux/screenshots/StickyHeaderSlideTransitionScreenshotTest.png and b/scripts/linux/screenshots/StickyHeaderSlideTransitionScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/SurfacesRasterizer.png b/scripts/linux/screenshots/SurfacesRasterizer.png
index e40cc72a28a..a36c11e02d9 100644
Binary files a/scripts/linux/screenshots/SurfacesRasterizer.png and b/scripts/linux/screenshots/SurfacesRasterizer.png differ
diff --git a/scripts/linux/screenshots/SwitchTheme_dark.png b/scripts/linux/screenshots/SwitchTheme_dark.png
index f272c2af4c0..16b4d9a150e 100644
Binary files a/scripts/linux/screenshots/SwitchTheme_dark.png and b/scripts/linux/screenshots/SwitchTheme_dark.png differ
diff --git a/scripts/linux/screenshots/SwitchTheme_light.png b/scripts/linux/screenshots/SwitchTheme_light.png
index 99cf1fd94f5..2bdecee3e2a 100644
Binary files a/scripts/linux/screenshots/SwitchTheme_light.png and b/scripts/linux/screenshots/SwitchTheme_light.png differ
diff --git a/scripts/linux/screenshots/TabsAnimatedIndicatorScreenshotTest.png b/scripts/linux/screenshots/TabsAnimatedIndicatorScreenshotTest.png
index 3730d694fac..ebc05b7fd24 100644
Binary files a/scripts/linux/screenshots/TabsAnimatedIndicatorScreenshotTest.png and b/scripts/linux/screenshots/TabsAnimatedIndicatorScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/TabsBehavior.png b/scripts/linux/screenshots/TabsBehavior.png
index 8d6ff2c9465..d88fde9d262 100644
Binary files a/scripts/linux/screenshots/TabsBehavior.png and b/scripts/linux/screenshots/TabsBehavior.png differ
diff --git a/scripts/linux/screenshots/TabsTheme_dark.png b/scripts/linux/screenshots/TabsTheme_dark.png
index cd243ceabf3..51c63bb1040 100644
Binary files a/scripts/linux/screenshots/TabsTheme_dark.png and b/scripts/linux/screenshots/TabsTheme_dark.png differ
diff --git a/scripts/linux/screenshots/TabsTheme_light.png b/scripts/linux/screenshots/TabsTheme_light.png
index c5b51b10989..037607e3ce3 100644
Binary files a/scripts/linux/screenshots/TabsTheme_light.png and b/scripts/linux/screenshots/TabsTheme_light.png differ
diff --git a/scripts/linux/screenshots/TensileBounceScreenshotTest.png b/scripts/linux/screenshots/TensileBounceScreenshotTest.png
index 42532fc0623..fa99d9076a6 100644
Binary files a/scripts/linux/screenshots/TensileBounceScreenshotTest.png and b/scripts/linux/screenshots/TensileBounceScreenshotTest.png differ
diff --git a/scripts/linux/screenshots/TextAreaAlignmentStates.png b/scripts/linux/screenshots/TextAreaAlignmentStates.png
index d3f63336c25..5606ffa9cd3 100644
Binary files a/scripts/linux/screenshots/TextAreaAlignmentStates.png and b/scripts/linux/screenshots/TextAreaAlignmentStates.png differ
diff --git a/scripts/linux/screenshots/TextFieldTheme_dark.png b/scripts/linux/screenshots/TextFieldTheme_dark.png
index fd9dbbd6f94..c759c44de3e 100644
Binary files a/scripts/linux/screenshots/TextFieldTheme_dark.png and b/scripts/linux/screenshots/TextFieldTheme_dark.png differ
diff --git a/scripts/linux/screenshots/TextFieldTheme_light.png b/scripts/linux/screenshots/TextFieldTheme_light.png
index bd15ec555f8..a81074e3eb3 100644
Binary files a/scripts/linux/screenshots/TextFieldTheme_light.png and b/scripts/linux/screenshots/TextFieldTheme_light.png differ
diff --git a/scripts/linux/screenshots/ToastBarTopPosition.png b/scripts/linux/screenshots/ToastBarTopPosition.png
index bb986498684..ae3acadee0b 100644
Binary files a/scripts/linux/screenshots/ToastBarTopPosition.png and b/scripts/linux/screenshots/ToastBarTopPosition.png differ
diff --git a/scripts/linux/screenshots/ToolbarTheme_dark.png b/scripts/linux/screenshots/ToolbarTheme_dark.png
index 5739c63b8a0..a8003cfd6b7 100644
Binary files a/scripts/linux/screenshots/ToolbarTheme_dark.png and b/scripts/linux/screenshots/ToolbarTheme_dark.png differ
diff --git a/scripts/linux/screenshots/ToolbarTheme_light.png b/scripts/linux/screenshots/ToolbarTheme_light.png
index 0541392c1d2..0c803c81e98 100644
Binary files a/scripts/linux/screenshots/ToolbarTheme_light.png and b/scripts/linux/screenshots/ToolbarTheme_light.png differ
diff --git a/scripts/linux/screenshots/UncoverHorizontalTransitionTest.png b/scripts/linux/screenshots/UncoverHorizontalTransitionTest.png
index 363250015ce..2dae992a488 100644
Binary files a/scripts/linux/screenshots/UncoverHorizontalTransitionTest.png and b/scripts/linux/screenshots/UncoverHorizontalTransitionTest.png differ
diff --git a/scripts/linux/screenshots/VRStereoScene.png b/scripts/linux/screenshots/VRStereoScene.png
index 0f0b02c0462..212ff7d5102 100644
Binary files a/scripts/linux/screenshots/VRStereoScene.png and b/scripts/linux/screenshots/VRStereoScene.png differ
diff --git a/scripts/linux/screenshots/ValidatorLightweightPicker.png b/scripts/linux/screenshots/ValidatorLightweightPicker.png
index 13787e143d7..116c6323f43 100644
Binary files a/scripts/linux/screenshots/ValidatorLightweightPicker.png and b/scripts/linux/screenshots/ValidatorLightweightPicker.png differ
diff --git a/scripts/linux/screenshots/VectorMapDarkStyle.png b/scripts/linux/screenshots/VectorMapDarkStyle.png
index ec9ae60d5f5..2749875cb6f 100644
Binary files a/scripts/linux/screenshots/VectorMapDarkStyle.png and b/scripts/linux/screenshots/VectorMapDarkStyle.png differ
diff --git a/scripts/linux/screenshots/VectorMapMarkers.png b/scripts/linux/screenshots/VectorMapMarkers.png
index ffb2351b48c..629d51e0cd7 100644
Binary files a/scripts/linux/screenshots/VectorMapMarkers.png and b/scripts/linux/screenshots/VectorMapMarkers.png differ
diff --git a/scripts/linux/screenshots/VectorMapShapes.png b/scripts/linux/screenshots/VectorMapShapes.png
index 49f8979c64f..880e7c64cbf 100644
Binary files a/scripts/linux/screenshots/VectorMapShapes.png and b/scripts/linux/screenshots/VectorMapShapes.png differ
diff --git a/scripts/linux/screenshots/Window-Dialog-1000x400.png b/scripts/linux/screenshots/Window-Dialog-1000x400.png
index 8cffac9bf3e..f52adee6a9e 100644
Binary files a/scripts/linux/screenshots/Window-Dialog-1000x400.png and b/scripts/linux/screenshots/Window-Dialog-1000x400.png differ
diff --git a/scripts/linux/screenshots/Window-Dialog-400x300.png b/scripts/linux/screenshots/Window-Dialog-400x300.png
index c45f579cedd..8fbd037f328 100644
Binary files a/scripts/linux/screenshots/Window-Dialog-400x300.png and b/scripts/linux/screenshots/Window-Dialog-400x300.png differ
diff --git a/scripts/linux/screenshots/Window-Dialog-900x700.png b/scripts/linux/screenshots/Window-Dialog-900x700.png
index fa496bc0d22..c520187fad5 100644
Binary files a/scripts/linux/screenshots/Window-Dialog-900x700.png and b/scripts/linux/screenshots/Window-Dialog-900x700.png differ
diff --git a/scripts/linux/screenshots/Window-Editing-1000x400.png b/scripts/linux/screenshots/Window-Editing-1000x400.png
index 6829fee4c5f..80c18e4b88f 100644
Binary files a/scripts/linux/screenshots/Window-Editing-1000x400.png and b/scripts/linux/screenshots/Window-Editing-1000x400.png differ
diff --git a/scripts/linux/screenshots/Window-Editing-400x300.png b/scripts/linux/screenshots/Window-Editing-400x300.png
index 110bd7bdb09..d4f007a0208 100644
Binary files a/scripts/linux/screenshots/Window-Editing-400x300.png and b/scripts/linux/screenshots/Window-Editing-400x300.png differ
diff --git a/scripts/linux/screenshots/Window-Editing-900x700.png b/scripts/linux/screenshots/Window-Editing-900x700.png
index 998c9e033f6..e38de325fda 100644
Binary files a/scripts/linux/screenshots/Window-Editing-900x700.png and b/scripts/linux/screenshots/Window-Editing-900x700.png differ
diff --git a/scripts/linux/screenshots/Window-Graphics-1000x400.png b/scripts/linux/screenshots/Window-Graphics-1000x400.png
index d9ca21c7707..05e161ffb57 100644
Binary files a/scripts/linux/screenshots/Window-Graphics-1000x400.png and b/scripts/linux/screenshots/Window-Graphics-1000x400.png differ
diff --git a/scripts/linux/screenshots/Window-Graphics-400x300.png b/scripts/linux/screenshots/Window-Graphics-400x300.png
index 239629b938a..ad13fbbb7bc 100644
Binary files a/scripts/linux/screenshots/Window-Graphics-400x300.png and b/scripts/linux/screenshots/Window-Graphics-400x300.png differ
diff --git a/scripts/linux/screenshots/Window-Graphics-900x700.png b/scripts/linux/screenshots/Window-Graphics-900x700.png
index a2f0d8c13de..80bbfba6d04 100644
Binary files a/scripts/linux/screenshots/Window-Graphics-900x700.png and b/scripts/linux/screenshots/Window-Graphics-900x700.png differ
diff --git a/scripts/linux/screenshots/Window-Layout-1000x400.png b/scripts/linux/screenshots/Window-Layout-1000x400.png
index 666adc85253..486a9f14866 100644
Binary files a/scripts/linux/screenshots/Window-Layout-1000x400.png and b/scripts/linux/screenshots/Window-Layout-1000x400.png differ
diff --git a/scripts/linux/screenshots/Window-Layout-400x300.png b/scripts/linux/screenshots/Window-Layout-400x300.png
index 518d7788ff6..58d97bb884b 100644
Binary files a/scripts/linux/screenshots/Window-Layout-400x300.png and b/scripts/linux/screenshots/Window-Layout-400x300.png differ
diff --git a/scripts/linux/screenshots/Window-Layout-900x700.png b/scripts/linux/screenshots/Window-Layout-900x700.png
index de6bff86530..b2a1b5bac9e 100644
Binary files a/scripts/linux/screenshots/Window-Layout-900x700.png and b/scripts/linux/screenshots/Window-Layout-900x700.png differ
diff --git a/scripts/linux/screenshots/Window-Modal-background.png b/scripts/linux/screenshots/Window-Modal-background.png
index cb8bfc3d899..7ba9c5119f1 100644
Binary files a/scripts/linux/screenshots/Window-Modal-background.png and b/scripts/linux/screenshots/Window-Modal-background.png differ
diff --git a/scripts/linux/screenshots/Window-Overlay-600x450.png b/scripts/linux/screenshots/Window-Overlay-600x450.png
index 5d9f7c82fe4..eb9635dfc2e 100644
Binary files a/scripts/linux/screenshots/Window-Overlay-600x450.png and b/scripts/linux/screenshots/Window-Overlay-600x450.png differ
diff --git a/scripts/linux/screenshots/Window-Scroll-1000x400.png b/scripts/linux/screenshots/Window-Scroll-1000x400.png
index a6ad7a0b2b9..3c33787174f 100644
Binary files a/scripts/linux/screenshots/Window-Scroll-1000x400.png and b/scripts/linux/screenshots/Window-Scroll-1000x400.png differ
diff --git a/scripts/linux/screenshots/Window-Scroll-400x300.png b/scripts/linux/screenshots/Window-Scroll-400x300.png
index 49c2b58a893..fe85af37a2a 100644
Binary files a/scripts/linux/screenshots/Window-Scroll-400x300.png and b/scripts/linux/screenshots/Window-Scroll-400x300.png differ
diff --git a/scripts/linux/screenshots/Window-Scroll-900x700.png b/scripts/linux/screenshots/Window-Scroll-900x700.png
index 51412c91d0e..61ea0d4afc8 100644
Binary files a/scripts/linux/screenshots/Window-Scroll-900x700.png and b/scripts/linux/screenshots/Window-Scroll-900x700.png differ
diff --git a/scripts/linux/screenshots/chart-bar-stacked.png b/scripts/linux/screenshots/chart-bar-stacked.png
index 4865f7f9f5c..12200036335 100644
Binary files a/scripts/linux/screenshots/chart-bar-stacked.png and b/scripts/linux/screenshots/chart-bar-stacked.png differ
diff --git a/scripts/linux/screenshots/chart-bar.png b/scripts/linux/screenshots/chart-bar.png
index 04b6ddd24b6..072d544484e 100644
Binary files a/scripts/linux/screenshots/chart-bar.png and b/scripts/linux/screenshots/chart-bar.png differ
diff --git a/scripts/linux/screenshots/chart-bubble.png b/scripts/linux/screenshots/chart-bubble.png
index 39119420cb5..8f48bf837e2 100644
Binary files a/scripts/linux/screenshots/chart-bubble.png and b/scripts/linux/screenshots/chart-bubble.png differ
diff --git a/scripts/linux/screenshots/chart-combined-xy.png b/scripts/linux/screenshots/chart-combined-xy.png
index 8084dbbe62b..4155e8d8a6e 100644
Binary files a/scripts/linux/screenshots/chart-combined-xy.png and b/scripts/linux/screenshots/chart-combined-xy.png differ
diff --git a/scripts/linux/screenshots/chart-cubic-line.png b/scripts/linux/screenshots/chart-cubic-line.png
index eb348b6c615..8fda6321718 100644
Binary files a/scripts/linux/screenshots/chart-cubic-line.png and b/scripts/linux/screenshots/chart-cubic-line.png differ
diff --git a/scripts/linux/screenshots/chart-doughnut.png b/scripts/linux/screenshots/chart-doughnut.png
index b81bce19b06..06461fe2593 100644
Binary files a/scripts/linux/screenshots/chart-doughnut.png and b/scripts/linux/screenshots/chart-doughnut.png differ
diff --git a/scripts/linux/screenshots/chart-line.png b/scripts/linux/screenshots/chart-line.png
index ba4df24c155..3dcacc29c9a 100644
Binary files a/scripts/linux/screenshots/chart-line.png and b/scripts/linux/screenshots/chart-line.png differ
diff --git a/scripts/linux/screenshots/chart-pie.png b/scripts/linux/screenshots/chart-pie.png
index 45079071073..1205719c5aa 100644
Binary files a/scripts/linux/screenshots/chart-pie.png and b/scripts/linux/screenshots/chart-pie.png differ
diff --git a/scripts/linux/screenshots/chart-radar.png b/scripts/linux/screenshots/chart-radar.png
index 3637aa1fb5c..9047243f995 100644
Binary files a/scripts/linux/screenshots/chart-radar.png and b/scripts/linux/screenshots/chart-radar.png differ
diff --git a/scripts/linux/screenshots/chart-range-bar.png b/scripts/linux/screenshots/chart-range-bar.png
index abd9732d0c8..96155b2a0a5 100644
Binary files a/scripts/linux/screenshots/chart-range-bar.png and b/scripts/linux/screenshots/chart-range-bar.png differ
diff --git a/scripts/linux/screenshots/chart-rotated-pie.png b/scripts/linux/screenshots/chart-rotated-pie.png
index 6c299f87bc7..0ecb0ea39a6 100644
Binary files a/scripts/linux/screenshots/chart-rotated-pie.png and b/scripts/linux/screenshots/chart-rotated-pie.png differ
diff --git a/scripts/linux/screenshots/chart-scatter.png b/scripts/linux/screenshots/chart-scatter.png
index c469bcf0c0f..e566573c3a0 100644
Binary files a/scripts/linux/screenshots/chart-scatter.png and b/scripts/linux/screenshots/chart-scatter.png differ
diff --git a/scripts/linux/screenshots/chart-time.png b/scripts/linux/screenshots/chart-time.png
index 2c5022daa94..4f82f1159f4 100644
Binary files a/scripts/linux/screenshots/chart-time.png and b/scripts/linux/screenshots/chart-time.png differ
diff --git a/scripts/linux/screenshots/chart-transform.png b/scripts/linux/screenshots/chart-transform.png
index 7e02828c1d6..705837fa8ca 100644
Binary files a/scripts/linux/screenshots/chart-transform.png and b/scripts/linux/screenshots/chart-transform.png differ
diff --git a/scripts/linux/screenshots/css-gradients.png b/scripts/linux/screenshots/css-gradients.png
index 8501c1b7ab0..502a201f078 100644
Binary files a/scripts/linux/screenshots/css-gradients.png and b/scripts/linux/screenshots/css-gradients.png differ
diff --git a/scripts/linux/screenshots/graphics-affine-scale.png b/scripts/linux/screenshots/graphics-affine-scale.png
index f6278ce5e9b..0a0b571c74f 100644
Binary files a/scripts/linux/screenshots/graphics-affine-scale.png and b/scripts/linux/screenshots/graphics-affine-scale.png differ
diff --git a/scripts/linux/screenshots/graphics-clip-under-rotation.png b/scripts/linux/screenshots/graphics-clip-under-rotation.png
index c4f69af73a0..ab7285b9e17 100644
Binary files a/scripts/linux/screenshots/graphics-clip-under-rotation.png and b/scripts/linux/screenshots/graphics-clip-under-rotation.png differ
diff --git a/scripts/linux/screenshots/graphics-clip.png b/scripts/linux/screenshots/graphics-clip.png
index b459244603f..c1fda205aff 100644
Binary files a/scripts/linux/screenshots/graphics-clip.png and b/scripts/linux/screenshots/graphics-clip.png differ
diff --git a/scripts/linux/screenshots/graphics-draw-arc.png b/scripts/linux/screenshots/graphics-draw-arc.png
index 80f535e093d..e040b283efe 100644
Binary files a/scripts/linux/screenshots/graphics-draw-arc.png and b/scripts/linux/screenshots/graphics-draw-arc.png differ
diff --git a/scripts/linux/screenshots/graphics-draw-gradient-stops.png b/scripts/linux/screenshots/graphics-draw-gradient-stops.png
index f893769f5bd..c26c7d5d536 100644
Binary files a/scripts/linux/screenshots/graphics-draw-gradient-stops.png and b/scripts/linux/screenshots/graphics-draw-gradient-stops.png differ
diff --git a/scripts/linux/screenshots/graphics-draw-gradient.png b/scripts/linux/screenshots/graphics-draw-gradient.png
index 6774b1aeec3..34945e0fbf2 100644
Binary files a/scripts/linux/screenshots/graphics-draw-gradient.png and b/scripts/linux/screenshots/graphics-draw-gradient.png differ
diff --git a/scripts/linux/screenshots/graphics-draw-image-rect.png b/scripts/linux/screenshots/graphics-draw-image-rect.png
index 3be1cba2c4d..62090cff610 100644
Binary files a/scripts/linux/screenshots/graphics-draw-image-rect.png and b/scripts/linux/screenshots/graphics-draw-image-rect.png differ
diff --git a/scripts/linux/screenshots/graphics-draw-line.png b/scripts/linux/screenshots/graphics-draw-line.png
index 61909b0b21c..74b18df1a32 100644
Binary files a/scripts/linux/screenshots/graphics-draw-line.png and b/scripts/linux/screenshots/graphics-draw-line.png differ
diff --git a/scripts/linux/screenshots/graphics-draw-rect.png b/scripts/linux/screenshots/graphics-draw-rect.png
index 0a1e7474c99..4c2a59c16e7 100644
Binary files a/scripts/linux/screenshots/graphics-draw-rect.png and b/scripts/linux/screenshots/graphics-draw-rect.png differ
diff --git a/scripts/linux/screenshots/graphics-draw-round-rect.png b/scripts/linux/screenshots/graphics-draw-round-rect.png
index 39b5ce5315c..d0e37a24a6a 100644
Binary files a/scripts/linux/screenshots/graphics-draw-round-rect.png and b/scripts/linux/screenshots/graphics-draw-round-rect.png differ
diff --git a/scripts/linux/screenshots/graphics-draw-shape.png b/scripts/linux/screenshots/graphics-draw-shape.png
index 1dd0016f6aa..743e0290ee2 100644
Binary files a/scripts/linux/screenshots/graphics-draw-shape.png and b/scripts/linux/screenshots/graphics-draw-shape.png differ
diff --git a/scripts/linux/screenshots/graphics-draw-string-decorated.png b/scripts/linux/screenshots/graphics-draw-string-decorated.png
index 8c1ce429e57..cd02e702b9f 100644
Binary files a/scripts/linux/screenshots/graphics-draw-string-decorated.png and b/scripts/linux/screenshots/graphics-draw-string-decorated.png differ
diff --git a/scripts/linux/screenshots/graphics-draw-string.png b/scripts/linux/screenshots/graphics-draw-string.png
index 3f955c6bdcc..622ac317ac3 100644
Binary files a/scripts/linux/screenshots/graphics-draw-string.png and b/scripts/linux/screenshots/graphics-draw-string.png differ
diff --git a/scripts/linux/screenshots/graphics-empty-clip.png b/scripts/linux/screenshots/graphics-empty-clip.png
index 8134902d1f0..342ac72d92b 100644
Binary files a/scripts/linux/screenshots/graphics-empty-clip.png and b/scripts/linux/screenshots/graphics-empty-clip.png differ
diff --git a/scripts/linux/screenshots/graphics-fill-arc.png b/scripts/linux/screenshots/graphics-fill-arc.png
index 64f66ba194b..fbff5963f4b 100644
Binary files a/scripts/linux/screenshots/graphics-fill-arc.png and b/scripts/linux/screenshots/graphics-fill-arc.png differ
diff --git a/scripts/linux/screenshots/graphics-fill-polygon.png b/scripts/linux/screenshots/graphics-fill-polygon.png
index 47e79b7b6bc..fb1f48f3e68 100644
Binary files a/scripts/linux/screenshots/graphics-fill-polygon.png and b/scripts/linux/screenshots/graphics-fill-polygon.png differ
diff --git a/scripts/linux/screenshots/graphics-fill-rect.png b/scripts/linux/screenshots/graphics-fill-rect.png
index 4cf6e7c4007..a7ac5121ed2 100644
Binary files a/scripts/linux/screenshots/graphics-fill-rect.png and b/scripts/linux/screenshots/graphics-fill-rect.png differ
diff --git a/scripts/linux/screenshots/graphics-fill-round-rect.png b/scripts/linux/screenshots/graphics-fill-round-rect.png
index 5e5f9884d7f..eddfb7a91c0 100644
Binary files a/scripts/linux/screenshots/graphics-fill-round-rect.png and b/scripts/linux/screenshots/graphics-fill-round-rect.png differ
diff --git a/scripts/linux/screenshots/graphics-fill-shape.png b/scripts/linux/screenshots/graphics-fill-shape.png
index f150b2bf7f8..81dd3cacdb9 100644
Binary files a/scripts/linux/screenshots/graphics-fill-shape.png and b/scripts/linux/screenshots/graphics-fill-shape.png differ
diff --git a/scripts/linux/screenshots/graphics-fill-triangle.png b/scripts/linux/screenshots/graphics-fill-triangle.png
index 8d5dc56ea00..92721e00f9f 100644
Binary files a/scripts/linux/screenshots/graphics-fill-triangle.png and b/scripts/linux/screenshots/graphics-fill-triangle.png differ
diff --git a/scripts/linux/screenshots/graphics-gaussian-blur.png b/scripts/linux/screenshots/graphics-gaussian-blur.png
index 80e24a83f61..a7f2d01f014 100644
Binary files a/scripts/linux/screenshots/graphics-gaussian-blur.png and b/scripts/linux/screenshots/graphics-gaussian-blur.png differ
diff --git a/scripts/linux/screenshots/graphics-inscribed-triangle-grid.png b/scripts/linux/screenshots/graphics-inscribed-triangle-grid.png
index 926b0c43208..550a8aabaa2 100644
Binary files a/scripts/linux/screenshots/graphics-inscribed-triangle-grid.png and b/scripts/linux/screenshots/graphics-inscribed-triangle-grid.png differ
diff --git a/scripts/linux/screenshots/graphics-large-stroke-dirty-clip.png b/scripts/linux/screenshots/graphics-large-stroke-dirty-clip.png
index d735a5dcde7..a211137fb5a 100644
Binary files a/scripts/linux/screenshots/graphics-large-stroke-dirty-clip.png and b/scripts/linux/screenshots/graphics-large-stroke-dirty-clip.png differ
diff --git a/scripts/linux/screenshots/graphics-partial-flush-clip-escape.png b/scripts/linux/screenshots/graphics-partial-flush-clip-escape.png
index b429276a374..5da60584147 100644
Binary files a/scripts/linux/screenshots/graphics-partial-flush-clip-escape.png and b/scripts/linux/screenshots/graphics-partial-flush-clip-escape.png differ
diff --git a/scripts/linux/screenshots/graphics-rotate.png b/scripts/linux/screenshots/graphics-rotate.png
index 3ea1f59e6c6..ca74621d88f 100644
Binary files a/scripts/linux/screenshots/graphics-rotate.png and b/scripts/linux/screenshots/graphics-rotate.png differ
diff --git a/scripts/linux/screenshots/graphics-scale.png b/scripts/linux/screenshots/graphics-scale.png
index 1b6026969f2..631a07fc533 100644
Binary files a/scripts/linux/screenshots/graphics-scale.png and b/scripts/linux/screenshots/graphics-scale.png differ
diff --git a/scripts/linux/screenshots/graphics-stroke-test.png b/scripts/linux/screenshots/graphics-stroke-test.png
index f5ff34b3e66..21e281d5cd7 100644
Binary files a/scripts/linux/screenshots/graphics-stroke-test.png and b/scripts/linux/screenshots/graphics-stroke-test.png differ
diff --git a/scripts/linux/screenshots/graphics-tile-image.png b/scripts/linux/screenshots/graphics-tile-image.png
index e22afbb2d74..b5a57fa5a70 100644
Binary files a/scripts/linux/screenshots/graphics-tile-image.png and b/scripts/linux/screenshots/graphics-tile-image.png differ
diff --git a/scripts/linux/screenshots/graphics-transform-camera.png b/scripts/linux/screenshots/graphics-transform-camera.png
index 71c08495468..410afdb0ac2 100644
Binary files a/scripts/linux/screenshots/graphics-transform-camera.png and b/scripts/linux/screenshots/graphics-transform-camera.png differ
diff --git a/scripts/linux/screenshots/graphics-transform-perspective.png b/scripts/linux/screenshots/graphics-transform-perspective.png
index 1f9440a8fa0..4e53f8085a5 100644
Binary files a/scripts/linux/screenshots/graphics-transform-perspective.png and b/scripts/linux/screenshots/graphics-transform-perspective.png differ
diff --git a/scripts/linux/screenshots/graphics-transform-rotation.png b/scripts/linux/screenshots/graphics-transform-rotation.png
index bc24eaccd3b..e662922f6ec 100644
Binary files a/scripts/linux/screenshots/graphics-transform-rotation.png and b/scripts/linux/screenshots/graphics-transform-rotation.png differ
diff --git a/scripts/linux/screenshots/graphics-transform-translation.png b/scripts/linux/screenshots/graphics-transform-translation.png
index edc7b651c34..f107a45e8f4 100644
Binary files a/scripts/linux/screenshots/graphics-transform-translation.png and b/scripts/linux/screenshots/graphics-transform-translation.png differ
diff --git a/scripts/linux/screenshots/kotlin.png b/scripts/linux/screenshots/kotlin.png
index 05879314463..b0156d58402 100644
Binary files a/scripts/linux/screenshots/kotlin.png and b/scripts/linux/screenshots/kotlin.png differ
diff --git a/scripts/linux/screenshots/landscape.png b/scripts/linux/screenshots/landscape.png
index 4c2f7c7a026..bd240669f77 100644
Binary files a/scripts/linux/screenshots/landscape.png and b/scripts/linux/screenshots/landscape.png differ
diff --git a/scripts/mac-catalyst/screenshots/CoverHorizontalTransitionTest.png b/scripts/mac-catalyst/screenshots/CoverHorizontalTransitionTest.png
index a8a2e815f05..2673169f4ac 100644
Binary files a/scripts/mac-catalyst/screenshots/CoverHorizontalTransitionTest.png and b/scripts/mac-catalyst/screenshots/CoverHorizontalTransitionTest.png differ
diff --git a/scripts/mac-catalyst/screenshots/DesktopChromeTheme_dark.png b/scripts/mac-catalyst/screenshots/DesktopChromeTheme_dark.png
new file mode 100644
index 00000000000..adcb37b463b
Binary files /dev/null and b/scripts/mac-catalyst/screenshots/DesktopChromeTheme_dark.png differ
diff --git a/scripts/mac-catalyst/screenshots/DesktopChromeTheme_light.png b/scripts/mac-catalyst/screenshots/DesktopChromeTheme_light.png
new file mode 100644
index 00000000000..db93b5f6249
Binary files /dev/null and b/scripts/mac-catalyst/screenshots/DesktopChromeTheme_light.png differ
diff --git a/scripts/mac-catalyst/screenshots/DesktopMode.png b/scripts/mac-catalyst/screenshots/DesktopMode.png
index 9fbc47eab7c..57d6f22415b 100644
Binary files a/scripts/mac-catalyst/screenshots/DesktopMode.png and b/scripts/mac-catalyst/screenshots/DesktopMode.png differ
diff --git a/scripts/mac-catalyst/screenshots/DesktopScrollbarTheme_dark.png b/scripts/mac-catalyst/screenshots/DesktopScrollbarTheme_dark.png
new file mode 100644
index 00000000000..b05df3d954c
Binary files /dev/null and b/scripts/mac-catalyst/screenshots/DesktopScrollbarTheme_dark.png differ
diff --git a/scripts/mac-catalyst/screenshots/DesktopScrollbarTheme_light.png b/scripts/mac-catalyst/screenshots/DesktopScrollbarTheme_light.png
new file mode 100644
index 00000000000..5af21f9bfc4
Binary files /dev/null and b/scripts/mac-catalyst/screenshots/DesktopScrollbarTheme_light.png differ
diff --git a/scripts/mac-catalyst/screenshots/DesktopWidgetsTheme_dark.png b/scripts/mac-catalyst/screenshots/DesktopWidgetsTheme_dark.png
new file mode 100644
index 00000000000..089c7ff4020
Binary files /dev/null and b/scripts/mac-catalyst/screenshots/DesktopWidgetsTheme_dark.png differ
diff --git a/scripts/mac-catalyst/screenshots/DesktopWidgetsTheme_light.png b/scripts/mac-catalyst/screenshots/DesktopWidgetsTheme_light.png
new file mode 100644
index 00000000000..b0bbe451299
Binary files /dev/null and b/scripts/mac-catalyst/screenshots/DesktopWidgetsTheme_light.png differ
diff --git a/scripts/mac-catalyst/screenshots/FadeTransitionTest.png b/scripts/mac-catalyst/screenshots/FadeTransitionTest.png
index 424ce44d120..dc683ee56b6 100644
Binary files a/scripts/mac-catalyst/screenshots/FadeTransitionTest.png and b/scripts/mac-catalyst/screenshots/FadeTransitionTest.png differ
diff --git a/scripts/mac-catalyst/screenshots/FlipTransitionTest.png b/scripts/mac-catalyst/screenshots/FlipTransitionTest.png
index 3e8b60f4899..5b018ca959d 100644
Binary files a/scripts/mac-catalyst/screenshots/FlipTransitionTest.png and b/scripts/mac-catalyst/screenshots/FlipTransitionTest.png differ
diff --git a/scripts/mac-catalyst/screenshots/MorphElementMorphScreenshotTest.png b/scripts/mac-catalyst/screenshots/MorphElementMorphScreenshotTest.png
index 2ea1f171e4a..b80056195b5 100644
Binary files a/scripts/mac-catalyst/screenshots/MorphElementMorphScreenshotTest.png and b/scripts/mac-catalyst/screenshots/MorphElementMorphScreenshotTest.png differ
diff --git a/scripts/mac-catalyst/screenshots/MorphTransitionScrubScreenshotTest.png b/scripts/mac-catalyst/screenshots/MorphTransitionScrubScreenshotTest.png
index 74a21543c4a..7beff444df4 100644
Binary files a/scripts/mac-catalyst/screenshots/MorphTransitionScrubScreenshotTest.png and b/scripts/mac-catalyst/screenshots/MorphTransitionScrubScreenshotTest.png differ
diff --git a/scripts/mac-catalyst/screenshots/MorphTransitionSnapshotTest.png b/scripts/mac-catalyst/screenshots/MorphTransitionSnapshotTest.png
index f95aeebe214..7fae41b106b 100644
Binary files a/scripts/mac-catalyst/screenshots/MorphTransitionSnapshotTest.png and b/scripts/mac-catalyst/screenshots/MorphTransitionSnapshotTest.png differ
diff --git a/scripts/mac-catalyst/screenshots/MorphTransitionTest.png b/scripts/mac-catalyst/screenshots/MorphTransitionTest.png
index 338f6609886..3d950c2f8be 100644
Binary files a/scripts/mac-catalyst/screenshots/MorphTransitionTest.png and b/scripts/mac-catalyst/screenshots/MorphTransitionTest.png differ
diff --git a/scripts/mac-catalyst/screenshots/SlideFadeTitleTransitionTest.png b/scripts/mac-catalyst/screenshots/SlideFadeTitleTransitionTest.png
index 8c6fe251481..0d461d709ad 100644
Binary files a/scripts/mac-catalyst/screenshots/SlideFadeTitleTransitionTest.png and b/scripts/mac-catalyst/screenshots/SlideFadeTitleTransitionTest.png differ
diff --git a/scripts/mac-catalyst/screenshots/SlideHorizontalBackTransitionTest.png b/scripts/mac-catalyst/screenshots/SlideHorizontalBackTransitionTest.png
index 9b4ac01a12f..cc6581d3d92 100644
Binary files a/scripts/mac-catalyst/screenshots/SlideHorizontalBackTransitionTest.png and b/scripts/mac-catalyst/screenshots/SlideHorizontalBackTransitionTest.png differ
diff --git a/scripts/mac-catalyst/screenshots/SlideHorizontalTransitionTest.png b/scripts/mac-catalyst/screenshots/SlideHorizontalTransitionTest.png
index 4b03c6466b9..c1d767e0a05 100644
Binary files a/scripts/mac-catalyst/screenshots/SlideHorizontalTransitionTest.png and b/scripts/mac-catalyst/screenshots/SlideHorizontalTransitionTest.png differ
diff --git a/scripts/mac-catalyst/screenshots/SlideVerticalTransitionTest.png b/scripts/mac-catalyst/screenshots/SlideVerticalTransitionTest.png
index bae8b7cb768..9b860b65ba5 100644
Binary files a/scripts/mac-catalyst/screenshots/SlideVerticalTransitionTest.png and b/scripts/mac-catalyst/screenshots/SlideVerticalTransitionTest.png differ
diff --git a/scripts/mac-catalyst/screenshots/UncoverHorizontalTransitionTest.png b/scripts/mac-catalyst/screenshots/UncoverHorizontalTransitionTest.png
index a2e10c770a4..3be69fe011c 100644
Binary files a/scripts/mac-catalyst/screenshots/UncoverHorizontalTransitionTest.png and b/scripts/mac-catalyst/screenshots/UncoverHorizontalTransitionTest.png differ
diff --git a/scripts/macos/screenshots/AdsScreen.png b/scripts/macos/screenshots/AdsScreen.png
index 02f1e51e858..b820c280318 100644
Binary files a/scripts/macos/screenshots/AdsScreen.png and b/scripts/macos/screenshots/AdsScreen.png differ
diff --git a/scripts/macos/screenshots/AnimateHierarchyScreenshotTest.png b/scripts/macos/screenshots/AnimateHierarchyScreenshotTest.png
index 4d2acb82b35..5da7c2db6ed 100644
Binary files a/scripts/macos/screenshots/AnimateHierarchyScreenshotTest.png and b/scripts/macos/screenshots/AnimateHierarchyScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/AnimateLayoutScreenshotTest.png b/scripts/macos/screenshots/AnimateLayoutScreenshotTest.png
index 1957728e1ab..9ad69e5e08f 100644
Binary files a/scripts/macos/screenshots/AnimateLayoutScreenshotTest.png and b/scripts/macos/screenshots/AnimateLayoutScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/AnimateUnlayoutScreenshotTest.png b/scripts/macos/screenshots/AnimateUnlayoutScreenshotTest.png
index de6ed5082f6..4b7e3ea374b 100644
Binary files a/scripts/macos/screenshots/AnimateUnlayoutScreenshotTest.png and b/scripts/macos/screenshots/AnimateUnlayoutScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/AppReviewDialog.png b/scripts/macos/screenshots/AppReviewDialog.png
index 6df602885c0..4935f3ae364 100644
Binary files a/scripts/macos/screenshots/AppReviewDialog.png and b/scripts/macos/screenshots/AppReviewDialog.png differ
diff --git a/scripts/macos/screenshots/ButtonTheme_dark.png b/scripts/macos/screenshots/ButtonTheme_dark.png
index a292cd44b84..6da25b85645 100644
Binary files a/scripts/macos/screenshots/ButtonTheme_dark.png and b/scripts/macos/screenshots/ButtonTheme_dark.png differ
diff --git a/scripts/macos/screenshots/ButtonTheme_light.png b/scripts/macos/screenshots/ButtonTheme_light.png
index 7ba6e46613f..178f0d93289 100644
Binary files a/scripts/macos/screenshots/ButtonTheme_light.png and b/scripts/macos/screenshots/ButtonTheme_light.png differ
diff --git a/scripts/macos/screenshots/CenteredDialogTitle_dark.png b/scripts/macos/screenshots/CenteredDialogTitle_dark.png
index 3bf7c018fa7..aaf4aad6c63 100644
Binary files a/scripts/macos/screenshots/CenteredDialogTitle_dark.png and b/scripts/macos/screenshots/CenteredDialogTitle_dark.png differ
diff --git a/scripts/macos/screenshots/CenteredDialogTitle_light.png b/scripts/macos/screenshots/CenteredDialogTitle_light.png
index bd4381a4a27..9ffcf719a78 100644
Binary files a/scripts/macos/screenshots/CenteredDialogTitle_light.png and b/scripts/macos/screenshots/CenteredDialogTitle_light.png differ
diff --git a/scripts/macos/screenshots/CenteredInteractionDialogTitle_dark.png b/scripts/macos/screenshots/CenteredInteractionDialogTitle_dark.png
index f4ea32f0ecf..179200d80a0 100644
Binary files a/scripts/macos/screenshots/CenteredInteractionDialogTitle_dark.png and b/scripts/macos/screenshots/CenteredInteractionDialogTitle_dark.png differ
diff --git a/scripts/macos/screenshots/CenteredInteractionDialogTitle_light.png b/scripts/macos/screenshots/CenteredInteractionDialogTitle_light.png
index a8e97095381..80204db7563 100644
Binary files a/scripts/macos/screenshots/CenteredInteractionDialogTitle_light.png and b/scripts/macos/screenshots/CenteredInteractionDialogTitle_light.png differ
diff --git a/scripts/macos/screenshots/ChatInput_dark.png b/scripts/macos/screenshots/ChatInput_dark.png
index 97305242d07..1966d7189b6 100644
Binary files a/scripts/macos/screenshots/ChatInput_dark.png and b/scripts/macos/screenshots/ChatInput_dark.png differ
diff --git a/scripts/macos/screenshots/ChatInput_light.png b/scripts/macos/screenshots/ChatInput_light.png
index 20657cdf7ff..20fcc68f7ae 100644
Binary files a/scripts/macos/screenshots/ChatInput_light.png and b/scripts/macos/screenshots/ChatInput_light.png differ
diff --git a/scripts/macos/screenshots/ChatView_dark.png b/scripts/macos/screenshots/ChatView_dark.png
index 2087123c094..6583a147ef9 100644
Binary files a/scripts/macos/screenshots/ChatView_dark.png and b/scripts/macos/screenshots/ChatView_dark.png differ
diff --git a/scripts/macos/screenshots/ChatView_light.png b/scripts/macos/screenshots/ChatView_light.png
index 60ef931e525..c056a26b318 100644
Binary files a/scripts/macos/screenshots/ChatView_light.png and b/scripts/macos/screenshots/ChatView_light.png differ
diff --git a/scripts/macos/screenshots/CheckBoxRadioTheme_dark.png b/scripts/macos/screenshots/CheckBoxRadioTheme_dark.png
index f06afed54c9..4114c100d02 100644
Binary files a/scripts/macos/screenshots/CheckBoxRadioTheme_dark.png and b/scripts/macos/screenshots/CheckBoxRadioTheme_dark.png differ
diff --git a/scripts/macos/screenshots/CheckBoxRadioTheme_light.png b/scripts/macos/screenshots/CheckBoxRadioTheme_light.png
index 44b332dd997..a7a7c598d96 100644
Binary files a/scripts/macos/screenshots/CheckBoxRadioTheme_light.png and b/scripts/macos/screenshots/CheckBoxRadioTheme_light.png differ
diff --git a/scripts/macos/screenshots/CodeEditor.png b/scripts/macos/screenshots/CodeEditor.png
index ecff1f55cd4..401dde28ac5 100644
Binary files a/scripts/macos/screenshots/CodeEditor.png and b/scripts/macos/screenshots/CodeEditor.png differ
diff --git a/scripts/macos/screenshots/ComponentReplaceFadeScreenshotTest.png b/scripts/macos/screenshots/ComponentReplaceFadeScreenshotTest.png
index 3c8b208cd2a..89839545de0 100644
Binary files a/scripts/macos/screenshots/ComponentReplaceFadeScreenshotTest.png and b/scripts/macos/screenshots/ComponentReplaceFadeScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/ComponentReplaceFlipScreenshotTest.png b/scripts/macos/screenshots/ComponentReplaceFlipScreenshotTest.png
index 56bbaa4a868..0eb0543c9fe 100644
Binary files a/scripts/macos/screenshots/ComponentReplaceFlipScreenshotTest.png and b/scripts/macos/screenshots/ComponentReplaceFlipScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/ComponentReplaceSlideScreenshotTest.png b/scripts/macos/screenshots/ComponentReplaceSlideScreenshotTest.png
index 448c368f7d7..420a194e044 100644
Binary files a/scripts/macos/screenshots/ComponentReplaceSlideScreenshotTest.png and b/scripts/macos/screenshots/ComponentReplaceSlideScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/CoverHorizontalTransitionTest.png b/scripts/macos/screenshots/CoverHorizontalTransitionTest.png
index 9163f3588ca..2f1687e0070 100644
Binary files a/scripts/macos/screenshots/CoverHorizontalTransitionTest.png and b/scripts/macos/screenshots/CoverHorizontalTransitionTest.png differ
diff --git a/scripts/macos/screenshots/DesktopChromeTheme_dark.png b/scripts/macos/screenshots/DesktopChromeTheme_dark.png
new file mode 100644
index 00000000000..617d885d8ee
Binary files /dev/null and b/scripts/macos/screenshots/DesktopChromeTheme_dark.png differ
diff --git a/scripts/macos/screenshots/DesktopChromeTheme_light.png b/scripts/macos/screenshots/DesktopChromeTheme_light.png
new file mode 100644
index 00000000000..ca595f77d50
Binary files /dev/null and b/scripts/macos/screenshots/DesktopChromeTheme_light.png differ
diff --git a/scripts/macos/screenshots/DesktopMode.png b/scripts/macos/screenshots/DesktopMode.png
index f5f17caca4e..b9c06022ed0 100644
Binary files a/scripts/macos/screenshots/DesktopMode.png and b/scripts/macos/screenshots/DesktopMode.png differ
diff --git a/scripts/macos/screenshots/DesktopScrollbarTheme_dark.png b/scripts/macos/screenshots/DesktopScrollbarTheme_dark.png
new file mode 100644
index 00000000000..8a83b38d2a6
Binary files /dev/null and b/scripts/macos/screenshots/DesktopScrollbarTheme_dark.png differ
diff --git a/scripts/macos/screenshots/DesktopScrollbarTheme_light.png b/scripts/macos/screenshots/DesktopScrollbarTheme_light.png
new file mode 100644
index 00000000000..01a89a61d75
Binary files /dev/null and b/scripts/macos/screenshots/DesktopScrollbarTheme_light.png differ
diff --git a/scripts/macos/screenshots/DesktopWidgetsTheme_dark.png b/scripts/macos/screenshots/DesktopWidgetsTheme_dark.png
new file mode 100644
index 00000000000..a8c3ceeb67d
Binary files /dev/null and b/scripts/macos/screenshots/DesktopWidgetsTheme_dark.png differ
diff --git a/scripts/macos/screenshots/DesktopWidgetsTheme_light.png b/scripts/macos/screenshots/DesktopWidgetsTheme_light.png
new file mode 100644
index 00000000000..91032f3740a
Binary files /dev/null and b/scripts/macos/screenshots/DesktopWidgetsTheme_light.png differ
diff --git a/scripts/macos/screenshots/DialogTheme_dark.png b/scripts/macos/screenshots/DialogTheme_dark.png
index bbbc10e060f..0aae8c3bd93 100644
Binary files a/scripts/macos/screenshots/DialogTheme_dark.png and b/scripts/macos/screenshots/DialogTheme_dark.png differ
diff --git a/scripts/macos/screenshots/DialogTheme_light.png b/scripts/macos/screenshots/DialogTheme_light.png
index 22fbaeb5ba1..d0c1e821d11 100644
Binary files a/scripts/macos/screenshots/DialogTheme_light.png and b/scripts/macos/screenshots/DialogTheme_light.png differ
diff --git a/scripts/macos/screenshots/FadeTransitionTest.png b/scripts/macos/screenshots/FadeTransitionTest.png
index 11688fbb880..92e360ee44d 100644
Binary files a/scripts/macos/screenshots/FadeTransitionTest.png and b/scripts/macos/screenshots/FadeTransitionTest.png differ
diff --git a/scripts/macos/screenshots/FlipTransitionTest.png b/scripts/macos/screenshots/FlipTransitionTest.png
index bf66fdeb127..9f25bea71e4 100644
Binary files a/scripts/macos/screenshots/FlipTransitionTest.png and b/scripts/macos/screenshots/FlipTransitionTest.png differ
diff --git a/scripts/macos/screenshots/FloatingActionButtonTheme_dark.png b/scripts/macos/screenshots/FloatingActionButtonTheme_dark.png
index 7283d88b517..73f7c1f6804 100644
Binary files a/scripts/macos/screenshots/FloatingActionButtonTheme_dark.png and b/scripts/macos/screenshots/FloatingActionButtonTheme_dark.png differ
diff --git a/scripts/macos/screenshots/FloatingActionButtonTheme_light.png b/scripts/macos/screenshots/FloatingActionButtonTheme_light.png
index ef3771bcd94..87972d7e667 100644
Binary files a/scripts/macos/screenshots/FloatingActionButtonTheme_light.png and b/scripts/macos/screenshots/FloatingActionButtonTheme_light.png differ
diff --git a/scripts/macos/screenshots/ImageViewerNavigationModes.png b/scripts/macos/screenshots/ImageViewerNavigationModes.png
index ae48904b73d..240a7eeb8ef 100644
Binary files a/scripts/macos/screenshots/ImageViewerNavigationModes.png and b/scripts/macos/screenshots/ImageViewerNavigationModes.png differ
diff --git a/scripts/macos/screenshots/LightweightPickerButtons.png b/scripts/macos/screenshots/LightweightPickerButtons.png
index 3d0ecd66329..986f0f748f5 100644
Binary files a/scripts/macos/screenshots/LightweightPickerButtons.png and b/scripts/macos/screenshots/LightweightPickerButtons.png differ
diff --git a/scripts/macos/screenshots/LightweightPickerButtons_above_center.png b/scripts/macos/screenshots/LightweightPickerButtons_above_center.png
index 6472671d7b1..3498cf4548f 100644
Binary files a/scripts/macos/screenshots/LightweightPickerButtons_above_center.png and b/scripts/macos/screenshots/LightweightPickerButtons_above_center.png differ
diff --git a/scripts/macos/screenshots/LightweightPickerButtons_below_right.png b/scripts/macos/screenshots/LightweightPickerButtons_below_right.png
index 8764a56349e..319e59d3892 100644
Binary files a/scripts/macos/screenshots/LightweightPickerButtons_below_right.png and b/scripts/macos/screenshots/LightweightPickerButtons_below_right.png differ
diff --git a/scripts/macos/screenshots/LightweightPickerButtons_between_mixed.png b/scripts/macos/screenshots/LightweightPickerButtons_between_mixed.png
index 7a6f3a5983e..af1d745a656 100644
Binary files a/scripts/macos/screenshots/LightweightPickerButtons_between_mixed.png and b/scripts/macos/screenshots/LightweightPickerButtons_between_mixed.png differ
diff --git a/scripts/macos/screenshots/ListTheme_dark.png b/scripts/macos/screenshots/ListTheme_dark.png
index edfa8527b1a..b2ffc60312d 100644
Binary files a/scripts/macos/screenshots/ListTheme_dark.png and b/scripts/macos/screenshots/ListTheme_dark.png differ
diff --git a/scripts/macos/screenshots/ListTheme_light.png b/scripts/macos/screenshots/ListTheme_light.png
index f99f2c713b6..ca3e84ee84c 100644
Binary files a/scripts/macos/screenshots/ListTheme_light.png and b/scripts/macos/screenshots/ListTheme_light.png differ
diff --git a/scripts/macos/screenshots/MainActivity.png b/scripts/macos/screenshots/MainActivity.png
index 624cceac699..ec19b7669e4 100644
Binary files a/scripts/macos/screenshots/MainActivity.png and b/scripts/macos/screenshots/MainActivity.png differ
diff --git a/scripts/macos/screenshots/MediaPlayback.png b/scripts/macos/screenshots/MediaPlayback.png
index b8bc00b82e6..b5b73bd19ec 100644
Binary files a/scripts/macos/screenshots/MediaPlayback.png and b/scripts/macos/screenshots/MediaPlayback.png differ
diff --git a/scripts/macos/screenshots/MorphElementMorphScreenshotTest.png b/scripts/macos/screenshots/MorphElementMorphScreenshotTest.png
index c231a8e9348..f1dff5742c2 100644
Binary files a/scripts/macos/screenshots/MorphElementMorphScreenshotTest.png and b/scripts/macos/screenshots/MorphElementMorphScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/MorphTransitionScrolledSourceTest.png b/scripts/macos/screenshots/MorphTransitionScrolledSourceTest.png
index 9af05ef541a..24268e625db 100644
Binary files a/scripts/macos/screenshots/MorphTransitionScrolledSourceTest.png and b/scripts/macos/screenshots/MorphTransitionScrolledSourceTest.png differ
diff --git a/scripts/macos/screenshots/MorphTransitionScrubScreenshotTest.png b/scripts/macos/screenshots/MorphTransitionScrubScreenshotTest.png
index 2795cb5e0f0..ee76723ccbf 100644
Binary files a/scripts/macos/screenshots/MorphTransitionScrubScreenshotTest.png and b/scripts/macos/screenshots/MorphTransitionScrubScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/MorphTransitionSnapshotTest.png b/scripts/macos/screenshots/MorphTransitionSnapshotTest.png
index 94a91e40e14..c79a4fa3aea 100644
Binary files a/scripts/macos/screenshots/MorphTransitionSnapshotTest.png and b/scripts/macos/screenshots/MorphTransitionSnapshotTest.png differ
diff --git a/scripts/macos/screenshots/MorphTransitionTest.png b/scripts/macos/screenshots/MorphTransitionTest.png
index 755522b9929..d84d96db8e3 100644
Binary files a/scripts/macos/screenshots/MorphTransitionTest.png and b/scripts/macos/screenshots/MorphTransitionTest.png differ
diff --git a/scripts/macos/screenshots/MultiButtonTheme_dark.png b/scripts/macos/screenshots/MultiButtonTheme_dark.png
index a5b34a61126..1e0149c7b20 100644
Binary files a/scripts/macos/screenshots/MultiButtonTheme_dark.png and b/scripts/macos/screenshots/MultiButtonTheme_dark.png differ
diff --git a/scripts/macos/screenshots/MultiButtonTheme_light.png b/scripts/macos/screenshots/MultiButtonTheme_light.png
index 99bbb568ddb..e31fddea3d9 100644
Binary files a/scripts/macos/screenshots/MultiButtonTheme_light.png and b/scripts/macos/screenshots/MultiButtonTheme_light.png differ
diff --git a/scripts/macos/screenshots/NativeMapFallback.png b/scripts/macos/screenshots/NativeMapFallback.png
index 3ccd6bec47b..c41761020e6 100644
Binary files a/scripts/macos/screenshots/NativeMapFallback.png and b/scripts/macos/screenshots/NativeMapFallback.png differ
diff --git a/scripts/macos/screenshots/PaletteOverrideTheme_dark.png b/scripts/macos/screenshots/PaletteOverrideTheme_dark.png
index c4832c87844..cdf6b5afb6a 100644
Binary files a/scripts/macos/screenshots/PaletteOverrideTheme_dark.png and b/scripts/macos/screenshots/PaletteOverrideTheme_dark.png differ
diff --git a/scripts/macos/screenshots/PaletteOverrideTheme_light.png b/scripts/macos/screenshots/PaletteOverrideTheme_light.png
index 055fa551983..500d9724247 100644
Binary files a/scripts/macos/screenshots/PaletteOverrideTheme_light.png and b/scripts/macos/screenshots/PaletteOverrideTheme_light.png differ
diff --git a/scripts/macos/screenshots/PickerTheme_dark.png b/scripts/macos/screenshots/PickerTheme_dark.png
index b4d5e77737e..a80888d77c9 100644
Binary files a/scripts/macos/screenshots/PickerTheme_dark.png and b/scripts/macos/screenshots/PickerTheme_dark.png differ
diff --git a/scripts/macos/screenshots/PickerTheme_light.png b/scripts/macos/screenshots/PickerTheme_light.png
index ed4f4fbdab0..34edf2f7984 100644
Binary files a/scripts/macos/screenshots/PickerTheme_light.png and b/scripts/macos/screenshots/PickerTheme_light.png differ
diff --git a/scripts/macos/screenshots/PullToRefreshSpinnerScreenshotTest.png b/scripts/macos/screenshots/PullToRefreshSpinnerScreenshotTest.png
index d8beee10101..4abd7386ed3 100644
Binary files a/scripts/macos/screenshots/PullToRefreshSpinnerScreenshotTest.png and b/scripts/macos/screenshots/PullToRefreshSpinnerScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/PureEditors.png b/scripts/macos/screenshots/PureEditors.png
index ecb7779f162..8d7299bea2f 100644
Binary files a/scripts/macos/screenshots/PureEditors.png and b/scripts/macos/screenshots/PureEditors.png differ
diff --git a/scripts/macos/screenshots/RealOsmVector.png b/scripts/macos/screenshots/RealOsmVector.png
index 3b9eef6a28d..d8c476cebae 100644
Binary files a/scripts/macos/screenshots/RealOsmVector.png and b/scripts/macos/screenshots/RealOsmVector.png differ
diff --git a/scripts/macos/screenshots/RichTextArea.png b/scripts/macos/screenshots/RichTextArea.png
index 889eb177036..dc89c1b53dc 100644
Binary files a/scripts/macos/screenshots/RichTextArea.png and b/scripts/macos/screenshots/RichTextArea.png differ
diff --git a/scripts/macos/screenshots/SVGStatic.png b/scripts/macos/screenshots/SVGStatic.png
index 095b57f1879..22c2137f9a6 100644
Binary files a/scripts/macos/screenshots/SVGStatic.png and b/scripts/macos/screenshots/SVGStatic.png differ
diff --git a/scripts/macos/screenshots/Sheet.png b/scripts/macos/screenshots/Sheet.png
index 15863286b94..68bff641c05 100644
Binary files a/scripts/macos/screenshots/Sheet.png and b/scripts/macos/screenshots/Sheet.png differ
diff --git a/scripts/macos/screenshots/SheetSlideUpAnimationScreenshotTest.png b/scripts/macos/screenshots/SheetSlideUpAnimationScreenshotTest.png
index 61989fe8af9..326d7dc1faa 100644
Binary files a/scripts/macos/screenshots/SheetSlideUpAnimationScreenshotTest.png and b/scripts/macos/screenshots/SheetSlideUpAnimationScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/ShowcaseTheme_dark.png b/scripts/macos/screenshots/ShowcaseTheme_dark.png
index 246fc0a3c13..86471c49d57 100644
Binary files a/scripts/macos/screenshots/ShowcaseTheme_dark.png and b/scripts/macos/screenshots/ShowcaseTheme_dark.png differ
diff --git a/scripts/macos/screenshots/ShowcaseTheme_light.png b/scripts/macos/screenshots/ShowcaseTheme_light.png
index 3bf2175b34c..f8b92d9f4d0 100644
Binary files a/scripts/macos/screenshots/ShowcaseTheme_light.png and b/scripts/macos/screenshots/ShowcaseTheme_light.png differ
diff --git a/scripts/macos/screenshots/SlideFadeTitleTransitionTest.png b/scripts/macos/screenshots/SlideFadeTitleTransitionTest.png
index 946abe39282..855f03fe597 100644
Binary files a/scripts/macos/screenshots/SlideFadeTitleTransitionTest.png and b/scripts/macos/screenshots/SlideFadeTitleTransitionTest.png differ
diff --git a/scripts/macos/screenshots/SlideHorizontalBackTransitionTest.png b/scripts/macos/screenshots/SlideHorizontalBackTransitionTest.png
index 319845ea263..56f7fc86c08 100644
Binary files a/scripts/macos/screenshots/SlideHorizontalBackTransitionTest.png and b/scripts/macos/screenshots/SlideHorizontalBackTransitionTest.png differ
diff --git a/scripts/macos/screenshots/SlideHorizontalTransitionTest.png b/scripts/macos/screenshots/SlideHorizontalTransitionTest.png
index 3242707b236..855f03fe597 100644
Binary files a/scripts/macos/screenshots/SlideHorizontalTransitionTest.png and b/scripts/macos/screenshots/SlideHorizontalTransitionTest.png differ
diff --git a/scripts/macos/screenshots/SlideVerticalTransitionTest.png b/scripts/macos/screenshots/SlideVerticalTransitionTest.png
index f6ea4d466e3..067c39518c2 100644
Binary files a/scripts/macos/screenshots/SlideVerticalTransitionTest.png and b/scripts/macos/screenshots/SlideVerticalTransitionTest.png differ
diff --git a/scripts/macos/screenshots/SmoothScrollScreenshotTest.png b/scripts/macos/screenshots/SmoothScrollScreenshotTest.png
index 4247f770887..2c24802a178 100644
Binary files a/scripts/macos/screenshots/SmoothScrollScreenshotTest.png and b/scripts/macos/screenshots/SmoothScrollScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/SpanLabelTheme_dark.png b/scripts/macos/screenshots/SpanLabelTheme_dark.png
index 8a09fac2bb7..3803df6ecc0 100644
Binary files a/scripts/macos/screenshots/SpanLabelTheme_dark.png and b/scripts/macos/screenshots/SpanLabelTheme_dark.png differ
diff --git a/scripts/macos/screenshots/SpanLabelTheme_light.png b/scripts/macos/screenshots/SpanLabelTheme_light.png
index 1cc27a36727..33a4606f08e 100644
Binary files a/scripts/macos/screenshots/SpanLabelTheme_light.png and b/scripts/macos/screenshots/SpanLabelTheme_light.png differ
diff --git a/scripts/macos/screenshots/StatusBarTapDiagnosticScreenshotTest.png b/scripts/macos/screenshots/StatusBarTapDiagnosticScreenshotTest.png
index 0d03d38f6d8..bf52b68707b 100644
Binary files a/scripts/macos/screenshots/StatusBarTapDiagnosticScreenshotTest.png and b/scripts/macos/screenshots/StatusBarTapDiagnosticScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/StickyHeaderFadeTransitionScreenshotTest.png b/scripts/macos/screenshots/StickyHeaderFadeTransitionScreenshotTest.png
index 65e55130a82..b4335950be3 100644
Binary files a/scripts/macos/screenshots/StickyHeaderFadeTransitionScreenshotTest.png and b/scripts/macos/screenshots/StickyHeaderFadeTransitionScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/StickyHeaderScreenshotTest.png b/scripts/macos/screenshots/StickyHeaderScreenshotTest.png
index b286320b3c9..e186de6c3c7 100644
Binary files a/scripts/macos/screenshots/StickyHeaderScreenshotTest.png and b/scripts/macos/screenshots/StickyHeaderScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/StickyHeaderSlideTransitionScreenshotTest.png b/scripts/macos/screenshots/StickyHeaderSlideTransitionScreenshotTest.png
index d092285c5aa..f9fabdd80c1 100644
Binary files a/scripts/macos/screenshots/StickyHeaderSlideTransitionScreenshotTest.png and b/scripts/macos/screenshots/StickyHeaderSlideTransitionScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/SurfacesRasterizer.png b/scripts/macos/screenshots/SurfacesRasterizer.png
index e9fda2dfcec..a27919f9642 100644
Binary files a/scripts/macos/screenshots/SurfacesRasterizer.png and b/scripts/macos/screenshots/SurfacesRasterizer.png differ
diff --git a/scripts/macos/screenshots/SwitchTheme_dark.png b/scripts/macos/screenshots/SwitchTheme_dark.png
index 74e6e88e6bc..b1d71cba4ab 100644
Binary files a/scripts/macos/screenshots/SwitchTheme_dark.png and b/scripts/macos/screenshots/SwitchTheme_dark.png differ
diff --git a/scripts/macos/screenshots/SwitchTheme_light.png b/scripts/macos/screenshots/SwitchTheme_light.png
index d0bc82d3e55..2cd2173adee 100644
Binary files a/scripts/macos/screenshots/SwitchTheme_light.png and b/scripts/macos/screenshots/SwitchTheme_light.png differ
diff --git a/scripts/macos/screenshots/TabsAnimatedIndicatorScreenshotTest.png b/scripts/macos/screenshots/TabsAnimatedIndicatorScreenshotTest.png
index 71a3d5b690e..78db4d3669a 100644
Binary files a/scripts/macos/screenshots/TabsAnimatedIndicatorScreenshotTest.png and b/scripts/macos/screenshots/TabsAnimatedIndicatorScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/TabsBehavior.png b/scripts/macos/screenshots/TabsBehavior.png
index 7409441ef4e..5ec02e00b5d 100644
Binary files a/scripts/macos/screenshots/TabsBehavior.png and b/scripts/macos/screenshots/TabsBehavior.png differ
diff --git a/scripts/macos/screenshots/TabsTheme_dark.png b/scripts/macos/screenshots/TabsTheme_dark.png
index d3d0cace9c3..c44925a62ef 100644
Binary files a/scripts/macos/screenshots/TabsTheme_dark.png and b/scripts/macos/screenshots/TabsTheme_dark.png differ
diff --git a/scripts/macos/screenshots/TabsTheme_light.png b/scripts/macos/screenshots/TabsTheme_light.png
index 867e0af6d8e..6174c4f6734 100644
Binary files a/scripts/macos/screenshots/TabsTheme_light.png and b/scripts/macos/screenshots/TabsTheme_light.png differ
diff --git a/scripts/macos/screenshots/TensileBounceScreenshotTest.png b/scripts/macos/screenshots/TensileBounceScreenshotTest.png
index d8ebfd8f938..ccf449abb47 100644
Binary files a/scripts/macos/screenshots/TensileBounceScreenshotTest.png and b/scripts/macos/screenshots/TensileBounceScreenshotTest.png differ
diff --git a/scripts/macos/screenshots/TextAreaAlignmentStates.png b/scripts/macos/screenshots/TextAreaAlignmentStates.png
index e9591a0f322..22918041b67 100644
Binary files a/scripts/macos/screenshots/TextAreaAlignmentStates.png and b/scripts/macos/screenshots/TextAreaAlignmentStates.png differ
diff --git a/scripts/macos/screenshots/TextFieldTheme_dark.png b/scripts/macos/screenshots/TextFieldTheme_dark.png
index 5c9e500a5c0..860a1a2fd27 100644
Binary files a/scripts/macos/screenshots/TextFieldTheme_dark.png and b/scripts/macos/screenshots/TextFieldTheme_dark.png differ
diff --git a/scripts/macos/screenshots/TextFieldTheme_light.png b/scripts/macos/screenshots/TextFieldTheme_light.png
index 02222665489..f4c9d61c8b4 100644
Binary files a/scripts/macos/screenshots/TextFieldTheme_light.png and b/scripts/macos/screenshots/TextFieldTheme_light.png differ
diff --git a/scripts/macos/screenshots/ToastBarTopPosition.png b/scripts/macos/screenshots/ToastBarTopPosition.png
index 0952666498f..8b6d4c79bc1 100644
Binary files a/scripts/macos/screenshots/ToastBarTopPosition.png and b/scripts/macos/screenshots/ToastBarTopPosition.png differ
diff --git a/scripts/macos/screenshots/ToolbarTheme_dark.png b/scripts/macos/screenshots/ToolbarTheme_dark.png
index a31b3432ad7..d6505b20779 100644
Binary files a/scripts/macos/screenshots/ToolbarTheme_dark.png and b/scripts/macos/screenshots/ToolbarTheme_dark.png differ
diff --git a/scripts/macos/screenshots/ToolbarTheme_light.png b/scripts/macos/screenshots/ToolbarTheme_light.png
index 4cbea7d0835..17089b1a28f 100644
Binary files a/scripts/macos/screenshots/ToolbarTheme_light.png and b/scripts/macos/screenshots/ToolbarTheme_light.png differ
diff --git a/scripts/macos/screenshots/UncoverHorizontalTransitionTest.png b/scripts/macos/screenshots/UncoverHorizontalTransitionTest.png
index 7587944fec6..13ba18b8088 100644
Binary files a/scripts/macos/screenshots/UncoverHorizontalTransitionTest.png and b/scripts/macos/screenshots/UncoverHorizontalTransitionTest.png differ
diff --git a/scripts/macos/screenshots/ValidatorLightweightPicker.png b/scripts/macos/screenshots/ValidatorLightweightPicker.png
index 21ec56b57d1..d9a9b032d23 100644
Binary files a/scripts/macos/screenshots/ValidatorLightweightPicker.png and b/scripts/macos/screenshots/ValidatorLightweightPicker.png differ
diff --git a/scripts/macos/screenshots/VectorMapDarkStyle.png b/scripts/macos/screenshots/VectorMapDarkStyle.png
index 4df20217b03..fe0ae8279e7 100644
Binary files a/scripts/macos/screenshots/VectorMapDarkStyle.png and b/scripts/macos/screenshots/VectorMapDarkStyle.png differ
diff --git a/scripts/macos/screenshots/VectorMapMarkers.png b/scripts/macos/screenshots/VectorMapMarkers.png
index d97b6fe2ef5..bf4105866a9 100644
Binary files a/scripts/macos/screenshots/VectorMapMarkers.png and b/scripts/macos/screenshots/VectorMapMarkers.png differ
diff --git a/scripts/macos/screenshots/VectorMapShapes.png b/scripts/macos/screenshots/VectorMapShapes.png
index e72221d99ac..820b9b52435 100644
Binary files a/scripts/macos/screenshots/VectorMapShapes.png and b/scripts/macos/screenshots/VectorMapShapes.png differ
diff --git a/scripts/macos/screenshots/Window-Dialog-1000x400.png b/scripts/macos/screenshots/Window-Dialog-1000x400.png
index bff373a6c99..7de0d602968 100644
Binary files a/scripts/macos/screenshots/Window-Dialog-1000x400.png and b/scripts/macos/screenshots/Window-Dialog-1000x400.png differ
diff --git a/scripts/macos/screenshots/Window-Dialog-400x300.png b/scripts/macos/screenshots/Window-Dialog-400x300.png
index a8436f24a48..552d26714ec 100644
Binary files a/scripts/macos/screenshots/Window-Dialog-400x300.png and b/scripts/macos/screenshots/Window-Dialog-400x300.png differ
diff --git a/scripts/macos/screenshots/Window-Dialog-900x700.png b/scripts/macos/screenshots/Window-Dialog-900x700.png
index 2d96e613fbd..ea58b9f4327 100644
Binary files a/scripts/macos/screenshots/Window-Dialog-900x700.png and b/scripts/macos/screenshots/Window-Dialog-900x700.png differ
diff --git a/scripts/macos/screenshots/Window-Editing-1000x400.png b/scripts/macos/screenshots/Window-Editing-1000x400.png
index f087ad6bad9..5ba8336b5a1 100644
Binary files a/scripts/macos/screenshots/Window-Editing-1000x400.png and b/scripts/macos/screenshots/Window-Editing-1000x400.png differ
diff --git a/scripts/macos/screenshots/Window-Editing-400x300.png b/scripts/macos/screenshots/Window-Editing-400x300.png
index edb61d51d51..96e45390a8b 100644
Binary files a/scripts/macos/screenshots/Window-Editing-400x300.png and b/scripts/macos/screenshots/Window-Editing-400x300.png differ
diff --git a/scripts/macos/screenshots/Window-Editing-900x700.png b/scripts/macos/screenshots/Window-Editing-900x700.png
index 65f08ea3bae..6211e8e2b3f 100644
Binary files a/scripts/macos/screenshots/Window-Editing-900x700.png and b/scripts/macos/screenshots/Window-Editing-900x700.png differ
diff --git a/scripts/macos/screenshots/Window-Graphics-1000x400.png b/scripts/macos/screenshots/Window-Graphics-1000x400.png
index 4dca97b2f70..7c8c7d66c6c 100644
Binary files a/scripts/macos/screenshots/Window-Graphics-1000x400.png and b/scripts/macos/screenshots/Window-Graphics-1000x400.png differ
diff --git a/scripts/macos/screenshots/Window-Graphics-400x300.png b/scripts/macos/screenshots/Window-Graphics-400x300.png
index b76c3b45d25..730b7e34ca5 100644
Binary files a/scripts/macos/screenshots/Window-Graphics-400x300.png and b/scripts/macos/screenshots/Window-Graphics-400x300.png differ
diff --git a/scripts/macos/screenshots/Window-Graphics-900x700.png b/scripts/macos/screenshots/Window-Graphics-900x700.png
index 7bf8f904a9a..4bcc60e2778 100644
Binary files a/scripts/macos/screenshots/Window-Graphics-900x700.png and b/scripts/macos/screenshots/Window-Graphics-900x700.png differ
diff --git a/scripts/macos/screenshots/Window-Layout-1000x400.png b/scripts/macos/screenshots/Window-Layout-1000x400.png
index 7e580101fcb..0b74cd2ad56 100644
Binary files a/scripts/macos/screenshots/Window-Layout-1000x400.png and b/scripts/macos/screenshots/Window-Layout-1000x400.png differ
diff --git a/scripts/macos/screenshots/Window-Layout-400x300.png b/scripts/macos/screenshots/Window-Layout-400x300.png
index a33e0e6e2a2..40283ca60ed 100644
Binary files a/scripts/macos/screenshots/Window-Layout-400x300.png and b/scripts/macos/screenshots/Window-Layout-400x300.png differ
diff --git a/scripts/macos/screenshots/Window-Layout-900x700.png b/scripts/macos/screenshots/Window-Layout-900x700.png
index 512c1b07af6..a7d8ba44d6c 100644
Binary files a/scripts/macos/screenshots/Window-Layout-900x700.png and b/scripts/macos/screenshots/Window-Layout-900x700.png differ
diff --git a/scripts/macos/screenshots/Window-Modal-background.png b/scripts/macos/screenshots/Window-Modal-background.png
index 556f11ce33f..d785396570f 100644
Binary files a/scripts/macos/screenshots/Window-Modal-background.png and b/scripts/macos/screenshots/Window-Modal-background.png differ
diff --git a/scripts/macos/screenshots/Window-Overlay-600x450.png b/scripts/macos/screenshots/Window-Overlay-600x450.png
index 258de6f300b..f6aae517919 100644
Binary files a/scripts/macos/screenshots/Window-Overlay-600x450.png and b/scripts/macos/screenshots/Window-Overlay-600x450.png differ
diff --git a/scripts/macos/screenshots/Window-Scroll-1000x400.png b/scripts/macos/screenshots/Window-Scroll-1000x400.png
index 3fe7f1e5e01..706bb677d2b 100644
Binary files a/scripts/macos/screenshots/Window-Scroll-1000x400.png and b/scripts/macos/screenshots/Window-Scroll-1000x400.png differ
diff --git a/scripts/macos/screenshots/Window-Scroll-400x300.png b/scripts/macos/screenshots/Window-Scroll-400x300.png
index 3a10f566ca2..7681d17b0a0 100644
Binary files a/scripts/macos/screenshots/Window-Scroll-400x300.png and b/scripts/macos/screenshots/Window-Scroll-400x300.png differ
diff --git a/scripts/macos/screenshots/Window-Scroll-900x700.png b/scripts/macos/screenshots/Window-Scroll-900x700.png
index 1c7e8bdedbc..7ca4caea9ee 100644
Binary files a/scripts/macos/screenshots/Window-Scroll-900x700.png and b/scripts/macos/screenshots/Window-Scroll-900x700.png differ
diff --git a/scripts/macos/screenshots/chart-bar-stacked.png b/scripts/macos/screenshots/chart-bar-stacked.png
index 5f029a0b524..9fe19a36a45 100644
Binary files a/scripts/macos/screenshots/chart-bar-stacked.png and b/scripts/macos/screenshots/chart-bar-stacked.png differ
diff --git a/scripts/macos/screenshots/chart-bar.png b/scripts/macos/screenshots/chart-bar.png
index d91876f66df..7ac320dfb55 100644
Binary files a/scripts/macos/screenshots/chart-bar.png and b/scripts/macos/screenshots/chart-bar.png differ
diff --git a/scripts/macos/screenshots/chart-bubble.png b/scripts/macos/screenshots/chart-bubble.png
index 78860816aeb..63cebe61c2b 100644
Binary files a/scripts/macos/screenshots/chart-bubble.png and b/scripts/macos/screenshots/chart-bubble.png differ
diff --git a/scripts/macos/screenshots/chart-combined-xy.png b/scripts/macos/screenshots/chart-combined-xy.png
index fc2e2564130..9a33559b21d 100644
Binary files a/scripts/macos/screenshots/chart-combined-xy.png and b/scripts/macos/screenshots/chart-combined-xy.png differ
diff --git a/scripts/macos/screenshots/chart-cubic-line.png b/scripts/macos/screenshots/chart-cubic-line.png
index d3f1affec16..b733c28a007 100644
Binary files a/scripts/macos/screenshots/chart-cubic-line.png and b/scripts/macos/screenshots/chart-cubic-line.png differ
diff --git a/scripts/macos/screenshots/chart-doughnut.png b/scripts/macos/screenshots/chart-doughnut.png
index 6f9fefca298..007f8138bdb 100644
Binary files a/scripts/macos/screenshots/chart-doughnut.png and b/scripts/macos/screenshots/chart-doughnut.png differ
diff --git a/scripts/macos/screenshots/chart-line.png b/scripts/macos/screenshots/chart-line.png
index 7a1dbacdc92..1e6db1da2e5 100644
Binary files a/scripts/macos/screenshots/chart-line.png and b/scripts/macos/screenshots/chart-line.png differ
diff --git a/scripts/macos/screenshots/chart-pie.png b/scripts/macos/screenshots/chart-pie.png
index 90862a74d52..2899d679183 100644
Binary files a/scripts/macos/screenshots/chart-pie.png and b/scripts/macos/screenshots/chart-pie.png differ
diff --git a/scripts/macos/screenshots/chart-radar.png b/scripts/macos/screenshots/chart-radar.png
index 18f055094d1..1b8ef065d3a 100644
Binary files a/scripts/macos/screenshots/chart-radar.png and b/scripts/macos/screenshots/chart-radar.png differ
diff --git a/scripts/macos/screenshots/chart-range-bar.png b/scripts/macos/screenshots/chart-range-bar.png
index eed70dd0b1d..a640908912d 100644
Binary files a/scripts/macos/screenshots/chart-range-bar.png and b/scripts/macos/screenshots/chart-range-bar.png differ
diff --git a/scripts/macos/screenshots/chart-rotated-pie.png b/scripts/macos/screenshots/chart-rotated-pie.png
index a44570081cf..6a7c331c4fb 100644
Binary files a/scripts/macos/screenshots/chart-rotated-pie.png and b/scripts/macos/screenshots/chart-rotated-pie.png differ
diff --git a/scripts/macos/screenshots/chart-scatter.png b/scripts/macos/screenshots/chart-scatter.png
index 46651e8bc5e..7687857c745 100644
Binary files a/scripts/macos/screenshots/chart-scatter.png and b/scripts/macos/screenshots/chart-scatter.png differ
diff --git a/scripts/macos/screenshots/chart-time.png b/scripts/macos/screenshots/chart-time.png
index c4e740709b5..825884328b5 100644
Binary files a/scripts/macos/screenshots/chart-time.png and b/scripts/macos/screenshots/chart-time.png differ
diff --git a/scripts/macos/screenshots/chart-transform.png b/scripts/macos/screenshots/chart-transform.png
index 2425aaf9f52..48081c1d9c3 100644
Binary files a/scripts/macos/screenshots/chart-transform.png and b/scripts/macos/screenshots/chart-transform.png differ
diff --git a/scripts/macos/screenshots/css-gradients.png b/scripts/macos/screenshots/css-gradients.png
index c08bcb5093d..83195b85df2 100644
Binary files a/scripts/macos/screenshots/css-gradients.png and b/scripts/macos/screenshots/css-gradients.png differ
diff --git a/scripts/macos/screenshots/graphics-affine-scale.png b/scripts/macos/screenshots/graphics-affine-scale.png
index 7f051460b4e..0b70ac083be 100644
Binary files a/scripts/macos/screenshots/graphics-affine-scale.png and b/scripts/macos/screenshots/graphics-affine-scale.png differ
diff --git a/scripts/macos/screenshots/graphics-clip-under-rotation.png b/scripts/macos/screenshots/graphics-clip-under-rotation.png
index 3adc2742ca0..540eddb53eb 100644
Binary files a/scripts/macos/screenshots/graphics-clip-under-rotation.png and b/scripts/macos/screenshots/graphics-clip-under-rotation.png differ
diff --git a/scripts/macos/screenshots/graphics-clip.png b/scripts/macos/screenshots/graphics-clip.png
index d5674269a52..014950057ea 100644
Binary files a/scripts/macos/screenshots/graphics-clip.png and b/scripts/macos/screenshots/graphics-clip.png differ
diff --git a/scripts/macos/screenshots/graphics-draw-arc.png b/scripts/macos/screenshots/graphics-draw-arc.png
index 1326aa5ec89..969ed1c5e21 100644
Binary files a/scripts/macos/screenshots/graphics-draw-arc.png and b/scripts/macos/screenshots/graphics-draw-arc.png differ
diff --git a/scripts/macos/screenshots/graphics-draw-gradient-stops.png b/scripts/macos/screenshots/graphics-draw-gradient-stops.png
index 98202a2eadb..24463677d77 100644
Binary files a/scripts/macos/screenshots/graphics-draw-gradient-stops.png and b/scripts/macos/screenshots/graphics-draw-gradient-stops.png differ
diff --git a/scripts/macos/screenshots/graphics-draw-gradient.png b/scripts/macos/screenshots/graphics-draw-gradient.png
index b74818e6687..2b1a097cec4 100644
Binary files a/scripts/macos/screenshots/graphics-draw-gradient.png and b/scripts/macos/screenshots/graphics-draw-gradient.png differ
diff --git a/scripts/macos/screenshots/graphics-draw-image-rect.png b/scripts/macos/screenshots/graphics-draw-image-rect.png
index 5a7ed096833..34760e8bdb1 100644
Binary files a/scripts/macos/screenshots/graphics-draw-image-rect.png and b/scripts/macos/screenshots/graphics-draw-image-rect.png differ
diff --git a/scripts/macos/screenshots/graphics-draw-line.png b/scripts/macos/screenshots/graphics-draw-line.png
index 1ab48a6d2b9..32067cd438f 100644
Binary files a/scripts/macos/screenshots/graphics-draw-line.png and b/scripts/macos/screenshots/graphics-draw-line.png differ
diff --git a/scripts/macos/screenshots/graphics-draw-rect.png b/scripts/macos/screenshots/graphics-draw-rect.png
index 421d6116e4a..fb3e0e92f4f 100644
Binary files a/scripts/macos/screenshots/graphics-draw-rect.png and b/scripts/macos/screenshots/graphics-draw-rect.png differ
diff --git a/scripts/macos/screenshots/graphics-draw-round-rect.png b/scripts/macos/screenshots/graphics-draw-round-rect.png
index 26d826cd401..818d25d8137 100644
Binary files a/scripts/macos/screenshots/graphics-draw-round-rect.png and b/scripts/macos/screenshots/graphics-draw-round-rect.png differ
diff --git a/scripts/macos/screenshots/graphics-draw-shape.png b/scripts/macos/screenshots/graphics-draw-shape.png
index fe587ee5eed..5ce63e476e9 100644
Binary files a/scripts/macos/screenshots/graphics-draw-shape.png and b/scripts/macos/screenshots/graphics-draw-shape.png differ
diff --git a/scripts/macos/screenshots/graphics-draw-string-decorated.png b/scripts/macos/screenshots/graphics-draw-string-decorated.png
index 5c7e59ffccf..98b5024036a 100644
Binary files a/scripts/macos/screenshots/graphics-draw-string-decorated.png and b/scripts/macos/screenshots/graphics-draw-string-decorated.png differ
diff --git a/scripts/macos/screenshots/graphics-draw-string.png b/scripts/macos/screenshots/graphics-draw-string.png
index 85613c8d1f8..5377c5cbac4 100644
Binary files a/scripts/macos/screenshots/graphics-draw-string.png and b/scripts/macos/screenshots/graphics-draw-string.png differ
diff --git a/scripts/macos/screenshots/graphics-empty-clip.png b/scripts/macos/screenshots/graphics-empty-clip.png
index d37b2d2dcaf..da58e682a43 100644
Binary files a/scripts/macos/screenshots/graphics-empty-clip.png and b/scripts/macos/screenshots/graphics-empty-clip.png differ
diff --git a/scripts/macos/screenshots/graphics-fill-arc.png b/scripts/macos/screenshots/graphics-fill-arc.png
index 1d2b774118a..dbd35c9dc13 100644
Binary files a/scripts/macos/screenshots/graphics-fill-arc.png and b/scripts/macos/screenshots/graphics-fill-arc.png differ
diff --git a/scripts/macos/screenshots/graphics-fill-polygon.png b/scripts/macos/screenshots/graphics-fill-polygon.png
index 05d47396399..93bf69ca60f 100644
Binary files a/scripts/macos/screenshots/graphics-fill-polygon.png and b/scripts/macos/screenshots/graphics-fill-polygon.png differ
diff --git a/scripts/macos/screenshots/graphics-fill-rect.png b/scripts/macos/screenshots/graphics-fill-rect.png
index 6ec2722dfe8..0d21baccae0 100644
Binary files a/scripts/macos/screenshots/graphics-fill-rect.png and b/scripts/macos/screenshots/graphics-fill-rect.png differ
diff --git a/scripts/macos/screenshots/graphics-fill-round-rect.png b/scripts/macos/screenshots/graphics-fill-round-rect.png
index e3b1f678c84..ca6aed2c617 100644
Binary files a/scripts/macos/screenshots/graphics-fill-round-rect.png and b/scripts/macos/screenshots/graphics-fill-round-rect.png differ
diff --git a/scripts/macos/screenshots/graphics-fill-shape.png b/scripts/macos/screenshots/graphics-fill-shape.png
index f459f42cfe7..82f44f7f074 100644
Binary files a/scripts/macos/screenshots/graphics-fill-shape.png and b/scripts/macos/screenshots/graphics-fill-shape.png differ
diff --git a/scripts/macos/screenshots/graphics-fill-triangle.png b/scripts/macos/screenshots/graphics-fill-triangle.png
index ee402e7a14e..a8514e95f6d 100644
Binary files a/scripts/macos/screenshots/graphics-fill-triangle.png and b/scripts/macos/screenshots/graphics-fill-triangle.png differ
diff --git a/scripts/macos/screenshots/graphics-gaussian-blur.png b/scripts/macos/screenshots/graphics-gaussian-blur.png
index 0b9939c8b7e..3ef40bf99c0 100644
Binary files a/scripts/macos/screenshots/graphics-gaussian-blur.png and b/scripts/macos/screenshots/graphics-gaussian-blur.png differ
diff --git a/scripts/macos/screenshots/graphics-inscribed-triangle-grid.png b/scripts/macos/screenshots/graphics-inscribed-triangle-grid.png
index ba8cb28ad9a..dedc875a488 100644
Binary files a/scripts/macos/screenshots/graphics-inscribed-triangle-grid.png and b/scripts/macos/screenshots/graphics-inscribed-triangle-grid.png differ
diff --git a/scripts/macos/screenshots/graphics-large-stroke-dirty-clip.png b/scripts/macos/screenshots/graphics-large-stroke-dirty-clip.png
index ca657a9d7a0..f17be207e74 100644
Binary files a/scripts/macos/screenshots/graphics-large-stroke-dirty-clip.png and b/scripts/macos/screenshots/graphics-large-stroke-dirty-clip.png differ
diff --git a/scripts/macos/screenshots/graphics-partial-flush-clip-escape.png b/scripts/macos/screenshots/graphics-partial-flush-clip-escape.png
index 095f873d7f0..9c2064c77cc 100644
Binary files a/scripts/macos/screenshots/graphics-partial-flush-clip-escape.png and b/scripts/macos/screenshots/graphics-partial-flush-clip-escape.png differ
diff --git a/scripts/macos/screenshots/graphics-rotate.png b/scripts/macos/screenshots/graphics-rotate.png
index 16f217a89ab..15b991bf145 100644
Binary files a/scripts/macos/screenshots/graphics-rotate.png and b/scripts/macos/screenshots/graphics-rotate.png differ
diff --git a/scripts/macos/screenshots/graphics-scale.png b/scripts/macos/screenshots/graphics-scale.png
index 2d0de813730..0b70ac083be 100644
Binary files a/scripts/macos/screenshots/graphics-scale.png and b/scripts/macos/screenshots/graphics-scale.png differ
diff --git a/scripts/macos/screenshots/graphics-stroke-test.png b/scripts/macos/screenshots/graphics-stroke-test.png
index a0e148609f2..793b4b38885 100644
Binary files a/scripts/macos/screenshots/graphics-stroke-test.png and b/scripts/macos/screenshots/graphics-stroke-test.png differ
diff --git a/scripts/macos/screenshots/graphics-tile-image.png b/scripts/macos/screenshots/graphics-tile-image.png
index 31c483ad4e4..08a342291c7 100644
Binary files a/scripts/macos/screenshots/graphics-tile-image.png and b/scripts/macos/screenshots/graphics-tile-image.png differ
diff --git a/scripts/macos/screenshots/graphics-transform-camera.png b/scripts/macos/screenshots/graphics-transform-camera.png
index c8313f55353..61b012cbe6b 100644
Binary files a/scripts/macos/screenshots/graphics-transform-camera.png and b/scripts/macos/screenshots/graphics-transform-camera.png differ
diff --git a/scripts/macos/screenshots/graphics-transform-perspective.png b/scripts/macos/screenshots/graphics-transform-perspective.png
index 179ad3c3bb1..32b46e18439 100644
Binary files a/scripts/macos/screenshots/graphics-transform-perspective.png and b/scripts/macos/screenshots/graphics-transform-perspective.png differ
diff --git a/scripts/macos/screenshots/graphics-transform-rotation.png b/scripts/macos/screenshots/graphics-transform-rotation.png
index d0f63135db8..bf37448862f 100644
Binary files a/scripts/macos/screenshots/graphics-transform-rotation.png and b/scripts/macos/screenshots/graphics-transform-rotation.png differ
diff --git a/scripts/macos/screenshots/graphics-transform-translation.png b/scripts/macos/screenshots/graphics-transform-translation.png
index 4327bfd49b5..60faf0d51b0 100644
Binary files a/scripts/macos/screenshots/graphics-transform-translation.png and b/scripts/macos/screenshots/graphics-transform-translation.png differ
diff --git a/scripts/macos/screenshots/kotlin.png b/scripts/macos/screenshots/kotlin.png
index 067b5ab47b9..c0b82db796e 100644
Binary files a/scripts/macos/screenshots/kotlin.png and b/scripts/macos/screenshots/kotlin.png differ
diff --git a/scripts/macos/screenshots/landscape.png b/scripts/macos/screenshots/landscape.png
index efeb919dc2e..732bdce22b9 100644
Binary files a/scripts/macos/screenshots/landscape.png and b/scripts/macos/screenshots/landscape.png differ
diff --git a/scripts/run-tv-ui-tests.sh b/scripts/run-tv-ui-tests.sh
index be9c09d0aa2..c6cbf9ff003 100755
--- a/scripts/run-tv-ui-tests.sh
+++ b/scripts/run-tv-ui-tests.sh
@@ -151,15 +151,33 @@ MAX_WAIT="${CN1SS_TV_TIMEOUT:-1200}"
TV_REF_DIR="${SCREENSHOT_REF_DIR:-$SCRIPT_DIR/ios/screenshots-tv}"
EXPECTED="$(/usr/bin/find "$TV_REF_DIR" -name '*.png' 2>/dev/null | wc -l | tr -d ' ')"
rt_log "Expecting $EXPECTED screenshots (golden set)"
-stable=0; waited=0
+stable=0; waited=0; prev=-1
while [ "$waited" -lt "$MAX_WAIT" ]; do
sleep 8; waited=$((waited+8))
cur="$(/usr/bin/find "$WS_RAW_DIR" -name '*.png' 2>/dev/null | wc -l | tr -d ' ')"
- if [ "$EXPECTED" -gt 0 ] && [ "$cur" -ge "$EXPECTED" ]; then
- stable=$((stable+1)); [ "$stable" -ge 2 ] && break
+ # The count alone is not an exit condition: EXPECTED counts the GOLDENS on disk, and a
+ # suite that captures more screenshots than there are goldens -- which is every run that
+ # adds a test, before its golden exists -- reaches EXPECTED while earlier captures are
+ # still in flight. Breaking there snapshots the directory mid-stream and reports whatever
+ # had not arrived as "Actual screenshot missing (test did not produce output)", naming
+ # tests that ran perfectly.
+ #
+ # Measured on tvOS run 35395173010: the comparison ran at 22:34:33 and called DesktopMode
+ # and Media360Panorama missing; the WebSocket sink logged both delivered, status=ok, at
+ # 22:35:04. Six new captures with no goldens had pushed the count to EXPECTED six
+ # screenshots early.
+ #
+ # So require the count to have STOPPED CHANGING as well. While captures are still
+ # arriving it keeps rising and this never fires; once it plateaus, two confirmations give
+ # the final writes their flush window. That also makes seeding a new golden possible at
+ # all, which it was not: the wait ended before the new captures landed.
+ if [ "$EXPECTED" -gt 0 ] && [ "$cur" -ge "$EXPECTED" ] && [ "$cur" -eq "$prev" ]; then
+ stable=$((stable+1)); prev="$cur"
+ [ "$stable" -ge 2 ] && break
continue
fi
stable=0
+ prev="$cur"
# The suite emits CN1SS:SUITE:FINISHED when done; bail early on that (covers
# the seed run where EXPECTED=0) or on an obvious native crash. Do not infer
# a hang from screenshot inactivity: the trailing assertion and performance
diff --git a/scripts/run-watch-ui-tests.sh b/scripts/run-watch-ui-tests.sh
index 50128fdfdbd..cda75f0e0dc 100755
--- a/scripts/run-watch-ui-tests.sh
+++ b/scripts/run-watch-ui-tests.sh
@@ -206,17 +206,35 @@ MAX_WAIT="${CN1SS_WATCH_TIMEOUT:-1200}"
WATCH_REF_DIR="${SCREENSHOT_REF_DIR:-$SCRIPT_DIR/ios/screenshots-watch}"
EXPECTED="$(/usr/bin/find "$WATCH_REF_DIR" -name '*.png' 2>/dev/null | wc -l | tr -d ' ')"
rw_log "Expecting $EXPECTED screenshots (golden set)"
-stable=0; suite_finished_stable=0; waited=0
+stable=0; suite_finished_stable=0; waited=0; prev=-1
while [ "$waited" -lt "$MAX_WAIT" ]; do
sleep 8; waited=$((waited+8))
cur="$(/usr/bin/find "$WS_RAW_DIR" -name '*.png' 2>/dev/null | wc -l | tr -d ' ')"
- # Preferred exit: the full golden set has arrived. Confirm once more so the
- # final PNG writes flush to disk before we snapshot.
- if [ "$EXPECTED" -gt 0 ] && [ "$cur" -ge "$EXPECTED" ]; then
- stable=$((stable+1)); [ "$stable" -ge 2 ] && break
+ # Preferred exit: the full golden set has arrived and stopped growing. Confirm
+ # once more so the final PNG writes flush to disk before we snapshot.
+ # The count alone is not an exit condition: EXPECTED counts the GOLDENS on disk, and a
+ # suite that captures more screenshots than there are goldens -- which is every run that
+ # adds a test, before its golden exists -- reaches EXPECTED while earlier captures are
+ # still in flight. Breaking there snapshots the directory mid-stream and reports whatever
+ # had not arrived as "Actual screenshot missing (test did not produce output)", naming
+ # tests that ran perfectly.
+ #
+ # Measured on tvOS run 35395173010: the comparison ran at 22:34:33 and called DesktopMode
+ # and Media360Panorama missing; the WebSocket sink logged both delivered, status=ok, at
+ # 22:35:04. Six new captures with no goldens had pushed the count to EXPECTED six
+ # screenshots early.
+ #
+ # So require the count to have STOPPED CHANGING as well. While captures are still
+ # arriving it keeps rising and this never fires; once it plateaus, two confirmations give
+ # the final writes their flush window. That also makes seeding a new golden possible at
+ # all, which it was not: the wait ended before the new captures landed.
+ if [ "$EXPECTED" -gt 0 ] && [ "$cur" -ge "$EXPECTED" ] && [ "$cur" -eq "$prev" ]; then
+ stable=$((stable+1)); prev="$cur"
+ [ "$stable" -ge 2 ] && break
continue
fi
stable=0
+ prev="$cur"
# stdout/stderr are attached directly by simctl launch, so the DeviceRunner
# completion marker is available here without waiting for unified-log
diff --git a/scripts/windows/screenshots/AdsScreen.png b/scripts/windows/screenshots/AdsScreen.png
index b3faea33f40..7b04f546076 100644
Binary files a/scripts/windows/screenshots/AdsScreen.png and b/scripts/windows/screenshots/AdsScreen.png differ
diff --git a/scripts/windows/screenshots/AnimateHierarchyScreenshotTest.png b/scripts/windows/screenshots/AnimateHierarchyScreenshotTest.png
index f759705467b..f024d249e10 100644
Binary files a/scripts/windows/screenshots/AnimateHierarchyScreenshotTest.png and b/scripts/windows/screenshots/AnimateHierarchyScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/AnimateLayoutScreenshotTest.png b/scripts/windows/screenshots/AnimateLayoutScreenshotTest.png
index 38d68c5d497..d86fabd6f8b 100644
Binary files a/scripts/windows/screenshots/AnimateLayoutScreenshotTest.png and b/scripts/windows/screenshots/AnimateLayoutScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/AnimateUnlayoutScreenshotTest.png b/scripts/windows/screenshots/AnimateUnlayoutScreenshotTest.png
index 95b085d917e..cf071473e9b 100644
Binary files a/scripts/windows/screenshots/AnimateUnlayoutScreenshotTest.png and b/scripts/windows/screenshots/AnimateUnlayoutScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/AppReviewDialog.png b/scripts/windows/screenshots/AppReviewDialog.png
index 69ba318be2e..1e19f39e4d7 100644
Binary files a/scripts/windows/screenshots/AppReviewDialog.png and b/scripts/windows/screenshots/AppReviewDialog.png differ
diff --git a/scripts/windows/screenshots/BrowserComponent.png b/scripts/windows/screenshots/BrowserComponent.png
index 9e3df28c55e..132e8963207 100644
Binary files a/scripts/windows/screenshots/BrowserComponent.png and b/scripts/windows/screenshots/BrowserComponent.png differ
diff --git a/scripts/windows/screenshots/ButtonTheme_dark.png b/scripts/windows/screenshots/ButtonTheme_dark.png
index 4a3902587ad..4d04b71998c 100644
Binary files a/scripts/windows/screenshots/ButtonTheme_dark.png and b/scripts/windows/screenshots/ButtonTheme_dark.png differ
diff --git a/scripts/windows/screenshots/ButtonTheme_light.png b/scripts/windows/screenshots/ButtonTheme_light.png
index 1b9fe7b4dbe..37bd7ff209c 100644
Binary files a/scripts/windows/screenshots/ButtonTheme_light.png and b/scripts/windows/screenshots/ButtonTheme_light.png differ
diff --git a/scripts/windows/screenshots/CenteredDialogTitle_dark.png b/scripts/windows/screenshots/CenteredDialogTitle_dark.png
index 696d8eb5cde..abbf20261e2 100644
Binary files a/scripts/windows/screenshots/CenteredDialogTitle_dark.png and b/scripts/windows/screenshots/CenteredDialogTitle_dark.png differ
diff --git a/scripts/windows/screenshots/CenteredDialogTitle_light.png b/scripts/windows/screenshots/CenteredDialogTitle_light.png
index 23cacac2ef2..1ea95a64f1f 100644
Binary files a/scripts/windows/screenshots/CenteredDialogTitle_light.png and b/scripts/windows/screenshots/CenteredDialogTitle_light.png differ
diff --git a/scripts/windows/screenshots/CenteredInteractionDialogTitle_dark.png b/scripts/windows/screenshots/CenteredInteractionDialogTitle_dark.png
index d0c107f3c1e..5f48c7e03d3 100644
Binary files a/scripts/windows/screenshots/CenteredInteractionDialogTitle_dark.png and b/scripts/windows/screenshots/CenteredInteractionDialogTitle_dark.png differ
diff --git a/scripts/windows/screenshots/CenteredInteractionDialogTitle_light.png b/scripts/windows/screenshots/CenteredInteractionDialogTitle_light.png
index 421a0edf068..a47c64859bc 100644
Binary files a/scripts/windows/screenshots/CenteredInteractionDialogTitle_light.png and b/scripts/windows/screenshots/CenteredInteractionDialogTitle_light.png differ
diff --git a/scripts/windows/screenshots/ChatInput_dark.png b/scripts/windows/screenshots/ChatInput_dark.png
index 31f69743187..ad14bdae5df 100644
Binary files a/scripts/windows/screenshots/ChatInput_dark.png and b/scripts/windows/screenshots/ChatInput_dark.png differ
diff --git a/scripts/windows/screenshots/ChatInput_light.png b/scripts/windows/screenshots/ChatInput_light.png
index 98197214343..94afc460582 100644
Binary files a/scripts/windows/screenshots/ChatInput_light.png and b/scripts/windows/screenshots/ChatInput_light.png differ
diff --git a/scripts/windows/screenshots/ChatView_dark.png b/scripts/windows/screenshots/ChatView_dark.png
index b86877c1761..3de3dd5ff7b 100644
Binary files a/scripts/windows/screenshots/ChatView_dark.png and b/scripts/windows/screenshots/ChatView_dark.png differ
diff --git a/scripts/windows/screenshots/ChatView_light.png b/scripts/windows/screenshots/ChatView_light.png
index 456ad3c52fa..7a3ff8b3f63 100644
Binary files a/scripts/windows/screenshots/ChatView_light.png and b/scripts/windows/screenshots/ChatView_light.png differ
diff --git a/scripts/windows/screenshots/CheckBoxRadioTheme_dark.png b/scripts/windows/screenshots/CheckBoxRadioTheme_dark.png
index 87599badf64..ab0f6cffc3d 100644
Binary files a/scripts/windows/screenshots/CheckBoxRadioTheme_dark.png and b/scripts/windows/screenshots/CheckBoxRadioTheme_dark.png differ
diff --git a/scripts/windows/screenshots/CheckBoxRadioTheme_light.png b/scripts/windows/screenshots/CheckBoxRadioTheme_light.png
index 648115bb589..6769b861da2 100644
Binary files a/scripts/windows/screenshots/CheckBoxRadioTheme_light.png and b/scripts/windows/screenshots/CheckBoxRadioTheme_light.png differ
diff --git a/scripts/windows/screenshots/CodeEditor.png b/scripts/windows/screenshots/CodeEditor.png
index 5de741d1ae7..478cd3a1bfb 100644
Binary files a/scripts/windows/screenshots/CodeEditor.png and b/scripts/windows/screenshots/CodeEditor.png differ
diff --git a/scripts/windows/screenshots/ComponentReplaceFadeScreenshotTest.png b/scripts/windows/screenshots/ComponentReplaceFadeScreenshotTest.png
index 25942718e51..e23e853ab65 100644
Binary files a/scripts/windows/screenshots/ComponentReplaceFadeScreenshotTest.png and b/scripts/windows/screenshots/ComponentReplaceFadeScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/ComponentReplaceFlipScreenshotTest.png b/scripts/windows/screenshots/ComponentReplaceFlipScreenshotTest.png
index 9a099e91d1f..a6fc95ced07 100644
Binary files a/scripts/windows/screenshots/ComponentReplaceFlipScreenshotTest.png and b/scripts/windows/screenshots/ComponentReplaceFlipScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/ComponentReplaceSlideScreenshotTest.png b/scripts/windows/screenshots/ComponentReplaceSlideScreenshotTest.png
index d6b6b04895f..5a45ddc0c25 100644
Binary files a/scripts/windows/screenshots/ComponentReplaceSlideScreenshotTest.png and b/scripts/windows/screenshots/ComponentReplaceSlideScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/CoverHorizontalTransitionTest.png b/scripts/windows/screenshots/CoverHorizontalTransitionTest.png
index 32209a95e02..f0a374d4060 100644
Binary files a/scripts/windows/screenshots/CoverHorizontalTransitionTest.png and b/scripts/windows/screenshots/CoverHorizontalTransitionTest.png differ
diff --git a/scripts/windows/screenshots/DesktopChromeTheme_dark.png b/scripts/windows/screenshots/DesktopChromeTheme_dark.png
new file mode 100644
index 00000000000..0064ecf1020
Binary files /dev/null and b/scripts/windows/screenshots/DesktopChromeTheme_dark.png differ
diff --git a/scripts/windows/screenshots/DesktopChromeTheme_light.png b/scripts/windows/screenshots/DesktopChromeTheme_light.png
new file mode 100644
index 00000000000..21eda304c98
Binary files /dev/null and b/scripts/windows/screenshots/DesktopChromeTheme_light.png differ
diff --git a/scripts/windows/screenshots/DesktopMode.png b/scripts/windows/screenshots/DesktopMode.png
index cd6487bba7a..582d2d8b74a 100644
Binary files a/scripts/windows/screenshots/DesktopMode.png and b/scripts/windows/screenshots/DesktopMode.png differ
diff --git a/scripts/windows/screenshots/DesktopScrollbarTheme_dark.png b/scripts/windows/screenshots/DesktopScrollbarTheme_dark.png
new file mode 100644
index 00000000000..2b3b041ae5b
Binary files /dev/null and b/scripts/windows/screenshots/DesktopScrollbarTheme_dark.png differ
diff --git a/scripts/windows/screenshots/DesktopScrollbarTheme_light.png b/scripts/windows/screenshots/DesktopScrollbarTheme_light.png
new file mode 100644
index 00000000000..19b0bd4c7b7
Binary files /dev/null and b/scripts/windows/screenshots/DesktopScrollbarTheme_light.png differ
diff --git a/scripts/windows/screenshots/DesktopWidgetsTheme_dark.png b/scripts/windows/screenshots/DesktopWidgetsTheme_dark.png
new file mode 100644
index 00000000000..bf9f805a678
Binary files /dev/null and b/scripts/windows/screenshots/DesktopWidgetsTheme_dark.png differ
diff --git a/scripts/windows/screenshots/DesktopWidgetsTheme_light.png b/scripts/windows/screenshots/DesktopWidgetsTheme_light.png
new file mode 100644
index 00000000000..5b24bab14ee
Binary files /dev/null and b/scripts/windows/screenshots/DesktopWidgetsTheme_light.png differ
diff --git a/scripts/windows/screenshots/DialogTheme_dark.png b/scripts/windows/screenshots/DialogTheme_dark.png
index c556843a754..25fd8a15dba 100644
Binary files a/scripts/windows/screenshots/DialogTheme_dark.png and b/scripts/windows/screenshots/DialogTheme_dark.png differ
diff --git a/scripts/windows/screenshots/DialogTheme_light.png b/scripts/windows/screenshots/DialogTheme_light.png
index 8ee3b4e344d..038727565c4 100644
Binary files a/scripts/windows/screenshots/DialogTheme_light.png and b/scripts/windows/screenshots/DialogTheme_light.png differ
diff --git a/scripts/windows/screenshots/FadeTransitionTest.png b/scripts/windows/screenshots/FadeTransitionTest.png
index da6de461a51..f4584ed5b98 100644
Binary files a/scripts/windows/screenshots/FadeTransitionTest.png and b/scripts/windows/screenshots/FadeTransitionTest.png differ
diff --git a/scripts/windows/screenshots/FlipTransitionTest.png b/scripts/windows/screenshots/FlipTransitionTest.png
index 8a15d195b95..804eae11fa1 100644
Binary files a/scripts/windows/screenshots/FlipTransitionTest.png and b/scripts/windows/screenshots/FlipTransitionTest.png differ
diff --git a/scripts/windows/screenshots/FloatingActionButtonTheme_dark.png b/scripts/windows/screenshots/FloatingActionButtonTheme_dark.png
index cb263cdfb73..81c9e5750c1 100644
Binary files a/scripts/windows/screenshots/FloatingActionButtonTheme_dark.png and b/scripts/windows/screenshots/FloatingActionButtonTheme_dark.png differ
diff --git a/scripts/windows/screenshots/FloatingActionButtonTheme_light.png b/scripts/windows/screenshots/FloatingActionButtonTheme_light.png
index 650ca151340..dc6431f5cc3 100644
Binary files a/scripts/windows/screenshots/FloatingActionButtonTheme_light.png and b/scripts/windows/screenshots/FloatingActionButtonTheme_light.png differ
diff --git a/scripts/windows/screenshots/Gpu3DAnimation.png b/scripts/windows/screenshots/Gpu3DAnimation.png
index b9c06984a0f..ce0c4f108f0 100644
Binary files a/scripts/windows/screenshots/Gpu3DAnimation.png and b/scripts/windows/screenshots/Gpu3DAnimation.png differ
diff --git a/scripts/windows/screenshots/Gpu3DCube.png b/scripts/windows/screenshots/Gpu3DCube.png
index 836a942182e..b89c5e60103 100644
Binary files a/scripts/windows/screenshots/Gpu3DCube.png and b/scripts/windows/screenshots/Gpu3DCube.png differ
diff --git a/scripts/windows/screenshots/Gpu3DModel.png b/scripts/windows/screenshots/Gpu3DModel.png
index d536638f722..391d7c6e537 100644
Binary files a/scripts/windows/screenshots/Gpu3DModel.png and b/scripts/windows/screenshots/Gpu3DModel.png differ
diff --git a/scripts/windows/screenshots/Gpu3DTexturedCube.png b/scripts/windows/screenshots/Gpu3DTexturedCube.png
index dfb524e71ac..e66ebea30f2 100644
Binary files a/scripts/windows/screenshots/Gpu3DTexturedCube.png and b/scripts/windows/screenshots/Gpu3DTexturedCube.png differ
diff --git a/scripts/windows/screenshots/ImageViewerNavigationModes.png b/scripts/windows/screenshots/ImageViewerNavigationModes.png
index 4e0fd2df3a3..6cc175854ab 100644
Binary files a/scripts/windows/screenshots/ImageViewerNavigationModes.png and b/scripts/windows/screenshots/ImageViewerNavigationModes.png differ
diff --git a/scripts/windows/screenshots/LightweightPickerButtons.png b/scripts/windows/screenshots/LightweightPickerButtons.png
index c924f4f87b6..8294df4fe5a 100644
Binary files a/scripts/windows/screenshots/LightweightPickerButtons.png and b/scripts/windows/screenshots/LightweightPickerButtons.png differ
diff --git a/scripts/windows/screenshots/LightweightPickerButtons_above_center.png b/scripts/windows/screenshots/LightweightPickerButtons_above_center.png
index df8b0590760..81352d93b2f 100644
Binary files a/scripts/windows/screenshots/LightweightPickerButtons_above_center.png and b/scripts/windows/screenshots/LightweightPickerButtons_above_center.png differ
diff --git a/scripts/windows/screenshots/LightweightPickerButtons_below_right.png b/scripts/windows/screenshots/LightweightPickerButtons_below_right.png
index 76575205da7..dc310eee59b 100644
Binary files a/scripts/windows/screenshots/LightweightPickerButtons_below_right.png and b/scripts/windows/screenshots/LightweightPickerButtons_below_right.png differ
diff --git a/scripts/windows/screenshots/LightweightPickerButtons_between_mixed.png b/scripts/windows/screenshots/LightweightPickerButtons_between_mixed.png
index 5791d6f6ba0..9d77598037e 100644
Binary files a/scripts/windows/screenshots/LightweightPickerButtons_between_mixed.png and b/scripts/windows/screenshots/LightweightPickerButtons_between_mixed.png differ
diff --git a/scripts/windows/screenshots/ListTheme_dark.png b/scripts/windows/screenshots/ListTheme_dark.png
index 89f24e33d9c..e976135b659 100644
Binary files a/scripts/windows/screenshots/ListTheme_dark.png and b/scripts/windows/screenshots/ListTheme_dark.png differ
diff --git a/scripts/windows/screenshots/ListTheme_light.png b/scripts/windows/screenshots/ListTheme_light.png
index f999190dc85..34e3c137ead 100644
Binary files a/scripts/windows/screenshots/ListTheme_light.png and b/scripts/windows/screenshots/ListTheme_light.png differ
diff --git a/scripts/windows/screenshots/MainActivity.png b/scripts/windows/screenshots/MainActivity.png
index 544c6a2cb13..1a8186c1af0 100644
Binary files a/scripts/windows/screenshots/MainActivity.png and b/scripts/windows/screenshots/MainActivity.png differ
diff --git a/scripts/windows/screenshots/Media360Panorama.png b/scripts/windows/screenshots/Media360Panorama.png
index 89057b64fd3..2d001d3d002 100644
Binary files a/scripts/windows/screenshots/Media360Panorama.png and b/scripts/windows/screenshots/Media360Panorama.png differ
diff --git a/scripts/windows/screenshots/MediaPlayback.png b/scripts/windows/screenshots/MediaPlayback.png
index 4e237a15933..531d1716fb9 100644
Binary files a/scripts/windows/screenshots/MediaPlayback.png and b/scripts/windows/screenshots/MediaPlayback.png differ
diff --git a/scripts/windows/screenshots/MorphElementMorphScreenshotTest.png b/scripts/windows/screenshots/MorphElementMorphScreenshotTest.png
index 4854ff34e4e..3ac86865437 100644
Binary files a/scripts/windows/screenshots/MorphElementMorphScreenshotTest.png and b/scripts/windows/screenshots/MorphElementMorphScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/MorphTransitionScrolledSourceTest.png b/scripts/windows/screenshots/MorphTransitionScrolledSourceTest.png
index 6a097b42ec2..369e2a3cd20 100644
Binary files a/scripts/windows/screenshots/MorphTransitionScrolledSourceTest.png and b/scripts/windows/screenshots/MorphTransitionScrolledSourceTest.png differ
diff --git a/scripts/windows/screenshots/MorphTransitionScrubScreenshotTest.png b/scripts/windows/screenshots/MorphTransitionScrubScreenshotTest.png
index bad2ee47dec..f022f24dfe0 100644
Binary files a/scripts/windows/screenshots/MorphTransitionScrubScreenshotTest.png and b/scripts/windows/screenshots/MorphTransitionScrubScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/MorphTransitionSnapshotTest.png b/scripts/windows/screenshots/MorphTransitionSnapshotTest.png
index ce13f63f163..96b2cf223ae 100644
Binary files a/scripts/windows/screenshots/MorphTransitionSnapshotTest.png and b/scripts/windows/screenshots/MorphTransitionSnapshotTest.png differ
diff --git a/scripts/windows/screenshots/MorphTransitionTest.png b/scripts/windows/screenshots/MorphTransitionTest.png
index c9f7b0f5c9f..0e3949273c5 100644
Binary files a/scripts/windows/screenshots/MorphTransitionTest.png and b/scripts/windows/screenshots/MorphTransitionTest.png differ
diff --git a/scripts/windows/screenshots/MultiButtonTheme_dark.png b/scripts/windows/screenshots/MultiButtonTheme_dark.png
index 733919bbd7c..bfde01ddbca 100644
Binary files a/scripts/windows/screenshots/MultiButtonTheme_dark.png and b/scripts/windows/screenshots/MultiButtonTheme_dark.png differ
diff --git a/scripts/windows/screenshots/MultiButtonTheme_light.png b/scripts/windows/screenshots/MultiButtonTheme_light.png
index e72fda2827d..f4f6063e14e 100644
Binary files a/scripts/windows/screenshots/MultiButtonTheme_light.png and b/scripts/windows/screenshots/MultiButtonTheme_light.png differ
diff --git a/scripts/windows/screenshots/NativeMapFallback.png b/scripts/windows/screenshots/NativeMapFallback.png
index 7a6f12dea9a..a700778092c 100644
Binary files a/scripts/windows/screenshots/NativeMapFallback.png and b/scripts/windows/screenshots/NativeMapFallback.png differ
diff --git a/scripts/windows/screenshots/PaletteOverrideTheme_dark.png b/scripts/windows/screenshots/PaletteOverrideTheme_dark.png
index 4672f404897..b01b135722d 100644
Binary files a/scripts/windows/screenshots/PaletteOverrideTheme_dark.png and b/scripts/windows/screenshots/PaletteOverrideTheme_dark.png differ
diff --git a/scripts/windows/screenshots/PaletteOverrideTheme_light.png b/scripts/windows/screenshots/PaletteOverrideTheme_light.png
index 36f941a0d87..4f4afe15766 100644
Binary files a/scripts/windows/screenshots/PaletteOverrideTheme_light.png and b/scripts/windows/screenshots/PaletteOverrideTheme_light.png differ
diff --git a/scripts/windows/screenshots/PickerTheme_dark.png b/scripts/windows/screenshots/PickerTheme_dark.png
index 1852ac08463..a774a6a8485 100644
Binary files a/scripts/windows/screenshots/PickerTheme_dark.png and b/scripts/windows/screenshots/PickerTheme_dark.png differ
diff --git a/scripts/windows/screenshots/PickerTheme_light.png b/scripts/windows/screenshots/PickerTheme_light.png
index fa33105595a..efb85a75a91 100644
Binary files a/scripts/windows/screenshots/PickerTheme_light.png and b/scripts/windows/screenshots/PickerTheme_light.png differ
diff --git a/scripts/windows/screenshots/PullToRefreshSpinnerScreenshotTest.png b/scripts/windows/screenshots/PullToRefreshSpinnerScreenshotTest.png
index 381754263f1..8501b7af707 100644
Binary files a/scripts/windows/screenshots/PullToRefreshSpinnerScreenshotTest.png and b/scripts/windows/screenshots/PullToRefreshSpinnerScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/PureEditors.png b/scripts/windows/screenshots/PureEditors.png
index d47a32fcb7f..9ac2b43bc4a 100644
Binary files a/scripts/windows/screenshots/PureEditors.png and b/scripts/windows/screenshots/PureEditors.png differ
diff --git a/scripts/windows/screenshots/RealOsmVector.png b/scripts/windows/screenshots/RealOsmVector.png
index e08e682656e..d93a80ac3f5 100644
Binary files a/scripts/windows/screenshots/RealOsmVector.png and b/scripts/windows/screenshots/RealOsmVector.png differ
diff --git a/scripts/windows/screenshots/RichTextArea.png b/scripts/windows/screenshots/RichTextArea.png
index 3b0739cd5be..ac5ace8991f 100644
Binary files a/scripts/windows/screenshots/RichTextArea.png and b/scripts/windows/screenshots/RichTextArea.png differ
diff --git a/scripts/windows/screenshots/SVGStatic.png b/scripts/windows/screenshots/SVGStatic.png
index b7d849a381a..564d8527fea 100644
Binary files a/scripts/windows/screenshots/SVGStatic.png and b/scripts/windows/screenshots/SVGStatic.png differ
diff --git a/scripts/windows/screenshots/Sheet.png b/scripts/windows/screenshots/Sheet.png
index 257c41ad7e1..c3071dd6e11 100644
Binary files a/scripts/windows/screenshots/Sheet.png and b/scripts/windows/screenshots/Sheet.png differ
diff --git a/scripts/windows/screenshots/SheetSlideUpAnimationScreenshotTest.png b/scripts/windows/screenshots/SheetSlideUpAnimationScreenshotTest.png
index 9bfb24d6a56..7c44cf6b4e3 100644
Binary files a/scripts/windows/screenshots/SheetSlideUpAnimationScreenshotTest.png and b/scripts/windows/screenshots/SheetSlideUpAnimationScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/ShowcaseTheme_dark.png b/scripts/windows/screenshots/ShowcaseTheme_dark.png
index 33acd6063c9..e0ffb554113 100644
Binary files a/scripts/windows/screenshots/ShowcaseTheme_dark.png and b/scripts/windows/screenshots/ShowcaseTheme_dark.png differ
diff --git a/scripts/windows/screenshots/ShowcaseTheme_light.png b/scripts/windows/screenshots/ShowcaseTheme_light.png
index 0945e746908..75d4cf5beb2 100644
Binary files a/scripts/windows/screenshots/ShowcaseTheme_light.png and b/scripts/windows/screenshots/ShowcaseTheme_light.png differ
diff --git a/scripts/windows/screenshots/SlideFadeTitleTransitionTest.png b/scripts/windows/screenshots/SlideFadeTitleTransitionTest.png
index 36a7aa13c36..32c9991efd2 100644
Binary files a/scripts/windows/screenshots/SlideFadeTitleTransitionTest.png and b/scripts/windows/screenshots/SlideFadeTitleTransitionTest.png differ
diff --git a/scripts/windows/screenshots/SlideHorizontalBackTransitionTest.png b/scripts/windows/screenshots/SlideHorizontalBackTransitionTest.png
index 57e6e328ac5..d3520e8516d 100644
Binary files a/scripts/windows/screenshots/SlideHorizontalBackTransitionTest.png and b/scripts/windows/screenshots/SlideHorizontalBackTransitionTest.png differ
diff --git a/scripts/windows/screenshots/SlideHorizontalTransitionTest.png b/scripts/windows/screenshots/SlideHorizontalTransitionTest.png
index d47536a6950..9d354c75bb0 100644
Binary files a/scripts/windows/screenshots/SlideHorizontalTransitionTest.png and b/scripts/windows/screenshots/SlideHorizontalTransitionTest.png differ
diff --git a/scripts/windows/screenshots/SlideVerticalTransitionTest.png b/scripts/windows/screenshots/SlideVerticalTransitionTest.png
index 7e4f5884665..02370a76544 100644
Binary files a/scripts/windows/screenshots/SlideVerticalTransitionTest.png and b/scripts/windows/screenshots/SlideVerticalTransitionTest.png differ
diff --git a/scripts/windows/screenshots/SmoothScrollScreenshotTest.png b/scripts/windows/screenshots/SmoothScrollScreenshotTest.png
index 63957572bc3..ec2f20e710e 100644
Binary files a/scripts/windows/screenshots/SmoothScrollScreenshotTest.png and b/scripts/windows/screenshots/SmoothScrollScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/SpanLabelTheme_dark.png b/scripts/windows/screenshots/SpanLabelTheme_dark.png
index 0f7231b5d89..8311c5a0f00 100644
Binary files a/scripts/windows/screenshots/SpanLabelTheme_dark.png and b/scripts/windows/screenshots/SpanLabelTheme_dark.png differ
diff --git a/scripts/windows/screenshots/SpanLabelTheme_light.png b/scripts/windows/screenshots/SpanLabelTheme_light.png
index 8cced60b109..7110f811e75 100644
Binary files a/scripts/windows/screenshots/SpanLabelTheme_light.png and b/scripts/windows/screenshots/SpanLabelTheme_light.png differ
diff --git a/scripts/windows/screenshots/StatusBarTapDiagnosticScreenshotTest.png b/scripts/windows/screenshots/StatusBarTapDiagnosticScreenshotTest.png
index 859709e87c2..70cf8d0c69d 100644
Binary files a/scripts/windows/screenshots/StatusBarTapDiagnosticScreenshotTest.png and b/scripts/windows/screenshots/StatusBarTapDiagnosticScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/StickyHeaderFadeTransitionScreenshotTest.png b/scripts/windows/screenshots/StickyHeaderFadeTransitionScreenshotTest.png
index 7c51ed7e42f..8c29d02792a 100644
Binary files a/scripts/windows/screenshots/StickyHeaderFadeTransitionScreenshotTest.png and b/scripts/windows/screenshots/StickyHeaderFadeTransitionScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/StickyHeaderScreenshotTest.png b/scripts/windows/screenshots/StickyHeaderScreenshotTest.png
index 6511a2093b9..c70cb42a4d9 100644
Binary files a/scripts/windows/screenshots/StickyHeaderScreenshotTest.png and b/scripts/windows/screenshots/StickyHeaderScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/StickyHeaderSlideTransitionScreenshotTest.png b/scripts/windows/screenshots/StickyHeaderSlideTransitionScreenshotTest.png
index 2643b647d57..ed9639512cc 100644
Binary files a/scripts/windows/screenshots/StickyHeaderSlideTransitionScreenshotTest.png and b/scripts/windows/screenshots/StickyHeaderSlideTransitionScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/SurfacesRasterizer.png b/scripts/windows/screenshots/SurfacesRasterizer.png
index 5674671b473..b0d2d459e31 100644
Binary files a/scripts/windows/screenshots/SurfacesRasterizer.png and b/scripts/windows/screenshots/SurfacesRasterizer.png differ
diff --git a/scripts/windows/screenshots/SwitchTheme_dark.png b/scripts/windows/screenshots/SwitchTheme_dark.png
index bdd97c042d7..ec8497351a6 100644
Binary files a/scripts/windows/screenshots/SwitchTheme_dark.png and b/scripts/windows/screenshots/SwitchTheme_dark.png differ
diff --git a/scripts/windows/screenshots/SwitchTheme_light.png b/scripts/windows/screenshots/SwitchTheme_light.png
index d1eed41994c..58c5ac23ddf 100644
Binary files a/scripts/windows/screenshots/SwitchTheme_light.png and b/scripts/windows/screenshots/SwitchTheme_light.png differ
diff --git a/scripts/windows/screenshots/TabsAnimatedIndicatorScreenshotTest.png b/scripts/windows/screenshots/TabsAnimatedIndicatorScreenshotTest.png
index 6a3bdd067e2..23e5bf1c00e 100644
Binary files a/scripts/windows/screenshots/TabsAnimatedIndicatorScreenshotTest.png and b/scripts/windows/screenshots/TabsAnimatedIndicatorScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/TabsBehavior.png b/scripts/windows/screenshots/TabsBehavior.png
index bfa901a45ae..6bffa724b72 100644
Binary files a/scripts/windows/screenshots/TabsBehavior.png and b/scripts/windows/screenshots/TabsBehavior.png differ
diff --git a/scripts/windows/screenshots/TabsTheme_dark.png b/scripts/windows/screenshots/TabsTheme_dark.png
index 7da1b8adc9c..af7dfe7a235 100644
Binary files a/scripts/windows/screenshots/TabsTheme_dark.png and b/scripts/windows/screenshots/TabsTheme_dark.png differ
diff --git a/scripts/windows/screenshots/TabsTheme_light.png b/scripts/windows/screenshots/TabsTheme_light.png
index 6858fd22df7..0af02087e8f 100644
Binary files a/scripts/windows/screenshots/TabsTheme_light.png and b/scripts/windows/screenshots/TabsTheme_light.png differ
diff --git a/scripts/windows/screenshots/TensileBounceScreenshotTest.png b/scripts/windows/screenshots/TensileBounceScreenshotTest.png
index 1cf248c3e91..e634c3b9170 100644
Binary files a/scripts/windows/screenshots/TensileBounceScreenshotTest.png and b/scripts/windows/screenshots/TensileBounceScreenshotTest.png differ
diff --git a/scripts/windows/screenshots/TextAreaAlignmentStates.png b/scripts/windows/screenshots/TextAreaAlignmentStates.png
index d627974ef6a..6aaced4900b 100644
Binary files a/scripts/windows/screenshots/TextAreaAlignmentStates.png and b/scripts/windows/screenshots/TextAreaAlignmentStates.png differ
diff --git a/scripts/windows/screenshots/TextFieldTheme_dark.png b/scripts/windows/screenshots/TextFieldTheme_dark.png
index b7a25932cb5..af30edffce6 100644
Binary files a/scripts/windows/screenshots/TextFieldTheme_dark.png and b/scripts/windows/screenshots/TextFieldTheme_dark.png differ
diff --git a/scripts/windows/screenshots/TextFieldTheme_light.png b/scripts/windows/screenshots/TextFieldTheme_light.png
index 2aa826b552a..db15d29a182 100644
Binary files a/scripts/windows/screenshots/TextFieldTheme_light.png and b/scripts/windows/screenshots/TextFieldTheme_light.png differ
diff --git a/scripts/windows/screenshots/ToastBarTopPosition.png b/scripts/windows/screenshots/ToastBarTopPosition.png
index 3ac062704a9..fd4993e27e7 100644
Binary files a/scripts/windows/screenshots/ToastBarTopPosition.png and b/scripts/windows/screenshots/ToastBarTopPosition.png differ
diff --git a/scripts/windows/screenshots/ToolbarTheme_dark.png b/scripts/windows/screenshots/ToolbarTheme_dark.png
index 958ec408fb2..2b25f3e3cd9 100644
Binary files a/scripts/windows/screenshots/ToolbarTheme_dark.png and b/scripts/windows/screenshots/ToolbarTheme_dark.png differ
diff --git a/scripts/windows/screenshots/ToolbarTheme_light.png b/scripts/windows/screenshots/ToolbarTheme_light.png
index 275b0fc2ef8..f5d241b3533 100644
Binary files a/scripts/windows/screenshots/ToolbarTheme_light.png and b/scripts/windows/screenshots/ToolbarTheme_light.png differ
diff --git a/scripts/windows/screenshots/UncoverHorizontalTransitionTest.png b/scripts/windows/screenshots/UncoverHorizontalTransitionTest.png
index 2f6d192ffa7..3e4873d2ee5 100644
Binary files a/scripts/windows/screenshots/UncoverHorizontalTransitionTest.png and b/scripts/windows/screenshots/UncoverHorizontalTransitionTest.png differ
diff --git a/scripts/windows/screenshots/VRStereoScene.png b/scripts/windows/screenshots/VRStereoScene.png
index d6dee09c038..a0bd6c3f76a 100644
Binary files a/scripts/windows/screenshots/VRStereoScene.png and b/scripts/windows/screenshots/VRStereoScene.png differ
diff --git a/scripts/windows/screenshots/ValidatorLightweightPicker.png b/scripts/windows/screenshots/ValidatorLightweightPicker.png
index cbdf3bd3b85..1916def8dc3 100644
Binary files a/scripts/windows/screenshots/ValidatorLightweightPicker.png and b/scripts/windows/screenshots/ValidatorLightweightPicker.png differ
diff --git a/scripts/windows/screenshots/VectorMapDarkStyle.png b/scripts/windows/screenshots/VectorMapDarkStyle.png
index d0a9d9da31c..9d7164f3865 100644
Binary files a/scripts/windows/screenshots/VectorMapDarkStyle.png and b/scripts/windows/screenshots/VectorMapDarkStyle.png differ
diff --git a/scripts/windows/screenshots/VectorMapMarkers.png b/scripts/windows/screenshots/VectorMapMarkers.png
index 2f00931ae6f..40093399220 100644
Binary files a/scripts/windows/screenshots/VectorMapMarkers.png and b/scripts/windows/screenshots/VectorMapMarkers.png differ
diff --git a/scripts/windows/screenshots/VectorMapShapes.png b/scripts/windows/screenshots/VectorMapShapes.png
index 338a95503fa..44703b0496e 100644
Binary files a/scripts/windows/screenshots/VectorMapShapes.png and b/scripts/windows/screenshots/VectorMapShapes.png differ
diff --git a/scripts/windows/screenshots/Window-Dialog-1000x400.png b/scripts/windows/screenshots/Window-Dialog-1000x400.png
index 3527fd78dda..2147f6b54b5 100644
Binary files a/scripts/windows/screenshots/Window-Dialog-1000x400.png and b/scripts/windows/screenshots/Window-Dialog-1000x400.png differ
diff --git a/scripts/windows/screenshots/Window-Dialog-400x300.png b/scripts/windows/screenshots/Window-Dialog-400x300.png
index e66bae316ab..461d710a4cd 100644
Binary files a/scripts/windows/screenshots/Window-Dialog-400x300.png and b/scripts/windows/screenshots/Window-Dialog-400x300.png differ
diff --git a/scripts/windows/screenshots/Window-Dialog-900x700.png b/scripts/windows/screenshots/Window-Dialog-900x700.png
index f787139304d..1b7ff2aa482 100644
Binary files a/scripts/windows/screenshots/Window-Dialog-900x700.png and b/scripts/windows/screenshots/Window-Dialog-900x700.png differ
diff --git a/scripts/windows/screenshots/Window-Editing-1000x400.png b/scripts/windows/screenshots/Window-Editing-1000x400.png
index 1ada770af95..e24eff11fee 100644
Binary files a/scripts/windows/screenshots/Window-Editing-1000x400.png and b/scripts/windows/screenshots/Window-Editing-1000x400.png differ
diff --git a/scripts/windows/screenshots/Window-Editing-400x300.png b/scripts/windows/screenshots/Window-Editing-400x300.png
index 16d359c4ffa..f96e5de5f3e 100644
Binary files a/scripts/windows/screenshots/Window-Editing-400x300.png and b/scripts/windows/screenshots/Window-Editing-400x300.png differ
diff --git a/scripts/windows/screenshots/Window-Editing-900x700.png b/scripts/windows/screenshots/Window-Editing-900x700.png
index 65094f353e1..570b25f17fd 100644
Binary files a/scripts/windows/screenshots/Window-Editing-900x700.png and b/scripts/windows/screenshots/Window-Editing-900x700.png differ
diff --git a/scripts/windows/screenshots/Window-Graphics-1000x400.png b/scripts/windows/screenshots/Window-Graphics-1000x400.png
index 713c933d93a..c72bacb77eb 100644
Binary files a/scripts/windows/screenshots/Window-Graphics-1000x400.png and b/scripts/windows/screenshots/Window-Graphics-1000x400.png differ
diff --git a/scripts/windows/screenshots/Window-Graphics-400x300.png b/scripts/windows/screenshots/Window-Graphics-400x300.png
index ed9e1bb7e6e..4cfdda6d834 100644
Binary files a/scripts/windows/screenshots/Window-Graphics-400x300.png and b/scripts/windows/screenshots/Window-Graphics-400x300.png differ
diff --git a/scripts/windows/screenshots/Window-Graphics-900x700.png b/scripts/windows/screenshots/Window-Graphics-900x700.png
index f4788d31af8..86a611a6b80 100644
Binary files a/scripts/windows/screenshots/Window-Graphics-900x700.png and b/scripts/windows/screenshots/Window-Graphics-900x700.png differ
diff --git a/scripts/windows/screenshots/Window-Layout-1000x400.png b/scripts/windows/screenshots/Window-Layout-1000x400.png
index 466d2fdc778..6522f251c42 100644
Binary files a/scripts/windows/screenshots/Window-Layout-1000x400.png and b/scripts/windows/screenshots/Window-Layout-1000x400.png differ
diff --git a/scripts/windows/screenshots/Window-Layout-400x300.png b/scripts/windows/screenshots/Window-Layout-400x300.png
index c53211d2b55..3c95315866b 100644
Binary files a/scripts/windows/screenshots/Window-Layout-400x300.png and b/scripts/windows/screenshots/Window-Layout-400x300.png differ
diff --git a/scripts/windows/screenshots/Window-Layout-900x700.png b/scripts/windows/screenshots/Window-Layout-900x700.png
index b6432d7e8cc..190e5360e1c 100644
Binary files a/scripts/windows/screenshots/Window-Layout-900x700.png and b/scripts/windows/screenshots/Window-Layout-900x700.png differ
diff --git a/scripts/windows/screenshots/Window-Modal-background.png b/scripts/windows/screenshots/Window-Modal-background.png
index d807b153867..e8111609854 100644
Binary files a/scripts/windows/screenshots/Window-Modal-background.png and b/scripts/windows/screenshots/Window-Modal-background.png differ
diff --git a/scripts/windows/screenshots/Window-Overlay-600x450.png b/scripts/windows/screenshots/Window-Overlay-600x450.png
index 357ede3b56c..e23168c3acc 100644
Binary files a/scripts/windows/screenshots/Window-Overlay-600x450.png and b/scripts/windows/screenshots/Window-Overlay-600x450.png differ
diff --git a/scripts/windows/screenshots/Window-Scroll-1000x400.png b/scripts/windows/screenshots/Window-Scroll-1000x400.png
index 0c1643d2b25..05d5a69c692 100644
Binary files a/scripts/windows/screenshots/Window-Scroll-1000x400.png and b/scripts/windows/screenshots/Window-Scroll-1000x400.png differ
diff --git a/scripts/windows/screenshots/Window-Scroll-400x300.png b/scripts/windows/screenshots/Window-Scroll-400x300.png
index 429c368753a..7fa22117ea1 100644
Binary files a/scripts/windows/screenshots/Window-Scroll-400x300.png and b/scripts/windows/screenshots/Window-Scroll-400x300.png differ
diff --git a/scripts/windows/screenshots/Window-Scroll-900x700.png b/scripts/windows/screenshots/Window-Scroll-900x700.png
index 65009de89c7..e0b88358530 100644
Binary files a/scripts/windows/screenshots/Window-Scroll-900x700.png and b/scripts/windows/screenshots/Window-Scroll-900x700.png differ
diff --git a/scripts/windows/screenshots/chart-bar-stacked.png b/scripts/windows/screenshots/chart-bar-stacked.png
index 51f4df70fc1..eabd3af3975 100644
Binary files a/scripts/windows/screenshots/chart-bar-stacked.png and b/scripts/windows/screenshots/chart-bar-stacked.png differ
diff --git a/scripts/windows/screenshots/chart-bar.png b/scripts/windows/screenshots/chart-bar.png
index 1d52e43ecea..eff6f7dda63 100644
Binary files a/scripts/windows/screenshots/chart-bar.png and b/scripts/windows/screenshots/chart-bar.png differ
diff --git a/scripts/windows/screenshots/chart-bubble.png b/scripts/windows/screenshots/chart-bubble.png
index b21069ec0c3..d7a29d4221c 100644
Binary files a/scripts/windows/screenshots/chart-bubble.png and b/scripts/windows/screenshots/chart-bubble.png differ
diff --git a/scripts/windows/screenshots/chart-combined-xy.png b/scripts/windows/screenshots/chart-combined-xy.png
index 89c1b27376c..8502ac1f5a9 100644
Binary files a/scripts/windows/screenshots/chart-combined-xy.png and b/scripts/windows/screenshots/chart-combined-xy.png differ
diff --git a/scripts/windows/screenshots/chart-cubic-line.png b/scripts/windows/screenshots/chart-cubic-line.png
index 148ceceb9b2..75be2c0ac16 100644
Binary files a/scripts/windows/screenshots/chart-cubic-line.png and b/scripts/windows/screenshots/chart-cubic-line.png differ
diff --git a/scripts/windows/screenshots/chart-doughnut.png b/scripts/windows/screenshots/chart-doughnut.png
index ce01ce5b81b..c91b87fabb1 100644
Binary files a/scripts/windows/screenshots/chart-doughnut.png and b/scripts/windows/screenshots/chart-doughnut.png differ
diff --git a/scripts/windows/screenshots/chart-line.png b/scripts/windows/screenshots/chart-line.png
index d4a2f0cfe40..b7847a0e092 100644
Binary files a/scripts/windows/screenshots/chart-line.png and b/scripts/windows/screenshots/chart-line.png differ
diff --git a/scripts/windows/screenshots/chart-pie.png b/scripts/windows/screenshots/chart-pie.png
index f1cd3fbb886..55494cfe044 100644
Binary files a/scripts/windows/screenshots/chart-pie.png and b/scripts/windows/screenshots/chart-pie.png differ
diff --git a/scripts/windows/screenshots/chart-radar.png b/scripts/windows/screenshots/chart-radar.png
index 4ab91012650..f2fc5e99e55 100644
Binary files a/scripts/windows/screenshots/chart-radar.png and b/scripts/windows/screenshots/chart-radar.png differ
diff --git a/scripts/windows/screenshots/chart-range-bar.png b/scripts/windows/screenshots/chart-range-bar.png
index 9d2675e6553..30ebe94b1be 100644
Binary files a/scripts/windows/screenshots/chart-range-bar.png and b/scripts/windows/screenshots/chart-range-bar.png differ
diff --git a/scripts/windows/screenshots/chart-rotated-pie.png b/scripts/windows/screenshots/chart-rotated-pie.png
index 9b605a79598..d81aa186b7c 100644
Binary files a/scripts/windows/screenshots/chart-rotated-pie.png and b/scripts/windows/screenshots/chart-rotated-pie.png differ
diff --git a/scripts/windows/screenshots/chart-scatter.png b/scripts/windows/screenshots/chart-scatter.png
index a1fea31113f..9965dea0a31 100644
Binary files a/scripts/windows/screenshots/chart-scatter.png and b/scripts/windows/screenshots/chart-scatter.png differ
diff --git a/scripts/windows/screenshots/chart-time.png b/scripts/windows/screenshots/chart-time.png
index 4bc1146f78d..f635607abd6 100644
Binary files a/scripts/windows/screenshots/chart-time.png and b/scripts/windows/screenshots/chart-time.png differ
diff --git a/scripts/windows/screenshots/chart-transform.png b/scripts/windows/screenshots/chart-transform.png
index 42a7ed2946a..98ebc695330 100644
Binary files a/scripts/windows/screenshots/chart-transform.png and b/scripts/windows/screenshots/chart-transform.png differ
diff --git a/scripts/windows/screenshots/css-gradients.png b/scripts/windows/screenshots/css-gradients.png
index 7e4847c8ac8..b872fc4bd06 100644
Binary files a/scripts/windows/screenshots/css-gradients.png and b/scripts/windows/screenshots/css-gradients.png differ
diff --git a/scripts/windows/screenshots/graphics-affine-scale.png b/scripts/windows/screenshots/graphics-affine-scale.png
index 7111c511944..8118af299c2 100644
Binary files a/scripts/windows/screenshots/graphics-affine-scale.png and b/scripts/windows/screenshots/graphics-affine-scale.png differ
diff --git a/scripts/windows/screenshots/graphics-clip-under-rotation.png b/scripts/windows/screenshots/graphics-clip-under-rotation.png
index ac6f50306ff..8c753f17559 100644
Binary files a/scripts/windows/screenshots/graphics-clip-under-rotation.png and b/scripts/windows/screenshots/graphics-clip-under-rotation.png differ
diff --git a/scripts/windows/screenshots/graphics-clip.png b/scripts/windows/screenshots/graphics-clip.png
index cd8ea826d52..5bb5ed9df19 100644
Binary files a/scripts/windows/screenshots/graphics-clip.png and b/scripts/windows/screenshots/graphics-clip.png differ
diff --git a/scripts/windows/screenshots/graphics-draw-arc.png b/scripts/windows/screenshots/graphics-draw-arc.png
index 795418fc437..098358b0fc3 100644
Binary files a/scripts/windows/screenshots/graphics-draw-arc.png and b/scripts/windows/screenshots/graphics-draw-arc.png differ
diff --git a/scripts/windows/screenshots/graphics-draw-gradient-stops.png b/scripts/windows/screenshots/graphics-draw-gradient-stops.png
index 3ac8682fbb5..45376b36e60 100644
Binary files a/scripts/windows/screenshots/graphics-draw-gradient-stops.png and b/scripts/windows/screenshots/graphics-draw-gradient-stops.png differ
diff --git a/scripts/windows/screenshots/graphics-draw-gradient.png b/scripts/windows/screenshots/graphics-draw-gradient.png
index 0ab16d4fe81..3541bf28044 100644
Binary files a/scripts/windows/screenshots/graphics-draw-gradient.png and b/scripts/windows/screenshots/graphics-draw-gradient.png differ
diff --git a/scripts/windows/screenshots/graphics-draw-image-rect.png b/scripts/windows/screenshots/graphics-draw-image-rect.png
index 8353ce64493..8080aa8ca01 100644
Binary files a/scripts/windows/screenshots/graphics-draw-image-rect.png and b/scripts/windows/screenshots/graphics-draw-image-rect.png differ
diff --git a/scripts/windows/screenshots/graphics-draw-line.png b/scripts/windows/screenshots/graphics-draw-line.png
index f7e5b1ec353..e73d6e8297e 100644
Binary files a/scripts/windows/screenshots/graphics-draw-line.png and b/scripts/windows/screenshots/graphics-draw-line.png differ
diff --git a/scripts/windows/screenshots/graphics-draw-rect.png b/scripts/windows/screenshots/graphics-draw-rect.png
index 9b953190b30..f787945ead5 100644
Binary files a/scripts/windows/screenshots/graphics-draw-rect.png and b/scripts/windows/screenshots/graphics-draw-rect.png differ
diff --git a/scripts/windows/screenshots/graphics-draw-round-rect.png b/scripts/windows/screenshots/graphics-draw-round-rect.png
index cdb59449938..cb1d7aa35f6 100644
Binary files a/scripts/windows/screenshots/graphics-draw-round-rect.png and b/scripts/windows/screenshots/graphics-draw-round-rect.png differ
diff --git a/scripts/windows/screenshots/graphics-draw-shape.png b/scripts/windows/screenshots/graphics-draw-shape.png
index 2e1bbcd04c7..49e33415d7b 100644
Binary files a/scripts/windows/screenshots/graphics-draw-shape.png and b/scripts/windows/screenshots/graphics-draw-shape.png differ
diff --git a/scripts/windows/screenshots/graphics-draw-string-decorated.png b/scripts/windows/screenshots/graphics-draw-string-decorated.png
index 2077c480605..9fbbcc695b6 100644
Binary files a/scripts/windows/screenshots/graphics-draw-string-decorated.png and b/scripts/windows/screenshots/graphics-draw-string-decorated.png differ
diff --git a/scripts/windows/screenshots/graphics-draw-string.png b/scripts/windows/screenshots/graphics-draw-string.png
index a8e20819141..e0f79d1e201 100644
Binary files a/scripts/windows/screenshots/graphics-draw-string.png and b/scripts/windows/screenshots/graphics-draw-string.png differ
diff --git a/scripts/windows/screenshots/graphics-empty-clip.png b/scripts/windows/screenshots/graphics-empty-clip.png
index 741b6f955f4..8666971370e 100644
Binary files a/scripts/windows/screenshots/graphics-empty-clip.png and b/scripts/windows/screenshots/graphics-empty-clip.png differ
diff --git a/scripts/windows/screenshots/graphics-fill-arc.png b/scripts/windows/screenshots/graphics-fill-arc.png
index e849c125b7e..0c6cae85a74 100644
Binary files a/scripts/windows/screenshots/graphics-fill-arc.png and b/scripts/windows/screenshots/graphics-fill-arc.png differ
diff --git a/scripts/windows/screenshots/graphics-fill-polygon.png b/scripts/windows/screenshots/graphics-fill-polygon.png
index c9267922997..31b4fad2afe 100644
Binary files a/scripts/windows/screenshots/graphics-fill-polygon.png and b/scripts/windows/screenshots/graphics-fill-polygon.png differ
diff --git a/scripts/windows/screenshots/graphics-fill-rect.png b/scripts/windows/screenshots/graphics-fill-rect.png
index 53e637b6e64..87d6711a677 100644
Binary files a/scripts/windows/screenshots/graphics-fill-rect.png and b/scripts/windows/screenshots/graphics-fill-rect.png differ
diff --git a/scripts/windows/screenshots/graphics-fill-round-rect.png b/scripts/windows/screenshots/graphics-fill-round-rect.png
index acb159bbcca..673230a1c68 100644
Binary files a/scripts/windows/screenshots/graphics-fill-round-rect.png and b/scripts/windows/screenshots/graphics-fill-round-rect.png differ
diff --git a/scripts/windows/screenshots/graphics-fill-shape.png b/scripts/windows/screenshots/graphics-fill-shape.png
index 19307627fc9..d3e53eabaa0 100644
Binary files a/scripts/windows/screenshots/graphics-fill-shape.png and b/scripts/windows/screenshots/graphics-fill-shape.png differ
diff --git a/scripts/windows/screenshots/graphics-fill-triangle.png b/scripts/windows/screenshots/graphics-fill-triangle.png
index 057835ef907..bd7320aefd2 100644
Binary files a/scripts/windows/screenshots/graphics-fill-triangle.png and b/scripts/windows/screenshots/graphics-fill-triangle.png differ
diff --git a/scripts/windows/screenshots/graphics-gaussian-blur.png b/scripts/windows/screenshots/graphics-gaussian-blur.png
index b8fcc984f5d..a7879e72c69 100644
Binary files a/scripts/windows/screenshots/graphics-gaussian-blur.png and b/scripts/windows/screenshots/graphics-gaussian-blur.png differ
diff --git a/scripts/windows/screenshots/graphics-inscribed-triangle-grid.png b/scripts/windows/screenshots/graphics-inscribed-triangle-grid.png
index bd155e2f75e..85aaa2d50bc 100644
Binary files a/scripts/windows/screenshots/graphics-inscribed-triangle-grid.png and b/scripts/windows/screenshots/graphics-inscribed-triangle-grid.png differ
diff --git a/scripts/windows/screenshots/graphics-large-stroke-dirty-clip.png b/scripts/windows/screenshots/graphics-large-stroke-dirty-clip.png
index c63f291fb0b..c61d505dab9 100644
Binary files a/scripts/windows/screenshots/graphics-large-stroke-dirty-clip.png and b/scripts/windows/screenshots/graphics-large-stroke-dirty-clip.png differ
diff --git a/scripts/windows/screenshots/graphics-partial-flush-clip-escape.png b/scripts/windows/screenshots/graphics-partial-flush-clip-escape.png
index 58ecba828b1..dc270356e28 100644
Binary files a/scripts/windows/screenshots/graphics-partial-flush-clip-escape.png and b/scripts/windows/screenshots/graphics-partial-flush-clip-escape.png differ
diff --git a/scripts/windows/screenshots/graphics-rotate.png b/scripts/windows/screenshots/graphics-rotate.png
index 69bdbc25f78..0f444762beb 100644
Binary files a/scripts/windows/screenshots/graphics-rotate.png and b/scripts/windows/screenshots/graphics-rotate.png differ
diff --git a/scripts/windows/screenshots/graphics-scale.png b/scripts/windows/screenshots/graphics-scale.png
index e8c55801898..4793ce93833 100644
Binary files a/scripts/windows/screenshots/graphics-scale.png and b/scripts/windows/screenshots/graphics-scale.png differ
diff --git a/scripts/windows/screenshots/graphics-stroke-test.png b/scripts/windows/screenshots/graphics-stroke-test.png
index 9382e7e8a94..ec714dc2fe6 100644
Binary files a/scripts/windows/screenshots/graphics-stroke-test.png and b/scripts/windows/screenshots/graphics-stroke-test.png differ
diff --git a/scripts/windows/screenshots/graphics-tile-image.png b/scripts/windows/screenshots/graphics-tile-image.png
index 020124405cd..eb52e03cc09 100644
Binary files a/scripts/windows/screenshots/graphics-tile-image.png and b/scripts/windows/screenshots/graphics-tile-image.png differ
diff --git a/scripts/windows/screenshots/graphics-transform-camera.png b/scripts/windows/screenshots/graphics-transform-camera.png
index e587e599bb9..8442046b9a2 100644
Binary files a/scripts/windows/screenshots/graphics-transform-camera.png and b/scripts/windows/screenshots/graphics-transform-camera.png differ
diff --git a/scripts/windows/screenshots/graphics-transform-perspective.png b/scripts/windows/screenshots/graphics-transform-perspective.png
index 12ead2523bb..25f0ed8cd31 100644
Binary files a/scripts/windows/screenshots/graphics-transform-perspective.png and b/scripts/windows/screenshots/graphics-transform-perspective.png differ
diff --git a/scripts/windows/screenshots/graphics-transform-rotation.png b/scripts/windows/screenshots/graphics-transform-rotation.png
index 8ad6e48b3b4..357be5b8d50 100644
Binary files a/scripts/windows/screenshots/graphics-transform-rotation.png and b/scripts/windows/screenshots/graphics-transform-rotation.png differ
diff --git a/scripts/windows/screenshots/graphics-transform-translation.png b/scripts/windows/screenshots/graphics-transform-translation.png
index 9f6f3202bfe..e1191719d28 100644
Binary files a/scripts/windows/screenshots/graphics-transform-translation.png and b/scripts/windows/screenshots/graphics-transform-translation.png differ
diff --git a/scripts/windows/screenshots/kotlin.png b/scripts/windows/screenshots/kotlin.png
index 9d4d5fb8604..946bd2d37e7 100644
Binary files a/scripts/windows/screenshots/kotlin.png and b/scripts/windows/screenshots/kotlin.png differ
diff --git a/scripts/windows/screenshots/landscape.png b/scripts/windows/screenshots/landscape.png
index f111f007eb0..fcd6379ce12 100644
Binary files a/scripts/windows/screenshots/landscape.png and b/scripts/windows/screenshots/landscape.png differ