Add behavior details panel & auto switch behavior details panels & mod behavior model add cache object #34
| @ -112,7 +112,7 @@ class BehaviorList extends Component<IBehaviorListProps & IMixinSettingProps & I | ||||
| 		if (behavior instanceof Behavior) { | ||||
| 			id = behavior.id; | ||||
| 			name = behavior.name; | ||||
| 			color = behavior.color; | ||||
| 			color = `rgb(${behavior.color.join(",")})`; | ||||
| 			needLocal = false; | ||||
|             info = behavior.behaviorName; | ||||
| 		} | ||||
|  | ||||
| @ -62,7 +62,7 @@ class BehaviorPicker extends Component<IBehaviorPickerProps & IMixinSettingProps | ||||
| 
 | ||||
|             return <> | ||||
|                 <div className="behavior-picker-line-color-view"> | ||||
|                     <div style={{ borderLeft: `10px solid ${behavior.color}` }}/> | ||||
|                     <div style={{ borderLeft: `10px solid rgb(${behavior.color.join(",")})` }}/> | ||||
|                 </div> | ||||
|                 <div | ||||
|                     className="behavior-picker-line-icon-view" | ||||
|  | ||||
| @ -123,8 +123,7 @@ class BehaviorPopupComponent extends Component< | ||||
|                 ) + " " + (recorder.nameIndex - 1).toString(); | ||||
| 
 | ||||
|                 // 赋予一个随机颜色
 | ||||
|                 let color = randomColor(true); | ||||
|                 newBehavior.color = `rgb(${color[0]},${color[1]},${color[2]})`; | ||||
|                 newBehavior.color = randomColor(true); | ||||
|             } | ||||
|         }); | ||||
|         this.props.onDismiss ? this.props.onDismiss() : undefined; | ||||
|  | ||||
| @ -11,7 +11,7 @@ import { Setting } from "./Setting"; | ||||
| import { I18N } from "@Component/Localization/Localization"; | ||||
| import { superConnectWithEvent, superConnect } from "./Context"; | ||||
| import { PopupController } from "./Popups"; | ||||
| import { Behavior } from "@Model/Behavior"; | ||||
| import { Behavior, IBehaviorParameter, IParamValue } from "@Model/Behavior"; | ||||
| import { Actuator } from "@Model/Actuator"; | ||||
| 
 | ||||
| function randomColor(unNormal: boolean = false) { | ||||
| @ -43,6 +43,7 @@ interface IStatusEvent { | ||||
|     rangeAttrChange: void; | ||||
|     labelAttrChange: void; | ||||
|     groupAttrChange: void; | ||||
|     behaviorAttrChange: void; | ||||
|     individualChange: void; | ||||
|     behaviorChange: void; | ||||
|     popupChange: void; | ||||
| @ -193,6 +194,24 @@ class Status extends Emitter<IStatusEvent> { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 修改群属性 | ||||
|      */ | ||||
|     public changeBehaviorAttrib<K extends IBehaviorParameter, P extends keyof K | keyof Behavior<K>> | ||||
|     (id: ObjectID, key: P, val: IParamValue<K[P]>) { | ||||
|         const behavior = this.model.getBehaviorById(id); | ||||
|         if (behavior) { | ||||
|             if (key === "color") { | ||||
|                 behavior.color = val as number[]; | ||||
|             } else if (key === "name") { | ||||
|                 behavior.name = val as string; | ||||
|             } else { | ||||
|                 behavior.parameter[key] = val; | ||||
|             } | ||||
|             this.emit("behaviorAttrChange"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public addGroupBehavior(id: ObjectID, val: Behavior) { | ||||
|         const group = this.model.getObjectById(id); | ||||
|         if (group && group instanceof Group) { | ||||
|  | ||||
| @ -48,6 +48,8 @@ const EN_US = { | ||||
|     "Panel.Info.Group.Details.View": "Edit view group attributes", | ||||
|     "Panel.Title.Behavior.List.View": "Behavior list", | ||||
|     "Panel.Info.Behavior.List.View": "Edit view behavior list", | ||||
|     "Panel.Title.Behavior.Details.View": "Behavior", | ||||
|     "Panel.Info.Behavior.Details.View": "Edit view Behavior attributes", | ||||
|     "Popup.Title.Unnamed": "Popup message", | ||||
|     "Popup.Title.Confirm": "Confirm message", | ||||
|     "Popup.Action.Yes": "Confirm", | ||||
| @ -109,5 +111,7 @@ const EN_US = { | ||||
|     "Panel.Info.Group.Details.Attr.Error.Unspecified": "Unspecified group object", | ||||
|     "Panel.Info.Label.Details.Error.Unspecified": "Label object not specified", | ||||
|     "Panel.Info.Label.List.Error.Nodata": "There are no labels in the model, click the button to create", | ||||
|     "Panel.Info.Behavior.Details.Error.Not.Behavior": "Please specify a behavior first to view the details", | ||||
|     "Panel.Info.Behavior.Details.Behavior.Props": "{behavior} parameter", | ||||
| } | ||||
| export default EN_US; | ||||
| @ -48,6 +48,8 @@ const ZH_CN = { | ||||
|     "Panel.Info.Group.Details.View": "编辑查看群属性", | ||||
|     "Panel.Title.Behavior.List.View": "行为列表", | ||||
|     "Panel.Info.Behavior.List.View": "编辑查看行为列表", | ||||
|     "Panel.Title.Behavior.Details.View": "行为", | ||||
|     "Panel.Info.Behavior.Details.View": "编辑查看行为属性", | ||||
|     "Popup.Title.Unnamed": "弹窗消息", | ||||
|     "Popup.Title.Confirm": "确认消息", | ||||
|     "Popup.Action.Yes": "确定", | ||||
| @ -109,5 +111,7 @@ const ZH_CN = { | ||||
|     "Panel.Info.Group.Details.Attr.Error.Unspecified": "未指定群对象", | ||||
|     "Panel.Info.Label.Details.Error.Unspecified": "未指定标签对象", | ||||
|     "Panel.Info.Label.List.Error.Nodata": "模型中没有标签,点击按钮以创建", | ||||
|     "Panel.Info.Behavior.Details.Error.Not.Behavior": "请先指定一个行为以查看详情", | ||||
|     "Panel.Info.Behavior.Details.Behavior.Props": "{behavior}参数", | ||||
| } | ||||
| export default ZH_CN; | ||||
| @ -6,6 +6,11 @@ import type { Model } from "./Model"; | ||||
| import type { Range } from "./Range"; | ||||
| import type { Label } from "./Label"; | ||||
| 
 | ||||
| type IObjectParamCacheType<P, Q = P> = { | ||||
|     picker: P; | ||||
|     objects: Q; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 参数类型 | ||||
|  */ | ||||
| @ -16,12 +21,10 @@ type IMapBasicParamTypeKeyToType = { | ||||
| } | ||||
| 
 | ||||
| type IMapObjectParamTypeKeyToType = { | ||||
|     "R"?: Range; | ||||
|     "G"?: Group; | ||||
|     "GR"?: Group | Range; | ||||
|     "LR"?: Label | Range; | ||||
|     "LG"?: Label | Group; | ||||
|     "LGR"?: Label | Group | Range; | ||||
|     "R": IObjectParamCacheType<Range | undefined>; | ||||
|     "G": IObjectParamCacheType<Group | undefined>; | ||||
|     "LR": IObjectParamCacheType<Label | Range | undefined, Range[]>; | ||||
|     "LG": IObjectParamCacheType<Label | Group | undefined, Range[]>; | ||||
| } | ||||
| 
 | ||||
| type IMapVectorParamTypeKeyToType = { | ||||
| @ -40,7 +43,7 @@ type IParamValue<K extends IParamType> = AllMapType[K]; | ||||
| /** | ||||
|  * 特殊对象类型判定 | ||||
|  */ | ||||
| const objectTypeListEnumSet = new Set<IParamType>(["R", "G", "GR", "LR", "LG", "LGR"]); | ||||
| const objectTypeListEnumSet = new Set<IParamType>(["R", "G", "LR", "LG"]); | ||||
| 
 | ||||
| /** | ||||
|  * 对象断言表达式 | ||||
| @ -247,6 +250,22 @@ class BehaviorRecorder< | ||||
|                     case "vec": | ||||
|                         defaultObj[key] = [0, 0, 0] as any; | ||||
|                         break; | ||||
|                      | ||||
|                     case "G": | ||||
|                     case "R": | ||||
|                         defaultObj[key] = { | ||||
|                             picker: undefined, | ||||
|                             objects: undefined | ||||
|                         } as any; | ||||
|                         break; | ||||
| 
 | ||||
|                     case "LR": | ||||
|                     case "LG": | ||||
|                         defaultObj[key] = { | ||||
|                             picker: undefined, | ||||
|                             objects: [] | ||||
|                         } as any; | ||||
|                         break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @ -295,7 +314,7 @@ class Behavior< | ||||
|     /** | ||||
|      * 颜色 | ||||
|      */ | ||||
|     public color: string = ""; | ||||
|     public color: number[] = [0, 0, 0]; | ||||
| 
 | ||||
|     /** | ||||
|      * 优先级 | ||||
| @ -397,7 +416,7 @@ class Behavior< | ||||
| type IRenderBehavior = BehaviorInfo | Behavior; | ||||
| 
 | ||||
| export { | ||||
|     Behavior, BehaviorRecorder, IBehaviorParameterOption, IBehaviorParameterOptionItem, | ||||
|     IAnyBehavior, IAnyBehaviorRecorder, BehaviorInfo, IRenderBehavior | ||||
|     Behavior, BehaviorRecorder, IBehaviorParameterOption, IBehaviorParameterOptionItem, IParamValue, | ||||
|     IAnyBehavior, IAnyBehaviorRecorder, BehaviorInfo, IRenderBehavior, IBehaviorParameter | ||||
| }; | ||||
| export default { Behavior }; | ||||
| @ -56,11 +56,11 @@ class SimulatorWeb extends Component { | ||||
|             this.status.newLabel().name = "New Label"; | ||||
|             this.status.newLabel().name = "Test Label 01"; | ||||
|             let dynamic = this.status.model.addBehavior(AllBehaviors[0]); | ||||
|             dynamic.name = "Dynamic"; dynamic.color = "rgb(250, 200, 80)"; | ||||
|             dynamic.name = "Dynamic"; dynamic.color = [250, 200, 80]; | ||||
|             let brownian = this.status.model.addBehavior(AllBehaviors[1]); | ||||
|             brownian.name = "Brownian"; brownian.color = "rgb(200, 80, 250)"; | ||||
|             brownian.name = "Brownian"; brownian.color = [200, 80, 250]; | ||||
|             let boundary = this.status.model.addBehavior(AllBehaviors[2]); | ||||
|             boundary.name = "Boundary"; boundary.color = "rgb(80, 200, 250)"; | ||||
|             boundary.name = "Boundary"; boundary.color = [80, 200, 250]; | ||||
|             boundary.parameter.range = this.status.model.allRangeLabel; | ||||
|             group.addBehavior(dynamic); | ||||
|             group.addBehavior(brownian); | ||||
| @ -75,9 +75,9 @@ class SimulatorWeb extends Component { | ||||
|             items: [ | ||||
|                 { | ||||
|                     items: [ | ||||
|                         {panels: ["RenderView", "Label Aa Bb", "Label aaa"]}, | ||||
|                         {panels: ["RenderView"]}, | ||||
|                         { | ||||
|                             items: [{panels: ["BehaviorList", "Label bbb"]}, {panels: ["LabelList"]}], | ||||
|                             items: [{panels: ["BehaviorList"]}, {panels: ["LabelList"]}], | ||||
|                             scale: 80, | ||||
|                             layout: LayoutDirection.X | ||||
|                         } | ||||
| @ -87,9 +87,9 @@ class SimulatorWeb extends Component { | ||||
|                 }, | ||||
|                 { | ||||
|                     items: [{ | ||||
|                         panels: ["ObjectList", "Test tab"] | ||||
|                         panels: ["ObjectList"] | ||||
|                     }, { | ||||
|                         panels: ["GroupDetails", "RangeDetails", "LabelDetails"] | ||||
|                         panels: ["GroupDetails", "RangeDetails", "LabelDetails", "BehaviorDetails"] | ||||
|                     }], | ||||
|                     scale: 30, | ||||
|                     layout: LayoutDirection.Y | ||||
|  | ||||
							
								
								
									
										0
									
								
								source/Panel/BehaviorDetails/BehaviorDetails.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								source/Panel/BehaviorDetails/BehaviorDetails.scss
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										77
									
								
								source/Panel/BehaviorDetails/BehaviorDetails.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								source/Panel/BehaviorDetails/BehaviorDetails.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,77 @@ | ||||
| import { Component, ReactNode} from "react"; | ||||
| import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; | ||||
| import { Behavior } from "@Model/Behavior"; | ||||
| import { Message } from "@Component/Message/Message"; | ||||
| import { AttrInput } from "@Component/AttrInput/AttrInput"; | ||||
| import { ColorInput } from "@Component/ColorInput/ColorInput"; | ||||
| import { TogglesInput } from "@Component/TogglesInput/TogglesInput"; | ||||
| import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup"; | ||||
| import "./BehaviorDetails.scss"; | ||||
| 
 | ||||
| interface IBehaviorDetailsProps {} | ||||
| 
 | ||||
| @useStatusWithEvent("focusBehaviorChange", "behaviorAttrChange") | ||||
| class BehaviorDetails extends Component<IBehaviorDetailsProps & IMixinStatusProps> { | ||||
| 
 | ||||
| 	private renderFrom(behavior: Behavior): ReactNode { | ||||
| 		return <> | ||||
|          | ||||
|             <Message i18nKey="Common.Attr.Title.Basic" isTitle first/> | ||||
| 
 | ||||
|             <AttrInput | ||||
|                 id={behavior.id} keyI18n="Common.Attr.Key.Display.Name" value={behavior.name} | ||||
|                 valueChange={(val) => { | ||||
|                     this.props.status?.changeBehaviorAttrib(behavior.id, "name", val); | ||||
|                 }} | ||||
|             /> | ||||
| 
 | ||||
|             <ColorInput | ||||
|                 keyI18n="Common.Attr.Key.Color" | ||||
|                 value={behavior.color} | ||||
|                 valueChange={(color) => { | ||||
|                     this.props.status?.changeBehaviorAttrib(behavior.id, "color", color); | ||||
|                 }} | ||||
|             /> | ||||
| 
 | ||||
|             <TogglesInput | ||||
| 				keyI18n="Common.Attr.Key.Delete" red | ||||
| 				onIconName="delete" offIconName="delete" | ||||
| 				valueChange={() => { | ||||
| 					if (this.props.status) { | ||||
|                         const status = this.props.status; | ||||
|                         status.popup.showPopup(ConfirmPopup, { | ||||
|                             infoI18n: "Popup.Delete.Behavior.Confirm", | ||||
|                             titleI18N: "Popup.Action.Objects.Confirm.Title", | ||||
|                             yesI18n: "Popup.Action.Objects.Confirm.Delete", | ||||
|                             red: "yes", | ||||
|                             yes: () => { | ||||
|                                 status.model.deleteBehavior(behavior); | ||||
|                                 status.setBehaviorObject(); | ||||
|                             } | ||||
|                         }) | ||||
|                     } | ||||
| 				}} | ||||
| 			/> | ||||
| 
 | ||||
|             <Message | ||||
|                 isTitle | ||||
|                 i18nKey="Panel.Info.Behavior.Details.Behavior.Props" | ||||
|                 options={{ | ||||
|                     behavior: behavior.getTerms(behavior.behaviorName) | ||||
|                 }} | ||||
|             /> | ||||
| 
 | ||||
|         </>; | ||||
| 	} | ||||
| 
 | ||||
| 	public render(): ReactNode { | ||||
| 		if (this.props.status) { | ||||
|             if (this.props.status.focusBehavior) { | ||||
|                 return this.renderFrom(this.props.status.focusBehavior); | ||||
|             } | ||||
|         } | ||||
| 		return <Message i18nKey="Panel.Info.Behavior.Details.Error.Not.Behavior"/>; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| export { BehaviorDetails }; | ||||
| @ -13,7 +13,7 @@ interface IBehaviorListProps { | ||||
| } | ||||
| 
 | ||||
| @useSetting | ||||
| @useStatusWithEvent("behaviorChange", "focusBehaviorChange") | ||||
| @useStatusWithEvent("behaviorChange", "focusBehaviorChange", "behaviorAttrChange") | ||||
| class BehaviorList extends Component<IBehaviorListProps & IMixinStatusProps & IMixinSettingProps> { | ||||
|      | ||||
|     private labelInnerClick: boolean = false; | ||||
| @ -45,9 +45,9 @@ class BehaviorList extends Component<IBehaviorListProps & IMixinStatusProps & IM | ||||
|                     if (this.props.status) { | ||||
|                         this.props.status.setBehaviorObject(behavior as Behavior); | ||||
|                     } | ||||
|                     // if (this.props.setting) {
 | ||||
|                     //     this.props.setting.layout.focus("LabelDetails");
 | ||||
|                     // }
 | ||||
|                     if (this.props.setting) { | ||||
|                         this.props.setting.layout.focus("BehaviorDetails"); | ||||
|                     } | ||||
|                     this.labelInnerClick = true; | ||||
|                 }} | ||||
|                 onAdd={() => { | ||||
| @ -63,6 +63,7 @@ class BehaviorList extends Component<IBehaviorListProps & IMixinStatusProps & IM | ||||
|                             red: "yes", | ||||
|                             yes: () => { | ||||
|                                 status.model.deleteBehavior(behavior); | ||||
|                                 status.setBehaviorObject(); | ||||
|                             } | ||||
|                         }) | ||||
|                     } | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import { Component, ReactNode } from "react"; | ||||
| import { AttrInput } from "@Component/AttrInput/AttrInput"; | ||||
| import { useStatusWithEvent, IMixinStatusProps, Status } from "@Context/Status"; | ||||
| import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; | ||||
| import { useSetting, IMixinSettingProps } from "@Context/Setting"; | ||||
| import { Message } from "@Component/Message/Message"; | ||||
| import { ObjectID } from "@Model/Renderer"; | ||||
| import { ColorInput } from "@Component/ColorInput/ColorInput"; | ||||
| @ -25,11 +26,13 @@ const allOption: IDisplayItem[] = [ | ||||
|     {nameKey: "Common.Attr.Key.Generation.Mod.Range", key: GenMod.Range} | ||||
| ]; | ||||
| 
 | ||||
| @useSetting | ||||
| @useStatusWithEvent( | ||||
|     "groupAttrChange", "groupLabelChange", "focusObjectChange", | ||||
|     "focusBehaviorChange", "behaviorChange", "groupBehaviorChange" | ||||
|     "focusBehaviorChange", "behaviorChange", "groupBehaviorChange", | ||||
|     "behaviorAttrChange" | ||||
| ) | ||||
| class GroupDetails extends Component<IGroupDetailsProps & IMixinStatusProps> { | ||||
| class GroupDetails extends Component<IGroupDetailsProps & IMixinStatusProps & IMixinSettingProps> { | ||||
| 
 | ||||
| 	private renderFrom(group: Group) { | ||||
| 		return <> | ||||
| @ -118,10 +121,15 @@ class GroupDetails extends Component<IGroupDetailsProps & IMixinStatusProps> { | ||||
|                 behavior={group.behaviors} | ||||
|                 focusBehavior={this.props.status?.focusBehavior} | ||||
|                 click={(behavior) => { | ||||
|                     if (behavior.isDeleted()) return; | ||||
|                     this.props.status?.setBehaviorObject(behavior); | ||||
|                 }} | ||||
|                 action={(behavior) => { | ||||
|                     if (behavior.isDeleted()) return; | ||||
|                     this.props.status?.setBehaviorObject(behavior); | ||||
|                     setTimeout(() => { | ||||
|                         this.props.setting?.layout.focus("BehaviorDetails"); | ||||
|                     }); | ||||
|                 }} | ||||
|                 delete={(behavior) => { | ||||
|                     this.props.status?.deleteGroupBehavior(group.id, behavior); | ||||
|  | ||||
| @ -20,16 +20,19 @@ div.object-list { | ||||
|             opacity: 0; | ||||
|         } | ||||
| 
 | ||||
|         i.checkbox-icon:hover { | ||||
|             color: $lt-green; | ||||
|         } | ||||
| 
 | ||||
|         i.type-icon { | ||||
|             display: inline-block; | ||||
|             opacity: 1; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     div.object-list-checkbox:hover { | ||||
| 
 | ||||
|         i.checkbox-icon { | ||||
|             color: $lt-green !important; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     div.details-list-item:hover { | ||||
| 
 | ||||
|         div.object-list-checkbox { | ||||
|  | ||||
| @ -51,9 +51,6 @@ class ObjectList extends Component<IMixinStatusProps & IMixinSettingProps> { | ||||
|                 } | ||||
|             }} | ||||
|             checkBox={(item) => { | ||||
|                 if (this.props.setting) { | ||||
|                     this.props.setting.layout.focus("ObjectList"); | ||||
|                 } | ||||
|                 if (this.props.status) { | ||||
|                     if ( | ||||
|                         this.props.status.focusObject.has(item.key.toString()) || | ||||
|  | ||||
| @ -9,6 +9,7 @@ import { LabelList } from "./LabelList/LabelList"; | ||||
| import { LabelDetails } from "./LabelDetails/LabelDetails"; | ||||
| import { GroupDetails } from "./GroupDetails/GroupDetails"; | ||||
| import { BehaviorList } from "./BehaviorList/BehaviorList"; | ||||
| import { BehaviorDetails } from "./BehaviorDetails/BehaviorDetails"; | ||||
| 
 | ||||
| interface IPanelInfo { | ||||
| 	nameKey: string; | ||||
| @ -29,6 +30,7 @@ type PanelId = "" | ||||
| | "LabelDetails" // 标签属性
 | ||||
| | "GroupDetails" // 群属性
 | ||||
| | "BehaviorList" // 行为列表
 | ||||
| | "BehaviorDetails" // 行为属性
 | ||||
| ; | ||||
| 
 | ||||
| const PanelInfoMap = new Map<PanelId, IPanelInfo>(); | ||||
| @ -60,6 +62,10 @@ PanelInfoMap.set("BehaviorList", { | ||||
|     nameKey: "Panel.Title.Behavior.List.View", introKay: "Panel.Info.Behavior.List.View", | ||||
|     class: BehaviorList, hidePadding: true | ||||
| }); | ||||
| PanelInfoMap.set("BehaviorDetails", { | ||||
|     nameKey: "Panel.Title.Behavior.Details.View", introKay: "Panel.Info.Behavior.Details.View", | ||||
|     class: BehaviorDetails | ||||
| }); | ||||
| 
 | ||||
| function getPanelById(panelId: PanelId): ReactNode { | ||||
| 	switch (panelId) { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user