Merge pull request 'Add behavior details panel & auto switch behavior details panels & mod behavior model add cache object' (#34) from dev-mrkbear into master

Reviewed-on: http://git.mrkbear.com/MrKBear/living-together/pulls/34
This commit is contained in:
MrKBear 2022-04-04 22:24:45 +08:00
commit 7531a55cf3
15 changed files with 173 additions and 36 deletions

View File

@ -112,7 +112,7 @@ class BehaviorList extends Component<IBehaviorListProps & IMixinSettingProps & I
if (behavior instanceof Behavior) { if (behavior instanceof Behavior) {
id = behavior.id; id = behavior.id;
name = behavior.name; name = behavior.name;
color = behavior.color; color = `rgb(${behavior.color.join(",")})`;
needLocal = false; needLocal = false;
info = behavior.behaviorName; info = behavior.behaviorName;
} }

View File

@ -62,7 +62,7 @@ class BehaviorPicker extends Component<IBehaviorPickerProps & IMixinSettingProps
return <> return <>
<div className="behavior-picker-line-color-view"> <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>
<div <div
className="behavior-picker-line-icon-view" className="behavior-picker-line-icon-view"

View File

@ -123,8 +123,7 @@ class BehaviorPopupComponent extends Component<
) + " " + (recorder.nameIndex - 1).toString(); ) + " " + (recorder.nameIndex - 1).toString();
// 赋予一个随机颜色 // 赋予一个随机颜色
let color = randomColor(true); newBehavior.color = randomColor(true);
newBehavior.color = `rgb(${color[0]},${color[1]},${color[2]})`;
} }
}); });
this.props.onDismiss ? this.props.onDismiss() : undefined; this.props.onDismiss ? this.props.onDismiss() : undefined;

View File

@ -11,7 +11,7 @@ import { Setting } from "./Setting";
import { I18N } from "@Component/Localization/Localization"; import { I18N } from "@Component/Localization/Localization";
import { superConnectWithEvent, superConnect } from "./Context"; import { superConnectWithEvent, superConnect } from "./Context";
import { PopupController } from "./Popups"; import { PopupController } from "./Popups";
import { Behavior } from "@Model/Behavior"; import { Behavior, IBehaviorParameter, IParamValue } from "@Model/Behavior";
import { Actuator } from "@Model/Actuator"; import { Actuator } from "@Model/Actuator";
function randomColor(unNormal: boolean = false) { function randomColor(unNormal: boolean = false) {
@ -43,6 +43,7 @@ interface IStatusEvent {
rangeAttrChange: void; rangeAttrChange: void;
labelAttrChange: void; labelAttrChange: void;
groupAttrChange: void; groupAttrChange: void;
behaviorAttrChange: void;
individualChange: void; individualChange: void;
behaviorChange: void; behaviorChange: void;
popupChange: 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) { public addGroupBehavior(id: ObjectID, val: Behavior) {
const group = this.model.getObjectById(id); const group = this.model.getObjectById(id);
if (group && group instanceof Group) { if (group && group instanceof Group) {

View File

@ -48,6 +48,8 @@ const EN_US = {
"Panel.Info.Group.Details.View": "Edit view group attributes", "Panel.Info.Group.Details.View": "Edit view group attributes",
"Panel.Title.Behavior.List.View": "Behavior list", "Panel.Title.Behavior.List.View": "Behavior list",
"Panel.Info.Behavior.List.View": "Edit 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.Unnamed": "Popup message",
"Popup.Title.Confirm": "Confirm message", "Popup.Title.Confirm": "Confirm message",
"Popup.Action.Yes": "Confirm", "Popup.Action.Yes": "Confirm",
@ -109,5 +111,7 @@ const EN_US = {
"Panel.Info.Group.Details.Attr.Error.Unspecified": "Unspecified group object", "Panel.Info.Group.Details.Attr.Error.Unspecified": "Unspecified group object",
"Panel.Info.Label.Details.Error.Unspecified": "Label object not specified", "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.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; export default EN_US;

View File

@ -48,6 +48,8 @@ const ZH_CN = {
"Panel.Info.Group.Details.View": "编辑查看群属性", "Panel.Info.Group.Details.View": "编辑查看群属性",
"Panel.Title.Behavior.List.View": "行为列表", "Panel.Title.Behavior.List.View": "行为列表",
"Panel.Info.Behavior.List.View": "编辑查看行为列表", "Panel.Info.Behavior.List.View": "编辑查看行为列表",
"Panel.Title.Behavior.Details.View": "行为",
"Panel.Info.Behavior.Details.View": "编辑查看行为属性",
"Popup.Title.Unnamed": "弹窗消息", "Popup.Title.Unnamed": "弹窗消息",
"Popup.Title.Confirm": "确认消息", "Popup.Title.Confirm": "确认消息",
"Popup.Action.Yes": "确定", "Popup.Action.Yes": "确定",
@ -109,5 +111,7 @@ const ZH_CN = {
"Panel.Info.Group.Details.Attr.Error.Unspecified": "未指定群对象", "Panel.Info.Group.Details.Attr.Error.Unspecified": "未指定群对象",
"Panel.Info.Label.Details.Error.Unspecified": "未指定标签对象", "Panel.Info.Label.Details.Error.Unspecified": "未指定标签对象",
"Panel.Info.Label.List.Error.Nodata": "模型中没有标签,点击按钮以创建", "Panel.Info.Label.List.Error.Nodata": "模型中没有标签,点击按钮以创建",
"Panel.Info.Behavior.Details.Error.Not.Behavior": "请先指定一个行为以查看详情",
"Panel.Info.Behavior.Details.Behavior.Props": "{behavior}参数",
} }
export default ZH_CN; export default ZH_CN;

View File

@ -6,6 +6,11 @@ import type { Model } from "./Model";
import type { Range } from "./Range"; import type { Range } from "./Range";
import type { Label } from "./Label"; import type { Label } from "./Label";
type IObjectParamCacheType<P, Q = P> = {
picker: P;
objects: Q;
}
/** /**
* *
*/ */
@ -16,12 +21,10 @@ type IMapBasicParamTypeKeyToType = {
} }
type IMapObjectParamTypeKeyToType = { type IMapObjectParamTypeKeyToType = {
"R"?: Range; "R": IObjectParamCacheType<Range | undefined>;
"G"?: Group; "G": IObjectParamCacheType<Group | undefined>;
"GR"?: Group | Range; "LR": IObjectParamCacheType<Label | Range | undefined, Range[]>;
"LR"?: Label | Range; "LG": IObjectParamCacheType<Label | Group | undefined, Range[]>;
"LG"?: Label | Group;
"LGR"?: Label | Group | Range;
} }
type IMapVectorParamTypeKeyToType = { 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": case "vec":
defaultObj[key] = [0, 0, 0] as any; defaultObj[key] = [0, 0, 0] as any;
break; 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; type IRenderBehavior = BehaviorInfo | Behavior;
export { export {
Behavior, BehaviorRecorder, IBehaviorParameterOption, IBehaviorParameterOptionItem, Behavior, BehaviorRecorder, IBehaviorParameterOption, IBehaviorParameterOptionItem, IParamValue,
IAnyBehavior, IAnyBehaviorRecorder, BehaviorInfo, IRenderBehavior IAnyBehavior, IAnyBehaviorRecorder, BehaviorInfo, IRenderBehavior, IBehaviorParameter
}; };
export default { Behavior }; export default { Behavior };

View File

@ -56,11 +56,11 @@ class SimulatorWeb extends Component {
this.status.newLabel().name = "New Label"; this.status.newLabel().name = "New Label";
this.status.newLabel().name = "Test Label 01"; this.status.newLabel().name = "Test Label 01";
let dynamic = this.status.model.addBehavior(AllBehaviors[0]); 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]); 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]); 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; boundary.parameter.range = this.status.model.allRangeLabel;
group.addBehavior(dynamic); group.addBehavior(dynamic);
group.addBehavior(brownian); group.addBehavior(brownian);
@ -75,9 +75,9 @@ class SimulatorWeb extends Component {
items: [ items: [
{ {
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, scale: 80,
layout: LayoutDirection.X layout: LayoutDirection.X
} }
@ -87,9 +87,9 @@ class SimulatorWeb extends Component {
}, },
{ {
items: [{ items: [{
panels: ["ObjectList", "Test tab"] panels: ["ObjectList"]
}, { }, {
panels: ["GroupDetails", "RangeDetails", "LabelDetails"] panels: ["GroupDetails", "RangeDetails", "LabelDetails", "BehaviorDetails"]
}], }],
scale: 30, scale: 30,
layout: LayoutDirection.Y layout: LayoutDirection.Y

View 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 };

View File

@ -13,7 +13,7 @@ interface IBehaviorListProps {
} }
@useSetting @useSetting
@useStatusWithEvent("behaviorChange", "focusBehaviorChange") @useStatusWithEvent("behaviorChange", "focusBehaviorChange", "behaviorAttrChange")
class BehaviorList extends Component<IBehaviorListProps & IMixinStatusProps & IMixinSettingProps> { class BehaviorList extends Component<IBehaviorListProps & IMixinStatusProps & IMixinSettingProps> {
private labelInnerClick: boolean = false; private labelInnerClick: boolean = false;
@ -45,9 +45,9 @@ class BehaviorList extends Component<IBehaviorListProps & IMixinStatusProps & IM
if (this.props.status) { if (this.props.status) {
this.props.status.setBehaviorObject(behavior as Behavior); this.props.status.setBehaviorObject(behavior as Behavior);
} }
// if (this.props.setting) { if (this.props.setting) {
// this.props.setting.layout.focus("LabelDetails"); this.props.setting.layout.focus("BehaviorDetails");
// } }
this.labelInnerClick = true; this.labelInnerClick = true;
}} }}
onAdd={() => { onAdd={() => {
@ -63,6 +63,7 @@ class BehaviorList extends Component<IBehaviorListProps & IMixinStatusProps & IM
red: "yes", red: "yes",
yes: () => { yes: () => {
status.model.deleteBehavior(behavior); status.model.deleteBehavior(behavior);
status.setBehaviorObject();
} }
}) })
} }

View File

@ -1,6 +1,7 @@
import { Component, ReactNode } from "react"; import { Component, ReactNode } from "react";
import { AttrInput } from "@Component/AttrInput/AttrInput"; 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 { Message } from "@Component/Message/Message";
import { ObjectID } from "@Model/Renderer"; import { ObjectID } from "@Model/Renderer";
import { ColorInput } from "@Component/ColorInput/ColorInput"; import { ColorInput } from "@Component/ColorInput/ColorInput";
@ -25,11 +26,13 @@ const allOption: IDisplayItem[] = [
{nameKey: "Common.Attr.Key.Generation.Mod.Range", key: GenMod.Range} {nameKey: "Common.Attr.Key.Generation.Mod.Range", key: GenMod.Range}
]; ];
@useSetting
@useStatusWithEvent( @useStatusWithEvent(
"groupAttrChange", "groupLabelChange", "focusObjectChange", "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) { private renderFrom(group: Group) {
return <> return <>
@ -118,10 +121,15 @@ class GroupDetails extends Component<IGroupDetailsProps & IMixinStatusProps> {
behavior={group.behaviors} behavior={group.behaviors}
focusBehavior={this.props.status?.focusBehavior} focusBehavior={this.props.status?.focusBehavior}
click={(behavior) => { click={(behavior) => {
if (behavior.isDeleted()) return;
this.props.status?.setBehaviorObject(behavior); this.props.status?.setBehaviorObject(behavior);
}} }}
action={(behavior) => { action={(behavior) => {
if (behavior.isDeleted()) return;
this.props.status?.setBehaviorObject(behavior); this.props.status?.setBehaviorObject(behavior);
setTimeout(() => {
this.props.setting?.layout.focus("BehaviorDetails");
});
}} }}
delete={(behavior) => { delete={(behavior) => {
this.props.status?.deleteGroupBehavior(group.id, behavior); this.props.status?.deleteGroupBehavior(group.id, behavior);

View File

@ -20,16 +20,19 @@ div.object-list {
opacity: 0; opacity: 0;
} }
i.checkbox-icon:hover {
color: $lt-green;
}
i.type-icon { i.type-icon {
display: inline-block; display: inline-block;
opacity: 1; opacity: 1;
} }
} }
div.object-list-checkbox:hover {
i.checkbox-icon {
color: $lt-green !important;
}
}
div.details-list-item:hover { div.details-list-item:hover {
div.object-list-checkbox { div.object-list-checkbox {

View File

@ -51,9 +51,6 @@ class ObjectList extends Component<IMixinStatusProps & IMixinSettingProps> {
} }
}} }}
checkBox={(item) => { checkBox={(item) => {
if (this.props.setting) {
this.props.setting.layout.focus("ObjectList");
}
if (this.props.status) { if (this.props.status) {
if ( if (
this.props.status.focusObject.has(item.key.toString()) || this.props.status.focusObject.has(item.key.toString()) ||

View File

@ -9,6 +9,7 @@ import { LabelList } from "./LabelList/LabelList";
import { LabelDetails } from "./LabelDetails/LabelDetails"; import { LabelDetails } from "./LabelDetails/LabelDetails";
import { GroupDetails } from "./GroupDetails/GroupDetails"; import { GroupDetails } from "./GroupDetails/GroupDetails";
import { BehaviorList } from "./BehaviorList/BehaviorList"; import { BehaviorList } from "./BehaviorList/BehaviorList";
import { BehaviorDetails } from "./BehaviorDetails/BehaviorDetails";
interface IPanelInfo { interface IPanelInfo {
nameKey: string; nameKey: string;
@ -29,6 +30,7 @@ type PanelId = ""
| "LabelDetails" // 标签属性 | "LabelDetails" // 标签属性
| "GroupDetails" // 群属性 | "GroupDetails" // 群属性
| "BehaviorList" // 行为列表 | "BehaviorList" // 行为列表
| "BehaviorDetails" // 行为属性
; ;
const PanelInfoMap = new Map<PanelId, IPanelInfo>(); 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", nameKey: "Panel.Title.Behavior.List.View", introKay: "Panel.Info.Behavior.List.View",
class: BehaviorList, hidePadding: true 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 { function getPanelById(panelId: PanelId): ReactNode {
switch (panelId) { switch (panelId) {