mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-08-28 03:33:54 +08:00
feat: 楼层选择组件 & fix: 滚动条交互错位
This commit is contained in:
parent
936c909451
commit
b7c7cdeca0
@ -75,7 +75,7 @@
|
||||
"markdown-it-mathjax3": "^4.3.2",
|
||||
"mermaid": "^11.5.0",
|
||||
"postcss-preset-env": "^9.6.0",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier": "^3.6.2",
|
||||
"rollup": "^3.29.5",
|
||||
"terser": "^5.39.0",
|
||||
"tsx": "^4.19.3",
|
||||
|
@ -0,0 +1,202 @@
|
||||
import { DefaultProps } from '@motajs/render-vue';
|
||||
import { SetupComponentOptions } from '@motajs/system-ui';
|
||||
import { clamp, isNil } from 'lodash-es';
|
||||
import { computed, defineComponent, ref, watch } from 'vue';
|
||||
import { Scroll, ScrollExpose } from './scroll';
|
||||
import { Font } from '@motajs/render-style';
|
||||
import { MotaOffscreenCanvas2D } from '@motajs/render-core';
|
||||
|
||||
export interface FloorSelectorProps extends DefaultProps {
|
||||
floors: FloorIds[];
|
||||
now?: number;
|
||||
}
|
||||
|
||||
export type FloorSelectorEmits = {
|
||||
/**
|
||||
* 点击关闭按钮时触发
|
||||
*/
|
||||
close: () => void;
|
||||
|
||||
/**
|
||||
* 当选中的楼层改变时触发
|
||||
* @param floor 楼层索引
|
||||
* @param floorId 楼层 id
|
||||
*/
|
||||
update: (floor: number, floorId: FloorIds) => void;
|
||||
|
||||
'update:now': (value: number) => void;
|
||||
};
|
||||
|
||||
const floorSelectorProps = {
|
||||
props: ['floors', 'now'],
|
||||
emits: ['close', 'update', 'update:now']
|
||||
} satisfies SetupComponentOptions<
|
||||
FloorSelectorProps,
|
||||
FloorSelectorEmits,
|
||||
keyof FloorSelectorEmits
|
||||
>;
|
||||
|
||||
export const FloorSelector = defineComponent<
|
||||
FloorSelectorProps,
|
||||
FloorSelectorEmits,
|
||||
keyof FloorSelectorEmits
|
||||
>((props, { emit }) => {
|
||||
const listFont = new Font(Font.defaultFamily, 12);
|
||||
|
||||
const now = ref(props.now ?? 0);
|
||||
const selList = ref(0);
|
||||
|
||||
const scrollRef = ref<ScrollExpose>();
|
||||
|
||||
const floorId = computed(() => props.floors[now.value]);
|
||||
const floorName = computed(() => core.floors[floorId.value].title);
|
||||
|
||||
watch(
|
||||
() => props.now,
|
||||
value => {
|
||||
if (!isNil(value)) now.value = value;
|
||||
}
|
||||
);
|
||||
|
||||
let gradient: CanvasGradient | null = null;
|
||||
|
||||
const getGradient = (ctx: CanvasRenderingContext2D) => {
|
||||
if (gradient) return gradient;
|
||||
gradient = ctx.createLinearGradient(0, 0, 0, 200);
|
||||
gradient.addColorStop(0, 'rgba(255,255,255,0)');
|
||||
gradient.addColorStop(0.2, 'rgba(255,255,255,1)');
|
||||
gradient.addColorStop(0.8, 'rgba(255,255,255,1)');
|
||||
gradient.addColorStop(1, 'rgba(255,255,255,0)');
|
||||
return gradient;
|
||||
};
|
||||
|
||||
const renderMask = (canvas: MotaOffscreenCanvas2D) => {
|
||||
const { ctx } = canvas;
|
||||
const gradient = getGradient(ctx);
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(0, 0, 144, 200);
|
||||
};
|
||||
|
||||
const changeTo = (index: number) => {
|
||||
const res = clamp(index, 0, props.floors.length - 1);
|
||||
now.value = res;
|
||||
selList.value = res;
|
||||
const y = res * 24;
|
||||
scrollRef.value?.scrollTo(y, 500);
|
||||
emit('update', now.value, floorId.value);
|
||||
emit('update:now', now.value);
|
||||
};
|
||||
|
||||
const changeFloor = (delta: number) => {
|
||||
changeTo(now.value + delta);
|
||||
};
|
||||
|
||||
const enterList = (index: number) => {
|
||||
selList.value = index;
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
return () => (
|
||||
<container>
|
||||
<text text={floorName.value} loc={[90, 24]} anc={[0.5, 0.5]} />
|
||||
<g-line line={[48, 40, 132, 40]} lineWidth={1} />
|
||||
<g-line line={[48, 440, 132, 440]} lineWidth={1} />
|
||||
<text
|
||||
text="退出"
|
||||
loc={[90, 456]}
|
||||
anc={[0.5, 0.5]}
|
||||
cursor="pointer"
|
||||
onClick={close}
|
||||
/>
|
||||
<text
|
||||
text="「 上移十层 」"
|
||||
loc={[90, 70]}
|
||||
anc={[0.5, 0.5]}
|
||||
cursor="pointer"
|
||||
onClick={() => changeFloor(-10)}
|
||||
/>
|
||||
<text
|
||||
text="「 上移一层 」"
|
||||
loc={[90, 110]}
|
||||
anc={[0.5, 0.5]}
|
||||
cursor="pointer"
|
||||
onClick={() => changeFloor(-1)}
|
||||
/>
|
||||
<text
|
||||
text="「 下移一层 」"
|
||||
loc={[90, 370]}
|
||||
anc={[0.5, 0.5]}
|
||||
cursor="pointer"
|
||||
onClick={() => changeFloor(1)}
|
||||
/>
|
||||
<text
|
||||
text="「 下移十层 」"
|
||||
loc={[90, 410]}
|
||||
anc={[0.5, 0.5]}
|
||||
cursor="pointer"
|
||||
onClick={() => changeFloor(10)}
|
||||
/>
|
||||
<container loc={[0, 140, 144, 200]}>
|
||||
<Scroll
|
||||
ref={scrollRef}
|
||||
loc={[0, 0, 144, 200]}
|
||||
noscroll
|
||||
zIndex={10}
|
||||
padEnd={88}
|
||||
>
|
||||
{props.floors.map((v, i) => {
|
||||
const floor = core.floors[v];
|
||||
const highlight =
|
||||
now.value === i || selList.value === i;
|
||||
const color = highlight ? '#fff' : '#aaa';
|
||||
const fill = highlight ? '#fff' : '#000';
|
||||
return (
|
||||
<container
|
||||
nocache
|
||||
loc={[0, i * 24 + 88, 144, 24]}
|
||||
key={v}
|
||||
>
|
||||
<text
|
||||
cursor="pointer"
|
||||
text={floor.title}
|
||||
loc={[114, 12]}
|
||||
anc={[1, 0.5]}
|
||||
font={listFont}
|
||||
fillStyle={color}
|
||||
onEnter={() => enterList(i)}
|
||||
onLeave={() => enterList(now.value)}
|
||||
onClick={() => changeTo(i)}
|
||||
/>
|
||||
<g-circle
|
||||
stroke
|
||||
fill
|
||||
lineWidth={1}
|
||||
circle={[130, 12, 3]}
|
||||
strokeStyle={color}
|
||||
fillStyle={now.value === i ? fill : '#000'}
|
||||
/>
|
||||
</container>
|
||||
);
|
||||
})}
|
||||
</Scroll>
|
||||
<g-line
|
||||
line={[130, 0, 130, 200]}
|
||||
zIndex={5}
|
||||
lineWidth={1}
|
||||
strokeStyle="#aaa"
|
||||
/>
|
||||
<sprite
|
||||
zIndex={20}
|
||||
loc={[0, 0, 144, 200]}
|
||||
nocache
|
||||
noevent
|
||||
render={renderMask}
|
||||
composite="destination-in"
|
||||
/>
|
||||
</container>
|
||||
</container>
|
||||
);
|
||||
}, floorSelectorProps);
|
@ -20,7 +20,12 @@ import {
|
||||
MotaOffscreenCanvas2D,
|
||||
IActionEvent,
|
||||
IWheelEvent,
|
||||
MouseType
|
||||
MouseType,
|
||||
EventProgress,
|
||||
ActionEventMap,
|
||||
ContainerCustom,
|
||||
ActionType,
|
||||
CustomContainerPropagateOrigin
|
||||
} from '@motajs/render';
|
||||
import { hyper, linear, Transition } from 'mutate-animate';
|
||||
import { clamp } from 'lodash-es';
|
||||
@ -355,6 +360,23 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
|
||||
|
||||
//#region 事件监听
|
||||
|
||||
const customPropagate = <T extends ActionType>(
|
||||
type: T,
|
||||
progress: EventProgress,
|
||||
event: ActionEventMap[T],
|
||||
_: ContainerCustom,
|
||||
origin: CustomContainerPropagateOrigin
|
||||
) => {
|
||||
if (progress === EventProgress.Capture) {
|
||||
if (direction.value === ScrollDirection.Horizontal) {
|
||||
event.offsetX += contentPos;
|
||||
} else {
|
||||
event.offsetY += contentPos;
|
||||
}
|
||||
}
|
||||
origin(type, progress, event);
|
||||
};
|
||||
|
||||
const wheelScroll = (delta: number, max: number) => {
|
||||
const sign = Math.sign(delta);
|
||||
const dx = Math.abs(delta);
|
||||
@ -532,6 +554,7 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
|
||||
ref={content}
|
||||
onDown={down}
|
||||
render={renderContent}
|
||||
propagate={customPropagate}
|
||||
zIndex={0}
|
||||
>
|
||||
{slots.default?.()}
|
||||
|
@ -9,8 +9,8 @@ import { SetupComponentOptions } from '@motajs/system-ui';
|
||||
|
||||
export interface ThumbnailProps extends SpriteProps {
|
||||
loc: ElementLocator;
|
||||
padStyle: CanvasStyle;
|
||||
floorId: FloorIds;
|
||||
padStyle?: CanvasStyle;
|
||||
map?: Block[];
|
||||
hero?: HeroStatus;
|
||||
// configs
|
||||
@ -65,7 +65,7 @@ export const Thumbnail = defineComponent<ThumbnailProps>(props => {
|
||||
options.centerY = hero.loc.y;
|
||||
}
|
||||
ctx.save();
|
||||
ctx.fillStyle = props.padStyle;
|
||||
ctx.fillStyle = props.padStyle ?? 'black';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
core.drawThumbnail(props.floorId, props.map, options);
|
||||
ctx.restore();
|
||||
|
@ -483,7 +483,6 @@ export class Damage extends RenderItem<EDamageEvent> {
|
||||
const { ctx } = canvas;
|
||||
ctx.save();
|
||||
transformCanvas(canvas, transform);
|
||||
ctx.lineJoin = 'round';
|
||||
|
||||
const render = this.calNeedRender(transform);
|
||||
const block = this.block;
|
||||
@ -521,6 +520,8 @@ export class Damage extends RenderItem<EDamageEvent> {
|
||||
const { ctx: ct } = temp;
|
||||
|
||||
ct.translate(-px, -py);
|
||||
ct.lineJoin = 'round';
|
||||
ct.lineCap = 'round';
|
||||
|
||||
const render = this.renderable.get(v);
|
||||
|
||||
@ -532,6 +533,7 @@ export class Damage extends RenderItem<EDamageEvent> {
|
||||
ct.font = v.font ?? this.font;
|
||||
ct.strokeStyle = v.stroke ?? this.strokeStyle;
|
||||
ct.lineWidth = v.strokeWidth ?? this.strokeWidth;
|
||||
|
||||
ct.strokeText(v.text, v.x, v.y);
|
||||
ct.fillText(v.text, v.x, v.y);
|
||||
});
|
||||
|
@ -151,8 +151,23 @@ export type CustomContainerRenderFn = (
|
||||
transform: Transform
|
||||
) => void;
|
||||
|
||||
export type CustomContainerPropagateOrigin = <T extends ActionType>(
|
||||
type: T,
|
||||
progress: EventProgress,
|
||||
event: ActionEventMap[T]
|
||||
) => void;
|
||||
|
||||
export type CustomContainerPropagateFn = <T extends ActionType>(
|
||||
type: T,
|
||||
progress: EventProgress,
|
||||
event: ActionEventMap[T],
|
||||
container: ContainerCustom,
|
||||
origin: CustomContainerPropagateOrigin
|
||||
) => void;
|
||||
|
||||
export class ContainerCustom extends Container {
|
||||
private renderFn?: CustomContainerRenderFn;
|
||||
private propagateFn?: CustomContainerPropagateFn;
|
||||
|
||||
protected render(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
@ -165,6 +180,20 @@ export class ContainerCustom extends Container {
|
||||
}
|
||||
}
|
||||
|
||||
protected propagateEvent<T extends ActionType>(
|
||||
type: T,
|
||||
progress: EventProgress,
|
||||
event: ActionEventMap[T]
|
||||
): void {
|
||||
if (this.propagateFn) {
|
||||
this.propagateFn(type, progress, event, this, () => {
|
||||
super.propagateEvent(type, progress, event);
|
||||
});
|
||||
} else {
|
||||
super.propagateEvent(type, progress, event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置这个自定义容器的渲染函数
|
||||
* @param render 渲染函数
|
||||
@ -173,6 +202,14 @@ export class ContainerCustom extends Container {
|
||||
this.renderFn = render;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置这个自定义容器的事件传递函数
|
||||
* @param propagate 事件传递函数
|
||||
*/
|
||||
setPropagateFn(propagate: CustomContainerPropagateFn) {
|
||||
this.propagateFn = propagate;
|
||||
}
|
||||
|
||||
protected handleProps(
|
||||
key: string,
|
||||
prevValue: any,
|
||||
@ -184,6 +221,11 @@ export class ContainerCustom extends Container {
|
||||
this.setRenderFn(nextValue);
|
||||
return true;
|
||||
}
|
||||
case 'propagate': {
|
||||
if (!this.assertType(nextValue, 'function', key)) return false;
|
||||
this.setPropagateFn(nextValue);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.handleProps(key, prevValue, nextValue);
|
||||
}
|
||||
|
@ -73,11 +73,11 @@ export interface IActionEventBase {
|
||||
ctrlKey: boolean;
|
||||
/** 触发时是否按下了 Windows(Windows) / Command(Mac) 键 */
|
||||
metaKey: boolean;
|
||||
/** 这次操作的标识符,在按下、移动、抬起阶段中保持不变 */
|
||||
identifier: number;
|
||||
}
|
||||
|
||||
export interface IActionEvent extends IActionEventBase {
|
||||
/** 这次操作的标识符,在按下、移动、抬起阶段中保持不变 */
|
||||
identifier: number;
|
||||
/** 相对于触发元素左上角的横坐标 */
|
||||
offsetX: number;
|
||||
/** 相对于触发元素左上角的纵坐标 */
|
||||
|
@ -165,14 +165,19 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
|
||||
}
|
||||
this.checkMouseEnterLeave(
|
||||
ev,
|
||||
event,
|
||||
this.beforeHovered,
|
||||
this.hoveredElement
|
||||
);
|
||||
});
|
||||
canvas.addEventListener('mouseleave', ev => {
|
||||
ev.preventDefault();
|
||||
const id = this.getMouseIdentifier(
|
||||
ActionType.Leave,
|
||||
this.getMouseType(ev)
|
||||
);
|
||||
this.hoveredElement.forEach(v => {
|
||||
v.emit('leave', this.createMouseActionBase(ev, v));
|
||||
v.emit('leave', this.createMouseActionBase(ev, id, v));
|
||||
});
|
||||
this.hoveredElement.clear();
|
||||
this.beforeHovered.clear();
|
||||
@ -210,6 +215,7 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
|
||||
this.captureEvent(ActionType.Move, v);
|
||||
this.checkTouchEnterLeave(
|
||||
ev,
|
||||
v,
|
||||
this.beforeHovered,
|
||||
this.hoveredElement
|
||||
);
|
||||
@ -308,10 +314,12 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
|
||||
|
||||
private createMouseActionBase(
|
||||
event: MouseEvent,
|
||||
id: number,
|
||||
target: RenderItem = this,
|
||||
mouse: MouseType = this.getMouseType(event)
|
||||
): IActionEventBase {
|
||||
return {
|
||||
identifier: id,
|
||||
target: target,
|
||||
touch: false,
|
||||
type: mouse,
|
||||
@ -325,9 +333,11 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
|
||||
|
||||
private createTouchActionBase(
|
||||
event: TouchEvent,
|
||||
id: number,
|
||||
target: RenderItem
|
||||
): IActionEventBase {
|
||||
return {
|
||||
identifier: id,
|
||||
target: target,
|
||||
touch: false,
|
||||
type: MouseType.Left,
|
||||
@ -480,36 +490,50 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
|
||||
|
||||
private checkMouseEnterLeave(
|
||||
event: MouseEvent,
|
||||
ev: IActionEvent,
|
||||
before: Set<RenderItem>,
|
||||
now: Set<RenderItem>
|
||||
) {
|
||||
// 先 leave,再 enter
|
||||
before.forEach(v => {
|
||||
if (!now.has(v)) {
|
||||
v.emit('leave', this.createMouseActionBase(event, v));
|
||||
v.emit(
|
||||
'leave',
|
||||
this.createMouseActionBase(event, ev.identifier, v)
|
||||
);
|
||||
}
|
||||
});
|
||||
now.forEach(v => {
|
||||
if (!before.has(v)) {
|
||||
v.emit('enter', this.createMouseActionBase(event, v));
|
||||
v.emit(
|
||||
'enter',
|
||||
this.createMouseActionBase(event, ev.identifier, v)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private checkTouchEnterLeave(
|
||||
event: TouchEvent,
|
||||
ev: IActionEvent,
|
||||
before: Set<RenderItem>,
|
||||
now: Set<RenderItem>
|
||||
) {
|
||||
// 先 leave,再 enter
|
||||
before.forEach(v => {
|
||||
if (!now.has(v)) {
|
||||
v.emit('leave', this.createTouchActionBase(event, v));
|
||||
v.emit(
|
||||
'leave',
|
||||
this.createTouchActionBase(event, ev.identifier, v)
|
||||
);
|
||||
}
|
||||
});
|
||||
now.forEach(v => {
|
||||
if (!before.has(v)) {
|
||||
v.emit('enter', this.createTouchActionBase(event, v));
|
||||
v.emit(
|
||||
'enter',
|
||||
this.createTouchActionBase(event, ev.identifier, v)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -6,7 +6,8 @@ import {
|
||||
ElementAnchor,
|
||||
ElementLocator,
|
||||
ElementScale,
|
||||
CustomContainerRenderFn
|
||||
CustomContainerRenderFn,
|
||||
CustomContainerPropagateFn
|
||||
} from '@motajs/render-core';
|
||||
import {
|
||||
BezierParams,
|
||||
@ -94,6 +95,8 @@ export interface ContainerProps extends BaseProps {}
|
||||
export interface ConatinerCustomProps extends ContainerProps {
|
||||
/** 自定义容器渲染函数 */
|
||||
render?: CustomContainerRenderFn;
|
||||
/** 自定义容器事件传递函数 */
|
||||
propagate?: CustomContainerPropagateFn;
|
||||
}
|
||||
|
||||
export interface GL2Props extends BaseProps {}
|
||||
|
1685
pnpm-lock.yaml
1685
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user