Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
8f3e451
Expand CSS gradient + filter:blur support across framework, ports, docs
shai-almog May 16, 2026
9c7faef
CI fixes + hellocodenameone screenshot tests for new gradient/filter …
shai-almog May 16, 2026
1d8161a
CI: pass surefire.failIfNoSpecifiedTests in designer workflow
shai-almog May 16, 2026
172cb62
Strip non-ASCII characters from new source files
shai-almog May 16, 2026
7be5658
Fix SpotBugs violations from the gradient/blur additions
shai-almog May 16, 2026
f281d86
Fix PMD violations in GradientDescriptor
shai-almog May 16, 2026
0a6f3ca
Refactor: Gradient hierarchy + uniform surefire + Android hang fix
shai-almog May 16, 2026
001f5a5
Drop unused Gradient-subclass imports in CodenameOneImplementation
shai-almog May 16, 2026
465d9f1
Fix CI failures: AsyncGraphics.fillGradient, simctl, native-iOS sim d…
shai-almog May 16, 2026
12cc3ce
Add JS-port reference screenshots for DrawGradientStops + GaussianBlur
shai-almog May 16, 2026
b622748
CSS compiler: route conic-gradient and repeating-*-gradient through t…
shai-almog May 16, 2026
a0b4b31
Fix repeating-* gradient rendering + CSS compiler cache invalidation
shai-almog May 16, 2026
58fc371
Split designer install from android/ios port build
shai-almog May 16, 2026
7ae68ea
Split combined declaration to satisfy PMD OneDeclarationPerLine
shai-almog May 16, 2026
884b029
Fix software repeating-gradient sampling + smoother demo CSS
shai-almog May 16, 2026
b0d180b
Bake new screenshot goldens for css-gradients + gradient-stops/gaussi…
shai-almog May 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/designer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
mvn -B -pl designer -am -DunitTests=true -Dcodename1.platform=javase \
-Plocal-dev-javase -Dmaven.javadoc.skip=true -Dmaven.antrun.skip=true \
-Dcn1.binaries="${CN1_BINARIES}" \
-Dtest=SimpleXmlParserTest -DfailIfNoTests=false test
-Dtest=SimpleXmlParserTest -Dsurefire.failIfNoSpecifiedTests=false test

- name: Verify designer CLI CSS compilation
run: |
Expand Down
43 changes: 43 additions & 0 deletions CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import com.codename1.ui.geom.Rectangle;
import com.codename1.ui.geom.Shape;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.Gradient;
import com.codename1.ui.plaf.Style;
import com.codename1.ui.util.ImageIO;
import com.codename1.util.AsyncResource;
Expand Down Expand Up @@ -3378,6 +3379,31 @@ private void fillLinearGradientImpl(Object graphics, int startColor, int endColo
setColor(graphics, oldColor);
}

/// Fills the rectangle (x, y, width, height) with the given multi-stop
/// gradient. Default implementation rasterizes into an ARGB pixel buffer
/// via the gradient's `sampleArgb(...)` hook; ports with hardware shader
/// support should override.
public void fillGradient(Object graphics, Gradient gradient, int x, int y, int width, int height) {
if (gradient == null || width <= 0 || height <= 0) {
return;
}
int[] rgb = new int[width * height];
for (int py = 0; py < height; py++) {
int row = py * width;
for (int px = 0; px < width; px++) {
rgb[row + px] = gradient.sampleArgb(px, py, width, height);
}
}
Object img = createImage(rgb, width, height);
drawImage(graphics, img, x, y);
}

/// In-place region blur for CSS backdrop-filter:blur(). Default returns false
/// signalling no in-place support - caller falls back to snapshot+blur.
public boolean blurRegion(Object graphics, int x, int y, int width, int height, float radius) {
return false;
}

private boolean checkIntersection(Object g, int y0, int x1, int x2, int y1, int y2, int[] intersections, int intersectionsCount) {
if (y0 > y1 && y0 < y2 || y0 > y2 && y0 < y1) {
if (y1 == y2) {
Expand Down Expand Up @@ -9279,6 +9305,11 @@ public void paintComponentBackground(Object nativeGraphics, int x, int y, int wi
case Style.BACKGROUND_GRADIENT_LINEAR_HORIZONTAL:
case Style.BACKGROUND_GRADIENT_LINEAR_VERTICAL:
case Style.BACKGROUND_GRADIENT_RADIAL:
case Style.BACKGROUND_GRADIENT_LINEAR:
case Style.BACKGROUND_GRADIENT_RADIAL_FULL:
case Style.BACKGROUND_GRADIENT_CONIC:
case Style.BACKGROUND_GRADIENT_REPEATING_LINEAR:
case Style.BACKGROUND_GRADIENT_REPEATING_RADIAL:
drawGradientBackground(s, nativeGraphics, x, y, width, height);
return;
default:
Expand Down Expand Up @@ -9312,6 +9343,18 @@ private void drawGradientBackground(Style s, Object nativeGraphics, int x, int y
x, y, width, height, s.getBackgroundGradientRelativeX(), s.getBackgroundGradientRelativeY(),
s.getBackgroundGradientRelativeSize());
return;
case Style.BACKGROUND_GRADIENT_LINEAR:
case Style.BACKGROUND_GRADIENT_RADIAL_FULL:
case Style.BACKGROUND_GRADIENT_CONIC:
case Style.BACKGROUND_GRADIENT_REPEATING_LINEAR:
case Style.BACKGROUND_GRADIENT_REPEATING_RADIAL: {
Gradient g = s.getGradient();
if (g != null) {
fillGradient(nativeGraphics, g, x, y, width, height);
return;
}
break;
}
default:
// Style.BACKGROUND_NONE
if (s.getBgTransparency() != 0) {
Expand Down
107 changes: 107 additions & 0 deletions CodenameOne/src/com/codename1/ui/ConicGradient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.codename1.ui;

import com.codename1.util.MathUtil;

/// Conic (sweep) gradient. Mirrors CSS `conic-gradient([from <angle>] [at <pos>], <stops>)`.
/// `fromAngleDegrees` follows the CSS convention: 0 degrees points up
/// (toward the top edge), sweep is clockwise.
///
/// Conic gradients have no notion of cycle method - the [0,1] stop range
/// always wraps around the circle once.
///
/// #### Since
///
/// 8.1
public final class ConicGradient extends Gradient {
private float fromAngleDegrees;
private float relativeCenterX = 0.5f;
private float relativeCenterY = 0.5f;

public ConicGradient(int[] colors, float[] positions) {
super(colors, positions);
}

@Override
public byte getKind() {
return KIND_CONIC;
}

public float getFromAngleDegrees() {
return fromAngleDegrees;
}

public ConicGradient setFromAngleDegrees(float fromAngleDegrees) {
this.fromAngleDegrees = fromAngleDegrees;
return this;
}

public float getRelativeCenterX() {
return relativeCenterX;
}

public ConicGradient setRelativeCenterX(float relativeCenterX) {
this.relativeCenterX = relativeCenterX;
return this;
}

public float getRelativeCenterY() {
return relativeCenterY;
}

public ConicGradient setRelativeCenterY(float relativeCenterY) {
this.relativeCenterY = relativeCenterY;
return this;
}

@Override
public int sampleArgb(int px, int py, int width, int height) {
double cx = relativeCenterX * width;
double cy = relativeCenterY * height;
double dx = px + 0.5 - cx;
double dy = py + 0.5 - cy;
// CSS conic: 0 degrees at top (north), sweep clockwise.
double theta = MathUtil.atan2(dx, -dy) - Math.toRadians(fromAngleDegrees);
double normalized = theta / (Math.PI * 2.0);
normalized -= Math.floor(normalized);
return sampleStops((float) normalized);
}

@Override
public ConicGradient copy() {
int[] c = getColors();
float[] p = getPositions();
int[] cc = new int[c.length];
float[] pp = new float[p.length];
System.arraycopy(c, 0, cc, 0, c.length);
System.arraycopy(p, 0, pp, 0, p.length);
ConicGradient g = new ConicGradient(cc, pp);
g.setCycleMethod(getCycleMethod());
g.fromAngleDegrees = fromAngleDegrees;
g.relativeCenterX = relativeCenterX;
g.relativeCenterY = relativeCenterY;
return g;
}
}
189 changes: 189 additions & 0 deletions CodenameOne/src/com/codename1/ui/Gradient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.codename1.ui;

import com.codename1.ui.geom.Rectangle2D;

/// Abstract description of a CSS-style gradient that can be used as a
/// background or painted directly via `Graphics#fillGradient`. Three
/// concrete subclasses mirror the corresponding CSS functions:
///
/// - `LinearGradient` for `linear-gradient(<angle>, <stops>)`
/// - `RadialGradient` for `radial-gradient([shape] [extent] [at pos], <stops>)`
/// - `ConicGradient` for `conic-gradient([from <angle>] [at pos], <stops>)`
///
/// A Gradient is a [Paint], so it can also be assigned via `Graphics#setColor(Paint)`
/// and consumed by `fillRect` / `fillShape`. The dedicated
/// `Graphics#fillGradient(Gradient, int, int, int, int)` entry point gives
/// the platform port the rectangle bounds up front so it can pick the
/// fastest native shader path (Java2D `LinearGradientPaint` /
/// `RadialGradientPaint`, Android `LinearGradient` / `RadialGradient` /
/// `SweepGradient`, Core Graphics `CGGradient`).
///
/// Subclass instances are intended to be immutable after construction;
/// modify via builder-style setters before handing the gradient off to
/// `Graphics` or `Style`. `copy()` produces a defensive deep clone for
/// places that must outlive caller mutation (e.g. async paint queues).
///
/// #### Since
///
/// 8.1
public abstract class Gradient implements Paint {
/// Sentinel returned by `getKind()` for `LinearGradient` instances.
public static final byte KIND_LINEAR = 0;
/// Sentinel returned by `getKind()` for `RadialGradient` instances.
public static final byte KIND_RADIAL = 1;
/// Sentinel returned by `getKind()` for `ConicGradient` instances.
public static final byte KIND_CONIC = 2;

/// Cycle modes mirroring `MultipleGradientPaint.CycleMethod`. Repeated as
/// byte constants here so that this class (and the .res serializer) does
/// not pull the enum across the resource format boundary.
public static final byte CYCLE_NONE = 0;
public static final byte CYCLE_REPEAT = 1;
public static final byte CYCLE_REFLECT = 2;

private int[] colors;
private float[] positions;
private byte cycleMethod = CYCLE_NONE;

Gradient(int[] colors, float[] positions) {
if (colors == null || positions == null || colors.length != positions.length || colors.length < 2) {
throw new IllegalArgumentException("colors and positions must be same length, at least 2");
}
this.colors = colors;
this.positions = positions;
}

/// Returns one of `KIND_LINEAR`, `KIND_RADIAL`, `KIND_CONIC`.
public abstract byte getKind();

/// ARGB stop colors (length >= 2).
public final int[] getColors() {
return colors;
}

/// Stop positions in [0,1] aligned with `getColors()`.
public final float[] getPositions() {
return positions;
}

/// One of `CYCLE_NONE` / `CYCLE_REPEAT` / `CYCLE_REFLECT`. Defaults to NONE.
public final byte getCycleMethod() {
return cycleMethod;
}

/// Sets the cycle method. Returns `this` for chaining.
public final Gradient setCycleMethod(byte cycleMethod) {
this.cycleMethod = cycleMethod;
return this;
}

/// Returns a defensive deep copy. Implemented by each concrete subclass
/// so async-paint queues can capture an immutable snapshot.
public abstract Gradient copy();

@Override
public final void paint(Graphics g, Rectangle2D bounds) {
paint(g, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
}

@Override
public final void paint(Graphics g, double x, double y, double w, double h) {
g.fillGradient(this, (int) x, (int) y, (int) w, (int) h);
}

/// Software-rasterizer hook used by the default port implementation when
/// no native gradient shader is available. Samples an ARGB color for the
/// pixel at (px, py) within a rectangle of the given width / height.
/// Ports overriding `fillGradient` directly do not call this.
public abstract int sampleArgb(int px, int py, int width, int height);

/// Samples one of the stops at fractional position t. Honors the
/// configured cycle method. Shared by the three subclasses' sampling
/// implementations.
///
/// CSS `repeating-*-gradient` stops define one period from
/// `positions[0]` to `positions[last]`, not `[0, 1]`. For
/// `white 0%, red 16%` the period is 0.16 of the gradient extent and
/// the pattern must wrap on that range; collapsing to `t - floor(t)`
/// would leak the final color across the rest of the rect.
protected final int sampleStops(float t) {
float p0 = positions[0];
float pN = positions[positions.length - 1];
float period = pN - p0;
switch (cycleMethod) {
case CYCLE_REPEAT:
if (period > 0) {
float rel = (t - p0) / period;
rel = rel - (float) Math.floor(rel);
t = p0 + rel * period;
}
break;
case CYCLE_REFLECT:
if (period > 0) {
float rel = Math.abs((t - p0) / period);
float intp = (float) Math.floor(rel);
float frac = rel - intp;
if ((((int) intp) & 1) != 0) {
frac = 1f - frac;
}
t = p0 + frac * period;
}
break;
default:
if (t <= p0) {
return colors[0];
}
if (t >= pN) {
return colors[colors.length - 1];
}
break;
}
for (int i = 1; i < positions.length; i++) {
if (t <= positions[i]) {
float span = positions[i] - positions[i - 1];
float local = span <= 0 ? 0 : (t - positions[i - 1]) / span;
return blendArgb(colors[i - 1], colors[i], local);
}
}
return colors[colors.length - 1];
}

static int blendArgb(int c0, int c1, float t) {
int a0 = (c0 >> 24) & 0xff;
int r0 = (c0 >> 16) & 0xff;
int g0 = (c0 >> 8) & 0xff;
int b0 = c0 & 0xff;
int a1 = (c1 >> 24) & 0xff;
int r1 = (c1 >> 16) & 0xff;
int g1 = (c1 >> 8) & 0xff;
int b1 = c1 & 0xff;
int a = (int) (a0 + (a1 - a0) * t + 0.5f);
int r = (int) (r0 + (r1 - r0) * t + 0.5f);
int g = (int) (g0 + (g1 - g0) * t + 0.5f);
int b = (int) (b0 + (b1 - b0) * t + 0.5f);
return (a << 24) | (r << 16) | (g << 8) | b;
}
}
Loading
Loading