mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 14:24:53 +08:00
Add link property for animation and skin selection.
This commit is contained in:
parent
1447b85c6e
commit
3fa9330c83
@ -27,26 +27,25 @@
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
interface ModalButton<T> {
|
||||
text: string;
|
||||
color?: string;
|
||||
value?: T;
|
||||
style?: 'primary' | 'secondary';
|
||||
// Common theme type
|
||||
interface ModalTheme {
|
||||
overlayBg: string;
|
||||
captionBg: string;
|
||||
captionText: string;
|
||||
contentBg: string;
|
||||
contentText: string;
|
||||
buttonBg: string;
|
||||
buttonText: string;
|
||||
buttonBorder: string;
|
||||
buttonHoverBg: string;
|
||||
closeColor: string;
|
||||
itemHoverBg: string;
|
||||
itemSelectedBg: string;
|
||||
}
|
||||
|
||||
interface ModalOptions<T> {
|
||||
darkMode: boolean;
|
||||
title: string;
|
||||
text: string;
|
||||
buttons: ModalButton<T>[];
|
||||
maxWidth?: number;
|
||||
}
|
||||
|
||||
export function showModal<T> (options: ModalOptions<T>): Promise<T | undefined> {
|
||||
return new Promise((resolve) => {
|
||||
const { title, text, buttons, darkMode = false } = options;
|
||||
|
||||
const theme = darkMode ? {
|
||||
// Helper: Create theme
|
||||
function getTheme (darkMode: boolean): ModalTheme {
|
||||
return darkMode ? {
|
||||
overlayBg: 'rgba(0, 0, 0, 0.5)',
|
||||
captionBg: 'rgb(71, 71, 71)', // gray9
|
||||
captionText: 'rgb(214, 214, 214)', // gray27
|
||||
@ -57,6 +56,8 @@ export function showModal<T> (options: ModalOptions<T>): Promise<T | undefined>
|
||||
buttonBorder: 'rgb(56, 56, 56)', // gray7
|
||||
buttonHoverBg: 'rgb(79, 79, 79)', // gray10
|
||||
closeColor: 'rgb(168, 168, 168)', // gray21
|
||||
itemHoverBg: 'rgb(79, 79, 79)', // gray10
|
||||
itemSelectedBg: 'rgb(56, 56, 56)', // gray7
|
||||
} : {
|
||||
overlayBg: 'rgba(0, 0, 0, 0.3)',
|
||||
captionBg: 'rgb(247, 247, 247)', // gray31
|
||||
@ -68,8 +69,13 @@ export function showModal<T> (options: ModalOptions<T>): Promise<T | undefined>
|
||||
buttonBorder: 'rgb(199, 199, 199)', // gray25
|
||||
buttonHoverBg: 'rgb(214, 214, 214)', // gray27
|
||||
closeColor: 'rgb(94, 94, 94)', // gray12
|
||||
itemHoverBg: 'rgb(214, 214, 214)', // gray27
|
||||
itemSelectedBg: 'rgb(199, 199, 199)', // gray25
|
||||
};
|
||||
}
|
||||
|
||||
// Helper: Create overlay
|
||||
function createOverlay (theme: ModalTheme): HTMLDivElement {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.style.cssText = `
|
||||
position: fixed;
|
||||
@ -85,18 +91,26 @@ export function showModal<T> (options: ModalOptions<T>): Promise<T | undefined>
|
||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
|
||||
font-size: 14px;
|
||||
`;
|
||||
return overlay;
|
||||
}
|
||||
|
||||
// Helper: Create dialog
|
||||
function createDialog (theme: ModalTheme, minWidth: number): HTMLDivElement {
|
||||
const dialog = document.createElement('div');
|
||||
dialog.style.cssText = `
|
||||
background: ${theme.contentBg};
|
||||
border-radius: 6px;
|
||||
min-width: 200px;
|
||||
min-width: ${minWidth}px;
|
||||
max-width: 550px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
filter: drop-shadow(0 4px 5px rgba(10,10,10,0.35)) drop-shadow(0 2px 1px rgba(10,10,10,0.5));
|
||||
`;
|
||||
return dialog;
|
||||
}
|
||||
|
||||
// Helper: Create caption with close button
|
||||
function createCaption (title: string, theme: ModalTheme, onClose: () => void): HTMLDivElement {
|
||||
const caption = document.createElement('div');
|
||||
caption.style.cssText = `
|
||||
background: ${theme.captionBg};
|
||||
@ -127,18 +141,16 @@ export function showModal<T> (options: ModalOptions<T>): Promise<T | undefined>
|
||||
`;
|
||||
closeBtn.onmouseover = () => { closeBtn.style.opacity = '1'; };
|
||||
closeBtn.onmouseout = () => { closeBtn.style.opacity = '0.7'; };
|
||||
closeBtn.addEventListener('click', onClose);
|
||||
|
||||
caption.appendChild(titleSpan);
|
||||
caption.appendChild(closeBtn);
|
||||
|
||||
const contents = document.createElement('div');
|
||||
contents.style.cssText = `
|
||||
padding: 12px 14px;
|
||||
color: ${theme.contentText};
|
||||
line-height: 1.4;
|
||||
`;
|
||||
contents.textContent = text;
|
||||
return caption;
|
||||
}
|
||||
|
||||
// Helper: Create footer
|
||||
function createFooter (): HTMLDivElement {
|
||||
const footer = document.createElement('div');
|
||||
footer.style.cssText = `
|
||||
padding: 8px 14px 12px;
|
||||
@ -146,20 +158,13 @@ export function showModal<T> (options: ModalOptions<T>): Promise<T | undefined>
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
`;
|
||||
return footer;
|
||||
}
|
||||
|
||||
const cleanup = () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
overlay.remove();
|
||||
};
|
||||
|
||||
closeBtn.addEventListener('click', () => {
|
||||
cleanup();
|
||||
resolve(undefined);
|
||||
});
|
||||
|
||||
buttons.forEach((buttonConfig, index) => {
|
||||
// Helper: Create button
|
||||
function createButton (text: string, theme: ModalTheme, onClick: () => void): HTMLButtonElement {
|
||||
const btn = document.createElement('button');
|
||||
btn.textContent = buttonConfig.text;
|
||||
btn.textContent = text;
|
||||
btn.style.cssText = `
|
||||
padding: 4px 14px;
|
||||
border: 1px solid ${theme.buttonBorder};
|
||||
@ -172,12 +177,85 @@ export function showModal<T> (options: ModalOptions<T>): Promise<T | undefined>
|
||||
`;
|
||||
btn.onmouseover = () => { btn.style.background = theme.buttonHoverBg; };
|
||||
btn.onmouseout = () => { btn.style.background = theme.buttonBg; };
|
||||
btn.addEventListener('click', onClick);
|
||||
return btn;
|
||||
}
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
// Helper: Setup modal event handlers
|
||||
function setupModalHandlers (overlay: HTMLDivElement, onCancel: () => void, extraKeyHandler?: (e: KeyboardEvent) => boolean) {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
onCancel();
|
||||
return;
|
||||
}
|
||||
if (extraKeyHandler?.(e)) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) {
|
||||
onCancel();
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
overlay.remove();
|
||||
};
|
||||
}
|
||||
|
||||
// Original interfaces
|
||||
interface ModalButton<T> {
|
||||
text: string;
|
||||
color?: string;
|
||||
value?: T;
|
||||
style?: 'primary' | 'secondary';
|
||||
}
|
||||
|
||||
interface ModalOptions<T> {
|
||||
darkMode: boolean;
|
||||
title: string;
|
||||
text: string;
|
||||
buttons: ModalButton<T>[];
|
||||
maxWidth?: number;
|
||||
}
|
||||
|
||||
export function showModal<T> (options: ModalOptions<T>): Promise<T | undefined> {
|
||||
return new Promise((resolve) => {
|
||||
const { title, text, buttons, darkMode = false } = options;
|
||||
|
||||
const theme = getTheme(darkMode);
|
||||
const overlay = createOverlay(theme);
|
||||
const dialog = createDialog(theme, 200);
|
||||
|
||||
const cleanup = setupModalHandlers(overlay, () => {
|
||||
cleanup();
|
||||
resolve(undefined);
|
||||
});
|
||||
|
||||
const caption = createCaption(title, theme, () => {
|
||||
cleanup();
|
||||
resolve(undefined);
|
||||
});
|
||||
|
||||
const contents = document.createElement('div');
|
||||
contents.style.cssText = `
|
||||
padding: 12px 14px;
|
||||
color: ${theme.contentText};
|
||||
line-height: 1.4;
|
||||
`;
|
||||
contents.textContent = text;
|
||||
|
||||
const footer = createFooter();
|
||||
|
||||
buttons.forEach((buttonConfig, index) => {
|
||||
const btn = createButton(buttonConfig.text, theme, () => {
|
||||
cleanup();
|
||||
resolve(buttonConfig.value);
|
||||
});
|
||||
|
||||
footer.appendChild(btn);
|
||||
|
||||
if (index === buttons.length - 1) {
|
||||
@ -185,21 +263,6 @@ export function showModal<T> (options: ModalOptions<T>): Promise<T | undefined>
|
||||
}
|
||||
});
|
||||
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) {
|
||||
cleanup();
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
cleanup();
|
||||
resolve(undefined);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
dialog.appendChild(caption);
|
||||
dialog.appendChild(contents);
|
||||
dialog.appendChild(footer);
|
||||
@ -208,3 +271,296 @@ export function showModal<T> (options: ModalOptions<T>): Promise<T | undefined>
|
||||
});
|
||||
}
|
||||
|
||||
interface ListSelectionOptions {
|
||||
darkMode: boolean;
|
||||
title: string;
|
||||
items: string[];
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
}
|
||||
|
||||
export function showListSelectionModal (options: ListSelectionOptions): Promise<string | undefined> {
|
||||
return new Promise((resolve) => {
|
||||
const { title, items, darkMode = false, maxHeight = 400 } = options;
|
||||
|
||||
const theme = getTheme(darkMode);
|
||||
const overlay = createOverlay(theme);
|
||||
const dialog = createDialog(theme, 300);
|
||||
|
||||
let selectedItem: string | undefined;
|
||||
|
||||
const cleanup = setupModalHandlers(overlay, () => {
|
||||
cleanup();
|
||||
resolve(undefined);
|
||||
}, (e) => {
|
||||
if (e.key === 'Enter' && selectedItem) {
|
||||
cleanup();
|
||||
resolve(selectedItem);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const caption = createCaption(title, theme, () => {
|
||||
cleanup();
|
||||
resolve(undefined);
|
||||
});
|
||||
|
||||
const contents = document.createElement('div');
|
||||
contents.style.cssText = `
|
||||
padding: 12px 0;
|
||||
color: ${theme.contentText};
|
||||
max-height: ${maxHeight}px;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
items.forEach((item) => {
|
||||
const itemDiv = document.createElement('div');
|
||||
itemDiv.textContent = item;
|
||||
itemDiv.style.cssText = `
|
||||
padding: 8px 14px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
itemDiv.addEventListener('click', () => {
|
||||
// Deselect all items
|
||||
contents.querySelectorAll('div').forEach(div => {
|
||||
div.style.background = 'transparent';
|
||||
});
|
||||
// Select this item
|
||||
itemDiv.style.background = theme.itemSelectedBg;
|
||||
selectedItem = item;
|
||||
});
|
||||
|
||||
itemDiv.addEventListener('mouseover', () => {
|
||||
if (itemDiv.style.background !== theme.itemSelectedBg) {
|
||||
itemDiv.style.background = theme.itemHoverBg;
|
||||
}
|
||||
});
|
||||
|
||||
itemDiv.addEventListener('mouseout', () => {
|
||||
if (itemDiv.style.background !== theme.itemSelectedBg) {
|
||||
itemDiv.style.background = 'transparent';
|
||||
}
|
||||
});
|
||||
|
||||
contents.appendChild(itemDiv);
|
||||
});
|
||||
|
||||
const footer = createFooter();
|
||||
|
||||
const cancelBtn = createButton('Cancel', theme, () => {
|
||||
cleanup();
|
||||
resolve(undefined);
|
||||
});
|
||||
|
||||
const okBtn = createButton('OK', theme, () => {
|
||||
cleanup();
|
||||
resolve(selectedItem);
|
||||
});
|
||||
|
||||
footer.appendChild(cancelBtn);
|
||||
footer.appendChild(okBtn);
|
||||
|
||||
dialog.appendChild(caption);
|
||||
dialog.appendChild(contents);
|
||||
dialog.appendChild(footer);
|
||||
overlay.appendChild(dialog);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
setTimeout(() => okBtn.focus(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
interface AlertOptions {
|
||||
darkMode: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export function showAlertModal (options: AlertOptions): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const { title, message, darkMode = false } = options;
|
||||
|
||||
const theme = getTheme(darkMode);
|
||||
const overlay = createOverlay(theme);
|
||||
const dialog = createDialog(theme, 300);
|
||||
|
||||
const cleanup = setupModalHandlers(overlay, () => {
|
||||
cleanup();
|
||||
resolve();
|
||||
}, (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
cleanup();
|
||||
resolve();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const caption = createCaption(title, theme, () => {
|
||||
cleanup();
|
||||
resolve();
|
||||
});
|
||||
|
||||
const contents = document.createElement('div');
|
||||
contents.style.cssText = `
|
||||
padding: 12px 14px;
|
||||
color: ${theme.contentText};
|
||||
line-height: 1.4;
|
||||
`;
|
||||
contents.textContent = message;
|
||||
|
||||
const footer = createFooter();
|
||||
|
||||
const okBtn = createButton('OK', theme, () => {
|
||||
cleanup();
|
||||
resolve();
|
||||
});
|
||||
|
||||
footer.appendChild(okBtn);
|
||||
|
||||
dialog.appendChild(caption);
|
||||
dialog.appendChild(contents);
|
||||
dialog.appendChild(footer);
|
||||
overlay.appendChild(dialog);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
setTimeout(() => okBtn.focus(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
interface MultiListSelectionOptions {
|
||||
darkMode: boolean;
|
||||
title: string;
|
||||
items: string[];
|
||||
selectedItems?: string[];
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
}
|
||||
|
||||
export function showMultiListSelectionModal (options: MultiListSelectionOptions): Promise<string[] | undefined> {
|
||||
return new Promise((resolve) => {
|
||||
const { title, items, selectedItems = [], darkMode = false, maxHeight = 400 } = options;
|
||||
|
||||
const theme = getTheme(darkMode);
|
||||
const overlay = createOverlay(theme);
|
||||
const dialog = createDialog(theme, 300);
|
||||
|
||||
const selectedSet = new Set(selectedItems);
|
||||
|
||||
const cleanup = setupModalHandlers(overlay, () => {
|
||||
cleanup();
|
||||
resolve(undefined);
|
||||
}, (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
cleanup();
|
||||
resolve(Array.from(selectedSet));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const caption = createCaption(title, theme, () => {
|
||||
cleanup();
|
||||
resolve(undefined);
|
||||
});
|
||||
|
||||
const contents = document.createElement('div');
|
||||
contents.style.cssText = `
|
||||
padding: 12px 0;
|
||||
color: ${theme.contentText};
|
||||
max-height: ${maxHeight}px;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
items.forEach((item) => {
|
||||
const itemDiv = document.createElement('div');
|
||||
itemDiv.style.cssText = `
|
||||
padding: 8px 14px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
// Create checkbox
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.checked = selectedSet.has(item);
|
||||
checkbox.style.cssText = `
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.textContent = item;
|
||||
label.style.cssText = `
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
itemDiv.appendChild(checkbox);
|
||||
itemDiv.appendChild(label);
|
||||
|
||||
const toggleSelection = () => {
|
||||
if (selectedSet.has(item)) {
|
||||
selectedSet.delete(item);
|
||||
checkbox.checked = false;
|
||||
} else {
|
||||
selectedSet.add(item);
|
||||
checkbox.checked = true;
|
||||
}
|
||||
};
|
||||
|
||||
itemDiv.addEventListener('click', (e) => {
|
||||
// Don't toggle if clicking directly on checkbox (it handles itself)
|
||||
if (e.target !== checkbox) {
|
||||
toggleSelection();
|
||||
}
|
||||
});
|
||||
|
||||
checkbox.addEventListener('change', () => {
|
||||
if (checkbox.checked) {
|
||||
selectedSet.add(item);
|
||||
} else {
|
||||
selectedSet.delete(item);
|
||||
}
|
||||
});
|
||||
|
||||
itemDiv.addEventListener('mouseover', () => {
|
||||
itemDiv.style.background = theme.itemHoverBg;
|
||||
});
|
||||
|
||||
itemDiv.addEventListener('mouseout', () => {
|
||||
itemDiv.style.background = 'transparent';
|
||||
});
|
||||
|
||||
contents.appendChild(itemDiv);
|
||||
});
|
||||
|
||||
const footer = createFooter();
|
||||
|
||||
const cancelBtn = createButton('Cancel', theme, () => {
|
||||
cleanup();
|
||||
resolve(undefined);
|
||||
});
|
||||
|
||||
const okBtn = createButton('OK', theme, () => {
|
||||
cleanup();
|
||||
resolve(Array.from(selectedSet));
|
||||
});
|
||||
|
||||
footer.appendChild(cancelBtn);
|
||||
footer.appendChild(okBtn);
|
||||
|
||||
dialog.appendChild(caption);
|
||||
dialog.appendChild(contents);
|
||||
dialog.appendChild(footer);
|
||||
overlay.appendChild(dialog);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
setTimeout(() => okBtn.focus(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
@ -579,7 +579,66 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
||||
}
|
||||
|
||||
public async selectAnimation () {
|
||||
console.log('[Spine] Select animation dialog called');
|
||||
if (!this.skeleton) {
|
||||
await spine.showAlertModal({
|
||||
darkMode: false,
|
||||
title: 'Error',
|
||||
message: 'Skeleton not loaded. Please ensure atlas and skeleton files are set.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const animations = this.skeleton.data.animations.map(anim => anim.name);
|
||||
if (animations.length === 0) {
|
||||
await spine.showAlertModal({
|
||||
darkMode: false,
|
||||
title: 'No Animations',
|
||||
message: 'No animations found in the skeleton.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedAnimation = await spine.showListSelectionModal({
|
||||
darkMode: false,
|
||||
title: 'Select Animation',
|
||||
items: animations,
|
||||
});
|
||||
|
||||
if (selectedAnimation) {
|
||||
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_ANIMATION, selectedAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
public async selectSkin () {
|
||||
if (!this.skeleton) {
|
||||
await spine.showAlertModal({
|
||||
darkMode: false,
|
||||
title: 'Error',
|
||||
message: 'Skeleton not loaded. Please ensure atlas and skeleton files are set.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const skins = this.skeleton.data.skins.map(skin => skin.name).filter(s => s !== "default");
|
||||
if (skins.length === 0) {
|
||||
await spine.showAlertModal({
|
||||
darkMode: false,
|
||||
title: 'No Skins',
|
||||
message: 'No skins found in the skeleton.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedSkins = await spine.showMultiListSelectionModal({
|
||||
darkMode: false,
|
||||
title: 'Select Skins',
|
||||
items: skins,
|
||||
selectedItems: this.skins,
|
||||
});
|
||||
|
||||
if (selectedSkins !== undefined) {
|
||||
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_SKIN, selectedSkins.join(","));
|
||||
}
|
||||
}
|
||||
|
||||
private lang (stringKey: string, interpolate: (string | number)[] = []): string {
|
||||
|
||||
@ -22,6 +22,11 @@
|
||||
"name": "Loader scale",
|
||||
"desc": "Loader scale"
|
||||
},
|
||||
"select-skin": {
|
||||
"name": "Select skin",
|
||||
"desc": "Open a dialog to select skins from the skeleton",
|
||||
"link-text": "Select"
|
||||
},
|
||||
"select-animation": {
|
||||
"name": "Select animation",
|
||||
"desc": "Open a dialog to select an animation from the skeleton",
|
||||
|
||||
@ -22,6 +22,11 @@
|
||||
"name": "加载比例",
|
||||
"desc": "加载比例"
|
||||
},
|
||||
"select-skin": {
|
||||
"name": "选择皮肤",
|
||||
"desc": "打开对话框从骨架中选择皮肤",
|
||||
"link-text": "选择"
|
||||
},
|
||||
"select-animation": {
|
||||
"name": "选择动画",
|
||||
"desc": "打开对话框从骨架中选择动画",
|
||||
|
||||
@ -78,6 +78,13 @@ const PLUGIN_CLASS = class SpineC3Plugin extends SDK.IPluginBase {
|
||||
new SDK.PluginProperty("projectfile", SpineC3Plugin.PROP_ATLAS, { initialValue: "", filter: ".atlas" }),
|
||||
new SDK.PluginProperty("projectfile", SpineC3Plugin.PROP_SKELETON, { initialValue: "", filter: ".json,.skel" }),
|
||||
new SDK.PluginProperty("float", SpineC3Plugin.PROP_LOADER_SCALE, 1),
|
||||
new SDK.PluginProperty("link", "select-skin", {
|
||||
linkCallback: async (instance) => {
|
||||
const sdkInst = instance as SDKEditorInstanceClass;
|
||||
await sdkInst.selectSkin();
|
||||
},
|
||||
callbackType: "for-each-instance"
|
||||
}),
|
||||
new SDK.PluginProperty("text", SpineC3Plugin.PROP_SKIN, ""),
|
||||
new SDK.PluginProperty("link", "select-animation", {
|
||||
linkCallback: async (instance) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user