Add desktop layout

This commit is contained in:
MrKBear 2022-04-14 17:54:37 +08:00
parent fca467a427
commit eccd7fdf7c
8 changed files with 240 additions and 68 deletions

View File

@ -1,11 +1,16 @@
div.header-bar { @import "../Theme/Theme.scss";
padding: 0 3px;
div.header-bar {
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
user-select: none; user-select: none;
div.title {
padding-left: 3px;
}
div.title > i, div.fps-view > i { div.title > i, div.fps-view > i {
font-size: 25px; font-size: 25px;
vertical-align: middle; vertical-align: middle;
@ -24,4 +29,43 @@ div.header-bar {
white-space: nowrap; white-space: nowrap;
} }
} }
div.header-windows-action {
height: 100%;
width: 135px;
min-width: 135px;
display: flex;
div.action-button {
width: 45px;
height: 45px;
display: flex;
justify-content: center;
align-items: center;
font-size: .5em;
}
div.action-button:hover {
cursor: pointer;
}
div.action-button.close-button:hover {
color: #FFFFFF !important;
background-color: $lt-red !important;
}
}
div.header-windows-action.light {
div.action-button:hover {
background-color: rgba($color: #000000, $alpha: .1);
}
}
div.header-windows-action.dark {
div.action-button:hover {
background-color: rgba($color: #FFFFFF, $alpha: .1);
}
}
} }

View File

@ -1,7 +1,7 @@
import { Component, ReactNode } from "react"; import { Component, ReactNode } from "react";
import { Icon } from '@fluentui/react/lib/Icon'; import { Icon } from '@fluentui/react/lib/Icon';
import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; import { useStatusWithEvent, useStatus, IMixinStatusProps } from "@Context/Status";
import { useSetting, IMixinSettingProps } from "@Context/Setting"; import { useSettingWithEvent, IMixinSettingProps, Platform } from "@Context/Setting";
import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme"; import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme";
import { LocalizationTooltipHost } from "@Component/Localization/LocalizationTooltipHost"; import { LocalizationTooltipHost } from "@Component/Localization/LocalizationTooltipHost";
import { I18N } from "@Component/Localization/Localization"; import { I18N } from "@Component/Localization/Localization";
@ -11,38 +11,48 @@ interface IHeaderBarProps {
height: number; height: number;
} }
interface HeaderBarState { interface IHeaderFpsViewState {
renderFps: number; renderFps: number;
physicsFps: number; physicsFps: number;
} }
/** @useStatus
* class HeaderFpsView extends Component<IMixinStatusProps & IMixinSettingProps, IHeaderFpsViewState> {
*/
@useSetting
@useStatusWithEvent("fileChange")
class HeaderBar extends Component<
IHeaderBarProps & IMixinStatusProps & IMixinSettingProps,
HeaderBarState
> {
public state = { public state = {
renderFps: 0, renderFps: 0,
physicsFps: 0, physicsFps: 0,
} }
private changeListener = () => { private updateTime: number = 0;
this.forceUpdate();
private renderFpsCalc: (t: number) => void = () => {};
private physicsFpsCalc: (t: number) => void = () => {};
public componentDidMount() {
const { status } = this.props;
this.renderFpsCalc = this.createFpsCalc("renderFps");
this.physicsFpsCalc = this.createFpsCalc("physicsFps");
if (status) {
status.on("physicsLoop", this.physicsFpsCalc);
status.on("renderLoop", this.renderFpsCalc);
}
} }
private updateTime: number = 0; public componentWillUnmount() {
const { status } = this.props;
if (status) {
status.off("physicsLoop", this.physicsFpsCalc);
status.off("renderLoop", this.renderFpsCalc);
}
}
private createFpsCalc(type: "renderFps" | "physicsFps") { private createFpsCalc(type: "renderFps" | "physicsFps") {
return (t: number) => { return (t: number) => {
if (t === 0) { if (t === 0) {
return; return;
} }
let newState: HeaderBarState = {} as any; let newState: IHeaderFpsViewState = {} as any;
newState[type] = 1 / t; newState[type] = 1 / t;
if (this.updateTime > 20) { if (this.updateTime > 20) {
this.updateTime = 0; this.updateTime = 0;
@ -52,49 +62,59 @@ class HeaderBar extends Component<
} }
} }
private renderFpsCalc: (t: number) => void = () => {}; public render() {
private physicsFpsCalc: (t: number) => void = () => {};
public componentDidMount() {
const { setting, status } = this.props;
this.renderFpsCalc = this.createFpsCalc("renderFps");
this.physicsFpsCalc = this.createFpsCalc("physicsFps");
if (setting) {
setting.on("language", this.changeListener);
}
if (status) {
status.on("physicsLoop", this.physicsFpsCalc);
status.on("renderLoop", this.renderFpsCalc);
}
}
public componentWillUnmount() {
const { setting, status } = this.props;
if (setting) {
setting.off("language", this.changeListener);
}
if (status) {
status.off("physicsLoop", this.physicsFpsCalc);
status.off("renderLoop", this.renderFpsCalc);
}
}
public render(): ReactNode {
const { status } = this.props;
let fileName: string = "";
let isNewFile: boolean = true;
let isSaved: boolean = false;
if (status) {
isNewFile = status.archive.isNewFile;
fileName = status.archive.fileName ?? "";
isSaved = status.archive.isSaved;
}
const fpsInfo = { const fpsInfo = {
renderFps: Math.floor(this.state.renderFps).toString(), renderFps: Math.floor(this.state.renderFps).toString(),
physicsFps: Math.floor(this.state.physicsFps).toString() physicsFps: Math.floor(this.state.physicsFps).toString()
}; };
return <LocalizationTooltipHost i18nKey="Header.Bar.Fps.Info" options={fpsInfo}>
<div className="fps-view">
<Icon iconName="SpeedHigh"></Icon>
<span>{I18N(this.props, "Header.Bar.Fps", fpsInfo)}</span>
</div>
</LocalizationTooltipHost>
}
}
class HeaderWindowsAction extends Component {
public render() {
return <Theme className="header-windows-action">
<div className="action-button">
<Icon iconName="ChromeMinimize"/>
</div>
<div className="action-button">
<Icon iconName="ChromeRestore"/>
</div>
<div className="action-button close-button">
<Icon iconName="ChromeClose"/>
</div>
</Theme>
}
}
/**
*
*/
@useSettingWithEvent("language")
@useStatusWithEvent("fileChange")
class HeaderBar extends Component<IHeaderBarProps & IMixinStatusProps & IMixinSettingProps> {
public render(): ReactNode {
const { status, setting } = this.props;
let fileName: string = "";
let isNewFile: boolean = true;
let isSaved: boolean = false;
if (status) {
isNewFile = status.archive.isNewFile;
fileName = status.archive.fileName ?? "";
isSaved = status.archive.isSaved;
}
return <Theme return <Theme
className="header-bar" className="header-bar"
backgroundLevel={BackgroundLevel.Level1} backgroundLevel={BackgroundLevel.Level1}
@ -125,15 +145,15 @@ class HeaderBar extends Component<
isSaved ? "" : "*" isSaved ? "" : "*"
}</div> }</div>
</LocalizationTooltipHost> </LocalizationTooltipHost>
<LocalizationTooltipHost i18nKey="Header.Bar.Fps.Info" options={fpsInfo}>
<div className="fps-view"> {
<Icon iconName="SpeedHigh"></Icon> setting?.platform === Platform.desktop ?
<span>{I18N(this.props, "Header.Bar.Fps", fpsInfo)}</span> <HeaderWindowsAction/> :
</div> <HeaderFpsView setting={setting}/>
</LocalizationTooltipHost> }
</Theme> </Theme>
} }
} }
export default HeaderBar;
export { HeaderBar }; export { HeaderBar };

View File

@ -11,6 +11,11 @@ enum Themes {
dark = 2 dark = 2
} }
enum Platform {
web = 1,
desktop = 2
}
type Language = "ZH_CN" | "EN_US"; type Language = "ZH_CN" | "EN_US";
interface ISettingEvents extends Setting { interface ISettingEvents extends Setting {
@ -19,6 +24,11 @@ interface ISettingEvents extends Setting {
class Setting extends Emitter<ISettingEvents> { class Setting extends Emitter<ISettingEvents> {
/**
*
*/
public platform: Platform = Platform.web;
/** /**
* *
*/ */
@ -63,5 +73,5 @@ const useSettingWithEvent = superConnectWithEvent<Setting, ISettingEvents>(Setti
export { export {
Themes, Setting, SettingContext, useSetting, Language, useSettingWithEvent, Themes, Setting, SettingContext, useSetting, Language, useSettingWithEvent,
IMixinSettingProps, SettingProvider, SettingConsumer IMixinSettingProps, SettingProvider, SettingConsumer, Platform
}; };

View File

@ -9,6 +9,8 @@ const EN_US = {
"Header.Bar.File.Save.Status.Unsaved": "UnSaved", "Header.Bar.File.Save.Status.Unsaved": "UnSaved",
"Header.Bar.Fps": "FPS: {renderFps} | {physicsFps}", "Header.Bar.Fps": "FPS: {renderFps} | {physicsFps}",
"Header.Bar.Fps.Info": "The rendering frame rate ({renderFps} fps) is on the left, and the simulation frame rate ({physicsFps} fps) is on the right.", "Header.Bar.Fps.Info": "The rendering frame rate ({renderFps} fps) is on the left, and the simulation frame rate ({physicsFps} fps) is on the right.",
"Header.Bar.Fps.Render.Info": "Render fps {fps}",
"Header.Bar.Fps.Simulate.Info": "Simulate fps {fps}",
"Command.Bar.Save.Info": "Save", "Command.Bar.Save.Info": "Save",
"Command.Bar.Play.Info": "Start simulation", "Command.Bar.Play.Info": "Start simulation",
"Command.Bar.Drag.Info": "Drag and drop to move the camera", "Command.Bar.Drag.Info": "Drag and drop to move the camera",

View File

@ -9,6 +9,8 @@ const ZH_CN = {
"Header.Bar.File.Save.Status.Unsaved": "未保存", "Header.Bar.File.Save.Status.Unsaved": "未保存",
"Header.Bar.Fps": "帧率: {renderFps} | {physicsFps}", "Header.Bar.Fps": "帧率: {renderFps} | {physicsFps}",
"Header.Bar.Fps.Info": "左侧为渲染帧率 ({renderFps} fps), 右侧为模拟帧率 ({physicsFps} fps)。", "Header.Bar.Fps.Info": "左侧为渲染帧率 ({renderFps} fps), 右侧为模拟帧率 ({physicsFps} fps)。",
"Header.Bar.Fps.Render.Info": "渲染帧率 {fps}",
"Header.Bar.Fps.Simulate.Info": "模拟帧率 {fps}",
"Command.Bar.Save.Info": "保存", "Command.Bar.Save.Info": "保存",
"Command.Bar.Play.Info": "开始仿真", "Command.Bar.Play.Info": "开始仿真",
"Command.Bar.Drag.Info": "拖拽进行视角移动", "Command.Bar.Drag.Info": "拖拽进行视角移动",

View File

@ -1,5 +1,5 @@
import { Component, ReactNode } from "react"; import { Component, ReactNode } from "react";
import { SettingProvider, Setting } from "@Context/Setting"; import { SettingProvider, Setting, Platform } from "@Context/Setting";
import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme"; import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme";
import { StatusProvider, Status } from "@Context/Status"; import { StatusProvider, Status } from "@Context/Status";
import { ClassicRenderer } from "@GLRender/ClassicRenderer"; import { ClassicRenderer } from "@GLRender/ClassicRenderer";
@ -33,7 +33,7 @@ class SimulatorWeb extends Component {
// TODO: 这里要读取设置 // TODO: 这里要读取设置
this.setting = new Setting(); this.setting = new Setting();
(window as any).setting = (this.setting as any); this.setting.platform = Platform.web;
// TODO: 这里要读取存档 // TODO: 这里要读取存档
const classicRender = new ClassicRenderer().onLoad(); const classicRender = new ClassicRenderer().onLoad();

View File

@ -6,4 +6,17 @@ div.render-view {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
}
div.render-view-fps {
height: 0;
width: 100%;
div.fps-view {
position: relative;
opacity: .5;
top: 10px;
left: 10px;
user-select: none;
}
} }

View File

@ -1,9 +1,85 @@
import { Component, ReactNode, createRef } from "react"; import { Component, ReactNode, createRef } from "react";
import { useStatus, IMixinStatusProps } from "@Context/Status"; import { useStatus, IMixinStatusProps } from "@Context/Status";
import { useSetting, IMixinSettingProps, Themes } from "@Context/Setting"; import { useSetting, IMixinSettingProps, Themes, Platform } from "@Context/Setting";
import { ClassicRenderer } from "@GLRender/ClassicRenderer"; import { ClassicRenderer } from "@GLRender/ClassicRenderer";
import { FontLevel, Theme } from "@Component/Theme/Theme";
import { Localization } from "@Component/Localization/Localization";
import "./RenderView.scss"; import "./RenderView.scss";
interface IRendererFpsViewProps {
renderFps: number;
physicsFps: number;
}
@useStatus
class RendererFpsView extends Component<IMixinStatusProps, IRendererFpsViewProps> {
public state = {
renderFps: 0,
physicsFps: 0,
}
private updateTime: number = 0;
private renderFpsCalc: (t: number) => void = () => {};
private physicsFpsCalc: (t: number) => void = () => {};
public componentDidMount() {
const { status } = this.props;
this.renderFpsCalc = this.createFpsCalc("renderFps");
this.physicsFpsCalc = this.createFpsCalc("physicsFps");
if (status) {
status.on("physicsLoop", this.physicsFpsCalc);
status.on("renderLoop", this.renderFpsCalc);
}
}
public componentWillUnmount() {
const { status } = this.props;
if (status) {
status.off("physicsLoop", this.physicsFpsCalc);
status.off("renderLoop", this.renderFpsCalc);
}
}
private createFpsCalc(type: "renderFps" | "physicsFps") {
return (t: number) => {
if (t === 0) {
return;
}
let newState: IRendererFpsViewProps = {} as any;
newState[type] = 1 / t;
if (this.updateTime > 20) {
this.updateTime = 0;
this.setState(newState);
}
this.updateTime ++;
}
}
public render() {
const fpsInfo = {
renderFps: Math.floor(this.state.renderFps).toString(),
physicsFps: Math.floor(this.state.physicsFps).toString()
};
return <Theme
className="render-view-fps"
fontLevel={FontLevel.normal}
>
<div className="fps-view">
<Localization i18nKey="Header.Bar.Fps.Render.Info" options={{
fps: fpsInfo.renderFps
}}/><br/>
<Localization i18nKey="Header.Bar.Fps.Simulate.Info" options={{
fps: fpsInfo.physicsFps
}}/>
</div>
</Theme>;
}
}
@useSetting @useSetting
@useStatus @useStatus
class RenderView extends Component<IMixinStatusProps & IMixinSettingProps> { class RenderView extends Component<IMixinStatusProps & IMixinSettingProps> {
@ -23,12 +99,17 @@ class RenderView extends Component<IMixinStatusProps & IMixinSettingProps> {
[190 / 255, 187 / 255, 184 / 255, 1] [190 / 255, 187 / 255, 184 / 255, 1]
} }
return <div ref={this.rootEle} className={classList.join(" ")}/>; return <>
{
this.props.setting?.platform === Platform.desktop ?
<RendererFpsView/> : null
}
<div ref={this.rootEle} className={classList.join(" ")}/>;
</>
} }
public componentDidMount() { public componentDidMount() {
let div = this.rootEle.current; let div = this.rootEle.current;
// console.log(div, div?.childNodes, this.props.status, this.props.status?.renderer.dom)
if (div && (!div.childNodes || div.childNodes.length <= 0) && this.props.status) { if (div && (!div.childNodes || div.childNodes.length <= 0) && this.props.status) {
div.appendChild(this.props.status.renderer.dom); div.appendChild(this.props.status.renderer.dom);
} }