mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 14:24:53 +08:00
Add alert modal when manually inserting animations or skins.
This commit is contained in:
parent
5e33132153
commit
7f092113d8
@ -27,7 +27,6 @@
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
// Common theme type
|
||||
interface ModalTheme {
|
||||
overlayBg: string;
|
||||
captionBg: string;
|
||||
@ -43,38 +42,36 @@ interface ModalTheme {
|
||||
itemSelectedBg: string;
|
||||
}
|
||||
|
||||
// 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
|
||||
contentBg: 'rgb(87, 87, 87)', // gray11
|
||||
contentText: 'rgb(214, 214, 214)', // gray27
|
||||
buttonBg: 'rgb(71, 71, 71)', // gray9
|
||||
buttonText: 'rgb(214, 214, 214)', // gray27
|
||||
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
|
||||
captionBg: 'rgb(71, 71, 71)',
|
||||
captionText: 'rgb(214, 214, 214)',
|
||||
contentBg: 'rgb(87, 87, 87)',
|
||||
contentText: 'rgb(214, 214, 214)',
|
||||
buttonBg: 'rgb(71, 71, 71)',
|
||||
buttonText: 'rgb(214, 214, 214)',
|
||||
buttonBorder: 'rgb(56, 56, 56)',
|
||||
buttonHoverBg: 'rgb(79, 79, 79)',
|
||||
closeColor: 'rgb(168, 168, 168)',
|
||||
itemHoverBg: 'rgb(79, 79, 79)',
|
||||
itemSelectedBg: 'rgb(56, 56, 56)',
|
||||
} : {
|
||||
overlayBg: 'rgba(0, 0, 0, 0.3)',
|
||||
captionBg: 'rgb(247, 247, 247)', // gray31
|
||||
captionText: 'rgb(94, 94, 94)', // gray12
|
||||
contentBg: 'rgb(232, 232, 232)', // gray29
|
||||
contentText: 'rgb(94, 94, 94)', // gray12
|
||||
buttonBg: 'rgb(222, 222, 222)', // gray28
|
||||
buttonText: 'rgb(94, 94, 94)', // gray12
|
||||
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
|
||||
captionBg: 'rgb(247, 247, 247)',
|
||||
captionText: 'rgb(94, 94, 94)',
|
||||
contentBg: 'rgb(232, 232, 232)',
|
||||
contentText: 'rgb(94, 94, 94)',
|
||||
buttonBg: 'rgb(222, 222, 222)',
|
||||
buttonText: 'rgb(94, 94, 94)',
|
||||
buttonBorder: 'rgb(199, 199, 199)',
|
||||
buttonHoverBg: 'rgb(214, 214, 214)',
|
||||
closeColor: 'rgb(94, 94, 94)',
|
||||
itemHoverBg: 'rgb(214, 214, 214)',
|
||||
itemSelectedBg: 'rgb(199, 199, 199)',
|
||||
};
|
||||
}
|
||||
|
||||
// Helper: Create overlay
|
||||
function createOverlay (theme: ModalTheme): HTMLDivElement {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.style.cssText = `
|
||||
@ -94,7 +91,6 @@ function createOverlay (theme: ModalTheme): HTMLDivElement {
|
||||
return overlay;
|
||||
}
|
||||
|
||||
// Helper: Create dialog
|
||||
function createDialog (theme: ModalTheme, minWidth: number): HTMLDivElement {
|
||||
const dialog = document.createElement('div');
|
||||
dialog.style.cssText = `
|
||||
@ -109,7 +105,6 @@ function createDialog (theme: ModalTheme, minWidth: number): HTMLDivElement {
|
||||
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 = `
|
||||
@ -149,7 +144,6 @@ function createCaption (title: string, theme: ModalTheme, onClose: () => void):
|
||||
return caption;
|
||||
}
|
||||
|
||||
// Helper: Create footer
|
||||
function createFooter (): HTMLDivElement {
|
||||
const footer = document.createElement('div');
|
||||
footer.style.cssText = `
|
||||
@ -161,7 +155,6 @@ function createFooter (): HTMLDivElement {
|
||||
return footer;
|
||||
}
|
||||
|
||||
// Helper: Create button
|
||||
function createButton (text: string, theme: ModalTheme, onClick: () => void): HTMLButtonElement {
|
||||
const btn = document.createElement('button');
|
||||
btn.textContent = text;
|
||||
@ -181,7 +174,6 @@ function createButton (text: string, theme: ModalTheme, onClick: () => void): HT
|
||||
return btn;
|
||||
}
|
||||
|
||||
// Helper: Setup modal event handlers
|
||||
function setupModalHandlers (overlay: HTMLDivElement, onCancel: () => void, extraKeyHandler?: (e: KeyboardEvent) => boolean) {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
@ -193,7 +185,9 @@ function setupModalHandlers (overlay: HTMLDivElement, onCancel: () => void, extr
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
}, 0);
|
||||
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) {
|
||||
@ -207,7 +201,6 @@ function setupModalHandlers (overlay: HTMLDivElement, onCancel: () => void, extr
|
||||
};
|
||||
}
|
||||
|
||||
// Original interfaces
|
||||
interface ModalButton<T> {
|
||||
text: string;
|
||||
color?: string;
|
||||
@ -324,11 +317,9 @@ export function showListSelectionModal (options: ListSelectionOptions): Promise<
|
||||
`;
|
||||
|
||||
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;
|
||||
});
|
||||
@ -486,7 +477,6 @@ export function showMultiListSelectionModal (options: MultiListSelectionOptions)
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
// Create checkbox
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.checked = selectedSet.has(item);
|
||||
@ -515,7 +505,6 @@ export function showMultiListSelectionModal (options: MultiListSelectionOptions)
|
||||
};
|
||||
|
||||
itemDiv.addEventListener('click', (e) => {
|
||||
// Don't toggle if clicking directly on checkbox (it handles itself)
|
||||
if (e.target !== checkbox) {
|
||||
toggleSelection();
|
||||
}
|
||||
|
||||
@ -126,8 +126,9 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
||||
if (id === PLUGIN_CLASS.PROP_SKIN) {
|
||||
this.skins = [];
|
||||
|
||||
const validatedString = this.validateSkinString();
|
||||
if (validatedString) {
|
||||
const skinString = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKIN) as string;
|
||||
const validatedString = await this.validateSkinString(skinString);
|
||||
if (validatedString !== undefined && validatedString !== skinString) {
|
||||
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_SKIN, validatedString);
|
||||
return;
|
||||
}
|
||||
@ -138,6 +139,14 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
||||
}
|
||||
|
||||
if (id === PLUGIN_CLASS.PROP_ANIMATION) {
|
||||
|
||||
const animationString = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_ANIMATION) as string;
|
||||
const validatedString = await this.validateAnimationString(animationString);
|
||||
if (validatedString !== undefined && validatedString !== this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_ANIMATION)) {
|
||||
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_ANIMATION, validatedString);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setAnimation();
|
||||
this.layoutView?.Refresh();
|
||||
return;
|
||||
@ -259,7 +268,6 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
||||
}
|
||||
}
|
||||
return zoom === -1 ? 1 : zoom;
|
||||
|
||||
}
|
||||
|
||||
private setAnimation () {
|
||||
@ -267,14 +275,47 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
||||
this.animation = propValue === "" ? undefined : propValue;
|
||||
}
|
||||
|
||||
private validateSkinString () {
|
||||
const skins = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKIN) as string;
|
||||
if (skins === "") return;
|
||||
const split = skins.split(",");
|
||||
private async validateSkinString (skins: string) {
|
||||
if (skins === "" || !this.skeleton) return;
|
||||
|
||||
if (!split.includes("")) return;
|
||||
const split = skins.split(",").filter(s => s !== "");
|
||||
if (split.length === 0) return;
|
||||
|
||||
return split.filter(s => s !== "").join(",");
|
||||
const availableSkins = this.skeleton.data.skins.map(skin => skin.name).filter(s => s !== "default");
|
||||
|
||||
const invalidSkins = split.filter(skinName => !availableSkins.includes(skinName));
|
||||
|
||||
if (invalidSkins.length > 0) {
|
||||
await spine.showAlertModal({
|
||||
darkMode: false,
|
||||
title: this.lang('invalid-skins.title'),
|
||||
message: this.lang('invalid-skins.message', [invalidSkins.join(", ")])
|
||||
});
|
||||
|
||||
const validSkins = split.filter(skinName => availableSkins.includes(skinName));
|
||||
console.log(validSkins);
|
||||
return validSkins.join(",");
|
||||
}
|
||||
|
||||
return split.join(",");
|
||||
}
|
||||
|
||||
private async validateAnimationString (animation: string) {
|
||||
if (animation === "" || !this.skeleton) return;
|
||||
|
||||
const availableAnimations = this.skeleton.data.animations.map(anim => anim.name);
|
||||
|
||||
if (!availableAnimations.includes(animation)) {
|
||||
await spine.showAlertModal({
|
||||
darkMode: false,
|
||||
title: this.lang('invalid-animation.title'),
|
||||
message: this.lang('invalid-animation.message', [animation])
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
return animation;
|
||||
}
|
||||
|
||||
private setSkin () {
|
||||
@ -483,10 +524,10 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
||||
|
||||
const errorTextC3 = iRenderer.CreateRendererText()
|
||||
this.errorTextC3 = errorTextC3;
|
||||
this.errorTextC3.SetFontSize(12);
|
||||
this.errorTextC3.SetColorRgb(1, 0, 0);
|
||||
this.errorTextC3.SetTextureUpdateCallback(() => iLayoutView.Refresh());
|
||||
return this.errorTextC3;
|
||||
errorTextC3.SetFontSize(12);
|
||||
errorTextC3.SetColorRgb(1, 0, 0);
|
||||
errorTextC3.SetTextureUpdateCallback(() => iLayoutView.Refresh());
|
||||
return errorTextC3;
|
||||
}
|
||||
|
||||
private update (delta: number) {
|
||||
@ -566,15 +607,15 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
||||
const action = await spine.showModal({
|
||||
darkMode: false,
|
||||
maxWidth: 500,
|
||||
title: this.lang(`showModal.${type}.title`),
|
||||
text: this.lang(`showModal.${type}.text`, [collisionSpriteName]),
|
||||
title: this.lang(`${type}.title`),
|
||||
text: this.lang(`${type}.message`, [collisionSpriteName]),
|
||||
buttons: [
|
||||
{
|
||||
text: this.lang(`showModal.${type}.buttons.${0}`),
|
||||
text: this.lang(`${type}.buttons.${0}`),
|
||||
value: 'disable'
|
||||
},
|
||||
{
|
||||
text: this.lang(`showModal.${type}.buttons.${1}`, [collisionSpriteName]),
|
||||
text: this.lang(`${type}.buttons.${1}`, [collisionSpriteName]),
|
||||
color: '#d9534f',
|
||||
value: 'delete'
|
||||
}
|
||||
@ -597,8 +638,8 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
||||
if (!this.skeleton) {
|
||||
await spine.showAlertModal({
|
||||
darkMode: false,
|
||||
title: 'Error',
|
||||
message: 'Skeleton not loaded. Please ensure atlas and skeleton files are set.',
|
||||
title: this.lang('skeleton-not-loaded.title'),
|
||||
message: this.lang('skeleton-not-loaded.message'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -607,15 +648,15 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
||||
if (animations.length === 0) {
|
||||
await spine.showAlertModal({
|
||||
darkMode: false,
|
||||
title: 'No Animations',
|
||||
message: 'No animations found in the skeleton.',
|
||||
title: this.lang('no-animations.title'),
|
||||
message: this.lang('no-animations.message'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedAnimation = await spine.showListSelectionModal({
|
||||
darkMode: false,
|
||||
title: 'Select Animation',
|
||||
title: this.lang('select-animation.title'),
|
||||
items: animations,
|
||||
});
|
||||
|
||||
@ -628,8 +669,8 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
||||
if (!this.skeleton) {
|
||||
await spine.showAlertModal({
|
||||
darkMode: false,
|
||||
title: 'Error',
|
||||
message: 'Skeleton not loaded. Please ensure atlas and skeleton files are set.',
|
||||
title: this.lang('skeleton-not-loaded.title'),
|
||||
message: this.lang('skeleton-not-loaded.message'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -638,15 +679,15 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
||||
if (skins.length === 0) {
|
||||
await spine.showAlertModal({
|
||||
darkMode: false,
|
||||
title: 'No Skins',
|
||||
message: 'No skins found in the skeleton.',
|
||||
title: this.lang('no-skins.title'),
|
||||
message: this.lang('no-skins.message'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedSkins = await spine.showMultiListSelectionModal({
|
||||
darkMode: false,
|
||||
title: 'Select Skins',
|
||||
title: this.lang('select-skins.title'),
|
||||
items: skins,
|
||||
selectedItems: this.skins,
|
||||
});
|
||||
|
||||
@ -689,15 +689,39 @@
|
||||
}
|
||||
},
|
||||
"custom_ui": {
|
||||
"showModal": {
|
||||
"spine-enable-collision": {
|
||||
"title": "Delete Sprite Object Type",
|
||||
"text": "This is the last instance of {0}. You can delete the {0} or just disable the Sprite collision body. Deleting the Sprite collision body will remove all related ACEs in the event sheet, if there's any. How do you want to proceed?",
|
||||
"message": "This is the last instance of {0}. You can delete the {0} or just disable the Sprite collision body. Deleting the Sprite collision body will remove all related ACEs in the event sheet, if there's any. How do you want to proceed?",
|
||||
"buttons": {
|
||||
"0": "Just disable",
|
||||
"1": "Disable and delete {0}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"skeleton-not-loaded": {
|
||||
"title": "Error",
|
||||
"message": "Skeleton not loaded. Please ensure atlas and skeleton files are set."
|
||||
},
|
||||
"no-animations": {
|
||||
"title": "No Animations",
|
||||
"message": "No animations found in the skeleton."
|
||||
},
|
||||
"no-skins": {
|
||||
"title": "No Skins",
|
||||
"message": "No skins found in the skeleton."
|
||||
},
|
||||
"select-animation": {
|
||||
"title": "Select Animation"
|
||||
},
|
||||
"select-skins": {
|
||||
"title": "Select Skins"
|
||||
},
|
||||
"invalid-skins": {
|
||||
"title": "Invalid Skins",
|
||||
"message": "The following skins do not exist in the skeleton: {0}.\n\nThey have been removed. Please use the \"Select skin\" button to choose from existing skins."
|
||||
},
|
||||
"invalid-animation": {
|
||||
"title": "Invalid Animation",
|
||||
"message": "The animation \"{0}\" does not exist in the skeleton.\n\nIt has been removed. Please use the \"Select animation\" button to choose from existing animations."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -689,15 +689,39 @@
|
||||
}
|
||||
},
|
||||
"custom_ui": {
|
||||
"showModal": {
|
||||
"spine-enable-collision": {
|
||||
"title": "删除精灵对象类型",
|
||||
"text": "这是{0}的最后一个实例。您可以删除{0}或仅禁用精灵碰撞体。删除精灵碰撞体将移除事件表中所有相关的ACE(如果有)。您想如何继续?",
|
||||
"message": "这是{0}的最后一个实例。您可以删除{0}或仅禁用精灵碰撞体。删除精灵碰撞体将移除事件表中所有相关的ACE(如果有)。您想如何继续?",
|
||||
"buttons": {
|
||||
"0": "仅禁用",
|
||||
"1": "禁用并删除{0}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"skeleton-not-loaded": {
|
||||
"title": "错误",
|
||||
"message": "骨架未加载。请确保已设置图集和骨架文件。"
|
||||
},
|
||||
"no-animations": {
|
||||
"title": "无动画",
|
||||
"message": "在骨架中未找到动画。"
|
||||
},
|
||||
"no-skins": {
|
||||
"title": "无皮肤",
|
||||
"message": "在骨架中未找到皮肤。"
|
||||
},
|
||||
"select-animation": {
|
||||
"title": "选择动画"
|
||||
},
|
||||
"select-skins": {
|
||||
"title": "选择皮肤"
|
||||
},
|
||||
"invalid-skins": {
|
||||
"title": "无效皮肤",
|
||||
"message": "以下皮肤在骨架中不存在:{0}。\n\n它们已被删除。请使用\"选择皮肤\"按钮从现有皮肤中选择。"
|
||||
},
|
||||
"invalid-animation": {
|
||||
"title": "无效动画",
|
||||
"message": "动画\"{0}\"在骨架中不存在。\n\n它已被删除。请使用\"选择动画\"按钮从现有动画中选择。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user