diff --git a/source/Component/BehaviorPicker/BehaviorPicker.scss b/source/Component/BehaviorPicker/BehaviorPicker.scss index 7e0889a..5f9b494 100644 --- a/source/Component/BehaviorPicker/BehaviorPicker.scss +++ b/source/Component/BehaviorPicker/BehaviorPicker.scss @@ -39,8 +39,11 @@ div.behavior-picker-list { i.view-icon { display: none; } + } - i.view-icon:hover { + div.behavior-picker-line-icon-view:hover { + + i.view-icon { color: $lt-green; } } @@ -57,16 +60,28 @@ div.behavior-picker-list { } } + div.behavior-picker-title.is-deleted { + + div { + text-decoration: line-through; + opacity: 0.6; + } + } + + div.behavior-picker-title.behavior-add-line { + width: calc(100% - 30px); + } + div.behavior-picker-line-delete-view { width: 30px; height: 100%; display: flex; justify-content: center; align-items: center; + } - i:hover { - color: $lt-red; - } + div.behavior-picker-line-delete-view:hover i { + color: $lt-red; } } diff --git a/source/Component/BehaviorPicker/BehaviorPicker.tsx b/source/Component/BehaviorPicker/BehaviorPicker.tsx index 92813d5..faab062 100644 --- a/source/Component/BehaviorPicker/BehaviorPicker.tsx +++ b/source/Component/BehaviorPicker/BehaviorPicker.tsx @@ -1,77 +1,190 @@ import { DetailsList } from "@Component/DetailsList/DetailsList"; -import { Component, ReactNode } from "react"; +import { Component, ReactNode, createRef } from "react"; import { Behavior } from "@Model/Behavior"; import { Icon } from "@fluentui/react"; import { useSettingWithEvent, IMixinSettingProps } from "@Context/Setting"; +import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; import { Localization } from "@Component/Localization/Localization"; +import { PickerList } from "@Component/PickerList/PickerList"; import "./BehaviorPicker.scss"; interface IBehaviorPickerProps { behavior: Behavior[]; + focusBehavior?: Behavior; + click?: (behavior: Behavior) => void; delete?: (behavior: Behavior) => void; action?: (behavior: Behavior) => void; - add?: () => void; + add?: (behavior: Behavior) => void; } +interface IBehaviorPickerState { + isPickerListOpen: boolean; +} + +@useStatusWithEvent("behaviorChange") @useSettingWithEvent("language") -class BehaviorPicker extends Component { +class BehaviorPicker extends Component { + + public state = { + isPickerListOpen: false + } + + private isInnerClick: boolean = false; + private clickLineRef = createRef(); private getData() { - let data: Array<{key: string, behavior: Behavior | undefined}> = []; + let data: Array<{select: boolean, key: string, behavior: Behavior | undefined}> = []; for (let i = 0; i < this.props.behavior.length; i++) { data.push({ key: this.props.behavior[i].id, - behavior: this.props.behavior[i] + behavior: this.props.behavior[i], + select: this.props.behavior[i].id === this.props.focusBehavior?.id }) } data.push({ key: "@@AddButton_List_Key", - behavior: undefined + behavior: undefined, + select: false }) return data; } private renderLine = (behavior?: Behavior): ReactNode => { if (behavior) { + + const titleClassList: string[] = ["behavior-picker-title"]; + if (this.props.setting) { + titleClassList.push(this.props.setting.language); + } + if (behavior.isDeleted()) { + titleClassList.push("is-deleted"); + } + return <>
-
- - +
{ + this.isInnerClick = true; + this.props.action && this.props.action(behavior); + }} + > + { + behavior.isDeleted() ? + : + <> + + + + }
-
+
{behavior.name}
-
+
{ + this.isInnerClick = true; + this.props.delete && this.props.delete(behavior); + }} + >
; } else { + + const openPicker = () => { + this.isInnerClick = true; + this.setState({ + isPickerListOpen: true + }); + } + return <> -
+
-
+
; } } - public render(): ReactNode { - return { + if (this.props.status) { + let res: Behavior[] = []; + for (let i = 0; i < this.props.status.model.behaviorPool.length; i++) { + + let isAdded = false; + for (let j = 0; j < this.props.behavior.length; j++) { + if (this.props.status.model.behaviorPool[i].id === this.props.behavior[j].id) { + isAdded = true; + break; + } + } + + if (!isAdded) { + res.push(this.props.status.model.behaviorPool[i]); + } + } + return res; + } else { + return []; + } + } + + private renderPickerList(): ReactNode { + return { + if (item instanceof Behavior && this.props.add) { + this.props.add(item); + } + this.setState({ + isPickerListOpen: false + }) + })} + dismiss={() => { + this.setState({ + isPickerListOpen: false + }); + }} /> } + + public render(): ReactNode { + return <> + { + if (!this.isInnerClick && this.props.click && item.behavior) { + this.props.click(item.behavior); + } + this.isInnerClick = false; + }} + columns={[{ + className: "behavior-picker-line", + key: "behavior", + render: this.renderLine + }]} + /> + {this.state.isPickerListOpen ? this.renderPickerList() : null} + + } } export { BehaviorPicker }; \ No newline at end of file diff --git a/source/Component/ObjectPicker/ObjectPicker.tsx b/source/Component/ObjectPicker/ObjectPicker.tsx index 8670868..f3940b5 100644 --- a/source/Component/ObjectPicker/ObjectPicker.tsx +++ b/source/Component/ObjectPicker/ObjectPicker.tsx @@ -8,6 +8,7 @@ import { PickerList, IDisplayItem, getObjectDisplayInfo, IDisplayInfo } from ".. import { Localization } from "@Component/Localization/Localization"; import { Icon } from "@fluentui/react"; import { CtrlObject } from "@Model/CtrlObject"; +import { Behavior } from "@Model/Behavior"; import "./ObjectPicker.scss"; type IObjectType = Label | Group | Range | CtrlObject; @@ -80,6 +81,7 @@ class ObjectPicker extends Component { + if (item instanceof Behavior) return; if (this.props.valueChange) { this.props.valueChange(item); } diff --git a/source/Component/PickerList/PickerList.tsx b/source/Component/PickerList/PickerList.tsx index 9533221..5b2cb3b 100644 --- a/source/Component/PickerList/PickerList.tsx +++ b/source/Component/PickerList/PickerList.tsx @@ -1,5 +1,6 @@ import { AllI18nKeys, Localization } from "@Component/Localization/Localization"; import { Callout, DirectionalHint, Icon } from "@fluentui/react"; +import { Behavior } from "@Model/Behavior"; import { CtrlObject } from "@Model/CtrlObject"; import { Group } from "@Model/Group"; import { Label } from "@Model/Label"; @@ -7,7 +8,7 @@ import { Range } from "@Model/Range"; import { Component, ReactNode, RefObject } from "react"; import "./PickerList.scss"; -type IPickerListItem = CtrlObject | Label | Range | Group; +type IPickerListItem = CtrlObject | Label | Range | Group | Behavior; interface IDisplayInfo { color: string; icon: string; @@ -75,6 +76,14 @@ function getObjectDisplayInfo(item?: IPickerListItem): IDisplayInfo { } } + if (item instanceof Behavior) { + color = item.color; + icon = item.iconName; + name = item.name; + internal = false; + allLabel = false; + } + if (Array.isArray(color)) { color = `rgb(${color[0]},${color[1]},${color[2]})`; } @@ -159,7 +168,7 @@ class PickerList extends Component { return
{this.props.objectList ? this.props.objectList.map((item) => { diff --git a/source/Context/Status.tsx b/source/Context/Status.tsx index 91a2f6b..768adba 100644 --- a/source/Context/Status.tsx +++ b/source/Context/Status.tsx @@ -38,6 +38,7 @@ interface IStatusEvent { objectChange: void; rangeLabelChange: void; groupLabelChange: void; + groupBehaviorChange: void; labelChange: void; rangeAttrChange: void; labelAttrChange: void; @@ -192,6 +193,22 @@ class Status extends Emitter { } } + public addGroupBehavior(id: ObjectID, val: Behavior) { + const group = this.model.getObjectById(id); + if (group && group instanceof Group) { + group.addBehavior(val); + this.emit("groupBehaviorChange"); + } + } + + public deleteGroupBehavior(id: ObjectID, val: Behavior) { + const group = this.model.getObjectById(id); + if (group && group instanceof Group) { + group.deleteBehavior(val); + this.emit("groupBehaviorChange"); + } + } + public addGroupLabel(id: ObjectID, val: Label) { const group = this.model.getObjectById(id); if (group && group instanceof Group) { diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index ba2cfe8..79d6f1b 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -31,6 +31,7 @@ const EN_US = { "Object.List.No.Data": "There are no objects in the model, click the button to create it", "Object.Picker.List.No.Data": "There is no model in the model for this option", "Behavior.Picker.Add.Button": "Click here to assign behavior to this group", + "Behavior.Picker.Add.Nodata": "There is no behavior that can be specified", "Panel.Title.Notfound": "{id}", "Panel.Info.Notfound": "This panel with id {id} can not found!", "Panel.Title.Render.View": "Live preview", diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index 8cbe5bb..7f582fc 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -31,6 +31,7 @@ const ZH_CN = { "Object.List.No.Data": "模型中没有任何对象,点击按钮以创建", "Object.Picker.List.No.Data": "模型中没有合适此选项的模型", "Behavior.Picker.Add.Button": "点击此处以赋予行为到此群", + "Behavior.Picker.Add.Nodata": "没有可以被指定的行为", "Panel.Title.Notfound": "{id}", "Panel.Info.Notfound": "这个编号为 {id} 的面板无法找到!", "Panel.Title.Render.View": "实时预览", diff --git a/source/Model/Group.ts b/source/Model/Group.ts index 21dcf27..6942210 100644 --- a/source/Model/Group.ts +++ b/source/Model/Group.ts @@ -315,8 +315,12 @@ class Group extends CtrlObject { public addBehavior(behavior: Behavior | Behavior[]): this { if (Array.isArray(behavior)) { this.behaviors = this.behaviors.concat(behavior); + for (let i = 0; i < behavior.length; i++) { + behavior[i].mount(this, this.model); + } } else { this.behaviors.push(behavior); + behavior.mount(this, this.model); } // 按照优先级 @@ -326,6 +330,27 @@ class Group extends CtrlObject { return this; } + /** + * 删除行为 + * @param behavior 添加行为 + */ + public deleteBehavior(behavior: Behavior): this { + + let deleteIndex = -1; + for (let i = 0; i < this.behaviors.length; i++) { + if (this.behaviors[i].id === behavior.id) { + deleteIndex = i; + } + } + + if (deleteIndex >= 0) { + this.behaviors[deleteIndex].unmount(this, this.model); + this.behaviors.splice(deleteIndex, 1); + } + + return this; + } + /** * 执行行为影响 * @param @@ -333,7 +358,11 @@ class Group extends CtrlObject { public runner(t: number, effectType: "finalEffect" | "effect" | "afterEffect" ): void { this.individuals.forEach((individual) => { for(let j = 0; j < this.behaviors.length; j++) { - this.behaviors[j][effectType](individual, this, this.model, t); + if (this.behaviors[j].isDeleted()) { + continue; + } else { + this.behaviors[j][effectType](individual, this, this.model, t); + } } }); } diff --git a/source/Panel/GroupDetails/GroupDetails.tsx b/source/Panel/GroupDetails/GroupDetails.tsx index baa824b..5108e95 100644 --- a/source/Panel/GroupDetails/GroupDetails.tsx +++ b/source/Panel/GroupDetails/GroupDetails.tsx @@ -25,7 +25,10 @@ const allOption: IDisplayItem[] = [ {nameKey: "Common.Attr.Key.Generation.Mod.Range", key: GenMod.Range} ]; -@useStatusWithEvent("groupAttrChange", "groupLabelChange", "focusObjectChange") +@useStatusWithEvent( + "groupAttrChange", "groupLabelChange", "focusObjectChange", + "focusBehaviorChange", "behaviorChange", "groupBehaviorChange" +) class GroupDetails extends Component { private renderFrom(group: Group) { @@ -113,6 +116,19 @@ class GroupDetails extends Component { { + this.props.status?.setBehaviorObject(behavior); + }} + action={(behavior) => { + this.props.status?.setBehaviorObject(behavior); + }} + delete={(behavior) => { + this.props.status?.deleteGroupBehavior(group.id, behavior); + }} + add={(behavior) => { + this.props.status?.addGroupBehavior(group.id, behavior); + }} />