From 57f1faff815f3f9a836dca8eabdf36d12c2cc858 Mon Sep 17 00:00:00 2001 From: KPal Date: Tue, 30 Jun 2026 10:00:49 +0100 Subject: [PATCH] refactor(ui): reapply legacy widget PCUI migration Reapplies 975d199c4 by reverting 9db98d2a5 after the release revert. --- sass/code-editor.scss | 2 +- ...{_ui-common.scss => _shared-overlays.scss} | 0 sass/editor.scss | 4 +- sass/editor/_editor-attributes-panel.scss | 48 +- sass/editor/_editor-main.scss | 149 +++- .../_editor-pcui-compat-theme.scss} | 33 +- .../_editor-pcui-compat.scss} | 0 sass/pcui/_pcui-asset-input.scss | 6 + src/common/pcui/compat-utils.ts | 41 + .../pcui/element/element-asset-input.ts | 3 +- .../pcui/element/element-curve-input.ts | 39 +- src/common/tooltips.ts | 285 ++++++- src/common/ui/button.ts | 58 -- src/common/ui/canvas.ts | 94 -- src/common/ui/checkbox.ts | 77 -- src/common/ui/code.ts | 19 - src/common/ui/color-field.ts | 252 ------ src/common/ui/container.ts | 253 ------ src/common/ui/curve-field.ts | 398 --------- src/common/ui/element.ts | 357 -------- src/common/ui/image-field.ts | 118 --- src/common/ui/label.ts | 93 -- src/common/ui/list-item.ts | 75 -- src/common/ui/list.ts | 153 ---- src/common/ui/menu-item.ts | 198 ----- src/common/ui/menu.ts | 241 ------ src/common/ui/number-field.ts | 252 ------ src/common/ui/overlay.ts | 101 --- src/common/ui/panel.ts | 406 --------- src/common/ui/progress.ts | 121 --- src/common/ui/select-field.ts | 471 ---------- src/common/ui/slider.ts | 304 ------- src/common/ui/text-field.ts | 190 ----- src/common/ui/textarea-field.ts | 189 ---- src/common/ui/tooltip.ts | 347 -------- src/editor/assets/asset-panel.ts | 7 +- src/editor/attributes/attributes-array.ts | 10 +- .../attributes/attributes-assets-list.ts | 69 +- .../attributes-components-script.ts | 32 +- src/editor/attributes/attributes-entity.ts | 8 +- src/editor/attributes/attributes-history.ts | 4 +- src/editor/attributes/attributes-panel.ts | 806 +++--------------- src/editor/attributes/attributes-pcui.ts | 368 ++++++++ src/editor/attributes/reference/reference.ts | 22 +- src/editor/chat/chat-system.ts | 4 +- src/editor/chat/chat-widget.ts | 4 +- src/editor/entities/entities-control.ts | 10 +- src/editor/entities/entities-panel.ts | 6 +- src/editor/hotkey/hotkey.ts | 6 - src/editor/inspector/assets/texture.ts | 4 +- src/editor/inspector/attributes-inspector.ts | 11 +- src/editor/inspector/components/light.ts | 4 +- src/editor/inspector/components/script.ts | 7 +- .../inspector/settings-panels/import-map.ts | 10 +- .../settings-panels/layers-layer-panel.ts | 4 +- .../layers-render-order-list.ts | 4 +- .../settings-panels/loading-screen.ts | 10 +- src/editor/pickers/picker-asset.ts | 12 +- src/editor/pickers/picker-color.ts | 79 +- src/editor/pickers/picker-curve.ts | 237 +++-- src/editor/pickers/picker-entity.ts | 11 +- src/editor/pickers/picker-gradient.ts | 231 +++-- src/editor/pickers/picker-node.ts | 11 +- ...ite-editor-frames-related-sprites-panel.ts | 9 +- .../settings-attributes-scripts-priority.ts | 36 +- .../templates/templates-override-panel.ts | 11 +- src/editor/toolbar/toolbar-code-editor.ts | 4 +- src/editor/toolbar/toolbar-controls.ts | 4 +- src/editor/toolbar/toolbar-discord.ts | 4 +- src/editor/toolbar/toolbar-editor-settings.ts | 4 +- src/editor/toolbar/toolbar-forum.ts | 4 +- src/editor/toolbar/toolbar-github.ts | 4 +- src/editor/toolbar/toolbar-gizmos.ts | 10 +- src/editor/toolbar/toolbar-help.ts | 4 +- src/editor/toolbar/toolbar-history.ts | 6 +- src/editor/toolbar/toolbar-lightmapper.ts | 10 +- src/editor/toolbar/toolbar-logo.ts | 4 +- src/editor/toolbar/toolbar-publish.ts | 4 +- .../viewport-controls/viewport-launch.ts | 26 +- .../viewport-controls/viewport-scene.ts | 10 +- .../viewport-controls/viewport-whois.ts | 6 +- 81 files changed, 1540 insertions(+), 5988 deletions(-) rename sass/common/{_ui-common.scss => _shared-overlays.scss} (100%) rename sass/{ui/_theme-dark.scss => editor/_editor-pcui-compat-theme.scss} (96%) rename sass/{ui/_ui.scss => editor/_editor-pcui-compat.scss} (100%) create mode 100644 src/common/pcui/compat-utils.ts delete mode 100644 src/common/ui/button.ts delete mode 100644 src/common/ui/canvas.ts delete mode 100644 src/common/ui/checkbox.ts delete mode 100644 src/common/ui/code.ts delete mode 100644 src/common/ui/color-field.ts delete mode 100644 src/common/ui/container.ts delete mode 100644 src/common/ui/curve-field.ts delete mode 100644 src/common/ui/element.ts delete mode 100644 src/common/ui/image-field.ts delete mode 100644 src/common/ui/label.ts delete mode 100644 src/common/ui/list-item.ts delete mode 100644 src/common/ui/list.ts delete mode 100644 src/common/ui/menu-item.ts delete mode 100644 src/common/ui/menu.ts delete mode 100644 src/common/ui/number-field.ts delete mode 100644 src/common/ui/overlay.ts delete mode 100644 src/common/ui/panel.ts delete mode 100644 src/common/ui/progress.ts delete mode 100644 src/common/ui/select-field.ts delete mode 100644 src/common/ui/slider.ts delete mode 100644 src/common/ui/text-field.ts delete mode 100644 src/common/ui/textarea-field.ts delete mode 100644 src/common/ui/tooltip.ts create mode 100644 src/editor/attributes/attributes-pcui.ts diff --git a/sass/code-editor.scss b/sass/code-editor.scss index 6226fa2d9..365b0faed 100644 --- a/sass/code-editor.scss +++ b/sass/code-editor.scss @@ -739,5 +739,5 @@ strong { } } -@import 'common/ui-common'; +@import 'common/shared-overlays'; @import 'common/version-control'; diff --git a/sass/common/_ui-common.scss b/sass/common/_shared-overlays.scss similarity index 100% rename from sass/common/_ui-common.scss rename to sass/common/_shared-overlays.scss diff --git a/sass/editor.scss b/sass/editor.scss index 02dc0aee1..66d96d801 100644 --- a/sass/editor.scss +++ b/sass/editor.scss @@ -1,9 +1,9 @@ @import 'pcui/pcui'; @import 'common/fonts'; @import 'common/monaco-merge'; -@import 'ui/ui'; +@import 'editor/editor-pcui-compat'; @import 'editor/editor-main'; -@import 'ui/theme-dark'; +@import 'editor/editor-pcui-compat-theme'; @import 'editor/editor-common'; @import 'editor/editor-version-control-picker'; @import 'editor/editor-version-control-diff'; diff --git a/sass/editor/_editor-attributes-panel.scss b/sass/editor/_editor-attributes-panel.scss index 6996822f4..37d2d116c 100644 --- a/sass/editor/_editor-attributes-panel.scss +++ b/sass/editor/_editor-attributes-panel.scss @@ -687,12 +687,12 @@ .ui-panel.component { outline: none; - > header { + > :is(header, .ui-header) { text-transform: uppercase; } &.pcui-collapsible { - > header { + > :is(header, .ui-header) { border-top: 1px solid $bcg-darker; &:hover { @@ -702,7 +702,7 @@ } &.pcui-collapsed { - > header { + > :is(header, .ui-header) { background-color: $bcg-primary; &:hover { @@ -714,7 +714,7 @@ } &.entity { - > header { + > :is(header, .ui-header) { border-top: 1px solid $bcg-dark; > .title { @@ -774,80 +774,80 @@ } } - &.script > header > .title::before { + &.script > :is(header, .ui-header) > .title::before { content: '\E236'; } - &.model > header > .title::before { + &.model > :is(header, .ui-header) > .title::before { content: '\E188'; } - &.animation > header > .title::before { + &.animation > :is(header, .ui-header) > .title::before { content: '\E198'; } - &.audiosource > header > .title::before { + &.audiosource > :is(header, .ui-header) > .title::before { content: '\E197'; } - &.sound > header > .title::before { + &.sound > :is(header, .ui-header) > .title::before { content: '\E197'; } - &.collision > header > .title::before { + &.collision > :is(header, .ui-header) > .title::before { content: '\E187'; } - &.rigidbody > header > .title::before { + &.rigidbody > :is(header, .ui-header) > .title::before { content: '\E189'; } - &.particlesystem > header > .title::before { + &.particlesystem > :is(header, .ui-header) > .title::before { content: '\E199'; } - &.light > header > .title::before { + &.light > :is(header, .ui-header) > .title::before { content: '\E194'; } - &.audiolistener > header > .title::before { + &.audiolistener > :is(header, .ui-header) > .title::before { content: '\E196'; } - &.camera > header > .title::before { + &.camera > :is(header, .ui-header) > .title::before { content: '\E212'; } - &.screen > header > .title::before { + &.screen > :is(header, .ui-header) > .title::before { content: '\E403'; } - &.element > header > .title::before { + &.element > :is(header, .ui-header) > .title::before { content: '\E378'; } - &.button > header > .title::before { + &.button > :is(header, .ui-header) > .title::before { content: '\E405'; } - &.scrollview > header > .title::before { + &.scrollview > :is(header, .ui-header) > .title::before { content: '\E408'; } - &.scrollbar > header > .title::before { + &.scrollbar > :is(header, .ui-header) > .title::before { content: '\E409'; } - &.sprite > header > .title::before { + &.sprite > :is(header, .ui-header) > .title::before { content: '\E395'; } - &.layoutgroup > header > .title::before, - &.layoutchild > header > .title::before { + &.layoutgroup > :is(header, .ui-header) > .title::before, + &.layoutchild > :is(header, .ui-header) > .title::before { content: '\E143'; } - &.gsplat > header > .title::before { + &.gsplat > :is(header, .ui-header) > .title::before { content: '\E217'; } } diff --git a/sass/editor/_editor-main.scss b/sass/editor/_editor-main.scss index f397e9bf8..48877aca8 100644 --- a/sass/editor/_editor-main.scss +++ b/sass/editor/_editor-main.scss @@ -1312,8 +1312,49 @@ strong { > .picker-curve-header { padding: 5px 10px; - > .content { - > .ui-button.picker-curve-toggle { + &.pcui-container { + align-items: center; + background-color: $bcg-primary; + } + + > .content, + &.pcui-container { + > .ui-label { + flex: none; + line-height: 22px; + vertical-align: bottom; + } + + > .ui-select-field.pcui-select-input { + flex: none; + height: 24px; + min-height: 0; + padding: 0; + border-radius: 0; + vertical-align: bottom; + + > .pcui-select-input-container-value { + height: 100%; + background-color: transparent; + } + + .pcui-select-input-value { + height: 22px; + line-height: 22px; + padding: 0 22px 0 8px; + } + + .pcui-select-input-icon { + right: 0; + width: 22px; + height: 22px; + line-height: 22px; + } + } + + > .ui-button.picker-curve-toggle, + > .pcui-button.picker-curve-toggle { + border-radius: 0; float: right; text-align: center; height: 24px; @@ -1339,14 +1380,32 @@ strong { > .picker-curve-footer { padding: 5px 10px; - > .content { - > .ui-number-field { + &.pcui-container { + align-items: center; + padding-top: 0; + padding-bottom: 0; + background-color: $bcg-primary; + } + + > .content, + &.pcui-container { + > .ui-number-field, + > .pcui-numeric-input { + flex: none; width: 143px; + border-radius: 0; + + > input.field { + line-height: 22px; + } } - > .ui-button { + > .ui-button, + > .pcui-button { @extend .font-icon; + flex: none; + border-radius: 0; font-size: 14px; width: 32px; height: 24px; @@ -1373,14 +1432,14 @@ strong { font-size: 11px; > .picker-gradient-gradient { - width: 346px; + width: 344px; height: 28px; display: block; padding: 10px 10px 0; } > .picker-gradient-anchors { - width: 346px; + width: 344px; height: 28px; display: block; padding: 0 10px; @@ -1389,8 +1448,50 @@ strong { > .picker-gradient-footer { padding: 5px; - > .content { - > .ui-number-field { + &.pcui-container { + align-items: center; + background-color: $bcg-primary; + } + + > .content, + &.pcui-container { + > .ui-label { + flex: none; + line-height: 22px; + vertical-align: bottom; + } + + > .ui-select-field.pcui-select-input { + flex: none; + height: 24px; + min-height: 0; + padding: 0; + border-radius: 0; + vertical-align: bottom; + + > .pcui-select-input-container-value { + height: 100%; + background-color: transparent; + } + + .pcui-select-input-value { + height: 22px; + line-height: 22px; + padding: 0 22px 0 8px; + } + + .pcui-select-input-icon { + right: 0; + width: 22px; + height: 22px; + line-height: 22px; + } + } + + > .ui-number-field, + > .pcui-numeric-input { + flex: none; + border-radius: 0; vertical-align: bottom; } @@ -1398,9 +1499,12 @@ strong { vertical-align: bottom; } - > .ui-button { + > .ui-button, + > .pcui-button { @extend .font-icon; + flex: none; + border-radius: 0; vertical-align: bottom; } } @@ -1409,7 +1513,12 @@ strong { > .color-panel { height: 156px; - > .content { + &.pcui-container { + background-color: $bcg-primary; + } + + > .content, + &.pcui-container { > .color-rect { margin: 5px 10px 10px; width: 140px; @@ -1453,14 +1562,26 @@ strong { height: 145px; vertical-align: top; - > .ui-number-field { + > .ui-number-field, + > .pcui-numeric-input { margin: 2px 0; width: 162px; + border-radius: 0; + + > input.field { + line-height: 22px; + } } - > .ui-text-field { + > .ui-text-field, + > .pcui-text-input { margin: 2px 0; width: 162px; + border-radius: 0; + + > input.field { + line-height: 22px; + } } } } @@ -7375,6 +7496,6 @@ strong { } } -@import '../common/ui-common'; +@import '../common/shared-overlays'; @import '../common/version-control'; @import 'playcanvas-theme'; diff --git a/sass/ui/_theme-dark.scss b/sass/editor/_editor-pcui-compat-theme.scss similarity index 96% rename from sass/ui/_theme-dark.scss rename to sass/editor/_editor-pcui-compat-theme.scss index b3d0bb362..a616100bb 100644 --- a/sass/ui/_theme-dark.scss +++ b/sass/editor/_editor-pcui-compat-theme.scss @@ -236,7 +236,8 @@ a:visited { } } -.ui-tooltip { +.ui-tooltip, +.pcui-tooltip { padding: 0; &.flip { @@ -948,30 +949,9 @@ a:visited { } } -.picker-curve { - > .content { - border: 1px solid $bcg-darkest; - background-color: $bcg-dark; - - > .picker-curve-panel { - > .picker-curve-header { - > .content { - .ui-button.picker-curve-toggle { - background-color: $bcg-primary; - color: $text-secondary; - - &:hover { - border-color: $bcg-darker; - } - - &.active { - background-color: $bcg-darker; - } - } - } - } - } - } +.picker-curve > .content { + border: 1px solid $bcg-darkest; + background-color: $bcg-dark; } .picker-gradient { @@ -981,7 +961,8 @@ a:visited { > .picker-gradient-panel { > .color-panel { - > .content { + > .content, + &.pcui-container { > .color-rect, > .hue-rect, > .alpha-rect { diff --git a/sass/ui/_ui.scss b/sass/editor/_editor-pcui-compat.scss similarity index 100% rename from sass/ui/_ui.scss rename to sass/editor/_editor-pcui-compat.scss diff --git a/sass/pcui/_pcui-asset-input.scss b/sass/pcui/_pcui-asset-input.scss index 0bd12cd89..6b1b541ae 100644 --- a/sass/pcui/_pcui-asset-input.scss +++ b/sass/pcui/_pcui-asset-input.scss @@ -51,6 +51,12 @@ box-sizing: border-box; } +.pcui-asset-input.star .pcui-asset-input-controls::before { + content: '*'; + margin: $element-margin; + align-self: flex-start; +} + // asset name field .pcui-asset-input-asset { flex-grow: 1; diff --git a/src/common/pcui/compat-utils.ts b/src/common/pcui/compat-utils.ts new file mode 100644 index 000000000..a68c9aa99 --- /dev/null +++ b/src/common/pcui/compat-utils.ts @@ -0,0 +1,41 @@ +const WILDCARD_ASSET_TYPE = '*'; + +const toOptionValue = (value: any, type = 'string') => { + if (value === '') { + return ''; + } + + if (type === 'number') { + return Number(value); + } + + if (type === 'boolean') { + return value === true || value === 'true'; + } + + return value; +}; + +const toOptions = (options: any = [], type = 'string') => { + const entries = Array.isArray(options) ? options : Object.entries(options).map(([v, t]) => ({ v, t })); + + return entries.map((option: any) => ({ + v: toOptionValue(option.v, type), + t: String(option.t) + })); +}; + +const acceptsAssetDropType = (assetType: string | undefined, type: string) => { + return !assetType || assetType === WILDCARD_ASSET_TYPE || type === `asset.${assetType}`; +}; + +const toLinkedFieldValue = (type: string | undefined, value: any, different: boolean) => { + return type === 'entity' && different ? null : value; +}; + +export { + acceptsAssetDropType, + toLinkedFieldValue, + toOptions, + WILDCARD_ASSET_TYPE +}; diff --git a/src/common/pcui/element/element-asset-input.ts b/src/common/pcui/element/element-asset-input.ts index a305ab7e4..c29def65a 100644 --- a/src/common/pcui/element/element-asset-input.ts +++ b/src/common/pcui/element/element-asset-input.ts @@ -4,6 +4,7 @@ import { Element, ElementArgs, Label, Container, Button, BindingObserversToEleme import { type AssetObserver } from '@/editor-api'; import { AssetThumbnail } from './element-asset-thumbnail'; +import { acceptsAssetDropType } from '../compat-utils'; import { CLASS_MULTIPLE_VALUES } from '../constants'; const CLASS_ASSET_INPUT = 'pcui-asset-input'; @@ -177,7 +178,7 @@ class AssetInput extends Element { ref: this, filter: (type: string, dropData: any) => { if (dropData.id && type.startsWith('asset') && - (!this._assetType || type === `asset.${this._assetType}`) && + acceptsAssetDropType(this._assetType, type) && parseInt(dropData.id, 10) !== this.value) { const asset = this._assets.get(dropData.id); diff --git a/src/common/pcui/element/element-curve-input.ts b/src/common/pcui/element/element-curve-input.ts index 136e410f4..74f380e48 100644 --- a/src/common/pcui/element/element-curve-input.ts +++ b/src/common/pcui/element/element-curve-input.ts @@ -245,12 +245,12 @@ class CurveInput extends Element { let evtPickerChanged = editor.on('picker:curve:change', this._onPickerChange.bind(this)); - let evtRefreshPicker = this.on('change', (value: any) => { + let evtRefreshPicker = this.on('change', () => { const args = Object.assign({ keepZoom: true }, this._pickerArgs); - editor.call('picker:curve:set', value, args); + editor.call('picker:curve:set', this.value, args); }); editor.once('picker:curve:close', () => { @@ -372,15 +372,16 @@ class CurveInput extends Element { return null; } - if (value.keys[0].length !== undefined) { - return value.keys.map((data: number[]) => { - const curve = new Curve(data); + if (Array.isArray(value.keys[0])) { + return (value.keys as number[][]).map((data: number[]) => { + const keys = Array.isArray(data?.[0]) ? data[0] : data; + const curve = new Curve(keys || []); curve.type = value.type; return curve; }); } - const curve = new Curve(value.keys); + const curve = new Curve(value.keys as number[]); curve.type = value.type; return [curve]; } @@ -441,9 +442,10 @@ class CurveInput extends Element { context.lineTo(x * precision, this._clampEdge(height * (1 - (val - minValue) / (maxValue - minValue)), 1, height - 1)); } - if (secondaryCurves) { + const secondaryCurve = secondaryCurves?.[i]; + if (secondaryCurve) { for (let x = Math.floor(width / precision); x >= 0; x--) { - const val = secondaryCurves[i].value(x * precision / width); + const val = secondaryCurve.value(x * precision / width); context.lineTo(x * precision, this._clampEdge(height * (1 - (val - minValue) / (maxValue - minValue)), 1, height - 1)); } @@ -480,7 +482,26 @@ class CurveInput extends Element { // TODO: maybe we should check for equality // but since this value will almost always be set using // the picker it's not worth the effort - this._value = Array.isArray(value) ? deepCopy(value) : [deepCopy(value)]; + const values = Array.isArray(value) ? deepCopy(value) : [deepCopy(value)]; + const first = values[0]; + + if (first?.betweenCurves && first.keys) { + const count = Array.isArray(first.keys[0]) ? first.keys.length : 1; + const second = values[1]; + const secondCount = second?.keys ? (Array.isArray(second.keys[0]) ? second.keys.length : 1) : 0; + + if (secondCount !== count) { + values[1] = secondCount === 1 && count > 1 && !Array.isArray(second.keys[0]) ? { + ...second, + keys: first.keys.map(() => deepCopy(second.keys)) + } : { + type: second?.type ?? first.type, + keys: deepCopy(first.keys) + }; + } + } + + this._value = values; this.class.remove(CLASS_MULTIPLE_VALUES); diff --git a/src/common/tooltips.ts b/src/common/tooltips.ts index 2e8e3da3c..8c0d20930 100644 --- a/src/common/tooltips.ts +++ b/src/common/tooltips.ts @@ -5,6 +5,287 @@ import type { Tooltip } from './pcui/element/element-tooltip.js'; export const tooltip = (): Tooltip => editor.call('layout.tooltip'); +class TooltipHandle extends Container { + arrow: HTMLElement; + + hoverable: boolean; + + x: number; + + y: number; + + private _align: 'top' | 'right' | 'bottom' | 'left'; + + private _removeTarget?: () => void; + + constructor(args: { + class?: string, + hoverable?: boolean, + x?: number, + y?: number, + align?: 'top' | 'right' | 'bottom' | 'left', + hidden?: boolean, + text?: string, + html?: string + } = {}) { + super({ + class: 'pcui-tooltip', + hidden: args.hidden ?? true + }); + + this.class.add('ui-tooltip'); + this.class.add('align-left'); + if (args.class) { + this.class.add(args.class); + } + + this.innerElement = document.createElement('div'); + this.innerElement.classList.add('inner'); + this.dom.appendChild(this.innerElement); + + this.arrow = document.createElement('div'); + this.arrow.classList.add('arrow'); + this.dom.appendChild(this.arrow); + + this.hoverable = args.hoverable || false; + this.x = args.x || 0; + this.y = args.y || 0; + this._align = 'left'; + this.align = args.align || 'left'; + + this.on('show', this._reflow.bind(this)); + if (args.html) { + this.html = args.html; + } else { + this.text = args.text || ''; + } + + this.dom.addEventListener('mouseover', this._handleMouseOver.bind(this), false); + this.dom.addEventListener('mouseleave', this._handleMouseLeave.bind(this), false); + this.on('destroy', () => this.detach()); + } + + set align(value: 'top' | 'right' | 'bottom' | 'left') { + if (this._align === value) { + return; + } + + this.class.remove(`align-${this._align}`); + this._align = value; + this.class.add(`align-${this._align}`); + this._reflow(); + } + + get align() { + return this._align; + } + + set flip(value: boolean) { + this.class.toggle('flip', value); + this._reflow(); + } + + get flip() { + return this.class.contains('flip'); + } + + set text(value: string) { + this.innerElement.textContent = value; + } + + get text() { + return this.innerElement.textContent; + } + + set html(value: string) { + this.innerElement.innerHTML = value; + } + + get html() { + return this.innerElement.innerHTML; + } + + _handleMouseOver(evt: MouseEvent) { + if (!this.hoverable) { + return; + } + + this.hidden = false; + this.emit('hover', evt); + } + + _handleMouseLeave() { + if (!this.hoverable) { + return; + } + + this.hidden = true; + } + + _reflow() { + if (this.hidden) { + return; + } + + this.style.top = ''; + this.style.right = ''; + this.style.bottom = ''; + this.style.left = ''; + this.arrow.style.top = ''; + this.arrow.style.right = ''; + this.arrow.style.bottom = ''; + this.arrow.style.left = ''; + this.style.display = 'block'; + + if (this._align === 'top') { + this.style.top = `${this.y}px`; + if (this.flip) { + this.style.right = `calc(100% - ${this.x}px)`; + } else { + this.style.left = `${this.x}px`; + } + } else if (this._align === 'right') { + this.style.top = `${this.y}px`; + this.style.right = `calc(100% - ${this.x}px)`; + } else if (this._align === 'bottom') { + this.style.bottom = `calc(100% - ${this.y}px)`; + if (this.flip) { + this.style.right = `calc(100% - ${this.x}px)`; + } else { + this.style.left = `${this.x}px`; + } + } else { + this.style.top = `${this.y}px`; + this.style.left = `${this.x}px`; + } + + const rect = this.dom.getBoundingClientRect(); + + if (rect.left < 0) { + this.style.left = '0px'; + this.style.right = ''; + } + if (rect.top < 0) { + this.style.top = '0px'; + this.style.bottom = ''; + } + if (rect.right > window.innerWidth) { + this.style.right = '0px'; + this.style.left = ''; + this.arrow.style.left = `${Math.floor(rect.right - window.innerWidth + 8)}px`; + } + if (rect.bottom > window.innerHeight) { + this.style.bottom = '0px'; + this.style.top = ''; + this.arrow.style.top = `${Math.floor(rect.bottom - window.innerHeight + 8)}px`; + } + + this.style.display = ''; + } + + position(x: number, y: number) { + x = Math.floor(x); + y = Math.floor(y); + + if (this.x === x && this.y === y) { + return; + } + + this.x = x; + this.y = y; + this._reflow(); + } + + attach(target: HTMLElement) { + if (this._removeTarget) { + this.detach(); + } + + const hover = () => { + const rect = target.getBoundingClientRect(); + let off = 16; + + if (this.align === 'top') { + if (rect.width < 64) { + off = rect.width / 2; + } + this.flip = rect.left + off > window.innerWidth / 2; + this.position(this.flip ? rect.right - off : rect.left + off, rect.bottom); + } else if (this.align === 'right') { + if (rect.height < 64) { + off = rect.height / 2; + } + this.flip = false; + this.position(rect.left, rect.top + off); + } else if (this.align === 'bottom') { + if (rect.width < 64) { + off = rect.width / 2; + } + this.flip = rect.left + off > window.innerWidth / 2; + this.position(this.flip ? rect.right - off : rect.left + off, rect.top); + } else { + if (rect.height < 64) { + off = rect.height / 2; + } + this.flip = false; + this.position(rect.right, rect.top + off); + } + + this.hidden = false; + }; + const blur = () => { + this.hidden = true; + }; + + target.addEventListener('mouseover', hover, false); + target.addEventListener('mouseout', blur, false); + this._removeTarget = () => { + target.removeEventListener('mouseover', hover, false); + target.removeEventListener('mouseout', blur, false); + }; + + if (target.matches(':hover')) { + hover(); + } + } + + detach() { + if (!this._removeTarget) { + return; + } + + this._removeTarget(); + this._removeTarget = undefined; + } + + static make(args: { + root?: Container, + text?: string, + html?: string, + align?: 'top' | 'right' | 'bottom' | 'left', + hoverable?: boolean, + class?: string + }) { + const item = new TooltipHandle(args); + (args.root || editor.call('layout.root')).append(item); + return item; + } + + static attach(args: { + root?: Container, + target: HTMLElement, + text?: string, + html?: string, + align?: 'top' | 'right' | 'bottom' | 'left', + hoverable?: boolean, + class?: string + }) { + const item = TooltipHandle.make(args); + item.attach(args.target); + return item; + } +} + /** * Creates a new simple tooltip item * @@ -30,6 +311,8 @@ export const tooltipSimpleItem = ({ return item; }; +export { TooltipHandle }; + /** * Creates a new API reference tooltip item * @@ -102,7 +385,7 @@ export const tooltipOverrideItem = ({ }: { templateRoot: Observer; entities: ObserverList; - override: object; + override: { override_type?: string }; }) => { const title = 'Override'; let subTitle = ''; diff --git a/src/common/ui/button.ts b/src/common/ui/button.ts deleted file mode 100644 index fa258f0bf..000000000 --- a/src/common/ui/button.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { LegacyElement } from './element'; - -class LegacyButton extends LegacyElement { - private _text: string; - - declare protected _element: HTMLDivElement & { ui: any }; - - constructor(args: { text?: string } = {}) { - super(); - this._text = args.text || ''; - - this.element = document.createElement('div'); - this._element.classList.add('ui-button'); - this._element.innerHTML = this._text; - - this._element.ui = this; - this._element.tabIndex = 0; - - this._element.addEventListener('keydown', this._onKeyDown.bind(this), false); - this.on('click', this._onClick.bind(this)); - - this._onLinkChange = (value: any) => { - (this._element as any).value = value; - }; - } - - set text(value: string) { - if (this._text === value) { - return; - } - this._text = value; - this._element.innerHTML = this._text; - } - - get text() { - return this._text; - } - - _onKeyDown(evt: KeyboardEvent) { - if (evt.keyCode === 27) { - return this._element.blur(); - } - - if (evt.keyCode !== 32 || this.disabled) { - return; - } - - evt.stopPropagation(); - evt.preventDefault(); - this.emit('click'); - } - - _onClick() { - this._element.blur(); - } -} - -export { LegacyButton }; diff --git a/src/common/ui/canvas.ts b/src/common/ui/canvas.ts deleted file mode 100644 index 7b498efd4..000000000 --- a/src/common/ui/canvas.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { LegacyElement } from './element'; - -class LegacyCanvas extends LegacyElement { - _width: number; - - _height: number; - - _ratio: number; - - constructor(args: Record = {}) { - super(); - this.element = document.createElement('canvas'); - this._element.classList.add('ui-canvas'); - - if (args.id !== undefined) { - this._element.id = args.id; - } - - if (args.tabindex !== undefined) { - this._element.setAttribute('tabindex', args.tabindex); - } - - this._width = 300; - this._height = 150; - this._ratio = (args.useDevicePixelRatio !== undefined && args.useDevicePixelRatio) ? window.devicePixelRatio : 1; - - this._element.onselectstart = this.onselectstart; - } - - set width(value: number) { - if (this._width === value) { - return; - } - - this._width = value; - const e = this._element as HTMLCanvasElement; - e.width = this.pixelWidth; - this._element.style.width = `${value}px`; - this.emit('resize', this._width, this._height); - } - - get width() { - return this._width; - } - - set height(value: number) { - if (this._height === value) { - return; - } - - this._height = value; - const e = this._element as HTMLCanvasElement; - e.height = this.pixelHeight; - this._element.style.height = `${value}px`; - this.emit('resize', this._width, this._height); - } - - get height() { - return this._height; - } - - get pixelWidth() { - return Math.floor(this._width * this._ratio); - } - - get pixelHeight() { - return Math.floor(this._height * this._ratio); - } - - get pixelRatio() { - return this._ratio; - } - - onselectstart() { - return false; - } - - resize(width: number, height: number) { - if (this._width === width && this._height === height) { - return; - } - - this._width = width; - this._height = height; - const e = this._element as HTMLCanvasElement; - e.width = this.pixelWidth; - e.height = this.pixelHeight; - this._element.style.width = `${width}px`; - this._element.style.height = `${height}px`; - this.emit('resize', width, height); - } -} - -export { LegacyCanvas }; diff --git a/src/common/ui/checkbox.ts b/src/common/ui/checkbox.ts deleted file mode 100644 index c351f9f2a..000000000 --- a/src/common/ui/checkbox.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { LegacyElement } from './element'; - -class LegacyCheckbox extends LegacyElement { - _text: string; - - constructor(args: Record = {}) { - super(); - this._text = args.text || ''; - - this.element = document.createElement('div'); - this._element.classList.add('ui-checkbox', 'noSelect'); - this._element.tabIndex = 0; - - this._element.addEventListener('keydown', this._onKeyDown.bind(this), false); - - this.on('click', this._onClick.bind(this)); - this.on('change', this._onChange.bind(this)); - } - - set value(value: boolean | null) { - if (this._link) { - this._link.set(this.path, value); - } else { - if (this._element.classList.contains('checked') !== value) { - this._onLinkChange(value); - } - } - } - - get value() { - if (this._link) { - return this._link.get(this.path); - } - return this._element.classList.contains('checked'); - } - - _onClick() { - this.value = !this.value; - this._element.blur(); - } - - _onChange() { - if (!this.renderChanges) { - return; - } - this.flash(); - } - - _onKeyDown(evt: KeyboardEvent) { - if (evt.keyCode === 27) { - return this._element.blur(); - } - - if (evt.keyCode !== 32 || this.disabled) { - return; - } - - evt.stopPropagation(); - evt.preventDefault(); - this.value = !this.value; - } - - _onLinkChange(value: boolean | null) { - if (value === null) { - this._element.classList.remove('checked'); - this._element.classList.add('null'); - } else if (value) { - this._element.classList.add('checked'); - this._element.classList.remove('null'); - } else { - this._element.classList.remove('checked', 'null'); - } - this.emit('change', value); - } -} - -export { LegacyCheckbox }; diff --git a/src/common/ui/code.ts b/src/common/ui/code.ts deleted file mode 100644 index cda312e01..000000000 --- a/src/common/ui/code.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { LegacyContainer } from './container'; - -class LegacyCode extends LegacyContainer { - constructor() { - super(); - this.element = document.createElement('pre'); - this._element.classList.add('ui-code'); - } - - set text(value: string) { - this._element.textContent = value; - } - - get text() { - return this._element.textContent; - } -} - -export { LegacyCode }; diff --git a/src/common/ui/color-field.ts b/src/common/ui/color-field.ts deleted file mode 100644 index 45286dddb..000000000 --- a/src/common/ui/color-field.ts +++ /dev/null @@ -1,252 +0,0 @@ -import type { EventHandle } from '@playcanvas/observer'; - -import { LegacyElement } from './element'; - -class LegacyColorField extends LegacyElement { - elementColor: HTMLSpanElement; - - _channels: number; - - _values: number[]; - - evtLinkChannels: EventHandle[]; - - constructor(args: Record = {}) { - super(); - this.element = document.createElement('div'); - this._element.tabIndex = 0; - this._element.classList.add('ui-color-field', 'rgb'); - - this.elementColor = document.createElement('span'); - this.elementColor.classList.add('color'); - this._element.appendChild(this.elementColor); - - this._channels = args.channels || 3; - this._values = [0, 0, 0, 0]; - - this._element.addEventListener('keydown', this._onKeyDown.bind(this), false); - - this.on('change', this._onChange.bind(this)); - this.evtLinkChannels = []; - this.on('link', this._onLink.bind(this)); - this.on('unlink', this._onUnlink.bind(this)); - } - - set value(value: number[] | null) { - if (!value) { - this.class.add('null'); - return; - } - this.class.remove('null'); - - if (this._link) { - this._link.set(this.path, value.map(channel => channel / 255)); - } else { - this._setValue(value); - } - } - - get value() { - if (this._link) { - return this._link.get(this.path).map(channel => Math.floor(channel * 255)); - } - return this._values.slice(0, this._channels); - } - - set channels(value: number) { - if (this._channels === value) { - return; - } - - this._channels = value; - this.emit('channels', this._channels); - } - - get channels() { - return this._channels; - } - - set r(value: number) { - value = Math.min(0, Math.max(255, value)); - - if (this._values[0] === value) { - return; - } - - this._values[0] = value; - this.emit('r', this._values[0]); - this.emit('change', this._values.slice(0, this._channels)); - } - - get r() { - if (this._link) { - return Math.floor(this._link.get(`${this.path}.0`) * 255); - } - return this._values[0]; - } - - set g(value: number) { - value = Math.min(0, Math.max(255, value)); - - if (this._values[1] === value) { - return; - } - - this._values[1] = value; - - if (this._channels >= 2) { - this.emit('g', this._values[1]); - this.emit('change', this._values.slice(0, this._channels)); - } - } - - get g() { - if (this._link) { - return Math.floor(this._link.get(`${this.path}.1`) * 255); - } - return this._values[1]; - } - - set b(value: number) { - value = Math.min(0, Math.max(255, value)); - - if (this._values[2] === value) { - return; - } - - this._values[2] = value; - - if (this._channels >= 3) { - this.emit('b', this._values[2]); - this.emit('change', this._values.slice(0, this._channels)); - } - } - - get b() { - if (this._link) { - return Math.floor(this._link.get(`${this.path}.2`) * 255); - } - return this._values[2]; - } - - set a(value: number) { - value = Math.min(0, Math.max(255, value)); - - if (this._values[3] === value) { - return; - } - - this._values[3] = value; - - if (this._channels >= 4) { - this.emit('a', this._values[3]); - this.emit('change', this._values.slice(0, this._channels)); - } - } - - get a() { - if (this._link) { - return Math.floor(this._link.get(`${this.path}.3`) * 255); - } - return this._values[3]; - } - - set hex(value: string) { - console.log('todo'); - } - - get hex() { - let values = this._values; - - if (this._link) { - values = this._link.get(this.path).map(channel => Math.floor(channel * 255)); - } - - let hex = ''; - for (let i = 0; i < this._channels; i++) { - hex += (`00${values[i].toString(16)}`).slice(-2); - } - return hex; - } - - _onKeyDown(evt: KeyboardEvent) { - if (evt.keyCode === 27) { - return this._element.blur(); - } - - const self = this as LegacyColorField & { ui: { disabled: boolean } }; - if (evt.keyCode !== 13 || self.ui.disabled) { - return; - } - - evt.stopPropagation(); - evt.preventDefault(); - this.emit('click'); - } - - _onChange(color: number[]) { - if (this._channels === 1) { - this.elementColor.style.backgroundColor = `rgb(${[this.r, this.r, this.r].join(',')})`; - } else if (this._channels === 3) { - this.elementColor.style.backgroundColor = `rgb(${this._values.slice(0, 3).join(',')})`; - } else if (this._channels === 4) { - const rgba = this._values.slice(0, 4); - rgba[3] /= 255; - this.elementColor.style.backgroundColor = `rgba(${rgba.join(',')})`; - } else { - console.log('unknown channels', color); - } - } - - _onLink() { - for (let i = 0; i < 4; i++) { - this.evtLinkChannels[i] = this._link.on(`${this.path}.${i}:set`, (value) => { - this._setValue(this._link.get(this.path)); - }); - } - } - - _onUnlink() { - for (let i = 0; i < this.evtLinkChannels.length; i++) { - this.evtLinkChannels[i].unbind(); - } - - this.evtLinkChannels = []; - } - - _onLinkChange(value: number[]) { - if (!value) { - return; - } - - this._setValue(value); - } - - _setValue(value: number[]) { - let changed = false; - - if (!value) { - return; - } - - if (value.length !== this._channels) { - changed = true; - this.channels = value.length; - } - - for (let i = 0; i < this._channels; i++) { - if (this._values[i] === Math.floor(value[i])) { - continue; - } - - changed = true; - this._values[i] = Math.floor(value[i]); - } - - if (changed) { - this.emit('change', this._values.slice(0, this._channels)); - } - } -} - -export { LegacyColorField }; diff --git a/src/common/ui/container.ts b/src/common/ui/container.ts deleted file mode 100644 index 40e025f16..000000000 --- a/src/common/ui/container.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { LegacyElement } from './element'; - -const OBSERVER_OPTIONS = { - childList: true, - attributes: true, - characterData: false, - subtree: true, - attributeOldValue: false, - characterDataOldValue: false -}; - -type FlexStyle = CSSStyleDeclaration & { - WebkitFlexDirection: string; - WebkitFlexWrap: string; - WebkitFlexGrow: string; - WebkitFlexShrink: string; -}; - -class LegacyContainer extends LegacyElement { - protected _observer: MutationObserver; - - protected _observerChanged: boolean; - - protected _observerOptions: Record; - - constructor() { - super(); - this._innerElement = null; - this._observerChanged = false; - this._observerOptions = OBSERVER_OPTIONS; - - const observerTimeout = () => { - this._observerChanged = false; - this.emit('nodesChanged'); - }; - - this._observer = new MutationObserver(() => { - if (this._observerChanged) { - return; - } - - this._observerChanged = true; - setTimeout(observerTimeout, 0); - }); - } - - set innerElement(value: HTMLElement & { ui: unknown }) { - if (this._innerElement) { - this._observer.disconnect(); - } - - this._innerElement = value; - this._observer.observe(this._innerElement, this._observerOptions); - } - - get innerElement() { - return this._innerElement; - } - - set flexible(value: boolean) { - if (this._element.classList.contains('flexible') === !!value) { - return; - } - - if (value) { - this._element.classList.add('flexible'); - } else { - this._element.classList.remove('flexible'); - } - } - - get flexible() { - return this._element.classList.contains('flexible'); - } - - set flex(value: boolean) { - if (this._element.classList.contains('flex') === !!value) { - return; - } - - if (value) { - this._element.classList.add('flex'); - } else { - this._element.classList.remove('flex'); - } - } - - get flex() { - return this._element.classList.contains('flex'); - } - - set flexDirection(value: string) { - this._innerElement.style.flexDirection = value; - (this._innerElement.style as FlexStyle).WebkitFlexDirection = value; - } - - get flexDirection() { - return this._innerElement.style.flexDirection; - } - - set flexWrap(value: string) { - this.flex = true; - this._innerElement.style.flexWrap = value; - (this._innerElement.style as FlexStyle).WebkitFlexWrap = value; - } - - get flexWrap() { - return this._innerElement.style.flexWrap; - } - - set flexGrow(value: boolean | number | string) { - if (value) { - this.flex = true; - } - - this._element.style.flexGrow = value ? '1' : '0'; - (this._element.style as FlexStyle).WebkitFlexGrow = value ? '1' : '0'; - this._innerElement.style.flexGrow = this._element.style.flexGrow; - (this._innerElement.style as FlexStyle).WebkitFlexGrow = this._element.style.flexGrow; - } - - get flexGrow() { - return (this._element.style.flexGrow as unknown) === 1; - } - - set flexShrink(value: boolean | number | string) { - if (value) { - this.flex = true; - } - - this._element.style.flexShrink = value ? '1' : '0'; - (this._element.style as FlexStyle).WebkitFlexShrink = value ? '1' : '0'; - this._innerElement.style.flexShrink = this._element.style.flexShrink; - (this._innerElement.style as FlexStyle).WebkitFlexShrink = this._element.style.flexShrink; - } - - get flexShrink() { - return (this._element.style.flexShrink as unknown) === 1; - } - - set scroll(value: boolean) { - this.class.add('scrollable'); - } - - get scroll() { - return this.class.contains('scrollable'); - } - - append(element: HTMLElement | LegacyElement) { - const html = (element instanceof HTMLElement); - const node = html ? element : element.element; - - this._innerElement.appendChild(node); - - if (!html) { - element.parent = this; - this.emit('append', element); - } - } - - appendBefore(element: HTMLElement | LegacyElement, reference: HTMLElement | LegacyElement) { - const html = (element instanceof HTMLElement); - const node = html ? element : element.element; - - if (reference instanceof LegacyElement) { - reference = reference.element; - } - - this._innerElement.insertBefore(node, reference); - - if (!html) { - element.parent = this; - this.emit('append', element); - } - } - - appendAfter(element: HTMLElement | LegacyElement, reference: HTMLElement | LegacyElement) { - const html = (element instanceof HTMLElement); - const node = html ? element : element.element; - - if (reference instanceof LegacyElement) { - reference = reference.element; - } - - const next = reference.nextSibling; - - if (next) { - this._innerElement.insertBefore(node, next); - } else { - this._innerElement.appendChild(node); - } - - if (!html) { - element.parent = this; - this.emit('append', element); - } - } - - prepend(element: HTMLElement | LegacyElement) { - const first = this._innerElement.firstChild; - const html = (element instanceof HTMLElement); - const node = html ? element : element.element; - - if (first) { - this._innerElement.insertBefore(node, first); - } else { - this._innerElement.appendChild(node); - } - - if (!html) { - element.parent = this; - this.emit('append', element); - } - } - - remove(element: HTMLElement | LegacyElement) { - const html = (element instanceof HTMLElement); - const node = html ? element : element.element; - - if (!node.parentNode || node.parentNode !== this._innerElement) { - return; - } - - this._innerElement.removeChild(node); - - if (!html) { - element.parent = null; - this.emit('remove', element); - } - } - - clear() { - let i, node; - - this._observer.disconnect(); - - i = this._innerElement.childNodes.length; - while (i--) { - node = this._innerElement.childNodes[i]; - - if (!node.ui) { - continue; - } - - node.ui.destroy(); - } - this._innerElement.innerHTML = ''; - - this._observer.observe(this._innerElement, this._observerOptions); - } -} - -export { LegacyContainer }; diff --git a/src/common/ui/curve-field.ts b/src/common/ui/curve-field.ts deleted file mode 100644 index 14fb6dc3f..000000000 --- a/src/common/ui/curve-field.ts +++ /dev/null @@ -1,398 +0,0 @@ -import type { EventHandle, Observer } from '@playcanvas/observer'; -import { Curve, CurveSet } from 'playcanvas'; - -import { LegacyCanvas } from './canvas'; -import { LegacyElement } from './element'; - -class LegacyCurveField extends LegacyElement { - static _canvasContext(canvas: HTMLCanvasElement & { ctx?: CanvasRenderingContext2D | null }) { - canvas.ctx = canvas.ctx || canvas.getContext('2d'); - return canvas.ctx!; - } - - canvas: LegacyCanvas; - - _lineWidth: number; - - checkerboardCanvas: LegacyCanvas; - - checkerboard: CanvasPattern; - - _value: any; - - _paths: string[]; - - _linkSetHandlers: EventHandle[]; - - _resizeInterval: ReturnType | null; - - _name: string; - - curveNames: string[]; - - gradient: boolean; - - min: number; - - max: number; - - constructor(args: Record = {}) { - super(); - this.element = document.createElement('div'); - this._element.classList.add('ui-curve-field'); - this._element.tabIndex = 0; - this._element.addEventListener('keydown', this._onKeyDown.bind(this), false); - - this.canvas = new LegacyCanvas({ useDevicePixelRatio: true }); - this._element.appendChild(this.canvas.element); - this.canvas.on('resize', this._render.bind(this)); - - this._lineWidth = args.lineWidth || 1; - - this.checkerboardCanvas = new LegacyCanvas(); - const size = 17; - const halfSize = size / 2; - this.checkerboardCanvas.width = size; - this.checkerboardCanvas.height = size; - const checkerboardCanvas = this.checkerboardCanvas.element as HTMLCanvasElement; - const ctx = checkerboardCanvas.getContext('2d'); - ctx.fillStyle = '#949a9c'; - ctx.fillRect(0, 0, halfSize, halfSize); - ctx.fillRect(halfSize, halfSize, halfSize, halfSize); - ctx.fillStyle = '#657375'; - ctx.fillRect(halfSize, 0, halfSize, halfSize); - ctx.fillRect(0, halfSize, halfSize, halfSize); - - const previewCanvas = this.canvas.element as HTMLCanvasElement; - this.checkerboard = previewCanvas.getContext('2d').createPattern(this.checkerboardCanvas.element, 'repeat'); - - this._value = null; - this._paths = []; - this._linkSetHandlers = []; - this._resizeInterval = null; - this._name = args.name; - this.curveNames = args.curves; - this.gradient = !!args.gradient; - - this.min = args.min !== undefined ? args.min : (args.verticalValue !== undefined ? -args.verticalValue : 0); - this.max = args.max !== undefined ? args.max : (args.verticalValue !== undefined ? args.verticalValue : 1); - } - - set value(value: any) { - this._setValue(value); - } - - get value() { - return this._value; - } - - _onKeyDown(evt: KeyboardEvent) { - if (evt.keyCode === 27) { - return this._element.blur(); - } - - if (evt.keyCode !== 32 || this.disabled) { - return; - } - - evt.stopPropagation(); - evt.preventDefault(); - this.emit('click'); - } - - _resize(width: number, height: number) { - if (this.canvas.width !== width || this.canvas.height !== height) { - this.canvas.resize(width, height); - this._render(); - } - } - - link(link: Observer, paths: string[]) { - if (this._link) { - this.unlink(); - } - this._link = link; - this._paths = paths; - - this.emit('link', paths); - - if (this._resizeInterval) { - clearInterval(this._resizeInterval); - } - - this._resizeInterval = setInterval(() => { - const rect = this._element.getBoundingClientRect(); - this.canvas.resize(rect.width, rect.height); - }, 1000 / 20); - - if (this._onLinkChange) { - const renderChanges = this.renderChanges; - this.renderChanges = false; - this._linkSetHandlers.push(this._link.on('*:set', (path: string) => { - const paths = this._paths; - const len = paths.length; - for (let i = 0; i < len; i++) { - if (path.indexOf(paths[i]) === 0) { - this._onLinkChange(); - break; - } - } - })); - - this._onLinkChange(); - - this.renderChanges = renderChanges; - } - } - - unlink() { - if (!this._link) { - return; - } - - this.emit('unlink', this._paths); - - this._linkSetHandlers.forEach((handler: EventHandle) => { - handler.unbind(); - }); - - this._linkSetHandlers.length = 0; - - clearInterval(this._resizeInterval); - - this._link = null; - this._value = null; - this._paths.length = 0; - } - - _onLinkChange() { - if (this.suspendEvents) { - return; - } - - const values = []; - - for (let i = 0; i < this._paths.length; i++) { - const value = this._link.get(this._paths[i]); - values.push(value !== undefined ? value : null); - } - - this._setValue(values); - } - - _setValue(value: any) { - this._value = value; - this._render(); - this.emit('change', value); - } - - _render() { - if (this.gradient) { - this._renderGradient(); - } else { - this._renderCurves(); - } - } - - _clampEdge(val: number, min: number, max: number) { - if (val < min && val > min - 2) { - return min; - } - if (val > max && val < max + 2) { - return max; - } - return val; - } - - _renderCurves() { - const canvas = this.canvas.element as HTMLCanvasElement & { ctx?: CanvasRenderingContext2D | null }; - const context = LegacyCurveField._canvasContext(canvas); - const value = this.value; - - const width = this.canvas.pixelWidth; - const height = this.canvas.pixelHeight; - - context.clearRect(0, 0, width, height); - - const curveColors = ['rgb(255, 0, 0)', 'rgb(0, 255, 0)', 'rgb(133, 133, 252)', 'rgb(255, 255, 255)']; - const fillColors = ['rgba(255, 0, 0, 0.5)', 'rgba(0, 255, 0, 0.5)', 'rgba(133, 133, 252, 0.5)', 'rgba(255, 255, 255, 0.5)']; - - const minMax = this._getMinMaxValues(value); - - if (value && value[0]) { - const primaryCurves = this._valueToCurves(value[0]); - - if (!primaryCurves) { - return; - } - - const secondaryCurves = value[0].betweenCurves && value.length > 1 ? this._valueToCurves(value[1]) : null; - - const minValue = minMax[0]; - const maxValue = minMax[1]; - - context.lineWidth = this._lineWidth; - - if (width === 0) { - return; - } - - for (let i = 0; i < primaryCurves.length; i++) { - context.strokeStyle = curveColors[i]; - context.fillStyle = fillColors[i]; - - context.beginPath(); - context.moveTo(0, this._clampEdge(height * (1 - (primaryCurves[i].value(0) - minValue) / (maxValue - minValue)), 1, height - 1)); - - const precision = 1; - - for (let x = 0; x < Math.floor(width / precision); x++) { - const val = primaryCurves[i].value(x * precision / width); - context.lineTo(x * precision, this._clampEdge(height * (1 - (val - minValue) / (maxValue - minValue)), 1, height - 1)); - } - - if (secondaryCurves) { - for (let x = Math.floor(width / precision); x >= 0; x--) { - const val = secondaryCurves[i].value(x * precision / width); - context.lineTo(x * precision, this._clampEdge(height * (1 - (val - minValue) / (maxValue - minValue)), 1, height - 1)); - } - - context.closePath(); - context.fill(); - } - - context.stroke(); - } - } - } - - _renderGradient() { - const canvas = this.canvas.element as HTMLCanvasElement & { ctx?: CanvasRenderingContext2D | null }; - const context = LegacyCurveField._canvasContext(canvas); - const value = this.value && this.value.length ? this.value[0] : null; - - context.fillStyle = this.checkerboard; - context.fillRect(0, 0, canvas.width, canvas.height); - - let swizzle = [0, 1, 2, 3]; - if (this.curveNames && this.curveNames.length === 1) { - if (this.curveNames[0] === 'g') { - swizzle = [1, 0, 2, 3]; - } else if (this.curveNames[0] === 'b') { - swizzle = [2, 1, 0, 3]; - } else if (this.curveNames[0] === 'a') { - swizzle = [3, 1, 2, 0]; - } - } - - if (value && value.keys && value.keys.length) { - const rgb = []; - - const curve = this.curveNames && this.curveNames.length === 1 ? new CurveSet([value.keys]) : new CurveSet(value.keys); - curve.type = value.type; - - const precision = 2; - - const gradient = context.createLinearGradient(0, 0, canvas.width, 0); - - for (let t = precision; t < canvas.width; t += precision) { - curve.value(t / canvas.width, rgb); - - const rgba = `${Math.round((rgb[swizzle[0]] || 0) * 255)},${ - Math.round((rgb[swizzle[1]] || 0) * 255)},${ - Math.round((rgb[swizzle[2]] || 0) * 255)},${ - isNaN(rgb[swizzle[3]]) ? 1 : rgb[swizzle[3]]}`; - - gradient.addColorStop(t / canvas.width, `rgba(${rgba})`); - } - - context.fillStyle = gradient; - context.fillRect(0, 0, canvas.width, canvas.height); - - } else { - context.fillStyle = 'black'; - context.fillRect(0, 0, canvas.width, canvas.height); - } - } - - _getMinMaxValues(curves: any) { - let minValue = Infinity; - let maxValue = -Infinity; - - if (curves) { - if (curves.length === undefined) { - curves = [curves]; - } - - curves.forEach((value: any) => { - if (value && value.keys && value.keys.length) { - if (value.keys[0].length !== undefined) { - value.keys.forEach((data: number[]) => { - for (let i = 1, len = data.length; i < len; i += 2) { - if (data[i] > maxValue) { - maxValue = data[i]; - } - - if (data[i] < minValue) { - minValue = data[i]; - } - } - }); - } else { - for (let i = 1, len = value.keys.length; i < len; i += 2) { - if (value.keys[i] > maxValue) { - maxValue = value.keys[i]; - } - - if (value.keys[i] < minValue) { - minValue = value.keys[i]; - } - } - } - } - }); - } - - if (minValue === Infinity) { - minValue = this.min; - } - - if (maxValue === -Infinity) { - maxValue = this.max; - } - - if (minValue > this.min) { - minValue = this.min; - } - - if (maxValue < this.max) { - maxValue = this.max; - } - - return [minValue, maxValue]; - } - - _valueToCurves(value: any) { - let curves = null; - - if (value && value.keys && value.keys.length) { - curves = []; - let curve; - if (value.keys[0].length !== undefined) { - value.keys.forEach((data: number[], index: number) => { - curve = new Curve(data); - curve.type = value.type; - curves.push(curve); - }); - } else { - curve = new Curve(value.keys); - curve.type = value.type; - curves.push(curve); - } - } - - return curves; - } -} - -export { LegacyCurveField }; diff --git a/src/common/ui/element.ts b/src/common/ui/element.ts deleted file mode 100644 index 361f2fc69..000000000 --- a/src/common/ui/element.ts +++ /dev/null @@ -1,357 +0,0 @@ -import { Events, type EventHandle, type Observer } from '@playcanvas/observer'; - -class LegacyElement extends Events { - protected _parent: LegacyElement | null; - - protected _destroyed: boolean; - - protected _element: (HTMLElement & { ui: unknown }) | null; - - protected _link: Observer | null; - - protected _linkOnSet: EventHandle | null; - - protected _linkOnUnset: EventHandle | null; - - protected _disabled: boolean; - - protected _disabledParent: boolean; - - protected _evtClick: (evt: Event) => void | null; - - protected _evtHover: (evt: Event) => void | null; - - protected _evtBlur: (evt: Event) => void | null; - - protected _parentDestroy: () => void; - - protected _parentDisable: () => void; - - protected _parentEnable: () => void; - - protected _onFlashDelay: () => void; - - protected _evtParentDestroy: EventHandle | null; - - protected _evtParentDisable: EventHandle | null; - - protected _evtParentEnable: EventHandle | null; - - path: string; - - renderChanges: boolean | null; - - disabledClick: boolean; - - protected _innerElement: (HTMLElement & { ui: unknown }) | null; - - constructor() { - super(); - - this._parent = null; - this._destroyed = false; - this._element = null; - this._link = null; - this._linkOnSet = null; - this._linkOnUnset = null; - this.path = ''; - this.renderChanges = null; - this.disabledClick = false; - this._disabled = false; - this._disabledParent = false; - this._innerElement = null; - this._evtClick = null; - - const self = this; - this._parentDestroy = function () { - self.destroy(); - }; - - setTimeout(() => { - if (self.renderChanges === null) { - self.renderChanges = true; - } - }, 0); - - this._parentDisable = function () { - if (self._disabledParent) { - return; - } - - self._disabledParent = true; - - if (!self._disabled) { - self.emit('disable'); - self.class.add('disabled'); - } - }; - - this._parentEnable = function () { - if (!self._disabledParent) { - return; - } - - self._disabledParent = false; - - if (!self._disabled) { - self.emit('enable'); - self.class.remove('disabled'); - } - }; - - this._onFlashDelay = function () { - self.class.remove('flash'); - }; - } - - set element(value: HTMLElement) { - if (this._element) { - return; - } - - this._element = value; - this._element.ui = this; - - const self = this; - this._evtClick = function (evt: Event) { - if (self.disabled && !self.disabledClick) { - return; - } - self.emit('click', evt); - }; - this._element.addEventListener('click', this._evtClick, false); - - this._evtHover = function (evt: Event) { - self.emit('hover', evt); - }; - this._element.addEventListener('mouseover', this._evtHover, false); - - this._evtBlur = function (evt: Event) { - self.emit('blur', evt); - }; - this._element.addEventListener('mouseout', this._evtBlur, false); - - if (!this.innerElement) { - this.innerElement = this._element; - } - } - - get element() { - return this._element; - } - - set innerElement(value: HTMLElement & { ui: unknown }) { - this._innerElement = value; - } - - get innerElement(): (HTMLElement & { ui: unknown }) | null { - return this._innerElement; - } - - set parent(value: LegacyElement | null) { - if (this._parent) { - this._parent = null; - this._evtParentDestroy.unbind(); - this._evtParentDisable.unbind(); - this._evtParentEnable.unbind(); - } - - if (value) { - this._parent = value; - this._evtParentDestroy = this._parent.once('destroy', this._parentDestroy); - this._evtParentDisable = this._parent.on('disable', this._parentDisable); - this._evtParentEnable = this._parent.on('enable', this._parentEnable); - - if (this._disabledParent !== this._parent.disabled) { - this._disabledParent = this._parent.disabled; - - if (this._disabledParent) { - this.class.add('disabled'); - this.emit('disable'); - } else { - this.class.remove('disabled'); - this.emit('enable'); - } - } - } - - this.emit('parent'); - } - - get parent() { - return this._parent; - } - - set disabled(value: boolean) { - if (this._disabled === value) { - return; - } - - this._disabled = !!value; - this.emit((this._disabled || this._disabledParent) ? 'disable' : 'enable'); - - if ((this._disabled || this._disabledParent)) { - this.class.add('disabled'); - } else { - this.class.remove('disabled'); - } - } - - get disabled() { - return this._disabled || this._disabledParent; - } - - get disabledSelf() { - return this._disabled; - } - - set enabled(value: boolean) { - this.disabled = !value; - } - - get enabled() { - return !this._disabled; - } - - set value(value: any) { - if (!this._link) { - return; - } - this._link.set(this.path, value); - } - - get value() { - if (!this._link) { - return null; - } - return this._link.get(this.path); - } - - set hidden(value: boolean) { - if (this._element.classList.contains('hidden') === !!value) { - return; - } - - if (value) { - this._element.classList.add('hidden'); - this.emit('hide'); - } else { - this._element.classList.remove('hidden'); - this.emit('show'); - } - } - - get hidden() { - return this._element.classList.contains('hidden'); - } - - get style() { - return this._element.style; - } - - get class() { - return this._element.classList; - } - - set flexGrow(value: string | number) { - this._element.style.flexGrow = `${value}`; - this._element.style.webkitFlexGrow = `${value}`; - } - - get flexGrow() { - return this._element.style.flexGrow; - } - - set flexShrink(value: string | number) { - this._element.style.flexShrink = `${value}`; - this._element.style.webkitFlexShrink = `${value}`; - } - - get flexShrink() { - return this._element.style.flexShrink; - } - - link(link: Observer, path: string) { - const self = this; - - if (this._link) { - this.unlink(); - } - this._link = link; - this.path = path; - - this.emit('link', path); - - if (this._onLinkChange !== LegacyElement.prototype._onLinkChange) { - const renderChanges = this.renderChanges; - this.renderChanges = false; - this._linkOnSet = this._link.on(`${this.path}:set`, (value) => { - self._onLinkChange(value); - }); - this._linkOnUnset = this._link.on(`${this.path}:unset`, (value) => { - self._onLinkChange(value); - }); - this._onLinkChange(this._link.get(this.path)); - this.renderChanges = renderChanges; - } - } - - unlink() { - if (!this._link) { - return; - } - - this.emit('unlink', this.path); - - if (this._linkOnSet) { - this._linkOnSet.unbind(); - this._linkOnSet = null; - - this._linkOnUnset.unbind(); - this._linkOnUnset = null; - } - - this._link = null; - this.path = ''; - } - - destroy() { - if (this._destroyed) { - return; - } - - this._destroyed = true; - - if (this._parent) { - this._evtParentDestroy.unbind(); - this._evtParentDisable.unbind(); - this._evtParentEnable.unbind(); - this._parent = null; - } - - if (this._element.parentNode) { - this._element.parentNode.removeChild(this._element); - } - - this.unlink(); - - this.emit('destroy'); - - this.unbind(); - } - - protected _onLinkChange(_value: any) { - // overridden by subclasses - } - - flash() { - this.class.add('flash'); - setTimeout(this._onFlashDelay, 200); - } - - get dom() { - return this._element; - } -} - -export { LegacyElement }; diff --git a/src/common/ui/image-field.ts b/src/common/ui/image-field.ts deleted file mode 100644 index 9e659b243..000000000 --- a/src/common/ui/image-field.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { LegacyElement } from './element'; - -class LegacyImageField extends LegacyElement { - elementImage: any; - - _value: string | number | null; - - constructor(args: Record = {}) { - super(); - this.element = document.createElement('div'); - this._element.classList.add('ui-image-field', 'empty'); - - if (args.canvas) { - this.elementImage = document.createElement('canvas'); - this.elementImage.width = 64; - this.elementImage.height = 64; - } else { - this.elementImage = new Image(); - } - - this.elementImage.classList.add('preview'); - this._element.appendChild(this.elementImage); - - this._value = null; - - this._element.removeEventListener('click', this._evtClick); - this._element.addEventListener('click', this._onClick.bind(this), false); - this.on('change', this._onChange.bind(this)); - - this._element.addEventListener('keydown', this._onKeyDown.bind(this), false); - } - - set image(value: string) { - if (this.elementImage.src === value) { - return; - } - - this.elementImage.src = value; - } - - get image() { - return this.elementImage.src; - } - - set empty(value: boolean) { - if (this.class.contains('empty') === !!value) { - return; - } - - if (value) { - this.class.add('empty'); - this.image = ''; - } else { - this.class.remove('empty'); - } - } - - get empty() { - return this.class.contains('empty'); - } - - set value(value: string | number | null) { - value = value && parseInt(value as string, 10) || null; - - if (this._link) { - if (!this._link.set(this.path, value)) { - this._value = this._link.get(this.path); - } - } else { - if (this._value === value && !this.class.contains('null')) { - return; - } - - this._value = value; - this.emit('change', value); - } - } - - get value() { - if (this._link) { - return this._link.get(this.path); - } - return this._value; - } - - _onClick(evt: MouseEvent) { - this.emit('click', evt); - } - - _onChange() { - if (!this.renderChanges) { - return; - } - - this.flash(); - } - - _onKeyDown(evt: KeyboardEvent) { - if (evt.keyCode === 27) { - return this._element.blur(); - } - - if (evt.keyCode !== 32 || this.disabled) { - return; - } - - evt.stopPropagation(); - evt.preventDefault(); - this.emit('pick'); - } - - _onLinkChange(value: string | number | null) { - this._value = value; - this.emit('change', value); - } -} - -export { LegacyImageField }; diff --git a/src/common/ui/label.ts b/src/common/ui/label.ts deleted file mode 100644 index ef95d2813..000000000 --- a/src/common/ui/label.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { LegacyElement } from './element'; - -class LegacyLabel extends LegacyElement { - _text: string; - - _unsafe: boolean; - - constructor(args: Record = {}) { - super(); - this._text = args.text || ''; - this._unsafe = !!args.unsafe; - - this.element = document.createElement('span'); - this._element.classList.add('ui-label'); - - if (this._text) { - this._setText(this._text); - } - - this.on('change', this._onChange.bind(this)); - - if (args.placeholder) { - this.placeholder = args.placeholder; - } - } - - set text(value: string) { - if (this._link) { - if (!this._link.set(this.path, value)) { - value = this._link.get(this.path); - this._setText(value); - } - } else { - if (this._text === value) { - return; - } - - this._text = value; - if (value === undefined || value === null) { - this._text = ''; - } - - this._setText(this._text); - this.emit('change', value); - } - } - - get text() { - if (this._link) { - return this._link.get(this.path); - } - return this._text; - } - - set value(value: string) { - this.text = value; - } - - get value() { - return this.text; - } - - set placeholder(value: string) { - this._element.setAttribute('placeholder', value); - } - - get placeholder() { - return this._element.getAttribute('placeholder'); - } - - _setText(text: string) { - if (this._unsafe) { - this._element.innerHTML = text; - } else { - this._element.textContent = text; - } - } - - _onChange() { - if (!this.renderChanges) { - return; - } - - this.flash(); - } - - _onLinkChange(value: string) { - this.text = value; - this.emit('change', value); - } -} - -export { LegacyLabel }; diff --git a/src/common/ui/list-item.ts b/src/common/ui/list-item.ts deleted file mode 100644 index 098ef986c..000000000 --- a/src/common/ui/list-item.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { LegacyElement } from './element'; - -class LegacyListItem extends LegacyElement { - _text: string; - - _selected: boolean; - - _allowDeselect: boolean; - - elementText: HTMLSpanElement; - - constructor(args: Record = {}) { - super(); - this._text = args.text || ''; - this._selected = args.selected || false; - this._allowDeselect = args.allowDeselect !== undefined ? args.allowDeselect : true; - - this.element = document.createElement('li'); - this._element.classList.add('ui-list-item'); - - this.elementText = document.createElement('span'); - this.elementText.textContent = this._text; - this._element.appendChild(this.elementText); - - this.on('click', this._onClick.bind(this)); - } - - set text(value: string) { - if (this._text === value) { - return; - } - this._text = value; - this.elementText.textContent = this._text; - } - - get text() { - return this._text; - } - - set selected(value: boolean) { - if (this._selected === value) { - return; - } - - this._selected = value; - - if (this._selected) { - this._element.classList.add('selected'); - } else { - this._element.classList.remove('selected'); - } - - this.emit(this.selected ? 'select' : 'deselect'); - - if (this.parent) { - this.parent.emit(this.selected ? 'select' : 'deselect', this); - } - - this.emit('change', this.selected); - } - - get selected() { - return this._selected; - } - - _onClick() { - if (!this.selected) { - this.selected = true; - } else if (this._allowDeselect) { - this.selected = false; - } - } -} - -export { LegacyListItem }; diff --git a/src/common/ui/list.ts b/src/common/ui/list.ts deleted file mode 100644 index af29d0347..000000000 --- a/src/common/ui/list.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { LegacyContainer } from './container'; -import { LegacyListItem } from './list-item'; - -class LegacyList extends LegacyContainer { - static _ctrl: (() => boolean) | null; - - static _shift: (() => boolean) | null; - - _changing: boolean; - - _selected: LegacyListItem[]; - - _selectable: boolean; - - constructor(args: Record = {}) { - super(); - this.element = document.createElement('ul'); - this._element.classList.add('ui-list'); - this.selectable = args.selectable !== undefined ? args.selectable : true; - - this._changing = false; - this._selected = []; - - this.on('select', this._onSelect.bind(this)); - this.on('deselect', this._onDeselect.bind(this)); - this.on('append', this._onAppend.bind(this)); - } - - set selectable(value: boolean) { - if (this._selectable === !!value) { - return; - } - - this._selectable = value; - - if (this._selectable) { - this.class.add('selectable'); - } else { - this.class.remove('selectable'); - } - } - - get selectable() { - return this._selectable; - } - - set selected(value: LegacyListItem[]) { - this._changing = true; - - const items = this.selected; - for (let i = 0; i < items.length; i++) { - if (value.indexOf(items[i]) !== -1) { - continue; - } - - items[i].selected = false; - } - - for (let i = 0; i < value.length; i++) { - value[i].selected = true; - } - - this._changing = false; - } - - get selected() { - return this._selected.slice(0); - } - - _onSelect(item: LegacyListItem) { - const ind = this._selected.indexOf(item); - if (ind === -1) { - this._selected.push(item); - } - - if (this._changing) { - return; - } - - if (LegacyList._ctrl && LegacyList._ctrl()) { - // ... - } else if (LegacyList._shift && LegacyList._shift() && this.selected.length) { - // ... - } else { - this._changing = true; - - const items = this.selected; - - if (items.length > 1) { - for (let i = 0; i < items.length; i++) { - if (items[i] === item) { - continue; - } - - items[i].selected = false; - } - } - - this._changing = false; - } - - this.emit('change'); - } - - _onDeselect(item: LegacyListItem) { - const ind = this._selected.indexOf(item); - if (ind !== -1) { - this._selected.splice(ind, 1); - } - - if (this._changing) { - return; - } - - if (LegacyList._ctrl && LegacyList._ctrl()) { - // ... - } else { - this._changing = true; - - const items = this.selected; - - if (items.length) { - for (let i = 0; i < items.length; i++) { - items[i].selected = false; - } - - item.selected = true; - } - - this._changing = false; - } - - this.emit('change'); - } - - _onAppend(item: LegacyListItem) { - if (!item.selected) { - return; - } - - const ind = this._selected.indexOf(item); - if (ind === -1) { - this._selected.push(item); - } - } - - clear() { - this._selected = []; - super.clear(); - } -} - -export { LegacyList }; diff --git a/src/common/ui/menu-item.ts b/src/common/ui/menu-item.ts deleted file mode 100644 index 48c2f9de3..000000000 --- a/src/common/ui/menu-item.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { LegacyContainer } from './container'; - -class LegacyMenuItem extends LegacyContainer { - _value: string; - - _hasChildren: boolean; - - _clickableSubmenus: boolean; - - elementTitle: HTMLDivElement & { ui: any }; - - elementIcon: HTMLSpanElement | null; - - elementText: HTMLSpanElement; - - _index: Record; - - _container: boolean; - - constructor(args: Record = {}) { - super(); - this._value = args.value || ''; - this._hasChildren = args.hasChildren; - this._clickableSubmenus = args.clickableSubmenus; - - this.element = document.createElement('div'); - this._element.classList.add('ui-menu-item'); - - if (args.className) { - this._element.classList.add(args.className); - } - - this.elementTitle = document.createElement('div'); - this.elementTitle.classList.add('title'); - this.elementTitle.ui = this; - this._element.appendChild(this.elementTitle); - - this.elementIcon = null; - - this.elementText = document.createElement('span'); - this.elementText.classList.add('text'); - this.elementText.textContent = args.text || 'Untitled'; - this.elementTitle.appendChild(this.elementText); - - this.innerElement = document.createElement('div'); - this.innerElement.classList.add('content'); - this._element.appendChild(this.innerElement); - - this._index = {}; - this._container = false; - - this.elementTitle.addEventListener('mouseenter', this._onMouseEnter.bind(this), false); - this.elementTitle.addEventListener('touchstart', this._onTouchStart.bind(this), { passive: true }); - this.elementTitle.addEventListener('touchend', this._onTouchEnd.bind(this), false); - this.elementTitle.addEventListener('click', this._onClick.bind(this), false); - - this.on('over', this._onOver.bind(this)); - this.on('select-propagate', this._onSelectPropagate.bind(this)); - this.on('append', this._onAppend.bind(this)); - - if (args.icon) { - this.icon = args.icon; - } - } - - set value(value: string) { - if (this._value === value) { - return; - } - - const valueOld = this._value; - this._value = value; - this.emit('value', value, valueOld); - } - - get value() { - return this._value; - } - - set text(value: string) { - if (this.elementText.textContent === value) { - return; - } - - this.elementText.textContent = value; - } - - get text() { - return this.elementText.textContent; - } - - set icon(value: string | null) { - if ((!value && !this.elementIcon) || (this.elementIcon && this.elementIcon.textContent === value)) { - return; - } - - if (!value) { - this.elementIcon.parentNode.removeChild(this.elementIcon); - this.elementIcon = null; - } else { - if (!this.elementIcon) { - this.elementIcon = document.createElement('span'); - this.elementIcon.classList.add('icon'); - this.elementTitle.insertBefore(this.elementIcon, this.elementText); - } - - this.elementIcon.innerHTML = value; - } - } - - get icon() { - return this.elementIcon ? this.elementIcon.textContent : null; - } - - _onMouseEnter(evt: MouseEvent) { - evt.stopPropagation(); - evt.preventDefault(); - - this.parent.emit('over', [this._value]); - } - - _onOver(path: string[]) { - if (!this.parent) { - return; - } - - path.splice(0, 0, this._value); - - this.parent.emit('over', path); - } - - _onClick(evt: MouseEvent) { - if (!this.parent || this.disabled) { - return; - } - - this.emit('select', this._value, this._hasChildren, evt); - this.parent.emit('select-propagate', [this._value], this._hasChildren, evt); - - if (!this._clickableSubmenus || !this._hasChildren) { - this.class.remove('hover'); - } - } - - _onTouchStart(evt: TouchEvent) { - if (!this.parent || this.disabled) { - return; - } - - if (!this._container || this.class.contains('hover')) { - this.emit('select', this._value, this._hasChildren, evt); - this.parent.emit('select-propagate', [this._value], this._hasChildren, evt); - this.class.remove('hover'); - } else { - this.parent.emit('over', [this._value]); - } - } - - _onTouchEnd(evt: TouchEvent) { - if (!this.parent || this.disabled) { - return; - } - - evt.preventDefault(); - evt.stopPropagation(); - } - - _onSelectPropagate(path: string[], selectedItemHasChildren: boolean, mouseEvent: MouseEvent) { - if (!this.parent) { - return; - } - - path.splice(0, 0, this._value); - - this.parent.emit('select-propagate', path, selectedItemHasChildren, mouseEvent); - - if (!this._clickableSubmenus || !selectedItemHasChildren) { - this.class.remove('hover'); - } - } - - _onAppend(item: any) { - this._container = true; - this.class.add('container'); - - this._index[item._value] = item; - - item.on('value', (value: any, valueOld: any) => { - delete this._index[valueOld]; - this._index[value] = item; - }); - item.once('destroy', () => { - delete this._index[item._value]; - }); - } -} - -export { LegacyMenuItem }; diff --git a/src/common/ui/menu.ts b/src/common/ui/menu.ts deleted file mode 100644 index 541b3d33a..000000000 --- a/src/common/ui/menu.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { LegacyContainer } from './container'; -import { LegacyMenuItem } from './menu-item'; - -class LegacyMenu extends LegacyContainer { - elementOverlay: HTMLDivElement & { ui: any }; - - _index: Record; - - _hovered: string[]; - - _clickableSubmenus: boolean; - - constructor(args: Record = {}) { - super(); - this.element = document.createElement('div'); - this._element.tabIndex = 1; - this._element.classList.add('ui-menu'); - this._element.addEventListener('keydown', this._onKeyDown.bind(this), false); - - this.elementOverlay = document.createElement('div'); - this.elementOverlay.ui = this; - this.elementOverlay.classList.add('overlay'); - this.elementOverlay.addEventListener('click', this._onClick.bind(this), false); - this.elementOverlay.addEventListener('contextmenu', this._onContextMenu.bind(this), false); - this._element.appendChild(this.elementOverlay); - - this.innerElement = document.createElement('div'); - this.innerElement.classList.add('inner'); - this._element.appendChild(this.innerElement); - - this._index = {}; - this._hovered = []; - this._clickableSubmenus = args.clickableSubmenus; - - this.on('select-propagate', this._onSelectPropagate.bind(this)); - this.on('append', this._onAppend.bind(this)); - this.on('over', this._onOver.bind(this)); - this.on('open', this._onOpen.bind(this)); - } - - set open(value: boolean) { - if (this.class.contains('open') === !!value) { - return; - } - - if (value) { - this.class.add('open'); - this._element.focus(); - } else { - this.class.remove('open'); - } - - this.emit('open', !!value); - } - - get open() { - return this.class.contains('open'); - } - - _onClick() { - this.open = false; - } - - _onContextMenu() { - this.open = false; - } - - _onKeyDown(evt: KeyboardEvent) { - if (this.open && evt.keyCode === 27) { - this.open = false; - } - } - - _onSelectPropagate(path: string[], selectedItemHasChildren: boolean, mouseEvent: MouseEvent) { - if (this._clickableSubmenus && selectedItemHasChildren) { - this._updatePath(path); - } else { - this.open = false; - this.emit(`${path.join('.')}:select`, path, mouseEvent); - this.emit('select', path, mouseEvent); - } - } - - _onAppend(item: any) { - this._index[item._value] = item; - - item.on('value', (value: any, valueOld: any) => { - delete this._index[valueOld]; - this._index[value] = item; - }); - item.once('destroy', () => { - delete this._index[item._value]; - }); - } - - _onOver(path: string[]) { - this._updatePath(path); - } - - _onOpen(state: boolean) { - if (state) { - return; - } - this._updatePath([]); - } - - findByPath(path: string | string[]) { - if (!(path instanceof Array)) { - path = path.split('.'); - } - - let item = this; - - for (let i = 0; i < path.length; i++) { - item = item._index[path[i]]; - if (!item) { - return null; - } - } - - return item; - } - - _updatePath(path: string[]) { - let node = this; - - for (let i = 0; i < this._hovered.length; i++) { - node = node._index[this._hovered[i]]; - if (!node) { - break; - } - if (path.length <= i || path[i] !== this._hovered[i]) { - node.class.remove('hover'); - node.innerElement.style.top = ''; - node.innerElement.style.left = ''; - node.innerElement.style.right = ''; - } - } - - this._hovered = path; - node = this; - - for (let i = 0; i < this._hovered.length; i++) { - node = node._index[this._hovered[i]]; - - if (!node) { - break; - } - - node.class.add('hover'); - node.innerElement.style.top = ''; - node.innerElement.style.left = ''; - node.innerElement.style.right = ''; - - const rect = node.innerElement.getBoundingClientRect(); - - if (rect.bottom > window.innerHeight) { - node.innerElement.style.top = `${-(rect.bottom - window.innerHeight)}px`; - } - if (rect.right > window.innerWidth) { - node.innerElement.style.left = 'auto'; - node.innerElement.style.right = `${node.parent.innerElement.clientWidth}px`; - } - } - } - - position(x: number, y: number) { - this._element.style.display = 'block'; - - const rect = this.innerElement.getBoundingClientRect(); - - let left = (x || 0); - let top = (y || 0); - - if (top + rect.height > window.innerHeight) { - top = window.innerHeight - rect.height; - } else if (top < 0) { - top = 0; - } - if (left + rect.width > window.innerWidth) { - left = window.innerWidth - rect.width; - } else if (left < 0) { - left = 0; - } - - this.innerElement.style.left = `${left}px`; - this.innerElement.style.top = `${top}px`; - - this._element.style.display = ''; - } - - createItem(key: string, data: Record) { - const item = new LegacyMenuItem({ - text: data.title || key, - className: data.className || null, - value: key, - icon: data.icon, - hasChildren: !!(data.items && Object.keys(data.items).length > 0), - clickableSubmenus: this._clickableSubmenus - }); - - if (data.select) { - item.on('select', data.select); - } - - if (data.filter) { - this.on('open', () => { - item.enabled = data.filter(); - }); - } - - if (data.hide) { - this.on('open', () => { - item.hidden = data.hide(); - }); - } - - return item; - } - - static fromData(data: Record, args: Record) { - const menu = new LegacyMenu(args); - - const listItems = function (data: Record, parent: LegacyMenu | LegacyMenuItem) { - for (const key in data) { - const item = menu.createItem(key, data[key]); - parent.append(item); - - if (data[key].items) { - listItems(data[key].items, item); - } - } - }; - - listItems(data, menu); - - return menu; - } -} - -export { LegacyMenu }; diff --git a/src/common/ui/number-field.ts b/src/common/ui/number-field.ts deleted file mode 100644 index 0a5d1cfb6..000000000 --- a/src/common/ui/number-field.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { LegacyElement } from './element'; - -class LegacyNumberField extends LegacyElement { - precision: number | null; - - step: number; - - max: number | null; - - min: number | null; - - allowNull: boolean; - - elementInput: HTMLInputElement & { ui: unknown }; - - blurOnEnter: boolean; - - refocusable: boolean; - - _lastValue: number | null; - - _mouseMove: ((evt: MouseEvent) => void) | null; - - _dragging: boolean; - - _dragDiff: number; - - _dragStart: number; - - constructor(args: Record = {}) { - super(); - this.precision = (args.precision != null) ? args.precision : null; - this.step = (args.step != null) ? args.step : ((args.precision != null) ? 1 / Math.pow(10, args.precision) : 1); - - this.max = (args.max !== null) ? args.max : null; - this.min = (args.min !== null) ? args.min : null; - - this.allowNull = !!args.allowNull; - - this.element = document.createElement('div'); - this._element.classList.add('ui-number-field'); - - this.elementInput = document.createElement('input'); - this.elementInput.ui = this; - this.elementInput.tabIndex = 0; - this.elementInput.classList.add('field'); - this.elementInput.type = 'text'; - this.elementInput.addEventListener('focus', this._onInputFocus.bind(this), false); - this.elementInput.addEventListener('blur', this._onInputBlur.bind(this), false); - this.elementInput.addEventListener('keydown', this._onKeyDown.bind(this), false); - this.elementInput.addEventListener('dblclick', this._onFullSelect.bind(this), false); - this.elementInput.addEventListener('contextmenu', this._onFullSelect.bind(this), false); - this._element.appendChild(this.elementInput); - - if (args.default !== undefined) { - this.value = args.default; - } - - this.elementInput.addEventListener('change', this._onChange.bind(this), false); - - this.blurOnEnter = true; - this.refocusable = true; - - this._lastValue = this.value; - this._mouseMove = null; - this._dragging = false; - this._dragDiff = 0; - this._dragStart = 0; - - this.on('disable', this._onDisable.bind(this)); - this.on('enable', this._onEnable.bind(this)); - this.on('change', this._onChangeField.bind(this)); - - if (args.placeholder) { - this.placeholder = args.placeholder; - } - } - - set value(value: number | null) { - if (this._link) { - if (!this._link.set(this.path, value)) { - this.elementInput.value = this._link.get(this.path); - } - } else { - if (value !== null) { - if (this.max !== null && this.max < value) { - value = this.max; - } - - if (this.min !== null && this.min > value) { - value = this.min; - } - } - - value = (value !== null && value !== undefined && (this.precision !== null) ? parseFloat(value.toFixed(this.precision)) : value); - if (value === undefined) { - value = null; - } - - const different = this._lastValue !== value; - - this._lastValue = value; - this.elementInput.value = `${value}`; - - if (different) { - this.emit('change', value); - } - } - } - - get value() { - if (this._link) { - return this._link.get(this.path); - } - return this.elementInput.value !== '' ? parseFloat(this.elementInput.value) : null; - } - - set placeholder(value: string) { - if (!value) { - this._element.removeAttribute('placeholder'); - } else { - this._element.setAttribute('placeholder', value); - } - } - - get placeholder() { - return this._element.getAttribute('placeholder'); - } - - set proxy(value: string) { - if (!value) { - this._element.removeAttribute('proxy'); - } else { - this._element.setAttribute('proxy', value); - } - } - - get proxy() { - return this._element.getAttribute('proxy'); - } - - _onLinkChange(value: number | null) { - this.elementInput.value = `${value || 0}`; - this.emit('change', value || 0); - } - - _onChange() { - const value = parseFloat(this.elementInput.value); - if (isNaN(value)) { - if (this.allowNull) { - this.value = null; - } else { - this.elementInput.value = '0'; - this.value = 0; - } - } else { - this.elementInput.value = `${value}`; - this.value = value; - } - } - - focus(select: boolean) { - this.elementInput.focus(); - if (select) { - this.elementInput.select(); - } - } - - _onInputFocus() { - this.class.add('focus'); - } - - _onInputBlur() { - this.class.remove('focus'); - } - - _onKeyDown(evt: KeyboardEvent) { - if (evt.keyCode === 27) { - return this.elementInput.blur(); - } - - if (this.blurOnEnter && evt.keyCode === 13) { - let focused = false; - - let parent: LegacyElement | null = this.parent; - while (parent) { - if (parent.focus) { - parent.focus(); - focused = true; - break; - } - - parent = parent.parent; - } - - if (!focused) { - this.elementInput.blur(); - } - - return; - } - - if (this.disabled || [38, 40].indexOf(evt.keyCode) === -1) { - return; - } - - let inc = evt.keyCode === 40 ? -1 : 1; - - if (evt.shiftKey) { - inc *= 10; - } - - let value = this.value + (this.step || 1) * inc; - - if (this.max != null) { - value = Math.min(this.max, value); - } - - if (this.min != null) { - value = Math.max(this.min, value); - } - - if (this.precision != null) { - value = parseFloat(value.toFixed(this.precision)); - } - - this.value = value; - this.value = value; - } - - _onFullSelect() { - this.elementInput.select(); - } - - _onDisable() { - this.elementInput.readOnly = true; - } - - _onEnable() { - this.elementInput.readOnly = false; - } - - _onChangeField() { - if (!this.renderChanges) { - return; - } - - this.flash(); - } -} - -export { LegacyNumberField }; diff --git a/src/common/ui/overlay.ts b/src/common/ui/overlay.ts deleted file mode 100644 index 19bba42e6..000000000 --- a/src/common/ui/overlay.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { LegacyContainer } from './container'; - -class LegacyOverlay extends LegacyContainer { - elementOverlay: HTMLDivElement & { ui: any }; - - _closeCallback: (() => boolean) | null; - - constructor(args: Record = {}) { - super(); - this.element = document.createElement('div'); - this._element.classList.add('ui-overlay', 'center'); - - this.elementOverlay = document.createElement('div'); - this.elementOverlay.ui = this; - this.elementOverlay.classList.add('overlay', 'clickable'); - this._element.appendChild(this.elementOverlay); - - this.elementOverlay.addEventListener('mousedown', this._onMouseDown.bind(this), false); - - this.innerElement = document.createElement('div'); - this.innerElement.classList.add('content'); - this._element.appendChild(this.innerElement); - } - - set center(value: boolean) { - if (value) { - this._element.classList.add('center'); - this.innerElement.style.left = ''; - this.innerElement.style.top = ''; - } else { - this._element.classList.remove('center'); - } - } - - get center() { - return this._element.classList.contains('center'); - } - - set transparent(value: boolean) { - if (value) { - this._element.classList.add('transparent'); - } else { - this._element.classList.remove('transparent'); - } - } - - get transparent() { - return this._element.classList.contains('transparent'); - } - - set clickable(value: boolean) { - if (value) { - this.elementOverlay.classList.add('clickable'); - } else { - this.elementOverlay.classList.remove('clickable'); - } - } - - get clickable() { - return this.elementOverlay.classList.contains('clickable'); - } - - get rect() { - return this.innerElement.getBoundingClientRect(); - } - - setCloseCallback(callback: () => boolean) { - this._closeCallback = callback; - } - - _onMouseDown(evt: MouseEvent) { - if (this._closeCallback && !this._closeCallback()) { - return false; - } - - if (!this.clickable) { - return false; - } - - document.body.blur(); - - requestAnimationFrame(() => { - this.hidden = true; - }); - - evt.preventDefault(); - } - - position(x: number, y: number) { - const area = this.elementOverlay.getBoundingClientRect(); - const rect = this.innerElement.getBoundingClientRect(); - - x = Math.max(0, Math.min(area.width - rect.width, x)); - y = Math.max(0, Math.min(area.height - rect.height, y)); - - this.innerElement.style.left = `${x}px`; - this.innerElement.style.top = `${y}px`; - } -} - -export { LegacyOverlay }; diff --git a/src/common/ui/panel.ts b/src/common/ui/panel.ts deleted file mode 100644 index e6220d1ab..000000000 --- a/src/common/ui/panel.ts +++ /dev/null @@ -1,406 +0,0 @@ -import { LegacyContainer } from './container'; - -class LegacyPanel extends LegacyContainer { - headerElement: HTMLElement | null; - - headerElementTitle: HTMLSpanElement | null; - - _handle: string | null; - - _handleElement: (HTMLDivElement & { ui: unknown }) | null; - - _resizeTouchId: number | null; - - _resizeData: { x: number; y: number; width: number; height: number } | null; - - _resizeLimits: { min: number; max: number }; - - headerSize: number; - - _resizeEvtMove: (evt: MouseEvent) => void; - - _resizeEvtEnd: (evt: MouseEvent) => void; - - _resizeEvtTouchMove: (evt: TouchEvent) => void; - - _resizeEvtTouchEnd: (evt: TouchEvent) => void; - - constructor(header?: string) { - super(); - this.element = document.createElement('div'); - this._element.classList.add('ui-panel', 'noHeader', 'noAnimation'); - - this.headerElement = null; - this.headerElementTitle = null; - - if (header) { - this.header = header; - } - - this.on('nodesChanged', this._onNodesChanged.bind(this)); - - this.innerElement = document.createElement('div') as HTMLDivElement & { ui: LegacyPanel }; - this.innerElement.ui = this; - this.innerElement.classList.add('content'); - this._element.appendChild(this.innerElement); - - this.innerElement.addEventListener('scroll', this._onScroll.bind(this), false); - - this._resizeEvtMove = (evt: MouseEvent) => { - evt.preventDefault(); - evt.stopPropagation(); - this._resizeMove(evt.clientX, evt.clientY); - }; - - this._resizeEvtEnd = (evt: MouseEvent) => { - evt.preventDefault(); - evt.stopPropagation(); - this._resizeEnd(); - }; - - this._resizeEvtTouchMove = (evt: TouchEvent) => { - for (let i = 0; i < evt.changedTouches.length; i++) { - const touch = evt.changedTouches[i]; - - if (touch.identifier !== this._resizeTouchId) { - continue; - } - - evt.preventDefault(); - evt.stopPropagation(); - this._resizeMove(touch.clientX, touch.clientY); - - return; - } - }; - - this._resizeEvtTouchEnd = (evt: TouchEvent) => { - for (let i = 0; i < evt.changedTouches.length; i++) { - const touch = evt.changedTouches[i]; - - if (touch.identifier !== this._resizeTouchId) { - continue; - } - - this._resizeTouchId = null; - - evt.preventDefault(); - evt.stopPropagation(); - this._resizeEnd(); - - return; - } - }; - - requestAnimationFrame(() => { - requestAnimationFrame(() => { - this.class.remove('noAnimation'); - }); - }); - - this.on('parent', this._onParent.bind(this)); - - this._handleElement = null; - this._handle = null; - this._resizeTouchId = null; - this._resizeData = null; - this._resizeLimits = { - min: 0, - max: Infinity - }; - - this.headerSize = 0; - } - - set header(value: string) { - if (!this.headerElement && value) { - this.headerElement = document.createElement('header'); - this.headerElement.classList.add('ui-header'); - - this.headerElementTitle = document.createElement('span'); - this.headerElementTitle.classList.add('title'); - this.headerElementTitle.textContent = value; - this.headerElement.appendChild(this.headerElementTitle); - - const first = this._element.firstChild; - if (first) { - this._element.insertBefore(this.headerElement, first); - } else { - this._element.appendChild(this.headerElement); - } - - this.class.remove('noHeader'); - - this.headerElement.addEventListener('click', (evt) => { - if (!this.foldable || (evt.target !== this.headerElement && evt.target !== this.headerElementTitle)) { - return; - } - - this.folded = !this.folded; - }, false); - } else if (!value && this.headerElement) { - this.headerElement.parentNode.removeChild(this.headerElement); - this.headerElement = null; - this.headerElementTitle = null; - this.class.add('noHeader'); - } else { - this.headerElementTitle.textContent = value || ''; - this.class.remove('noHeader'); - } - } - - get header() { - return (this.headerElement && this.headerElementTitle.textContent) || ''; - } - - set foldable(value: boolean) { - if (value) { - this.class.add('foldable'); - - if (this.class.contains('folded')) { - this.emit('fold'); - } - } else { - this.class.remove('foldable'); - - if (this.class.contains('folded')) { - this.emit('unfold'); - } - } - - this._reflow(); - } - - get foldable() { - return this.class.contains('foldable'); - } - - set folded(value: boolean) { - if (this.hidden) { - return; - } - - if (this.class.contains('folded') === !!value) { - return; - } - - if (this.headerElement && this.headerSize === 0) { - this.headerSize = this.headerElement.clientHeight; - } - - if (value) { - this.class.add('folded'); - - if (this.class.contains('foldable')) { - this.emit('fold'); - } - } else { - this.class.remove('folded'); - - if (this.class.contains('foldable')) { - this.emit('unfold'); - } - } - - this._reflow(); - } - - get folded() { - return this.class.contains('foldable') && this.class.contains('folded'); - } - - set horizontal(value: boolean) { - if (value) { - this.class.add('horizontal'); - } else { - this.class.remove('horizontal'); - } - this._reflow(); - } - - get horizontal() { - return this.class.contains('horizontal'); - } - - set resizable(value: string | null) { - if (this._handle === value) { - return; - } - - const oldHandle = this._handle; - this._handle = value; - - if (this._handle) { - if (!this._handleElement) { - this._handleElement = document.createElement('div'); - this._handleElement.ui = this; - this._handleElement.classList.add('handle'); - this._handleElement.addEventListener('mousedown', this._resizeStart.bind(this), false); - this._handleElement.addEventListener('touchstart', this._resizeStart.bind(this), { passive: false }); - } - - if (this._handleElement.parentNode) { - this._element.removeChild(this._handleElement); - } - this._element.appendChild(this._handleElement); - this.class.add('resizable', `resizable-${this._handle}`); - } else { - this._element.removeChild(this._handleElement); - this.class.remove('resizable', `resizable-${oldHandle}`); - } - - this._reflow(); - } - - get resizable() { - return this._handle; - } - - set resizeMin(value: number) { - this._resizeLimits.min = Math.max(0, Math.min(this._resizeLimits.max, value)); - } - - get resizeMin() { - return this._resizeLimits.min; - } - - set resizeMax(value: number) { - this._resizeLimits.max = Math.max(this._resizeLimits.min, value); - } - - get resizeMax() { - return this._resizeLimits.max; - } - - headerAppend(element: HTMLElement | { element: HTMLElement; parent: any }) { - if (!this.headerElement) { - return; - } - - const html = (element instanceof HTMLElement); - const node = html ? element : element.element; - - this.headerElement.insertBefore(node, this.headerElementTitle); - - if (!html) { - element.parent = this; - } - } - - _reflow() { - if (this.hidden) { - return; - } - - if (this.folded) { - if (this.horizontal) { - this.style.height = ''; - this.style.width = `${this.headerSize || 32}px`; - } else { - this.style.height = `${this.headerSize || 32}px`; - } - } else if (this.foldable) { - if (this.horizontal) { - this.style.height = ''; - this.style.width = `${this._innerElement.clientWidth}px`; - } else { - this.style.height = `${(this.headerSize || 32) + this._innerElement.clientHeight}px`; - } - } - } - - _onNodesChanged() { - if (!this.foldable || this.folded || this.horizontal || this.hidden) { - return; - } - - this.style.height = `${Math.max(0, (this.headerSize || 32)) + this.innerElement.clientHeight}px`; - } - - _onParent() { - setTimeout(this._reflow.bind(this)); - } - - _onScroll(evt: Event) { - this.emit('scroll', evt); - } - - _resizeStart(evt: MouseEvent | TouchEvent) { - if (!this._handle) { - return; - } - - if ((evt as TouchEvent).changedTouches) { - for (let i = 0; i < (evt as TouchEvent).changedTouches.length; i++) { - const touch = (evt as TouchEvent).changedTouches[i]; - if ((touch.target as unknown) !== this) { - continue; - } - - this._resizeTouchId = touch.identifier; - } - } - - this.class.add('noAnimation', 'resizing'); - this._resizeData = null; - - window.addEventListener('mousemove', this._resizeEvtMove, false); - window.addEventListener('mouseup', this._resizeEvtEnd, false); - - window.addEventListener('touchmove', this._resizeEvtTouchMove, false); - window.addEventListener('touchend', this._resizeEvtTouchEnd, false); - - evt.preventDefault(); - evt.stopPropagation(); - } - - _resizeMove(x: number, y: number) { - if (!this._resizeData) { - this._resizeData = { - x: x, - y: y, - width: this._innerElement.clientWidth, - height: this._innerElement.clientHeight - }; - } else { - if (this._handle === 'left' || this._handle === 'right') { - let offsetX = this._resizeData.x - x; - - if (this._handle === 'right') { - offsetX = -offsetX; - } - - const width = Math.max(this._resizeLimits.min, Math.min(this._resizeLimits.max, (this._resizeData.width + offsetX))); - - this.style.width = `${width + 4}px`; - this._innerElement.style.width = `${width + 4}px`; - } else { - let offsetY = this._resizeData.y - y; - - if (this._handle === 'bottom') { - offsetY = -offsetY; - } - - const height = Math.max(this._resizeLimits.min, Math.min(this._resizeLimits.max, (this._resizeData.height + offsetY))); - - this.style.height = `${height + (this.headerSize === -1 ? 0 : this.headerSize || 32)}px`; - this._innerElement.style.height = `${height}px`; - } - } - - this.emit('resize'); - } - - _resizeEnd(evt?: Event) { - window.removeEventListener('mousemove', this._resizeEvtMove, false); - window.removeEventListener('mouseup', this._resizeEvtEnd, false); - - window.removeEventListener('touchmove', this._resizeEvtTouchMove, false); - window.removeEventListener('touchend', this._resizeEvtTouchEnd, false); - - this.class.remove('noAnimation', 'resizing'); - this._resizeData = null; - } -} - -export { LegacyPanel }; diff --git a/src/common/ui/progress.ts b/src/common/ui/progress.ts deleted file mode 100644 index 82cc8885b..000000000 --- a/src/common/ui/progress.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { LegacyElement } from './element'; - -class LegacyProgress extends LegacyElement { - _progress: number; - - _targetProgress: number; - - _lastProgress: number; - - _inner: HTMLDivElement; - - _speed: number; - - _now: number; - - _animating: boolean; - - _failed: boolean; - - _animateHandler: () => void; - - constructor(args: Record = {}) { - super(); - this._progress = args.progress ? Math.max(0, Math.min(1, args.progress)) : 0; - this._targetProgress = this._progress; - this._lastProgress = Math.floor(this._progress * 100); - - this.element = document.createElement('div'); - this._element.classList.add('ui-progress'); - - this._inner = document.createElement('div'); - this._inner.classList.add('inner'); - this._inner.style.width = `${this._progress * 100}%`; - this._element.appendChild(this._inner); - - this._speed = args.speed || 1; - this._now = Date.now(); - this._animating = false; - this._failed = false; - - this._animateHandler = this._animate.bind(this); - } - - set progress(value: number) { - value = Math.max(0, Math.min(1, value)); - - if (this._targetProgress === value) { - return; - } - - this._targetProgress = value; - - if (this._speed === 0 || this._speed === 1) { - this._progress = this._targetProgress; - this._inner.style.width = `${this._progress * 100}%`; - - const progress = Math.max(0, Math.min(100, Math.round(this._progress * 100))); - if (progress !== this._lastProgress) { - this._lastProgress = progress; - this.emit(`progress:${progress}`); - this.emit('progress', progress); - } - } else if (!this._animating) { - requestAnimationFrame(this._animateHandler); - } - } - - get progress() { - return this._progress; - } - - set speed(value: number) { - this._speed = Math.max(0, Math.min(1, value)); - } - - get speed() { - return this._speed; - } - - set failed(value: boolean) { - this._failed = !!value; - - if (this._failed) { - this.class.add('failed'); - } else { - this.class.remove('failed'); - } - } - - get failed() { - return this._failed; - } - - _animate() { - if (Math.abs(this._targetProgress - this._progress) < 0.01) { - this._progress = this._targetProgress; - this._animating = false; - } else { - if (!this._animating) { - this._now = Date.now() - (1000 / 60); - this._animating = true; - } - requestAnimationFrame(this._animateHandler); - - const dt = Math.max(0.1, Math.min(3, (Date.now() - this._now) / (1000 / 60))); - this._now = Date.now(); - this._progress += ((this._targetProgress - this._progress) * (this._speed * dt)); - } - - const progress = Math.max(0, Math.min(100, Math.round(this._progress * 100))); - if (progress !== this._lastProgress) { - this._lastProgress = progress; - this.emit(`progress:${progress}`); - this.emit('progress', progress); - } - - this._inner.style.width = `${this._progress * 100}%`; - } -} - -export { LegacyProgress }; diff --git a/src/common/ui/select-field.ts b/src/common/ui/select-field.ts deleted file mode 100644 index 274c2d69d..000000000 --- a/src/common/ui/select-field.ts +++ /dev/null @@ -1,471 +0,0 @@ -import { LegacyElement } from './element'; - -type SelectOptionElement = HTMLLIElement & { - uiElement: LegacySelectField; - uiValue: string; -}; - -class LegacySelectField extends LegacyElement { - options: Record; - - optionsKeys: string[]; - - elementValue: HTMLDivElement & { ui?: unknown }; - - elementOptions: HTMLUListElement; - - optionElements: Record; - - _oldValue: any; - - _value: any; - - _type: string; - - _optionClassNamePrefix: string | null; - - timerClickAway: ReturnType | null; - - evtTouchId: number | null; - - evtTouchSecond: boolean; - - evtMouseDist: number[]; - - evtMouseUp: (evt: MouseEvent) => void; - - evtTouchEnd: (evt: TouchEvent) => void; - - constructor(args: Record = {}) { - super(); - this.options = args.options || {}; - this.optionsKeys = []; - if (this.options instanceof Array) { - const options = {}; - for (let i = 0; i < this.options.length; i++) { - this.optionsKeys.push(this.options[i].v); - options[this.options[i].v] = this.options[i].t; - } - this.options = options; - } else { - this.optionsKeys = Object.keys(this.options); - } - - this.element = document.createElement('div'); - this._element.tabIndex = 0; - this._element.classList.add('ui-select-field', 'noSelect'); - - this.elementValue = document.createElement('div'); - this.elementValue.ui = this; - this.elementValue.classList.add('value'); - this._element.appendChild(this.elementValue); - - this._oldValue = null; - this._value = null; - this._type = args.type || 'string'; - - this._optionClassNamePrefix = args.optionClassNamePrefix || null; - - this.timerClickAway = null; - this.evtTouchId = null; - this.evtTouchSecond = false; - this.evtMouseDist = [0, 0]; - this.evtMouseUp = (evt: MouseEvent) => { - evt.preventDefault(); - evt.stopPropagation(); - this._onHoldSelect(evt.target as SelectOptionElement | null, evt.pageX, evt.pageY); - }; - - this.evtTouchEnd = (evt: TouchEvent) => { - for (let i = 0; i < evt.changedTouches.length; i++) { - const touch = evt.changedTouches[i]; - if (touch.identifier !== this.evtTouchId) { - continue; - } - - this.evtTouchId = null; - - evt.preventDefault(); - evt.stopPropagation(); - - const target = document.elementFromPoint(touch.pageX, touch.pageY) as SelectOptionElement | null; - - this._onHoldSelect(target, touch.pageX, touch.pageY); - } - - if (this.evtTouchSecond) { - evt.preventDefault(); - evt.stopPropagation(); - this.close(); - } else if (this._element.classList.contains('active')) { - this.evtTouchSecond = true; - } - }; - - this.elementValue.addEventListener('mousedown', this._onMouseDown.bind(this), false); - this.elementValue.addEventListener('touchstart', this._onTouchStart.bind(this), { passive: false }); - - this.elementOptions = document.createElement('ul'); - this._element.appendChild(this.elementOptions); - - this.optionElements = {}; - - if (args.default !== undefined && this.options[args.default] !== undefined) { - this._value = this.valueToType(args.default); - this._oldValue = this._value; - } - - this.on('link', this._onLink.bind(this)); - this._updateOptions(); - - this.on('change', this._onChange.bind(this)); - - this._element.addEventListener('keydown', this._onKeyDown.bind(this), false); - - if (args.placeholder) { - this.placeholder = args.placeholder; - } - } - - set value(raw: string | number | null) { - let value = this.valueToType(raw); - - if (this._link) { - this._oldValue = this._value; - this.emit('change:before', value); - this._link.set(this.path, value); - } else { - if ((value === null || value === undefined || raw === '') && this.optionElements['']) { - value = ''; - } - - if (this._oldValue === value) { - return; - } - if (value !== null && this.options[value] === undefined) { - return; - } - - if (this.optionElements[this._oldValue]) { - this.optionElements[this._oldValue].classList.remove('selected'); - } - - this._value = value; - if (value !== '') { - this._value = this.valueToType(this._value); - } - - this.emit('change:before', this._value); - this._oldValue = this._value; - if (this.options[this._value]) { - this.elementValue.textContent = this.options[this._value]; - this.optionElements[this._value].classList.add('selected'); - } else { - this.elementValue.textContent = ''; - } - this.emit('change', this._value); - } - } - - get value() { - if (this._link) { - return this._link.get(this.path); - } - return this._value; - } - - set placeholder(value: string) { - if (!value) { - this.elementValue.removeAttribute('placeholder'); - } else { - this.elementValue.setAttribute('placeholder', value); - } - } - - get placeholder() { - return this.elementValue.getAttribute('placeholder'); - } - - _onHoldSelect(target: SelectOptionElement | null, x: number, y: number) { - if (target && target.uiElement === this && target.classList.contains('selected')) { - return; - } - - if ((Math.abs(x - this.evtMouseDist[0]) + Math.abs(y - this.evtMouseDist[1])) < 8) { - return; - } - - if (target && target.uiElement === this) { - this._onOptionSelect.call(target); - } - - this.close(); - } - - _onMouseDown(evt: MouseEvent) { - if (this.disabled && !this.disabledClick) { - return; - } - - if (this._element.classList.contains('active')) { - this.close(); - } else { - evt.preventDefault(); - evt.stopPropagation(); - this.evtMouseDist[0] = evt.pageX; - this.evtMouseDist[1] = evt.pageY; - this.element.focus(); - this.open(); - window.addEventListener('mouseup', this.evtMouseUp); - } - } - - _onTouchStart(evt: TouchEvent) { - if (this.disabled && !this.disabledClick) { - return; - } - - if (this._element.classList.contains('active')) { - this.close(); - } else { - evt.preventDefault(); - evt.stopPropagation(); - - let touch; - - for (let i = 0; i < evt.changedTouches.length; i++) { - if ((evt.changedTouches[i].target as unknown) !== this) { - continue; - } - - touch = evt.changedTouches[i]; - - break; - } - - if (!touch) { - return; - } - - this.evtTouchId = touch.identifier; - this.evtMouseDist[0] = touch.pageX; - this.evtMouseDist[1] = touch.pageY; - this.element.focus(); - this.open(); - window.addEventListener('touchend', this.evtTouchEnd); - } - } - - _onLink(path: string) { - if (this._link.schema && this._link.schema.has(path)) { - const field = this._link.schema.get(path); - const options = field.options || {}; - this._updateOptions(options); - } - } - - _onChange() { - if (!this.renderChanges) { - return; - } - - this.flash(); - } - - _onKeyDown(evt: KeyboardEvent) { - if (evt.keyCode === 27) { - this.close(); - this._element.blur(); - return; - } - - if ((this.disabled && !this.disabledClick) || [38, 40].indexOf(evt.keyCode) === -1) { - return; - } - - evt.stopPropagation(); - evt.preventDefault(); - - const keys = Object.keys(this.options); - const ind = keys.indexOf(this.value !== undefined ? this.value.toString() : null); - - const y = evt.keyCode === 38 ? -1 : 1; - - if (y === -1 && ind <= 0) { - return; - } - - if (y === 1 && ind === (keys.length - 1)) { - return; - } - - this.value = keys[ind + y]; - } - - valueToType(value: any) { - switch (this._type) { - case 'boolean': - return !!value; - case 'number': - return parseInt(value, 10); - case 'string': - return `${value}`; - } - } - - open() { - if ((this.disabled && !this.disabledClick) || this._element.classList.contains('active')) { - return; - } - - this._element.classList.add('active'); - - const rect = this._element.getBoundingClientRect(); - - const left = Math.round(rect.left) + ((Math.round(rect.width) - this._element.clientWidth) / 2); - - let top = rect.top; - if (this.optionElements[this._value]) { - top -= this.optionElements[this._value].offsetTop; - top += (Math.round(rect.height) - this.optionElements[this._value].clientHeight) / 2; - } - - if (top + this.elementOptions.clientHeight > window.innerHeight) { - top = window.innerHeight - this.elementOptions.clientHeight + 1; - } else if (top < 0) { - top = 0; - } - - this.elementOptions.style.top = `${Math.max(0, top)}px`; - this.elementOptions.style.left = `${left}px`; - this.elementOptions.style.width = `${Math.round(this._element.clientWidth)}px`; - if (top <= 0 && this.elementOptions.offsetHeight >= window.innerHeight) { - this.elementOptions.style.bottom = '0'; - this.elementOptions.style.height = 'auto'; - - if (this.optionElements[this._value]) { - const off = this.optionElements[this._value].offsetTop - rect.top; - this.elementOptions.scrollTop = off; - } - } else { - this.elementOptions.style.bottom = ''; - this.elementOptions.style.height = ''; - } - - this.timerClickAway = setTimeout(() => { - const looseActive = () => { - this.element.classList.remove('active'); - this.element.blur(); - window.removeEventListener('click', looseActive); - }; - - window.addEventListener('click', looseActive); - }, 300); - - this.emit('open'); - } - - close() { - if ((this.disabled && !this.disabledClick) || !this._element.classList.contains('active')) { - return; - } - - window.removeEventListener('mouseup', this.evtMouseUp); - window.removeEventListener('touchend', this.evtTouchEnd); - - if (this.timerClickAway) { - clearTimeout(this.timerClickAway); - this.timerClickAway = null; - } - - this._element.classList.remove('active'); - - this.elementOptions.style.top = ''; - this.elementOptions.style.right = ''; - this.elementOptions.style.bottom = ''; - this.elementOptions.style.left = ''; - this.elementOptions.style.width = ''; - this.elementOptions.style.height = ''; - - this.emit('close'); - - this.evtTouchSecond = false; - } - - toggle() { - if (this._element.classList.contains('active')) { - this.close(); - } else { - this.open(); - } - } - - _updateOptions(options?: any) { - if (options !== undefined) { - if (options instanceof Array) { - this.options = {}; - this.optionsKeys = []; - for (let i = 0; i < options.length; i++) { - this.optionsKeys.push(options[i].v); - this.options[options[i].v] = options[i].t; - } - } else { - this.options = options; - this.optionsKeys = Object.keys(options); - } - } - - this.optionElements = {}; - this.elementOptions.innerHTML = ''; - - for (let i = 0; i < this.optionsKeys.length; i++) { - if (!this.options.hasOwnProperty(this.optionsKeys[i])) { - continue; - } - - const element = document.createElement('li') as SelectOptionElement; - element.textContent = this.options[this.optionsKeys[i]]; - element.uiElement = this; - element.uiValue = this.optionsKeys[i]; - element.addEventListener('touchstart', this._onOptionSelect, { passive: true }); - element.addEventListener('mouseover', this._onOptionHover); - element.addEventListener('mouseout', this._onOptionOut); - - if (this._optionClassNamePrefix) { - element.classList.add(`${this._optionClassNamePrefix}-${element.textContent.toLowerCase().replace(/ /g, '-')}`); - } - - this.elementOptions.appendChild(element); - this.optionElements[this.optionsKeys[i]] = element; - } - } - - _onOptionSelect(this: SelectOptionElement) { - this.uiElement.value = this.uiValue; - } - - _onOptionHover(this: SelectOptionElement) { - this.classList.add('hover'); - } - - _onOptionOut(this: SelectOptionElement) { - this.classList.remove('hover'); - } - - _onLinkChange(value: any) { - if (this.optionElements[value] === undefined) { - return; - } - - if (this.optionElements[this._oldValue]) { - this.optionElements[this._oldValue].classList.remove('selected'); - } - - this._value = this.valueToType(value); - this.elementValue.textContent = this.options[value]; - this.optionElements[value].classList.add('selected'); - this.emit('change', value); - } -} - -export { LegacySelectField }; diff --git a/src/common/ui/slider.ts b/src/common/ui/slider.ts deleted file mode 100644 index 713206639..000000000 --- a/src/common/ui/slider.ts +++ /dev/null @@ -1,304 +0,0 @@ -import { LegacyElement } from './element'; - -class LegacySlider extends LegacyElement { - _value: number; - - _lastValue: number; - - precision: number; - - _min: number; - - _max: number; - - elementBar: HTMLDivElement & { ui: unknown }; - - elementHandle: HTMLDivElement & { ui: unknown; tabIndex: number }; - - evtMouseMove: (evt: MouseEvent) => void; - - evtMouseUp: (evt: MouseEvent) => void; - - evtTouchId: number | null; - - evtTouchMove: (evt: TouchEvent) => void; - - evtTouchEnd: (evt: TouchEvent) => void; - - constructor(args: { precision?: number; min?: number; max?: number } = {}) { - super(); - this._value = 0; - this._lastValue = 0; - - this.precision = isNaN(args.precision) ? 2 : args.precision; - this._min = isNaN(args.min) ? 0 : args.min; - this._max = isNaN(args.max) ? 1 : args.max; - - this.element = document.createElement('div'); - this.element.classList.add('ui-slider'); - - this.elementBar = document.createElement('div'); - this.elementBar.ui = this; - this.elementBar.classList.add('bar'); - this.element.appendChild(this.elementBar); - - this.elementHandle = document.createElement('div'); - this.elementHandle.ui = this; - this.elementHandle.tabIndex = 0; - this.elementHandle.classList.add('handle'); - this.elementBar.appendChild(this.elementHandle); - - this.element.addEventListener('mousedown', this._onMouseDown.bind(this), false); - this.element.addEventListener('touchstart', this._onTouchStart.bind(this), false); - - this.evtMouseMove = (evt: MouseEvent) => { - evt.stopPropagation(); - evt.preventDefault(); - this._onSlideMove(evt.pageX); - }; - this.evtMouseUp = (evt: MouseEvent) => { - evt.stopPropagation(); - evt.preventDefault(); - this._onSlideEnd(evt.pageX); - }; - - this.evtTouchId = null; - - this.evtTouchMove = (evt: TouchEvent) => { - for (let i = 0; i < evt.changedTouches.length; i++) { - const touch = evt.changedTouches[i]; - - if (touch.identifier !== this.evtTouchId) { - continue; - } - - evt.stopPropagation(); - evt.preventDefault(); - - this._onSlideMove(touch.pageX); - break; - } - }; - this.evtTouchEnd = (evt: TouchEvent) => { - for (let i = 0; i < evt.changedTouches.length; i++) { - const touch = evt.changedTouches[i]; - - if (touch.identifier !== this.evtTouchId) { - continue; - } - - evt.stopPropagation(); - evt.preventDefault(); - - this._onSlideEnd(touch.pageX); - this.evtTouchId = null; - break; - } - }; - - this.on('change', this._onChange.bind(this)); - - this.element.addEventListener('keydown', this._onKeyDown.bind(this), false); - } - - set min(value: number) { - if (this._min === value) { - return; - } - - this._min = value; - this._updateHandle(this._value); - } - - get min() { - return this._min; - } - - set max(value: number) { - if (this._max === value) { - return; - } - - this._max = value; - this._updateHandle(this._value); - } - - get max() { - return this._max; - } - - set value(value: number | null) { - if (this._link) { - if (!this._link.set(this.path, value)) { - this._updateHandle(this._link.get(this.path)); - } - } else { - if (this._max !== null && this._max < value) { - value = this._max; - } - - if (this._min !== null && this._min > value) { - value = this._min; - } - - if (value === null) { - this.class.add('null'); - } else { - if (typeof value !== 'number') { - value = undefined; - } - - value = (value !== undefined && this.precision !== null) ? parseFloat(value.toFixed(this.precision)) : value; - this.class.remove('null'); - } - - this._updateHandle(value); - this._value = value; - - if (this._lastValue !== value) { - this._lastValue = value; - this.emit('change', value); - } - } - } - - get value() { - if (this._link) { - return this._link.get(this.path); - } - return this._value; - } - - _onChange() { - if (!this.renderChanges) { - return; - } - - this.flash(); - } - - _onKeyDown(evt: KeyboardEvent) { - if (evt.keyCode === 27) { - return this.element.blur(); - } - - if (this.disabled || [37, 39].indexOf(evt.keyCode) === -1) { - return; - } - - evt.stopPropagation(); - evt.preventDefault(); - - let x = evt.keyCode === 37 ? -1 : 1; - - if (evt.shiftKey) { - x *= 10; - } - - const rect = this._element.getBoundingClientRect(); - const step = (this._max - this._min) / rect.width; - let value = Math.max(this._min, Math.min(this._max, this.value + x * step)); - value = parseFloat(value.toFixed(this.precision)); - - this.renderChanges = false; - this._updateHandle(value); - this.value = value; - this.renderChanges = true; - } - - _onLinkChange(value: any) { - this._updateHandle(value); - this._value = value; - this.emit('change', value || 0); - } - - _updateHandle(value: number) { - this.elementHandle.style.left = `${Math.max(0, Math.min(1, ((value || 0) - this._min) / (this._max - this._min))) * 100}%`; - } - - _onMouseDown(evt: MouseEvent) { - if (evt.button !== 0 || this.disabled) { - return; - } - - this._onSlideStart(evt.pageX); - } - - _onTouchStart(evt: TouchEvent) { - if (this.disabled) { - return; - } - - for (let i = 0; i < evt.changedTouches.length; i++) { - const touch = evt.changedTouches[i]; - const target = touch.target as (EventTarget & { ui?: LegacySlider }) | null; - if (!target?.ui || target.ui !== this) { - continue; - } - - this.evtTouchId = touch.identifier; - this._onSlideStart(touch.pageX); - break; - } - } - - _onSlideStart(pageX: number) { - this.elementHandle.focus(); - - this.renderChanges = false; - - if (this.evtTouchId === null) { - window.addEventListener('mousemove', this.evtMouseMove, false); - window.addEventListener('mouseup', this.evtMouseUp, false); - } else { - window.addEventListener('touchmove', this.evtTouchMove, false); - window.addEventListener('touchend', this.evtTouchEnd, false); - } - - this.class.add('active'); - - this.emit('start', this.value); - - this._onSlideMove(pageX); - - if (this._link && this._link.history) { - this._link.history.combine = true; - } - } - - _onSlideMove(pageX: number) { - const rect = this.element.getBoundingClientRect(); - const x = Math.max(0, Math.min(1, (pageX - rect.left) / rect.width)); - - const range = this._max - this._min; - let value = (x * range) + this._min; - value = parseFloat(value.toFixed(this.precision)); - - this._updateHandle(value); - this.value = value; - } - - _onSlideEnd(pageX: number) { - this._onSlideMove(pageX); - - this.renderChanges = true; - - this.class.remove('active'); - - if (this.evtTouchId === null) { - window.removeEventListener('mousemove', this.evtMouseMove); - window.removeEventListener('mouseup', this.evtMouseUp); - } else { - window.removeEventListener('touchmove', this.evtTouchMove); - window.removeEventListener('touchend', this.evtTouchEnd); - } - - if (this._link && this._link.history) { - this._link.history.combine = false; - } - - this.emit('end', this.value); - } -} - -export { LegacySlider }; diff --git a/src/common/ui/text-field.ts b/src/common/ui/text-field.ts deleted file mode 100644 index 99084b62e..000000000 --- a/src/common/ui/text-field.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { LegacyElement } from './element'; - -class LegacyTextField extends LegacyElement { - elementInput: HTMLInputElement & { ui: unknown }; - - evtKeyChange: boolean; - - ignoreChange: boolean; - - blurOnEnter: boolean; - - refocusable: boolean; - - constructor(args: Record = {}) { - super(); - this.element = document.createElement('div'); - this._element.classList.add('ui-text-field'); - - this.elementInput = document.createElement('input'); - this.elementInput.ui = this; - this.elementInput.classList.add('field'); - this.elementInput.type = 'text'; - this.elementInput.tabIndex = 0; - this.elementInput.addEventListener('focus', this._onInputFocus.bind(this), false); - this.elementInput.addEventListener('blur', this._onInputBlur.bind(this), false); - this._element.appendChild(this.elementInput); - - if (args.default !== undefined) { - this.value = args.default; - } - - this.elementInput.addEventListener('change', this._onChange.bind(this), false); - this.elementInput.addEventListener('keydown', this._onKeyDown.bind(this), false); - this.elementInput.addEventListener('contextmenu', this._onFullSelect.bind(this), false); - this.evtKeyChange = false; - this.ignoreChange = false; - - this.blurOnEnter = true; - this.refocusable = true; - - this.on('disable', this._onDisable.bind(this)); - this.on('enable', this._onEnable.bind(this)); - this.on('change', this._onChangeField.bind(this)); - - if (args.placeholder) { - this.placeholder = args.placeholder; - } - } - - set value(value: string) { - if (this._link) { - if (!this._link.set(this.path, value)) { - this.elementInput.value = this._link.get(this.path); - } - } else { - if (this.elementInput.value === value) { - return; - } - - this.elementInput.value = value || ''; - this.emit('change', value); - } - } - - get value() { - if (this._link) { - return this._link.get(this.path); - } - return this.elementInput.value; - } - - set placeholder(value: string) { - if (!value) { - this._element.removeAttribute('placeholder'); - } else { - this._element.setAttribute('placeholder', value); - } - } - - get placeholder() { - return this._element.getAttribute('placeholder'); - } - - set proxy(value: string) { - if (!value) { - this._element.removeAttribute('proxy'); - } else { - this._element.setAttribute('proxy', value); - } - } - - get proxy() { - return this._element.getAttribute('proxy'); - } - - set keyChange(value: boolean) { - if (!!this.evtKeyChange === !!value) { - return; - } - - if (value) { - this.elementInput.addEventListener('keyup', this._onChange.bind(this), false); - } else { - this.elementInput.removeEventListener('keyup', this._onChange.bind(this)); - } - } - - get keyChange() { - return !!this.evtKeyChange; - } - - _onLinkChange(value: string) { - this.elementInput.value = value; - this.emit('change', value); - } - - _onChange() { - if (this.ignoreChange) { - return; - } - - this.value = this.value || ''; - - if (!this._link) { - this.emit('change', this.value); - } - } - - _onKeyDown(evt: KeyboardEvent) { - if (evt.keyCode === 27) { - this.elementInput.blur(); - } else if (this.blurOnEnter && evt.keyCode === 13) { - let focused = false; - - let parent: LegacyElement | null = this.parent; - while (parent) { - if (parent.focus) { - parent.focus(); - focused = true; - break; - } - - parent = parent.parent; - } - - if (!focused) { - this.elementInput.blur(); - } - } - } - - _onFullSelect() { - this.elementInput.select(); - } - - focus(select: boolean) { - this.elementInput.focus(); - if (select) { - this.elementInput.select(); - } - } - - _onInputFocus() { - this.class.add('focus'); - this.emit('input:focus'); - } - - _onInputBlur() { - this.class.remove('focus'); - this.emit('input:blur'); - } - - _onDisable() { - this.elementInput.readOnly = true; - } - - _onEnable() { - this.elementInput.readOnly = false; - } - - _onChangeField() { - if (!this.renderChanges) { - return; - } - - this.flash(); - } -} - -export { LegacyTextField }; diff --git a/src/common/ui/textarea-field.ts b/src/common/ui/textarea-field.ts deleted file mode 100644 index 2c755fbba..000000000 --- a/src/common/ui/textarea-field.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { LegacyElement } from './element'; - -class LegacyTextAreaField extends LegacyElement { - elementInput: HTMLTextAreaElement & { ui: unknown }; - - evtKeyChange: boolean; - - ignoreChange: boolean; - - blurOnEnter: boolean; - - refocusable: boolean; - - constructor(args: Record = {}) { - super(); - this.element = document.createElement('div'); - this._element.classList.add('ui-textarea-field'); - - this.elementInput = document.createElement('textarea'); - this.elementInput.ui = this; - this.elementInput.classList.add('field'); - this.elementInput.tabIndex = 0; - this.elementInput.addEventListener('focus', this._onInputFocus.bind(this), false); - this.elementInput.addEventListener('blur', this._onInputBlur.bind(this), false); - this._element.appendChild(this.elementInput); - - if (args.default !== undefined) { - this.value = args.default; - } - - this.elementInput.addEventListener('change', this._onChange.bind(this), false); - this.elementInput.addEventListener('keydown', this._onKeyDown.bind(this), false); - this.elementInput.addEventListener('contextmenu', this._onFullSelect.bind(this), false); - this.evtKeyChange = false; - this.ignoreChange = false; - - this.blurOnEnter = args.blurOnEnter !== undefined ? args.blurOnEnter : true; - this.refocusable = true; - - this.on('disable', this._onDisable.bind(this)); - this.on('enable', this._onEnable.bind(this)); - this.on('change', this._onChangeField.bind(this)); - - if (args.placeholder) { - this.placeholder = args.placeholder; - } - } - - set value(value: string) { - if (this._link) { - if (!this._link.set(this.path, value)) { - this.elementInput.value = this._link.get(this.path); - } - } else { - if (this.elementInput.value === value) { - return; - } - - this.elementInput.value = value || ''; - this.emit('change', value); - } - } - - get value() { - if (this._link) { - return this._link.get(this.path); - } - return this.elementInput.value; - } - - set placeholder(value: string) { - if (!value) { - this._element.removeAttribute('placeholder'); - } else { - this._element.setAttribute('placeholder', value); - } - } - - get placeholder() { - return this._element.getAttribute('placeholder'); - } - - set keyChange(value: boolean) { - if (!!this.evtKeyChange === !!value) { - return; - } - - if (value) { - this.elementInput.addEventListener('keyup', this._onChange.bind(this), false); - } else { - this.elementInput.removeEventListener('keyup', this._onChange.bind(this)); - } - } - - get keyChange() { - return !!this.evtKeyChange; - } - - set proxy(value: string) { - if (!value) { - this._element.removeAttribute('proxy'); - } else { - this._element.setAttribute('proxy', value); - } - } - - get proxy() { - return this._element.getAttribute('proxy'); - } - - _onLinkChange(value: string) { - this.elementInput.value = value; - this.emit('change', value); - } - - _onChange() { - if (this.ignoreChange) { - return; - } - - this.value = this.value || ''; - - if (!this._link) { - this.emit('change', this.value); - } - } - - _onKeyDown(evt: KeyboardEvent) { - if (evt.keyCode === 27) { - this.elementInput.blur(); - } else if (this.blurOnEnter && evt.keyCode === 13 && !evt.shiftKey) { - let focused = false; - - let parent: LegacyElement | null = this.parent; - while (parent) { - if (parent.focus) { - parent.focus(); - focused = true; - break; - } - - parent = parent.parent; - } - - if (!focused) { - this.elementInput.blur(); - } - } - } - - _onFullSelect() { - this.elementInput.select(); - } - - focus(select: boolean) { - this.elementInput.focus(); - if (select) { - this.elementInput.select(); - } - } - - _onInputFocus() { - this.class.add('focus'); - this.emit('input:focus'); - } - - _onInputBlur() { - this.class.remove('focus'); - this.emit('input:blur'); - } - - _onDisable() { - this.elementInput.readOnly = true; - } - - _onEnable() { - this.elementInput.readOnly = false; - } - - _onChangeField() { - if (!this.renderChanges) { - return; - } - - this.flash(); - } -} - -export { LegacyTextAreaField }; diff --git a/src/common/ui/tooltip.ts b/src/common/ui/tooltip.ts deleted file mode 100644 index 6f165c06a..000000000 --- a/src/common/ui/tooltip.ts +++ /dev/null @@ -1,347 +0,0 @@ -import type { Container } from '@playcanvas/pcui'; - -import { LegacyContainer } from './container'; - -class LegacyTooltip extends LegacyContainer { - arrow: HTMLElement; - - hoverable: boolean; - - x: number; - - y: number; - - private _align: 'top' | 'right' | 'bottom' | 'left'; - - private _removeTarget? : () => void; - - constructor(args: { - class?: string, - hoverable?: boolean, - x?: number, - y?: number, - align?: 'top' | 'right' | 'bottom' | 'left', - hidden?: boolean, - text?: string, - html?: string - } = {}) { - super(); - this.element = document.createElement('div'); - this._element.classList.add('ui-tooltip', 'align-left'); - if (args.class) { - this._element.classList.add(args.class); - } - - this.innerElement = document.createElement('div'); - this.innerElement.classList.add('inner'); - this._element.appendChild(this.innerElement); - - this.arrow = document.createElement('div'); - this.arrow.classList.add('arrow'); - this._element.appendChild(this.arrow); - - this.hoverable = args.hoverable || false; - - this.x = args.x || 0; - this.y = args.y || 0; - - this._align = 'left'; - this.align = args.align || 'left'; - - this.on('show', this._reflow.bind(this)); - this.hidden = args.hidden !== undefined ? args.hidden : true; - if (args.html) { - this.html = args.html; - } else { - this.text = args.text || ''; - } - - this._element.addEventListener('mouseover', this._onMouseOver.bind(this), false); - this._element.addEventListener('mouseleave', this._onMouseLeave.bind(this), false); - - this.on('destroy', () => { - this.detach(); - }); - } - - set align(value: 'top' | 'right' | 'bottom' | 'left') { - if (this._align === value) { - return; - } - - this.class.remove(`align-${this._align}`); - this._align = value; - this.class.add(`align-${this._align}`); - - this._reflow(); - } - - get align() { - return this._align; - } - - set flip(value: boolean) { - if (this.class.contains('flip') === value) { - return; - } - - if (value) { - this.class.add('flip'); - } else { - this.class.remove('flip'); - } - - this._reflow(); - } - - get flip() { - return this.class.contains('flip'); - } - - set text(value: string) { - if (this.innerElement.textContent === value) { - return; - } - - this.innerElement.textContent = value; - } - - get text() { - return this.innerElement.textContent; - } - - set html(value: string) { - if (this.innerElement.innerHTML === value) { - return; - } - - this.innerElement.innerHTML = value; - } - - get html() { - return this.innerElement.innerHTML; - } - - _onMouseOver(evt: MouseEvent) { - if (!this.hoverable) { - return; - } - - this.hidden = false; - this.emit('hover', evt); - } - - _onMouseLeave() { - if (!this.hoverable) { - return; - } - - this.hidden = true; - } - - _reflow() { - if (this.hidden) { - return; - } - - this._element.style.top = ''; - this._element.style.right = ''; - this._element.style.bottom = ''; - this._element.style.left = ''; - - this.arrow.style.top = ''; - this.arrow.style.right = ''; - this.arrow.style.bottom = ''; - this.arrow.style.left = ''; - - this._element.style.display = 'block'; - - switch (this._align) { - case 'top': - this._element.style.top = `${this.y}px`; - if (this.flip) { - this._element.style.right = `calc(100% - ${this.x}px)`; - } else { - this._element.style.left = `${this.x}px`; - } - break; - case 'right': - this._element.style.top = `${this.y}px`; - this._element.style.right = `calc(100% - ${this.x}px)`; - break; - case 'bottom': - this._element.style.bottom = `calc(100% - ${this.y}px)`; - if (this.flip) { - this._element.style.right = `calc(100% - ${this.x}px)`; - } else { - this._element.style.left = `${this.x}px`; - } - break; - case 'left': - this._element.style.top = `${this.y}px`; - this._element.style.left = `${this.x}px`; - break; - } - - const rect = this._element.getBoundingClientRect(); - - if (rect.left < 0) { - this._element.style.left = '0px'; - this._element.style.right = ''; - } - if (rect.top < 0) { - this._element.style.top = '0px'; - this._element.style.bottom = ''; - } - if (rect.right > window.innerWidth) { - this._element.style.right = '0px'; - this._element.style.left = ''; - this.arrow.style.left = `${Math.floor(rect.right - window.innerWidth + 8)}px`; - } - if (rect.bottom > window.innerHeight) { - this._element.style.bottom = '0px'; - this._element.style.top = ''; - this.arrow.style.top = `${Math.floor(rect.bottom - window.innerHeight + 8)}px`; - } - - this._element.style.display = ''; - } - - position(x: number, y: number) { - x = Math.floor(x); - y = Math.floor(y); - - if (this.x === x && this.y === y) { - return; - } - - this.x = x; - this.y = y; - - this._reflow(); - } - - attach(target: HTMLElement) { - if (this._removeTarget) { - this.detach(); - } - - const evtHover = () => { - const rect = target.getBoundingClientRect(); - let off = 16; - - switch (this.align) { - case 'top': - if (rect.width < 64) { - off = rect.width / 2; - } - this.flip = rect.left + off > window.innerWidth / 2; - if (this.flip) { - this.position(rect.right - off, rect.bottom); - } else { - this.position(rect.left + off, rect.bottom); - } - break; - case 'right': - if (rect.height < 64) { - off = rect.height / 2; - } - this.flip = false; - this.position(rect.left, rect.top + off); - break; - case 'bottom': - if (rect.width < 64) { - off = rect.width / 2; - } - this.flip = rect.left + off > window.innerWidth / 2; - if (this.flip) { - this.position(rect.right - off, rect.top); - } else { - this.position(rect.left + off, rect.top); - } - break; - case 'left': - if (rect.height < 64) { - off = rect.height / 2; - } - this.flip = false; - this.position(rect.right, rect.top + off); - break; - } - - this.hidden = false; - }; - const evtBlur = () => { - this.hidden = true; - }; - - target.addEventListener('mouseover', evtHover, false); - target.addEventListener('mouseout', evtBlur, false); - - const remove = () => { - target.removeEventListener('mouseover', evtHover, false); - target.removeEventListener('mouseout', evtBlur, false); - }; - - this._removeTarget = remove; - - if (target.matches(':hover')) { - evtHover(); - } - } - - detach() { - if (!this._removeTarget) { - return; - } - this._removeTarget(); - this._removeTarget = undefined; - } - - static create(args: { - root: Container, - text?: string, - html?: string, - align?: 'top' | 'right' | 'bottom' | 'left', - hoverable?: boolean, - class?: string - }) { - const data: { - text?: string, - html?: string, - align: 'top' | 'right' | 'bottom' | 'left', - hoverable: boolean, - class: string | null - } = { - align: args.align, - hoverable: args.hoverable, - class: args.class ?? null - }; - if (args.html) { - data.html = args.html; - } else { - data.text = args.text || ''; - } - const item = new LegacyTooltip(data); - - args.root.append(item); - - return item; - } - - static attach(args: { - root: Container, - target: HTMLElement, - text?: string, - html?: string, - align?: 'top' | 'right' | 'bottom' | 'left', - hoverable?: boolean, - class?: string - }) { - const item = LegacyTooltip.create(args); - item.attach(args.target); - return item; - } -} - -export { LegacyTooltip }; diff --git a/src/editor/assets/asset-panel.ts b/src/editor/assets/asset-panel.ts index 26e9003de..114a678b5 100644 --- a/src/editor/assets/asset-panel.ts +++ b/src/editor/assets/asset-panel.ts @@ -25,8 +25,7 @@ import { DropTarget } from '@/common/pcui/element/element-drop-target'; import { Table } from '@/common/pcui/element/element-table'; import { TableCell } from '@/common/pcui/element/element-table-cell'; import { TableRow } from '@/common/pcui/element/element-table-row'; -import { type Tooltip } from '@/common/pcui/element/element-tooltip'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; import { bytesToHuman, naturalCompare } from '@/common/utils'; import { config } from '@/editor/config'; import { type AssetObserver } from '@/editor-api'; @@ -270,7 +269,7 @@ class AssetPanel extends Panel { private _containerControls: Container; - private _tooltips: Tooltip[]; + private _tooltips: TooltipHandle[]; private _btnNew: Button; @@ -453,7 +452,7 @@ class AssetPanel extends Panel { // header controls - const tooltip = LegacyTooltip.create({ + const tooltip = TooltipHandle.make({ text: 'Add Asset', align: 'bottom', class: 'pcui-tooltip-clipboard', diff --git a/src/editor/attributes/attributes-array.ts b/src/editor/attributes/attributes-array.ts index 0ee0f7160..6ec9157ff 100644 --- a/src/editor/attributes/attributes-array.ts +++ b/src/editor/attributes/attributes-array.ts @@ -1,9 +1,9 @@ import { Observer } from '@playcanvas/observer'; -import { LegacyButton } from '@/common/ui/button'; -import { LegacyPanel } from '@/common/ui/panel'; import { deepCopy } from '@/common/utils'; +import { createButton, createPanel } from './attributes-pcui'; + editor.once('load', () => { const defaults = { checkbox: false, @@ -34,7 +34,7 @@ editor.once('load', () => { const arrayElements = []; let timeoutRefreshElements = null; - const panel = new LegacyPanel(); + const panel = createPanel(); panel.class.add('attributes-array'); panel.flex = true; panel.flexGrow = 1; @@ -90,7 +90,7 @@ editor.once('load', () => { }); // container for array elements - const panelElements = new LegacyPanel(); + const panelElements = createPanel(); panelElements.class.add('attributes-array-elements'); panelElements.flex = true; panelElements.flexGrow = 1; @@ -153,7 +153,7 @@ editor.once('load', () => { arrayElements.push(field); // button to remove array element - const btnRemove = new LegacyButton({ + const btnRemove = createButton({ text: '', unsafe: true }); diff --git a/src/editor/attributes/attributes-assets-list.ts b/src/editor/attributes/attributes-assets-list.ts index 6bff627be..5aa15c5bc 100644 --- a/src/editor/attributes/attributes-assets-list.ts +++ b/src/editor/attributes/attributes-assets-list.ts @@ -1,11 +1,11 @@ -import type { Observer } from '@playcanvas/observer'; - -import { LegacyButton } from '@/common/ui/button'; -import { LegacyLabel } from '@/common/ui/label'; -import { LegacyList } from '@/common/ui/list'; -import { LegacyListItem } from '@/common/ui/list-item'; -import { LegacyPanel } from '@/common/ui/panel'; -import { LegacyTextField } from '@/common/ui/text-field'; +import { + createButton, + createLabel, + createList, + createListItem, + createPanel, + createTextInput +} from './attributes-pcui'; editor.once('load', () => { @@ -13,11 +13,11 @@ editor.once('load', () => { const root = editor.call('layout.attributes'); // get the right path from args - const pathAt = function (args: { path?: string; paths?: string[] }, index: number) { + const pathAt = function (args: any, index: number) { return args.paths ? args.paths[index] : args.path; }; - const historyState = function (item: Observer, state: boolean) { + const historyState = function (item: any, state: boolean) { if (item.history !== undefined) { if (typeof item.history === 'boolean') { item.history = state; @@ -40,12 +40,7 @@ editor.once('load', () => { * @param args.filterFn - A custom function that filters assets that can be dragged on the list. The function * takes the asset as its only argument. */ - editor.method('attributes:addAssetsList', (args: { - link: Observer[]; - type?: string; - filterFn?: (asset: Observer) => boolean; - panel: LegacyPanel; - }) => { + editor.method('attributes:addAssetsList', (args: any) => { const link = args.link; const assetType = args.type; const assetFilterFn = args.filterFn; @@ -54,7 +49,7 @@ editor.once('load', () => { // index list items by asset id let assetIndex = {}; - const panelWidget = new LegacyPanel(); + const panelWidget = createPanel(); panelWidget.flex = true; panelWidget.class.add('asset-list'); @@ -62,26 +57,26 @@ editor.once('load', () => { let currentSelection = null; // button that enables selection mode - const btnSelectionMode = new LegacyButton({ + const btnSelectionMode = createButton({ text: 'Add Assets' }); btnSelectionMode.class.add('selection-mode'); panelWidget.append(btnSelectionMode); // panel for buttons - const panelButtons = new LegacyPanel(); + const panelButtons = createPanel(); panelButtons.class.add('buttons'); panelButtons.flex = true; panelButtons.hidden = true; // label - const labelAdd = new LegacyLabel({ + const labelAdd = createLabel({ text: 'Add Assets' }); panelButtons.append(labelAdd); // add button - const btnAdd = new LegacyButton({ + const btnAdd = createButton({ text: 'ADD SELECTION' }); btnAdd.disabled = true; @@ -90,7 +85,7 @@ editor.once('load', () => { panelButtons.append(btnAdd); // done button - const btnDone = new LegacyButton({ + const btnDone = createButton({ text: 'DONE' }); btnDone.flexGrow = 1; @@ -153,7 +148,7 @@ editor.once('load', () => { }); // search field - const fieldFilter = new LegacyTextField(); + const fieldFilter = createTextInput(); fieldFilter.hidden = true; fieldFilter.elementInput.setAttribute('placeholder', 'Type to filter'); fieldFilter.keyChange = true; @@ -162,7 +157,7 @@ editor.once('load', () => { // assets var fieldAssets; - const fieldAssetsList = new LegacyList(); + const fieldAssetsList = createList(); fieldAssetsList.class.add('empty'); fieldAssetsList.flexGrow = 1; @@ -284,25 +279,25 @@ editor.once('load', () => { }; // add asset list item to the list - const addAssetListItem = function (assetId: number | string, after: LegacyListItem | null) { - assetId = parseInt(assetId, 10); + const addAssetListItem = function (assetId: number | string, after?: any) { + const id = typeof assetId === 'number' ? assetId : parseInt(assetId, 10); - let item = assetIndex[assetId]; + let item = assetIndex[id]; if (item) { item.count++; item.text = (item.count === link.length ? '' : '* ') + item._assetText; return; } - const asset = editor.call('assets:get', assetId); - let text = assetId; + const asset = editor.call('assets:get', id); + let text = String(id); if (asset && asset.get('name')) { text = asset.get('name'); } else if (!asset) { text += ' (Missing)'; } - item = new LegacyListItem({ + item = createListItem({ text: (link.length === 1) ? text : `* ${text}` }); if (asset) { @@ -321,20 +316,20 @@ editor.once('load', () => { fieldAssetsList.class.remove('empty'); fieldFilter.hidden = false; - assetIndex[assetId] = item; + assetIndex[id] = item; // remove button - const btnRemove = new LegacyButton(); + const btnRemove = createButton(); btnRemove.class.add('remove'); btnRemove.on('click', () => { - removeAsset(assetId); + removeAsset(id); }); btnRemove.parent = item; item.element.appendChild(btnRemove.element); item.once('destroy', () => { - delete assetIndex[assetId]; + delete assetIndex[id]; }); }; @@ -352,7 +347,7 @@ editor.once('load', () => { item.destroy(); fieldAssets.emit('remove', item); - if (!fieldAssetsList.element.children.length) { + if (!fieldAssetsList.innerElement.children.length) { fieldAssetsList.class.add('empty'); fieldFilter.hidden = true; } @@ -416,10 +411,10 @@ editor.once('load', () => { dropRef.disabled = true; // clear list item - const items = fieldAssetsList.element.children; + const items = fieldAssetsList.innerElement.children; let i = items.length; while (i--) { - if (!items[i].ui || !(items[i].ui instanceof LegacyListItem)) { + if (!items[i].ui?.isListItem) { continue; } diff --git a/src/editor/attributes/attributes-components-script.ts b/src/editor/attributes/attributes-components-script.ts index 71642fe12..ef7ea5217 100644 --- a/src/editor/attributes/attributes-components-script.ts +++ b/src/editor/attributes/attributes-components-script.ts @@ -1,9 +1,9 @@ import { Observer } from '@playcanvas/observer'; -import { LegacyButton } from '@/common/ui/button'; -import { LegacyPanel } from '@/common/ui/panel'; import { config } from '@/editor/config'; +import { createButton, createPanel } from './attributes-pcui'; + editor.once('load', () => { if (!editor.call('settings:project').get('useLegacyScripts')) { return; @@ -111,7 +111,7 @@ editor.once('load', () => { const scriptNameRegex = /^(?:[\w.-]+\/)*[\w.-]+(?:\.[j|][s|](?:[o|][n|])?)?$/i; // scripts.add - const btnAddScript = new LegacyButton({ + const btnAddScript = createButton({ text: 'Add Script' }); btnAddScript.class.add('add-script'); @@ -136,7 +136,7 @@ editor.once('load', () => { }); }); - const panelScripts = new LegacyPanel(); + const panelScripts = createPanel(); panelScripts.class.add('components-scripts'); panel.append(panelScripts); @@ -354,7 +354,7 @@ editor.once('load', () => { }); }; - const updateAttributeFields = function (script: Observer, parent: LegacyPanel) { + const updateAttributeFields = function (script: Observer, parent: any) { const attributes = script.get('attributesOrder'); const children = parent.innerElement.childNodes; const list = []; @@ -417,9 +417,9 @@ editor.once('load', () => { } }; - var createAttributeField = function (script: Observer, attribute: string, parent: LegacyPanel) { - let choices = null; - attribute = script.get(`attributes.${attribute}`); + var createAttributeField = function (script: Observer, attributeName: string, parent: any) { + let choices: any = null; + const attribute: any = script.get(`attributes.${attributeName}`); if (attribute.type === 'enumeration') { choices = [{ v: '', t: '...' }]; @@ -455,7 +455,7 @@ editor.once('load', () => { let field; - const reference = { + const reference: any = { title: attribute.name, subTitle: scriptAttributeRuntimeTypes[attribute.type] }; @@ -496,7 +496,7 @@ editor.once('load', () => { type = 'string'; } - const args = { + const args: any = { parent: parent, name: attribute.displayName || attribute.name, type: type, @@ -726,7 +726,7 @@ editor.once('load', () => { return; } - panelScript = new LegacyPanel(script.get('url')); + panelScript = createPanel(script.get('url')); panelScript.class.add('component-script'); panelScript.count = 1; @@ -759,7 +759,7 @@ editor.once('load', () => { })); // remove - const fieldRemoveScript = new LegacyButton(); + const fieldRemoveScript = createButton(); fieldRemoveScript.parent = panelScript; fieldRemoveScript.class.add('remove'); fieldRemoveScript.on('click', (value) => { @@ -859,7 +859,7 @@ editor.once('load', () => { // allow reordering scripts if all entities scripts components are identical // move down - const fieldMoveDown = new LegacyButton(); + const fieldMoveDown = createButton(); fieldMoveDown.class.add('move-down'); fieldMoveDown.element.title = 'Move script down'; fieldMoveDown.on('click', () => { @@ -875,7 +875,7 @@ editor.once('load', () => { } // move up - const fieldMoveUp = new LegacyButton(); + const fieldMoveUp = createButton(); fieldMoveUp.class.add('move-up'); fieldMoveUp.element.title = 'Move script up'; fieldMoveUp.on('click', () => { @@ -890,7 +890,7 @@ editor.once('load', () => { } // refresh attributes - const fieldRefreshAttributes = new LegacyButton(); + const fieldRefreshAttributes = createButton(); fieldRefreshAttributes.class.add('refresh'); fieldRefreshAttributes.element.title = 'Refresh script attributes'; panelScript.headerElement.appendChild(fieldRefreshAttributes.element); @@ -903,7 +903,7 @@ editor.once('load', () => { fieldRefreshAttributes.hidden = true; // attributes panel - const attributes = new LegacyPanel(); + const attributes = createPanel(); panelScript.append(attributes); if (script.has('attributesOrder')) { diff --git a/src/editor/attributes/attributes-entity.ts b/src/editor/attributes/attributes-entity.ts index 4d906f64f..6535e9ced 100644 --- a/src/editor/attributes/attributes-entity.ts +++ b/src/editor/attributes/attributes-entity.ts @@ -1,6 +1,4 @@ -import { LegacyButton } from '@/common/ui/button'; -import { LegacyLabel } from '@/common/ui/label'; - +import { createButton, createLabel } from './attributes-pcui'; import { EntityInspector } from '../inspector/entity'; import { TemplatesEntityInspector } from '../templates/templates-entity-inspector'; import { TemplateOverridesView } from '../templates/templates-override-panel'; @@ -70,7 +68,7 @@ editor.once('load', () => { }); // remove - const fieldRemove = new LegacyButton(); + const fieldRemove = createButton(); fieldRemove.hidden = !editor.call('permissions:write'); events.push(editor.on('permissions:writeState', (state) => { @@ -137,7 +135,7 @@ editor.once('load', () => { panel.headerAppend(fieldEnabled); // toggle-label - const labelEnabled = new LegacyLabel(); + const labelEnabled = createLabel(); labelEnabled.renderChanges = false; labelEnabled.class.add('component-toggle-label'); panel.headerAppend(labelEnabled); diff --git a/src/editor/attributes/attributes-history.ts b/src/editor/attributes/attributes-history.ts index fb64a2524..f981f63bb 100644 --- a/src/editor/attributes/attributes-history.ts +++ b/src/editor/attributes/attributes-history.ts @@ -1,6 +1,6 @@ import { Button, Container } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; editor.once('load', () => { let list = []; @@ -121,7 +121,7 @@ editor.once('load', () => { } }; - const tooltip = LegacyTooltip.attach({ + const tooltip = TooltipHandle.attach({ target: btnBack.dom, text: '-', align: 'top', diff --git a/src/editor/attributes/attributes-panel.ts b/src/editor/attributes/attributes-panel.ts index d09bbc614..86f3a749e 100644 --- a/src/editor/attributes/attributes-panel.ts +++ b/src/editor/attributes/attributes-panel.ts @@ -1,25 +1,25 @@ import type { Observer } from '@playcanvas/observer'; -import { CubemapThumbnailRenderer } from '@/common/thumbnail-renderers/cubemap-thumbnail-renderer'; -import { FontThumbnailRenderer } from '@/common/thumbnail-renderers/font-thumbnail-renderer'; -import { MaterialThumbnailRenderer } from '@/common/thumbnail-renderers/material-thumbnail-renderer'; -import { ModelThumbnailRenderer } from '@/common/thumbnail-renderers/model-thumbnail-renderer'; -import { SpriteThumbnailRenderer } from '@/common/thumbnail-renderers/sprite-thumbnail-renderer'; -import { LegacyButton } from '@/common/ui/button'; -import { LegacyCheckbox } from '@/common/ui/checkbox'; -import { LegacyCode } from '@/common/ui/code'; -import { LegacyColorField } from '@/common/ui/color-field'; -import { LegacyCurveField } from '@/common/ui/curve-field'; -import { LegacyImageField } from '@/common/ui/image-field'; -import { LegacyLabel } from '@/common/ui/label'; -import { LegacyNumberField } from '@/common/ui/number-field'; -import { LegacyPanel } from '@/common/ui/panel'; -import { LegacyProgress } from '@/common/ui/progress'; -import { LegacySelectField } from '@/common/ui/select-field'; -import { LegacySlider } from '@/common/ui/slider'; -import { LegacyTextField } from '@/common/ui/text-field'; -import { LegacyTextAreaField } from '@/common/ui/textarea-field'; -import { buildQueryUrl } from '@/common/utils'; +import { toLinkedFieldValue } from '@/common/pcui/compat-utils'; + +import { + createAssetInput, + createButton, + createCheckbox, + createCode, + createColorInput, + createCurveInput, + createEntityInput, + createGradientInput, + createLabel, + createNumberInput, + createPanel, + createProgress, + createSelectInput, + createSliderInput, + createTextAreaInput, + createTextInput +} from './attributes-pcui'; editor.once('load', () => { const legacyScripts = editor.call('settings:project').get('useLegacyScripts'); @@ -49,7 +49,7 @@ editor.once('load', () => { // add panel editor.method('attributes:addPanel', (args = {}) => { // panel - const panel = new LegacyPanel(args.name || ''); + const panel = createPanel(args.name || ''); // parent (args.parent || root).append(panel); @@ -60,7 +60,7 @@ editor.once('load', () => { return panel; }); - const historyState = function (item: Observer, state: boolean) { + const historyState = function (item: any, state: boolean) { if (item.history !== undefined) { if (typeof item.history === 'boolean') { item.history = state; @@ -75,7 +75,7 @@ editor.once('load', () => { }; // get the right path from args - const pathAt = function (args: { path?: string; paths?: string[] }, index: number) { + const pathAt = function (args: any, index: number) { return args.paths ? args.paths[index] : args.path; }; @@ -90,6 +90,7 @@ editor.once('load', () => { update = function () { let different = false; + let values = null; let path = pathAt(args, 0); let value = args.link[0].has(path) ? args.link[0].get(path) : undefined; if (args.type === 'rgb') { @@ -103,12 +104,12 @@ editor.once('load', () => { } } } - if (value) { - value = value.map((v) => { - return Math.floor(v * 255); - }); - } } else if (args.type === 'asset') { + values = args.link.map((link, i) => { + const itemPath = pathAt(args, i); + return link.has(itemPath) ? link.get(itemPath) || null : null; + }); + let countUndefined = value === undefined ? 1 : 0; for (let i = 1; i < args.link.length; i++) { path = pathAt(args, i); @@ -132,19 +133,12 @@ editor.once('load', () => { if (countUndefined && countUndefined !== args.link.length) { args.field.class.add('star'); - if (!/^\* /.test(args.field._title.text)) { - args.field._title.text = `* ${args.field._title.text}`; - } } else { args.field.class.remove('star'); - if (/^\* /.test(args.field._title.text)) { - args.field._title.text = args.field._title.text.substring(2); - } } if (different) { args.field.class.add('null'); - args.field._title.text = 'various'; } else { args.field.class.remove('null'); } @@ -186,7 +180,14 @@ editor.once('load', () => { } args.field._changing = true; - args.field.value = value; + if (args.type === 'asset' && different) { + args.field.values = values; + } else { + args.field.value = toLinkedFieldValue(args.type, value, different); + } + if (args.type === 'entity' && different) { + args.field.text = 'various'; + } if (args.type === 'checkbox') { args.field._onLinkChange(value); @@ -219,14 +220,10 @@ editor.once('load', () => { } if (args.trim) { - value = value.trim(); + value = typeof value === 'string' ? value.trim() : value; } - if (args.type === 'rgb') { - value = value.map((v) => { - return v / 255; - }); - } else if (args.type === 'asset') { + if (args.type === 'asset') { args.field.class.remove('null'); } @@ -383,9 +380,6 @@ editor.once('load', () => { events.push(args.field.on('click', () => { colorPickerOn = true; - // set picker color - editor.call('picker:color', args.field.value); - let items = []; // picking starts @@ -393,33 +387,14 @@ editor.once('load', () => { items = historyStart(); }); - // picked color - const evtColorPick = editor.on('picker:color', (color) => { - args.field.value = color; - }); - const evtColorPickEnd = editor.on('picker:color:end', () => { - historyEnd(items.slice(0), args.field.value.map((v) => { - return v / 255; - })); - }); - - // position picker - const rectPicker = editor.call('picker:color:rect'); - const rectField = args.field.element.getBoundingClientRect(); - editor.call('picker:color:position', rectField.left - rectPicker.width, rectField.top); - - // color changed, update picker - const evtColorToPicker = args.field.on('change', function () { - editor.call('picker:color:set', this.value); + historyEnd(items.slice(0), args.field.value); }); // picker closed editor.once('picker:color:close', () => { - evtColorPick.unbind(); evtColorPickStart.unbind(); evtColorPickEnd.unbind(); - evtColorToPicker.unbind(); colorPickerOn = false; args.field.element.focus(); }); @@ -465,7 +440,7 @@ editor.once('load', () => { let panel = args.panel; if (!panel) { - panel = new LegacyPanel(); + panel = createPanel(); panel.flexWrap = 'nowrap'; panel.WebkitFlexWrap = 'nowrap'; panel.style.display = ''; @@ -481,7 +456,7 @@ editor.once('load', () => { let label; if (args.name) { - label = new LegacyLabel({ + label = createLabel({ text: args.name }); label.class.add('label-field'); @@ -521,8 +496,8 @@ editor.once('load', () => { const linkField = args.linkField = function () { if (args.link) { - const link = function (field: LegacyNumberField | LegacyTextField | LegacyCheckbox | LegacySelectField | LegacySlider | LegacyColorField | LegacyImageField | LegacyLabel, path: string | string[]) { - const data = { + const link = function (field: any, path?: string | string[]) { + const data: any = { field: field, type: args.type, slider: args.slider, @@ -580,11 +555,11 @@ editor.once('load', () => { switch (args.type) { case 'string': if (args.enum) { - field = new LegacySelectField({ + field = createSelectInput({ options: args.enum }); } else { - field = new LegacyTextField(); + field = createTextInput(); } field.value = args.value || ''; @@ -602,11 +577,11 @@ editor.once('load', () => { case 'tags': // TODO: why isn't this in a seperate class/file??? - var innerPanel = new LegacyPanel(); + var innerPanel = createPanel(); var tagType = args.tagType || 'string'; if (args.enum) { - field = new LegacySelectField({ + field = createSelectInput({ options: args.enum, type: tagType }); @@ -627,7 +602,7 @@ editor.once('load', () => { innerPanel.append(field); } else { - field = new LegacyTextField(); + field = createTextInput(); field.blurOnEnter = false; field.renderChanges = false; @@ -642,7 +617,7 @@ editor.once('load', () => { innerPanel.append(field); - const btnAdd = new LegacyButton({ + const btnAdd = createButton({ text: '' }); btnAdd.flexGrow = 0; @@ -658,7 +633,7 @@ editor.once('load', () => { } - var tagsPanel = new LegacyPanel(); + var tagsPanel = createPanel(); tagsPanel.class.add('tags'); tagsPanel.flex = true; innerPanel.append(tagsPanel); @@ -738,15 +713,16 @@ editor.once('load', () => { } }; - var addTag = function (tag: string) { + var addTag = function (tag: string | number) { const records = []; // convert to number if needed if (args.tagType === 'number') { - tag = parseInt(tag, 10); - if (isNaN(tag)) { + const numeric = parseInt(String(tag), 10); + if (isNaN(numeric)) { return; } + tag = numeric; } for (let i = 0; i < args.link.length; i++) { @@ -877,7 +853,7 @@ editor.once('load', () => { // the original tag value before tagToString is called. Useful // if the tag value is an id for example - item.originalValue = tag; + (item as any).originalValue = tag; // attach click handler on text part of the tag - bind the listener // to the tag item so that `this` refers to that tag in the listener @@ -888,7 +864,7 @@ editor.once('load', () => { const icon = document.createElement('span'); icon.innerHTML = ''; icon.classList.add('icon'); - icon.tag = tag; + (icon as any).tag = tag; icon.addEventListener('click', onRemoveClick, false); item.appendChild(icon); @@ -980,7 +956,7 @@ editor.once('load', () => { break; case 'text': - field = new LegacyTextAreaField(); + field = createTextAreaInput(); field.value = args.value || ''; field.flexGrow = 1; @@ -996,14 +972,14 @@ editor.once('load', () => { case 'number': if (args.enum) { - field = new LegacySelectField({ + field = createSelectInput({ options: args.enum, type: 'number' }); } else if (args.slider) { - field = new LegacySlider(); + field = createSliderInput(); } else { - field = new LegacyNumberField(); + field = createNumberInput(); } field.value = args.value || 0; @@ -1040,13 +1016,13 @@ editor.once('load', () => { case 'checkbox': if (args.enum) { - field = new LegacySelectField({ + field = createSelectInput({ options: args.enum, type: 'boolean' }); field.flexGrow = 1; } else { - field = new LegacyCheckbox(); + field = createCheckbox(); } field.value = args.value || 0; @@ -1064,7 +1040,7 @@ editor.once('load', () => { field = []; for (let i = 0; i < channels; i++) { - field[i] = new LegacyNumberField(); + field[i] = createNumberInput(); field[i].flexGrow = 1; field[i].style.width = '24px'; field[i].value = (args.value && args.value[i]) || 0; @@ -1098,524 +1074,64 @@ editor.once('load', () => { break; case 'rgb': - field = new LegacyColorField(); + field = createColorInput(args); if (args.channels != null) { field.channels = args.channels; } linkField(); - - var colorPickerOn = false; - field.on('click', () => { - colorPickerOn = true; - - // set picker color - editor.call('picker:color', field.value); - - // picking starts - const evtColorPickStart = editor.on('picker:color:start', () => { - }); - - // picked color - const evtColorPick = editor.on('picker:color', (color) => { - field.value = color; - }); - - // position picker - const rectPicker = editor.call('picker:color:rect'); - const rectField = field.element.getBoundingClientRect(); - editor.call('picker:color:position', rectField.left - rectPicker.width, rectField.top); - - // color changed, update picker - const evtColorToPicker = field.on('change', function () { - editor.call('picker:color:set', this.value); - }); - - // picker closed - editor.once('picker:color:close', () => { - evtColorPick.unbind(); - evtColorPickStart.unbind(); - evtColorToPicker.unbind(); - colorPickerOn = false; - field.element.focus(); - }); - }); - - // close picker if field destroyed - field.on('destroy', () => { - if (colorPickerOn) { - editor.call('picker:color:close'); - } - }); - panel.append(field); break; case 'asset': - field = new LegacyImageField({ - canvas: args.kind === 'material' || args.kind === 'model' || args.kind === 'cubemap' || args.kind === 'font' || args.kind === 'sprite' - }); - var evtPick; - - if (label) { - label.renderChanges = false; - field._label = label; - - label.style.width = '32px'; - label.flexGrow = 1; - } - - - var panelFields = document.createElement('div'); - panelFields.classList.add('top'); - - var panelControls = document.createElement('div'); - panelControls.classList.add('controls'); - - var fieldTitle = field._title = new LegacyLabel(); - fieldTitle.text = 'Empty'; - fieldTitle.parent = panel; - fieldTitle.flexGrow = 1; - fieldTitle.placeholder = '...'; - - var btnEdit = new LegacyButton({ - text: '' - }); - btnEdit.disabled = true; - btnEdit.parent = panel; - btnEdit.flexGrow = 0; - - var btnRemove = new LegacyButton({ - text: '' - }); - btnRemove.disabled = true; - btnRemove.parent = panel; - btnRemove.flexGrow = 0; - - fieldTitle.on('click', () => { - const asset = editor.call('assets:get', field.value); - editor.call('picker:asset', { - type: args.kind, - currentAsset: asset - }); - - evtPick = editor.once('picker:asset', (asset) => { - const oldValues = { }; - if (args.onSet && args.link && args.link instanceof Array) { - for (let i = 0; i < args.link.length; i++) { - let id = 0; - if (args.link[i]._type === 'asset') { - id = args.link[i].get('id'); - } else if (args.link[i]._type === 'entity') { - id = args.link[i].get('resource_id'); - } else { - continue; - } - - oldValues[id] = args.link[i].get(pathAt(args, i)); - } - } - - field.emit('beforechange', asset.get('id')); - field.value = asset.get('id'); - evtPick = null; - if (args.onSet) { - args.onSet(asset, oldValues); - } - }); - - editor.once('picker:asset:close', () => { - if (evtPick) { - evtPick.unbind(); - evtPick = null; + field = createAssetInput({ + assets: editor.call('assets:raw'), + assetType: args.kind || args.assetType || '*', + allowDragDrop: true, + validateAssetFn: (asset: Observer) => { + if (legacyScripts && asset.get('type') === 'script') { + return false; } - field.element.focus(); - }); - }); - - field.on('click', function () { - if (!this.value) { - return; - } - - const asset = editor.call('assets:get', this.value); - if (!asset) { - return; - } - editor.call('selector:set', 'asset', [asset]); - - if (legacyScripts && asset.get('type') === 'script') { - editor.call('assets:panel:currentFolder', 'scripts'); - } else { - const path = asset.get('path'); - if (path.length) { - editor.call('assets:panel:currentFolder', editor.call('assets:get', path[path.length - 1])); - } else { - editor.call('assets:panel:currentFolder', null); - } - } - }); - btnEdit.on('click', () => { - field.emit('click'); - }); - btnRemove.on('click', () => { - field.emit('beforechange', null); - field.value = null; + return args.filterFn ? args.filterFn(asset) : true; + }, + dragEnterFn: args.over, + dragLeaveFn: args.leave }); - - var previewRenderer; - var renderQueued; - var queueRender; - - var evtThumbnailChange; - var updateThumbnail = function (empty?: boolean) { - const asset = editor.call('assets:get', field.value); - - if (previewRenderer) { - previewRenderer.destroy(); - previewRenderer = null; - } - - if (empty) { - field.image = ''; - } else if (!asset) { - field.image = `${config.url.home}/editor/scene/img/asset-placeholder-texture.png`; - } else { - if (asset.has('thumbnails.m')) { - const src = asset.get('thumbnails.m'); - if (src.startsWith('data:image/png;base64')) { - field.image = asset.get('thumbnails.m'); - } else { - field.image = buildQueryUrl(config.url.home + asset.get('thumbnails.m'), { t: asset.get('file.hash') }); - } - } else { - field.image = `/editor/scene/img/asset-placeholder-${asset.get('type')}.png`; - } - - if (args.kind === 'material') { - previewRenderer = new MaterialThumbnailRenderer(asset, field.elementImage); - } else if (args.kind === 'model') { - previewRenderer = new ModelThumbnailRenderer(asset, field.elementImage); - } else if (args.kind === 'cubemap') { - previewRenderer = new CubemapThumbnailRenderer(asset, field.elementImage, editor.call('assets:raw')); - } else if (args.kind === 'font') { - previewRenderer = new FontThumbnailRenderer(asset, field.elementImage); - } else if (args.kind === 'sprite') { - previewRenderer = new SpriteThumbnailRenderer(asset, field.elementImage, editor.call('assets:raw')); - } - } - - if (queueRender) { - queueRender(); - } - }; - - if (args.kind === 'material' || args.kind === 'model' || args.kind === 'font' || args.kind === 'sprite' || args.kind === 'cubemap') { - if (args.kind !== 'sprite' && args.kind !== 'cubemap') { - field.elementImage.classList.add('flipY'); - } - - const renderPreview = function () { - renderQueued = false; - - if (previewRenderer) { - // render - previewRenderer.render(); - } else { - let ctx = field.elementImage.ctx; - if (!ctx) { - ctx = field.elementImage.ctx = field.elementImage.getContext('2d'); - } - - ctx.clearRect(0, 0, field.elementImage.width, field.elementImage.height); - } - }; - - renderPreview(); - - queueRender = function () { - if (renderQueued) { - return; - } - renderQueued = true; - requestAnimationFrame(renderPreview); - }; - - field.once('destroy', () => { - if (previewRenderer) { - previewRenderer.destroy(); - previewRenderer = null; - } - }); - } - + field.flexGrow = 1; linkField(); - - var updateField = function () { - const value = field.value; - - fieldTitle.text = field.class.contains('null') ? 'various' : 'Empty'; - - btnEdit.disabled = !value; - btnRemove.disabled = !value && !field.class.contains('null'); - - if (evtThumbnailChange) { - evtThumbnailChange.unbind(); - evtThumbnailChange = null; - } - - if (!value) { - if (field.class.contains('star')) { - fieldTitle.text = `* ${fieldTitle.text}`; - } - - field.empty = true; - updateThumbnail(true); - - return; - } - - field.empty = false; - - const asset = editor.call('assets:get', value); - - if (!asset) { - return updateThumbnail(); - } - - evtThumbnailChange = asset.on('file.hash.m:set', updateThumbnail); - updateThumbnail(); - - fieldTitle.text = asset.get('name'); - - if (field.class.contains('star')) { - fieldTitle.text = `* ${fieldTitle.text}`; - } - }; - field.on('change', updateField); - if (args.value) { field.value = args.value; } - - updateField(); - - var assetDropRef = editor.call('drop:target', { - ref: panel, - filter: function (type: string, data: { id?: string }) { - const rectA = root.innerElement.getBoundingClientRect(); - const rectB = panel.element.getBoundingClientRect(); - return data.id && (args.kind === '*' || type === `asset.${args.kind}`) && parseInt(data.id, 10) !== field.value && !editor.call('assets:get', parseInt(data.id, 10)).get('source') && rectB.top > rectA.top && rectB.bottom < rectA.bottom; - }, - drop: function (type: string, data: { id?: string }) { - if ((args.kind !== '*' && type !== `asset.${args.kind}`) || editor.call('assets:get', parseInt(data.id, 10)).get('source')) { - return; - } - - const oldValues = { }; - if (args.onSet && args.link && args.link instanceof Array) { - for (let i = 0; i < args.link.length; i++) { - let id = 0; - if (args.link[i]._type === 'asset') { - id = args.link[i].get('id'); - } else if (args.link[i]._type === 'entity') { - id = args.link[i].get('resource_id'); - } else { - continue; - } - - oldValues[id] = args.link[i].get(pathAt(args, i)); - } - } - - field.emit('beforechange', parseInt(data.id, 10)); - field.value = parseInt(data.id, 10); - - if (args.onSet) { - const asset = editor.call('assets:get', parseInt(data.id, 10)); - if (asset) { - args.onSet(asset, oldValues); - } - } - }, - over: function (type: string, data: { id?: string }) { - if (args.over) { - args.over(type, data); - } - }, - leave: function () { - if (args.leave) { - args.leave(); - } - } - }); - field.on('destroy', () => { - assetDropRef.destroy(); - if (evtThumbnailChange) { - evtThumbnailChange.unbind(); - evtThumbnailChange = null; - } - }); - - // thumbnail panel.append(field); - // right side - panel.append(panelFields); - // controls - panelFields.appendChild(panelControls); - // label - if (label) { - panel.innerElement.removeChild(label.element); - panelControls.appendChild(label.element); - } - panelControls.classList.remove('label-field'); - // edit - panelControls.appendChild(btnEdit.element); - // remove - panelControls.appendChild(btnRemove.element); - - // title - panelFields.appendChild(fieldTitle.element); break; - // entity picker case 'entity': - field = new LegacyLabel(); - field.class.add('add-entity'); - field.flexGrow = 1; - field.class.add('null'); - - field.text = 'Select Entity'; - field.placeholder = '...'; - - panel.append(field); - - var icon = document.createElement('span'); - icon.classList.add('icon'); - - icon.addEventListener('click', (e) => { - e.stopPropagation(); - - if (editor.call('permissions:write')) { - field.text = ''; - } - }); - - field.on('change', (value) => { - if (value) { - const entity = editor.call('entities:get', value); - if (!entity) { - field.text = null; - return; - } - - field.element.innerHTML = entity.get('name'); - field.element.appendChild(icon); - field.placeholder = ''; - - if (value !== 'various') { - field.class.remove('null'); - } - } else { - field.element.innerHTML = 'Select Entity'; - field.placeholder = '...'; - field.class.add('null'); - } - }); - - linkField(); - - var getCurrentEntity = function () { - let entity = null; - if (args.link) { - if (!(args.link instanceof Array)) { - args.link = [args.link]; - } - - // get initial value only if it's the same for all - // links otherwise set it to null - for (let i = 0, len = args.link.length; i < len; i++) { - const val = args.link[i].get(pathAt(args, i)); - if (entity !== val) { - if (entity) { - entity = null; - break; - } else { - entity = val; - } - } - } - } - - return entity; - }; - - field.on('click', () => { - var evtEntityPick = editor.once('picker:entity', (entity) => { - field.text = entity ? entity.get('resource_id') : null; - evtEntityPick = null; - }); - - const initialValue = getCurrentEntity(); - - editor.call('picker:entity', initialValue, args.filter || null); - - editor.once('picker:entity:close', () => { - if (evtEntityPick) { - evtEntityPick.unbind(); + field = createEntityInput({ + entities: editor.call('entities:raw'), + allowDragDrop: true, + pickEntityFn: (callback: (resourceId: string | null) => void) => { + let evtEntityPick = editor.once('picker:entity', (entity) => { + callback(entity ? entity.get('resource_id') : null); evtEntityPick = null; - } - }); - }); - - // highlight on hover - field.on('hover', () => { - const entity = getCurrentEntity(); - if (!entity) { - return; - } - - editor.call('entities:panel:highlight', entity, true); - - field.once('blur', () => { - editor.call('entities:panel:highlight', entity, false); - }); - - field.once('click', () => { - editor.call('entities:panel:highlight', entity, false); - }); - }); + }); - editor.call('drop:target', { - ref: field, - filter: function (type: string, data: { resource_id?: string }) { - const rectA = root.innerElement.getBoundingClientRect(); - const rectB = field.element.getBoundingClientRect(); - return type === 'entity' && data.resource_id !== field.value && rectB.top > rectA.top && rectB.bottom < rectA.bottom; - }, - drop: function (type: string, data: { resource_id?: string }) { - if (type !== 'entity') { - return; - } + editor.call('picker:entity', field.value, args.filter || null); - field.value = data.resource_id; - }, - over: function (type: string, data: { resource_id?: string }) { - if (args.over) { - args.over(type, data); - } - }, - leave: function () { - if (args.leave) { - args.leave(); - } + editor.once('picker:entity:close', () => { + if (evtEntityPick) { + evtEntityPick.unbind(); + evtEntityPick = null; + } + }); } }); - - + field.flexGrow = 1; + linkField(); + panel.append(field); break; + case 'image': panel.flex = false; @@ -1628,14 +1144,14 @@ editor.once('load', () => { break; case 'progress': - field = new LegacyProgress(); + field = createProgress(); field.flexGrow = 1; panel.append(field); break; case 'code': - field = new LegacyCode(); + field = createCode(); field.flexGrow = 1; if (args.value) { @@ -1646,7 +1162,7 @@ editor.once('load', () => { break; case 'button': - field = new LegacyButton(); + field = createButton(); field.flexGrow = 1; field.text = args.text || 'Button'; panel.append(field); @@ -1658,7 +1174,7 @@ editor.once('load', () => { break; case 'curveset': - field = new LegacyCurveField(args); + field = createCurveInput(args); field.flexGrow = 1; field.text = args.text || ''; @@ -1826,123 +1342,11 @@ editor.once('load', () => { break; case 'gradient': - field = new LegacyCurveField(args); + field = createGradientInput(args); field.flexGrow = 1; field.text = args.text || ''; - if (args.link) { - let link = args.link; - if (args.link instanceof Array) { - link = args.link[0]; - } - const path = pathAt(args, 0); - field.link(link, [path]); - } - - var gradientPickerVisible = false; - - var toggleGradientPicker = function () { - if (!field.class.contains('disabled') && !gradientPickerVisible) { - editor.call('picker:gradient', field.value, args); - - gradientPickerVisible = true; - - // position picker - const rectPicker = editor.call('picker:gradient:rect'); - const rectField = field.element.getBoundingClientRect(); - editor.call('picker:gradient:position', rectField.right - rectPicker.width, rectField.bottom); - - const evtPickerChanged = editor.on('picker:curve:change', (paths, values) => { - if (!field._link) { - return; - } - - const link = field._link; - - const previous = { - paths: [], - values: [] - }; - - let path; - for (let i = 0; i < paths.length; i++) { - // always use 0 because we do not support multiselect - path = pathAt(args, 0) + paths[i].substring(1); - previous.paths.push(path); - previous.values.push(field._link.get(path)); - } - - const undo = function () { - const item = link.latest(); - - if (!item) { - return; - } - - let history = false; - if (item.history) { - history = item.history.enabled; - item.history.enabled = false; - } - - for (let i = 0; i < previous.paths.length; i++) { - item.set(previous.paths[i], previous.values[i]); - } - - if (item.history) { - item.history.enabled = history; - } - }; - - const redo = function () { - const item = link.latest(); - - if (!item) { - return; - } - - let history = false; - if (item.history) { - history = item.history.enabled; - item.history.enabled = false; - } - - for (let i = 0; i < paths.length; i++) { - // always use 0 because we do not support multiselect - path = pathAt(args, 0) + paths[i].substring(1); - item.set(path, values[i]); - } - - if (item.history) { - item.history.enabled = history; - } - }; - - redo(); - - editor.api.globals.history.add({ - name: `${path}.curves`, - combine: false, - undo: undo, - redo: redo - }); - }); - - const evtRefreshPicker = field.on('change', (value) => { - editor.call('picker:gradient:set', value, args); - }); - - editor.once('picker:gradient:close', () => { - evtRefreshPicker.unbind(); - evtPickerChanged.unbind(); - gradientPickerVisible = false; - }); - } - }; - - // open curve editor on click - field.on('click', toggleGradientPicker); - + linkField(); panel.append(field); break; @@ -1953,7 +1357,7 @@ editor.once('load', () => { break; default: - field = new LegacyLabel(); + field = createLabel(); field.flexGrow = 1; field.text = args.value || ''; field.class.add('selectable'); @@ -1968,8 +1372,10 @@ editor.once('load', () => { break; } - if (args.className && field instanceof Element) { + if (args.className && field?.class) { field.class.add(args.className); + } else if (args.className && field?.classList) { + field.classList.add(args.className); } return field; @@ -2002,7 +1408,7 @@ editor.once('load', () => { // nothing selected if (items.length === 0) { - const label = new LegacyLabel({ text: 'Select anything to Inspect' }); + const label = createLabel({ text: 'Select anything to Inspect' }); label.style.display = 'block'; label.style.textAlign = 'center'; root.append(label); diff --git a/src/editor/attributes/attributes-pcui.ts b/src/editor/attributes/attributes-pcui.ts new file mode 100644 index 000000000..500029476 --- /dev/null +++ b/src/editor/attributes/attributes-pcui.ts @@ -0,0 +1,368 @@ +import { + BooleanInput, + Button, + Code, + Container, + Label, + NumericInput, + Overlay, + Panel, + Progress, + SelectInput, + SliderInput, + TextAreaInput, + TextInput +} from '@playcanvas/pcui'; + +import { toOptions } from '@/common/pcui/compat-utils'; +import { AssetInput } from '@/common/pcui/element/element-asset-input'; +import { ColorInput } from '@/common/pcui/element/element-color-input'; +import { CurveInput } from '@/common/pcui/element/element-curve-input'; +import { EntityInput } from '@/common/pcui/element/element-entity-input'; +import { GradientInput } from '@/common/pcui/element/element-gradient-input'; + +const defineAlias = (element: any, name: string, descriptor: PropertyDescriptor) => { + if (!Object.getOwnPropertyDescriptor(element, name)) { + Object.defineProperty(element, name, { + configurable: true, + ...descriptor + }); + } +}; + +const syncEnabledClass = (element: any) => { + if (element.enabled) { + element.class.remove('disabled'); + } else { + element.class.add('disabled'); + } +}; + +const withCompat = (element: any, ...classes: string[]) => { + for (const cls of classes) { + element.class.add(cls); + } + + element.element = element.dom; + if (element.domContent) { + element.innerElement = element.domContent; + element.domContent.classList.add('content'); + } + + if (element.input) { + element.elementInput = element.input; + } + + defineAlias(element, 'disabled', { + get() { + return !this.enabled; + }, + set(value: boolean) { + this.enabled = !value; + } + }); + + defineAlias(element, 'proxy', { + get() { + return this.dom.getAttribute('proxy'); + }, + set(value: string | null | undefined) { + if (value === null || value === undefined || value === '') { + this.dom.removeAttribute('proxy'); + } else { + this.dom.setAttribute('proxy', value); + } + } + }); + + defineAlias(element, 'WebkitFlexWrap', { + get() { + return this.style.webkitFlexWrap; + }, + set(value: string) { + this.style.webkitFlexWrap = value; + } + }); + + if (!element.focus) { + element.focus = () => element.dom.focus(); + } + + element.on('enable', () => syncEnabledClass(element)); + element.on('disable', () => syncEnabledClass(element)); + syncEnabledClass(element); + + return element; +}; + +const withPanelCompat = (panel: any, headerText = '') => { + withCompat(panel, 'ui-panel'); + + if (panel.header) { + panel.headerElement = panel.header.dom; + panel.headerElement.classList.add('ui-header'); + panel.headerElementTitle = panel.header.dom.querySelector('.pcui-panel-header-title'); + panel.headerElementTitle?.classList.add('title'); + panel.headerAppend = (element: any) => panel.header.append(element); + + defineAlias(panel, 'foldable', { + get() { + return this.collapsible; + }, + set(value: boolean) { + this.collapsible = !!value; + } + }); + + defineAlias(panel, 'folded', { + get() { + return this.collapsed; + }, + set(value: boolean) { + this.collapsed = !!value; + } + }); + } else { + panel.class.add('noHeader'); + panel.headerAppend = (element: any) => panel.append(element); + panel.headerElement = null; + panel.headerElementTitle = null; + panel.foldable = false; + panel.folded = false; + } + + if (headerText) { + panel.headerText = headerText; + } + + return panel; +}; + +const createPanel = (headerText = '') => { + if (headerText) { + const header = document.createElement('header'); + return withPanelCompat(new Panel({ header, headerText }), headerText); + } + + return withPanelCompat(new Container({ flex: true })); +}; + +const createButton = (args: any = {}) => withCompat(new Button({ + ...args, + unsafe: args.unsafe ?? true +}), 'ui-button'); + +const createCheckbox = (args: any = {}) => { + const field = withCompat(new BooleanInput(args), 'ui-checkbox', 'noSelect'); + const sync = (value: boolean | null) => { + field.class.toggle('checked', !!value); + field.class.toggle('null', value === null); + }; + + field._onLinkChange = sync; + field.on('change', sync); + sync(field.value); + + return field; +}; + +const createCode = (args: any = {}) => withCompat(new Code(args), 'ui-code'); + +const createColorInput = (args: any = {}) => withCompat(new ColorInput(args), 'ui-color-field'); + +const withCurveLink = (field: any, arrayValue = true) => { + field._link = null; + field._paths = []; + field._linkSetHandlers = []; + field.suspendEvents = false; + + field.link = (link: any, paths: string[]) => { + field.unlink(); + field._link = link; + field._paths = paths; + + const update = () => { + if (field.suspendEvents) { + return; + } + + const values = paths.map((path) => { + const value = link.get(path); + return value !== undefined ? value : null; + }); + field.value = arrayValue ? values : values[0]; + }; + + field._linkSetHandlers.push(link.on('*:set', (path: string) => { + if (paths.some(p => path.indexOf(p) === 0)) { + update(); + } + })); + + update(); + }; + + field.unlink = () => { + field._linkSetHandlers.forEach((handler: any) => handler.unbind()); + field._linkSetHandlers.length = 0; + field._link = null; + field._paths = []; + }; + + return field; +}; + +const createCurveInput = (args: any = {}) => { + const field = withCurveLink(withCompat(new CurveInput(args), 'ui-curve-field')); + field._openCurvePicker = () => {}; + return field; +}; + +const createGradientInput = (args: any = {}) => withCurveLink(withCompat(new GradientInput(args), 'ui-curve-field'), false); + +const createAssetInput = (args: any = {}) => withCompat(new AssetInput(args), 'ui-image-field'); + +const createEntityInput = (args: any = {}) => { + const field = withCompat(new EntityInput(args), 'ui-label', 'add-entity'); + defineAlias(field, 'text', { + get() { + return this._label?.value; + }, + set(value: string) { + this._label.value = value || ''; + } + }); + + if (args.pickEntityFn) { + field._pickEntity = args.pickEntityFn; + } + return field; +}; + +const createLabel = (args: any = {}) => withCompat(new Label(args), 'ui-label'); + +const createList = () => { + const list = withCompat(new Container({ flex: true }), 'ui-list'); + const append = list.append.bind(list); + const appendAfter = list.appendAfter.bind(list); + const bind = (item: any) => item.on('click', () => list.emit('select', item)); + + list.append = (item: any) => { + bind(item); + append(item); + }; + + list.appendAfter = (item: any, after: any) => { + bind(item); + appendAfter(item, after); + }; + + return list; +}; + +const createListItem = (args: any = {}) => { + const item = withCompat(new Container({ flex: true }), 'ui-list-item'); + const label = createLabel({ text: args.text || '' }); + item.append(label); + item.isListItem = true; + + defineAlias(item, 'text', { + get() { + return label.text; + }, + set(value: string) { + label.text = value; + } + }); + + return item; +}; + +const createOverlay = (args: any = {}) => withCompat(new Overlay(args), 'ui-overlay'); + +const createNumberInput = (args: any = {}) => withCompat(new NumericInput(args), 'ui-number-field'); + +const createProgress = (args: any = {}) => withCompat(new Progress(args), 'ui-progress'); + +const createSelectInput = (args: any = {}) => { + let options = toOptions(args.options, args.type); + const hidden = new Set(); + const field = withCompat(new SelectInput({ + ...args, + options, + defaultValue: args.default, + value: args.default + }), 'ui-select-field', 'noSelect'); + const applyOptions = () => { + field.options = options.filter((option: any) => !hidden.has(option.v)); + }; + + field.optionElements = { + '': { + style: { + set display(value: string) { + if (value === 'none') { + hidden.add(''); + } else { + hidden.delete(''); + } + applyOptions(); + }, + get display() { + return hidden.has('') ? 'none' : ''; + } + } + } + }; + field._updateOptions = (value: any = args.options) => { + options = toOptions(value, args.type); + applyOptions(); + }; + + return field; +}; + +const createSliderInput = (args: any = {}) => { + const field = withCompat(new SliderInput(args), 'ui-slider'); + const onSlideStart = field._onSlideStart.bind(field); + const onSlideEnd = field._onSlideEnd.bind(field); + + field._onSlideStart = (pageX: number) => { + field.emit('start'); + onSlideStart(pageX); + }; + + field._onSlideEnd = (pageX: number) => { + onSlideEnd(pageX); + field.emit('end'); + }; + + return field; +}; + +const createTextAreaInput = (args: any = {}) => withCompat(new TextAreaInput(args), 'ui-textarea-field'); + +const createTextInput = (args: any = {}) => withCompat(new TextInput(args), 'ui-text-field'); + +export { + createAssetInput, + createButton, + createCheckbox, + createCode, + createColorInput, + createCurveInput, + createEntityInput, + createGradientInput, + createLabel, + createList, + createListItem, + createOverlay, + createNumberInput, + createPanel, + createProgress, + createSelectInput, + createSliderInput, + createTextAreaInput, + createTextInput, + toOptions +}; diff --git a/src/editor/attributes/reference/reference.ts b/src/editor/attributes/reference/reference.ts index 6726bd5f8..ba8384331 100644 --- a/src/editor/attributes/reference/reference.ts +++ b/src/editor/attributes/reference/reference.ts @@ -1,4 +1,4 @@ -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; import type { AttributeReference, LegacyAttributeReference } from './reference.type'; @@ -53,7 +53,7 @@ editor.once('load', () => { }); editor.method('attributes:reference', (attr: LegacyAttributeReference) => { - const tooltip = new LegacyTooltip({ + const tooltip = new TooltipHandle({ align: 'right' }); tooltip.hoverable = true; @@ -89,22 +89,26 @@ editor.once('load', () => { let timerHover = null; let timerBlur = null; - tooltip.attach = function (args: { - target?: HTMLElement, + (tooltip as any).attach = function (args: { + target?: any, element?: HTMLElement, - panel?: HTMLElement + panel?: any }) { let target = args.target; - let element = args.element; - let targetPanel = args.panel || panel; - targetPanel = targetPanel.dom || targetPanel.element; + let element = args.element || target?.element || target?.dom || target; + let targetPanel: any = args.panel || panel; + targetPanel = targetPanel.dom || targetPanel.element || targetPanel; + + if (!target || !element) { + return; + } const show = function () { if (!target || target.hidden) { return; } // fix top offset for new framework - const topOffset = (element.ui instanceof Element ? 6 : 16); + const topOffset = ((element as any).ui instanceof Element ? 6 : 16); tooltip.position(targetPanel.getBoundingClientRect().left, element.getBoundingClientRect().top + topOffset); tooltip.hidden = false; }; diff --git a/src/editor/chat/chat-system.ts b/src/editor/chat/chat-system.ts index a6e5f4a83..0bd65bf70 100644 --- a/src/editor/chat/chat-system.ts +++ b/src/editor/chat/chat-system.ts @@ -1,4 +1,4 @@ -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; editor.once('load', () => { const root = editor.call('layout.root'); @@ -123,7 +123,7 @@ editor.once('load', () => { const date = new Date(); - element.tooltip = LegacyTooltip.attach({ + element.tooltip = TooltipHandle.attach({ target: img, text: `${(`00${date.getHours()}`).slice(-2)}:${(`00${date.getMinutes()}`).slice(-2)}`, align: 'right', diff --git a/src/editor/chat/chat-widget.ts b/src/editor/chat/chat-widget.ts index 333352019..3d2394a6d 100644 --- a/src/editor/chat/chat-widget.ts +++ b/src/editor/chat/chat-widget.ts @@ -1,6 +1,6 @@ import { Panel, Button, Container, TextInput } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; editor.once('load', () => { const root = editor.call('layout.root'); @@ -42,7 +42,7 @@ editor.once('load', () => { }); chatPanel.header.append(notify); - const tooltipNotify = LegacyTooltip.attach({ + const tooltipNotify = TooltipHandle.attach({ target: notify.dom, text: 'Notifications (enabled)', align: 'bottom', diff --git a/src/editor/entities/entities-control.ts b/src/editor/entities/entities-control.ts index f479e68b6..68fe8fd30 100644 --- a/src/editor/entities/entities-control.ts +++ b/src/editor/entities/entities-control.ts @@ -1,6 +1,6 @@ import { Menu, Container, Button } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; editor.once('load', () => { const root = editor.call('layout.root'); @@ -35,7 +35,7 @@ editor.once('load', () => { }); controls.append(btnAdd); - LegacyTooltip.attach({ + TooltipHandle.attach({ target: btnAdd.dom, text: 'Add Entity', align: 'top', @@ -57,7 +57,7 @@ editor.once('load', () => { }); controls.append(btnDuplicate); - const tooltipDuplicate = LegacyTooltip.attach({ + const tooltipDuplicate = TooltipHandle.attach({ target: btnDuplicate.dom, text: 'Duplicate Entity', align: 'top', @@ -80,7 +80,7 @@ editor.once('load', () => { }); controls.append(btnDelete); - const tooltipDelete = LegacyTooltip.attach({ + const tooltipDelete = TooltipHandle.attach({ target: btnDelete.dom, text: 'Delete Entity', align: 'top', @@ -99,7 +99,7 @@ editor.once('load', () => { }); controls.append(btnMore); - LegacyTooltip.attach({ + TooltipHandle.attach({ target: btnMore.dom, text: 'More Options', align: 'top', diff --git a/src/editor/entities/entities-panel.ts b/src/editor/entities/entities-panel.ts index fb3adbcaf..7783d216a 100644 --- a/src/editor/entities/entities-panel.ts +++ b/src/editor/entities/entities-panel.ts @@ -1,7 +1,7 @@ import type { Observer } from '@playcanvas/observer'; import { Button, Container, TreeViewItem } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; import { EntitiesTreeView } from './entities-treeview'; @@ -11,7 +11,7 @@ const CLASS_ROW_HOVERED = 'entities-treeview-row-hovered'; interface EyeEntry { button: Button; - tooltip: LegacyTooltip; + tooltip: TooltipHandle; onMouseEnter: () => void; onMouseLeave: () => void; contentsRow: HTMLElement; @@ -66,7 +66,7 @@ editor.once('load', () => { ignoreParent: true }); - const tooltip = LegacyTooltip.attach({ + const tooltip = TooltipHandle.attach({ target: button.dom, text: 'Hide in viewport', align: 'left', diff --git a/src/editor/hotkey/hotkey.ts b/src/editor/hotkey/hotkey.ts index 67f7d6a20..393de8b61 100644 --- a/src/editor/hotkey/hotkey.ts +++ b/src/editor/hotkey/hotkey.ts @@ -1,5 +1,3 @@ -import { LegacyList } from '@/common/ui/list'; - editor.once('load', () => { // State management const hotkeys = new Map(); @@ -155,8 +153,4 @@ editor.once('load', () => { ['keyup', 'mousedown', 'mouseup', 'click'].forEach((eventName: string) => { window.addEventListener(eventName, updateModifierState, false); }); - - // Legacy support - LegacyList._ctrl = () => modifierState.ctrl; - LegacyList._shift = () => modifierState.shift; }); diff --git a/src/editor/inspector/assets/texture.ts b/src/editor/inspector/assets/texture.ts index 24614d8e0..cc72ba8ca 100644 --- a/src/editor/inspector/assets/texture.ts +++ b/src/editor/inspector/assets/texture.ts @@ -319,7 +319,7 @@ const DOM = parent => [ ] }, { root: { - compressionLegacyContainer: new Container() + compressionLegacySection: new Container() }, children: [ { @@ -553,7 +553,7 @@ class TextureAssetInspector extends Container { _basisDivider: Divider; - _compressionLegacyContainer: Container; + _compressionLegacySection: Container; _compressionLegacyAttributesInspector: AttributesInspector; diff --git a/src/editor/inspector/attributes-inspector.ts b/src/editor/inspector/attributes-inspector.ts index 5108277c5..8cc7cc23a 100644 --- a/src/editor/inspector/attributes-inspector.ts +++ b/src/editor/inspector/attributes-inspector.ts @@ -2,8 +2,7 @@ import type { Observer, ObserverList } from '@playcanvas/observer'; import { Element, type IBindable, Container, LabelGroup, Panel, Button, ArrayInput, BindingTwoWay, Label, type ContainerArgs } from '@playcanvas/pcui'; import { AssetInput } from '@/common/pcui/element/element-asset-input'; -import { tooltip, tooltipRefItem } from '@/common/tooltips'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { tooltip, tooltipRefItem, TooltipHandle } from '@/common/tooltips'; import type { History } from '@/editor-api'; import type { Attribute, Divider } from './attribute.type.d'; @@ -28,8 +27,8 @@ const isEnabledAttribute = ({ label, type }: { label?: string; type?: string }) const CLASS_ROOT = 'pcui-inspector'; -let tooltipCopy: LegacyTooltip | null = null; -let tooltipPaste: LegacyTooltip | null = null; +let tooltipCopy: TooltipHandle | null = null; +let tooltipPaste: TooltipHandle | null = null; class AttributesInspector extends Container { private _observers: Observer[] | null; @@ -286,7 +285,7 @@ class AttributesInspector extends Container { // tooltip on hover for copy if (!tooltipCopy) { - tooltipCopy = LegacyTooltip.create({ + tooltipCopy = TooltipHandle.make({ text: 'Copy', align: 'bottom', class: 'pcui-tooltip-clipboard', @@ -296,7 +295,7 @@ class AttributesInspector extends Container { // tooltip on hover for paste if (!tooltipPaste) { - tooltipPaste = LegacyTooltip.create({ + tooltipPaste = TooltipHandle.make({ text: 'Paste', align: 'bottom', class: 'pcui-tooltip-clipboard', diff --git a/src/editor/inspector/components/light.ts b/src/editor/inspector/components/light.ts index e96ba05fb..54f96ad6d 100644 --- a/src/editor/inspector/components/light.ts +++ b/src/editor/inspector/components/light.ts @@ -14,7 +14,7 @@ import { SHADOWUPDATE_THISFRAME } from 'playcanvas'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; import type { EntityObserver } from '@/editor-api'; import { ComponentInspector, type ComponentInspectorArgs } from './component'; @@ -494,7 +494,7 @@ class LightComponentInspector extends ComponentInspector { }); this._field('shadowUpdateMode').parent.append(this._btnUpdateShadow); - const tooltip = LegacyTooltip.attach({ + const tooltip = TooltipHandle.attach({ target: this._btnUpdateShadow.dom, text: 'Update Shadows', align: 'bottom', diff --git a/src/editor/inspector/components/script.ts b/src/editor/inspector/components/script.ts index c5ff206b1..9ec702bea 100644 --- a/src/editor/inspector/components/script.ts +++ b/src/editor/inspector/components/script.ts @@ -3,8 +3,7 @@ import { Panel, Container, Button, BooleanInput, LabelGroup, Label, Menu, Select import { CLASS_ERROR, DEFAULTS } from '@/common/pcui/constants'; import { AssetInput } from '@/common/pcui/element/element-asset-input'; -import { tooltip, tooltipSimpleItem } from '@/common/tooltips'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { tooltip, tooltipSimpleItem, TooltipHandle } from '@/common/tooltips'; import { deepCopy } from '@/common/utils'; import type { AssetObserver, EntityObserver, History, LocalStorage } from '@/editor-api'; @@ -195,7 +194,7 @@ class ScriptInspector extends Panel { this.header.append(this._btnEdit); this._btnEdit.on('click', this._onClickEdit.bind(this)); - const tooltipEdit = LegacyTooltip.attach({ + const tooltipEdit = TooltipHandle.attach({ target: this._btnEdit.dom, text: editor.call('permissions:write') ? 'Edit' : 'View', align: 'bottom', @@ -215,7 +214,7 @@ class ScriptInspector extends Panel { this.header.append(this._btnParse); this._btnParse.on('click', this._onClickParse.bind(this)); - const tooltipParse = LegacyTooltip.attach({ + const tooltipParse = TooltipHandle.attach({ target: this._btnParse.dom, text: 'Parse', align: 'bottom', diff --git a/src/editor/inspector/settings-panels/import-map.ts b/src/editor/inspector/settings-panels/import-map.ts index bb9dfe2d6..6f7a1f0a2 100644 --- a/src/editor/inspector/settings-panels/import-map.ts +++ b/src/editor/inspector/settings-panels/import-map.ts @@ -1,6 +1,6 @@ import { Container, Button } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; import { BaseSettingsPanel, type BaseSettingsPanelArgs } from './base'; import type { Attribute } from '../attribute.type.d'; @@ -51,9 +51,9 @@ class ImportMapSettingsPanel extends BaseSettingsPanel { _buttonContainer: Container; - _selectExistingTooltip: LegacyTooltip; + _selectExistingTooltip: TooltipHandle; - _createDefaultTooltip: LegacyTooltip; + _createDefaultTooltip: TooltipHandle; constructor(args: BaseSettingsPanelArgs) { args = Object.assign({}, args); @@ -74,14 +74,14 @@ class ImportMapSettingsPanel extends BaseSettingsPanel { }); - this._selectExistingTooltip = LegacyTooltip.attach({ + this._selectExistingTooltip = TooltipHandle.attach({ target: this._selectExistingButton.dom, text: 'Select an existing Import Map', align: 'bottom', root: editor.call('layout.root') }); - this._createDefaultTooltip = LegacyTooltip.attach({ + this._createDefaultTooltip = TooltipHandle.attach({ target: this._createDefaultButton.dom, text: 'Create a default Import Map', align: 'bottom', diff --git a/src/editor/inspector/settings-panels/layers-layer-panel.ts b/src/editor/inspector/settings-panels/layers-layer-panel.ts index 09a6e6d2e..a6b83fcbe 100644 --- a/src/editor/inspector/settings-panels/layers-layer-panel.ts +++ b/src/editor/inspector/settings-panels/layers-layer-panel.ts @@ -1,5 +1,5 @@ import { CLASS_ERROR } from '@/common/pcui/constants'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; import { BaseSettingsPanel, type BaseSettingsPanelArgs } from './base'; import type { Attribute } from '../attribute.type.d'; @@ -96,7 +96,7 @@ class LayersSettingsPanelLayerPanel extends BaseSettingsPanel { let deleteTooltip; if (!this.enabled) { - deleteTooltip = LegacyTooltip.attach({ + deleteTooltip = TooltipHandle.attach({ target: this._btnRemove.dom, text: 'You cannot delete a built-in layer', align: 'bottom', diff --git a/src/editor/inspector/settings-panels/layers-render-order-list.ts b/src/editor/inspector/settings-panels/layers-render-order-list.ts index bfee40240..2ea944107 100644 --- a/src/editor/inspector/settings-panels/layers-render-order-list.ts +++ b/src/editor/inspector/settings-panels/layers-render-order-list.ts @@ -1,7 +1,7 @@ import { Observer, type EventHandle } from '@playcanvas/observer'; import { Container, Panel, Label, BooleanInput, type ContainerArgs } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; interface RenderOrderListArgs extends ContainerArgs { settings?: Observer; @@ -143,7 +143,7 @@ class LayersSettingsPanelRenderOrderList extends Container { let deleteTooltip; if (layer.layer < 1000) { - deleteTooltip = LegacyTooltip.attach({ + deleteTooltip = TooltipHandle.attach({ target: layerPanel._btnRemove.dom, text: 'You cannot delete a built-in layer', align: 'bottom', diff --git a/src/editor/inspector/settings-panels/loading-screen.ts b/src/editor/inspector/settings-panels/loading-screen.ts index c978a04a8..8e000b8df 100644 --- a/src/editor/inspector/settings-panels/loading-screen.ts +++ b/src/editor/inspector/settings-panels/loading-screen.ts @@ -1,6 +1,6 @@ import { Container, Button } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; import type { Asset } from '@/editor-api'; import { BaseSettingsPanel, type BaseSettingsPanelArgs } from './base'; @@ -52,9 +52,9 @@ class LoadingScreenSettingsPanel extends BaseSettingsPanel { _buttonContainer: Container; - _selectExistingTooltip: LegacyTooltip; + _selectExistingTooltip: TooltipHandle; - _createDefaultTooltip: LegacyTooltip; + _createDefaultTooltip: TooltipHandle; constructor(args: BaseSettingsPanelArgs) { args = Object.assign({}, args); @@ -75,14 +75,14 @@ class LoadingScreenSettingsPanel extends BaseSettingsPanel { }); - this._selectExistingTooltip = LegacyTooltip.attach({ + this._selectExistingTooltip = TooltipHandle.attach({ target: this._selectExistingButton.dom, text: 'Select an existing loading screen script', align: 'bottom', root: editor.call('layout.root') }); - this._createDefaultTooltip = LegacyTooltip.attach({ + this._createDefaultTooltip = TooltipHandle.attach({ target: this._createDefaultButton.dom, text: 'Create a default loading script', align: 'bottom', diff --git a/src/editor/pickers/picker-asset.ts b/src/editor/pickers/picker-asset.ts index 2f0216f7f..57af707ba 100644 --- a/src/editor/pickers/picker-asset.ts +++ b/src/editor/pickers/picker-asset.ts @@ -1,14 +1,14 @@ import type { Observer } from '@playcanvas/observer'; - -import { LegacyOverlay } from '@/common/ui/overlay'; +import { Overlay } from '@playcanvas/pcui'; editor.once('load', () => { const legacyScripts = editor.call('settings:project').get('useLegacyScripts'); - const overlay = new LegacyOverlay(); - overlay.class.add('picker-asset'); - overlay.center = false; - overlay.hidden = true; + const overlay = new Overlay({ + class: 'picker-asset', + clickable: true, + hidden: true + }); const root = editor.call('layout.root'); root.append(overlay); diff --git a/src/editor/pickers/picker-color.ts b/src/editor/pickers/picker-color.ts index b5c797f10..85e67de65 100644 --- a/src/editor/pickers/picker-color.ts +++ b/src/editor/pickers/picker-color.ts @@ -1,6 +1,5 @@ -import { LegacyNumberField } from '@/common/ui/number-field'; -import { LegacyOverlay } from '@/common/ui/overlay'; -import { LegacyTextField } from '@/common/ui/text-field'; +import { NumericInput, Overlay, TextInput } from '@playcanvas/pcui'; + import { hsv2rgb, rgb2hsv } from '@/core/color'; editor.once('load', () => { @@ -200,11 +199,13 @@ editor.once('load', () => { // overlay - const overlay = new LegacyOverlay(); - overlay.class.add('picker-color'); - overlay.center = false; - overlay.transparent = true; - overlay.hidden = true; + const overlay = new Overlay({ + class: 'picker-color', + clickable: true, + hidden: true, + transparent: true + }); + overlay.domContent.classList.add('content'); // rectangular picker @@ -296,73 +297,78 @@ editor.once('load', () => { // R - const fieldR = new LegacyNumberField({ + const fieldR = new NumericInput({ precision: 1, step: 1, min: 0, - max: 255 + max: 255, + hideSlider: true, + renderChanges: false, + placeholder: 'r', + flexGrow: 1 }); channels.push(fieldR); - fieldR.renderChanges = false; - fieldR.placeholder = 'r'; - fieldR.flexGrow = 1; fieldR.class.add('field', 'field-r'); fieldR.on('change', updateRects); - panelFields.appendChild(fieldR.element); + panelFields.appendChild(fieldR.dom); // G - const fieldG = new LegacyNumberField({ + const fieldG = new NumericInput({ precision: 1, step: 1, min: 0, - max: 255 + max: 255, + hideSlider: true, + renderChanges: false, + placeholder: 'g' }); channels.push(fieldG); - fieldG.renderChanges = false; - fieldG.placeholder = 'g'; fieldG.class.add('field', 'field-g'); fieldG.on('change', updateRects); - panelFields.appendChild(fieldG.element); + panelFields.appendChild(fieldG.dom); // B - const fieldB = new LegacyNumberField({ + const fieldB = new NumericInput({ precision: 1, step: 1, min: 0, - max: 255 + max: 255, + hideSlider: true, + renderChanges: false, + placeholder: 'b' }); channels.push(fieldB); - fieldB.renderChanges = false; - fieldB.placeholder = 'b'; fieldB.class.add('field', 'field-b'); fieldB.on('change', updateRects); - panelFields.appendChild(fieldB.element); + panelFields.appendChild(fieldB.dom); // A - var fieldA = new LegacyNumberField({ + var fieldA = new NumericInput({ precision: 1, step: 1, min: 0, - max: 255 + max: 255, + hideSlider: true, + renderChanges: false, + placeholder: 'a' }); channels.push(fieldA); - fieldA.renderChanges = false; - fieldA.placeholder = 'a'; fieldA.class.add('field', 'field-a'); fieldA.on('change', updateRectAlpha); - panelFields.appendChild(fieldA.element); + panelFields.appendChild(fieldA.dom); // HEX - var fieldHex = new LegacyTextField(); - fieldHex.renderChanges = false; - fieldHex.placeholder = '#'; + var fieldHex = new TextInput({ + renderChanges: false, + placeholder: '#' + }); fieldHex.class.add('field', 'field-hex'); fieldHex.on('change', () => { updateHex(); }); - panelFields.appendChild(fieldHex.element); + panelFields.appendChild(fieldHex.dom); const root = editor.call('layout.root'); @@ -420,11 +426,10 @@ editor.once('load', () => { overlay.hidden = false; // focus on hex field - fieldHex.elementInput.focus(); + fieldHex.focus(); setTimeout(() => { - fieldHex.elementInput.focus(); - fieldHex.elementInput.select(); + fieldHex.focus(true); }, 100); }); @@ -433,7 +438,7 @@ editor.once('load', () => { }); editor.method('picker:color:rect', () => { - return overlay.rect; + return overlay.domContent.getBoundingClientRect(); }); // position color picker diff --git a/src/editor/pickers/picker-curve.ts b/src/editor/pickers/picker-curve.ts index a5686c957..0ef8d8b76 100644 --- a/src/editor/pickers/picker-curve.ts +++ b/src/editor/pickers/picker-curve.ts @@ -1,14 +1,7 @@ +import { BooleanInput, Button, Canvas, Container, Label, NumericInput, Overlay, SelectInput } from '@playcanvas/pcui'; import { Curve, CurveSet, math } from 'playcanvas'; -import { LegacyButton } from '@/common/ui/button'; -import { LegacyCanvas } from '@/common/ui/canvas'; -import { LegacyCheckbox } from '@/common/ui/checkbox'; -import { LegacyLabel } from '@/common/ui/label'; -import { LegacyNumberField } from '@/common/ui/number-field'; -import { LegacyOverlay } from '@/common/ui/overlay'; -import { LegacyPanel } from '@/common/ui/panel'; -import { LegacySelectField } from '@/common/ui/select-field'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; editor.once('load', () => { // used to disable event handlers @@ -18,11 +11,13 @@ editor.once('load', () => { let changing = false; // overlay - const overlay = new LegacyOverlay(); - overlay.class.add('picker-curve'); - overlay.center = false; - overlay.transparent = true; - overlay.hidden = true; + const overlay = new Overlay({ + class: 'picker-curve', + clickable: true, + hidden: true, + transparent: true + }); + overlay.domContent.classList.add('content'); // color variables const colors = { @@ -87,14 +82,20 @@ editor.once('load', () => { overlay.append(panel); // header - const header = new LegacyPanel(); + const header = new Container({ + flex: true, + flexDirection: 'row' + }); header.class.add('picker-curve-header'); + header.domContent.classList.add('content'); - panel.appendChild(header.element); + panel.appendChild(header.dom); - header.append(new LegacyLabel({ + const labelType = new Label({ text: 'Type' - })); + }); + labelType.class.add('ui-label'); + header.append(labelType); // esc to close @@ -111,19 +112,20 @@ editor.once('load', () => { // type selector - const fieldType = new LegacySelectField({ - options: { - 0: 'Linear', - 1: 'Smooth Step', - 2: 'Legacy Spline', // catmull, deprecated - // 3: 'Spline (Legacy)', // cardinal, deprecated - 4: 'Spline', // spline - 5: 'Step' - }, + const fieldType = new SelectInput({ + options: [ + { v: 0, t: 'Linear' }, + { v: 1, t: 'Smooth Step' }, + { v: 2, t: 'Legacy Spline' }, // catmull, deprecated + // { v: 3, t: 'Spline (Legacy)' }, // cardinal, deprecated + { v: 4, t: 'Spline' }, // spline + { v: 5, t: 'Step' } + ], type: 'number' }); fieldType.style['font-size'] = '11px'; + fieldType.class.add('ui-select-field', 'noSelect'); fieldType.value = 1; fieldType.on('change', (value) => { @@ -150,14 +152,17 @@ editor.once('load', () => { header.append(fieldType); // randomize - const labelRandomize = new LegacyLabel({ + const labelRandomize = new Label({ text: 'Randomize' }); + labelRandomize.class.add('ui-label'); labelRandomize.style['margin-left'] = '25px'; header.append(labelRandomize); - const fieldRandomize = new LegacyCheckbox(); + const fieldRandomize = new BooleanInput({ + type: 'toggle' + }); fieldRandomize.class.add('component-toggle'); fieldRandomize.on('change', (value: boolean) => { let i; @@ -237,65 +242,89 @@ editor.once('load', () => { }; for (let i = 0; i < colors.curves.length; i++) { - const btn = new LegacyButton(); - btn.class.add('picker-curve-toggle', 'active'); - btn.element.style.color = colors.curves[3 - i]; - curveToggles.splice(0, 0, btn); + const btn = new Button(); + btn.class.add('ui-button', 'picker-curve-toggle', 'active'); + btn.style.color = colors.curves[i]; + if (i === 0) { + btn.style['margin-left'] = 'auto'; + } + curveToggles.push(btn); header.append(btn); btn.on('click', onCurveToggleClick.bind(btn)); } // canvas - const canvas = new LegacyCanvas({ useDevicePixelRatio: true }); + const canvas = new Canvas({ useDevicePixelRatio: true }); + const canvasDom = canvas.dom as HTMLCanvasElement; canvas.resize(panel.clientWidth, 200); - panel.appendChild(canvas.element); + panel.appendChild(canvasDom); // canvas for checkerboard pattern - const checkerboardCanvas = new LegacyCanvas(); + const checkerboardCanvas = new Canvas(); + const checkerboardDom = checkerboardCanvas.dom as HTMLCanvasElement; checkerboardCanvas.width = 16; checkerboardCanvas.height = 16; - const pctx = checkerboardCanvas.element.getContext('2d'); + const pctx = checkerboardDom.getContext('2d'); pctx.fillStyle = '#949a9c'; pctx.fillRect(0, 0, 8, 8); pctx.fillRect(8, 8, 8, 8); pctx.fillStyle = '#657375'; pctx.fillRect(8, 0, 8, 8); pctx.fillRect(0, 8, 8, 8); - const checkerboardPattern = canvas.element.getContext('2d').createPattern(checkerboardCanvas.element, 'repeat'); + const checkerboardPattern = canvasDom.getContext('2d').createPattern(checkerboardDom, 'repeat'); // gradient canvas - const gradientCanvas = new LegacyCanvas(); + const gradientCanvas = new Canvas(); + const gradientCanvasDom = gradientCanvas.dom as HTMLCanvasElement; gradientCanvas.resize(panel.clientWidth, 32); gradientCanvas.style.display = 'block'; - panel.appendChild(gradientCanvas.element); + panel.appendChild(gradientCanvasDom); + + function resizeCanvases() { + const width = Math.round(overlay.domContent.getBoundingClientRect().width || panel.clientWidth); + canvas.resize(width, 200); + context.setTransform(canvas.pixelRatio, 0, 0, canvas.pixelRatio, 0, 0); + gradientCanvas.resize(width, 32); + } // footer - const footer = new LegacyPanel(); + const footer = new Container({ + flex: true, + flexDirection: 'row' + }); footer.class.add('picker-curve-footer'); - panel.appendChild(footer.element); + footer.domContent.classList.add('content'); + panel.appendChild(footer.dom); // time input field - const fieldTime = new LegacyNumberField({ + const fieldTime = new NumericInput({ min: 0, max: 1, - step: 0.1 + step: 0.1, + hideSlider: true, + renderChanges: false, + value: 0, + flexGrow: 1, + placeholder: 'Time' }); - fieldTime.renderChanges = false; - fieldTime.value = 0; + fieldTime.class.add('ui-number-field'); + fieldTime.input.classList.add('field'); fieldTime.on('change', onFieldChanged); - fieldTime.flexGrow = 1; - fieldTime.placeholder = 'Time'; footer.append(fieldTime); // value input field - const fieldValue = new LegacyNumberField(); - fieldValue.renderChanges = false; - fieldValue.value = 0; + const fieldValue = new NumericInput({ + hideSlider: true, + renderChanges: false, + value: 0, + flexGrow: 1, + placeholder: 'Value' + }); + fieldValue.class.add('ui-number-field'); + fieldValue.input.classList.add('field'); fieldValue.on('change', onFieldChanged); - fieldValue.flexGrow = 1; - fieldValue.placeholder = 'Value'; footer.append(fieldValue); // called when time or value field change value @@ -324,10 +353,12 @@ editor.once('load', () => { } // reset zoom - const btnResetZoom = new LegacyButton({ - text: '' + const btnResetZoom = new Button({ + text: '', + unsafe: true }); + btnResetZoom.class.add('ui-button'); btnResetZoom.flexGrow = 1; btnResetZoom.on('click', () => { @@ -338,22 +369,24 @@ editor.once('load', () => { footer.append(btnResetZoom); - LegacyTooltip.attach({ - target: btnResetZoom.element, + TooltipHandle.attach({ + target: btnResetZoom.dom, text: 'Reset Zoom', align: 'bottom', root: root }); // reset curve - const btnResetCurve = new LegacyButton({ - text: '' + const btnResetCurve = new Button({ + text: '', + unsafe: true }); + btnResetCurve.class.add('ui-button'); btnResetCurve.flexGrow = 1; - LegacyTooltip.attach({ - target: btnResetCurve.element, + TooltipHandle.attach({ + target: btnResetCurve.dom, text: 'Reset Curve', align: 'bottom', root: root @@ -375,10 +408,12 @@ editor.once('load', () => { footer.append(btnResetCurve); - const btnCopy = new LegacyButton({ - text: '' + const btnCopy = new Button({ + text: '', + unsafe: true }); + btnCopy.class.add('ui-button'); btnCopy.on('click', () => { const data = { primaryKeys: [], @@ -406,8 +441,8 @@ editor.once('load', () => { editor.call('localStorage:set', 'playcanvas_editor_clipboard_curves', data); }); - LegacyTooltip.attach({ - target: btnCopy.element, + TooltipHandle.attach({ + target: btnCopy.dom, text: 'Copy', align: 'bottom', root: root @@ -415,10 +450,12 @@ editor.once('load', () => { footer.append(btnCopy); - const btnPaste = new LegacyButton({ - text: '' + const btnPaste = new Button({ + text: '', + unsafe: true }); + btnPaste.class.add('ui-button'); btnPaste.on('click', () => { const data = editor.call('localStorage:get', 'playcanvas_editor_clipboard_curves'); if (!data) { @@ -512,8 +549,8 @@ editor.once('load', () => { render(); }); - LegacyTooltip.attach({ - target: btnPaste.element, + TooltipHandle.attach({ + target: btnPaste.dom, text: 'Paste', align: 'bottom', root: root @@ -521,7 +558,7 @@ editor.once('load', () => { footer.append(btnPaste); - const context = canvas.element.getContext('2d'); + const context = canvasDom.getContext('2d'); context.setTransform(canvas.pixelRatio, 0, 0, canvas.pixelRatio, 0, 0); function cleanup() { @@ -534,7 +571,7 @@ editor.once('load', () => { scrolling = false; window.removeEventListener('mouseup', onMouseUp); window.removeEventListener('mousemove', onMouseMove); - canvas.element.removeEventListener('wheel', onMouseWheel); + canvasDom.removeEventListener('wheel', onMouseWheel); } function resetCurve(curve: Curve) { @@ -577,9 +614,13 @@ editor.once('load', () => { const suspend = suspendEvents; suspendEvents = true; - numCurves = value[0].keys[0].length ? value[0].keys.length : 1; + numCurves = Array.isArray(value[0].keys[0]) ? value[0].keys.length : 1; betweenCurves = value[0].betweenCurves; + if (betweenCurves && value.length === 1) { + value = [value[0], value[0]]; + } + fieldRandomize.value = betweenCurves; curveType = value[0].type; @@ -606,18 +647,21 @@ editor.once('load', () => { } } + const getCurveKeys = (keys: any, i: number) => { + const data = Array.isArray(keys?.[0]) ? keys[i] : keys; + if (!Array.isArray(data)) { + return []; + } + return Array.isArray(data[0]) ? data[0] : data; + }; + curves.length = 0; - value.forEach((data: { keys: number[][] }) => { - if (numCurves === 1) { - const c = new Curve(data.keys); + value.forEach((data: { keys: number[] | number[][] }) => { + const keys = data?.keys || value[0].keys; + for (let i = 0; i < numCurves; i++) { + const c = new Curve(getCurveKeys(keys, i)); c.type = curveType; curves.push(c); - } else { - data.keys.forEach((keys: number[]) => { - const c = new Curve(keys); - c.type = curveType; - curves.push(c); - }); } }); @@ -915,7 +959,7 @@ editor.once('load', () => { // Draws color gradient for a set of curves function renderColorGradient() { - const ctx = gradientCanvas.element.getContext('2d'); + const ctx = gradientCanvasDom.getContext('2d'); let t; const rgb = []; const precision = 2; @@ -1095,7 +1139,7 @@ editor.once('load', () => { } function getTargetCoords(e: MouseEvent) { - const rect = canvas.element.getBoundingClientRect(); + const rect = canvasDom.getBoundingClientRect(); const left = Math.floor(rect.left); const top = Math.floor(rect.top); @@ -1267,10 +1311,10 @@ editor.once('load', () => { // Change the mouse cursor to a pointer if (curve || anchor) { - canvas.element.style.cursor = 'pointer'; + canvasDom.style.cursor = 'pointer'; updateFields(anchor); } else { - canvas.element.style.cursor = ''; + canvasDom.style.cursor = ''; updateFields(selectedAnchor); } } @@ -1308,9 +1352,10 @@ editor.once('load', () => { // Return the hovered anchor and graph function getHoveredAnchor(coords: number[]) { - const result = { + const result: any = { graph: null, - anchor: null + anchor: null, + curve: null }; const hoveredTime = calculateAnchorTime(coords); @@ -1431,8 +1476,8 @@ editor.once('load', () => { } // Handles mouse down - canvas.element.addEventListener('mousedown', (e: MouseEvent) => { - if (e.target !== canvas.element) { + canvasDom.addEventListener('mousedown', (e: MouseEvent) => { + if (e.target !== canvasDom) { return; } @@ -1592,6 +1637,7 @@ editor.once('load', () => { editor.method('picker:curve', (value: { keys: number[][] }[], args?: Record) => { // show overlay overlay.hidden = false; + resizeCanvases(); const suspend = suspendEvents; suspendEvents = true; @@ -1601,10 +1647,17 @@ editor.once('load', () => { suspendEvents = suspend; setValue(value, args || {}); + requestAnimationFrame(() => { + if (overlay.hidden) { + return; + } + resizeCanvases(); + render(); + }); window.addEventListener('mouseup', onMouseUp); window.addEventListener('mousemove', onMouseMove); - canvas.element.addEventListener('wheel', onMouseWheel); + canvasDom.addEventListener('wheel', onMouseWheel); }); editor.method('picker:curve:close', () => { @@ -1615,7 +1668,7 @@ editor.once('load', () => { }); editor.method('picker:curve:rect', () => { - return overlay.rect; + return overlay.domContent.getBoundingClientRect(); }); // position picker @@ -1626,6 +1679,8 @@ editor.once('load', () => { } overlay.position(x, y); + resizeCanvases(); + render(); }); // update value of picker diff --git a/src/editor/pickers/picker-entity.ts b/src/editor/pickers/picker-entity.ts index 0a54681b5..fe427a147 100644 --- a/src/editor/pickers/picker-entity.ts +++ b/src/editor/pickers/picker-entity.ts @@ -1,12 +1,13 @@ -import { LegacyOverlay } from '@/common/ui/overlay'; +import { Overlay } from '@playcanvas/pcui'; const CLASS_ENTITY_PICKER_MODE = 'entity-picker-mode'; editor.once('load', () => { - const overlay = new LegacyOverlay(); - overlay.class.add('picker-entity'); - overlay.center = false; - overlay.hidden = true; + const overlay = new Overlay({ + class: 'picker-entity', + clickable: true, + hidden: true + }); const root = editor.call('layout.root'); root.append(overlay); diff --git a/src/editor/pickers/picker-gradient.ts b/src/editor/pickers/picker-gradient.ts index f0bd35f9a..c6cb91a68 100644 --- a/src/editor/pickers/picker-gradient.ts +++ b/src/editor/pickers/picker-gradient.ts @@ -1,18 +1,22 @@ import { Events } from '@playcanvas/observer'; +import { Button, Canvas, Container, Label, NumericInput, Overlay, SelectInput, TextInput } from '@playcanvas/pcui'; import { Curve, CURVE_LINEAR, CURVE_SPLINE, CURVE_STEP, math } from 'playcanvas'; -import { LegacyButton } from '@/common/ui/button'; -import { LegacyCanvas } from '@/common/ui/canvas'; -import { LegacyLabel } from '@/common/ui/label'; -import { LegacyNumberField } from '@/common/ui/number-field'; -import { LegacyOverlay } from '@/common/ui/overlay'; -import { LegacyPanel } from '@/common/ui/panel'; -import { LegacySelectField } from '@/common/ui/select-field'; -import { LegacyTextField } from '@/common/ui/text-field'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; import { assignEvents } from '@/common/utils'; import { hexStr, hsv2rgb, normalizedCoord, rgb2hsv, rgbaStr, toHsva, toRgba } from '@/core/color'; +const COLOR_RECT_SIZE = 140; +const HUE_RECT_WIDTH = 20; +const GRADIENT_WIDTH = 344; +const GRADIENT_HEIGHT = 28; + +const legacyInput = (field: NumericInput | TextInput, cls: string) => { + field.class.add(cls); + field.input.classList.add('field'); + return field; +}; + class ColorPicker extends Events { panel: any; @@ -69,35 +73,33 @@ class ColorPicker extends Events { }; } - this.panel = new LegacyPanel(); + this.panel = new Container(); this.panel.class.add('color-panel'); - parent.appendChild(this.panel.element); + this.panel.domContent.classList.add('content'); + parent.appendChild(this.panel.dom); - this.colorRect = new LegacyCanvas({ useDevicePixelRatio: true }); + this.colorRect = new Canvas({ useDevicePixelRatio: true }); this.colorRect.class.add('color-rect'); - this.panel.append(this.colorRect.element); - this.colorRect.resize(this.colorRect.element.clientWidth, - this.colorRect.element.clientHeight); + this.panel.append(this.colorRect); + this.colorRect.resize(COLOR_RECT_SIZE, COLOR_RECT_SIZE); this.colorHandle = document.createElement('div'); this.colorHandle.classList.add('color-handle'); this.panel.append(this.colorHandle); - this.hueRect = new LegacyCanvas({ useDevicePixelRatio: true }); + this.hueRect = new Canvas({ useDevicePixelRatio: true }); this.hueRect.class.add('hue-rect'); - this.panel.append(this.hueRect.element); - this.hueRect.resize(this.hueRect.element.clientWidth, - this.hueRect.element.clientHeight); + this.panel.append(this.hueRect); + this.hueRect.resize(HUE_RECT_WIDTH, COLOR_RECT_SIZE); this.hueHandle = document.createElement('div'); this.hueHandle.classList.add('hue-handle'); this.panel.append(this.hueHandle); - this.alphaRect = new LegacyCanvas({ useDevicePixelRatio: true }); + this.alphaRect = new Canvas({ useDevicePixelRatio: true }); this.alphaRect.class.add('alpha-rect'); - this.panel.append(this.alphaRect.element); - this.alphaRect.resize(this.alphaRect.element.clientWidth, - this.alphaRect.element.clientHeight); + this.panel.append(this.alphaRect); + this.alphaRect.resize(HUE_RECT_WIDTH, COLOR_RECT_SIZE); this.alphaHandle = document.createElement('div'); this.alphaHandle.classList.add('alpha-handle'); @@ -114,16 +116,17 @@ class ColorPicker extends Events { this.upHandler = genEvtHandler(this, this._onMouseUp); function numberField(label: string) { - const field = new LegacyNumberField({ + const field = legacyInput(new NumericInput({ precision: 1, step: 1, min: 0, - max: 255 - }); - field.renderChanges = false; - field.placeholder = label; + max: 255, + hideSlider: true, + renderChanges: false, + placeholder: label + }), 'ui-number-field'); field.on('change', this.fieldChangeHandler); - this.fields.appendChild(field.element); + this.fields.appendChild(field.dom); return field; } @@ -132,23 +135,35 @@ class ColorPicker extends Events { this.bField = numberField.call(this, 'b'); this.aField = numberField.call(this, 'a'); - this.hexField = new LegacyTextField({}); - this.hexField.renderChanges = false; - this.hexField.placeholder = '#'; + this.hexField = legacyInput(new TextInput({ + renderChanges: false, + placeholder: '#' + }), 'ui-text-field'); this.hexField.on('change', this.hexChangeHandler); - this.fields.appendChild(this.hexField.element); + this.fields.appendChild(this.hexField.dom); // hook up mouse handlers - this.colorRect.element.addEventListener('mousedown', this.downHandler); - this.hueRect.element.addEventListener('mousedown', this.downHandler); - this.alphaRect.element.addEventListener('mousedown', this.downHandler); + this.colorRect.dom.addEventListener('mousedown', this.downHandler); + this.hueRect.dom.addEventListener('mousedown', this.downHandler); + this.alphaRect.dom.addEventListener('mousedown', this.downHandler); + + this._generateHue(this.hueRect); + this._generateAlpha(this.alphaRect); + } + + resize() { + this.colorRect.resize(COLOR_RECT_SIZE, COLOR_RECT_SIZE); + this.hueRect.resize(HUE_RECT_WIDTH, COLOR_RECT_SIZE); + this.alphaRect.resize(HUE_RECT_WIDTH, COLOR_RECT_SIZE); this._generateHue(this.hueRect); this._generateAlpha(this.alphaRect); + this._generateGradient(this.colorRect, hsv2rgb([this._hsva[0], 1, 1])); + this.hsva = this._hsva.slice(); } - _generateHue(canvas: LegacyCanvas) { - const ctx = canvas.element.getContext('2d'); + _generateHue(canvas: Canvas) { + const ctx = (canvas.dom as HTMLCanvasElement).getContext('2d'); const w = canvas.pixelWidth; const h = canvas.pixelHeight; const gradient = ctx.createLinearGradient(0, 0, 0, h); @@ -159,8 +174,8 @@ class ColorPicker extends Events { ctx.fillRect(0, 0, w, h); } - _generateAlpha(canvas: LegacyCanvas) { - const ctx = canvas.element.getContext('2d'); + _generateAlpha(canvas: Canvas) { + const ctx = (canvas.dom as HTMLCanvasElement).getContext('2d'); const w = canvas.pixelWidth; const h = canvas.pixelHeight; const gradient = ctx.createLinearGradient(0, 0, 0, h); @@ -170,8 +185,8 @@ class ColorPicker extends Events { ctx.fillRect(0, 0, w, h); } - _generateGradient(canvas: LegacyCanvas, clr: number[]) { - const ctx = canvas.element.getContext('2d'); + _generateGradient(canvas: Canvas, clr: number[]) { + const ctx = (canvas.dom as HTMLCanvasElement).getContext('2d'); const w = canvas.pixelWidth; const h = canvas.pixelHeight; @@ -217,9 +232,9 @@ class ColorPicker extends Events { } _onMouseDown(evt: MouseEvent) { - if (evt.currentTarget === this.colorRect.element) { + if (evt.currentTarget === this.colorRect.dom) { this._dragMode = 1; // drag color - } else if (evt.currentTarget === this.hueRect.element) { + } else if (evt.currentTarget === this.hueRect.dom) { this._dragMode = 2; // drag hue } else { this._dragMode = 3; // drag alpha @@ -279,7 +294,7 @@ class ColorPicker extends Events { this._generateGradient(this.colorRect, hueRgb); } - const e = this.colorRect.element; + const e = this.colorRect.dom; const r = e.getBoundingClientRect(); const w = r.width - 2; const h = r.height - 2; @@ -327,13 +342,13 @@ class ColorPicker extends Events { set editAlpha(editAlpha: boolean) { if (editAlpha) { - this.alphaRect.element.style.display = 'inline'; + this.alphaRect.dom.style.display = 'inline'; this.alphaHandle.style.display = 'block'; - this.aField.element.style.display = 'inline-block'; + this.aField.dom.style.display = 'inline-block'; } else { - this.alphaRect.element.style.display = 'none'; + this.alphaRect.dom.style.display = 'none'; this.alphaHandle.style.display = 'none'; - this.aField.element.style.display = 'none'; + this.aField.dom.style.display = 'none'; } } } @@ -349,26 +364,44 @@ editor.once('load', () => { }; // ui widgets - const UI = { + const UI: any = { root: editor.call('layout.root'), - overlay: new LegacyOverlay(), + overlay: new Overlay({ + class: 'picker-gradient', + clickable: true, + hidden: true, + transparent: true + }), panel: document.createElement('div'), - gradient: new LegacyCanvas({ useDevicePixelRatio: true }), + gradient: new Canvas({ useDevicePixelRatio: true }), checkerPattern: createCheckerPattern(), - anchors: new LegacyCanvas({ useDevicePixelRatio: true }), - footer: new LegacyPanel(), - typeLabel: new LegacyLabel({ text: 'Type' }), - typeCombo: new LegacySelectField({ - options: { 0: 'placeholder' }, + anchors: new Canvas({ useDevicePixelRatio: true }), + footer: new Container({ + flex: true, + flexDirection: 'row' + }), + typeLabel: new Label({ text: 'Type' }), + typeCombo: new SelectInput({ + options: [{ v: 0, t: 'placeholder' }], type: 'number' }), - positionLabel: new LegacyLabel({ text: 'Position' }), - positionEdit: new LegacyNumberField({ min: 0, max: 100, step: 1 }), - resetButton: new LegacyButton({ text: '' }), - copyButton: new LegacyButton({ text: '' }), - pasteButton: new LegacyButton({ text: '' }), - colorPicker: null + positionLabel: new Label({ text: 'Position' }), + positionEdit: new NumericInput({ min: 0, max: 100, step: 1, hideSlider: true, allowNull: true }), + resetButton: new Button({ text: '', unsafe: true }), + copyButton: new Button({ text: '', unsafe: true }), + pasteButton: new Button({ text: '', unsafe: true }), + colorPicker: null, + draggingAnchor: false }; + UI.overlay.domContent.classList.add('content'); + UI.footer.domContent.classList.add('content'); + UI.typeLabel.class.add('ui-label'); + UI.typeCombo.class.add('ui-select-field', 'noSelect'); + UI.positionLabel.class.add('ui-label'); + legacyInput(UI.positionEdit, 'ui-number-field'); + UI.resetButton.class.add('ui-button'); + UI.copyButton.class.add('ui-button'); + UI.pasteButton.class.add('ui-button'); // current state const STATE = { @@ -397,7 +430,7 @@ editor.once('load', () => { function onOpen() { window.addEventListener('mousemove', anchorsOnMouseMove); window.addEventListener('mouseup', anchorsOnMouseUp); - UI.anchors.element.addEventListener('mousedown', anchorsOnMouseDown); + UI.anchors.dom.addEventListener('mousedown', anchorsOnMouseDown); editor.emit('picker:gradient:open'); editor.emit('picker:open', 'gradient'); } @@ -407,7 +440,7 @@ editor.once('load', () => { STATE.hoveredAnchor = -1; window.removeEventListener('mousemove', anchorsOnMouseMove); window.removeEventListener('mouseup', anchorsOnMouseUp); - UI.anchors.element.removeEventListener('mousedown', anchorsOnMouseDown); + UI.anchors.dom.removeEventListener('mousedown', anchorsOnMouseDown); editor.emit('picker:gradient:close'); editor.emit('picker:close', 'gradient'); } @@ -438,8 +471,14 @@ editor.once('load', () => { renderAnchors(); } + function resizeCanvases() { + UI.gradient.resize(GRADIENT_WIDTH, GRADIENT_HEIGHT); + UI.anchors.resize(GRADIENT_WIDTH, GRADIENT_HEIGHT); + UI.colorPicker.resize(); + } + function renderGradient() { - const ctx = UI.gradient.element.getContext('2d'); + const ctx = UI.gradient.dom.getContext('2d'); const w = UI.gradient.width; const h = UI.gradient.height; const r = UI.gradient.pixelRatio; @@ -484,7 +523,7 @@ editor.once('load', () => { } function renderAnchors() { - const ctx = UI.anchors.element.getContext('2d'); + const ctx = UI.anchors.dom.getContext('2d'); const w = UI.anchors.width; const h = UI.anchors.height; const r = UI.anchors.pixelRatio; @@ -621,7 +660,7 @@ editor.once('load', () => { // user clicked in empty space, create new anchor and select it const coord = calcNormalizedCoord(e.clientX, e.clientY, - getClientRect(UI.anchors.element)); + getClientRect(UI.anchors.dom)); insertAnchor(coord[0], evaluateGradient(coord[0])); selectAnchor(STATE.anchors.indexOf(coord[0])); } else if (STATE.hoveredAnchor !== STATE.selectedAnchor) { @@ -637,7 +676,7 @@ editor.once('load', () => { function anchorsOnMouseMove(e: MouseEvent) { const coord = calcNormalizedCoord(e.clientX, e.clientY, - getClientRect(UI.anchors.element)); + getClientRect(UI.anchors.dom)); if (UI.draggingAnchor) { dragUpdate(math.clamp(coord[0], 0, 1)); @@ -675,7 +714,7 @@ editor.once('load', () => { function selectHovered(index: number) { STATE.hoveredAnchor = index; - UI.anchors.element.style.cursor = (index === -1 ? '' : 'pointer'); + UI.anchors.dom.style.cursor = (index === -1 ? '' : 'pointer'); } function selectAnchor(index: number) { @@ -873,17 +912,17 @@ editor.once('load', () => { } function createCheckerPattern() { - const canvas = new LegacyCanvas(); + const canvas = new Canvas(); canvas.width = 16; canvas.height = 16; - const ctx = canvas.element.getContext('2d'); + const ctx = (canvas.dom as HTMLCanvasElement).getContext('2d'); ctx.fillStyle = '#949a9c'; ctx.fillRect(0, 0, 8, 8); ctx.fillRect(8, 8, 8, 8); ctx.fillStyle = '#657375'; ctx.fillRect(8, 0, 8, 8); ctx.fillRect(0, 8, 8, 8); - return ctx.createPattern(canvas.element, 'repeat'); + return ctx.createPattern(canvas.dom as HTMLCanvasElement, 'repeat'); } function setValue(value: Array<{ keys?: number[][]; type?: number }>, args?: unknown) { @@ -913,7 +952,9 @@ editor.once('load', () => { comboItems[3] = 'Legacy'; STATE.typeMap[3] = value[0].type; } - UI.typeCombo._updateOptions(comboItems); + UI.typeCombo.options = Object.entries(comboItems).map(([v, t]) => { + return { v: Number(v), t }; + }); UI.typeCombo.value = { 0: 1, 1: 3, 2: 3, 3: 3, 4: 2, 5: 0 }[value[0].type]; // store the curves @@ -939,11 +980,6 @@ editor.once('load', () => { // initialize overlay UI.root.append(UI.overlay); - UI.overlay.class.add('picker-gradient'); - UI.overlay.center = false; - UI.overlay.transparent = true; - UI.overlay.hidden = true; - UI.overlay.on('show', () => { onOpen(); }); @@ -957,19 +993,17 @@ editor.once('load', () => { UI.overlay.append(UI.panel); // gradient - UI.panel.appendChild(UI.gradient.element); + UI.panel.appendChild(UI.gradient.dom); UI.gradient.class.add('picker-gradient-gradient'); - let r = getClientRect(UI.gradient.element); - UI.gradient.resize(r.width, r.height); + UI.gradient.resize(GRADIENT_WIDTH, GRADIENT_HEIGHT); // anchors - UI.panel.appendChild(UI.anchors.element); + UI.panel.appendChild(UI.anchors.dom); UI.anchors.class.add('picker-gradient-anchors'); - r = getClientRect(UI.anchors.element); - UI.anchors.resize(r.width, r.height); + UI.anchors.resize(GRADIENT_WIDTH, GRADIENT_HEIGHT); // footer - UI.panel.appendChild(UI.footer.element); + UI.panel.appendChild(UI.footer.dom); UI.footer.append(UI.typeLabel); UI.footer.class.add('picker-gradient-footer'); @@ -990,8 +1024,8 @@ editor.once('load', () => { UI.resetButton.on('click', doReset); UI.footer.append(UI.resetButton); - LegacyTooltip.attach({ - target: UI.resetButton.element, + TooltipHandle.attach({ + target: UI.resetButton.dom, text: 'Reset', align: 'bottom', root: UI.root @@ -999,8 +1033,8 @@ editor.once('load', () => { UI.copyButton.on('click', doCopy); UI.footer.append(UI.copyButton); - LegacyTooltip.attach({ - target: UI.copyButton.element, + TooltipHandle.attach({ + target: UI.copyButton.dom, text: 'Copy', align: 'bottom', root: UI.root @@ -1008,8 +1042,8 @@ editor.once('load', () => { UI.pasteButton.on('click', doPaste); UI.footer.append(UI.pasteButton); - LegacyTooltip.attach({ - target: UI.pasteButton.element, + TooltipHandle.attach({ + target: UI.pasteButton.dom, text: 'Paste', align: 'bottom', root: UI.root @@ -1043,6 +1077,15 @@ editor.once('load', () => { editor.method('picker:gradient', (value, args) => { setValue(value, args); open(); + resizeCanvases(); + render(); + requestAnimationFrame(() => { + if (UI.overlay.hidden) { + return; + } + resizeCanvases(); + render(); + }); }); editor.method('picker:gradient:set', (value, args) => { @@ -1050,7 +1093,7 @@ editor.once('load', () => { }); editor.method('picker:gradient:rect', () => { - return UI.overlay.rect; + return UI.overlay.domContent.getBoundingClientRect(); }); editor.method('picker:gradient:position', (x, y) => { @@ -1058,5 +1101,7 @@ editor.once('load', () => { y = window.innerHeight - UI.panel.clientHeight; } UI.overlay.position(x, y); + resizeCanvases(); + render(); }); }); diff --git a/src/editor/pickers/picker-node.ts b/src/editor/pickers/picker-node.ts index 09495174b..ec5c1486c 100644 --- a/src/editor/pickers/picker-node.ts +++ b/src/editor/pickers/picker-node.ts @@ -1,12 +1,13 @@ -import { LegacyOverlay } from '@/common/ui/overlay'; +import { Overlay } from '@playcanvas/pcui'; import { ModelAssetInspectorMeshInstances } from '../inspector/assets/model-mesh-instances'; editor.once('load', () => { - const overlay = new LegacyOverlay(); - overlay.class.add('picker-node'); - overlay.center = false; - overlay.hidden = true; + const overlay = new Overlay({ + class: 'picker-node', + clickable: true, + hidden: true + }); const root = editor.call('layout.root'); root.append(overlay); diff --git a/src/editor/pickers/sprite-editor/sprite-editor-frames-related-sprites-panel.ts b/src/editor/pickers/sprite-editor/sprite-editor-frames-related-sprites-panel.ts index c8f4adc4a..2313f721e 100644 --- a/src/editor/pickers/sprite-editor/sprite-editor-frames-related-sprites-panel.ts +++ b/src/editor/pickers/sprite-editor/sprite-editor-frames-related-sprites-panel.ts @@ -1,8 +1,5 @@ import type { EventHandle, Observer } from '@playcanvas/observer'; -import { type Container, Label, Panel } from '@playcanvas/pcui'; - -import { LegacyList } from '@/common/ui/list'; -import { LegacyListItem } from '@/common/ui/list-item'; +import { Container, Label, Panel } from '@playcanvas/pcui'; editor.once('load', () => { editor.method('picker:sprites:attributes:frames:relatedSprites', (args) => { @@ -26,7 +23,7 @@ editor.once('load', () => { }); panel.append(labelNoAssets); - const list = new LegacyList(); + const list = new Container(); list.class.add('related-assets'); panel.append(list); @@ -51,7 +48,7 @@ editor.once('load', () => { const createAssetPanel = (asset: Observer): void => { const assetEvents = []; - const item = new LegacyListItem({ + const item = new Label({ text: asset.get('name') }); item.class.add('type-sprite'); diff --git a/src/editor/settings/attributes/settings-attributes-scripts-priority.ts b/src/editor/settings/attributes/settings-attributes-scripts-priority.ts index 6feeb170b..f5fadadad 100644 --- a/src/editor/settings/attributes/settings-attributes-scripts-priority.ts +++ b/src/editor/settings/attributes/settings-attributes-scripts-priority.ts @@ -1,9 +1,11 @@ -import { LegacyButton } from '@/common/ui/button'; -import { LegacyLabel } from '@/common/ui/label'; -import { LegacyList } from '@/common/ui/list'; -import { LegacyListItem } from '@/common/ui/list-item'; -import { LegacyOverlay } from '@/common/ui/overlay'; -import { LegacyPanel } from '@/common/ui/panel'; +import { + createButton, + createLabel, + createList, + createListItem, + createOverlay, + createPanel +} from '../../attributes/attributes-pcui'; editor.once('load', () => { if (!editor.call('settings:project').get('useLegacyScripts')) { @@ -13,20 +15,20 @@ editor.once('load', () => { const sceneSettings = editor.call('sceneSettings'); let priorityScripts = []; - const priorityList = new LegacyList(); + const priorityList = createList(); const refreshPriorityList = function () { priorityList.clear(); if (priorityScripts.length === 0) { - const item = new LegacyListItem(); + const item = createListItem(); priorityList.append(item); } else { priorityScripts.forEach((script, index) => { - const item = new LegacyListItem(); + const item = createListItem(); item.text = script; - const moveUp = new LegacyButton(); + const moveUp = createButton(); moveUp.class.add('move-up'); if (index) { moveUp.on('click', () => { @@ -40,7 +42,7 @@ editor.once('load', () => { moveUp.class.add('not-visible'); } - const moveDown = new LegacyButton(); + const moveDown = createButton(); moveDown.class.add('move-down'); if (index < priorityScripts.length - 1) { moveDown.on('click', () => { @@ -54,7 +56,7 @@ editor.once('load', () => { moveDown.class.add('not-visible'); } - const remove = new LegacyButton(); + const remove = createButton(); remove.class.add('remove'); remove.on('click', () => { const index = priorityScripts.indexOf(script); @@ -78,25 +80,25 @@ editor.once('load', () => { const root = editor.call('layout.root'); - const overlay = new LegacyOverlay(); + const overlay = createOverlay(); overlay.class.add('script-priorities'); overlay.hidden = true; - const label = new LegacyLabel(); + const label = createLabel(); label.text = 'Script Loading Priority'; label.class.add('title'); overlay.append(label); - const description = new LegacyLabel(); + const description = createLabel(); description.text = 'Scripts in the priority list are loaded first in the order that they are listed. Other scripts are loaded in an unspecified order.'; description.class.add('description'); overlay.append(description); - const panel = new LegacyPanel(); + const panel = createPanel(); overlay.append(panel); // Add new script button - const button = new LegacyButton(); + const button = createButton(); button.text = 'Add Script'; button.class.add('add-script'); button.on('click', (evt) => { diff --git a/src/editor/templates/templates-override-panel.ts b/src/editor/templates/templates-override-panel.ts index 92fdbd1c8..7b4027568 100644 --- a/src/editor/templates/templates-override-panel.ts +++ b/src/editor/templates/templates-override-panel.ts @@ -9,7 +9,6 @@ import { CurveInput } from '@/common/pcui/element/element-curve-input'; import { EntityInput } from '@/common/pcui/element/element-entity-input'; import { GradientInput } from '@/common/pcui/element/element-gradient-input'; import { LayersInput } from '@/common/pcui/element/element-layers-input'; -import { LegacyLabel } from '@/common/ui/label'; import { AttributesInspector } from '../inspector/attributes-inspector'; @@ -146,10 +145,10 @@ class TemplateOverridesView extends Container { private _dropdownMenu: Container; - private _dropdownMarker: LegacyLabel | null = null; + private _dropdownMarker: Label | null = null; private _evtWindowClick = (e: MouseEvent) => { - if (this._dropdownMarker && this._dropdownMarker.dom.contains(e.target)) { + if (this._dropdownMarker && this._dropdownMarker.dom.contains(e.target as Node)) { return; } @@ -471,8 +470,8 @@ class TemplateOverridesView extends Container { }); } - _createOverrideMarker(override: Record | null, type?: string): LegacyLabel { - const label = new LegacyLabel({ + _createOverrideMarker(override: Record | null, type?: string) { + const label = new Label({ text: override ? '' : '', unsafe: true }); @@ -577,7 +576,7 @@ class TemplateOverridesView extends Container { } // Creates an override group for the left and right sides - _handleOverrideGroup(name: string, override: Record, result: OverrideGroup[]) { + _handleOverrideGroup(name: string, override: Record, result: (OverrideGroup | Label)[]) { let markerOverride = override; if (override.missing_in_dst) { diff --git a/src/editor/toolbar/toolbar-code-editor.ts b/src/editor/toolbar/toolbar-code-editor.ts index 342a15c40..38c068371 100644 --- a/src/editor/toolbar/toolbar-code-editor.ts +++ b/src/editor/toolbar/toolbar-code-editor.ts @@ -1,7 +1,7 @@ import type { Observer } from '@playcanvas/observer'; import { Button } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; editor.once('load', () => { const projectUserSettings = editor.call('settings:projectUser'); @@ -88,7 +88,7 @@ editor.once('load', () => { } }); - LegacyTooltip.attach({ + TooltipHandle.attach({ target: button.dom, text: 'Code Editor (Shift-click to open in popup)', align: 'left', diff --git a/src/editor/toolbar/toolbar-controls.ts b/src/editor/toolbar/toolbar-controls.ts index f3ffad862..4795ad846 100644 --- a/src/editor/toolbar/toolbar-controls.ts +++ b/src/editor/toolbar/toolbar-controls.ts @@ -1,6 +1,6 @@ import { Button } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; editor.once('load', () => { const toolbar = editor.call('layout.toolbar'); @@ -23,7 +23,7 @@ editor.once('load', () => { button.class.remove('active'); }); - LegacyTooltip.attach({ + TooltipHandle.attach({ target: button.dom, text: 'Controls', align: 'left', diff --git a/src/editor/toolbar/toolbar-discord.ts b/src/editor/toolbar/toolbar-discord.ts index 275700b8d..ed32b0654 100644 --- a/src/editor/toolbar/toolbar-discord.ts +++ b/src/editor/toolbar/toolbar-discord.ts @@ -1,6 +1,6 @@ import { Button } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; const discordSvg = (color, style = '') => { const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); @@ -49,7 +49,7 @@ editor.once('load', () => { path.setAttribute('fill', color); }); - LegacyTooltip.attach({ + TooltipHandle.attach({ target: button.dom, text: 'Join our Discord server', align: 'left', diff --git a/src/editor/toolbar/toolbar-editor-settings.ts b/src/editor/toolbar/toolbar-editor-settings.ts index eac5c6f43..324e5cc43 100644 --- a/src/editor/toolbar/toolbar-editor-settings.ts +++ b/src/editor/toolbar/toolbar-editor-settings.ts @@ -1,6 +1,6 @@ import { Button } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; editor.once('load', () => { const toolbar = editor.call('layout.toolbar'); @@ -30,7 +30,7 @@ editor.once('load', () => { button.enabled = !state; }); - LegacyTooltip.attach({ + TooltipHandle.attach({ target: button.dom, text: 'Settings', align: 'left', diff --git a/src/editor/toolbar/toolbar-forum.ts b/src/editor/toolbar/toolbar-forum.ts index f7fee0907..12b050331 100644 --- a/src/editor/toolbar/toolbar-forum.ts +++ b/src/editor/toolbar/toolbar-forum.ts @@ -1,6 +1,6 @@ import { Button } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; editor.once('load', () => { const toolbar = editor.call('layout.toolbar'); @@ -11,7 +11,7 @@ editor.once('load', () => { }); toolbar.append(contact); - LegacyTooltip.attach({ + TooltipHandle.attach({ target: contact.dom, text: 'Ask for help on our Forum', align: 'left', diff --git a/src/editor/toolbar/toolbar-github.ts b/src/editor/toolbar/toolbar-github.ts index 5a92bd41a..1d99f1088 100644 --- a/src/editor/toolbar/toolbar-github.ts +++ b/src/editor/toolbar/toolbar-github.ts @@ -1,6 +1,6 @@ import { Button } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; editor.once('load', () => { const toolbar = editor.call('layout.toolbar'); @@ -15,7 +15,7 @@ editor.once('load', () => { window.open('https://github.com/playcanvas/editor/issues', '_blank'); }); - LegacyTooltip.attach({ + TooltipHandle.attach({ target: button.dom, text: 'Report Github Issues', align: 'left', diff --git a/src/editor/toolbar/toolbar-gizmos.ts b/src/editor/toolbar/toolbar-gizmos.ts index 825c306a3..a0aabfc21 100644 --- a/src/editor/toolbar/toolbar-gizmos.ts +++ b/src/editor/toolbar/toolbar-gizmos.ts @@ -1,6 +1,6 @@ import { Button } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; editor.once('load', () => { const root = editor.call('layout.root'); @@ -52,7 +52,7 @@ editor.once('load', () => { toolbar.append(button); - button.tooltip = LegacyTooltip.attach({ + button.tooltip = TooltipHandle.attach({ target: button.dom, text: item.tooltip, align: 'left', @@ -86,7 +86,7 @@ editor.once('load', () => { editor.call('gizmo:coordSystem', buttonWorld.class.contains('active') ? 'world' : 'local'); }); - const tooltipWorld = LegacyTooltip.attach({ + const tooltipWorld = TooltipHandle.attach({ target: buttonWorld.dom, align: 'left', root: root @@ -113,7 +113,7 @@ editor.once('load', () => { }); toolbar.append(buttonSnap); - const tooltipSnap = LegacyTooltip.attach({ + const tooltipSnap = TooltipHandle.attach({ target: buttonSnap.dom, text: 'Snap', align: 'left', @@ -156,7 +156,7 @@ editor.once('load', () => { } }); - const tooltipFocus = LegacyTooltip.attach({ + const tooltipFocus = TooltipHandle.attach({ target: buttonFocus.dom, text: 'Focus', align: 'left', diff --git a/src/editor/toolbar/toolbar-help.ts b/src/editor/toolbar/toolbar-help.ts index 4c2b0f526..7e0d0b060 100644 --- a/src/editor/toolbar/toolbar-help.ts +++ b/src/editor/toolbar/toolbar-help.ts @@ -1,6 +1,6 @@ import { Button } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; editor.once('load', () => { const toolbar = editor.call('layout.toolbar'); @@ -23,7 +23,7 @@ editor.once('load', () => { button.class.remove('active'); }); - LegacyTooltip.attach({ + TooltipHandle.attach({ target: button.dom, text: 'How do I...?', align: 'left', diff --git a/src/editor/toolbar/toolbar-history.ts b/src/editor/toolbar/toolbar-history.ts index 0fcca1794..42b396337 100644 --- a/src/editor/toolbar/toolbar-history.ts +++ b/src/editor/toolbar/toolbar-history.ts @@ -1,6 +1,6 @@ import { Button } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; editor.once('load', () => { const root = editor.call('layout.root'); @@ -28,7 +28,7 @@ editor.once('load', () => { history.undo(); }); - const tooltipUndo = LegacyTooltip.attach({ + const tooltipUndo = TooltipHandle.attach({ target: buttonUndo.dom, text: 'Undo', align: 'left', @@ -60,7 +60,7 @@ editor.once('load', () => { history.redo(); }); - const tooltipRedo = LegacyTooltip.attach({ + const tooltipRedo = TooltipHandle.attach({ target: buttonRedo.dom, text: 'Redo', align: 'left', diff --git a/src/editor/toolbar/toolbar-lightmapper.ts b/src/editor/toolbar/toolbar-lightmapper.ts index c3820c769..c77a93409 100644 --- a/src/editor/toolbar/toolbar-lightmapper.ts +++ b/src/editor/toolbar/toolbar-lightmapper.ts @@ -1,7 +1,6 @@ import { Button } from '@playcanvas/pcui'; -import { LegacyButton } from '@/common/ui/button'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; editor.once('load', () => { const root = editor.call('layout.root'); @@ -31,7 +30,7 @@ editor.once('load', () => { }); // tooltip - const tooltipBake = LegacyTooltip.attach({ + const tooltipBake = TooltipHandle.attach({ target: buttonBake.dom, align: 'left', root: root @@ -60,11 +59,10 @@ editor.once('load', () => { elUV1.textContent = 'UV1 is missing on some models. Please upload models with UV1 or use '; tooltipBake.innerElement.appendChild(elUV1); - const btnAutoUnwrap = new LegacyButton({ + const btnAutoUnwrap = new Button({ text: 'Auto-Unwrap' }); - btnAutoUnwrap.parent = tooltipBake; - elUV1.appendChild(btnAutoUnwrap.element); + elUV1.appendChild(btnAutoUnwrap.dom); btnAutoUnwrap.on('click', () => { if (!uv1Missing) { return; diff --git a/src/editor/toolbar/toolbar-logo.ts b/src/editor/toolbar/toolbar-logo.ts index 823a1e2c3..4dd5663ab 100644 --- a/src/editor/toolbar/toolbar-logo.ts +++ b/src/editor/toolbar/toolbar-logo.ts @@ -1,6 +1,6 @@ import { Button, Menu } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; import { formatShortcut } from '@/common/utils'; editor.once('load', () => { @@ -403,7 +403,7 @@ editor.once('load', () => { menu.position(40, 0); root.append(menu); - const tooltip = LegacyTooltip.attach({ + const tooltip = TooltipHandle.attach({ target: logo.dom, text: 'Menu', align: 'left', diff --git a/src/editor/toolbar/toolbar-publish.ts b/src/editor/toolbar/toolbar-publish.ts index 7c1188b6c..26ed94769 100644 --- a/src/editor/toolbar/toolbar-publish.ts +++ b/src/editor/toolbar/toolbar-publish.ts @@ -1,6 +1,6 @@ import { Button } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; editor.once('load', () => { const toolbar = editor.call('layout.toolbar'); @@ -23,7 +23,7 @@ editor.once('load', () => { button.class.remove('active'); }); - LegacyTooltip.attach({ + TooltipHandle.attach({ target: button.dom, text: 'Publish / Download', align: 'left', diff --git a/src/editor/viewport-controls/viewport-launch.ts b/src/editor/viewport-controls/viewport-launch.ts index 262c3eae2..50faa1b04 100644 --- a/src/editor/viewport-controls/viewport-launch.ts +++ b/src/editor/viewport-controls/viewport-launch.ts @@ -1,6 +1,6 @@ import { Container, Button, BooleanInput, Label, Divider } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; import { config } from '@/editor/config'; editor.once('load', () => { @@ -41,7 +41,7 @@ editor.once('load', () => { }); launch.append(buttonLaunch); - const tooltipLaunch = LegacyTooltip.attach({ + const tooltipLaunch = TooltipHandle.attach({ target: buttonLaunch.dom, text: 'Launch the scene (Shift-click to open in popup)', align: 'right', @@ -186,7 +186,7 @@ editor.once('load', () => { const launchWithWebGpu = createButton('webgpu', `Launch with WebGPU${editor.projectEngineV2 ? '' : ' (beta)'}`); - const tooltipPreferWebGpu = LegacyTooltip.attach({ + const tooltipPreferWebGpu = TooltipHandle.attach({ target: launchWithWebGpu.parent.dom, text: `Launch the scene using WebGPU${editor.projectEngineV2 ? '' : ' (beta)'}.`, align: 'right', @@ -195,7 +195,7 @@ editor.once('load', () => { tooltipPreferWebGpu.class.add('launch-tooltip'); const launchWithWebGL2 = createButton('webgl2', 'Launch with WebGL 2.0'); - const tooltipPreferWebGl2 = LegacyTooltip.attach({ + const tooltipPreferWebGl2 = TooltipHandle.attach({ target: launchWithWebGL2.parent.dom, text: 'Launch the scene using WebGL 2.0.', align: 'right', @@ -204,7 +204,7 @@ editor.once('load', () => { tooltipPreferWebGl2.class.add('launch-tooltip'); const launchWithWebGL1 = createButton('webgl1', 'Launch with WebGL 1.0'); - const tooltipPreferWebGl1 = LegacyTooltip.attach({ + const tooltipPreferWebGl1 = TooltipHandle.attach({ target: launchWithWebGL1.parent.dom, text: 'Launch the scene using WebGL 1.0.', align: 'right', @@ -214,7 +214,7 @@ editor.once('load', () => { launchWithWebGL1.parent.hidden = editor.projectEngineV2; const optionProfiler = createOption('profiler', 'Profiler'); - const tooltipProfiler = LegacyTooltip.attach({ + const tooltipProfiler = TooltipHandle.attach({ target: optionProfiler.parent.dom, text: 'Enable the visual performance profiler in the launch page.', align: 'right', @@ -238,7 +238,7 @@ editor.once('load', () => { settings.set('editor.launchDebug', value); }); - const tooltipDebug = LegacyTooltip.attach({ + const tooltipDebug = TooltipHandle.attach({ target: optionDebug.parent.dom, text: 'Enable the logging of warning and error messages to the JavaScript console.', align: 'right', @@ -248,7 +248,7 @@ editor.once('load', () => { if (!legacyScripts) { const optionConcatenate = createOption('concatenate', 'Concatenate Scripts (Classic)'); - const tooltipConcatenate = LegacyTooltip.attach({ + const tooltipConcatenate = TooltipHandle.attach({ target: optionConcatenate.parent.dom, text: 'Concatenate Classic scripts on launch to reduce scene load time.', align: 'right', @@ -260,7 +260,7 @@ editor.once('load', () => { if (editor.call('users:hasFlag', 'hasBundles')) { const optionDisableBundles = createOption('disableBundles', 'Disable Asset Bundles'); - const tooltipBundles = LegacyTooltip.attach({ + const tooltipBundles = TooltipHandle.attach({ target: optionDisableBundles.parent.dom, text: 'Disable loading assets from Asset Bundles.', align: 'right', @@ -280,7 +280,7 @@ editor.once('load', () => { optionMiniStats.on('change', (value: boolean) => { settings.set('editor.launchMinistats', value); }); - LegacyTooltip.attach({ + TooltipHandle.attach({ target: optionMiniStats.parent.dom, text: 'Show the MiniStats in the launched application.', align: 'right', @@ -290,7 +290,7 @@ editor.once('load', () => { // force engine version const force = config.engineVersions.force; const optionForce = createOption('force', `Force Engine V${force.version[0]}`); - const tooltipForce = LegacyTooltip.attach({ + const tooltipForce = TooltipHandle.attach({ target: optionForce.parent.dom, text: `Force the launcher to use v${force.version}.`, align: 'right', @@ -310,7 +310,7 @@ editor.once('load', () => { optionReleaseCandidate.on('change', (value: boolean) => { settings.set('editor.launchReleaseCandidate', value); }); - LegacyTooltip.attach({ + TooltipHandle.attach({ target: optionReleaseCandidate.parent.dom, text: `Launch the scene using the engine release candidate (version ${releaseCandidate}).`, align: 'right', @@ -392,7 +392,7 @@ editor.once('load', () => { editor.call('viewport:expand'); }); - const tooltipExpand = LegacyTooltip.attach({ + const tooltipExpand = TooltipHandle.attach({ target: buttonExpand.dom, text: 'Hide Panels', align: 'top', diff --git a/src/editor/viewport-controls/viewport-scene.ts b/src/editor/viewport-controls/viewport-scene.ts index 64eb53aae..ce72e6e19 100644 --- a/src/editor/viewport-controls/viewport-scene.ts +++ b/src/editor/viewport-controls/viewport-scene.ts @@ -1,6 +1,6 @@ import { Button, Container } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; import { config } from '@/editor/config'; editor.once('load', () => { @@ -26,7 +26,7 @@ editor.once('load', () => { window.open(`/project/${config.project.id}`, '_blank'); }); - LegacyTooltip.attach({ + TooltipHandle.attach({ target: homeButton.dom, text: 'Home', align: 'top', @@ -39,7 +39,7 @@ editor.once('load', () => { }); panel.append(settingsButton); - LegacyTooltip.attach({ + TooltipHandle.attach({ target: settingsButton.dom, text: 'Settings', align: 'top', @@ -74,7 +74,7 @@ editor.once('load', () => { editor.call('picker:versioncontrol'); }); - LegacyTooltip.attach({ + TooltipHandle.attach({ target: versionControlButton.dom, text: 'Version Control', align: 'top', @@ -100,7 +100,7 @@ editor.once('load', () => { scenesButton.text = name; }); - LegacyTooltip.attach({ + TooltipHandle.attach({ target: scenesButton.dom, text: 'Manage Scenes', align: 'top', diff --git a/src/editor/viewport-controls/viewport-whois.ts b/src/editor/viewport-controls/viewport-whois.ts index 6a573c7a8..4e70e4782 100644 --- a/src/editor/viewport-controls/viewport-whois.ts +++ b/src/editor/viewport-controls/viewport-whois.ts @@ -1,6 +1,6 @@ import { Button, Container } from '@playcanvas/pcui'; -import { LegacyTooltip } from '@/common/ui/tooltip'; +import { TooltipHandle } from '@/common/tooltips'; editor.once('load', () => { const root = editor.call('layout.root'); @@ -26,7 +26,7 @@ editor.once('load', () => { assetPanel.on('collapse', adjustPosition); assetPanel.on('expand', adjustPosition); - const userMap = new Map(); + const userMap = new Map(); editor.on('whoisonline:add', (id: number) => { if (userMap.has(id)) { @@ -41,7 +41,7 @@ editor.once('load', () => { let clickHandle = button.on('click', () => window.open(`/${id}`, '_blank')); - const tooltip = LegacyTooltip.attach({ + const tooltip = TooltipHandle.attach({ target: button.dom, text: '', align: 'bottom',