Compare commits

..

No commits in common. "a689d23b5f19457cb4c88e2b4b6f25d1e27c645d" and "a0547095e23a9396c5bbfb118e6f4708d42e7211" have entirely different histories.

13 changed files with 17 additions and 499 deletions

View File

@ -9,7 +9,6 @@ import { SettingPopup } from "@Component/SettingPopup/SettingPopup";
import { BehaviorPopup } from "@Component/BehaviorPopup/BehaviorPopup"; import { BehaviorPopup } from "@Component/BehaviorPopup/BehaviorPopup";
import { MouseMod } from "@GLRender/ClassicRenderer"; import { MouseMod } from "@GLRender/ClassicRenderer";
import { ArchiveSave } from "@Context/Archive"; import { ArchiveSave } from "@Context/Archive";
import { ActuatorModel } from "@Model/Actuator";
import "./CommandBar.scss"; import "./CommandBar.scss";
const COMMAND_BAR_WIDTH = 45; const COMMAND_BAR_WIDTH = 45;
@ -51,62 +50,6 @@ class CommandBar extends Component<IMixinSettingProps & IMixinStatusProps, IComm
isSaveRunning: false isSaveRunning: false
}; };
private renderPlayActionButton(): ReactNode {
let icon: string = "Play";
let handel: () => any = () => {};
// 播放模式
if (this.props.status?.focusClip) {
// 暂停播放
if (this.props.status?.actuator.mod === ActuatorModel.Play) {
icon = "Pause";
handel = () => {
this.props.status?.actuator.pausePlay();
console.log("ClipRecorder: Pause play...");
};
}
// 开始播放
else {
icon = "Play";
handel = () => {
this.props.status?.actuator.playing();
console.log("ClipRecorder: Play start...");
};
}
}
// 正在录制中
else if (
this.props.status?.actuator.mod === ActuatorModel.Record ||
this.props.status?.actuator.mod === ActuatorModel.Offline
) {
// 暂停录制
icon = "Stop";
handel = () => {
this.props.status?.actuator.endRecord();
console.log("ClipRecorder: Rec end...");
};
}
// 正常控制主时钟
else {
icon = this.props.status?.actuator.start() ? "Pause" : "Play";
handel = () => this.props.status?.actuator.start(
!this.props.status?.actuator.start()
);
}
return <CommandButton
iconName={icon}
i18NKey="Command.Bar.Play.Info"
click={handel}
/>;
}
public render(): ReactNode { public render(): ReactNode {
const mouseMod = this.props.status?.mouseMod ?? MouseMod.Drag; const mouseMod = this.props.status?.mouseMod ?? MouseMod.Drag;
@ -141,7 +84,13 @@ class CommandBar extends Component<IMixinSettingProps & IMixinStatusProps, IComm
}} }}
/> />
{this.renderPlayActionButton()} <CommandButton
iconName={this.props.status?.actuator.start() ? "Pause" : "Play"}
i18NKey="Command.Bar.Play.Info"
click={() => this.props.status ? this.props.status.actuator.start(
!this.props.status.actuator.start()
) : undefined}
/>
<CommandButton <CommandButton
iconName="HandsFree" iconName="HandsFree"

View File

@ -1,8 +0,0 @@
@import "../Theme/Theme.scss";
div.offline-render-popup {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 10px;
}

View File

@ -1,124 +0,0 @@
import { Component, ReactNode } from "react";
import { Popup } from "@Context/Popups";
import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status";
import { Localization } from "@Component/Localization/Localization";
import { AttrInput } from "@Input/AttrInput/AttrInput";
import { Message } from "@Input/Message/Message";
import { ConfirmContent } from "@Component/ConfirmPopup/ConfirmPopup";
import { ProcessPopup } from "@Component/ProcessPopup/ProcessPopup";
import { Emitter } from "@Model/Emitter";
import "./OfflineRender.scss";
interface IOfflineRenderProps {
close?: () => any;
}
interface IOfflineRenderState {
time: number;
fps: number;
name: string;
}
class OfflineRender extends Popup<IOfflineRenderProps> {
public minWidth: number = 250;
public minHeight: number = 150;
public width: number = 400;
public height: number = 300;
public maskForSelf: boolean = true;
public onRenderHeader(): ReactNode {
return <Localization i18nKey="Popup.Offline.Render.Title"/>
}
public render(): ReactNode {
return <OfflineRenderComponent {...this.props} close={() => {
this.close();
}}/>
}
}
@useStatusWithEvent()
class OfflineRenderComponent extends Component<IOfflineRenderProps & IMixinStatusProps, IOfflineRenderState> {
public constructor(props: IOfflineRenderProps & IMixinStatusProps) {
super(props);
this.state = {
name: this.props.status?.getNewClipName() ?? "",
time: 10,
fps: 60
}
}
public render(): ReactNode {
return <ConfirmContent
className="offline-render-popup"
actions={[{
i18nKey: "Popup.Offline.Render.Input.Start",
onClick: () => {
// 获取新实例
let newClip = this.props.status?.newClip();
if (newClip) {
newClip.name = this.state.name;
this.props.status?.actuator.offlineRender(newClip, this.state.time, this.state.fps);
// 开启进度条弹窗
this.props.status?.popup.showPopup(ProcessPopup, {});
}
// 关闭这个弹窗
this.props.close && this.props.close();
}
}]}
>
<Message i18nKey="Popup.Offline.Render.Message" isTitle first/>
<AttrInput
id={"Render-Name"}
value={this.state.name}
keyI18n="Popup.Offline.Render.Input.Name"
maxLength={15}
valueChange={(val) => {
this.setState({
name: val
});
}}
/>
<AttrInput
isNumber
id={"Render-Time"}
value={this.state.time}
keyI18n="Popup.Offline.Render.Input.Time"
max={3600}
min={1}
valueChange={(val) => {
this.setState({
time: parseFloat(val)
});
}}
/>
<AttrInput
isNumber
id={"Render-FPS"}
max={1000}
min={1}
value={this.state.fps}
keyI18n="Popup.Offline.Render.Input.Fps"
valueChange={(val) => {
this.setState({
fps: parseFloat(val)
});
}}
/>
</ConfirmContent>
}
}
export { OfflineRender };

View File

@ -1,42 +0,0 @@
@import "../Theme/Theme.scss";
div.process-popup {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 10px;
div.ms-ProgressIndicator {
transform: none;
div.ms-ProgressIndicator-progressTrack {
transform: none;
}
div.ms-ProgressIndicator-progressBar {
transform: none;
}
}
}
div.confirm-root.dark div.ms-ProgressIndicator {
div.ms-ProgressIndicator-progressTrack {
background-color: $lt-bg-color-lvl3-dark;
}
div.ms-ProgressIndicator-progressBar {
background-color: $lt-font-color-normal-dark;
}
}
div.confirm-root.light div.ms-ProgressIndicator {
div.ms-ProgressIndicator-progressTrack {
background-color: $lt-bg-color-lvl3-light;
}
div.ms-ProgressIndicator-progressBar {
background-color: $lt-font-color-normal-light;
}
}

View File

@ -1,89 +0,0 @@
import { Component, ReactNode } from "react";
import { Popup } from "@Context/Popups";
import { Localization } from "@Component/Localization/Localization";
import { ConfirmContent } from "@Component/ConfirmPopup/ConfirmPopup";
import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status";
import { ProgressIndicator } from "@fluentui/react";
import { ActuatorModel } from "@Model/Actuator";
import "./ProcessPopup.scss";
interface IProcessPopupProps {
close?: () => void;
}
class ProcessPopup extends Popup<IProcessPopupProps> {
public minWidth: number = 400;
public minHeight: number = 150;
public width: number = 400;
public height: number = 150;
public maskForSelf: boolean = true;
public onClose(): void {}
public onRenderHeader(): ReactNode {
return <Localization i18nKey="Popup.Offline.Render.Process.Title"/>
}
public render(): ReactNode {
return <ProcessPopupComponent {...this.props} close={() => this.close()}/>
}
}
@useStatusWithEvent("offlineLoop", "actuatorStartChange", "recordLoop")
class ProcessPopupComponent extends Component<IProcessPopupProps & IMixinStatusProps> {
public render(): ReactNode {
let current = this.props.status?.actuator.offlineCurrentFrame ?? 0;
let all = this.props.status?.actuator.offlineAllFrame ?? 0;
const isRendering = this.props.status?.actuator.mod === ActuatorModel.Offline;
let i18nKey = "";
let color: undefined | "red";
let onClick = () => {};
if (isRendering) {
i18nKey = "Popup.Offline.Render.Input.End";
color = "red";
onClick = () => {
this.props.status?.actuator.endOfflineRender();
this.forceUpdate();
}
}
else {
i18nKey = "Popup.Offline.Render.Input.Finished";
onClick = () => {
this.props.close && this.props.close();
}
}
return <ConfirmContent
className="process-popup"
actions={[{
i18nKey: i18nKey,
color: color,
onClick: onClick
}]}
>
<ProgressIndicator
percentComplete={current / all}
barHeight={3}
/>
<Localization
i18nKey="Popup.Offline.Render.Process"
options={{
current: current.toString(),
all: all.toString()
}}
/>
</ConfirmContent>
}
}
export { ProcessPopup };

View File

@ -30,7 +30,7 @@ div.recorder-root {
span.ms-Slider-inactive { span.ms-Slider-inactive {
height: 3px; height: 3px;
transition: none; animation: none;
} }
} }

View File

@ -14,7 +14,6 @@ interface IRecorderProps {
allTime?: number; allTime?: number;
currentTime?: number; currentTime?: number;
action?: () => void; action?: () => void;
valueChange?: (value: number) => any;
} }
class Recorder extends Component<IRecorderProps> { class Recorder extends Component<IRecorderProps> {
@ -86,38 +85,19 @@ class Recorder extends Component<IRecorderProps> {
max={this.props.allFrame} max={this.props.allFrame}
className={"recorder-slider" + (isSliderDisable ? " disable" : "")} className={"recorder-slider" + (isSliderDisable ? " disable" : "")}
showValue={false} showValue={false}
onChange={(value) => {
if (this.props.valueChange && !isSliderDisable) {
this.props.valueChange(value);
}
}}
/> />
<div className="recorder-content"> <div className="recorder-content">
<div className="time-view"> <div className="time-view">
{this.getRecordInfo()} {this.getRecordInfo()}
</div> </div>
<div className="ctrl-button"> <div className="ctrl-button">
<div <div className={"ctrl-action" + (isJumpDisable ? " disable" : "")}>
className={"ctrl-action" + (isJumpDisable ? " disable" : "")}
onClick={() => {
if (this.props.valueChange && !isJumpDisable && this.props.currentFrame !== undefined) {
this.props.valueChange(this.props.currentFrame - 1);
}
}}
>
<Icon iconName="Back"/> <Icon iconName="Back"/>
</div> </div>
<div className="ctrl-action ctrl-action-main" onClick={this.props.action}> <div className="ctrl-action ctrl-action-main" onClick={this.props.action}>
<Icon iconName={this.getActionIcon()}/> <Icon iconName={this.getActionIcon()}/>
</div> </div>
<div <div className={"ctrl-action" + (isJumpDisable ? " disable" : "")}>
className={"ctrl-action" + (isJumpDisable ? " disable" : "")}
onClick={() => {
if (this.props.valueChange && !isJumpDisable && this.props.currentFrame !== undefined) {
this.props.valueChange(this.props.currentFrame + 1);
}
}}
>
<Icon iconName="Forward"/> <Icon iconName="Forward"/>
</div> </div>
</div> </div>

View File

@ -37,7 +37,6 @@ interface IStatusEvent {
renderLoop: number; renderLoop: number;
physicsLoop: number; physicsLoop: number;
recordLoop: number; recordLoop: number;
offlineLoop: number;
mouseModChange: void; mouseModChange: void;
focusObjectChange: void; focusObjectChange: void;
focusLabelChange: void; focusLabelChange: void;
@ -130,7 +129,6 @@ class Status extends Emitter<IStatusEvent> {
// 循环事件 // 循环事件
this.actuator.on("loop", (t) => { this.emit("physicsLoop", t) }); this.actuator.on("loop", (t) => { this.emit("physicsLoop", t) });
this.actuator.on("record", (t) => { this.emit("recordLoop", t) }); this.actuator.on("record", (t) => { this.emit("recordLoop", t) });
this.actuator.on("offline", (t) => { this.emit("offlineLoop", t) });
// 对象变化事件 // 对象变化事件
this.model.on("objectChange", () => this.emit("objectChange")); this.model.on("objectChange", () => this.emit("objectChange"));
@ -426,7 +424,7 @@ class Status extends Emitter<IStatusEvent> {
return label; return label;
} }
public getNewClipName() { public newClip() {
let searchKey = I18N(this.setting.language, "Object.List.New.Clip", { id: "" }); let searchKey = I18N(this.setting.language, "Object.List.New.Clip", { id: "" });
let nextIndex = 1; let nextIndex = 1;
this.model.clipPool.forEach((obj) => { this.model.clipPool.forEach((obj) => {
@ -434,13 +432,11 @@ class Status extends Emitter<IStatusEvent> {
obj.name, searchKey obj.name, searchKey
)); ));
}); });
return I18N(this.setting.language, "Object.List.New.Clip", { const clip = this.model.addClip(
I18N(this.setting.language, "Object.List.New.Clip", {
id: nextIndex.toString() id: nextIndex.toString()
}); })
} );
public newClip() {
const clip = this.model.addClip(this.getNewClipName());
return clip; return clip;
} }

View File

@ -72,16 +72,6 @@ const EN_US = {
"Popup.Delete.Clip.Confirm": "Are you sure you want to delete this clip? The clip cannot be restored after deletion.", "Popup.Delete.Clip.Confirm": "Are you sure you want to delete this clip? The clip cannot be restored after deletion.",
"Popup.Restore.Behavior.Confirm": "Are you sure you want to reset all parameters of this behavior? This operation cannot be recalled.", "Popup.Restore.Behavior.Confirm": "Are you sure you want to reset all parameters of this behavior? This operation cannot be recalled.",
"Popup.Setting.Title": "Preferences setting", "Popup.Setting.Title": "Preferences setting",
"Popup.Offline.Render.Title": "Offline rendering",
"Popup.Offline.Render.Process.Title": "Rendering progress",
"Popup.Offline.Render.Message": "Rendering Parameters",
"Popup.Offline.Render.Input.Name": "Clip name",
"Popup.Offline.Render.Input.Time": "Duration (s)",
"Popup.Offline.Render.Input.Fps": "FPS (f/s)",
"Popup.Offline.Render.Input.Start": "Start rendering",
"Popup.Offline.Render.Input.End": "Terminate rendering",
"Popup.Offline.Render.Input.Finished": "Finished",
"Popup.Offline.Render.Process": "Number of frames completed: {current} / {all}",
"Popup.Load.Save.Title": "Load save", "Popup.Load.Save.Title": "Load save",
"Popup.Load.Save.confirm": "Got it", "Popup.Load.Save.confirm": "Got it",
"Popup.Load.Save.Overwrite": "Overwrite and continue", "Popup.Load.Save.Overwrite": "Overwrite and continue",

View File

@ -72,16 +72,6 @@ const ZH_CN = {
"Popup.Delete.Clip.Confirm": "你确定删除这个剪辑片段,剪辑片段删除后将无法恢复。", "Popup.Delete.Clip.Confirm": "你确定删除这个剪辑片段,剪辑片段删除后将无法恢复。",
"Popup.Restore.Behavior.Confirm": "你确定要重置此行为的全部参数吗?此操作无法撤回。", "Popup.Restore.Behavior.Confirm": "你确定要重置此行为的全部参数吗?此操作无法撤回。",
"Popup.Setting.Title": "首选项设置", "Popup.Setting.Title": "首选项设置",
"Popup.Offline.Render.Title": "离线渲染",
"Popup.Offline.Render.Process.Title": "渲染进度",
"Popup.Offline.Render.Message": "渲染参数",
"Popup.Offline.Render.Input.Name": "剪辑名称",
"Popup.Offline.Render.Input.Time": "时长 (s)",
"Popup.Offline.Render.Input.Fps": "帧率 (f/s)",
"Popup.Offline.Render.Input.Start": "开始渲染",
"Popup.Offline.Render.Input.End": "终止渲染",
"Popup.Offline.Render.Input.Finished": "完成",
"Popup.Offline.Render.Process": "完成帧数: {current} / {all}",
"Popup.Load.Save.Title": "加载存档", "Popup.Load.Save.Title": "加载存档",
"Popup.Load.Save.confirm": "我知道了", "Popup.Load.Save.confirm": "我知道了",
"Popup.Load.Save.Overwrite": "覆盖并继续", "Popup.Load.Save.Overwrite": "覆盖并继续",

View File

@ -13,7 +13,6 @@ interface IActuatorEvent {
startChange: boolean; startChange: boolean;
record: number; record: number;
loop: number; loop: number;
offline: number;
} }
/** /**
@ -218,124 +217,6 @@ class Actuator extends Emitter<IActuatorEvent> {
private playTickerTimer?: number; private playTickerTimer?: number;
/**
*
*/
public setPlayProcess(id: number) {
if (this.playClip && id >= 0 && id < this.playClip.frames.length) {
// 跳转值这帧
this.playFrameId = id;
this.playFrame = this.playClip.frames[this.playFrameId];
this.emit("record", this.playFrame.duration);
if (this.mod !== ActuatorModel.Play) {
this.playClip.play(this.playFrame);
}
}
}
/**
* 线
*/
public offlineAllFrame: number = 0;
public offlineCurrentFrame: number = 0;
private offlineRenderTickTimer?: number;
/**
* 线
*/
public endOfflineRender() {
// 清除 timer
clearTimeout(this.offlineRenderTickTimer);
this.recordClip && (this.recordClip.isRecording = false);
this.recordClip = undefined;
// 设置状态
this.mod = ActuatorModel.View;
// 激发结束事件
this.start(false);
this.emit("record", 0);
}
/**
* 线 tick
*/
private offlineRenderTick(dt: number) {
if (this.mod !== ActuatorModel.Offline) {
return;
}
if (this.offlineCurrentFrame >= this.offlineAllFrame) {
return this.endOfflineRender();
}
// 更新模型
this.model.update(dt);
// 录制
this.recordClip?.record(dt);
// 限制更新频率
if (this.offlineCurrentFrame % 10 === 0) {
this.emit("offline", dt);
}
this.offlineCurrentFrame++
if (this.offlineCurrentFrame <= this.offlineAllFrame) {
// 下一个 tick
this.offlineRenderTickTimer = setTimeout(() => this.offlineRenderTick(dt)) as any;
} else {
this.endOfflineRender();
}
}
/**
* 线
*/
public offlineRender(clip: Clip, time: number, fps: number) {
// 记录录制片段
this.recordClip = clip;
clip.isRecording = true;
// 如果仿真正在进行,停止仿真
if (this.start()) this.start(false);
// 如果正在录制,阻止
if (this.mod === ActuatorModel.Record || this.mod === ActuatorModel.Offline) {
return;
}
// 如果正在播放,暂停播放
if (this.mod === ActuatorModel.Play) {
this.pausePlay();
}
// 设置状态
this.mod = ActuatorModel.Offline;
// 计算帧数
this.offlineCurrentFrame = 0;
this.offlineAllFrame = Math.round(time * fps) - 1;
let dt = time / this.offlineAllFrame;
// 第一帧渲染
clip.record(0);
// 开启时钟
this.offlineRenderTick(dt);
this.emit("record", dt);
}
/** /**
* *
*/ */

View File

@ -6,7 +6,6 @@ import { Message } from "@Input/Message/Message";
import { Clip } from "@Model/Clip"; import { Clip } from "@Model/Clip";
import { ActuatorModel } from "@Model/Actuator"; import { ActuatorModel } from "@Model/Actuator";
import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup"; import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup";
import { OfflineRender } from "@Component/OfflineRender/OfflineRender"
import "./ClipPlayer.scss"; import "./ClipPlayer.scss";
@useStatusWithEvent("clipChange", "focusClipChange", "actuatorStartChange") @useStatusWithEvent("clipChange", "focusClipChange", "actuatorStartChange")
@ -51,7 +50,6 @@ class ClipPlayer extends Component<IMixinStatusProps> {
}} }}
add={() => { add={() => {
this.isInnerClick = true; this.isInnerClick = true;
this.props.status?.popup.showPopup(OfflineRender, {});
}} }}
click={(clip) => { click={(clip) => {
this.isInnerClick = true; this.isInnerClick = true;

View File

@ -90,12 +90,9 @@ class ClipRecorder extends Component<IMixinStatusProps> {
// 启动播放时钟 // 启动播放时钟
this.props.status?.actuator.pausePlay(); this.props.status?.actuator.pausePlay();
console.log("ClipRecorder: Pause play..."); console.log("ClipRecorder: Pause start...");
} }
}} }}
valueChange={(value) => {
this.props.status?.actuator.setPlayProcess(value);
}}
/> />
} }
} }