Merge pull request 'Add header bar component' (#6) from dev-mrkbear into master
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone Build is passing

Reviewed-on: http://git.mrkbear.com/MrKBear/living-together/pulls/6
This commit is contained in:
MrKBear 2022-02-25 16:31:21 +08:00
commit bed8e9fbc8
12 changed files with 168 additions and 15 deletions

View File

@ -1,3 +1,27 @@
div.header-bar {
padding: 0 20px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
user-select: none;
div.title > i, div.fps-view > i {
font-size: larger;
vertical-align: text-bottom;
padding-right: 5px;
}
div.ms-TooltipHost {
padding: 0 5px;
overflow: hidden;
flex-shrink: 1;
div {
overflow: hidden;
text-overflow: ellipsis;
word-break: keep-all;
white-space: nowrap;
}
}
}

View File

@ -1,37 +1,126 @@
import { Component, ReactNode } from "react";
import { useStatus, IMixinStatusProps } from "@Context/Status";
import { useSetting, IMixinSettingProps } from "@Context/Setting";
import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme";
import { Icon } from '@fluentui/react/lib/Icon';
import { I18N } from "../Localization/Localization";
import "./HeaderBar.scss";
import { Tooltip, TooltipHost } from "@fluentui/react";
interface IHeaderBarProps {
height: number;
}
interface HeaderBarState {
renderFps: number;
physicsFps: number;
}
/**
*
*/
@useSetting
@useStatus
class HeaderBar extends Component<IHeaderBarProps & IMixinStatusProps> {
class HeaderBar extends Component<
IHeaderBarProps & IMixinStatusProps & IMixinSettingProps,
HeaderBarState
> {
public state = {
renderFps: 0,
physicsFps: 0,
}
private changeListener = () => {
this.forceUpdate();
}
public componentDidMount() {
private updateTime: number = 0;
private createFpsCalc(type: "renderFps" | "physicsFps") {
return (t: number) => {
let newState: HeaderBarState = {} as any;
newState[type] = 1 / t;
if (this.updateTime > 60) {
this.updateTime = 0;
this.setState(newState);
}
this.updateTime ++;
}
}
private renderFpsCalc: (t: number) => void = () => {};
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.model.on("loop", this.physicsFpsCalc);
status.renderer.on("loop", this.renderFpsCalc);
}
}
public componentWillUnmount() {
const { setting, status } = this.props;
if (setting) {
setting.off("language", this.changeListener);
}
if (status) {
status.model.off("loop", this.physicsFpsCalc);
status.renderer.off("loop", 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 = {
renderFps: Math.floor(this.state.renderFps).toString(),
physicsFps: Math.floor(this.state.physicsFps).toString()
};
return <Theme
className="header-bar"
backgroundLevel={BackgroundLevel.Level1}
fontLevel={FontLevel.Level3}
style={{ height: this.props.height }}
>
Living Together | Web
<TooltipHost content={I18N(this.props, "Header.Bar.Title.Info")}>
<div className="title">
<Icon iconName="HomeGroup"></Icon>
<span>{I18N(this.props, "Header.Bar.Title")}</span>
</div>
</TooltipHost>
<TooltipHost content={I18N(this.props, "Header.Bar.File.Name.Info", {
file: isNewFile ? I18N(this.props, "Header.Bar.New.File.Name") : fileName,
status: isSaved ? I18N(this.props, "Header.Bar.File.Save.Status.Saved") :
I18N(this.props, "Header.Bar.File.Save.Status.Unsaved")
})}>
<div className="file-name">{
isNewFile ? I18N(this.props, "Header.Bar.New.File.Name") : fileName
}{
isSaved ? "" : "*"
}</div>
</TooltipHost>
<TooltipHost content={I18N(this.props, "Header.Bar.Fps.Info", fpsInfo)}>
<div className="fps-view">
<Icon iconName="SpeedHigh"></Icon>
<span>{I18N(this.props, "Header.Bar.Fps", fpsInfo)}</span>
</div>
</TooltipHost>
</Theme>
}
}

View File

@ -15,8 +15,14 @@ interface ILocalizationProps {
options?: Record<string, string>;
}
function I18N(language: Language, key: keyof typeof EN_US, values?: Record<string, string>) {
let i18nValue = LanguageDataBase[language][key];
function I18N(language: Language | IMixinSettingProps, key: keyof typeof EN_US, values?: Record<string, string>) {
let lang: Language;
if (typeof language === "string") {
lang = language;
} else {
lang = language.setting?.language ?? "EN_US";
}
let i18nValue = LanguageDataBase[lang][key];
if (values) {
for (let valueKey in values) {
i18nValue = i18nValue.replaceAll(new RegExp(`\\{\\s*${valueKey}\\s*\\}`, "g"), values[valueKey]);

View File

@ -18,10 +18,10 @@ $lt-bg-color-lvl4-dark: $ms-color-gray180;
$lt-bg-color-lvl5-dark: $ms-color-gray200;
// 文字颜色
$lt-font-color-normal-dark: $ms-color-gray110;
$lt-font-color-lvl3-dark: $ms-color-gray100;
$lt-font-color-lvl2-dark: $ms-color-gray100;
$lt-font-color-lvl1-dark: $ms-color-gray90;
$lt-font-color-normal-dark: $ms-color-gray90;
$lt-font-color-lvl3-dark: $ms-color-gray80;
$lt-font-color-lvl2-dark: $ms-color-gray80;
$lt-font-color-lvl1-dark: $ms-color-gray70;
// 背景颜色
$lt-bg-color-lvl1-light: $ms-color-gray10;

View File

@ -18,7 +18,7 @@ abstract class BasicRenderer<
P extends IRendererParam = {},
M extends IAnyObject = {},
E extends Record<EventType, any> = {}
> extends AbstractRenderer<P, M & IRendererParams, E> {
> extends AbstractRenderer<P, M & IRendererParams, E & {loop: number}> {
/**
*

View File

@ -75,6 +75,8 @@ class ClassicRenderer extends BasicRenderer<{}, IClassicRendererParams> {
loop(t: number): void {
this.emit("loop", t);
// 常规绘制窗口
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);

View File

@ -1,5 +1,13 @@
const EN_US = {
"EN_US": "English (US)",
"ZH_CN": "Chinese (Simplified)"
"ZH_CN": "Chinese (Simplified)",
"Header.Bar.Title": "Living Together | Emulator",
"Header.Bar.Title.Info": "Group Behavior Research Emulator",
"Header.Bar.File.Name.Info": "{file} ({status})",
"Header.Bar.New.File.Name": "New File",
"Header.Bar.File.Save.Status.Saved": "Saved",
"Header.Bar.File.Save.Status.Unsaved": "UnSaved",
"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.",
}
export default EN_US;

View File

@ -1,5 +1,13 @@
const ZH_CN = {
"EN_US": "英语 (美国)",
"ZH_CN": "中文 (简体)"
"ZH_CN": "中文 (简体)",
"Header.Bar.Title": "群生共进 | 仿真器",
"Header.Bar.Title.Info": "群体行为研究仿真器",
"Header.Bar.File.Name.Info": "{file} ({status})",
"Header.Bar.New.File.Name": "新存档",
"Header.Bar.File.Save.Status.Saved": "已保存",
"Header.Bar.File.Save.Status.Unsaved": "未保存",
"Header.Bar.Fps": "帧率: {renderFps} | {physicsFps}",
"Header.Bar.Fps.Info": "左侧为渲染帧率 ({renderFps} / fps), 右侧为模拟帧率 ({physicsFps} / fps)。",
}
export default ZH_CN;

View File

@ -20,6 +20,11 @@ class Archive<
*/
public fileName?: string;
/**
*
*/
public isSaved: boolean = false;
/**
*
*/

View File

@ -8,6 +8,7 @@ import { ObjectID, AbstractRenderer } from "./Renderer";
import { Label } from "./Label";
type ModelEvent = {
loop: number;
groupAdd: Group;
rangeAdd: Range;
labelAdd: Label;
@ -205,6 +206,8 @@ class Model extends Emitter<ModelEvent> {
} as any);
}
}
this.emit("loop", t);
}
}

View File

@ -46,10 +46,15 @@ interface IRendererConstructor<
M extends IAnyObject = {}
> {
new (canvas: HTMLCanvasElement, param?: M): AbstractRenderer<
IRendererParam, IAnyObject, Record<EventType, any>
IRendererParam, IAnyObject, AbstractRendererEvent
>
}
type AbstractRendererEvent = {
[x: EventType]: any;
loop: number;
}
/**
* API
* @template P
@ -59,7 +64,7 @@ interface IRendererConstructor<
abstract class AbstractRenderer<
P extends IRendererParam = {},
M extends IAnyObject = {},
E extends Record<EventType, any> = {}
E extends AbstractRendererEvent = {loop: number}
> extends Emitter<E> {
/**

View File

@ -6,8 +6,11 @@ import { Localization } from "@Component/Localization/Localization";
import { Entry } from "../Entry/Entry";
import { StatusProvider, Status } from "@Context/Status";
import { ClassicRenderer } from "@GLRender/ClassicRenderer";
import { initializeIcons } from '@fluentui/font-icons-mdl2';
import "./SimulatorWeb.scss";
initializeIcons();
class SimulatorWeb extends Component {
/**