mirror of
https://github.com/XCharts-Team/XCharts.git
synced 2026-05-27 11:40:13 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10d67cff41 | ||
|
|
4120b61f2a | ||
|
|
0174453b05 | ||
|
|
68cb18305d | ||
|
|
39514f82b3 | ||
|
|
5f66391428 | ||
|
|
99e56d238a | ||
|
|
dcac0f9655 | ||
|
|
2bb56fcd28 |
@@ -81,6 +81,17 @@ slug: /changelog
|
||||
|
||||
## master
|
||||
|
||||
* (2026.05.17) 修复`DataZoom`点击时指示区域不准的问题
|
||||
* (2026.05.17) 增加`Legend`的`Width`和`Height`可设置固定宽高
|
||||
* (2026.05.17) 修复`Serie`的`EndLabel`在`Y`轴是`MinMax`类型时显示的数值不对的问题
|
||||
* (2026.05.16) 修复`Candlestick`按昨收判断涨跌颜色,一字涨停/跌停显示不对的问题 (#362)
|
||||
* (2026.03.29) 修复`Legend`的`Background`区域在`Horizonal`模式下不对的问题
|
||||
* (2026.03.25) 增加`Chart`的`Json`导出导入
|
||||
* (2026.03.10) 增加`Sankey`的线条tooltip触发显示
|
||||
* (2026.03.10) 增加`UITable`的排序功能支持
|
||||
* (2026.03.07) 修复`UITable`在尺寸变化时背景没有实时刷新的问题
|
||||
* (2026.03.06) 优化`UITable`支持万级以上数据量不卡顿
|
||||
|
||||
## v3.15.0
|
||||
|
||||
版本要点:
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace XCharts.Editor
|
||||
public static readonly GUIContent btnSaveAsImage = new GUIContent("Save As Image", "");
|
||||
public static readonly GUIContent btnCheckWarning = new GUIContent("Check Warning", "");
|
||||
public static readonly GUIContent btnHideWarning = new GUIContent("Hide Warning", "");
|
||||
public static readonly GUIContent btnImportJsonData = new GUIContent("Import Json", "");
|
||||
public static readonly GUIContent btnExportJsonData = new GUIContent("Export Json", "");
|
||||
}
|
||||
protected BaseChart m_Chart;
|
||||
protected SerializedProperty m_Script;
|
||||
@@ -36,6 +38,7 @@ namespace XCharts.Editor
|
||||
private bool m_BaseFoldout;
|
||||
|
||||
private bool m_CheckWarning = false;
|
||||
private bool m_ExportPending = false;
|
||||
private int m_LastComponentCount = 0;
|
||||
private int m_LastSerieCount = 0;
|
||||
private string m_VersionString = "";
|
||||
@@ -300,6 +303,14 @@ namespace XCharts.Editor
|
||||
m_CheckWarning = false;
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
if (GUILayout.Button(Styles.btnImportJsonData))
|
||||
{
|
||||
ChartJsonImportWindow.ShowWindow(m_Chart);
|
||||
}
|
||||
if (GUILayout.Button(Styles.btnExportJsonData))
|
||||
{
|
||||
RequestExportJsonData();
|
||||
}
|
||||
sb.Length = 0;
|
||||
sb.AppendFormat("v{0}", XChartsMgr.fullVersion);
|
||||
if (!string.IsNullOrEmpty(m_Chart.warningInfo))
|
||||
@@ -321,8 +332,47 @@ namespace XCharts.Editor
|
||||
m_CheckWarning = true;
|
||||
m_Chart.CheckWarning();
|
||||
}
|
||||
if (GUILayout.Button(Styles.btnImportJsonData))
|
||||
{
|
||||
ChartJsonImportWindow.ShowWindow(m_Chart);
|
||||
}
|
||||
if (GUILayout.Button(Styles.btnExportJsonData))
|
||||
{
|
||||
RequestExportJsonData();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void RequestExportJsonData()
|
||||
{
|
||||
if (m_ExportPending) return;
|
||||
m_ExportPending = true;
|
||||
var chart = m_Chart;
|
||||
EditorApplication.delayCall += delegate()
|
||||
{
|
||||
m_ExportPending = false;
|
||||
ExportJsonData(chart);
|
||||
};
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
|
||||
private static void ExportJsonData(BaseChart chart)
|
||||
{
|
||||
if (chart == null) return;
|
||||
var json = chart.ExportToJson(true);
|
||||
var defaultName = chart.gameObject.name + ".json";
|
||||
var path = EditorUtility.SaveFilePanel("Save Chart JSON", "", defaultName, "json");
|
||||
if (string.IsNullOrEmpty(path)) return;
|
||||
try
|
||||
{
|
||||
System.IO.File.WriteAllText(path, json);
|
||||
Debug.Log("[XCharts] JSON exported to: " + path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError("[XCharts] Failed to save JSON: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ namespace XCharts.Editor
|
||||
{
|
||||
++EditorGUI.indentLevel;
|
||||
PropertyField("m_IconType");
|
||||
PropertyField("m_Width");
|
||||
PropertyField("m_Height");
|
||||
PropertyField("m_ItemWidth");
|
||||
PropertyField("m_ItemHeight");
|
||||
PropertyField("m_ItemGap");
|
||||
|
||||
@@ -13,10 +13,13 @@ namespace XCharts.Editor
|
||||
public static readonly GUIContent btnAddComponent = new GUIContent("Add Main Component", "");
|
||||
public static readonly GUIContent btnRebuildChartObject = new GUIContent("Rebuild Object", "");
|
||||
public static readonly GUIContent btnSaveAsImage = new GUIContent("Save As Image", "");
|
||||
public static readonly GUIContent btnImportJsonData = new GUIContent("Import Json", "");
|
||||
public static readonly GUIContent btnExportJsonData = new GUIContent("Export Json", "");
|
||||
public static readonly GUIContent btnCheckWarning = new GUIContent("Check Warning", "");
|
||||
public static readonly GUIContent btnHideWarning = new GUIContent("Hide Warning", "");
|
||||
}
|
||||
public UIComponent m_UIComponent;
|
||||
private bool m_ExportPending;
|
||||
|
||||
public static T AddUIComponent<T>(string chartName) where T : UIComponent
|
||||
{
|
||||
@@ -56,6 +59,14 @@ namespace XCharts.Editor
|
||||
{
|
||||
m_UIComponent.SaveAsImage("png", "", 4f);
|
||||
}
|
||||
if (GUILayout.Button(Styles.btnImportJsonData))
|
||||
{
|
||||
UIComponentJsonImportWindow.ShowWindow(m_UIComponent);
|
||||
}
|
||||
if (GUILayout.Button(Styles.btnExportJsonData))
|
||||
{
|
||||
RequestExportJsonData();
|
||||
}
|
||||
OnDebugEndInspectorGUI();
|
||||
}
|
||||
|
||||
@@ -89,6 +100,37 @@ namespace XCharts.Editor
|
||||
EditorGUILayout.PropertyField(property, title);
|
||||
}
|
||||
|
||||
private void RequestExportJsonData()
|
||||
{
|
||||
if (m_ExportPending) return;
|
||||
m_ExportPending = true;
|
||||
var target = m_UIComponent;
|
||||
EditorApplication.delayCall += delegate ()
|
||||
{
|
||||
m_ExportPending = false;
|
||||
ExportJsonData(target);
|
||||
};
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
|
||||
private static void ExportJsonData(UIComponent target)
|
||||
{
|
||||
if (target == null) return;
|
||||
var json = target.ExportToJson(true);
|
||||
var defaultName = target.gameObject.name + ".json";
|
||||
var path = EditorUtility.SaveFilePanel("Save UI Component JSON", "", defaultName, "json");
|
||||
if (string.IsNullOrEmpty(path)) return;
|
||||
try
|
||||
{
|
||||
System.IO.File.WriteAllText(path, json);
|
||||
Debug.Log("[XCharts] UI JSON exported to: " + path);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogError("[XCharts] Failed to save UI JSON: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
protected void PropertyListField(string relativePropName, bool showOrder = true, params HeaderMenuInfo[] menus)
|
||||
{
|
||||
var m_DrawRect = GUILayoutUtility.GetRect(1f, 17f);
|
||||
|
||||
201
Editor/Windows/ChartJsonImportWindow.cs
Normal file
201
Editor/Windows/ChartJsonImportWindow.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using XCharts.Runtime;
|
||||
|
||||
namespace XCharts.Editor
|
||||
{
|
||||
public class ChartJsonImportWindow : EditorWindow
|
||||
{
|
||||
private const int TEXTAREA_SAFE_CHAR_LIMIT = 8000;
|
||||
private const int LARGE_JSON_PREVIEW_CHAR_LIMIT = 4000;
|
||||
|
||||
private static BaseChart s_TargetChart;
|
||||
private string m_JsonInput = "";
|
||||
private Vector2 m_ScrollPos;
|
||||
private bool m_ShowPreview = false;
|
||||
private string m_PreviewText = "";
|
||||
private bool m_OpenFilePending = false;
|
||||
private bool m_PreviewPending = false;
|
||||
private bool m_ImportPending = false;
|
||||
|
||||
public static void ShowWindow(BaseChart targetChart)
|
||||
{
|
||||
s_TargetChart = targetChart;
|
||||
var window = GetWindow<ChartJsonImportWindow>("Import Chart JSON");
|
||||
window.minSize = new Vector2(600, 400);
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (s_TargetChart == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Target chart is null. Please select a chart first.", MessageType.Error);
|
||||
if (GUILayout.Button("Close")) Close();
|
||||
return;
|
||||
}
|
||||
if (m_JsonInput == null) m_JsonInput = "";
|
||||
EditorGUILayout.LabelField("Target Chart: " + s_TargetChart.gameObject.name, EditorStyles.boldLabel);
|
||||
GUILayout.Space(10);
|
||||
EditorGUILayout.LabelField("Paste JSON Data:", EditorStyles.boldLabel);
|
||||
using (var scroll = new EditorGUILayout.ScrollViewScope(m_ScrollPos, GUILayout.Height(250)))
|
||||
{
|
||||
m_ScrollPos = scroll.scrollPosition;
|
||||
if (m_JsonInput.Length <= TEXTAREA_SAFE_CHAR_LIMIT)
|
||||
{
|
||||
m_JsonInput = EditorGUILayout.TextArea(m_JsonInput, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true));
|
||||
}
|
||||
else
|
||||
{
|
||||
var preview = m_JsonInput.Substring(0, LARGE_JSON_PREVIEW_CHAR_LIMIT);
|
||||
EditorGUILayout.HelpBox("JSON content is very large. To avoid editor text rendering limits, only a preview is shown below. Import uses full content.", MessageType.Info);
|
||||
EditorGUILayout.TextArea(preview, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true));
|
||||
}
|
||||
}
|
||||
EditorGUILayout.HelpBox("Paste JSON directly, or click Open Json File.", MessageType.Info);
|
||||
GUILayout.Space(10);
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("Open Json File", GUILayout.Width(120)))
|
||||
{
|
||||
RequestOpenJsonFile();
|
||||
}
|
||||
if (GUILayout.Button("Preview", GUILayout.Width(100)))
|
||||
{
|
||||
RequestPreviewJson();
|
||||
}
|
||||
}
|
||||
if (m_ShowPreview && !string.IsNullOrEmpty(m_PreviewText))
|
||||
{
|
||||
EditorGUILayout.TextArea(m_PreviewText, GUILayout.Height(150));
|
||||
}
|
||||
GUILayout.Space(10);
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("Import", GUILayout.Height(40), GUILayout.Width(150)))
|
||||
{
|
||||
RequestImportJson();
|
||||
}
|
||||
if (GUILayout.Button("Cancel", GUILayout.Height(40), GUILayout.Width(150))) Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void RequestOpenJsonFile()
|
||||
{
|
||||
if (m_OpenFilePending) return;
|
||||
m_OpenFilePending = true;
|
||||
EditorApplication.delayCall += delegate ()
|
||||
{
|
||||
m_OpenFilePending = false;
|
||||
if (this == null) return;
|
||||
OpenJsonFile();
|
||||
Repaint();
|
||||
};
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
|
||||
private void RequestPreviewJson()
|
||||
{
|
||||
if (m_PreviewPending) return;
|
||||
m_PreviewPending = true;
|
||||
EditorApplication.delayCall += delegate ()
|
||||
{
|
||||
m_PreviewPending = false;
|
||||
if (this == null) return;
|
||||
PreviewJson();
|
||||
Repaint();
|
||||
};
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
|
||||
private void RequestImportJson()
|
||||
{
|
||||
if (m_ImportPending) return;
|
||||
m_ImportPending = true;
|
||||
EditorApplication.delayCall += delegate ()
|
||||
{
|
||||
m_ImportPending = false;
|
||||
if (this == null) return;
|
||||
ImportJson();
|
||||
};
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
|
||||
private void PreviewJson()
|
||||
{
|
||||
if (string.IsNullOrEmpty(m_JsonInput))
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", "JSON input is empty.", "OK");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
var json = JsonUtility.FromJson<XCharts.Runtime.ChartJson>(m_JsonInput);
|
||||
if (json == null)
|
||||
{
|
||||
m_PreviewText = "Invalid JSON or unsupported schema.";
|
||||
}
|
||||
else
|
||||
{
|
||||
var componentCount = json.components != null ? json.components.Count : 0;
|
||||
var seriesCount = json.series != null ? json.series.Count : 0;
|
||||
m_PreviewText = "Chart Type: " + json.chartType + "\nComponents: " + componentCount + "\nSeries: " + seriesCount + "\n(Full validation on import)";
|
||||
}
|
||||
m_ShowPreview = true;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Preview Error", "Invalid JSON: " + ex.Message, "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private void ImportJson()
|
||||
{
|
||||
if (string.IsNullOrEmpty(m_JsonInput))
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", "JSON input is empty. Please paste JSON data.", "OK");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
Undo.RecordObject(s_TargetChart, "Import Chart JSON");
|
||||
s_TargetChart.ImportFromJson(m_JsonInput);
|
||||
s_TargetChart.RebuildChartObject();
|
||||
s_TargetChart.RefreshAllComponent();
|
||||
s_TargetChart.RefreshChart();
|
||||
EditorUtility.SetDirty(s_TargetChart);
|
||||
UnityEditor.SceneView.RepaintAll();
|
||||
var chart = s_TargetChart;
|
||||
EditorApplication.delayCall += delegate ()
|
||||
{
|
||||
if (chart == null) return;
|
||||
chart.RefreshAllComponent();
|
||||
chart.RefreshChart();
|
||||
UnityEditor.SceneView.RepaintAll();
|
||||
};
|
||||
EditorUtility.DisplayDialog("Success", "Chart '" + s_TargetChart.gameObject.name + "' imported successfully!", "OK");
|
||||
Close();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Import Error", "Failed to import JSON:\n" + ex.Message + "\n\n" + ex.StackTrace, "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenJsonFile()
|
||||
{
|
||||
var path = EditorUtility.OpenFilePanel("Open Chart JSON", "", "json");
|
||||
if (string.IsNullOrEmpty(path)) return;
|
||||
try
|
||||
{
|
||||
m_JsonInput = System.IO.File.ReadAllText(path);
|
||||
m_ShowPreview = false;
|
||||
m_PreviewText = "";
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Open File Error", "Failed to read JSON file:\n" + ex.Message, "OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/Windows/ChartJsonImportWindow.cs.meta
Normal file
11
Editor/Windows/ChartJsonImportWindow.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21f2eafb07ab34d4abf575784acc56a3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
193
Editor/Windows/UIComponentJsonImportWindow.cs
Normal file
193
Editor/Windows/UIComponentJsonImportWindow.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using XCharts.Runtime;
|
||||
|
||||
namespace XCharts.Editor
|
||||
{
|
||||
public class UIComponentJsonImportWindow : EditorWindow
|
||||
{
|
||||
private const int TEXTAREA_SAFE_CHAR_LIMIT = 8000;
|
||||
private const int LARGE_JSON_PREVIEW_CHAR_LIMIT = 4000;
|
||||
|
||||
private static UIComponent s_TargetComponent;
|
||||
private string m_JsonInput = "";
|
||||
private Vector2 m_ScrollPos;
|
||||
private bool m_ShowPreview = false;
|
||||
private string m_PreviewText = "";
|
||||
private bool m_OpenFilePending = false;
|
||||
private bool m_PreviewPending = false;
|
||||
private bool m_ImportPending = false;
|
||||
|
||||
public static void ShowWindow(UIComponent target)
|
||||
{
|
||||
s_TargetComponent = target;
|
||||
var window = GetWindow<UIComponentJsonImportWindow>("Import UI JSON");
|
||||
window.minSize = new Vector2(600, 400);
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (s_TargetComponent == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Target UI component is null. Please select a component first.", MessageType.Error);
|
||||
if (GUILayout.Button("Close")) Close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_JsonInput == null) m_JsonInput = "";
|
||||
EditorGUILayout.LabelField("Target: " + s_TargetComponent.gameObject.name + " (" + s_TargetComponent.GetType().Name + ")", EditorStyles.boldLabel);
|
||||
GUILayout.Space(10);
|
||||
EditorGUILayout.LabelField("Paste JSON Data:", EditorStyles.boldLabel);
|
||||
|
||||
using (var scroll = new EditorGUILayout.ScrollViewScope(m_ScrollPos, GUILayout.Height(250)))
|
||||
{
|
||||
m_ScrollPos = scroll.scrollPosition;
|
||||
if (m_JsonInput.Length <= TEXTAREA_SAFE_CHAR_LIMIT)
|
||||
{
|
||||
m_JsonInput = EditorGUILayout.TextArea(m_JsonInput, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true));
|
||||
}
|
||||
else
|
||||
{
|
||||
var preview = m_JsonInput.Substring(0, LARGE_JSON_PREVIEW_CHAR_LIMIT);
|
||||
EditorGUILayout.HelpBox("JSON content is very large. Only a preview is shown below. Import uses full content.", MessageType.Info);
|
||||
EditorGUILayout.TextArea(preview, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true));
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox("Paste JSON directly, or click Open Json File.", MessageType.Info);
|
||||
GUILayout.Space(10);
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("Open Json File", GUILayout.Width(120))) RequestOpenJsonFile();
|
||||
if (GUILayout.Button("Preview", GUILayout.Width(100))) RequestPreviewJson();
|
||||
}
|
||||
|
||||
if (m_ShowPreview && !string.IsNullOrEmpty(m_PreviewText))
|
||||
{
|
||||
EditorGUILayout.TextArea(m_PreviewText, GUILayout.Height(120));
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("Import", GUILayout.Height(40), GUILayout.Width(150))) RequestImportJson();
|
||||
if (GUILayout.Button("Cancel", GUILayout.Height(40), GUILayout.Width(150))) Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void RequestOpenJsonFile()
|
||||
{
|
||||
if (m_OpenFilePending) return;
|
||||
m_OpenFilePending = true;
|
||||
EditorApplication.delayCall += delegate ()
|
||||
{
|
||||
m_OpenFilePending = false;
|
||||
if (this == null) return;
|
||||
OpenJsonFile();
|
||||
Repaint();
|
||||
};
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
|
||||
private void RequestPreviewJson()
|
||||
{
|
||||
if (m_PreviewPending) return;
|
||||
m_PreviewPending = true;
|
||||
EditorApplication.delayCall += delegate ()
|
||||
{
|
||||
m_PreviewPending = false;
|
||||
if (this == null) return;
|
||||
PreviewJson();
|
||||
Repaint();
|
||||
};
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
|
||||
private void RequestImportJson()
|
||||
{
|
||||
if (m_ImportPending) return;
|
||||
m_ImportPending = true;
|
||||
EditorApplication.delayCall += delegate ()
|
||||
{
|
||||
m_ImportPending = false;
|
||||
if (this == null) return;
|
||||
ImportJson();
|
||||
};
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
|
||||
private void PreviewJson()
|
||||
{
|
||||
if (string.IsNullOrEmpty(m_JsonInput))
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", "JSON input is empty.", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var json = JsonUtility.FromJson<UIComponentJson>(m_JsonInput);
|
||||
if (json == null)
|
||||
m_PreviewText = "Invalid JSON or unsupported schema.";
|
||||
else
|
||||
m_PreviewText = "Component Type: " + json.componentType + "\nSchema: " + json.schemaVersion + "\nVersion: " + json.componentVersion;
|
||||
m_ShowPreview = true;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Preview Error", "Invalid JSON: " + ex.Message, "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private void ImportJson()
|
||||
{
|
||||
if (string.IsNullOrEmpty(m_JsonInput))
|
||||
{
|
||||
EditorUtility.DisplayDialog("Error", "JSON input is empty. Please paste JSON data.", "OK");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
Undo.RecordObject(s_TargetComponent, "Import UI Component JSON");
|
||||
s_TargetComponent.ImportFromJson(m_JsonInput);
|
||||
s_TargetComponent.RebuildChartObject();
|
||||
s_TargetComponent.RefreshAllComponent();
|
||||
s_TargetComponent.RefreshGraph();
|
||||
EditorUtility.SetDirty(s_TargetComponent);
|
||||
SceneView.RepaintAll();
|
||||
var target = s_TargetComponent;
|
||||
EditorApplication.delayCall += delegate ()
|
||||
{
|
||||
if (target == null) return;
|
||||
target.RefreshAllComponent();
|
||||
target.RefreshGraph();
|
||||
SceneView.RepaintAll();
|
||||
};
|
||||
EditorUtility.DisplayDialog("Success", "UI component imported successfully!", "OK");
|
||||
Close();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Import Error", "Failed to import JSON:\n" + ex.Message + "\n\n" + ex.StackTrace, "OK");
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenJsonFile()
|
||||
{
|
||||
var path = EditorUtility.OpenFilePanel("Open UI Component JSON", "", "json");
|
||||
if (string.IsNullOrEmpty(path)) return;
|
||||
try
|
||||
{
|
||||
m_JsonInput = System.IO.File.ReadAllText(path);
|
||||
m_ShowPreview = false;
|
||||
m_PreviewText = "";
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Open File Error", "Failed to read JSON file:\n" + ex.Message, "OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Editor/Windows/UIComponentJsonImportWindow.cs.meta
Normal file
11
Editor/Windows/UIComponentJsonImportWindow.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68157a6f7d4e94ccc8ccbb4913d187f3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -495,17 +495,17 @@ namespace XCharts.Runtime
|
||||
public static double GetAxisPositionValue(GridCoord grid, Axis axis, Vector3 pos)
|
||||
{
|
||||
if (axis is YAxis)
|
||||
return GetAxisPositionValue(pos.y, grid.context.height, axis.context.minMaxRange, grid.context.y, axis.context.offset);
|
||||
return GetAxisPositionValue(pos.y, grid.context.height, axis.context.minMaxRange, grid.context.y, axis.context.offset, axis.context.minValue);
|
||||
else if (axis is XAxis)
|
||||
return GetAxisPositionValue(pos.x, grid.context.width, axis.context.minMaxRange, grid.context.x, axis.context.offset);
|
||||
return GetAxisPositionValue(pos.x, grid.context.width, axis.context.minMaxRange, grid.context.x, axis.context.offset, axis.context.minValue);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static double GetAxisPositionValue(float xy, float axisLength, double axisRange, float axisStart, float axisOffset)
|
||||
public static double GetAxisPositionValue(float xy, float axisLength, double axisRange, float axisStart, float axisOffset, double minValue = 0)
|
||||
{
|
||||
var yRate = axisRange / axisLength;
|
||||
return yRate * (xy - axisStart - axisOffset);
|
||||
return minValue + yRate * (xy - axisStart - axisOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace XCharts.Runtime
|
||||
[SerializeField] private float m_BorderWidth;
|
||||
[SerializeField] private Color32 m_BorderColor;
|
||||
[SerializeField] private bool m_RoundedCorner = true;
|
||||
[SerializeField] private float[] m_CornerRadius = new float[] { 0, 0, 0, 0 };
|
||||
[SerializeField] private float[] m_CornerRadius = new float[] { 10, 10, 10, 10 };
|
||||
|
||||
/// <summary>
|
||||
/// whether the border is visible.
|
||||
|
||||
@@ -255,22 +255,47 @@ namespace XCharts.Runtime
|
||||
if (dataZoom.IsInZoom(localPos) &&
|
||||
!dataZoom.IsInSelectedZoom(localPos))
|
||||
{
|
||||
var pointerX = localPos.x;
|
||||
var selectWidth = grid.context.width * (dataZoom.end - dataZoom.start) / 100;
|
||||
var startX = pointerX - selectWidth / 2;
|
||||
var endX = pointerX + selectWidth / 2;
|
||||
if (startX < grid.context.x)
|
||||
var start = dataZoom.start;
|
||||
var end = dataZoom.end;
|
||||
switch (dataZoom.orient)
|
||||
{
|
||||
startX = grid.context.x;
|
||||
endX = grid.context.x + selectWidth;
|
||||
case Orient.Horizonal:
|
||||
var pointerX = localPos.x;
|
||||
var selectWidth = dataZoom.context.width * (dataZoom.end - dataZoom.start) / 100;
|
||||
var startX = pointerX - selectWidth / 2;
|
||||
var endX = pointerX + selectWidth / 2;
|
||||
if (startX < dataZoom.context.x)
|
||||
{
|
||||
startX = dataZoom.context.x;
|
||||
endX = dataZoom.context.x + selectWidth;
|
||||
}
|
||||
else if (endX > dataZoom.context.x + dataZoom.context.width)
|
||||
{
|
||||
endX = dataZoom.context.x + dataZoom.context.width;
|
||||
startX = dataZoom.context.x + dataZoom.context.width - selectWidth;
|
||||
}
|
||||
start = (startX - dataZoom.context.x) / dataZoom.context.width * 100;
|
||||
end = (endX - dataZoom.context.x) / dataZoom.context.width * 100;
|
||||
break;
|
||||
case Orient.Vertical:
|
||||
var pointerY = localPos.y;
|
||||
var selectHeight = dataZoom.context.height * (dataZoom.end - dataZoom.start) / 100;
|
||||
var startY = pointerY - selectHeight / 2;
|
||||
var endY = pointerY + selectHeight / 2;
|
||||
if (startY < dataZoom.context.y)
|
||||
{
|
||||
startY = dataZoom.context.y;
|
||||
endY = dataZoom.context.y + selectHeight;
|
||||
}
|
||||
else if (endY > dataZoom.context.y + dataZoom.context.height)
|
||||
{
|
||||
endY = dataZoom.context.y + dataZoom.context.height;
|
||||
startY = dataZoom.context.y + dataZoom.context.height - selectHeight;
|
||||
}
|
||||
start = (startY - dataZoom.context.y) / dataZoom.context.height * 100;
|
||||
end = (endY - dataZoom.context.y) / dataZoom.context.height * 100;
|
||||
break;
|
||||
}
|
||||
else if (endX > grid.context.x + grid.context.width)
|
||||
{
|
||||
endX = grid.context.x + grid.context.width;
|
||||
startX = grid.context.x + grid.context.width - selectWidth;
|
||||
}
|
||||
var start = (startX - grid.context.x) / grid.context.width * 100;
|
||||
var end = (endX - grid.context.x) / grid.context.width * 100;
|
||||
UpdateDataZoomRange(dataZoom, start, end, grid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,8 @@ namespace XCharts.Runtime
|
||||
[SerializeField] private bool m_ItemAutoColor = true;
|
||||
[SerializeField] private float m_ItemOpacity = 1;
|
||||
[SerializeField][Since("v3.15.0")] private float m_ItemInactiveOpacity = 1;
|
||||
[SerializeField][Since("v3.16.0")] private float m_Width = 0;
|
||||
[SerializeField][Since("v3.16.0")] private float m_Height = 0;
|
||||
[SerializeField] private string m_Formatter;
|
||||
[SerializeField] private LabelStyle m_LabelStyle = new LabelStyle();
|
||||
[SerializeField][Since("v3.10.0")] private TextLimit m_TextLimit = new TextLimit();
|
||||
@@ -202,6 +204,25 @@ namespace XCharts.Runtime
|
||||
set { if (PropertyUtil.SetClass(ref m_Formatter, value)) SetComponentDirty(); }
|
||||
}
|
||||
/// <summary>
|
||||
/// the width of legend component. Default is 0 for auto adapt. When set a value between 0 and 1, it means the percentage relative to chart width and height.
|
||||
/// ||图例组件的宽。默认为0自适应。当设置0-1的值时,表示相对于图表宽高的比例。
|
||||
/// </summary>
|
||||
public float width
|
||||
{
|
||||
get { return m_Width; }
|
||||
set { if (PropertyUtil.SetStruct(ref m_Width, value)) SetComponentDirty(); }
|
||||
}
|
||||
/// <summary>
|
||||
/// the height of legend component. Default is 0 for auto adapt. When set a value between 0 and 1, it means the percentage relative to chart width and height.
|
||||
/// ||图例组件的高。默认为0自适应。当设置0-1的值时,表示相对于图表宽高的比例。
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public float height
|
||||
{
|
||||
get { return m_Height; }
|
||||
set { if (PropertyUtil.SetStruct(ref m_Height, value)) SetComponentDirty(); }
|
||||
}
|
||||
/// <summary>
|
||||
/// the style of text.
|
||||
/// ||文本样式。
|
||||
/// </summary>
|
||||
|
||||
@@ -93,10 +93,23 @@ namespace XCharts.Runtime
|
||||
var startY = 0f;
|
||||
var legendMaxWidth = chartWidth - legend.location.runtimeLeft - legend.location.runtimeRight;
|
||||
var legendMaxHeight = chartHeight - legend.location.runtimeTop - legend.location.runtimeBottom;
|
||||
var isVertical = legend.orient == Orient.Vertical;
|
||||
var fixedWidth = legend.width <= 0 ? 0
|
||||
: legend.width < 1 ? chartWidth * legend.width
|
||||
: legend.width;
|
||||
var fixedHeight = legend.height <= 0 ? 0
|
||||
: legend.height < 1 ? chartHeight * legend.height
|
||||
: legend.height;
|
||||
// Horizonal: width constrains layout wrapping; Vertical: height constrains layout wrapping.
|
||||
// The other axis only affects the background size, not the layout.
|
||||
if (!isVertical && fixedWidth > 0) legendMaxWidth = fixedWidth;
|
||||
if (isVertical && fixedHeight > 0) legendMaxHeight = fixedHeight;
|
||||
UpdateLegendWidthAndHeight(legend, legendMaxWidth, legendMaxHeight);
|
||||
// Override context size for fixed dimensions (controls background rect size).
|
||||
if (fixedWidth > 0) legend.context.width = fixedWidth;
|
||||
if (fixedHeight > 0) legend.context.height = fixedHeight;
|
||||
var legendRuntimeWidth = legend.context.width;
|
||||
var legendRuntimeHeight = legend.context.height;
|
||||
var isVertical = legend.orient == Orient.Vertical;
|
||||
switch (legend.location.align)
|
||||
{
|
||||
case Location.Align.TopCenter:
|
||||
@@ -198,11 +211,14 @@ namespace XCharts.Runtime
|
||||
legend.context.eachHeight = 0;
|
||||
if (legend.orient == Orient.Horizonal)
|
||||
{
|
||||
var maxRowWidth = 0f;
|
||||
foreach (var kv in legend.context.buttonList)
|
||||
{
|
||||
if (width + kv.Value.width > maxWidth)
|
||||
{
|
||||
realWidth = width - legend.itemGap;
|
||||
if (realWidth > maxRowWidth)
|
||||
maxRowWidth = realWidth;
|
||||
realHeight += height + legend.itemGap;
|
||||
if (legend.context.eachHeight < height + legend.itemGap)
|
||||
{
|
||||
@@ -216,8 +232,10 @@ namespace XCharts.Runtime
|
||||
height = kv.Value.height;
|
||||
}
|
||||
width -= legend.itemGap;
|
||||
if (width > maxRowWidth)
|
||||
maxRowWidth = width;
|
||||
legend.context.height = realHeight + height;
|
||||
legend.context.width = realWidth > 0 ? realWidth : width;
|
||||
legend.context.width = maxRowWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -778,5 +778,27 @@ namespace XCharts.Runtime
|
||||
foreach (var component in m_Components) component.ResetStatus();
|
||||
foreach (var handler in m_SerieHandlers) handler.ForceUpdateSerieContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Export chart configuration and data to JSON string.
|
||||
/// ||导出图表配置和数据为JSON字符串。
|
||||
/// </summary>
|
||||
[Since("v3.16.0")]
|
||||
public string ExportToJson(bool prettyPrint = true)
|
||||
{
|
||||
return XCharts.Runtime.ChartJsonSerializer.Serialize(this, prettyPrint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Import JSON and update current chart configuration.
|
||||
/// ||导入JSON并更新当前图表配置。
|
||||
/// </summary>
|
||||
[Since("v3.16.0")]
|
||||
public void ImportFromJson(string json)
|
||||
{
|
||||
XCharts.Runtime.ChartJsonDeserializer.Deserialize(json, this);
|
||||
RefreshAllComponent();
|
||||
RefreshChart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,5 +154,25 @@ namespace XCharts.Runtime
|
||||
}
|
||||
|
||||
protected virtual void OnThemeChanged() { }
|
||||
|
||||
/// <summary>
|
||||
/// Export UI component configuration to JSON string.
|
||||
/// </summary>
|
||||
[Since("v3.16.0")]
|
||||
public string ExportToJson(bool prettyPrint = true)
|
||||
{
|
||||
return UIComponentJsonSerializer.Serialize(this, prettyPrint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Import JSON and update current UI component configuration.
|
||||
/// </summary>
|
||||
[Since("v3.16.0")]
|
||||
public void ImportFromJson(string json)
|
||||
{
|
||||
UIComponentJsonDeserializer.Deserialize(json, this);
|
||||
RefreshAllComponent();
|
||||
RefreshGraph();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1290,8 +1290,7 @@ namespace XCharts.Runtime
|
||||
return null;
|
||||
|
||||
var clampedExportScale = Mathf.Max(1f, exportScale);
|
||||
var scaleFactor = canvas.scaleFactor <= 0 ? 1f : canvas.scaleFactor;
|
||||
var outputScaleFactor = scaleFactor * clampedExportScale;
|
||||
var outputScaleFactor = clampedExportScale;
|
||||
var width = Mathf.Max(1, Mathf.CeilToInt(rectTransform.rect.width * outputScaleFactor));
|
||||
var height = Mathf.Max(1, Mathf.CeilToInt(rectTransform.rect.height * outputScaleFactor));
|
||||
var chart = rectTransform.GetComponent<BaseChart>();
|
||||
@@ -1404,8 +1403,8 @@ namespace XCharts.Runtime
|
||||
// so the saved image has original width/height but higher quality.
|
||||
if (clampedExportScale > 1f)
|
||||
{
|
||||
var targetWidth = Mathf.Max(1, Mathf.CeilToInt(rectTransform.rect.width * scaleFactor));
|
||||
var targetHeight = Mathf.Max(1, Mathf.CeilToInt(rectTransform.rect.height * scaleFactor));
|
||||
var targetWidth = Mathf.Max(1, Mathf.CeilToInt(rectTransform.rect.width));
|
||||
var targetHeight = Mathf.Max(1, Mathf.CeilToInt(rectTransform.rect.height));
|
||||
|
||||
var smallRT = RenderTexture.GetTemporary(targetWidth, targetHeight, 0, rt.format);
|
||||
Graphics.Blit(rt, smallRT);
|
||||
@@ -1416,7 +1415,7 @@ namespace XCharts.Runtime
|
||||
tex.Apply();
|
||||
RenderTexture.ReleaseTemporary(smallRT);
|
||||
|
||||
var cornerRadiiFinal = GetChartCornerRadius(chart, rectTransform.rect.width, rectTransform.rect.height, scaleFactor);
|
||||
var cornerRadiiFinal = GetChartCornerRadius(chart, rectTransform.rect.width, rectTransform.rect.height, 1f);
|
||||
ApplyRoundedCornerClip(tex, cornerRadiiFinal);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -217,7 +217,8 @@ namespace XCharts.Runtime
|
||||
var close = serieData.GetCurrData(startDataIndex + 1, dataAddDuration, dataChangeDuration, yAxis.inverse, yMinValue, yMaxValue, unscaledTime);
|
||||
var lowest = serieData.GetCurrData(startDataIndex + 2, dataAddDuration, dataChangeDuration, yAxis.inverse, yMinValue, yMaxValue, unscaledTime);
|
||||
var heighest = serieData.GetCurrData(startDataIndex + 3, dataAddDuration, dataChangeDuration, yAxis.inverse, yMinValue, yMaxValue, unscaledTime);
|
||||
var isRise = yAxis.inverse ? close < open : close > open;
|
||||
var isBodyRise = yAxis.inverse ? close <= open : close >= open;
|
||||
var isColorRise = IsColorRise(showData, i, startDataIndex, open, close);
|
||||
var borderWidth = open == 0 ? 0f :
|
||||
(itemStyle.borderWidth == 0 ? theme.serie.candlestickBorderWidth :
|
||||
itemStyle.borderWidth);
|
||||
@@ -239,7 +240,7 @@ namespace XCharts.Runtime
|
||||
Vector3 plb, plt, prt, prb, top;
|
||||
|
||||
var offset = 2 * borderWidth;
|
||||
if (isRise)
|
||||
if (isBodyRise)
|
||||
{
|
||||
plb = new Vector3(pX + gap + offset, pY + offset);
|
||||
plt = new Vector3(pX + gap + offset, pY + currHig - offset);
|
||||
@@ -265,10 +266,10 @@ namespace XCharts.Runtime
|
||||
}
|
||||
serie.context.dataPoints.Add(top);
|
||||
serie.context.dataIndexs.Add(serieData.index);
|
||||
var areaColor = isRise ?
|
||||
var areaColor = isColorRise ?
|
||||
itemStyle.GetColor(theme.serie.candlestickColor) :
|
||||
itemStyle.GetColor0(theme.serie.candlestickColor0);
|
||||
var borderColor = isRise ?
|
||||
var borderColor = isColorRise ?
|
||||
itemStyle.GetBorderColor(theme.serie.candlestickBorderColor) :
|
||||
itemStyle.GetBorderColor0(theme.serie.candlestickBorderColor0);
|
||||
var itemWidth = Mathf.Abs(prt.x - plb.x);
|
||||
@@ -312,7 +313,7 @@ namespace XCharts.Runtime
|
||||
{
|
||||
UGL.DrawLine(vh, openCenterPos, closeCenterPos, Mathf.Max(borderWidth, barWidth / 2), borderColor);
|
||||
}
|
||||
if (isRise)
|
||||
if (isBodyRise)
|
||||
{
|
||||
UGL.DrawLine(vh, openCenterPos, lowPos, borderWidth, borderColor);
|
||||
UGL.DrawLine(vh, closeCenterPos, heighPos, borderWidth, borderColor);
|
||||
@@ -333,5 +334,17 @@ namespace XCharts.Runtime
|
||||
chart.RefreshPainter(serie);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsColorRise(List<SerieData> showData, int dataIndex, int startDataIndex, double open, double close)
|
||||
{
|
||||
if (dataIndex > 0)
|
||||
{
|
||||
var prevSerieData = showData[dataIndex - 1];
|
||||
var prevStartDataIndex = prevSerieData.data.Count > 4 ? 1 : 0;
|
||||
var prevClose = prevSerieData.GetData(prevStartDataIndex + 1);
|
||||
return close >= prevClose;
|
||||
}
|
||||
return close >= open;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,7 +151,8 @@ namespace XCharts.Runtime
|
||||
var close = serieData.GetCurrData(startDataIndex + 1, dataAddDuration, dataChangeDuration, yAxis.inverse, yMinValue, yMaxValue, unscaledTime);
|
||||
var lowest = serieData.GetCurrData(startDataIndex + 2, dataAddDuration, dataChangeDuration, yAxis.inverse, yMinValue, yMaxValue, unscaledTime);
|
||||
var heighest = serieData.GetCurrData(startDataIndex + 3, dataAddDuration, dataChangeDuration, yAxis.inverse, yMinValue, yMaxValue, unscaledTime);
|
||||
var isRise = yAxis.inverse ? close<open : close> open;
|
||||
var isBodyRise = yAxis.inverse ? close <= open : close >= open;
|
||||
var isColorRise = IsColorRise(showData, i, startDataIndex, open, close);
|
||||
var borderWidth = open == 0 ? 0f :
|
||||
(itemStyle.borderWidth == 0 ? theme.serie.candlestickBorderWidth :
|
||||
itemStyle.borderWidth);
|
||||
@@ -173,7 +174,7 @@ namespace XCharts.Runtime
|
||||
Vector3 plb, plt, prt, prb, top;
|
||||
|
||||
var offset = 2 * borderWidth;
|
||||
if (isRise)
|
||||
if (isBodyRise)
|
||||
{
|
||||
plb = new Vector3(pX + gap + offset, pY + offset);
|
||||
plt = new Vector3(pX + gap + offset, pY + currHig - offset);
|
||||
@@ -199,10 +200,10 @@ namespace XCharts.Runtime
|
||||
// }
|
||||
serie.context.dataPoints.Add(top);
|
||||
serie.context.dataIndexs.Add(serieData.index);
|
||||
var areaColor = isRise ?
|
||||
var areaColor = isColorRise ?
|
||||
itemStyle.GetColor(theme.serie.candlestickColor) :
|
||||
itemStyle.GetColor0(theme.serie.candlestickColor0);
|
||||
var borderColor = isRise ?
|
||||
var borderColor = isColorRise ?
|
||||
itemStyle.GetBorderColor(theme.serie.candlestickBorderColor) :
|
||||
itemStyle.GetBorderColor0(theme.serie.candlestickBorderColor0);
|
||||
var itemWidth = Mathf.Abs(prt.x - plb.x);
|
||||
@@ -235,7 +236,7 @@ namespace XCharts.Runtime
|
||||
UGL.DrawBorder(vh, center, itemWidth, itemHeight, 2 * borderWidth, borderColor, 0,
|
||||
itemStyle.cornerRadius, isYAxis, 0.5f);
|
||||
}
|
||||
if (isRise)
|
||||
if (isBodyRise)
|
||||
{
|
||||
UGL.DrawLine(vh, openCenterPos, lowPos, borderWidth, borderColor);
|
||||
UGL.DrawLine(vh, closeCenterPos, heighPos, borderWidth, borderColor);
|
||||
@@ -261,5 +262,17 @@ namespace XCharts.Runtime
|
||||
chart.RefreshPainter(serie);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsColorRise(List<SerieData> showData, int dataIndex, int startDataIndex, double open, double close)
|
||||
{
|
||||
if (dataIndex > 0)
|
||||
{
|
||||
var prevSerieData = showData[dataIndex - 1];
|
||||
var prevStartDataIndex = prevSerieData.data.Count > 4 ? 1 : 0;
|
||||
var prevClose = prevSerieData.GetData(prevStartDataIndex + 1);
|
||||
return close >= prevClose;
|
||||
}
|
||||
return close >= open;
|
||||
}
|
||||
}
|
||||
}
|
||||
1562
Runtime/Utilities/ChartJsonKit.cs
Normal file
1562
Runtime/Utilities/ChartJsonKit.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Runtime/Utilities/ChartJsonKit.cs.meta
Normal file
11
Runtime/Utilities/ChartJsonKit.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 529ec33cd4fb6466784b3a682204428c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
291
Runtime/Utilities/ResourceRefHandler.cs
Normal file
291
Runtime/Utilities/ResourceRefHandler.cs
Normal file
@@ -0,0 +1,291 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
#if dUI_TextMeshPro
|
||||
using TMPro;
|
||||
#endif
|
||||
|
||||
namespace XCharts.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Portable resource reference that supports a multi-level lookup strategy:
|
||||
/// GUID → path → name → fallbackName → null/default
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ResourceRef
|
||||
{
|
||||
/// <summary>Unity AssetDatabase GUID (editor-only, same project).</summary>
|
||||
public string guid;
|
||||
/// <summary>Asset path relative to project root e.g. "Assets/Fonts/Arial.ttf".</summary>
|
||||
public string path;
|
||||
/// <summary>Asset.name used for cross-project name search.</summary>
|
||||
public string name;
|
||||
/// <summary>Optional secondary name (system font alias, built-in default, etc.).</summary>
|
||||
public string fallbackName;
|
||||
/// <summary>Optional Base64-encoded asset bytes for full portability (< 100 KB recommended).</summary>
|
||||
public string base64;
|
||||
|
||||
public bool IsEmpty()
|
||||
{
|
||||
return string.IsNullOrEmpty(guid)
|
||||
&& string.IsNullOrEmpty(path)
|
||||
&& string.IsNullOrEmpty(name)
|
||||
&& string.IsNullOrEmpty(fallbackName)
|
||||
&& string.IsNullOrEmpty(base64);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("ResourceRef{{name={0}, path={1}, guid={2}}}", name, path, guid);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles serialization and resolution of Unity asset references for chart JSON export/import.
|
||||
/// Supports Font, TMP_FontAsset, Sprite, Material, Texture2D.
|
||||
/// </summary>
|
||||
public static class ResourceRefHandler
|
||||
{
|
||||
// ─── Serialize ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Serializes a Unity Object into a portable ResourceRef.
|
||||
/// </summary>
|
||||
public static ResourceRef Serialize(UnityEngine.Object asset, bool includeBase64 = false)
|
||||
{
|
||||
if (asset == null) return null;
|
||||
|
||||
var refData = new ResourceRef
|
||||
{
|
||||
name = asset.name
|
||||
};
|
||||
|
||||
#if UNITY_EDITOR
|
||||
var assetPath = AssetDatabase.GetAssetPath(asset);
|
||||
if (!string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
refData.path = assetPath;
|
||||
refData.guid = AssetDatabase.AssetPathToGUID(assetPath);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Optional base64 embedding for portability
|
||||
if (includeBase64)
|
||||
{
|
||||
var b64 = TryEncodeBase64(asset);
|
||||
if (!string.IsNullOrEmpty(b64))
|
||||
refData.base64 = b64;
|
||||
}
|
||||
|
||||
return refData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes a Font with a fallback name hint.
|
||||
/// </summary>
|
||||
public static ResourceRef SerializeFont(Font font, string fallbackName = null)
|
||||
{
|
||||
if (font == null) return null;
|
||||
var refData = Serialize(font) ?? new ResourceRef();
|
||||
refData.fallbackName = fallbackName ?? "Arial";
|
||||
return refData;
|
||||
}
|
||||
|
||||
#if dUI_TextMeshPro
|
||||
/// <summary>
|
||||
/// Serializes a TMP_FontAsset with a fallback name hint.
|
||||
/// </summary>
|
||||
public static ResourceRef SerializeTMPFont(TMP_FontAsset font, string fallbackName = null)
|
||||
{
|
||||
if (font == null) return null;
|
||||
var refData = Serialize(font) ?? new ResourceRef();
|
||||
refData.fallbackName = fallbackName ?? "LiberationSans SDF";
|
||||
return refData;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ─── Resolve ────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to resolve a ResourceRef back to a Unity asset using the fallback chain:<br/>
|
||||
/// GUID → path → name → fallbackName → null
|
||||
/// </summary>
|
||||
public static T TryResolve<T>(ResourceRef refData) where T : UnityEngine.Object
|
||||
{
|
||||
if (refData == null || refData.IsEmpty()) return null;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// 1. GUID lookup (same project, exact)
|
||||
if (!string.IsNullOrEmpty(refData.guid))
|
||||
{
|
||||
var guidPath = AssetDatabase.GUIDToAssetPath(refData.guid);
|
||||
if (!string.IsNullOrEmpty(guidPath))
|
||||
{
|
||||
var asset = AssetDatabase.LoadAssetAtPath<T>(guidPath);
|
||||
if (asset != null) return asset;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Path-based lookup
|
||||
if (!string.IsNullOrEmpty(refData.path))
|
||||
{
|
||||
var asset = AssetDatabase.LoadAssetAtPath<T>(refData.path);
|
||||
if (asset != null) return asset;
|
||||
}
|
||||
|
||||
// 3. Name-based search across project
|
||||
if (!string.IsNullOrEmpty(refData.name))
|
||||
{
|
||||
var found = FindAssetByName<T>(refData.name);
|
||||
if (found != null) return found;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 4. Resources.Load by name
|
||||
if (!string.IsNullOrEmpty(refData.name))
|
||||
{
|
||||
var asset = Resources.Load<T>(refData.name);
|
||||
if (asset != null) return asset;
|
||||
}
|
||||
|
||||
// 5. Fallback name
|
||||
if (!string.IsNullOrEmpty(refData.fallbackName))
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
var found = FindAssetByName<T>(refData.fallbackName);
|
||||
if (found != null) return found;
|
||||
#endif
|
||||
var asset = Resources.Load<T>(refData.fallbackName);
|
||||
if (asset != null) return asset;
|
||||
}
|
||||
|
||||
// 6. Base64 decode
|
||||
if (!string.IsNullOrEmpty(refData.base64))
|
||||
{
|
||||
var decoded = TryDecodeBase64<T>(refData.base64, refData.name ?? "imported_asset");
|
||||
if (decoded != null) return decoded;
|
||||
}
|
||||
|
||||
if (!IsUnityBuiltinDefaultResourceRef(refData))
|
||||
Debug.LogWarning(string.Format("[XCharts] ResourceRefHandler: Could not resolve asset '{0}'. Using default.", refData));
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsUnityBuiltinDefaultResourceRef(ResourceRef refData)
|
||||
{
|
||||
if (refData == null) return false;
|
||||
|
||||
bool pathIsBuiltin = !string.IsNullOrEmpty(refData.path)
|
||||
&& refData.path.IndexOf("Library/unity default resources", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
|
||||
bool guidIsBuiltin = !string.IsNullOrEmpty(refData.guid)
|
||||
&& string.Equals(refData.guid, "0000000000000000e000000000000000", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
bool nameIsBuiltinFont = string.Equals(refData.name, "Arial", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(refData.fallbackName, "Arial", StringComparison.OrdinalIgnoreCase)
|
||||
#if dUI_TextMeshPro
|
||||
|| string.Equals(refData.name, "LiberationSans SDF", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(refData.fallbackName, "LiberationSans SDF", StringComparison.OrdinalIgnoreCase)
|
||||
#endif
|
||||
;
|
||||
|
||||
return pathIsBuiltin || guidIsBuiltin || nameIsBuiltinFont;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private static T FindAssetByName<T>(string assetName) where T : UnityEngine.Object
|
||||
{
|
||||
string typeName = typeof(T).Name;
|
||||
var guids = AssetDatabase.FindAssets(string.Format("{0} t:{1}", assetName, typeName));
|
||||
foreach (var g in guids)
|
||||
{
|
||||
var p = AssetDatabase.GUIDToAssetPath(g);
|
||||
var asset = AssetDatabase.LoadAssetAtPath<T>(p);
|
||||
if (asset != null && asset.name == assetName)
|
||||
return asset;
|
||||
}
|
||||
// Looser match (name contains)
|
||||
foreach (var g in guids)
|
||||
{
|
||||
var p = AssetDatabase.GUIDToAssetPath(g);
|
||||
var asset = AssetDatabase.LoadAssetAtPath<T>(p);
|
||||
if (asset != null) return asset;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ─── Base64 helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
private static string TryEncodeBase64(UnityEngine.Object asset)
|
||||
{
|
||||
var texture = asset as Texture2D;
|
||||
if (texture != null)
|
||||
return Convert.ToBase64String(texture.EncodeToPNG());
|
||||
// Font/Material: not trivially serializable as bytes at runtime; skip.
|
||||
return null;
|
||||
}
|
||||
|
||||
private static T TryDecodeBase64<T>(string base64, string assetName) where T : UnityEngine.Object
|
||||
{
|
||||
try
|
||||
{
|
||||
if (typeof(T) == typeof(Texture2D) || typeof(T) == typeof(Sprite))
|
||||
{
|
||||
var bytes = Convert.FromBase64String(base64);
|
||||
var tex = new Texture2D(2, 2);
|
||||
if (tex.LoadImage(bytes))
|
||||
{
|
||||
tex.name = assetName;
|
||||
if (typeof(T) == typeof(Sprite))
|
||||
{
|
||||
var sprite = Sprite.Create(tex,
|
||||
new Rect(0, 0, tex.width, tex.height),
|
||||
new Vector2(0.5f, 0.5f));
|
||||
sprite.name = assetName;
|
||||
return sprite as T;
|
||||
}
|
||||
return tex as T;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning(string.Format("[XCharts] ResourceRefHandler: Base64 decode failed: {0}", ex.Message));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ─── Convenience overloads ───────────────────────────────────────────────────
|
||||
|
||||
public static Font TryResolveFont(ResourceRef refData)
|
||||
{
|
||||
return TryResolve<Font>(refData);
|
||||
}
|
||||
|
||||
public static Sprite TryResolveSprite(ResourceRef refData)
|
||||
{
|
||||
return TryResolve<Sprite>(refData);
|
||||
}
|
||||
|
||||
public static Material TryResolveMaterial(ResourceRef refData)
|
||||
{
|
||||
return TryResolve<Material>(refData);
|
||||
}
|
||||
|
||||
public static Texture2D TryResolveTexture(ResourceRef refData)
|
||||
{
|
||||
return TryResolve<Texture2D>(refData);
|
||||
}
|
||||
|
||||
#if dUI_TextMeshPro
|
||||
public static TMP_FontAsset TryResolveTMPFont(ResourceRef refData)
|
||||
{
|
||||
return TryResolve<TMP_FontAsset>(refData);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
11
Runtime/Utilities/ResourceRefHandler.cs.meta
Normal file
11
Runtime/Utilities/ResourceRefHandler.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c3e7340177cb43e488f9f9547ceea7b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
299
Runtime/Utilities/UIComponentJsonKit.cs
Normal file
299
Runtime/Utilities/UIComponentJsonKit.cs
Normal file
@@ -0,0 +1,299 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace XCharts.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public class UIComponentJson
|
||||
{
|
||||
public string schemaVersion = "1.0";
|
||||
public string componentType;
|
||||
public string componentVersion;
|
||||
public string exportedAt;
|
||||
public string data;
|
||||
}
|
||||
|
||||
public static class UIComponentJsonSerializer
|
||||
{
|
||||
public static string Serialize(UIComponent component, bool prettyPrint = true)
|
||||
{
|
||||
if (component == null) throw new ArgumentNullException("component");
|
||||
|
||||
var currentJson = JsonUtility.ToJson(component);
|
||||
var defaultJson = GetDefaultInstanceJson(component.GetType());
|
||||
var dataJson = BuildDataJson(component.GetType(), currentJson, defaultJson);
|
||||
|
||||
var dto = new UIComponentJson
|
||||
{
|
||||
schemaVersion = "1.0",
|
||||
componentType = component.GetType().Name,
|
||||
componentVersion = XChartsMgr.version,
|
||||
exportedAt = DateTime.UtcNow.ToString("o"),
|
||||
data = dataJson
|
||||
};
|
||||
|
||||
var json = JsonUtility.ToJson(dto, prettyPrint);
|
||||
json = ChartJsonDataFieldCodec.ConvertEscapedDataStringToRawObject(json);
|
||||
if (prettyPrint)
|
||||
{
|
||||
object parsedJson;
|
||||
if (SimpleJson.TryParse(json, out parsedJson))
|
||||
json = SimpleJson.Stringify(parsedJson, true);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
private static string BuildDataJson(Type componentType, string currentJson, string defaultJson)
|
||||
{
|
||||
if (componentType == null)
|
||||
return JsonDiffPruner.PruneDefaults(currentJson, defaultJson);
|
||||
|
||||
if (string.Equals(componentType.Name, "UITable", StringComparison.Ordinal))
|
||||
return PruneUITableData(componentType, currentJson, defaultJson);
|
||||
|
||||
return JsonDiffPruner.PruneDefaults(currentJson, defaultJson);
|
||||
}
|
||||
|
||||
private static string PruneUITableData(Type tableType, string currentJson, string defaultJson)
|
||||
{
|
||||
object currentParsed;
|
||||
object defaultParsed;
|
||||
if (!SimpleJson.TryParse(currentJson, out currentParsed))
|
||||
return JsonDiffPruner.PruneDefaults(currentJson, defaultJson);
|
||||
if (!SimpleJson.TryParse(defaultJson, out defaultParsed))
|
||||
return JsonDiffPruner.PruneDefaults(currentJson, defaultJson);
|
||||
|
||||
var currentRoot = currentParsed as System.Collections.Generic.Dictionary<string, object>;
|
||||
var defaultRoot = defaultParsed as System.Collections.Generic.Dictionary<string, object>;
|
||||
if (currentRoot == null)
|
||||
return JsonDiffPruner.PruneDefaults(currentJson, defaultJson);
|
||||
|
||||
var prunedRootObj = JsonDiffPruner.PruneParsedDefaults(currentRoot, defaultRoot);
|
||||
var prunedRoot = prunedRootObj as System.Collections.Generic.Dictionary<string, object>;
|
||||
if (prunedRoot == null)
|
||||
prunedRoot = new System.Collections.Generic.Dictionary<string, object>();
|
||||
|
||||
object currentRowsObj;
|
||||
if (!currentRoot.TryGetValue("m_Data", out currentRowsObj))
|
||||
return SimpleJson.Stringify(prunedRoot);
|
||||
|
||||
var currentRows = currentRowsObj as System.Collections.Generic.List<object>;
|
||||
if (currentRows == null)
|
||||
return SimpleJson.Stringify(prunedRoot);
|
||||
|
||||
var rowDefaultObj = CreateDefaultParsedObject(tableType.Assembly, "XCharts.Runtime.UI.TableRow");
|
||||
if (rowDefaultObj == null)
|
||||
return JsonDiffPruner.PruneDefaults(currentJson, defaultJson);
|
||||
|
||||
var cellDefaultObj = CreateDefaultParsedObject(tableType.Assembly, "XCharts.Runtime.UI.TableCell");
|
||||
|
||||
var prunedRows = new System.Collections.Generic.List<object>(currentRows.Count);
|
||||
for (int i = 0; i < currentRows.Count; i++)
|
||||
{
|
||||
var prunedRow = PruneTableRowKeepCellShape(currentRows[i], rowDefaultObj, cellDefaultObj);
|
||||
prunedRows.Add(prunedRow ?? new System.Collections.Generic.Dictionary<string, object>());
|
||||
}
|
||||
|
||||
prunedRoot["m_Data"] = prunedRows;
|
||||
return SimpleJson.Stringify(prunedRoot);
|
||||
}
|
||||
|
||||
private static object PruneTableRowKeepCellShape(object rowObj, object rowDefaultObj, object cellDefaultObj)
|
||||
{
|
||||
var rowDict = rowObj as System.Collections.Generic.Dictionary<string, object>;
|
||||
if (rowDict == null)
|
||||
return JsonDiffPruner.PruneParsedDefaults(rowObj, rowDefaultObj);
|
||||
|
||||
object rawCells;
|
||||
rowDict.TryGetValue("m_Data", out rawCells);
|
||||
var currentCells = rawCells as System.Collections.Generic.List<object>;
|
||||
|
||||
var prunedRowObj = JsonDiffPruner.PruneParsedDefaults(rowObj, rowDefaultObj);
|
||||
var prunedRowDict = prunedRowObj as System.Collections.Generic.Dictionary<string, object>;
|
||||
if (prunedRowDict == null)
|
||||
prunedRowDict = new System.Collections.Generic.Dictionary<string, object>();
|
||||
|
||||
if (currentCells != null)
|
||||
{
|
||||
var prunedCells = new System.Collections.Generic.List<object>(currentCells.Count);
|
||||
for (int i = 0; i < currentCells.Count; i++)
|
||||
{
|
||||
var prunedCell = cellDefaultObj != null
|
||||
? JsonDiffPruner.PruneParsedDefaults(currentCells[i], cellDefaultObj)
|
||||
: JsonDiffPruner.PruneParsedDefaults(currentCells[i], null);
|
||||
prunedCells.Add(prunedCell ?? new System.Collections.Generic.Dictionary<string, object>());
|
||||
}
|
||||
prunedRowDict["m_Data"] = prunedCells;
|
||||
}
|
||||
|
||||
if (prunedRowDict.Count == 0)
|
||||
return null;
|
||||
return prunedRowDict;
|
||||
}
|
||||
|
||||
private static object CreateDefaultParsedObject(Assembly assembly, string fullTypeName)
|
||||
{
|
||||
if (assembly == null || string.IsNullOrEmpty(fullTypeName))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var type = assembly.GetType(fullTypeName);
|
||||
if (type == null)
|
||||
return null;
|
||||
|
||||
var instance = Activator.CreateInstance(type);
|
||||
if (instance == null)
|
||||
return null;
|
||||
|
||||
var json = JsonUtility.ToJson(instance);
|
||||
object parsed;
|
||||
if (!SimpleJson.TryParse(json, out parsed))
|
||||
return null;
|
||||
return parsed;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetDefaultInstanceJson(Type type)
|
||||
{
|
||||
if (type == null) return "{}";
|
||||
|
||||
GameObject tempObject = null;
|
||||
try
|
||||
{
|
||||
if (typeof(MonoBehaviour).IsAssignableFrom(type))
|
||||
{
|
||||
tempObject = new GameObject("__XCharts_UIJson_Default__");
|
||||
tempObject.hideFlags = HideFlags.HideAndDontSave;
|
||||
var defaultComponent = tempObject.AddComponent(type) as UIComponent;
|
||||
if (defaultComponent != null)
|
||||
return JsonUtility.ToJson(defaultComponent);
|
||||
return "{}";
|
||||
}
|
||||
|
||||
var instance = Activator.CreateInstance(type);
|
||||
if (instance == null)
|
||||
return "{}";
|
||||
return JsonUtility.ToJson(instance);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "{}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (tempObject != null)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
UnityEngine.Object.DestroyImmediate(tempObject);
|
||||
#else
|
||||
UnityEngine.Object.Destroy(tempObject);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class UIComponentJsonDeserializer
|
||||
{
|
||||
private const string LOG_TAG = "[XCharts] UIComponentJsonDeserializer";
|
||||
|
||||
public static void Deserialize(string json, UIComponent target)
|
||||
{
|
||||
if (string.IsNullOrEmpty(json)) throw new ArgumentNullException("json");
|
||||
if (target == null) throw new ArgumentNullException("target");
|
||||
|
||||
json = ChartJsonDataFieldCodec.ConvertRawObjectDataToEscapedString(json);
|
||||
|
||||
UIComponentJson dto;
|
||||
try
|
||||
{
|
||||
dto = JsonUtility.FromJson<UIComponentJson>(json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ArgumentException(string.Format("Invalid JSON format: {0}", ex.Message), ex);
|
||||
}
|
||||
|
||||
if (dto == null || string.IsNullOrEmpty(dto.schemaVersion))
|
||||
throw new ArgumentException("JSON does not appear to be a valid XCharts UI component export (missing schemaVersion).");
|
||||
if (!dto.schemaVersion.StartsWith("1."))
|
||||
throw new ArgumentException(string.Format("Unsupported schema version '{0}'. This version only supports '1.x'.", dto.schemaVersion));
|
||||
|
||||
ValidateComponentType(dto.componentType, target);
|
||||
|
||||
if (!string.IsNullOrEmpty(dto.data))
|
||||
JsonUtility.FromJsonOverwrite(dto.data, target);
|
||||
|
||||
target.RefreshAllComponent();
|
||||
target.RefreshGraph();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorUtility.SetDirty(target);
|
||||
#endif
|
||||
Debug.Log(string.Format("{0}: Import complete for '{1}'.", LOG_TAG, target.GetType().Name));
|
||||
}
|
||||
|
||||
private static void ValidateComponentType(string typeName, UIComponent target)
|
||||
{
|
||||
if (string.IsNullOrEmpty(typeName) || target == null) return;
|
||||
|
||||
var resolved = ResolveType(typeName);
|
||||
if (resolved == null) return;
|
||||
|
||||
if (!resolved.IsAssignableFrom(target.GetType()))
|
||||
throw new ArgumentException(string.Format("JSON is for UI component '{0}', target is '{1}'.", resolved.Name, target.GetType().Name));
|
||||
}
|
||||
|
||||
private static Type ResolveType(string typeName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(typeName)) return null;
|
||||
|
||||
var type = Type.GetType(typeName);
|
||||
if (type != null) return type;
|
||||
|
||||
var shortName = typeName.Split(',')[0].Trim();
|
||||
var simpleName = shortName.Contains(".") ? shortName.Substring(shortName.LastIndexOf(".") + 1) : shortName;
|
||||
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
try
|
||||
{
|
||||
type = asm.GetType(shortName);
|
||||
if (type != null) return type;
|
||||
|
||||
var types = asm.GetTypes();
|
||||
for (int i = 0; i < types.Length; i++)
|
||||
{
|
||||
var candidate = types[i];
|
||||
if (candidate == null) continue;
|
||||
if (string.Equals(candidate.Name, simpleName, StringComparison.Ordinal))
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
catch (ReflectionTypeLoadException rtle)
|
||||
{
|
||||
var partialTypes = rtle.Types;
|
||||
if (partialTypes == null) continue;
|
||||
for (int i = 0; i < partialTypes.Length; i++)
|
||||
{
|
||||
var candidate = partialTypes[i];
|
||||
if (candidate == null) continue;
|
||||
if (string.Equals(candidate.Name, simpleName, StringComparison.Ordinal))
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/Utilities/UIComponentJsonKit.cs.meta
Normal file
11
Runtime/Utilities/UIComponentJsonKit.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20febe04d2ef346e196ab69da9c1d7d4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user