exodeui-react-native 1.0.3 → 1.0.5
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 +221 -45
package/package.json
CHANGED
package/src/engine.ts
CHANGED
|
@@ -1629,31 +1629,41 @@ export class ExodeUIEngine {
|
|
|
1629
1629
|
const opts = state?.options || obj.options || {};
|
|
1630
1630
|
const paint = Skia.Paint();
|
|
1631
1631
|
|
|
1632
|
-
const isPressed = this.draggingSliderId === obj.id;
|
|
1633
|
-
|
|
1632
|
+
const isPressed = this.draggingSliderId === obj.id;
|
|
1633
|
+
// Parse color safely
|
|
1634
|
+
const bgNormal = opts.backgroundColor || opts.background || '#3b82f6';
|
|
1635
|
+
const bgActive = opts.activeBackgroundColor || opts.activeBackground || '#2563eb';
|
|
1636
|
+
const bgStr = typeof bgNormal === 'string' ? bgNormal : '#3b82f6';
|
|
1637
|
+
const bgActiveStr = typeof bgActive === 'string' ? bgActive : '#2563eb';
|
|
1638
|
+
try {
|
|
1639
|
+
paint.setColor(Skia.Color((isPressed ? bgActiveStr : bgStr).replace(/\s+/g, '')));
|
|
1640
|
+
} catch {
|
|
1641
|
+
paint.setColor(Skia.Color('#3b82f6'));
|
|
1642
|
+
}
|
|
1634
1643
|
|
|
1635
1644
|
const r = opts.cornerRadius ?? 8;
|
|
1636
1645
|
canvas.drawRRect(Skia.RRectXY({ x: -w/2, y: -h/2, width: w, height: h }, r, r), paint);
|
|
1637
1646
|
|
|
1638
|
-
const
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
const font = this.getFont(fontSize);
|
|
1644
|
-
|
|
1645
|
-
// DEBUG: Cyan marker for button text
|
|
1646
|
-
const linePaint = Skia.Paint();
|
|
1647
|
-
linePaint.setColor(Skia.Color('cyan'));
|
|
1648
|
-
linePaint.setStrokeWidth(1);
|
|
1649
|
-
canvas.drawLine(-w/4, fontSize/2, w/4, fontSize/2, linePaint);
|
|
1650
|
-
|
|
1651
|
-
if (this._renderCount % 60 === 1) {
|
|
1652
|
-
console.log(`[ExodeUIEngine] Button text: "${label.text}" at ${-w/4},${fontSize/2} font_size=${font.getSize()}`);
|
|
1653
|
-
}
|
|
1647
|
+
const labelObj = opts.label || {};
|
|
1648
|
+
const labelText = labelObj.text || opts.text || opts.title || 'Button';
|
|
1649
|
+
const fontSize = labelObj.fontSize || opts.fontSize || 14;
|
|
1650
|
+
const labelColor = labelObj.color || opts.color || '#ffffff';
|
|
1651
|
+
const font = this.getFont(fontSize, 'Helvetica Neue');
|
|
1654
1652
|
|
|
1655
|
-
|
|
1653
|
+
const textPaint = Skia.Paint();
|
|
1654
|
+
try {
|
|
1655
|
+
textPaint.setColor(Skia.Color((typeof labelColor === 'string' ? labelColor : '#ffffff').replace(/\s+/g, '')));
|
|
1656
|
+
} catch {
|
|
1657
|
+
textPaint.setColor(Skia.Color('#ffffff'));
|
|
1656
1658
|
}
|
|
1659
|
+
if (isPressed) textPaint.setAlphaf(0.85);
|
|
1660
|
+
|
|
1661
|
+
// Center text horizontally using getTextWidth
|
|
1662
|
+
const textWidth = font.getTextWidth ? font.getTextWidth(labelText) : 0;
|
|
1663
|
+
canvas.save();
|
|
1664
|
+
canvas.translate(-textWidth / 2, fontSize / 3);
|
|
1665
|
+
canvas.drawText(String(labelText), 0, 0, textPaint, font);
|
|
1666
|
+
canvas.restore();
|
|
1657
1667
|
}
|
|
1658
1668
|
|
|
1659
1669
|
private renderToggle(canvas: any, obj: any, w: number, h: number) {
|
|
@@ -1662,7 +1672,8 @@ export class ExodeUIEngine {
|
|
|
1662
1672
|
const checked = opts.checked || false;
|
|
1663
1673
|
|
|
1664
1674
|
const paint = Skia.Paint();
|
|
1665
|
-
|
|
1675
|
+
const trackColor = checked ? (opts.activeColor || '#3b82f6') : (opts.inactiveColor || '#374151');
|
|
1676
|
+
try { paint.setColor(Skia.Color(trackColor.replace(/\s+/g, ''))); } catch { paint.setColor(Skia.Color('#374151')); }
|
|
1666
1677
|
|
|
1667
1678
|
const r = h / 2;
|
|
1668
1679
|
canvas.drawRRect(Skia.RRectXY({ x: -w/2, y: -h/2, width: w, height: h }, r, r), paint);
|
|
@@ -1672,6 +1683,20 @@ export class ExodeUIEngine {
|
|
|
1672
1683
|
const thumbRadius = (h - 8) / 2;
|
|
1673
1684
|
const thumbX = checked ? (w / 2 - thumbRadius - 4) : (-w / 2 + thumbRadius + 4);
|
|
1674
1685
|
canvas.drawCircle(thumbX, 0, thumbRadius, thumbPaint);
|
|
1686
|
+
|
|
1687
|
+
// Label text
|
|
1688
|
+
const labelText = opts.label || opts.text || '';
|
|
1689
|
+
if (labelText) {
|
|
1690
|
+
const fontSize = opts.fontSize || 12;
|
|
1691
|
+
const font = this.getFont(fontSize, 'Helvetica Neue');
|
|
1692
|
+
const labelPaint = Skia.Paint();
|
|
1693
|
+
const labelColor = opts.labelColor || opts.color || '#ffffff';
|
|
1694
|
+
try { labelPaint.setColor(Skia.Color((typeof labelColor === 'string' ? labelColor : '#ffffff').replace(/\s+/g, ''))); } catch { labelPaint.setColor(Skia.Color('#ffffff')); }
|
|
1695
|
+
canvas.save();
|
|
1696
|
+
canvas.translate(w/2 + 8, fontSize / 3);
|
|
1697
|
+
canvas.drawText(String(labelText), 0, 0, labelPaint, font);
|
|
1698
|
+
canvas.restore();
|
|
1699
|
+
}
|
|
1675
1700
|
}
|
|
1676
1701
|
|
|
1677
1702
|
private renderSlider(canvas: any, obj: any, w: number, h: number) {
|
|
@@ -1680,23 +1705,40 @@ export class ExodeUIEngine {
|
|
|
1680
1705
|
const value = opts.value ?? 50;
|
|
1681
1706
|
const min = opts.min ?? 0;
|
|
1682
1707
|
const max = opts.max ?? 100;
|
|
1683
|
-
const percentage = (value - min) / (max - min);
|
|
1708
|
+
const percentage = Math.max(0, Math.min(1, (value - min) / (max - min)));
|
|
1684
1709
|
|
|
1685
1710
|
const trackPaint = Skia.Paint();
|
|
1686
|
-
|
|
1711
|
+
const inactiveColor = opts.inactiveColor || opts.trackColor || '#374151';
|
|
1712
|
+
try { trackPaint.setColor(Skia.Color((typeof inactiveColor === 'string' ? inactiveColor : '#374151').replace(/\s+/g, ''))); } catch { trackPaint.setColor(Skia.Color('#374151')); }
|
|
1687
1713
|
const trackHeight = opts.trackHeight || 4;
|
|
1688
|
-
canvas.
|
|
1714
|
+
canvas.drawRRect(Skia.RRectXY({ x: -w/2, y: -trackHeight/2, width: w, height: trackHeight }, trackHeight/2, trackHeight/2), trackPaint);
|
|
1689
1715
|
|
|
1690
1716
|
const activePaint = Skia.Paint();
|
|
1691
|
-
|
|
1717
|
+
const activeColor = opts.activeColor || opts.thumbColor || '#3b82f6';
|
|
1718
|
+
try { activePaint.setColor(Skia.Color((typeof activeColor === 'string' ? activeColor : '#3b82f6').replace(/\s+/g, ''))); } catch { activePaint.setColor(Skia.Color('#3b82f6')); }
|
|
1692
1719
|
const thumbWidth = opts.thumbWidth ?? 16;
|
|
1693
1720
|
const travelW = w - thumbWidth;
|
|
1694
1721
|
const thumbX = -w / 2 + (thumbWidth / 2) + (percentage * travelW);
|
|
1695
|
-
|
|
1722
|
+
const activeW = thumbX + w/2;
|
|
1723
|
+
if (activeW > 0) canvas.drawRRect(Skia.RRectXY({ x: -w/2, y: -trackHeight/2, width: activeW, height: trackHeight }, trackHeight/2, trackHeight/2), activePaint);
|
|
1696
1724
|
|
|
1697
1725
|
const thumbPaint = Skia.Paint();
|
|
1698
|
-
|
|
1726
|
+
const thumbCol = opts.thumbColor || '#ffffff';
|
|
1727
|
+
try { thumbPaint.setColor(Skia.Color((typeof thumbCol === 'string' ? thumbCol : '#ffffff').replace(/\s+/g, ''))); } catch { thumbPaint.setColor(Skia.Color('#ffffff')); }
|
|
1699
1728
|
canvas.drawCircle(thumbX, 0, thumbWidth / 2, thumbPaint);
|
|
1729
|
+
|
|
1730
|
+
// Value label above thumb
|
|
1731
|
+
if (opts.showValue !== false) {
|
|
1732
|
+
const fontSize = opts.fontSize || 11;
|
|
1733
|
+
const font = this.getFont(fontSize, 'Helvetica Neue');
|
|
1734
|
+
const valPaint = Skia.Paint();
|
|
1735
|
+
valPaint.setColor(Skia.Color('#ffffff'));
|
|
1736
|
+
const valText = String(Math.round(value));
|
|
1737
|
+
canvas.save();
|
|
1738
|
+
canvas.translate(thumbX, -thumbWidth / 2 - fontSize - 2);
|
|
1739
|
+
canvas.drawText(valText, 0, 0, valPaint, font);
|
|
1740
|
+
canvas.restore();
|
|
1741
|
+
}
|
|
1700
1742
|
}
|
|
1701
1743
|
|
|
1702
1744
|
private renderDropdown(canvas: any, obj: any, w: number, h: number) {
|
|
@@ -1892,19 +1934,33 @@ export class ExodeUIEngine {
|
|
|
1892
1934
|
const opts = state?.options || obj.options || {};
|
|
1893
1935
|
|
|
1894
1936
|
const paint = Skia.Paint();
|
|
1895
|
-
|
|
1937
|
+
const bgColor = typeof (opts.backgroundColor || opts.background) === 'string' ? (opts.backgroundColor || opts.background || '#1f2937') : '#1f2937';
|
|
1938
|
+
try { paint.setColor(Skia.Color(bgColor.replace(/\s+/g, ''))); } catch { paint.setColor(Skia.Color('#1f2937')); }
|
|
1896
1939
|
const r = opts.cornerRadius ?? 8;
|
|
1897
1940
|
canvas.drawRRect(Skia.RRectXY({ x: -w/2, y: -h/2, width: w, height: h }, r, r), paint);
|
|
1898
1941
|
|
|
1942
|
+
// Border
|
|
1943
|
+
if (opts.borderColor || opts.borderWidth) {
|
|
1944
|
+
const borderP = Skia.Paint();
|
|
1945
|
+
borderP.setStyle(PaintStyle.Stroke);
|
|
1946
|
+
borderP.setStrokeWidth(opts.borderWidth || 1);
|
|
1947
|
+
try { borderP.setColor(Skia.Color((opts.borderColor || '#4b5563').replace(/\s+/g, ''))); } catch { borderP.setColor(Skia.Color('#4b5563')); }
|
|
1948
|
+
canvas.drawRRect(Skia.RRectXY({ x: -w/2, y: -h/2, width: w, height: h }, r, r), borderP);
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1899
1951
|
const textPaint = Skia.Paint();
|
|
1900
|
-
|
|
1952
|
+
const textColor = typeof (opts.color || opts.textColor) === 'string' ? (opts.color || opts.textColor || '#ffffff') : '#ffffff';
|
|
1953
|
+
try { textPaint.setColor(Skia.Color(textColor.replace(/\s+/g, ''))); } catch { textPaint.setColor(Skia.Color('#ffffff')); }
|
|
1901
1954
|
const fontSize = opts.fontSize || 14;
|
|
1902
|
-
const font = this.getFont(fontSize);
|
|
1903
|
-
const text = opts.text || '';
|
|
1955
|
+
const font = this.getFont(fontSize, 'Helvetica Neue');
|
|
1956
|
+
const text = opts.text || opts.value || '';
|
|
1904
1957
|
const display = text || opts.placeholder || 'Enter text...';
|
|
1905
1958
|
if (!text) textPaint.setAlphaf(0.5);
|
|
1906
1959
|
|
|
1907
|
-
canvas.
|
|
1960
|
+
canvas.save();
|
|
1961
|
+
canvas.translate(-w/2 + 15, fontSize / 3);
|
|
1962
|
+
canvas.drawText(String(display), 0, 0, textPaint, font);
|
|
1963
|
+
canvas.restore();
|
|
1908
1964
|
}
|
|
1909
1965
|
|
|
1910
1966
|
private renderSVG(canvas: any, obj: any, w: number, h: number) {
|
|
@@ -1923,30 +1979,150 @@ export class ExodeUIEngine {
|
|
|
1923
1979
|
private renderLineGraph(canvas: any, geom: any, w: number, h: number) {
|
|
1924
1980
|
const state = this.objectStates.get(geom.id);
|
|
1925
1981
|
const datasets = geom.datasets || [];
|
|
1982
|
+
const opts = geom.options || {};
|
|
1926
1983
|
if (datasets.length === 0) return;
|
|
1927
1984
|
|
|
1928
|
-
const
|
|
1929
|
-
|
|
1930
|
-
|
|
1985
|
+
const font = this.getFont(10, 'Helvetica Neue');
|
|
1986
|
+
const axisMarginL = 30; // left margin for y-axis labels
|
|
1987
|
+
const axisMarginB = 20; // bottom margin for x-axis labels
|
|
1988
|
+
const plotW = w - axisMarginL;
|
|
1989
|
+
const plotH = h - axisMarginB;
|
|
1990
|
+
|
|
1991
|
+
// Axis lines
|
|
1992
|
+
const axisPaint = Skia.Paint();
|
|
1993
|
+
axisPaint.setColor(Skia.Color(opts.axisColor || '#4b5563'));
|
|
1994
|
+
axisPaint.setStrokeWidth(1);
|
|
1995
|
+
axisPaint.setStyle(PaintStyle.Stroke);
|
|
1996
|
+
canvas.drawLine(-w/2 + axisMarginL, -h/2, -w/2 + axisMarginL, h/2 - axisMarginB, axisPaint); // Y axis
|
|
1997
|
+
canvas.drawLine(-w/2 + axisMarginL, h/2 - axisMarginB, w/2, h/2 - axisMarginB, axisPaint); // X axis
|
|
1998
|
+
|
|
1999
|
+
// Get global max across all datasets
|
|
2000
|
+
let globalMax = 1;
|
|
2001
|
+
datasets.forEach((ds: any) => {
|
|
2002
|
+
const m = Math.max(...(ds.data || [0]));
|
|
2003
|
+
if (m > globalMax) globalMax = m;
|
|
2004
|
+
});
|
|
1931
2005
|
|
|
1932
2006
|
datasets.forEach((ds: any) => {
|
|
1933
2007
|
const data = ds.data || [];
|
|
1934
2008
|
if (data.length < 2) return;
|
|
1935
2009
|
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
const
|
|
1941
|
-
|
|
2010
|
+
const lineColorStr = (typeof ds.lineColor === 'string' ? ds.lineColor : '#3b82f6').replace(/\s+/g, '');
|
|
2011
|
+
let lineColor;
|
|
2012
|
+
try { lineColor = Skia.Color(lineColorStr); } catch { lineColor = Skia.Color('#3b82f6'); }
|
|
2013
|
+
|
|
2014
|
+
const stepX = plotW / (data.length - 1);
|
|
2015
|
+
const getX = (i: number) => -w/2 + axisMarginL + i * stepX;
|
|
2016
|
+
const getY = (val: number) => h/2 - axisMarginB - (val / globalMax) * plotH;
|
|
2017
|
+
|
|
2018
|
+
const smooth = opts.smooth !== false && (ds.smooth !== false);
|
|
2019
|
+
|
|
2020
|
+
// Gradient fill
|
|
2021
|
+
if (ds.fill !== false && opts.fill !== false) {
|
|
2022
|
+
const fillPath = Skia.Path.Make();
|
|
2023
|
+
fillPath.moveTo(getX(0), h/2 - axisMarginB);
|
|
2024
|
+
data.forEach((val: number, i: number) => {
|
|
2025
|
+
const x = getX(i);
|
|
2026
|
+
const y = getY(val);
|
|
2027
|
+
if (i === 0) fillPath.lineTo(x, y);
|
|
2028
|
+
else if (smooth && i < data.length) {
|
|
2029
|
+
const prevX = getX(i - 1);
|
|
2030
|
+
const prevY = getY(data[i - 1]);
|
|
2031
|
+
const cpX = (prevX + x) / 2;
|
|
2032
|
+
fillPath.cubicTo(cpX, prevY, cpX, y, x, y);
|
|
2033
|
+
} else {
|
|
2034
|
+
fillPath.lineTo(x, y);
|
|
2035
|
+
}
|
|
2036
|
+
});
|
|
2037
|
+
fillPath.lineTo(getX(data.length - 1), h/2 - axisMarginB);
|
|
2038
|
+
fillPath.close();
|
|
2039
|
+
|
|
2040
|
+
const fillPaint = Skia.Paint();
|
|
2041
|
+
fillPaint.setStyle(PaintStyle.Fill);
|
|
2042
|
+
fillPaint.setAlphaf(0.15);
|
|
2043
|
+
try { fillPaint.setColor(lineColor); } catch { fillPaint.setColor(Skia.Color('#3b82f6')); }
|
|
2044
|
+
canvas.drawPath(fillPath, fillPaint);
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
// Line
|
|
2048
|
+
const linePaint = Skia.Paint();
|
|
2049
|
+
linePaint.setStyle(PaintStyle.Stroke);
|
|
2050
|
+
linePaint.setStrokeWidth(ds.lineWidth || geom.lineWidth || 2);
|
|
2051
|
+
try { linePaint.setColor(lineColor); } catch { linePaint.setColor(Skia.Color('#3b82f6')); }
|
|
2052
|
+
|
|
2053
|
+
const linePath = Skia.Path.Make();
|
|
1942
2054
|
data.forEach((val: number, i: number) => {
|
|
1943
|
-
const x =
|
|
1944
|
-
const y =
|
|
1945
|
-
if (i === 0)
|
|
1946
|
-
else
|
|
2055
|
+
const x = getX(i);
|
|
2056
|
+
const y = getY(val);
|
|
2057
|
+
if (i === 0) linePath.moveTo(x, y);
|
|
2058
|
+
else if (smooth && i < data.length) {
|
|
2059
|
+
const prevX = getX(i - 1);
|
|
2060
|
+
const prevY = getY(data[i - 1]);
|
|
2061
|
+
const cpX = (prevX + x) / 2;
|
|
2062
|
+
linePath.cubicTo(cpX, prevY, cpX, y, x, y);
|
|
2063
|
+
} else {
|
|
2064
|
+
linePath.lineTo(x, y);
|
|
2065
|
+
}
|
|
1947
2066
|
});
|
|
1948
|
-
canvas.drawPath(
|
|
2067
|
+
canvas.drawPath(linePath, linePaint);
|
|
2068
|
+
|
|
2069
|
+
// Data points
|
|
2070
|
+
if (ds.showPoints !== false && data.length <= 20) {
|
|
2071
|
+
data.forEach((val: number, i: number) => {
|
|
2072
|
+
const dotPaint = Skia.Paint();
|
|
2073
|
+
try { dotPaint.setColor(lineColor); } catch { dotPaint.setColor(Skia.Color('#3b82f6')); }
|
|
2074
|
+
canvas.drawCircle(getX(i), getY(val), 3, dotPaint);
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
1949
2077
|
});
|
|
2078
|
+
|
|
2079
|
+
// X-axis labels
|
|
2080
|
+
const xLabels = opts.xLabels || geom.xLabels || [];
|
|
2081
|
+
const labelPaint = Skia.Paint();
|
|
2082
|
+
labelPaint.setColor(Skia.Color(opts.labelColor || '#9ca3af'));
|
|
2083
|
+
const firstDs = datasets[0];
|
|
2084
|
+
const dataLen = (firstDs?.data || []).length;
|
|
2085
|
+
if (xLabels.length > 0 && dataLen > 0) {
|
|
2086
|
+
const stepX = plotW / (dataLen - 1);
|
|
2087
|
+
xLabels.forEach((label: string, i: number) => {
|
|
2088
|
+
const x = -w/2 + axisMarginL + i * stepX;
|
|
2089
|
+
canvas.save();
|
|
2090
|
+
canvas.translate(x, h/2 - axisMarginB + 12);
|
|
2091
|
+
canvas.drawText(String(label), 0, 0, labelPaint, font);
|
|
2092
|
+
canvas.restore();
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
// Y-axis labels
|
|
2097
|
+
const yLabels = opts.yLabels || geom.yLabels || [];
|
|
2098
|
+
if (yLabels.length > 0) {
|
|
2099
|
+
yLabels.forEach((label: string, i: number) => {
|
|
2100
|
+
const y = h/2 - axisMarginB - (i / (yLabels.length - 1)) * plotH;
|
|
2101
|
+
canvas.save();
|
|
2102
|
+
canvas.translate(-w/2 + axisMarginL - 4, y);
|
|
2103
|
+
const lp = Skia.Paint();
|
|
2104
|
+
lp.setColor(Skia.Color(opts.labelColor || '#9ca3af'));
|
|
2105
|
+
canvas.drawText(String(label), -(font.getTextWidth ? font.getTextWidth(String(label)) : 24), 0, lp, font);
|
|
2106
|
+
canvas.restore();
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
// Legend
|
|
2111
|
+
if (datasets.length > 1 || datasets[0]?.label) {
|
|
2112
|
+
datasets.forEach((ds: any, i: number) => {
|
|
2113
|
+
if (!ds.label) return;
|
|
2114
|
+
const legX = -w/2 + axisMarginL + i * 80;
|
|
2115
|
+
const legY = -h/2 + 10;
|
|
2116
|
+
const legPaint = Skia.Paint();
|
|
2117
|
+
const lcStr = (typeof ds.lineColor === 'string' ? ds.lineColor : '#3b82f6').replace(/\s+/g, '');
|
|
2118
|
+
try { legPaint.setColor(Skia.Color(lcStr)); } catch { legPaint.setColor(Skia.Color('#3b82f6')); }
|
|
2119
|
+
canvas.drawRect({ x: legX, y: legY - 5, width: 16, height: 3 }, legPaint);
|
|
2120
|
+
canvas.save();
|
|
2121
|
+
canvas.translate(legX + 20, legY);
|
|
2122
|
+
canvas.drawText(String(ds.label), 0, 0, legPaint, font);
|
|
2123
|
+
canvas.restore();
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
1950
2126
|
}
|
|
1951
2127
|
private renderShape(canvas: SkCanvas, obj: ShapeObject, w: number, h: number) {
|
|
1952
2128
|
const state = this.objectStates.get(obj.id);
|