exodeui-react-native 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/engine.ts +67 -51
package/package.json
CHANGED
package/src/engine.ts
CHANGED
|
@@ -1535,8 +1535,19 @@ export class ExodeUIEngine {
|
|
|
1535
1535
|
|
|
1536
1536
|
canvas.save();
|
|
1537
1537
|
canvas.translate(cx, cy);
|
|
1538
|
-
|
|
1539
|
-
|
|
1538
|
+
|
|
1539
|
+
// Rotate around the center of the object
|
|
1540
|
+
if (rotation !== 0) {
|
|
1541
|
+
canvas.translate(w / 2, h / 2);
|
|
1542
|
+
canvas.rotate(rotation * Math.PI / 180, 0, 0);
|
|
1543
|
+
canvas.translate(-w / 2, -h / 2);
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
if (scaleX !== 1 || scaleY !== 1) {
|
|
1547
|
+
canvas.translate(w / 2, h / 2);
|
|
1548
|
+
canvas.scale(scaleX, scaleY);
|
|
1549
|
+
canvas.translate(-w / 2, -h / 2);
|
|
1550
|
+
}
|
|
1540
1551
|
|
|
1541
1552
|
if (this._renderCount % 120 === 1) {
|
|
1542
1553
|
console.log(`[ExodeUIEngine] - Drawing object: ${obj.name || obj.id} (${obj.type}), pos: ${cx.toFixed(1)},${cy.toFixed(1)}`);
|
|
@@ -1638,15 +1649,15 @@ export class ExodeUIEngine {
|
|
|
1638
1649
|
const segments = geom.segments || obj.segments || [];
|
|
1639
1650
|
|
|
1640
1651
|
if (enableSegments && segments.length > 0) {
|
|
1641
|
-
let offsetX =
|
|
1652
|
+
let offsetX = 0;
|
|
1642
1653
|
const totalWidth = segments.reduce((acc: number, seg: any) => {
|
|
1643
1654
|
const font = this.getFont(seg.fontSize || geom.fontSize || 14, seg.fontFamily || geom.fontFamily || 'System');
|
|
1644
1655
|
return acc + font.getTextWidth(seg.text);
|
|
1645
1656
|
}, 0);
|
|
1646
1657
|
|
|
1647
1658
|
const align = geom.textAlign || 'left';
|
|
1648
|
-
if (align === 'center') offsetX = -totalWidth / 2;
|
|
1649
|
-
else if (align === 'right') offsetX = w
|
|
1659
|
+
if (align === 'center') offsetX = (w - totalWidth) / 2;
|
|
1660
|
+
else if (align === 'right') offsetX = w - totalWidth;
|
|
1650
1661
|
|
|
1651
1662
|
segments.forEach((seg: any) => {
|
|
1652
1663
|
const font = this.getFont(seg.fontSize || geom.fontSize || 14, seg.fontFamily || geom.fontFamily || 'System');
|
|
@@ -1655,7 +1666,9 @@ export class ExodeUIEngine {
|
|
|
1655
1666
|
try { paint.setColor(Skia.Color(col.replace(/\s+/g, ''))); } catch { paint.setColor(Skia.Color('#ffffff')); }
|
|
1656
1667
|
paint.setAlphaf((state?.opacity ?? 1) * (seg.fill?.opacity ?? 1));
|
|
1657
1668
|
|
|
1658
|
-
|
|
1669
|
+
const fontSize = seg.fontSize || geom.fontSize || 14;
|
|
1670
|
+
const y = fontSize; // Basic baseline
|
|
1671
|
+
canvas.drawText(seg.text, offsetX, y, paint, font);
|
|
1659
1672
|
offsetX += font.getTextWidth(seg.text);
|
|
1660
1673
|
});
|
|
1661
1674
|
return;
|
|
@@ -1682,12 +1695,12 @@ export class ExodeUIEngine {
|
|
|
1682
1695
|
|
|
1683
1696
|
const align = geom.textAlign || obj.textAlign || 'center';
|
|
1684
1697
|
const textWidth = font.getTextWidth(text);
|
|
1685
|
-
let x =
|
|
1686
|
-
if (align === 'center') x = -textWidth / 2;
|
|
1687
|
-
else if (align === 'right') x = w
|
|
1698
|
+
let x = 0;
|
|
1699
|
+
if (align === 'center') x = (w - textWidth) / 2;
|
|
1700
|
+
else if (align === 'right') x = w - textWidth;
|
|
1688
1701
|
|
|
1689
1702
|
// Check baseline
|
|
1690
|
-
const y = geom.verticalAlign === 'center' || !geom.verticalAlign ? fontSize /
|
|
1703
|
+
const y = geom.verticalAlign === 'center' || !geom.verticalAlign ? (h + fontSize/2) / 2 : fontSize;
|
|
1691
1704
|
canvas.drawText(text, x, y, paint, font);
|
|
1692
1705
|
}
|
|
1693
1706
|
|
|
@@ -1722,8 +1735,8 @@ export class ExodeUIEngine {
|
|
|
1722
1735
|
paint.setAlphaf(state?.opacity ?? 1);
|
|
1723
1736
|
canvas.drawImageRect(
|
|
1724
1737
|
img,
|
|
1725
|
-
|
|
1726
|
-
|
|
1738
|
+
Skia.XYWHRect(0, 0, img.width(), img.height()),
|
|
1739
|
+
Skia.XYWHRect(0, 0, w, h),
|
|
1727
1740
|
paint
|
|
1728
1741
|
);
|
|
1729
1742
|
} else {
|
|
@@ -2105,11 +2118,11 @@ export class ExodeUIEngine {
|
|
|
2105
2118
|
const svgContent = geometry.svgContent;
|
|
2106
2119
|
if (!svgContent) return;
|
|
2107
2120
|
|
|
2108
|
-
// Extract path data from potentially multiple <path d="..."> tags
|
|
2109
|
-
const pathMatches = svgContent.matchAll(/d="([^"]+)"/g);
|
|
2121
|
+
// Extract path data from potentially multiple <path d="..."> or d='...' tags
|
|
2122
|
+
const pathMatches = svgContent.matchAll(/d=["']([^"']+)["']/g);
|
|
2110
2123
|
const paths: string[] = [];
|
|
2111
2124
|
for (const match of pathMatches) {
|
|
2112
|
-
paths.push(match[1]
|
|
2125
|
+
if (match[1]) paths.push(match[1]);
|
|
2113
2126
|
}
|
|
2114
2127
|
|
|
2115
2128
|
if (paths.length === 0) return;
|
|
@@ -2118,30 +2131,34 @@ export class ExodeUIEngine {
|
|
|
2118
2131
|
const style = state?.style || obj.style || {};
|
|
2119
2132
|
|
|
2120
2133
|
paths.forEach(d => {
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2134
|
+
try {
|
|
2135
|
+
const path = Skia.Path.MakeFromSVGString(d);
|
|
2136
|
+
if (!path) return;
|
|
2137
|
+
|
|
2138
|
+
// Draw fill
|
|
2139
|
+
if (style.fill && style.fill.type !== 'None') {
|
|
2140
|
+
const paint = Skia.Paint();
|
|
2141
|
+
const fillCol = style.fill.color || '#ffffff';
|
|
2142
|
+
const fillColStr = typeof fillCol === 'string' ? fillCol.replace(/\s+/g, '') : '#ffffff';
|
|
2143
|
+
try { paint.setColor(Skia.Color(fillColStr)); } catch { paint.setColor(Skia.Color('#ffffff')); }
|
|
2144
|
+
paint.setAlphaf((state?.opacity ?? 1) * (style.fill.opacity ?? 1));
|
|
2145
|
+
paint.setStyle(PaintStyle.Fill);
|
|
2146
|
+
canvas.drawPath(path, paint);
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
// Draw stroke if enabled
|
|
2150
|
+
if (style.stroke && style.stroke.width > 0 && style.stroke.isEnabled !== false) {
|
|
2151
|
+
const paint = Skia.Paint();
|
|
2152
|
+
const strokeCol = style.stroke.color || '#000000';
|
|
2153
|
+
const strokeColStr = typeof strokeCol === 'string' ? strokeCol.replace(/\s+/g, '') : '#000000';
|
|
2154
|
+
try { paint.setColor(Skia.Color(strokeColStr)); } catch { paint.setColor(Skia.Color('#000000')); }
|
|
2155
|
+
paint.setStrokeWidth(style.stroke.width);
|
|
2156
|
+
paint.setAlphaf((state?.opacity ?? 1) * (style.stroke.opacity ?? 1));
|
|
2157
|
+
paint.setStyle(PaintStyle.Stroke);
|
|
2158
|
+
canvas.drawPath(path, paint);
|
|
2159
|
+
}
|
|
2160
|
+
} catch (e) {
|
|
2161
|
+
console.warn(`[ExodeUIEngine] Failed to parse SVG path for ${obj.id}:`, e);
|
|
2145
2162
|
}
|
|
2146
2163
|
});
|
|
2147
2164
|
}
|
|
@@ -2304,8 +2321,8 @@ export class ExodeUIEngine {
|
|
|
2304
2321
|
const style = state.style || obj.style;
|
|
2305
2322
|
|
|
2306
2323
|
const path = Skia.Path.Make();
|
|
2324
|
+
const rect = Skia.XYWHRect(0, 0, w, h);
|
|
2307
2325
|
if (geometry.type === 'Rectangle') {
|
|
2308
|
-
const rect = { x: -w/2, y: -h/2, width: w, height: h };
|
|
2309
2326
|
if (state.cornerRadius !== undefined || geometry.corner_radius !== undefined) {
|
|
2310
2327
|
const rawCr = state.cornerRadius ?? geometry.corner_radius;
|
|
2311
2328
|
const cr = Array.isArray(rawCr) ? rawCr[0] : Number(rawCr);
|
|
@@ -2314,11 +2331,11 @@ export class ExodeUIEngine {
|
|
|
2314
2331
|
path.addRect(rect);
|
|
2315
2332
|
}
|
|
2316
2333
|
} else if (geometry.type === 'Ellipse') {
|
|
2317
|
-
path.addOval(
|
|
2334
|
+
path.addOval(rect);
|
|
2318
2335
|
} else if (geometry.type === 'Triangle') {
|
|
2319
|
-
path.moveTo(
|
|
2320
|
-
path.lineTo(w
|
|
2321
|
-
path.lineTo(
|
|
2336
|
+
path.moveTo(w / 2, 0);
|
|
2337
|
+
path.lineTo(w, h);
|
|
2338
|
+
path.lineTo(0, h);
|
|
2322
2339
|
path.close();
|
|
2323
2340
|
} else if (geometry.type === 'Star') {
|
|
2324
2341
|
const ir = geometry.inner_radius || 20;
|
|
@@ -2327,8 +2344,8 @@ export class ExodeUIEngine {
|
|
|
2327
2344
|
for (let i = 0; i < sp * 2; i++) {
|
|
2328
2345
|
const a = (i * Math.PI / sp) - (Math.PI / 2);
|
|
2329
2346
|
const rad = i % 2 === 0 ? or : ir;
|
|
2330
|
-
const px = rad * Math.cos(a);
|
|
2331
|
-
const py = rad * Math.sin(a);
|
|
2347
|
+
const px = w/2 + rad * Math.cos(a);
|
|
2348
|
+
const py = h/2 + rad * Math.sin(a);
|
|
2332
2349
|
if (i === 0) path.moveTo(px, py);
|
|
2333
2350
|
else path.lineTo(px, py);
|
|
2334
2351
|
}
|
|
@@ -2342,14 +2359,14 @@ export class ExodeUIEngine {
|
|
|
2342
2359
|
if (style.fill.type === 'LinearGradient' && style.fill.stops && style.fill.stops.length >= 2) {
|
|
2343
2360
|
const colors = style.fill.stops.map((s: any) => Skia.Color(s.color.replace(/\s+/g, '')));
|
|
2344
2361
|
const offsets = style.fill.stops.map((s: any) => s.offset);
|
|
2345
|
-
const start = style.fill.start ? { x:
|
|
2346
|
-
const end = style.fill.end ? { x:
|
|
2362
|
+
const start = style.fill.start ? { x: style.fill.start[0] * w, y: style.fill.start[1] * h } : { x: 0, y: 0 };
|
|
2363
|
+
const end = style.fill.end ? { x: style.fill.end[0] * w, y: style.fill.end[1] * h } : { x: w, y: 0 };
|
|
2347
2364
|
paint.setShader(Skia.Shader.MakeLinearGradient(Skia.Point(start.x, start.y), Skia.Point(end.x, end.y), colors, offsets, TileMode.Clamp));
|
|
2348
2365
|
hasShader = true;
|
|
2349
2366
|
} else if (style.fill.type === 'RadialGradient' && style.fill.stops && style.fill.stops.length >= 2) {
|
|
2350
2367
|
const colors = style.fill.stops.map((s: any) => Skia.Color(s.color.replace(/\s+/g, '')));
|
|
2351
2368
|
const offsets = style.fill.stops.map((s: any) => s.offset);
|
|
2352
|
-
const center = style.fill.center ? { x:
|
|
2369
|
+
const center = style.fill.center ? { x: style.fill.center[0] * w, y: style.fill.center[1] * h } : { x: w/2, y: h/2 };
|
|
2353
2370
|
const radius = (style.fill.radius || 0.5) * Math.max(w, h);
|
|
2354
2371
|
paint.setShader(Skia.Shader.MakeRadialGradient(Skia.Point(center.x, center.y), radius, colors, offsets, TileMode.Clamp));
|
|
2355
2372
|
hasShader = true;
|
|
@@ -2357,7 +2374,6 @@ export class ExodeUIEngine {
|
|
|
2357
2374
|
const img = this.getOrDecodeImage(style.fill.url);
|
|
2358
2375
|
if (img) {
|
|
2359
2376
|
const matrix = Skia.Matrix();
|
|
2360
|
-
matrix.translate(-w/2, -h/2);
|
|
2361
2377
|
matrix.scale(w / img.width(), h / img.height());
|
|
2362
2378
|
paint.setShader(img.makeShaderOptions(0, 0, 0, 0, matrix));
|
|
2363
2379
|
hasShader = true;
|
|
@@ -2375,7 +2391,7 @@ export class ExodeUIEngine {
|
|
|
2375
2391
|
|
|
2376
2392
|
// Apply clipping for Frame types before drawing
|
|
2377
2393
|
if (obj.type === 'Frame' || (obj as any).clipContent) {
|
|
2378
|
-
canvas.clipRect(
|
|
2394
|
+
canvas.clipRect(rect, ClipOp.Intersect, true);
|
|
2379
2395
|
}
|
|
2380
2396
|
|
|
2381
2397
|
if (style.shadow && style.shadow.opacity > 0) {
|