更多内容可以查看专题https://codeteenager.github.io/theia-analysis/在上一篇脚手架源码分析文章中,我们对前端页面进行了分析在启动过程中如何显示它,那么本文我们将介绍theia布局的相关内容以及如何自定义布局。PhosphorJSTheia的组件和布局系统是使用PhosphorJS实现的,它提供了一套丰富的组件、布局、事件和数据结构。这些使开发人员能够构建高质量、类似于桌面的Web应用程序。为什么Theia使用PhosphorJS作为布局系统?IDE应用程序中的选项卡式和停靠式面板,这些类型的交互必须使用JavaScript实现,并以可扩展和优雅的方式动态添加许多模式,这包括消息传递、调整大小/附加/分离/显示/隐藏事件、大小-约束聚合和高效的布局计算。PhosphorJS以一种灵活、自包含且与现有代码兼容的方式提供了目前Web上缺少的这些功能。Github地址:https://github.com/phosphorjs/phosphor,文档地址:http://phosphorjs.github.io/。但是,PhosphorJS的作者已经退休,该项目已被存档。该项目现被Jupyter团队更名为jupyterlab/lumino,Github地址为:https://github.com/jupyterlab/lumino。如何?PhosphorJS提供了一个简单而灵活的小部件类,它为消息传递和DOM节点操作建立了层次结构。这允许在整个层次结构中传播各种消息,例如:调整大小、附加、分离、显示和隐藏(以及其他功能)。一旦建立可靠传播的调整大小消息,就可以在JavaScript中实现布局,这是单独使用CSS无法实现的。通过以绝对值明确指定节点的位置和大小,浏览器能够优化重排以包含在页面的受影响部分内。这意味着对应用程序的一部分进行更改不会导致重排整个页面的成本。PhosphorJS认识到CSS在很多方面都很好,并且不会阻止开发人员在适当的时候使用它。PhosphorJS布局与标准CSS布局配合得很好,两者可以在小部件层次结构中自由混合。PhosphorJS认识到开发人员最喜欢的框架非常适合特定任务。PhosphorWidget实例可以托管由任何其他框架生成的DOM内容,并且此类小部件可以自由嵌入任何PhosphorWidget层次结构中。PhosphorJS提供了大量难以正确有效实现的预定义小部件和布局,例如:菜单和菜单栏、拆分面板、选项卡和停靠面板。这使得创建前面描述的丰富的桌面样式应用程序变得简单。@phosphor/widgets提供了很多布局和组件:BoxLayoutBoxPanelDockLayoutDockPanelMenuMenuBarPanelPanelLayoutTabBar其中BoxLayout和DockLayout都继承布局,像BoxPanel、MenuBar、TabBar等都继承Widget。Widget有很多生命周期回调函数:onActivateRequestonBeforeShowonAfterShowonBeforeHideonAfterHideonBeforeAttachonAfterAttachonBeforeDetachonAfterDetachonChildAddedonChildRemovedonCloseRequestonResizeonUpdateRequestonFitRequest通过attach方法将widget插入dom节点。Attach实现如下://@phosphor/widgets/src/widget.tsexportfunctionattach(widget:Widget,host:HTMLElement,ref:HTMLElement|null=null):void{if(widget.parent){thrownewError('无法附加子部件。');}if(widget.isAttached||document.body.contains(widget.node)){thrownewError('Widget已经附加。');}if(!document.body.contains(host)){thrownewError('Hostisnotattached.');}MessageLoop.sendMessage(widget,Widget.Msg.BeforeAttach);host.insertBefore(widget.node,ref);MessageLoop.sendMessage(widget,Widget.Msg.AfterAttach);}最后调用host.insertBefore在ref节点之前插入。在前面的脚手架分析中,我们终于看到FrontendApplication的start方法主要做了以下几件事:1.初始化并启动前端应用贡献;2、调用@phosphor/widgets的Widget.attach方法,将ApplicationShell布局插入到document.body中类为theia-preload的节点之前,3、初始化ApplicationShell布局,4、隐藏启动动画,显示页。//@theia/core/src/browser/frontend-application.tsgetshell():ApplicationShell{returnthis._shell;}protectedattachShell(host:HTMLElement):void{constref=this.getStartupIndicator(host);Widget.attach(this.shell,host,ref);}其中shell就是ApplicationShell,接下来详细介绍一下ApplicationShell。ApplicationShellTheia整个视图布局主要包括topPanel、leftPanel、mainPanel、rightPanel、bottomPanel和statusBar。ApplicationShell继承Widget,在ApplicationShell中定义了上述视图,并在createLayout方法中使用@phosphor/widgets提供的布局容器进行组装。//@theia/core/src/browser/shell/application-shell.ts@injectable()exportclassApplicationShellextendsWidget{/***主shell区域中的停靠面板。这是编辑们通常去的地方。*/主面板:TheiaDockPanel;/***底部外壳区域的停靠面板。与主面板不同,底部面板*可以折叠和展开。*/底部面板:TheiaDockPanel;/***左侧面板的处理程序。主要的应用程序视图位于此处,例如*文件资源管理器和git视图。*/leftPanelHandler:SidePanelHandler;/***右侧面板的处理程序。辅助应用程序视图放在此处,例如*大纲视图。*/rightPanelHandler:SidePanelHandler;/***应用程序shell的常规选项。*/protectedoptions:ApplicationShell.Options;/***固定大小的面板显示在顶部。这个通常包含主菜单。*/topPanel:面板;protectedinitializeShell():void{this.addClass(APPLICATION_SHELL_CLASS);this.id='theia-app-shell';//将用户定义的应用程序选项与默认选项合并this.options={bottomPanel:{...ApplicationShell.DEFAULT_OPTIONS.bottomPanel,...this.options?.bottomPanel||{}},leftPanel:{...ApplicationShell.DEFAULT_OPTIONS.leftPanel,...this.options?.leftPanel||{}},rightPanel:{...ApplicationShell.DEFAULT_OPTIONS.rightPanel,...this.options?.rightPanel||{}}};this.mainPanel=this.createMainPanel();this.topPanel=this.createTopPanel();this.bottomPanel=this.createBottomPanel();this.leftPanelHandler=this.sidePanelHandlerFactory();this.leftPanelHandler.create('左',this.options.leftPanel);this.leftPanelHandler.dockPanel.widgetAdded.connect((_,widget)=>this.fireDidAddWidget(widget));this.leftPanelHandler.dockPanel.widgetRemoved.connect((_,widget)=>this.fireDidRemoveWidget(widget));this.rightPanelHandler=this.sidePanelHandlerFactory();this.rightPanelHandler.create('右',this.options.rightPanel);this.rightPanelHandler.dockPanel.widgetAdded.connect((_,widget)=>this.fireDidAddWidget(widget));this.rightPanelHandler.dockPanel.widgetRemoved.connect((_,widget)=>this.fireDidRemoveWidget(widget));this.layout=this.createLayout();this.tracker.currentChanged.connect(this.onCurrentChanged,this);this.tracker.activeChanged.connect(this.onActiveChanged,this);}/***组装应用程序外壳布局。覆盖此方法以更改主区域和侧面板的排列*。*/protectedcreateLayout():Layout{constbottomSplitLayout=this.createSplitLayout([this.mainPanel,this.bottomPanel],[1,0],{orientation:'vertical',spacing:0});constpanelForBottomArea=newSplitPanel({layout:bottomSplitLayout});panelForBottomArea.id='theia-bottom-split-panel';constleftRightSplitLayout=this.createSplitLayout([this.leftPanelHandler.container,panelForBottomArea,this.rightPanelHandler.container],[0,1,0],{orientation:'horizo??ntal',spacing:0});constpanelForSideAreas=newSplitPanel({layout:leftRightSplitLayout});panelForSideAreas.id='theia-left-right-split-panel';returnthis.createBoxLayout([this.topPanel,panelForSideAreas,this.statusBar],[0,1,0],{direction:'top-to-bottom',spacing:0});}}自定义布局以上介绍了ApplicationShell组成和布局,那么我们很容易扩展一个工具栏或者模拟器,只需要重写ApplicationShell的createLayout方法,添加自己定义的视图,然后使用inversify重新绑定即可。其实官方提供了一个@theia/工具栏模块,也是按照上面的方法重写的。效果如图:代码如下:@injectable()exportclassApplicationShellWithToolbarOverrideextendsApplicationShell{@inject(ToolbarPreferences)protectedtoolbarPreferences:ToolbarPreferences;@inject(PreferenceService)protectedreadonlypreferenceService:PreferenceService;@inject(ToolbarFactory)protectedreadonlytoolbarFactory:()=>工具栏;受保护的工具栏:工具栏;@postConstruct()protectedoverrideasyncinit():Promise
