diff --git a/source/Component/CommandBar/CommandBar.tsx b/source/Component/CommandBar/CommandBar.tsx index 05940e9..bc2e4b7 100644 --- a/source/Component/CommandBar/CommandBar.tsx +++ b/source/Component/CommandBar/CommandBar.tsx @@ -1,6 +1,7 @@ import { BackgroundLevel, Theme } from "@Component/Theme/Theme"; import { DirectionalHint, IconButton } from "@fluentui/react"; import { LocalizationTooltipHost } from "../Localization/LocalizationTooltipHost"; +import { useSetting, IMixinSettingProps } from "@Context/Setting"; import { AllI18nKeys } from "../Localization/Localization"; import { Component, ReactNode } from "react"; import "./CommandBar.scss"; @@ -9,13 +10,19 @@ interface ICommandBarProps { width: number; } -class CommandBar extends Component { +@useSetting +class CommandBar extends Component { render(): ReactNode { return { + if (this.props.setting) { + this.props.setting.layout.focus(""); + } + }} >
{this.getRenderButton({ iconName: "Save", i18NKey: "Command.Bar.Save.Info" })} diff --git a/source/Component/Container/Container.scss b/source/Component/Container/Container.scss index 6f6609d..2714068 100644 --- a/source/Component/Container/Container.scss +++ b/source/Component/Container/Container.scss @@ -43,6 +43,7 @@ div.app-container { box-sizing: border-box; display: flex; border: .8px solid rgba($color: #000000, $alpha: 0); + // transition: all 300ms ease-in-out; justify-content: space-between; align-items: stretch; flex-direction: column; @@ -63,7 +64,7 @@ div.app-container { box-sizing: border-box; height: 32.8px; border: .8px solid rgba($color: #000000, $alpha: 0); - transition: all 300ms ease-in-out; + // transition: all 300ms ease-in-out; } div.title-view { @@ -82,6 +83,7 @@ div.app-container { div.app-tab-header-item.active { border: .8px solid blue; + transition: none; } div.app-tab-header-item::after { @@ -92,32 +94,52 @@ div.app-container { } } + div.app-panel.has-padding { + padding: 10px; + } + div.app-panel { width: 100%; height: 100%; box-sizing: border-box; + overflow: scroll; + -ms-overflow-style: none; border: .8px solid rgba($color: #000000, $alpha: 0); } + div.app-panel.hide-scrollbar::-webkit-scrollbar { + width : 0; /*高宽分别对应横竖滚动条的尺寸*/ + height: 0; + } + + div.app-panel::-webkit-scrollbar { + width : 8px; /*高宽分别对应横竖滚动条的尺寸*/ + height: 0; + } + + div.app-panel::-webkit-scrollbar-thumb { + /*滚动条里面小方块*/ + border-radius: 8px; + } + + div.app-panel::-webkit-scrollbar-track { + /*滚动条里面轨道*/ + border-radius: 8px; + background-color: rgba($color: #000000, $alpha: 0); + } + div.app-panel.active { border: .8px solid blue !important; } } -div.app-panel { - overflow: scroll; - -ms-overflow-style: none; -} -div.app-panel::-webkit-scrollbar { - width: 0 !important; - height: 0 !important; -} - div.dark.app-container.end-containe { border: .8px solid $lt-bg-color-lvl3-dark; + div.app-tab-header-item.tab, div.app-tab-header-item.active, div.app-tab-header-item:hover { + transition: none; background-color: $lt-bg-color-lvl4-dark; color: rgba($color: #FFFFFF, $alpha: .85); } @@ -125,21 +147,33 @@ div.dark.app-container.end-containe { div.app-tab-header-item.active { div.border-view::after { background-color: $lt-bg-color-lvl4-dark; + transition: none; } } + + div.app-panel::-webkit-scrollbar-thumb { + background-color: $lt-bg-color-lvl1-dark; + } } div.light.app-container.end-containe { border: .8px solid $lt-bg-color-lvl3-light; + div.app-tab-header-item.tab, div.app-tab-header-item.active, div.app-tab-header-item:hover { + transition: none; color: rgba($color: #000000, $alpha: .85); } div.app-tab-header-item.active { div.border-view::after { background-color: $lt-bg-color-lvl4-light; + transition: none; } } + + div.app-panel::-webkit-scrollbar-thumb { + background-color: $lt-bg-color-lvl1-light; + } } \ No newline at end of file diff --git a/source/Component/Container/Container.tsx b/source/Component/Container/Container.tsx index 1c88ed0..40d8d35 100644 --- a/source/Component/Container/Container.tsx +++ b/source/Component/Container/Container.tsx @@ -1,7 +1,11 @@ +import { Localization } from "@Component/Localization/Localization"; import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme"; import { Themes } from "@Context/Setting"; +import { DirectionalHint } from "@fluentui/react"; import { ILayout, LayoutDirection } from "@Model/Layout"; -import { Component, ReactNode } from "react"; +import { Component, ReactNode, MouseEvent } from "react"; +import { getPanelById, getPanelInfoById } from "../../Panel/Panel"; +import { LocalizationTooltipHost } from "../Localization/LocalizationTooltipHost"; import "./Container.scss"; interface IContainerProps extends ILayout { @@ -10,17 +14,13 @@ interface IContainerProps extends ILayout { theme?: Themes; focusId?: string; onScaleChange?: (id: number, scale: number) => any; -} - -function getPanelById(id: string) { - return {id} + onFocusTab?: (id: string) => any; } class Container extends Component { private focusEdgeId: number | undefined; + private readonly edgeInfo = { direction: LayoutDirection.Y, rootWidth: 0, @@ -31,34 +31,142 @@ class Container extends Component { mouseY: 0 }; - private renderPanel(panles: string[], showBar: boolean) { + /** + * 渲染此 Tab 下的 ELE + */ + private renderPanel(panles: string[], showBar: boolean, focus?: string) { const classList: string[] = []; const theme: Themes = this.props.theme ?? Themes.dark; + const showPanelId = focus ?? panles[0]; + const showPanelInfo = getPanelInfoById(showPanelId as any); classList.push(theme === Themes.light ? "light" : "dark"); classList.push(`background-${BackgroundLevel.Level3}`); classList.push(`font-${FontLevel.Level3}`); classList.push("app-tab-header"); + const hasActivePanel = panles.some((id) => id === this.props.focusId); + return <> {showBar ? -
{ +
{ + this.props.onFocusTab ? this.props.onFocusTab("") : undefined + }}>{ panles.map((panelId: string) => { - return
-
-
{panelId}
-
+ + const classList: string[] = ["app-tab-header-item"]; + if (panelId === this.props.focusId) classList.push("active"); + if (panelId === showPanelId) classList.push("tab"); + const panelInfo = getPanelInfoById(panelId as any); + + return +
{ + e.stopPropagation(); + this.props.onFocusTab ? this.props.onFocusTab(panelId) : undefined; + }} + > +
+
+ { + panelInfo ? + : + + } + +
+
+
}) }
: null } - {getPanelById(panles[0])} +
this.props.onFocusTab ? this.props.onFocusTab(showPanelId) : undefined} + className={[ + "app-panel", + hasActivePanel ? "active" : "", + showPanelInfo?.hidePadding ? "" : "has-padding", + showPanelInfo?.hideScrollBar ? "hide-scrollbar" : "" + ].filter(x => !!x).join(" ")} + draggable={false} + > + {getPanelById(showPanelId as any)} +
} - private renderContainer( - props: IContainerProps, - selfScale: number = 50, + /** + * 处理鼠标移动数据 + */ + private handelMouseMove = (e: MouseEvent) => { + + if (this.props.onScaleChange && this.focusEdgeId !== undefined) { + e.preventDefault(); + let mouveDist: number = 0; + let rootSize: number = 0; + let edgeSize: number = 0; + let newSize: number = 0; + + if (this.edgeInfo.direction === LayoutDirection.X) { + mouveDist = e.clientX - this.edgeInfo.mouseX; + rootSize = this.edgeInfo.rootWidth; + edgeSize = this.edgeInfo.edgeWidth; + newSize = edgeSize + mouveDist; + } + + if (this.edgeInfo.direction === LayoutDirection.Y) { + mouveDist = e.clientY - this.edgeInfo.mouseY; + rootSize = this.edgeInfo.rootHeight; + edgeSize = this.edgeInfo.edgeHeight + newSize = edgeSize + mouveDist; + } + + if (newSize < 38) { newSize = 38; } + if ((rootSize - newSize) < 38) { newSize = rootSize - 38; } + + let newScale = newSize / rootSize; + this.props.onScaleChange(this.focusEdgeId, newScale * 100); + } + } + + /** + * 处理鼠标按下事件 + * 记录鼠标数据 + */ + private handelMouseDown = (props: ILayout, e: MouseEvent) => { + const targetNode = e.target; + + if (targetNode instanceof HTMLDivElement) { + let root = targetNode.parentNode?.parentNode; + let firstDiv = targetNode.parentNode?.parentNode?.childNodes[0]; + + if (root instanceof HTMLDivElement && firstDiv instanceof HTMLDivElement) { + this.edgeInfo.rootWidth = root.offsetWidth; + this.edgeInfo.rootHeight = root.offsetHeight; + this.edgeInfo.edgeWidth = firstDiv.offsetWidth; + this.edgeInfo.edgeHeight = firstDiv.offsetHeight; + } + } + this.edgeInfo.mouseX = e.clientX; + this.edgeInfo.mouseY = e.clientY; + + this.edgeInfo.direction = props.layout ?? LayoutDirection.Y; + this.focusEdgeId = props.id ?? 0; + } + + /** + * 递归渲染全部容器 + */ + private renderContainer ( + props: IContainerProps, selfScale: number = 50, selfLayout: LayoutDirection = LayoutDirection.Y ) { @@ -70,6 +178,7 @@ class Container extends Component { const isRoot: boolean = !!props.isRoot; const classList: string[] = []; const theme: Themes = this.props.theme ?? Themes.dark; + const focusPanel: string | undefined = props.focusPanel; classList.push(theme === Themes.light ? "light" : "dark"); classList.push(`background-${BackgroundLevel.Level4}`); @@ -86,69 +195,30 @@ class Container extends Component { width: isRoot ? "100%" : selfLayout === LayoutDirection.X ? `${selfScale}%` : undefined, height: isRoot ? "100%" : selfLayout === LayoutDirection.Y ? `${selfScale}%` : undefined }} - onMouseMove={isRoot ? (e) => { - if (this.props.onScaleChange && this.focusEdgeId !== undefined) { - e.preventDefault(); - let mouveDist: number = 0; - let rootSize: number = 0; - let edgeSize: number = 0; - let newSize: number = 0; - - if (this.edgeInfo.direction === LayoutDirection.X) { - mouveDist = e.clientX - this.edgeInfo.mouseX; - rootSize = this.edgeInfo.rootWidth; - edgeSize = this.edgeInfo.edgeWidth; - newSize = edgeSize + mouveDist; - } - - if (this.edgeInfo.direction === LayoutDirection.Y) { - mouveDist = e.clientY - this.edgeInfo.mouseY; - rootSize = this.edgeInfo.rootHeight; - edgeSize = this.edgeInfo.edgeHeight - newSize = edgeSize + mouveDist; - } - - if (newSize < 38) { newSize = 38; } - if ((rootSize - newSize) < 38) { newSize = rootSize - 38; } - - let newScale = newSize / rootSize; - this.props.onScaleChange(this.focusEdgeId, newScale * 100); - } - } : undefined} - onMouseUp={isRoot ? () => { - this.focusEdgeId = undefined; - } : undefined} + onMouseMove={isRoot ? this.handelMouseMove : undefined} + onMouseUp={isRoot ? () => this.focusEdgeId = undefined : undefined} > - {panles.length > 0 && !items ? this.renderPanel(panles, showBar) : null} - {items && items[0] ? this.renderContainer(items[0], scale, layout) : null} - {items && items[1] ?
-
{ - const targetNode = e.target; - if (targetNode instanceof HTMLDivElement) { - let root = targetNode.parentNode?.parentNode; - let firstDiv = targetNode.parentNode?.parentNode?.childNodes[0]; + {/* 渲染 Panel */} + {panles.length > 0 && !items ? this.renderPanel(panles, showBar, focusPanel) : null} - if (root instanceof HTMLDivElement && firstDiv instanceof HTMLDivElement) { - this.edgeInfo.rootWidth = root.offsetWidth; - this.edgeInfo.rootHeight = root.offsetHeight; - this.edgeInfo.edgeWidth = firstDiv.offsetWidth; - this.edgeInfo.edgeHeight = firstDiv.offsetHeight; - } - } - this.edgeInfo.mouseX = e.clientX; - this.edgeInfo.mouseY = e.clientY; - this.edgeInfo.direction = props.layout ?? LayoutDirection.Y; - this.focusEdgeId = props.id ?? 0; - }} - onMouseUp={() => { this.focusEdgeId = undefined }} - > -
-
: null} + {/* 渲染第一部分 */} + {items && items[0] ? this.renderContainer(items[0], scale, layout) : null} + + {/* 渲染拖拽条 */} + {items && items[1] ? +
+
this.focusEdgeId = undefined } + /> +
: null + } + + {/* 渲染第二部分 */} {items && items[1] ? this.renderContainer(items[1], 100 - scale, layout) : null}
} diff --git a/source/Component/Container/RootContainer.tsx b/source/Component/Container/RootContainer.tsx index c80d1fa..6f01c42 100644 --- a/source/Component/Container/RootContainer.tsx +++ b/source/Component/Container/RootContainer.tsx @@ -13,6 +13,7 @@ class RootContainer extends Component { if (this.props.setting) { this.props.setting.layout.on("layoutChange", this.handelChange); this.props.setting.layout.on("scaleChange", this.handelChange); + this.props.setting.layout.on("switchTab", this.handelChange); this.props.setting.on("themes", this.handelChange); } } @@ -21,6 +22,7 @@ class RootContainer extends Component { if (this.props.setting) { this.props.setting.layout.off("layoutChange", this.handelChange); this.props.setting.layout.off("scaleChange", this.handelChange); + this.props.setting.layout.off("switchTab", this.handelChange); this.props.setting.off("themes", this.handelChange); } } @@ -28,6 +30,7 @@ class RootContainer extends Component { public render(): ReactNode { const layoutData = this.props.setting ? this.props.setting.layout.getData() : {}; const theme = this.props.setting?.themes ?? Themes.dark; + const focusId = this.props.setting?.layout.focusId ?? ""; return { theme={theme} isRoot={true} onScaleChange={this.props.setting?.layout.setScale} + onFocusTab={this.props.setting?.layout.focus} id={layoutData.id} + focusId={focusId} /> } } diff --git a/source/Component/HeaderBar/HeaderBar.tsx b/source/Component/HeaderBar/HeaderBar.tsx index f7c8717..9453c85 100644 --- a/source/Component/HeaderBar/HeaderBar.tsx +++ b/source/Component/HeaderBar/HeaderBar.tsx @@ -100,6 +100,11 @@ class HeaderBar extends Component< backgroundLevel={BackgroundLevel.Level1} fontLevel={FontLevel.Level3} style={{ height: this.props.height }} + onClick={() => { + if (this.props.setting) { + this.props.setting.layout.focus(""); + } + }} >
diff --git a/source/GLRender/BasicRenderer.ts b/source/GLRender/BasicRenderer.ts index efa4306..661c6cf 100644 --- a/source/GLRender/BasicRenderer.ts +++ b/source/GLRender/BasicRenderer.ts @@ -20,6 +20,10 @@ abstract class BasicRenderer< E extends Record = {} > extends AbstractRenderer { + public get dom() { + return this.canvas.dom + } + /** * 渲染器参数 */ diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index 8c15d38..608e97d 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -19,5 +19,10 @@ const EN_US = { "Command.Bar.Add.Tag.Info": "Add label object", "Command.Bar.Camera.Info": "Renderer settings", "Command.Bar.Setting.Info": "Global Settings", + "Panel.Title.Notfound": "{id}", + "Panel.Info.Notfound": "This panel with id {id} can not found!", + "Panel.Title.Render.View": "Live preview", + "Panel.Info.Render.View": "Live simulation results preview", + } export default EN_US; \ No newline at end of file diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index dd94e48..aaa6290 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -19,5 +19,9 @@ const ZH_CN = { "Command.Bar.Add.Tag.Info": "添加标签对象", "Command.Bar.Camera.Info": "渲染器设置", "Command.Bar.Setting.Info": "全局设置", + "Panel.Title.Notfound": "找不到面板: {id}", + "Panel.Info.Notfound": "这个编号为 {id} 的面板无法找到!", + "Panel.Title.Render.View": "实时预览", + "Panel.Info.Render.View": "实时仿真结果预览", } export default ZH_CN; \ No newline at end of file diff --git a/source/Model/Layout.ts b/source/Model/Layout.ts index 5ce21ac..72002a0 100644 --- a/source/Model/Layout.ts +++ b/source/Model/Layout.ts @@ -8,6 +8,7 @@ enum LayoutDirection { class ILayout { items?: [ILayout, ILayout]; panles?: string[]; + focusPanel?: string; layout?: LayoutDirection; scale?: number; id?: number; @@ -16,6 +17,7 @@ class ILayout { interface ILayoutEvent { layoutChange: Layout; scaleChange: Layout; + switchTab: Layout; } class Layout extends Emitter { @@ -24,6 +26,11 @@ class Layout extends Emitter { private data: ILayout = {}; + /** + * 焦点面板 ID + */ + public focusId: string = ""; + private map(fn: (layout: ILayout) => boolean | void, layout?: ILayout) { const currentLayout = layout ? layout : this.data; if( fn(currentLayout) ) return; @@ -44,6 +51,9 @@ class Layout extends Emitter { this.id = 0; this.map((layout) => { layout.id = this.id; + if (!layout.focusPanel && layout.panles && layout.panles.length > 0) { + layout.focusPanel = layout.panles[0] + } this.id ++; }); this.emit("layoutChange", this); @@ -62,6 +72,31 @@ class Layout extends Emitter { this.emit("scaleChange", this); } } + + public focus = (panelId: string) => { + if (panelId === "") { + this.focusId = panelId; + this.emit("switchTab", this); + } + + this.map((layout) => { + if (layout.panles && layout.panles.length > 0) { + let index = -1; + for (let i = 0; i < layout.panles.length; i++) { + if (layout.panles[i] === panelId) { + index = i; + break; + } + } + if (index >= 0) { + layout.focusPanel = panelId; + this.focusId = panelId; + this.emit("switchTab", this); + return true; + } + } + }) + } }; export { Layout, ILayout, LayoutDirection }; \ No newline at end of file diff --git a/source/Model/Renderer.ts b/source/Model/Renderer.ts index 02a992f..d9714f1 100644 --- a/source/Model/Renderer.ts +++ b/source/Model/Renderer.ts @@ -67,6 +67,8 @@ abstract class AbstractRenderer< E extends AbstractRendererEvent = {loop: number} > extends Emitter { + abstract dom: HTMLDivElement | HTMLCanvasElement; + /** * 渲染器参数 */ diff --git a/source/Page/SimulatorWeb/SimulatorWeb.tsx b/source/Page/SimulatorWeb/SimulatorWeb.tsx index e26e1a7..cee10f3 100644 --- a/source/Page/SimulatorWeb/SimulatorWeb.tsx +++ b/source/Page/SimulatorWeb/SimulatorWeb.tsx @@ -60,7 +60,7 @@ class SimulatorWeb extends Component { items: [ { items: [ - {panles: ["Label A", "Label Aa Bb", "Label aaa"]}, + {panles: ["RenderView", "Label Aa Bb", "Label aaa"]}, { items: [{panles: ["Label b", "Label bbb"]}, {panles: ["C"]}], scale: 80, diff --git a/source/Panel/Panel.tsx b/source/Panel/Panel.tsx new file mode 100644 index 0000000..8defca0 --- /dev/null +++ b/source/Panel/Panel.tsx @@ -0,0 +1,43 @@ +import { ReactNode, Component, FunctionComponent } from "react"; +import { Theme } from "@Component/Theme/Theme"; +import { Localization } from "@Component/Localization/Localization"; +import { RenderView } from "./RenderView/RenderView"; + +interface IPanelInfo { + nameKey: string; + introKay: string; + class: (new (...p: any) => Component) | FunctionComponent; + hidePadding?: boolean; + hideScrollBar?: boolean; + option?: Record; +} + +type PanelId = "" +| "RenderView" // 主渲染器 +; + +const PanelInfoMap = new Map(); +PanelInfoMap.set("RenderView", { + nameKey: "Panel.Title.Render.View", introKay: "Panel.Info.Render.View", class: RenderView, hidePadding: true, hideScrollBar: true +}); + +function getPanelById(panelId: PanelId): ReactNode { + switch (panelId) { + default: + let info = PanelInfoMap.get(panelId); + if (info) { + const C = info.class; + return + } else return + + + } +} + +function getPanelInfoById(panelId: PanelId): IPanelInfo | undefined { + return PanelInfoMap.get(panelId); +} + +export { PanelId, getPanelById, getPanelInfoById} \ No newline at end of file diff --git a/source/Panel/RenderView/RenderView.scss b/source/Panel/RenderView/RenderView.scss new file mode 100644 index 0000000..f174294 --- /dev/null +++ b/source/Panel/RenderView/RenderView.scss @@ -0,0 +1,9 @@ +div.render-view { + width: 100%; + height: 100%; + + div.canvas { + width: 100%; + height: 100%; + } +} \ No newline at end of file diff --git a/source/Panel/RenderView/RenderView.tsx b/source/Panel/RenderView/RenderView.tsx new file mode 100644 index 0000000..8bef356 --- /dev/null +++ b/source/Panel/RenderView/RenderView.tsx @@ -0,0 +1,38 @@ +import { Component, ReactNode, createRef } from "react"; +import { useStatus, IMixinStatusProps } from "@Context/Status"; +import { useSetting, IMixinSettingProps, Themes } from "@Context/Setting"; +import { ClassicRenderer } from "@GLRender/ClassicRenderer"; +import "./RenderView.scss"; + +@useSetting +@useStatus +class RenderView extends Component { + + private rootEle = createRef(); + + public render(): ReactNode { + const theme = this.props.setting?.themes ?? Themes.dark; + const classList: string[] = ["render-view", "background-lvl5"]; + if (theme === Themes.light) classList.push("light"); + if (theme === Themes.dark) classList.push("dark"); + + if (this.props.status) { + (this.props.status.renderer as ClassicRenderer).cleanColor = + (theme === Themes.dark) ? + [27 / 255, 26 / 255, 25 / 255, 1] : + [190 / 255, 187 / 255, 184 / 255, 1] + } + + return
; + } + + public componentDidMount() { + 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) { + div.appendChild(this.props.status.renderer.dom); + } + } +} + +export { RenderView } \ No newline at end of file