diff --git a/spx-gui/src/components/editor/common/AnglePicker.vue b/spx-gui/src/components/editor/common/AnglePicker.vue index 3468a6c4f..620f0721b 100644 --- a/spx-gui/src/components/editor/common/AnglePicker.vue +++ b/spx-gui/src/components/editor/common/AnglePicker.vue @@ -2,7 +2,7 @@ import { computed, ref, watch } from 'vue' import { useDraggableAngleForElement } from '@/utils/dom' import { makeArcPathString } from '@/utils/svg' -import { nomalizeDegree, useDebouncedModel } from '@/utils/utils' +import { normalizeDegree, useDebouncedModel } from '@/utils/utils' import { specialDirections } from '@/utils/spx' import { UITag } from '@/components/ui' @@ -16,7 +16,7 @@ const emit = defineEmits<{ const [modelValue] = useDebouncedModel( () => props.modelValue, - (v) => emit('update:modelValue', nomalizeDegree(Math.floor(v))) + (v) => emit('update:modelValue', normalizeDegree(Math.floor(v))) ) const svgEl = ref(null) @@ -26,7 +26,7 @@ const arcPath = computed(() => { return makeArcPathString({ x: 70, y: 70, r: 63, start, end }) }) const angle = useDraggableAngleForElement(svgEl, { initialValue: props.modelValue, snap: 15 }) -watch(angle, (v) => (modelValue.value = nomalizeDegree(v))) +watch(angle, (v) => (modelValue.value = normalizeDegree(v))) + + + + + + diff --git a/spx-gui/src/components/editor/common/viewer/quick-config/SpriteQuickConfig.vue b/spx-gui/src/components/editor/common/viewer/quick-config/SpriteQuickConfig.vue new file mode 100644 index 000000000..32eaf4452 --- /dev/null +++ b/spx-gui/src/components/editor/common/viewer/quick-config/SpriteQuickConfig.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/spx-gui/src/components/editor/common/viewer/quick-config/WidgetQuickConfig.vue b/spx-gui/src/components/editor/common/viewer/quick-config/WidgetQuickConfig.vue new file mode 100644 index 000000000..8a4f8e923 --- /dev/null +++ b/spx-gui/src/components/editor/common/viewer/quick-config/WidgetQuickConfig.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/spx-gui/src/components/editor/common/viewer/quick-config/common/ConfigItem.vue b/spx-gui/src/components/editor/common/viewer/quick-config/common/ConfigItem.vue new file mode 100644 index 000000000..b4bb3e22f --- /dev/null +++ b/spx-gui/src/components/editor/common/viewer/quick-config/common/ConfigItem.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/spx-gui/src/components/editor/common/viewer/quick-config/common/ConfigPanel.vue b/spx-gui/src/components/editor/common/viewer/quick-config/common/ConfigPanel.vue new file mode 100644 index 000000000..6280dd384 --- /dev/null +++ b/spx-gui/src/components/editor/common/viewer/quick-config/common/ConfigPanel.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/spx-gui/src/components/editor/common/viewer/quick-config/common/PositionConfigPanel.vue b/spx-gui/src/components/editor/common/viewer/quick-config/common/PositionConfigPanel.vue new file mode 100644 index 000000000..43b0a90f8 --- /dev/null +++ b/spx-gui/src/components/editor/common/viewer/quick-config/common/PositionConfigPanel.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/spx-gui/src/components/editor/common/viewer/quick-config/common/SizeConfigPanel.vue b/spx-gui/src/components/editor/common/viewer/quick-config/common/SizeConfigPanel.vue new file mode 100644 index 000000000..7ec6c3532 --- /dev/null +++ b/spx-gui/src/components/editor/common/viewer/quick-config/common/SizeConfigPanel.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/spx-gui/src/components/editor/common/viewer/quick-config/common/ZorderConfigItem.vue b/spx-gui/src/components/editor/common/viewer/quick-config/common/ZorderConfigItem.vue new file mode 100644 index 000000000..3358860e9 --- /dev/null +++ b/spx-gui/src/components/editor/common/viewer/quick-config/common/ZorderConfigItem.vue @@ -0,0 +1,65 @@ + + + + + + + diff --git a/spx-gui/src/components/editor/common/viewer/quick-config/sprite/DefaultConfigPanel.vue b/spx-gui/src/components/editor/common/viewer/quick-config/sprite/DefaultConfigPanel.vue new file mode 100644 index 000000000..596b0fa71 --- /dev/null +++ b/spx-gui/src/components/editor/common/viewer/quick-config/sprite/DefaultConfigPanel.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/spx-gui/src/components/editor/common/viewer/quick-config/sprite/HeadingConfigPanel.vue b/spx-gui/src/components/editor/common/viewer/quick-config/sprite/HeadingConfigPanel.vue new file mode 100644 index 000000000..c785b9dfe --- /dev/null +++ b/spx-gui/src/components/editor/common/viewer/quick-config/sprite/HeadingConfigPanel.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/spx-gui/src/components/editor/common/viewer/quick-config/utils.ts b/spx-gui/src/components/editor/common/viewer/quick-config/utils.ts new file mode 100644 index 000000000..66bf1fbbc --- /dev/null +++ b/spx-gui/src/components/editor/common/viewer/quick-config/utils.ts @@ -0,0 +1,143 @@ +import { ref, type Ref } from 'vue' +import { RotationStyle, type Sprite } from '@/models/sprite' +import type { Action, Project } from '@/models/project' +import type { Widget } from '@/models/widget' + +interface ILocalConfigProvider { + id: string + + get x(): number + get y(): number + get size(): number + get visible(): boolean + + setX(x: number): void + setY(y: number): void + setSize(size: number): void +} + +interface LocalConfigChanges { + x?: number + y?: number + size?: number + visible?: boolean +} + +export class LocalConfig { + constructor( + private provider: ILocalConfigProvider, + private project: Project, + private action: Action + ) {} + + protected changes = ref({}) as Ref> + + get id() { + return this.provider.id + } + + get x() { + return this.changes.value.x ?? this.provider.x + } + setX(x: number) { + this.changes.value.x = x + } + + get y() { + return this.changes.value.y ?? this.provider.y + } + setY(y: number) { + this.changes.value.y = y + } + + get size() { + return this.changes.value.size ?? this.provider.size + } + setSize(size: number) { + this.changes.value.size = size + } + + get visible() { + return this.provider.visible + } + + sync() { + this.project.history.doAction(this.action, () => { + Object.keys(this.changes.value).forEach((key) => this.applyChanges(key)) + this.changes.value = {} + }) + } + + protected applyChanges(key: string) { + switch (key) { + case 'x': + this.provider.setX(this.x) + break + case 'y': + this.provider.setY(this.y) + break + case 'size': + this.provider.setSize(this.size) + break + } + } +} + +export class SpriteLocalConfig extends LocalConfig<{ + heading?: number + rotationStyle?: RotationStyle +}> { + constructor( + private sprite: Sprite, + project: Project + ) { + super(sprite, project, { name: { en: `Configure sprite ${sprite.name}`, zh: `修改精灵 ${sprite.name} 配置` } }) + } + + get heading() { + return this.changes.value.heading ?? this.sprite.heading + } + setHeading(heading: number) { + this.changes.value.heading = heading + } + + get rotationStyle() { + return this.changes.value.rotationStyle ?? this.sprite.rotationStyle + } + setRotationStyle(rotationStyle: RotationStyle) { + this.changes.value.rotationStyle = rotationStyle + } + + get defaultCostume() { + return this.sprite.defaultCostume + } + + override applyChanges(key: string) { + super.applyChanges(key) + switch (key) { + case 'heading': + this.sprite.setHeading(this.heading) + break + case 'rotationStyle': + this.sprite.setRotationStyle(this.rotationStyle) + break + } + } +} + +export class WidgetLocalConfig extends LocalConfig { + constructor( + private widget: Widget, + project: Project + ) { + super(widget, project, { name: { en: `Configure widget ${widget.name}`, zh: `修改组件 ${widget.name} 配置` } }) + } + + get label() { + return this.widget.label + } + + get variableName() { + return this.widget.variableName + } +} diff --git a/spx-gui/src/components/editor/common/viewer/quick-config/widget/DefaultConfigPanel.vue b/spx-gui/src/components/editor/common/viewer/quick-config/widget/DefaultConfigPanel.vue new file mode 100644 index 000000000..c25b02a7a --- /dev/null +++ b/spx-gui/src/components/editor/common/viewer/quick-config/widget/DefaultConfigPanel.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/spx-gui/src/components/editor/map-editor/map-viewer/MapViewer.vue b/spx-gui/src/components/editor/map-editor/map-viewer/MapViewer.vue index e67e4bde9..e8a83d28d 100644 --- a/spx-gui/src/components/editor/map-editor/map-viewer/MapViewer.vue +++ b/spx-gui/src/components/editor/map-editor/map-viewer/MapViewer.vue @@ -1,14 +1,14 @@ @@ -428,14 +509,7 @@ const handleWheel = (e: KonvaEventObject) => { class="map-viewer" @mousemove="updateMousePos" > - + ) => { @@ -464,31 +539,12 @@ const handleWheel = (e: KonvaEventObject) => { - - - {{ $t(moveActionNames.up) }} - {{ $t(moveActionNames.top) }} - {{ $t(moveActionNames.down) }} - {{ $t(moveActionNames.bottom) }} - - + + + + + @@ -509,4 +565,10 @@ const handleWheel = (e: KonvaEventObject) => { background-size: contain; position: relative; } + +.quick-config { + position: absolute; + top: 0; + left: 0; +} diff --git a/spx-gui/src/components/editor/panels/sprite/SpritesPanel.vue b/spx-gui/src/components/editor/panels/sprite/SpritesPanel.vue index cdf48a141..410905962 100644 --- a/spx-gui/src/components/editor/panels/sprite/SpritesPanel.vue +++ b/spx-gui/src/components/editor/panels/sprite/SpritesPanel.vue @@ -23,30 +23,6 @@ @@ -36,4 +42,9 @@ withDefaults( height: 100%; } } + +.eye-off { + color: var(--ui-color-grey-700); + cursor: auto; +} diff --git a/spx-gui/src/components/ui/icons/UIIcon.vue b/spx-gui/src/components/ui/icons/UIIcon.vue index 3c35bf60e..020cf0801 100644 --- a/spx-gui/src/components/ui/icons/UIIcon.vue +++ b/spx-gui/src/components/ui/icons/UIIcon.vue @@ -21,6 +21,7 @@ import trash from './trash.svg?raw' import edit from './edit.svg?raw' import eye from './eye.svg?raw' import eyeSlash from './eye-slash.svg?raw' +import eyeOff from './eye-off.svg?raw' import more from './more.svg?raw' import exchange from './exchange.svg?raw' import search from './search.svg?raw' @@ -64,6 +65,10 @@ import redo from './redo.svg?raw' import localFile from './local-file.svg?raw' import assetLibrary from './asset-library.svg?raw' import minus from './minus.svg?raw' +import layer from './layer.svg?raw' +import notRotate from './not-rotate.svg?raw' +import leftRight from './left-right.svg?raw' +import rotateAround from './rotate-around.svg?raw' const typeIconMap = { file, @@ -82,12 +87,14 @@ const typeIconMap = { edit, eye, eyeSlash, + eyeOff, more, exchange, search, close, closeCircle, rotate, + rotateAround, share, arrowAlt, doubleArrowDown, @@ -124,7 +131,10 @@ const typeIconMap = { redo, localFile, assetLibrary, - minus + minus, + layer, + notRotate, + leftRight } export type Type = keyof typeof typeIconMap diff --git a/spx-gui/src/components/ui/icons/eye-off.svg b/spx-gui/src/components/ui/icons/eye-off.svg new file mode 100644 index 000000000..ba66fe0d8 --- /dev/null +++ b/spx-gui/src/components/ui/icons/eye-off.svg @@ -0,0 +1,3 @@ + + + diff --git a/spx-gui/src/components/ui/icons/layer.svg b/spx-gui/src/components/ui/icons/layer.svg new file mode 100644 index 000000000..1afdea134 --- /dev/null +++ b/spx-gui/src/components/ui/icons/layer.svg @@ -0,0 +1,3 @@ + + + diff --git a/spx-gui/src/components/ui/icons/left-right.svg b/spx-gui/src/components/ui/icons/left-right.svg new file mode 100644 index 000000000..cea2dcc64 --- /dev/null +++ b/spx-gui/src/components/ui/icons/left-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/spx-gui/src/components/ui/icons/not-rotate.svg b/spx-gui/src/components/ui/icons/not-rotate.svg new file mode 100644 index 000000000..332165390 --- /dev/null +++ b/spx-gui/src/components/ui/icons/not-rotate.svg @@ -0,0 +1,3 @@ + + + diff --git a/spx-gui/src/components/ui/icons/rotate-around.svg b/spx-gui/src/components/ui/icons/rotate-around.svg new file mode 100644 index 000000000..e41abbc5f --- /dev/null +++ b/spx-gui/src/components/ui/icons/rotate-around.svg @@ -0,0 +1,3 @@ + + + diff --git a/spx-gui/src/models/sprite.ts b/spx-gui/src/models/sprite.ts index db8092622..acf6711ad 100644 --- a/spx-gui/src/models/sprite.ts +++ b/spx-gui/src/models/sprite.ts @@ -4,7 +4,7 @@ */ import { reactive } from 'vue' -import { nomalizeDegree } from '@/utils/utils' +import { normalizeDegree } from '@/utils/utils' import { join } from '@/utils/path' import { Disposable } from '@/utils/disposable' import { fromText, type Files, fromConfig, toText, toConfig, listDirs } from './common/file' @@ -723,7 +723,7 @@ export enum LeftRight { } export function headingToLeftRight(heading: number): LeftRight { - heading = nomalizeDegree(heading) + heading = normalizeDegree(heading) if (heading >= 0) return LeftRight.right return LeftRight.left } diff --git a/spx-gui/src/utils/utils.test.ts b/spx-gui/src/utils/utils.test.ts index cecf97f4f..a191c45ef 100644 --- a/spx-gui/src/utils/utils.test.ts +++ b/spx-gui/src/utils/utils.test.ts @@ -3,7 +3,7 @@ import { describe, it, expect, vitest } from 'vitest' import { isImage, isSound, - nomalizeDegree, + normalizeDegree, memoizeAsync, localStorageRef, humanizeListWithLimit, @@ -73,21 +73,21 @@ describe('isSound', () => { }) }) -describe('nomalizeDegree', () => { +describe('normalizeDegree', () => { it('should work well', () => { - expect(nomalizeDegree(0)).toBe(0) - expect(nomalizeDegree(90)).toBe(90) - expect(nomalizeDegree(180)).toBe(180) - expect(nomalizeDegree(270)).toBe(-90) - expect(nomalizeDegree(360)).toBe(0) - expect(nomalizeDegree(450)).toBe(90) - expect(nomalizeDegree(720)).toBe(0) - expect(nomalizeDegree(-90)).toBe(-90) - expect(nomalizeDegree(-180)).toBe(180) - expect(nomalizeDegree(-270)).toBe(90) - expect(nomalizeDegree(-360)).toBe(0) - expect(nomalizeDegree(-450)).toBe(-90) - expect(nomalizeDegree(-720)).toBe(0) + expect(normalizeDegree(0)).toBe(0) + expect(normalizeDegree(90)).toBe(90) + expect(normalizeDegree(180)).toBe(180) + expect(normalizeDegree(270)).toBe(-90) + expect(normalizeDegree(360)).toBe(0) + expect(normalizeDegree(450)).toBe(90) + expect(normalizeDegree(720)).toBe(0) + expect(normalizeDegree(-90)).toBe(-90) + expect(normalizeDegree(-180)).toBe(180) + expect(normalizeDegree(-270)).toBe(90) + expect(normalizeDegree(-360)).toBe(0) + expect(normalizeDegree(-450)).toBe(-90) + expect(normalizeDegree(-720)).toBe(0) }) }) diff --git a/spx-gui/src/utils/utils.ts b/spx-gui/src/utils/utils.ts index fe2db390a..3acfae505 100644 --- a/spx-gui/src/utils/utils.ts +++ b/spx-gui/src/utils/utils.ts @@ -196,7 +196,7 @@ function untilConditionMet( } /** Convert arbitrary degree value to `(-180, 180]` */ -export function nomalizeDegree(num: number) { +export function normalizeDegree(num: number) { if (!Number.isFinite(num) || Number.isNaN(num)) return num num = num % 360 if (num > 180) num = num - 360