fix: 输入框相关问题

This commit is contained in:
unanmed 2025-10-11 16:31:17 +08:00
parent 0ac7165d60
commit f9ab9fb73d
6 changed files with 91 additions and 48 deletions

View File

@ -10,10 +10,11 @@ import {
Transform Transform
} from '@motajs/render-core'; } from '@motajs/render-core';
import { Font } from '@motajs/render-style'; import { Font } from '@motajs/render-style';
import { transitionedColor } from '../use'; import { transitionedColor, useKey } from '../use';
import { linear } from 'mutate-animate'; import { linear } from 'mutate-animate';
import { Background, Selection } from './misc'; import { Background, Selection } from './misc';
import { GameUI, IUIMountable, SetupComponentOptions } from '@motajs/system-ui'; import { GameUI, IUIMountable, SetupComponentOptions } from '@motajs/system-ui';
import { KeyCode } from '@motajs/client-base';
export interface InputProps extends DefaultProps, Partial<TextContentProps> { export interface InputProps extends DefaultProps, Partial<TextContentProps> {
/** 输入框的提示内容 */ /** 输入框的提示内容 */
@ -49,7 +50,16 @@ export type InputEmits = {
}; };
const inputProps = { const inputProps = {
props: ['placeholder', 'value', 'multiline'], props: [
'loc',
'placeholder',
'value',
'multiline',
'border',
'circle',
'borderWidth',
'pad'
],
emits: ['change', 'input', 'update:value'] emits: ['change', 'input', 'update:value']
} satisfies SetupComponentOptions<InputProps, InputEmits, keyof InputEmits>; } satisfies SetupComponentOptions<InputProps, InputEmits, keyof InputEmits>;
@ -92,6 +102,10 @@ export const Input = defineComponent<InputProps, InputEmits, keyof InputEmits>(
width.value - padding.value * 2, width.value - padding.value * 2,
height.value - padding.value * 2 height.value - padding.value * 2
]); ]);
const rectLoc = computed<ElementLocator>(() => {
const b = props.borderWidth ?? 1;
return [b, b, width.value - b * 2, height.value - b * 2];
});
const borderColor = transitionedColor( const borderColor = transitionedColor(
props.border ?? '#ddd', props.border ?? '#ddd',
@ -112,6 +126,9 @@ export const Input = defineComponent<InputProps, InputEmits, keyof InputEmits>(
} }
}); });
ele.addEventListener('blur', () => { ele.addEventListener('blur', () => {
if (ele) {
updateInput(ele.value);
}
ele?.remove(); ele?.remove();
}); });
}; };
@ -143,42 +160,52 @@ export const Input = defineComponent<InputProps, InputEmits, keyof InputEmits>(
if (!ele) createInput(props.multiline ?? false); if (!ele) createInput(props.multiline ?? false);
if (!ele) return; if (!ele) return;
// 计算当前绝对位置 // 计算当前绝对位置
const renderer = MotaRenderer.get('render-main');
const canvas = renderer?.getCanvas();
if (!canvas) return;
const chain: RenderItem[] = []; const chain: RenderItem[] = [];
let now: RenderItem | undefined = root.value; let now: RenderItem | undefined = root.value;
let renderer: MotaRenderer | undefined;
if (!now) return; if (!now) return;
while (now) { while (now) {
chain.unshift(now); chain.unshift(now);
if (now?.isRoot) {
renderer = now as MotaRenderer;
}
now = now.parent; now = now.parent;
} }
// 应用内边距偏移 const canvas = renderer?.getCanvas();
const { clientLeft, clientTop } = canvas; if (!canvas) return;
const trans = new Transform();
trans.translate(clientLeft, clientTop); const w = width.value;
trans.scale(core.domStyle.scale); const h = height.value;
const border = props.borderWidth ?? 1;
const inputWidth = w - border * 2;
const inputHeight = h - border * 2;
// 应用根画布偏移
const box = canvas.getBoundingClientRect();
let trans = new Transform();
trans.translate(box.x, box.y);
trans.scale(renderer?.getScale() ?? 1);
for (const item of chain) { for (const item of chain) {
const { anchorX, anchorY, width, height } = item; const { anchorX, anchorY, width, height } = item;
trans.translate(-anchorX * width, -anchorY * height); trans.translate(-anchorX * width, -anchorY * height);
trans.multiply(item.transform); trans = trans.multiply(item.transform);
} }
trans.translate(padding.value, padding.value); trans.translate(border, border);
// 构建CSS transform的matrix字符串 // 构建CSS transform的matrix字符串
const [a, b, , c, d, , e, f] = trans.mat; const [a, b, , c, d, , e, f] = trans.mat;
const str = `matrix(${a},${b},${c},${d},${e},${f})`; const str = `matrix(${a},${b},${c},${d},${e},${f})`;
const w = width.value * core.domStyle.scale;
const h = height.value * core.domStyle.scale;
const font = props.font ?? Font.defaults(); const font = props.font ?? Font.defaults();
ele.style.transform = str; ele.style.transform = str;
ele.style.width = `${w - padding.value * 2}px`; ele.style.width = `${inputWidth}px`;
ele.style.height = `${h - padding.value * 2}px`; ele.style.height = `${inputHeight}px`;
ele.style.font = font.string(); ele.style.font = font.string();
ele.style.color = String(props.fillStyle ?? 'white'); ele.style.color = String(props.fillStyle ?? 'white');
ele.style.zIndex = '100';
document.body.appendChild(ele); document.body.appendChild(ele);
ele.focus(); ele.focus();
}; };
@ -191,6 +218,14 @@ export const Input = defineComponent<InputProps, InputEmits, keyof InputEmits>(
borderColor.set(props.border ?? '#ddd'); borderColor.set(props.border ?? '#ddd');
}; };
const [key] = useKey();
key.realize('confirm', (_, code) => {
if (code === KeyCode.Enter) {
// 特判回车键
ele?.blur();
}
});
watch( watch(
() => props.value, () => props.value,
newValue => { newValue => {
@ -214,6 +249,7 @@ export const Input = defineComponent<InputProps, InputEmits, keyof InputEmits>(
return () => ( return () => (
<container <container
loc={props.loc}
ref={root} ref={root}
cursor="text" cursor="text"
onClick={click} onClick={click}
@ -221,11 +257,14 @@ export const Input = defineComponent<InputProps, InputEmits, keyof InputEmits>(
onLeave={leave} onLeave={leave}
> >
<g-rectr <g-rectr
loc={[0, 0, width.value, height.value]} loc={rectLoc.value}
circle={props.circle} circle={props.circle}
lineWidth={props.borderWidth} lineWidth={props.borderWidth ?? 1}
fill
stroke
fillStyle="#111"
strokeStyle={borderColor.ref.value} strokeStyle={borderColor.ref.value}
zIndex={10} zIndex={0}
/> />
<TextContent <TextContent
{...attrs} {...attrs}
@ -233,8 +272,9 @@ export const Input = defineComponent<InputProps, InputEmits, keyof InputEmits>(
loc={textLoc.value} loc={textLoc.value}
width={width.value - padding.value * 2} width={width.value - padding.value * 2}
text={showText.value} text={showText.value}
fillStyle="white"
alpha={value.value.length === 0 ? 0.6 : 1} alpha={value.value.length === 0 ? 0.6 : 1}
zIndex={0} zIndex={10}
/> />
</container> </container>
); );
@ -353,12 +393,12 @@ export const InputBox = defineComponent<
const noText = computed(() => props.noText ?? '取消'); const noText = computed(() => props.noText ?? '取消');
const text = computed(() => props.text ?? '请输入内容:'); const text = computed(() => props.text ?? '请输入内容:');
const padding = computed(() => props.pad ?? 8); const padding = computed(() => props.pad ?? 8);
const inputHeight = computed(() => props.inputHeight ?? 16); const inputHeight = computed(() => props.inputHeight ?? 24);
const inputLoc = computed<ElementLocator>(() => [ const inputLoc = computed<ElementLocator>(() => [
padding.value, padding.value,
padding.value * 2 + contentHeight.value, padding.value * 2 + contentHeight.value,
props.width - padding.value * 2, props.width - padding.value * 2,
inputHeight.value - padding.value * 2 inputHeight.value
]); ]);
const yesLoc = computed<ElementLocator>(() => { const yesLoc = computed<ElementLocator>(() => {
const y = height.value - padding.value; const y = height.value - padding.value;
@ -379,10 +419,17 @@ export const InputBox = defineComponent<
return [x, y + 4, width + 8, height + 8, 0.5, 1]; return [x, y + 4, width + 8, height + 8, 0.5, 1];
} }
}); });
const boxLoc = computed<ElementLocator>(() => {
const [x = 0, y = 0, , , ax = 0, ay = 0] = props.loc;
return [x, y, props.width, height.value, ax, ay];
});
const updateHeight = (h: number) => { const updateHeight = (h: number) => {
contentHeight.value = h; contentHeight.value = h;
height.value = h + inputHeight.value + padding.value * 4; const [, yh] = yesSize.value;
const [, nh] = noSize.value;
const buttonHeight = Math.max(yh, nh);
height.value = h + inputHeight.value + padding.value * 4 + buttonHeight;
}; };
const change = (value: string) => { const change = (value: string) => {
@ -411,11 +458,11 @@ export const InputBox = defineComponent<
}; };
return () => ( return () => (
<container> <container loc={boxLoc.value}>
<Background <Background
loc={[0, 0, props.width, height.value]} loc={[0, 0, props.width, height.value]}
winskin={props.winskin} winskin={props.winskin}
color={props.color} color={props.color ?? '#333'}
border={props.border} border={props.border}
zIndex={0} zIndex={0}
/> />
@ -426,12 +473,14 @@ export const InputBox = defineComponent<
width={props.width - padding.value * 2} width={props.width - padding.value * 2}
zIndex={5} zIndex={5}
onUpdateHeight={updateHeight} onUpdateHeight={updateHeight}
autoHeight
/> />
<Input <Input
{...(props.input ?? {})} {...(props.input ?? {})}
loc={inputLoc.value} loc={inputLoc.value}
v-model={value.value} v-model={value.value}
zIndex={10} zIndex={10}
circle={[4]}
onChange={change} onChange={change}
onInput={input} onInput={input}
/> />

View File

@ -364,7 +364,7 @@ export const SyncSave = defineComponent<MainSettingsProps>(props => {
[SyncSaveChoice.ToServer, '同步存档至服务器'], [SyncSaveChoice.ToServer, '同步存档至服务器'],
[SyncSaveChoice.FromServer, '从服务器加载存档'], [SyncSaveChoice.FromServer, '从服务器加载存档'],
[SyncSaveChoice.ToLocal, '存档至本地文件'], [SyncSaveChoice.ToLocal, '存档至本地文件'],
[SyncSaveChoice.FromLocal, '本地文件读档'], [SyncSaveChoice.FromLocal, '本地文件读档'],
[SyncSaveChoice.ClearLocal, '清空本地存档'], [SyncSaveChoice.ClearLocal, '清空本地存档'],
[SyncSaveChoice.Back, '返回上一级'] [SyncSaveChoice.Back, '返回上一级']
]; ];

View File

@ -2,6 +2,7 @@ import { compressToBase64, decompressFromBase64 } from 'lz-string';
import { getConfirm, waitbox } from '../components'; import { getConfirm, waitbox } from '../components';
import { IUIMountable } from '@motajs/system-ui'; import { IUIMountable } from '@motajs/system-ui';
import { SyncSaveFromServerResponse } from '@motajs/client-base'; import { SyncSaveFromServerResponse } from '@motajs/client-base';
import { CENTER_LOC, POP_BOX_WIDTH } from '../shared';
export interface SaveData { export interface SaveData {
name: string; name: string;
@ -128,15 +129,15 @@ export async function syncFromServer(
return void getConfirm( return void getConfirm(
controller, controller,
'不合法的存档编号+密码!请检查格式!', '不合法的存档编号+密码!请检查格式!',
[240, 240, void 0, void 0, 0.5, 0.5], CENTER_LOC,
240 POP_BOX_WIDTH
); );
} }
const [id, password] = parseIdPassword(identifier); const [id, password] = parseIdPassword(identifier);
const result = await waitbox( const result = await waitbox(
controller, controller,
[240, 240, void 0, void 0, 0.5, 0.5], CENTER_LOC,
240, POP_BOX_WIDTH,
syncLoad(id, password) syncLoad(id, password)
); );
if (typeof result === 'number') { if (typeof result === 'number') {
@ -150,16 +151,16 @@ export async function syncFromServer(
return void getConfirm( return void getConfirm(
controller, controller,
map[result], map[result],
[240, 240, void 0, void 0, 0.5, 0.5], CENTER_LOC,
240 POP_BOX_WIDTH
); );
} }
if (result instanceof Array) { if (result instanceof Array) {
const confirm = await getConfirm( const confirm = await getConfirm(
controller, controller,
'所有本地存档都将被覆盖,确认?', '所有本地存档都将被覆盖,确认?',
[240, 240, void 0, void 0, 0.5, 0.5], CENTER_LOC,
240, POP_BOX_WIDTH,
{ {
defaultYes: true defaultYes: true
} }
@ -176,8 +177,8 @@ export async function syncFromServer(
return void getConfirm( return void getConfirm(
controller, controller,
'同步成功!\n你的本地所有存档均已被覆盖。', '同步成功!\n你的本地所有存档均已被覆盖。',
[240, 240, void 0, void 0, 0.5, 0.5], CENTER_LOC,
240 POP_BOX_WIDTH
); );
} }
} else { } else {
@ -188,8 +189,8 @@ export async function syncFromServer(
return void getConfirm( return void getConfirm(
controller, controller,
`同步成功!\n单存档已覆盖至存档 ${idx}`, `同步成功!\n单存档已覆盖至存档 ${idx}`,
[240, 240, void 0, void 0, 0.5, 0.5], CENTER_LOC,
240 POP_BOX_WIDTH
); );
} }
} }

View File

@ -133,7 +133,6 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
// 画布监听 // 画布监听
const canvas = this.target.canvas; const canvas = this.target.canvas;
canvas.addEventListener('mousedown', ev => { canvas.addEventListener('mousedown', ev => {
ev.preventDefault();
const mouse = this.getMouseType(ev); const mouse = this.getMouseType(ev);
this.lastMouse = mouse; this.lastMouse = mouse;
this.captureEvent( this.captureEvent(
@ -142,13 +141,11 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
); );
}); });
canvas.addEventListener('mouseup', ev => { canvas.addEventListener('mouseup', ev => {
ev.preventDefault();
const event = this.createMouseAction(ev, ActionType.Up); const event = this.createMouseAction(ev, ActionType.Up);
this.captureEvent(ActionType.Up, event); this.captureEvent(ActionType.Up, event);
this.captureEvent(ActionType.Click, event); this.captureEvent(ActionType.Click, event);
}); });
canvas.addEventListener('mousemove', ev => { canvas.addEventListener('mousemove', ev => {
ev.preventDefault();
const event = this.createMouseAction( const event = this.createMouseAction(
ev, ev,
ActionType.Move, ActionType.Move,
@ -171,7 +168,6 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
); );
}); });
canvas.addEventListener('mouseleave', ev => { canvas.addEventListener('mouseleave', ev => {
ev.preventDefault();
const id = this.getMouseIdentifier( const id = this.getMouseIdentifier(
ActionType.Leave, ActionType.Leave,
this.getMouseType(ev) this.getMouseType(ev)
@ -183,13 +179,11 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
this.beforeHovered.clear(); this.beforeHovered.clear();
}); });
document.addEventListener('touchstart', ev => { document.addEventListener('touchstart', ev => {
ev.preventDefault();
this.createTouchAction(ev, ActionType.Down).forEach(v => { this.createTouchAction(ev, ActionType.Down).forEach(v => {
this.captureEvent(ActionType.Down, v); this.captureEvent(ActionType.Down, v);
}); });
}); });
document.addEventListener('touchend', ev => { document.addEventListener('touchend', ev => {
ev.preventDefault();
this.createTouchAction(ev, ActionType.Up).forEach(v => { this.createTouchAction(ev, ActionType.Up).forEach(v => {
this.captureEvent(ActionType.Up, v); this.captureEvent(ActionType.Up, v);
this.captureEvent(ActionType.Click, v); this.captureEvent(ActionType.Click, v);
@ -199,7 +193,6 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
}); });
}); });
document.addEventListener('touchcancel', ev => { document.addEventListener('touchcancel', ev => {
ev.preventDefault();
this.createTouchAction(ev, ActionType.Up).forEach(v => { this.createTouchAction(ev, ActionType.Up).forEach(v => {
this.captureEvent(ActionType.Up, v); this.captureEvent(ActionType.Up, v);
}); });
@ -208,7 +201,6 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
}); });
}); });
document.addEventListener('touchmove', ev => { document.addEventListener('touchmove', ev => {
ev.preventDefault();
this.createTouchAction(ev, ActionType.Move).forEach(v => { this.createTouchAction(ev, ActionType.Move).forEach(v => {
const list = this.touchInfo.values(); const list = this.touchInfo.values();
if (!list.some(vv => v.identifier === vv.identifier)) { if (!list.some(vv => v.identifier === vv.identifier)) {
@ -228,7 +220,6 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
}); });
}); });
canvas.addEventListener('wheel', ev => { canvas.addEventListener('wheel', ev => {
ev.preventDefault();
this.captureEvent( this.captureEvent(
ActionType.Wheel, ActionType.Wheel,
this.createWheelAction(ev, ActionType.Wheel) this.createWheelAction(ev, ActionType.Wheel)

View File

@ -192,6 +192,7 @@ export class Transform {
if (this.modified) { if (this.modified) {
const result = new Transform(); const result = new Transform();
mat3.multiply(result.mat, this.mat, transform.mat); mat3.multiply(result.mat, this.mat, transform.mat);
result.modified = true;
return result; return result;
} else { } else {
return transform.clone(); return transform.clone();

View File

@ -127,5 +127,6 @@ div.toolbar-editor-item {
position: fixed; position: fixed;
border: none; border: none;
z-index: 1000; z-index: 1000;
background-color: transparent; background-color: black;
transform-origin: 0 0;
} }