From aff68fa435dcea5e215e0aa86623811444f3dff4 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Sun, 6 Mar 2022 11:30:27 +0800 Subject: [PATCH 1/7] Fix object flex bug --- source/Component/CommandBar/CommandBar.tsx | 10 ++++++++-- source/Component/DetailsList/DetailsList.scss | 3 ++- source/Panel/ObjectList/ObjectList.scss | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/source/Component/CommandBar/CommandBar.tsx b/source/Component/CommandBar/CommandBar.tsx index ff4d5f7..dfcb06c 100644 --- a/source/Component/CommandBar/CommandBar.tsx +++ b/source/Component/CommandBar/CommandBar.tsx @@ -62,12 +62,18 @@ class CommandBar extends Component this.props.status ? this.props.status.newGroup() : undefined + click: () => { + this.props.status ? this.props.status.newGroup() : undefined; + this.props.status ? this.props.status.model.draw() : undefined; + } })} {this.getRenderButton({ iconName: "CubeShape", i18NKey: "Command.Bar.Add.Range.Info", - click: () => this.props.status ? this.props.status.newRange() : undefined + click: () => { + this.props.status ? this.props.status.newRange() : undefined; + this.props.status ? this.props.status.model.draw() : undefined; + } })} {this.getRenderButton({ iconName: "StepSharedAdd", i18NKey: "Command.Bar.Add.Behavior.Info" })} {this.getRenderButton({ iconName: "Tag", i18NKey: "Command.Bar.Add.Tag.Info" })} diff --git a/source/Component/DetailsList/DetailsList.scss b/source/Component/DetailsList/DetailsList.scss index c48ebe9..c9a6d31 100644 --- a/source/Component/DetailsList/DetailsList.scss +++ b/source/Component/DetailsList/DetailsList.scss @@ -11,7 +11,7 @@ div.details-list { min-height: 30px; div.details-list-value { - padding: 5px 10px; + padding: 5px 5px; display: flex; justify-content: center; align-items: center; @@ -19,6 +19,7 @@ div.details-list { div.details-list-checkbox { display: flex; + flex-shrink: 0; width: 30px; align-items: center; justify-content: center; diff --git a/source/Panel/ObjectList/ObjectList.scss b/source/Panel/ObjectList/ObjectList.scss index c82464d..e66398b 100644 --- a/source/Panel/ObjectList/ObjectList.scss +++ b/source/Panel/ObjectList/ObjectList.scss @@ -22,6 +22,7 @@ div.object-list-command-bar { width: 30px; height: 100%; display: flex; + flex-shrink: 0; justify-content: center; align-items: center; user-select: none; From 779076012ff083ef147ecd7f6fc0804505c694a4 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Sun, 6 Mar 2022 20:28:47 +0800 Subject: [PATCH 2/7] Add range details panel --- source/Component/AttrInput/AttrInput.scss | 78 +++++++++++++++++++++ source/Component/AttrInput/AttrInput.tsx | 37 ++++++++++ source/Localization/EN-US.ts | 2 + source/Localization/ZH-CN.ts | 2 + source/Page/SimulatorWeb/SimulatorWeb.tsx | 3 +- source/Panel/Panel.tsx | 6 ++ source/Panel/RangeDetails/RangeDetails.scss | 0 source/Panel/RangeDetails/RangeDetails.tsx | 14 ++++ 8 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 source/Component/AttrInput/AttrInput.scss create mode 100644 source/Component/AttrInput/AttrInput.tsx create mode 100644 source/Panel/RangeDetails/RangeDetails.scss create mode 100644 source/Panel/RangeDetails/RangeDetails.tsx diff --git a/source/Component/AttrInput/AttrInput.scss b/source/Component/AttrInput/AttrInput.scss new file mode 100644 index 0000000..3aebe35 --- /dev/null +++ b/source/Component/AttrInput/AttrInput.scss @@ -0,0 +1,78 @@ +@import "../Theme/Theme.scss"; + +div.attr-input { + width: 100%; + display: flex; + min-height: 24px; + padding: 8px 0; + + div.input-intro { + width: 50%; + height: 100%; + max-width: 180px; + display: flex; + align-items: center; + } + + div.input-content { + width: 50%; + height: 100%; + max-width: 180px; + box-sizing: border-box; + border-radius: 3px; + overflow: hidden; + display: flex; + justify-content: space-between; + align-items: center; + min-height: 24px; + + input { + padding: 0 5px; + width: 100%; + height: 100%; + border: none; + outline:none; + }; + + input:focus { + border: none; + } + + div.button-left, div.button-right { + height: 100%; + display: flex; + justify-content: center; + align-items: center; + vertical-align: middle; + cursor: pointer; + user-select: none; + padding: 0 3px; + } + } +} + +div.dark.attr-input { + + div.input-content, + div.input-content input { + background-color: $lt-bg-color-lvl3-dark; + color: $lt-font-color-normal-dark; + } + + div.button-left:hover, div.button-right:hover { + background-color: $lt-bg-color-lvl2-dark; + } +} + +div.light.attr-input { + + div.input-content, + div.input-content input { + background-color: $lt-bg-color-lvl3-light; + color: $lt-font-color-normal-light; + } + + div.button-left:hover, div.button-right:hover { + background-color: $lt-bg-color-lvl2-light; + } +} \ No newline at end of file diff --git a/source/Component/AttrInput/AttrInput.tsx b/source/Component/AttrInput/AttrInput.tsx new file mode 100644 index 0000000..dd273b4 --- /dev/null +++ b/source/Component/AttrInput/AttrInput.tsx @@ -0,0 +1,37 @@ +import { Component, ReactNode } from "react"; +import { FontLevel, Theme } from "@Component/Theme/Theme"; +import "./AttrInput.scss"; +import { Icon } from "@fluentui/react"; + +interface IAttrInputProps { + isNumber?: boolean; +} + +class AttrInput extends Component { + public render(): ReactNode { + + return +
+ Input +
+
+ { + this.props.isNumber ?
+ +
: null + } + + { + this.props.isNumber ?
+ +
: null + } +
+
+ } +} + +export { AttrInput }; \ No newline at end of file diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index 1ec0799..fda2eff 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -28,6 +28,8 @@ const EN_US = { "Panel.Info.Render.View": "Live simulation results preview", "Panel.Title.Object.List.View": "Object list", "Panel.Info.Object.List.View": "Edit View All Object Properties", + "Panel.Title.Range.Details.View": "Range attributes", + "Panel.Info.Range.Details.View": "Edit View Range attributes", } export default EN_US; \ No newline at end of file diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index c9ef69d..c986442 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -28,5 +28,7 @@ const ZH_CN = { "Panel.Info.Render.View": "实时仿真结果预览", "Panel.Title.Object.List.View": "对象列表", "Panel.Info.Object.List.View": "编辑查看全部对象属性", + "Panel.Title.Range.Details.View": "范围属性", + "Panel.Info.Range.Details.View": "编辑查看范围属性", } export default ZH_CN; \ No newline at end of file diff --git a/source/Page/SimulatorWeb/SimulatorWeb.tsx b/source/Page/SimulatorWeb/SimulatorWeb.tsx index 3c14aa1..2559350 100644 --- a/source/Page/SimulatorWeb/SimulatorWeb.tsx +++ b/source/Page/SimulatorWeb/SimulatorWeb.tsx @@ -75,8 +75,7 @@ class SimulatorWeb extends Component { items: [{ panles: ["ObjectList", "Test tab"] }, { - items: [{panles: ["Label e", "ee"]}, {panles: ["F"]}], - layout: LayoutDirection.Y + panles: ["RangeDetails", "Label e"] }], layout: LayoutDirection.Y } diff --git a/source/Panel/Panel.tsx b/source/Panel/Panel.tsx index 47a684e..66b5c66 100644 --- a/source/Panel/Panel.tsx +++ b/source/Panel/Panel.tsx @@ -4,6 +4,7 @@ import { Localization } from "@Component/Localization/Localization"; import { RenderView } from "./RenderView/RenderView"; import { ObjectList } from "./ObjectList/ObjectList"; import { ObjectCommand } from "./ObjectList/ObjectCommand"; +import { RangeDetails } from "./RangeDetails/RangeDetails"; interface IPanelInfo { nameKey: string; @@ -19,6 +20,7 @@ interface IPanelInfo { type PanelId = "" | "RenderView" // 主渲染器 | "ObjectList" // 对象列表 +| "RangeDetails" // 范围属性 ; const PanelInfoMap = new Map(); @@ -30,6 +32,10 @@ PanelInfoMap.set("ObjectList", { nameKey: "Panel.Title.Object.List.View", introKay: "Panel.Info.Object.List.View", class: ObjectList, header: ObjectCommand, hidePadding: true }) +PanelInfoMap.set("RangeDetails", { + nameKey: "Panel.Title.Range.Details.View", introKay: "Panel.Info.Range.Details.View", + class: RangeDetails +}) function getPanelById(panelId: PanelId): ReactNode { switch (panelId) { diff --git a/source/Panel/RangeDetails/RangeDetails.scss b/source/Panel/RangeDetails/RangeDetails.scss new file mode 100644 index 0000000..e69de29 diff --git a/source/Panel/RangeDetails/RangeDetails.tsx b/source/Panel/RangeDetails/RangeDetails.tsx new file mode 100644 index 0000000..0e456db --- /dev/null +++ b/source/Panel/RangeDetails/RangeDetails.tsx @@ -0,0 +1,14 @@ +import { Component, ReactNode } from "react"; +import { AttrInput } from "@Component/AttrInput/AttrInput"; +import "./RangeDetails.scss"; + +class RangeDetails extends Component { + public render(): ReactNode { + return
+ + +
+ } +} + +export { RangeDetails }; \ No newline at end of file From 9bc8ddf06121445bdd83e6ffb41701a5aa37cd22 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Mon, 7 Mar 2022 10:43:23 +0800 Subject: [PATCH 3/7] Update use status api with mount event --- source/Component/CommandBar/CommandBar.tsx | 22 +---- source/Component/HeaderBar/HeaderBar.tsx | 8 +- source/Context/Status.tsx | 93 +++++++++++++++++++--- source/Page/SimulatorWeb/SimulatorWeb.tsx | 2 +- source/Panel/ObjectList/ObjectCommand.tsx | 3 - source/Panel/ObjectList/ObjectList.tsx | 22 +---- 6 files changed, 93 insertions(+), 57 deletions(-) diff --git a/source/Component/CommandBar/CommandBar.tsx b/source/Component/CommandBar/CommandBar.tsx index dfcb06c..98e6a14 100644 --- a/source/Component/CommandBar/CommandBar.tsx +++ b/source/Component/CommandBar/CommandBar.tsx @@ -2,7 +2,7 @@ import { BackgroundLevel, Theme } from "@Component/Theme/Theme"; import { DirectionalHint, IconButton } from "@fluentui/react"; import { LocalizationTooltipHost } from "../Localization/LocalizationTooltipHost"; import { useSetting, IMixinSettingProps } from "@Context/Setting"; -import { useStatus, IMixinStatusProps } from "@Context/Status"; +import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; import { AllI18nKeys } from "../Localization/Localization"; import { Component, ReactNode } from "react"; import { MouseMod } from "@GLRender/ClassicRenderer"; @@ -12,26 +12,10 @@ interface ICommandBarProps { width: number; } -@useStatus @useSetting +@useStatusWithEvent("mouseModChange") class CommandBar extends Component { - private handelChange = () => { - this.forceUpdate(); - } - - componentDidMount() { - if (this.props.status) { - this.props.status.on("mouseModChange", this.handelChange) - } - } - - componentWillUnmount() { - if (this.props.status) { - this.props.status.off("mouseModChange", this.handelChange) - } - } - render(): ReactNode { const mouseMod = this.props.status?.mouseMod ?? MouseMod.Drag; @@ -64,7 +48,6 @@ class CommandBar extends Component { this.props.status ? this.props.status.newGroup() : undefined; - this.props.status ? this.props.status.model.draw() : undefined; } })} {this.getRenderButton({ @@ -72,7 +55,6 @@ class CommandBar extends Component { this.props.status ? this.props.status.newRange() : undefined; - this.props.status ? this.props.status.model.draw() : undefined; } })} {this.getRenderButton({ iconName: "StepSharedAdd", i18NKey: "Command.Bar.Add.Behavior.Info" })} diff --git a/source/Component/HeaderBar/HeaderBar.tsx b/source/Component/HeaderBar/HeaderBar.tsx index 9453c85..5b51313 100644 --- a/source/Component/HeaderBar/HeaderBar.tsx +++ b/source/Component/HeaderBar/HeaderBar.tsx @@ -62,8 +62,8 @@ class HeaderBar extends Component< } if (status) { status.archive.on("save", this.changeListener); - status.model.on("loop", this.physicsFpsCalc); - status.renderer.on("loop", this.renderFpsCalc); + status.on("physicsLoop", this.physicsFpsCalc); + status.on("renderLoop", this.renderFpsCalc); } } @@ -74,8 +74,8 @@ class HeaderBar extends Component< } if (status) { status.archive.off("save", this.changeListener); - status.model.off("loop", this.physicsFpsCalc); - status.renderer.off("loop", this.renderFpsCalc); + status.off("physicsLoop", this.physicsFpsCalc); + status.off("renderLoop", this.renderFpsCalc); } } diff --git a/source/Context/Status.tsx b/source/Context/Status.tsx index cea98b7..3289c69 100644 --- a/source/Context/Status.tsx +++ b/source/Context/Status.tsx @@ -1,8 +1,7 @@ -import { createContext, Component, FunctionComponent } from "react"; +import { createContext, Component, FunctionComponent, useState, useEffect, ReactNode } from "react"; import { Emitter } from "@Model/Emitter"; import { Model, ObjectID } from "@Model/Model"; import { Archive } from "@Model/Archive"; -import { CtrlObject } from "@Model/CtrlObject"; import { AbstractRenderer } from "@Model/Renderer"; import { ClassicRenderer, MouseMod } from "@GLRender/ClassicRenderer"; import { Setting } from "./Setting"; @@ -16,10 +15,16 @@ function randomColor() { ] } -class Status extends Emitter<{ - mouseModChange: MouseMod, - focusObjectChange: Set -}> { +interface IStatusEvent { + renderLoop: number; + physicsLoop: number; + mouseModChange: void; + focusObjectChange: void; + objectChange: void; + labelChange: void; +} + +class Status extends Emitter { public setting: Setting = undefined as any; @@ -48,12 +53,34 @@ class Status extends Emitter<{ */ public focusObject: Set = new Set(); + public constructor() { + super(); + + // 循环事件 + this.model.on("loop", (t) => { this.emit("physicsLoop", t) }); + + // 对象变化事件 + this.model.on("objectChange", () => this.emit("objectChange")); + this.model.on("labelChange", () => this.emit("labelChange")); + + // 对象变换时执行渲染,更新渲染器数据 + this.on("objectChange", () => { + this.model.draw(); + }) + } + + public bindRenderer(renderer: AbstractRenderer) { + this.renderer = renderer; + this.renderer.on("loop", (t) => { this.emit("renderLoop", t) }); + this.model.bindRenderer(this.renderer); + } + /** * 更新焦点对象 */ public setFocusObject(focusObject: Set) { this.focusObject = focusObject; - this.emit("focusObjectChange", this.focusObject); + this.emit("focusObjectChange"); } /** @@ -87,7 +114,7 @@ class Status extends Emitter<{ this.renderer.mouseMod = mod; this.renderer.setMouseIcon(); } - this.emit("mouseModChange", mod); + this.emit("mouseModChange"); } } @@ -116,7 +143,55 @@ function useStatus(components: R): R { }) as any; } +function useStatusWithEvent(...events: Array) { + return (components: R): R => { + const C = components as any; + return class extends Component { + + private status: Status | undefined; + private isEventMount: boolean = false; + + private handelChange = () => { + this.forceUpdate(); + } + + private mountEvent() { + if (this.status && !this.isEventMount) { + this.isEventMount = true; + console.log("event mount"); + for (let i = 0; i < events.length; i++) { + this.status.on(events[i], this.handelChange); + } + } + } + + private unmountEvent() { + if (this.status) { + for (let i = 0; i < events.length; i++) { + this.status.off(events[i], this.handelChange); + } + } + } + + public render(): ReactNode { + return + {(status: Status) => { + this.status = status; + this.mountEvent(); + return ; + }} + + } + + public componentWillUnmount() { + this.unmountEvent(); + } + + } as any; + } +} + export { - Status, StatusContext, useStatus, + Status, StatusContext, useStatus, useStatusWithEvent, IMixinStatusProps, StatusProvider, StatusConsumer }; \ No newline at end of file diff --git a/source/Page/SimulatorWeb/SimulatorWeb.tsx b/source/Page/SimulatorWeb/SimulatorWeb.tsx index 2559350..ebc510e 100644 --- a/source/Page/SimulatorWeb/SimulatorWeb.tsx +++ b/source/Page/SimulatorWeb/SimulatorWeb.tsx @@ -35,7 +35,7 @@ class SimulatorWeb extends Component { // TODO: 这里要读取存档 this.status = new Status(); this.status.renderer = new ClassicRenderer({ className: "canvas" }).onLoad(); - this.status.model.bindRenderer(this.status.renderer); + this.status.bindRenderer(this.status.renderer); this.status.setting = this.setting; // 测试代码 diff --git a/source/Panel/ObjectList/ObjectCommand.tsx b/source/Panel/ObjectList/ObjectCommand.tsx index 5af857b..71cbe42 100644 --- a/source/Panel/ObjectList/ObjectCommand.tsx +++ b/source/Panel/ObjectList/ObjectCommand.tsx @@ -41,7 +41,6 @@ class ObjectCommand extends Component { className="command-item" onClick={() => { this.props.status ? this.props.status.newGroup() : undefined; - this.props.status ? this.props.status.model.draw() : undefined; }} > @@ -50,7 +49,6 @@ class ObjectCommand extends Component { className="command-item" onClick={() => { this.props.status ? this.props.status.newRange() : undefined; - this.props.status ? this.props.status.model.draw() : undefined; }} > @@ -65,7 +63,6 @@ class ObjectCommand extends Component { }) this.props.status.model.deleteObject(deleteId); this.props.status.setFocusObject(new Set()); - this.props.status.model.draw(); } }} > diff --git a/source/Panel/ObjectList/ObjectList.tsx b/source/Panel/ObjectList/ObjectList.tsx index cff972f..5d6fc7e 100644 --- a/source/Panel/ObjectList/ObjectList.tsx +++ b/source/Panel/ObjectList/ObjectList.tsx @@ -1,33 +1,15 @@ import { Component, ReactNode } from "react"; import { DetailsList } from "@Component/DetailsList/DetailsList"; -import { useStatus, IMixinStatusProps } from "@Context/Status"; +import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; import { useSetting, IMixinSettingProps } from "@Context/Setting"; import { Localization } from "@Component/Localization/Localization"; import { ObjectID } from "@Model/Renderer"; import "./ObjectList.scss"; @useSetting -@useStatus +@useStatusWithEvent("objectChange", "focusObjectChange") class ObjectList extends Component { - private handelChange = () => { - this.forceUpdate(); - } - - public componentDidMount(){ - if (this.props.status) { - this.props.status.model.on("objectChange", this.handelChange); - this.props.status.on("focusObjectChange", this.handelChange); - } - } - - public componentWillUnmount(){ - if (this.props.status) { - this.props.status.model.off("objectChange", this.handelChange); - this.props.status.off("focusObjectChange", this.handelChange); - } - } - private renderList() { const objList = this.props.status?.model.objectPool ?? []; From 71e17b2f5aad0d4c8da33d821686e6c6cf086986 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Mon, 7 Mar 2022 15:53:02 +0800 Subject: [PATCH 4/7] Add Attrinput component --- source/Component/AttrInput/AttrInput.scss | 87 +++++++----- source/Component/AttrInput/AttrInput.tsx | 150 ++++++++++++++++++--- source/Component/Container/Container.scss | 6 +- source/Component/Theme/Theme.scss | 3 + source/Localization/EN-US.ts | 9 +- source/Localization/ZH-CN.ts | 8 ++ source/Panel/RangeDetails/RangeDetails.tsx | 6 +- 7 files changed, 208 insertions(+), 61 deletions(-) diff --git a/source/Component/AttrInput/AttrInput.scss b/source/Component/AttrInput/AttrInput.scss index 3aebe35..ce1cd82 100644 --- a/source/Component/AttrInput/AttrInput.scss +++ b/source/Component/AttrInput/AttrInput.scss @@ -4,51 +4,70 @@ div.attr-input { width: 100%; display: flex; min-height: 24px; - padding: 8px 0; + padding: 5px 0; div.input-intro { width: 50%; height: 100%; - max-width: 180px; + max-width: 220px; display: flex; align-items: center; + padding-right: 5px; + box-sizing: border-box; } - div.input-content { - width: 50%; - height: 100%; - max-width: 180px; - box-sizing: border-box; - border-radius: 3px; - overflow: hidden; - display: flex; - justify-content: space-between; - align-items: center; - min-height: 24px; + div.root-content { + width: 50%; + height: 100%; + max-width: 180px; - input { - padding: 0 5px; - width: 100%; - height: 100%; - border: none; - outline:none; - }; + div.input-content { + box-sizing: border-box; + border: 1px solid transparent; + border-radius: 3px; + overflow: hidden; + display: flex; + justify-content: space-between; + align-items: center; + min-height: 24px; + + input { + width: 100%; + height: 100%; + border: none; + outline:none; + }; + + input:focus { + border: none; + } + + div.button-left, div.button-right { + min-height: 24px; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + vertical-align: middle; + cursor: pointer; + user-select: none; + padding: 0 3px; + } + } + + div.input-content.error { + border: 1px solid $lt-red; + } - input:focus { - border: none; - } + div.input-content.focus { + border: 1px solid $lt-blue; + } - div.button-left, div.button-right { - height: 100%; - display: flex; - justify-content: center; - align-items: center; - vertical-align: middle; - cursor: pointer; - user-select: none; - padding: 0 3px; - } - } + div.err-message { + color: $lt-red; + padding-top: 5px; + } + } } div.dark.attr-input { diff --git a/source/Component/AttrInput/AttrInput.tsx b/source/Component/AttrInput/AttrInput.tsx index dd273b4..633bc06 100644 --- a/source/Component/AttrInput/AttrInput.tsx +++ b/source/Component/AttrInput/AttrInput.tsx @@ -2,35 +2,143 @@ import { Component, ReactNode } from "react"; import { FontLevel, Theme } from "@Component/Theme/Theme"; import "./AttrInput.scss"; import { Icon } from "@fluentui/react"; +import { Localization, AllI18nKeys } from "@Component/Localization/Localization"; interface IAttrInputProps { + keyI18n: AllI18nKeys; + infoI18n?: AllI18nKeys; + value?: number | string; isNumber?: boolean; + maxLength?: number; + max?: number; + min?: number; + step?: number; + valueChange?: (value: this["isNumber"] extends true ? number : string) => any; } -class AttrInput extends Component { +interface AttrInputState { + error: ReactNode; + value: string; +} + +class AttrInput extends Component { + + public constructor(props: IAttrInputProps) { + super(props); + const value = props.value ?? props.isNumber ? "0" : ""; + this.state = { + error: this.check(value), + value: value + } + } + + private check(value: string): ReactNode { + + // 长度校验 + const maxLength = this.props.maxLength ?? 32; + if (value.length > maxLength) { + return + } + + if (this.props.isNumber) { + const praseNumber = (value as any) / 1; + + // 数字校验 + if (isNaN(praseNumber)) { + return + } + + // 最大值校验 + if (this.props.max !== undefined && praseNumber > this.props.max) { + return + } + + // 最小值校验 + if (this.props.min !== undefined && praseNumber < this.props.min) { + return + } + + } + return undefined; + } + + private handelValueChange = () => { + if (!this.state.error && this.props.valueChange) { + this.props.valueChange(this.state.value); + } + } + + private changeValue = (direction: number) => { + if (this.state.error) { + return; + } else { + let newVal = (this.state.value as any / 1) + (this.props.step ?? 1) * direction; + + // 最大值校验 + if (this.props.max !== undefined && newVal > this.props.max) { + newVal = this.props.max; + } + + // 最小值校验 + if (this.props.min !== undefined && newVal < this.props.min) { + newVal = this.props.min; + } + + this.setState( + { value: newVal.toString() }, + () => this.handelValueChange() + ); + } + } + public render(): ReactNode { return -
- Input -
-
- { - this.props.isNumber ?
- -
: null - } - - { - this.props.isNumber ?
- -
: null - } -
-
+ className="attr-input" + fontLevel={FontLevel.normal} + > +
+ +
+
+
+ { + this.props.isNumber ?
this.changeValue(-1)} + > + +
: null + } + { + this.setState({ + error: this.check(e.target.value), + value: e.target.value + }, () => this.handelValueChange()); + }} + > + { + this.props.isNumber ?
this.changeValue(1)} + > + +
: null + } +
+ { +
+ {this.state.error} +
+ } +
+ } } diff --git a/source/Component/Container/Container.scss b/source/Component/Container/Container.scss index 99442d6..a2d4287 100644 --- a/source/Component/Container/Container.scss +++ b/source/Component/Container/Container.scss @@ -27,7 +27,7 @@ div.app-container { } div:hover { - background-color: blue; + background-color: $lt-blue; } } @@ -83,7 +83,7 @@ div.app-container { } div.app-tab-header-item.active { - border: .8px solid blue; + border: .8px solid $lt-blue; transition: none; } @@ -141,7 +141,7 @@ div.app-container { } div.app-panel-root.active { - border: .8px solid blue !important; + border: .8px solid $lt-blue !important; } } diff --git a/source/Component/Theme/Theme.scss b/source/Component/Theme/Theme.scss index 3f24727..8069a6d 100644 --- a/source/Component/Theme/Theme.scss +++ b/source/Component/Theme/Theme.scss @@ -1,5 +1,8 @@ @import "@fluentui/react/dist/sass/References"; +$lt-blue: rgb(81, 79, 235); +$lt-red: rgb(240, 94, 94); + $lt-font-size-normal: 13px; $lt-font-size-lvl3: $ms-font-size-16; $lt-font-size-lvl2: $ms-font-size-18; diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index fda2eff..dc506d9 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -19,6 +19,10 @@ const EN_US = { "Command.Bar.Add.Tag.Info": "Add label object", "Command.Bar.Camera.Info": "Renderer settings", "Command.Bar.Setting.Info": "Global Settings", + "Input.Error.Not.Number": "Please key in numbers", + "Input.Error.Max": "Enter value must be less than {num}", + "Input.Error.Min": "Enter value must be greater than {num}", + "Input.Error.Length": "The length of the input content must be less than {num}", "Object.List.New.Group": "Group object {id}", "Object.List.New.Range": "Range object {id}", "Object.List.No.Data": "There are no objects in the model, click the button to create it", @@ -30,6 +34,9 @@ const EN_US = { "Panel.Info.Object.List.View": "Edit View All Object Properties", "Panel.Title.Range.Details.View": "Range attributes", "Panel.Info.Range.Details.View": "Edit View Range attributes", - + "Common.Attr.Key.Display.Name": "Display name", + "Common.Attr.Key.Position.X": "Position X", + "Common.Attr.Key.Position.Y": "Position Y", + "Common.Attr.Key.Position.Z": "Position Z", } export default EN_US; \ No newline at end of file diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index c986442..7e6f645 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -19,6 +19,10 @@ const ZH_CN = { "Command.Bar.Add.Tag.Info": "添加标签对象", "Command.Bar.Camera.Info": "渲染器设置", "Command.Bar.Setting.Info": "全局设置", + "Input.Error.Not.Number": "请输入数字", + "Input.Error.Max": "输入数值须小于 {number}", + "Input.Error.Min": "输入数值须大于 {number}", + "Input.Error.Length": "输入内容长度须小于 {number}", "Object.List.New.Group": "组对象 {id}", "Object.List.New.Range": "范围对象 {id}", "Object.List.No.Data": "模型中没有任何对象,点击按钮以创建", @@ -30,5 +34,9 @@ const ZH_CN = { "Panel.Info.Object.List.View": "编辑查看全部对象属性", "Panel.Title.Range.Details.View": "范围属性", "Panel.Info.Range.Details.View": "编辑查看范围属性", + "Common.Attr.Key.Display.Name": "显示名称", + "Common.Attr.Key.Position.X": "X 坐标", + "Common.Attr.Key.Position.Y": "Y 坐标", + "Common.Attr.Key.Position.Z": "Z 坐标", } export default ZH_CN; \ No newline at end of file diff --git a/source/Panel/RangeDetails/RangeDetails.tsx b/source/Panel/RangeDetails/RangeDetails.tsx index 0e456db..52cf4b7 100644 --- a/source/Panel/RangeDetails/RangeDetails.tsx +++ b/source/Panel/RangeDetails/RangeDetails.tsx @@ -5,8 +5,10 @@ import "./RangeDetails.scss"; class RangeDetails extends Component { public render(): ReactNode { return
- - + + + +
} } From f3ba3b61507bef0f92525fcdd9892eaa63f89939 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Mon, 7 Mar 2022 17:47:55 +0800 Subject: [PATCH 5/7] Add Attrinput component --- source/Component/AttrInput/AttrInput.scss | 1 + source/Component/AttrInput/AttrInput.tsx | 120 +++++++++++---------- source/Context/Status.tsx | 15 +++ source/Localization/EN-US.ts | 3 + source/Localization/ZH-CN.ts | 3 + source/Model/Model.ts | 2 +- source/Model/Range.ts | 2 +- source/Panel/ObjectList/ObjectList.tsx | 2 +- source/Panel/RangeDetails/RangeDetails.tsx | 80 ++++++++++++-- 9 files changed, 162 insertions(+), 66 deletions(-) diff --git a/source/Component/AttrInput/AttrInput.scss b/source/Component/AttrInput/AttrInput.scss index ce1cd82..78678d7 100644 --- a/source/Component/AttrInput/AttrInput.scss +++ b/source/Component/AttrInput/AttrInput.scss @@ -66,6 +66,7 @@ div.attr-input { div.err-message { color: $lt-red; padding-top: 5px; + min-height: 24px; } } } diff --git a/source/Component/AttrInput/AttrInput.tsx b/source/Component/AttrInput/AttrInput.tsx index 633bc06..fba16e8 100644 --- a/source/Component/AttrInput/AttrInput.tsx +++ b/source/Component/AttrInput/AttrInput.tsx @@ -13,24 +13,15 @@ interface IAttrInputProps { max?: number; min?: number; step?: number; + disable?: boolean; + disableI18n?: AllI18nKeys; valueChange?: (value: this["isNumber"] extends true ? number : string) => any; } -interface AttrInputState { - error: ReactNode; - value: string; -} +class AttrInput extends Component { -class AttrInput extends Component { - - public constructor(props: IAttrInputProps) { - super(props); - const value = props.value ?? props.isNumber ? "0" : ""; - this.state = { - error: this.check(value), - value: value - } - } + private value: string = ""; + private error: ReactNode; private check(value: string): ReactNode { @@ -63,16 +54,17 @@ class AttrInput extends Component { } private handelValueChange = () => { - if (!this.state.error && this.props.valueChange) { - this.props.valueChange(this.state.value); + if (!this.error && this.props.valueChange) { + this.props.valueChange(this.value); } + this.forceUpdate(); } private changeValue = (direction: number) => { - if (this.state.error) { + if (this.error) { return; } else { - let newVal = (this.state.value as any / 1) + (this.props.step ?? 1) * direction; + let newVal = (this.value as any / 1) + (this.props.step ?? 1) * direction; // 最大值校验 if (this.props.max !== undefined && newVal > this.props.max) { @@ -84,15 +76,60 @@ class AttrInput extends Component { newVal = this.props.min; } - this.setState( - { value: newVal.toString() }, - () => this.handelValueChange() - ); + this.value = newVal.toString(); + this.handelValueChange() } } + private renderInput() { + return <> +
+ { + this.props.isNumber ?
this.changeValue(-1)} + > + +
: null + } + { + this.value = e.target.value; + this.error = this.check(e.target.value); + this.handelValueChange(); + }} + > + { + this.props.isNumber ?
this.changeValue(1)} + > + +
: null + } +
+ { + this.error ? +
+ {this.error} +
: null + } + + } + public render(): ReactNode { + if (!this.error) { + const value = this.props.value ?? (this.props.isNumber ? "0" : ""); + this.value = value.toString(); + this.error = this.check(value.toString()); + } + return {
-
- { - this.props.isNumber ?
this.changeValue(-1)} - > - -
: null - } - { - this.setState({ - error: this.check(e.target.value), - value: e.target.value - }, () => this.handelValueChange()); - }} - > - { - this.props.isNumber ?
this.changeValue(1)} - > - -
: null - } -
{ -
- {this.state.error} -
+ this.props.disable ? + this.props.disableI18n ? + : +
{this.props.value}
: + this.renderInput() }
diff --git a/source/Context/Status.tsx b/source/Context/Status.tsx index 3289c69..81cbdcc 100644 --- a/source/Context/Status.tsx +++ b/source/Context/Status.tsx @@ -1,6 +1,7 @@ import { createContext, Component, FunctionComponent, useState, useEffect, ReactNode } from "react"; import { Emitter } from "@Model/Emitter"; import { Model, ObjectID } from "@Model/Model"; +import { Range } from "@Model/Range"; import { Archive } from "@Model/Archive"; import { AbstractRenderer } from "@Model/Renderer"; import { ClassicRenderer, MouseMod } from "@GLRender/ClassicRenderer"; @@ -22,6 +23,7 @@ interface IStatusEvent { focusObjectChange: void; objectChange: void; labelChange: void; + rangeAttrChange: void; } class Status extends Emitter { @@ -83,6 +85,19 @@ class Status extends Emitter { this.emit("focusObjectChange"); } + /** + * 修改范围属性 + */ + public changeRangeAttrib + (id: ObjectID, key: K, val: Range[K]) { + const range = this.model.getObjectById(id); + if (range && range instanceof Range) { + range[key] = val; + this.emit("rangeAttrChange"); + this.model.draw(); + } + } + /** * 鼠标工具状态 */ diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index dc506d9..6211a6d 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -38,5 +38,8 @@ const EN_US = { "Common.Attr.Key.Position.X": "Position X", "Common.Attr.Key.Position.Y": "Position Y", "Common.Attr.Key.Position.Z": "Position Z", + "Common.Attr.Key.Error.Multiple": "Cannot edit multiple values", + "Panel.Info.Range.Details.Attr.Error.Not.Range": "The focus object is not a Range", + "Panel.Info.Range.Details.Attr.Error.Unspecified": "Unspecified range object", } export default EN_US; \ No newline at end of file diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index 7e6f645..bed66b3 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -38,5 +38,8 @@ const ZH_CN = { "Common.Attr.Key.Position.X": "X 坐标", "Common.Attr.Key.Position.Y": "Y 坐标", "Common.Attr.Key.Position.Z": "Z 坐标", + "Common.Attr.Key.Error.Multiple": "无法编辑多重数值", + "Panel.Info.Range.Details.Attr.Error.Not.Range": "焦点对象不是一个范围", + "Panel.Info.Range.Details.Attr.Error.Unspecified": "未指定范围对象", } export default ZH_CN; \ No newline at end of file diff --git a/source/Model/Model.ts b/source/Model/Model.ts index 95fd3c8..cda7ccb 100644 --- a/source/Model/Model.ts +++ b/source/Model/Model.ts @@ -39,7 +39,7 @@ class Model extends Emitter { public getObjectById(id: ObjectID): CtrlObject | undefined { for (let i = 0; i < this.objectPool.length; i++) { - if (this.objectPool[i].id === id) { + if (this.objectPool[i].id.toString() === id.toString()) { return this.objectPool[i]; } } diff --git a/source/Model/Range.ts b/source/Model/Range.ts index 6177d68..5e7c1f3 100644 --- a/source/Model/Range.ts +++ b/source/Model/Range.ts @@ -8,7 +8,7 @@ class Range extends CtrlObject { /** * 坐标 */ - public position: number[] = []; + public position: number[] = [0, 0, 0]; /** * 半径 diff --git a/source/Panel/ObjectList/ObjectList.tsx b/source/Panel/ObjectList/ObjectList.tsx index 5d6fc7e..fbbee28 100644 --- a/source/Panel/ObjectList/ObjectList.tsx +++ b/source/Panel/ObjectList/ObjectList.tsx @@ -7,7 +7,7 @@ import { ObjectID } from "@Model/Renderer"; import "./ObjectList.scss"; @useSetting -@useStatusWithEvent("objectChange", "focusObjectChange") +@useStatusWithEvent("objectChange", "focusObjectChange", "rangeAttrChange") class ObjectList extends Component { private renderList() { diff --git a/source/Panel/RangeDetails/RangeDetails.tsx b/source/Panel/RangeDetails/RangeDetails.tsx index 52cf4b7..57d2e34 100644 --- a/source/Panel/RangeDetails/RangeDetails.tsx +++ b/source/Panel/RangeDetails/RangeDetails.tsx @@ -1,15 +1,81 @@ import { Component, ReactNode } from "react"; import { AttrInput } from "@Component/AttrInput/AttrInput"; +import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; +import { AllI18nKeys } from "@Component/Localization/Localization"; +import { Range } from "@Model/Range"; +import { ObjectID } from "@Model/Renderer"; import "./RangeDetails.scss"; -class RangeDetails extends Component { +@useStatusWithEvent("rangeAttrChange", "focusObjectChange") +class RangeDetails extends Component { + + private renderErrorFrom(error: AllI18nKeys) { + return <> + + + + + + } + + private renderFrom(range: Range) { + return <> + { + this.props.status ? this.props.status.changeRangeAttrib(range.id, "displayName", e) : null; + }} + /> + { + if (this.props.status) { + range.position[0] = (e as any) / 1; + this.props.status.changeRangeAttrib(range.id, "position", range.position); + } + }} + /> + { + this.props.status ? this.props.status.changeRangeAttrib(range.id, "displayName", e) : null; + }} + /> + { + this.props.status ? this.props.status.changeRangeAttrib(range.id, "displayName", e) : null; + }} + /> + + } + public render(): ReactNode { - return
- - - - -
+ if (this.props.status) { + if (this.props.status.focusObject.size <= 0) { + return this.renderErrorFrom("Panel.Info.Range.Details.Attr.Error.Unspecified"); + } + if (this.props.status.focusObject.size > 1) { + return this.renderErrorFrom("Common.Attr.Key.Error.Multiple"); + } + let id: ObjectID = 0; + this.props.status.focusObject.forEach((cid => id = cid)); + + let range = this.props.status!.model.getObjectById(id); + + if (range instanceof Range) { + return this.renderFrom(range); + } else { + return this.renderErrorFrom("Panel.Info.Range.Details.Attr.Error.Not.Range"); + } + } + return this.renderErrorFrom("Panel.Info.Range.Details.Attr.Error.Unspecified"); } } From 0e1aa51d4bc84ebe12c1e5f6691d08d2231d01a9 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Mon, 7 Mar 2022 20:29:42 +0800 Subject: [PATCH 6/7] Fix input clean error --- source/Component/AttrInput/AttrInput.scss | 13 +++++++++---- source/Component/AttrInput/AttrInput.tsx | 21 +++++++++++++++------ source/Model/Layout.ts | 14 +++++++++----- source/Panel/RangeDetails/RangeDetails.tsx | 14 ++++++++++++-- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/source/Component/AttrInput/AttrInput.scss b/source/Component/AttrInput/AttrInput.scss index 78678d7..727a96e 100644 --- a/source/Component/AttrInput/AttrInput.scss +++ b/source/Component/AttrInput/AttrInput.scss @@ -1,9 +1,12 @@ @import "../Theme/Theme.scss"; +$line-min-height: 24px; +$root-min-height: 26px; + div.attr-input { width: 100%; display: flex; - min-height: 24px; + min-height: $line-min-height; padding: 5px 0; div.input-intro { @@ -20,6 +23,7 @@ div.attr-input { width: 50%; height: 100%; max-width: 180px; + min-height: $root-min-height; div.input-content { box-sizing: border-box; @@ -29,13 +33,14 @@ div.attr-input { display: flex; justify-content: space-between; align-items: center; - min-height: 24px; + min-height: $root-min-height; input { width: 100%; height: 100%; border: none; outline:none; + min-height: $line-min-height; }; input:focus { @@ -43,7 +48,7 @@ div.attr-input { } div.button-left, div.button-right { - min-height: 24px; + min-height: $line-min-height; height: 100%; display: flex; justify-content: center; @@ -66,7 +71,7 @@ div.attr-input { div.err-message { color: $lt-red; padding-top: 5px; - min-height: 24px; + min-height: $line-min-height; } } } diff --git a/source/Component/AttrInput/AttrInput.tsx b/source/Component/AttrInput/AttrInput.tsx index fba16e8..37213fa 100644 --- a/source/Component/AttrInput/AttrInput.tsx +++ b/source/Component/AttrInput/AttrInput.tsx @@ -55,6 +55,10 @@ class AttrInput extends Component { private handelValueChange = () => { if (!this.error && this.props.valueChange) { + if (this.props.isNumber) { + let numberVal = (this.value as any) * 10000; + this.value = (Math.round(numberVal) / 10000).toString(); + } this.props.valueChange(this.value); } this.forceUpdate(); @@ -122,13 +126,18 @@ class AttrInput extends Component { } - public render(): ReactNode { + public shouldComponentUpdate(nextProps: IAttrInputProps) { + this.updateValueFromProps(nextProps.value); + return true; + } - if (!this.error) { - const value = this.props.value ?? (this.props.isNumber ? "0" : ""); - this.value = value.toString(); - this.error = this.check(value.toString()); - } + private updateValueFromProps(val: IAttrInputProps["value"]) { + const value = val ?? (this.props.isNumber ? "0" : ""); + this.value = value.toString(); + this.error = this.check(value.toString()); + } + + public render(): ReactNode { return { } public focus = (panelId: string) => { - if (panelId === "") { + if (panelId === "" && this.focusId !== "") { this.focusId = panelId; this.emit("switchTab", this); } @@ -89,10 +89,14 @@ class Layout extends Emitter { } } if (index >= 0) { - layout.focusPanel = panelId; - this.focusId = panelId; - this.emit("switchTab", this); - return true; + if (layout.focusPanel === panelId && this.focusId === panelId) { + return true; + } else { + layout.focusPanel = panelId; + this.focusId = panelId; + this.emit("switchTab", this); + return true; + } } } }) diff --git a/source/Panel/RangeDetails/RangeDetails.tsx b/source/Panel/RangeDetails/RangeDetails.tsx index 57d2e34..d54160a 100644 --- a/source/Panel/RangeDetails/RangeDetails.tsx +++ b/source/Panel/RangeDetails/RangeDetails.tsx @@ -40,17 +40,27 @@ class RangeDetails extends Component { }} /> { - this.props.status ? this.props.status.changeRangeAttrib(range.id, "displayName", e) : null; + if (this.props.status) { + range.position[1] = (e as any) / 1; + this.props.status.changeRangeAttrib(range.id, "position", range.position); + } }} /> { - this.props.status ? this.props.status.changeRangeAttrib(range.id, "displayName", e) : null; + if (this.props.status) { + range.position[2] = (e as any) / 1; + this.props.status.changeRangeAttrib(range.id, "position", range.position); + } }} /> From 8f5fff920f31a12a9f03b87679e306a8d9e5d622 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Tue, 8 Mar 2022 11:51:04 +0800 Subject: [PATCH 7/7] Optimize decimal point number input --- source/Component/AttrInput/AttrInput.tsx | 15 +++++++++++++-- source/Panel/RangeDetails/RangeDetails.tsx | 4 ++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/source/Component/AttrInput/AttrInput.tsx b/source/Component/AttrInput/AttrInput.tsx index 37213fa..d798072 100644 --- a/source/Component/AttrInput/AttrInput.tsx +++ b/source/Component/AttrInput/AttrInput.tsx @@ -3,8 +3,10 @@ import { FontLevel, Theme } from "@Component/Theme/Theme"; import "./AttrInput.scss"; import { Icon } from "@fluentui/react"; import { Localization, AllI18nKeys } from "@Component/Localization/Localization"; +import { ObjectID } from "@Model/Renderer"; interface IAttrInputProps { + id?: ObjectID; keyI18n: AllI18nKeys; infoI18n?: AllI18nKeys; value?: number | string; @@ -35,7 +37,7 @@ class AttrInput extends Component { const praseNumber = (value as any) / 1; // 数字校验 - if (isNaN(praseNumber)) { + if (isNaN(praseNumber) || /\.0*$/.test(value)) { return } @@ -127,7 +129,16 @@ class AttrInput extends Component { } public shouldComponentUpdate(nextProps: IAttrInputProps) { - this.updateValueFromProps(nextProps.value); + + // ID 都为空时更新 + if (!nextProps.id && !this.props.id) { + this.updateValueFromProps(nextProps.value); + } + + // ID 变换时更新 State 到最新的 Props + if (nextProps.id !== this.props.id) { + this.updateValueFromProps(nextProps.value); + } return true; } diff --git a/source/Panel/RangeDetails/RangeDetails.tsx b/source/Panel/RangeDetails/RangeDetails.tsx index d54160a..21c944d 100644 --- a/source/Panel/RangeDetails/RangeDetails.tsx +++ b/source/Panel/RangeDetails/RangeDetails.tsx @@ -21,6 +21,7 @@ class RangeDetails extends Component { private renderFrom(range: Range) { return <> { @@ -28,6 +29,7 @@ class RangeDetails extends Component { }} /> { }} /> { }} />