Compare commits
	
		
			3 Commits
		
	
	
		
			5ac9cb442a
			...
			bed8e9fbc8
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| bed8e9fbc8 | |||
| 567c1f2ea4 | |||
| 6bc498f14d | 
| @ -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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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> | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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]); | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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}> { | ||||
| 
 | ||||
|     /** | ||||
| 	 * 渲染器参数 | ||||
|  | ||||
| @ -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); | ||||
| 
 | ||||
|  | ||||
| @ -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; | ||||
| @ -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; | ||||
| @ -20,6 +20,11 @@ class Archive< | ||||
|      */ | ||||
|     public fileName?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * 是否保存 | ||||
|      */ | ||||
|     public isSaved: boolean = false; | ||||
| 
 | ||||
|     /** | ||||
|      * 文件数据 | ||||
|      */ | ||||
|  | ||||
| @ -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); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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> { | ||||
| 
 | ||||
| 	/** | ||||
|  | ||||
| @ -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 { | ||||
|      | ||||
|     /** | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user