Add behavior model & search box & setting add behavior popup #26

Merged
MrKBear merged 8 commits from dev-mrkbear into master 2022-03-26 19:00:52 +08:00
4 changed files with 384 additions and 36 deletions
Showing only changes of commit 156b4651f5 - Show all commits

View File

@ -83,13 +83,13 @@ class Status extends Emitter<IStatusEvent> {
*/
public focusLabel?: Label;
private drawtimer?: NodeJS.Timeout;
private drawTimer?: NodeJS.Timeout;
private delayDraw = () => {
this.drawtimer ? clearTimeout(this.drawtimer) : null;
this.drawtimer = setTimeout(() => {
this.drawTimer ? clearTimeout(this.drawTimer) : null;
this.drawTimer = setTimeout(() => {
this.model.draw();
this.drawtimer = undefined;
this.drawTimer = undefined;
});
}

View File

@ -3,40 +3,328 @@ import { Emitter, EventType } from "./Emitter";
import type { Individual } from "./Individual";
import type { Group } from "./Group";
import type { Model } from "./Model";
import type { Range } from "./Range";
import type { Label } from "./Label";
/**
*
*
*/
abstract class Behavior<
P extends IAnyObject = {},
E extends Record<EventType, any> = {}
> extends Emitter<E> {
type IBehaviorConstructor<B extends Behavior<any, any>> =
new (id: string, parameter: IBehaviorParameterValue<B["parameterOption"]>) => B;
/**
*
*/
type IMapBasicParamTypeKeyToType = {
"number": number;
"string": string;
"boolean": boolean;
}
type IMapObjectParamTypeKeyToType = {
"R"?: Range;
"G"?: Group;
"GR"?: Group | Range;
"LR"?: Label | Range;
"LG"?: Label | Group;
"LGR"?: Label | Group | Range;
}
type IMapVectorParamTypeKeyToType = {
"vec": number[];
}
/**
*
*/
type AllMapType = IMapBasicParamTypeKeyToType & IMapObjectParamTypeKeyToType & IMapVectorParamTypeKeyToType;
type IParamType = keyof AllMapType;
type IObjectType = keyof IMapObjectParamTypeKeyToType;
type IVectorType = keyof IMapVectorParamTypeKeyToType;
type IParamValue<K extends IParamType> = AllMapType[K];
/**
*
*/
const objectTypeListEnumSet = new Set<IParamType>(["R", "G", "GR", "LR", "LG", "LGR"]);
/**
*
*/
function isObjectType(key: IParamType): key is IVectorType {
return objectTypeListEnumSet.has(key);
}
/**
*
*/
function isVectorType(key: IParamType): key is IObjectType {
return key === "vec";
}
/**
*
*/
interface IBehaviorParameterOptionItem<T extends IParamType = IParamType> {
/**
*
*/
type: T;
/**
*
*/
defaultValue?: IParamValue<T>;
/**
*
*/
onChange?: (value: IParamValue<T>) => any;
/**
*
*/
name: string;
/**
*
*/
stringLength?: number;
/**
*
*/
numberStep?: number;
/**
*
*/
numberMax?: number;
numberMin?: number;
/**
*
*/
iconName?: string;
}
/**
*
*/
type IBehaviorParameterValueItem<P extends IBehaviorParameterOptionItem> = IParamValue<P["type"]>;
/**
*
*/
interface IBehaviorParameterOption {
[x: string]: IBehaviorParameterOptionItem;
}
/**
*
*/
type IBehaviorParameterValue<P extends IBehaviorParameterOption> = {
[x in keyof P]: IBehaviorParameterValueItem<P[x]>
}
/**
*
*/
class BehaviorInfo<E extends Record<EventType, any> = {}> extends Emitter<E> {
/**
*
*/
public iconName: string = ""
/**
* ID
*/
abstract id: string;
public behaviorId: string = "";
/**
*
*/
abstract name: string;
public behaviorName: string = "";
/**
*
*/
public describe?: string = "";
}
class BehaviorRecorder<
B extends Behavior<any, any>
> extends BehaviorInfo {
/**
*
*/
public nameIndex: number = 0;
/**
* ID
*/
public getNextId() {
return `B-${this.behaviorName}-${this.nameIndex ++}`;
}
/**
*
*/
public behavior: IBehaviorConstructor<B>;
/**
*
*/
public behaviorInstance: B;
/**
*
*/
public parameterOption: B["parameterOption"];
/**
*
*/
public getDefaultValue(): IBehaviorParameterValue<B["parameterOption"]> {
let defaultObj = {} as IBehaviorParameterValue<B["parameterOption"]>;
for (let key in this.parameterOption) {
let defaultVal = this.parameterOption[key].defaultValue;
defaultObj[key] = defaultVal as any;
if (defaultObj[key] === undefined) {
switch (this.parameterOption[key].type) {
case "string":
defaultObj[key] = "" as any;
break;
case "number":
defaultObj[key] = 0 as any;
break;
case "boolean":
defaultObj[key] = false as any;
break;
case "vec":
defaultObj[key] = [0, 0, 0] as any;
break;
}
}
}
return defaultObj;
}
/**
*
*/
public new(): B {
return new this.behavior(this.getNextId(), this.getDefaultValue());
}
public constructor(behavior: IBehaviorConstructor<B>) {
super();
this.behavior = behavior;
this.behaviorInstance = new this.behavior(this.getNextId(), {} as any);
this.parameterOption = this.behaviorInstance.parameterOption;
this.iconName = this.behaviorInstance.iconName;
this.behaviorId = this.behaviorInstance.behaviorId;
this.behaviorName = this.behaviorInstance.behaviorName;
this.describe = this.behaviorInstance.describe;
}
}
/**
*
*/
class Behavior<
P extends IBehaviorParameterOption = {},
E extends Record<EventType, any> = {}
> extends BehaviorInfo<E> {
/**
*
*/
public name: string = "";
/**
* ID
*/
public id: string = "";
/**
*
*
*/
public priority?: number = 0;
public priority: number = 0;
/**
*
*/
abstract parameter?: P;
public parameter: IBehaviorParameterValue<P>;
/**
*
*/
public parameterOption: P = {} as any;
public constructor(id: string, parameter: IBehaviorParameterValue<P>) {
super();
this.id = id;
this.parameter = parameter;
}
/**
*
*/
public equal(behavior: Behavior<any, any>): boolean {
return this === behavior || this.id === behavior.id;
};
/**
*
*/
private deleteFlag: boolean = false;
/**
*
*/
public markDelete() {
this.deleteFlag = true;
};
/**
*
*/
public isDeleted(): boolean {
return this.deleteFlag;
}
/**
*
*/
public load(model: Model): void {}
/**
*
*/
public unload(model: Model): void {}
/**
*
*/
public mount(group: Group, model: Model): void {}
/**
*
*/
public unmount(group: Group, model: Model): void {}
/**
*
@ -67,5 +355,5 @@ abstract class Behavior<
}
export { Behavior };
export { Behavior, BehaviorRecorder };
export default { Behavior };

View File

@ -60,22 +60,36 @@ class CtrlObject extends LabelObject {
return this === obj || this.id === obj.id;
}
/**
*
*/
public markDelete() {
this.deleteFlag = true;
};
/**
*
*/
private deleteFlag: boolean = false;
/**
*
*/
public isDeleted(): boolean {
if (this.deleteFlag) return true;
/**
*
*/
public testDelete() {
for (let i = 0; i < this.model.objectPool.length; i++) {
if (this.model.objectPool[i].equal(this)) return false;
if (this.model.objectPool[i].equal(this)) {
this.deleteFlag = false;
return;
}
}
this.deleteFlag = true;
return true;
}
/**
*
*/
public isDeleted(): boolean {
return this.deleteFlag;
}
}

View File

@ -1,4 +1,3 @@
import { Individual } from "./Individual";
import { Group } from "./Group";
import { Range } from "./Range";
@ -6,18 +5,14 @@ import { Emitter, EventType, EventMixin } from "./Emitter";
import { CtrlObject } from "./CtrlObject";
import { ObjectID, AbstractRenderer } from "./Renderer";
import { Label } from "./Label";
import { Behavior, BehaviorRecorder } from "./Behavior";
type ModelEvent = {
loop: number;
groupAdd: Group;
rangeAdd: Range;
labelAdd: Label;
labelDelete: Label;
labelChange: Label[];
objectAdd: CtrlObject;
objectDelete: CtrlObject[];
objectChange: CtrlObject[];
individualChange: Group;
behaviorChange: Behavior;
};
/**
@ -68,7 +63,6 @@ class Model extends Emitter<ModelEvent> {
console.log(`Model: Creat label with id ${this.idIndex}`);
let label = new Label(this, this.nextId("L"), name);
this.labelPool.push(label);
this.emit("labelAdd", label);
this.emit("labelChange", this.labelPool);
return label;
}
@ -97,7 +91,6 @@ class Model extends Emitter<ModelEvent> {
this.labelPool.splice(index, 1);
deletedLabel.testDelete();
console.log(`Model: Delete label ${deletedLabel.name ?? deletedLabel.id}`);
this.emit("labelDelete", deletedLabel);
this.emit("labelChange", this.labelPool);
}
}
@ -135,8 +128,6 @@ class Model extends Emitter<ModelEvent> {
console.log(`Model: Creat group with id ${this.idIndex}`);
let group = new Group(this, this.nextId("G"));
this.objectPool.push(group);
this.emit("groupAdd", group);
this.emit("objectAdd", group);
this.emit("objectChange", this.objectPool);
return group;
}
@ -148,8 +139,6 @@ class Model extends Emitter<ModelEvent> {
console.log(`Model: Creat range with id ${this.idIndex}`);
let range = new Range(this, this.nextId("R"));
this.objectPool.push(range);
this.emit("rangeAdd", range);
this.emit("objectAdd", range);
this.emit("objectChange", this.objectPool);
return range;
}
@ -176,6 +165,7 @@ class Model extends Emitter<ModelEvent> {
if (needDeleted) {
deletedObject.push(currentObject);
currentObject.markDelete();
return false;
} else {
return true;
@ -184,12 +174,68 @@ class Model extends Emitter<ModelEvent> {
if (deletedObject.length) {
console.log(`Model: Delete object ${deletedObject.map((object) => object.id).join(", ")}`);
this.emit("objectDelete", deletedObject);
this.emit("objectChange", this.objectPool);
}
return deletedObject;
}
/**
*
*/
public behaviorPool: Behavior<any, any>[] = [];
/**
*
*/
public addBehavior<B extends Behavior<any, any>>(recorder: BehaviorRecorder<B>): B {
let behavior = recorder.new();
behavior.load(this);
this.behaviorPool.push(behavior);
console.log(`Model: Add ${behavior.behaviorName} behavior ${behavior.id}`);
this.emit("behaviorChange", behavior);
return behavior;
};
/**
* ID
*/
public getBehaviorById(id: ObjectID): Behavior<any, any> | undefined {
for (let i = 0; i < this.behaviorPool.length; i++) {
if (this.behaviorPool[i].id.toString() === id.toString()) {
return this.behaviorPool[i];
}
}
}
/**
* Behavior
* @param name
*/
public deleteBehavior(name: Behavior<any, any> | ObjectID) {
let deletedBehavior: Behavior<any, any> | undefined;
let index = 0;
for (let i = 0; i < this.behaviorPool.length; i++) {
if (name instanceof Behavior) {
if (this.behaviorPool[i].equal(name)) {
deletedBehavior = this.behaviorPool[i];
index = i;
}
} else if (name === this.behaviorPool[i].id) {
deletedBehavior = this.behaviorPool[i];
index = i;
}
}
if (deletedBehavior) {
this.behaviorPool.splice(index, 1);
deletedBehavior.unload(this);
deletedBehavior.markDelete();
console.log(`Model: Delete behavior ${deletedBehavior.name ?? deletedBehavior.id}`);
this.emit("behaviorChange", deletedBehavior);
}
}
/**
*
*/