Compare commits
	
		
			No commits in common. "bed8e9fbc85f033c700d2aa6039337a419c614b2" and "5ac9cb442a31ab85c395b190e22d8c513a545ba1" have entirely different histories.
		
	
	
		
			bed8e9fbc8
			...
			5ac9cb442a
		
	
		
| @ -1,27 +1,3 @@ | |||||||
| div.header-bar { | div.header-bar { | ||||||
|     padding: 0 20px; |  | ||||||
|     box-sizing: border-box; |  | ||||||
|     display: flex; |     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,126 +1,37 @@ | |||||||
| import { Component, ReactNode } from "react"; | import { Component, ReactNode } from "react"; | ||||||
| import { useStatus, IMixinStatusProps } from "@Context/Status"; | import { useStatus, IMixinStatusProps } from "@Context/Status"; | ||||||
| import { useSetting, IMixinSettingProps } from "@Context/Setting"; |  | ||||||
| import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme"; | 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 { | interface IHeaderBarProps { | ||||||
|     height: number; |     height: number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| interface HeaderBarState { |  | ||||||
|     renderFps: number; |  | ||||||
|     physicsFps: number; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * 头部信息栏 |  * 头部信息栏 | ||||||
|  */ |  */ | ||||||
| @useSetting |  | ||||||
| @useStatus | @useStatus | ||||||
| class HeaderBar extends Component< | class HeaderBar extends Component<IHeaderBarProps & IMixinStatusProps> { | ||||||
|     IHeaderBarProps & IMixinStatusProps & IMixinSettingProps, |  | ||||||
|     HeaderBarState |  | ||||||
| > { |  | ||||||
| 
 |  | ||||||
|     public state = { |  | ||||||
|         renderFps: 0, |  | ||||||
|         physicsFps: 0, |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     private changeListener = () => { |     private changeListener = () => { | ||||||
|         this.forceUpdate(); |         this.forceUpdate(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     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() { |     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() { |     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 { |     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 |         return <Theme | ||||||
|             className="header-bar" |             className="header-bar" | ||||||
|             backgroundLevel={BackgroundLevel.Level1} |             backgroundLevel={BackgroundLevel.Level1} | ||||||
|             fontLevel={FontLevel.Level3} |             fontLevel={FontLevel.Level3} | ||||||
|             style={{ height: this.props.height }} |             style={{ height: this.props.height }} | ||||||
|         > |         > | ||||||
|             <TooltipHost content={I18N(this.props, "Header.Bar.Title.Info")}> |             Living Together | Web | ||||||
|                 <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> |         </Theme> | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -15,14 +15,8 @@ interface ILocalizationProps { | |||||||
|     options?: Record<string, string>; |     options?: Record<string, string>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function I18N(language: Language | IMixinSettingProps, key: keyof typeof EN_US, values?: Record<string, string>) { | function I18N(language: Language, key: keyof typeof EN_US, values?: Record<string, string>) { | ||||||
|     let lang: Language; |     let i18nValue = LanguageDataBase[language][key]; | ||||||
|     if (typeof language === "string") { |  | ||||||
|         lang = language; |  | ||||||
|     } else { |  | ||||||
|         lang = language.setting?.language ?? "EN_US"; |  | ||||||
|     } |  | ||||||
|     let i18nValue = LanguageDataBase[lang][key]; |  | ||||||
|     if (values) { |     if (values) { | ||||||
|         for (let valueKey in values) { |         for (let valueKey in values) { | ||||||
|             i18nValue = i18nValue.replaceAll(new RegExp(`\\{\\s*${valueKey}\\s*\\}`, "g"), values[valueKey]); |             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-bg-color-lvl5-dark: $ms-color-gray200; | ||||||
| 
 | 
 | ||||||
| // 文字颜色 | // 文字颜色 | ||||||
| $lt-font-color-normal-dark: $ms-color-gray90; | $lt-font-color-normal-dark: $ms-color-gray110; | ||||||
| $lt-font-color-lvl3-dark: $ms-color-gray80; | $lt-font-color-lvl3-dark: $ms-color-gray100; | ||||||
| $lt-font-color-lvl2-dark: $ms-color-gray80; | $lt-font-color-lvl2-dark: $ms-color-gray100; | ||||||
| $lt-font-color-lvl1-dark: $ms-color-gray70; | $lt-font-color-lvl1-dark: $ms-color-gray90; | ||||||
| 
 | 
 | ||||||
| // 背景颜色 | // 背景颜色 | ||||||
| $lt-bg-color-lvl1-light: $ms-color-gray10; | $lt-bg-color-lvl1-light: $ms-color-gray10; | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ abstract class BasicRenderer< | |||||||
|     P extends IRendererParam = {}, |     P extends IRendererParam = {}, | ||||||
|     M extends IAnyObject = {}, |     M extends IAnyObject = {}, | ||||||
|     E extends Record<EventType, any> = {} |     E extends Record<EventType, any> = {} | ||||||
| > extends AbstractRenderer<P, M & IRendererParams, E & {loop: number}> { | > extends AbstractRenderer<P, M & IRendererParams, E> { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| 	 * 渲染器参数 | 	 * 渲染器参数 | ||||||
|  | |||||||
| @ -75,8 +75,6 @@ class ClassicRenderer extends BasicRenderer<{}, IClassicRendererParams> { | |||||||
| 
 | 
 | ||||||
|     loop(t: number): void { |     loop(t: number): void { | ||||||
| 
 | 
 | ||||||
|         this.emit("loop", t); |  | ||||||
| 
 |  | ||||||
|         // 常规绘制窗口
 |         // 常规绘制窗口
 | ||||||
|         this.gl.viewport(0, 0, this.canvas.width, this.canvas.height); |         this.gl.viewport(0, 0, this.canvas.width, this.canvas.height); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,13 +1,5 @@ | |||||||
| const EN_US = { | const EN_US = { | ||||||
|     "EN_US": "English (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; | export default EN_US; | ||||||
| @ -1,13 +1,5 @@ | |||||||
| const ZH_CN = { | const ZH_CN = { | ||||||
|     "EN_US": "英语 (美国)", |     "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; | export default ZH_CN; | ||||||
| @ -20,11 +20,6 @@ class Archive< | |||||||
|      */ |      */ | ||||||
|     public fileName?: string; |     public fileName?: string; | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * 是否保存 |  | ||||||
|      */ |  | ||||||
|     public isSaved: boolean = false; |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * 文件数据 |      * 文件数据 | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -8,7 +8,6 @@ import { ObjectID, AbstractRenderer } from "./Renderer"; | |||||||
| import { Label } from "./Label"; | import { Label } from "./Label"; | ||||||
| 
 | 
 | ||||||
| type ModelEvent = { | type ModelEvent = { | ||||||
|     loop: number; |  | ||||||
|     groupAdd: Group; |     groupAdd: Group; | ||||||
|     rangeAdd: Range; |     rangeAdd: Range; | ||||||
|     labelAdd: Label; |     labelAdd: Label; | ||||||
| @ -206,8 +205,6 @@ class Model extends Emitter<ModelEvent> { | |||||||
|                 } as any); |                 } as any); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         this.emit("loop", t); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -46,15 +46,10 @@ interface IRendererConstructor< | |||||||
| 	M extends IAnyObject = {} | 	M extends IAnyObject = {} | ||||||
| > { | > { | ||||||
| 	new (canvas: HTMLCanvasElement, param?: M): AbstractRenderer< | 	new (canvas: HTMLCanvasElement, param?: M): AbstractRenderer< | ||||||
| 		IRendererParam, IAnyObject, AbstractRendererEvent | 		IRendererParam, IAnyObject, Record<EventType, any> | ||||||
| 	> | 	> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type AbstractRendererEvent = { |  | ||||||
|     [x: EventType]: any; |  | ||||||
|     loop: number; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * 渲染器 API |  * 渲染器 API | ||||||
|  * @template P 渲染器绘制参数 |  * @template P 渲染器绘制参数 | ||||||
| @ -64,7 +59,7 @@ type AbstractRendererEvent = { | |||||||
| abstract class AbstractRenderer< | abstract class AbstractRenderer< | ||||||
| 	P extends IRendererParam = {}, | 	P extends IRendererParam = {}, | ||||||
| 	M extends IAnyObject = {}, | 	M extends IAnyObject = {}, | ||||||
| 	E extends AbstractRendererEvent = {loop: number} | 	E extends Record<EventType, any> = {} | ||||||
| > extends Emitter<E> { | > extends Emitter<E> { | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
|  | |||||||
| @ -6,11 +6,8 @@ import { Localization } from "@Component/Localization/Localization"; | |||||||
| import { Entry } from "../Entry/Entry"; | import { Entry } from "../Entry/Entry"; | ||||||
| import { StatusProvider, Status } from "@Context/Status"; | import { StatusProvider, Status } from "@Context/Status"; | ||||||
| import { ClassicRenderer } from "@GLRender/ClassicRenderer"; | import { ClassicRenderer } from "@GLRender/ClassicRenderer"; | ||||||
| import { initializeIcons } from '@fluentui/font-icons-mdl2'; |  | ||||||
| import "./SimulatorWeb.scss"; | import "./SimulatorWeb.scss"; | ||||||
| 
 | 
 | ||||||
| initializeIcons(); |  | ||||||
| 
 |  | ||||||
| class SimulatorWeb extends Component { | class SimulatorWeb extends Component { | ||||||
|      |      | ||||||
|     /** |     /** | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user