Consulo UI API从构思到原型

大家好,很长时间以来我都没有写过有关该项目在中心上的生命的文章,我决定对其进行修复,也许我将从现在正在研究的内容开始,即Consulo UI API。


Consulo是IntelliJ IDEA社区版的分支,具有对.NET(C#),Java的支持

让我们开始吧


问: Consulo UI API-这是什么?


答:这是一组用于创建UI的API。 实际上,一组简单的接口重复了不同的组件-Button,RadionButton,Label等。


问:如果已经有Swing,则创建另一组UI API的目的是什么(因为IDEA UI使用Swing显示界面)


答:为此,让我们深入研究在使用Consulo UI API时遵循的想法。 由于我是Consulo项目的主要参与者,几乎是唯一的参与者,所以随着时间的推移,我很难维持现在的项目数量(约156个存储库)。 关于海量代码分析存在一个问题,但是不可能在Desktop平台上的一个IDE实例的框架内完成它,而且我不想练习使用JetBrains的插件,由于多个原因,一个想法最终的项目包含了所有插件。


这个想法是在Web服务器上进行分析的。 “常规分析”在Web服务器上不太适合我,我想制作一个Web IDE (至少在一开始是只读的),同时仍具有与台式机相同的功能。


您可以说这重复了一些Upsource ,想法本身是相似的-但方法完全不同。


然后到了这一刻-当这个主意出现时,却不知道如何去做。 我有使用GWT和Vaadin框架的经验-我不想使用其他非Java框架来生成JS(很好或普通的JS)。


我花了一个月的时间对此进行研究 。 这是对我这部分能力的考验。 最初,我仅使用GWT-对于信息,我使用了内置的RPC。


有一个简单的目标-项目已经打开,只需要显示Project Tree + Editor Tabs即可 。 在这种情况下,所有内容都应该与台式机版本相似。


刚出现的后端立即出现问题。 例如,使用EventQueue进行内部操作


简而言之,EventQueue是UI(AWT,Swing)流;几乎与UI相关的所有事情都在其中发生-渲染,处理按钮单击等。
历史上,在IDEA中,写操作应始终在UI线程中执行。
写操作是对文件的记录,或对某些服务的更改(例如,重命名模块)

首先,可以忽略EventQueue的问题-但随后出现其他问题。 例如,平庸的图标 。 想象一下,我们有一个项目树


  • []项目名称
    • [] src
      • [] Main.java
    • 【测试】
    • [] build.gradle

对于每个文件,我们需要上传并显示图片。 由于我们使用的是Swing代码,因此我们使用javax.swing.Icon类。 问题是它只是一个接口-有很多不同的实现


  • 图像图标是仅包装图像(即文件系统中的常规图像)的图标
  • Laredred icon分层图标,由两个或更多彼此堆叠的图标组成
  • 禁用图标-应用了灰色滤镜的图标
  • 透明图标-具有指定透明度的图标
  • 还有很多

结果,为了在浏览器中显示图标,您需要支持整个动物园(几乎同时支持所有动物园)。相关的问题之一是可能出现一个文件完全不为您所知的图标(例如,在某些插件中逐个像素地绘制一个符号)-等等。需要忽略。


使用拐杖方法(嗯,没有拐杖的地方)-做出了决定。 在instanceof中检查我们所需的类型是很陈旧的,而忽略所有其他类型。


一段时间后,支持导航文件系统,打开文件,语法突出显示,语义分析,快速文档信息,代码引用导航(支持Ctrl + B或Ctrl + MouseClick1的组合)。 从本质上讲,编辑器与桌面平台非常相似。


它看起来像什么:



所以-使Web界面成为我的强项。 但这是一项非常艰巨的工作-必须重做。 然后,瓦丹来营救。


我决定重做我的GWT实现以使用Vaadin框架。 事实证明该测试非常糟糕(性能非常低下)-我使用Vaadin的经验对其影响更大,而我拒绝了该选项(我甚至在当前的早午餐上进行了一次硬重置,以忘记它:D)。


但是使用Vaadin的经验一直对我有用,这个想法产生了-统一UI,以便您可以编写一个代码,但在输出上会得到不同的结果,具体取决于平台。


统一UI的另一个原因是IntelliJ平台内完整的Swing组件动物园。 这种问题的一个示例是两个完全不同的Tabs实现。




分离UI逻辑:


  • 前端-每个元素的一组接口,例如consulo.ui.Button#create()
  • 后端-平台相关的实现
    • Swing-桌面实施
    • WGWT-Web实施

什么是WGWT ? Wrapper GWT的首字母缩写。 这是一个自写的框架-存储该组件的STATE并通过WebSocket将其发送到浏览器(后者又生成html)。 他着眼于Vaadin(是的-另一个拐杖)。


时间过去了-我已经可以启动在桌面和浏览器中都可以使用的测试UI



我还并行使用了Vaadin,因为如果使用Java,这是构建Web UI的最便宜的选择之一。 我对Vaadin的研究越来越多-我决定再次将WGWT重写为Vaadin,但进行了一些更正。


进行了哪些修改:


  • 拒绝使用几乎所有Vaadin成分。 原因有很多-其中之一是组件太有限(定制很少)。
  • 使用WGWT框架中的现有组件;即其GWT实施
  • 还有一个补丁可以让您编写Connect注释而无需直接链接到服务器组件(对项目结构进行了更多操作,以避免客户端代码中的服务器类可用)

结果,结果是这样的:


  • 前端-每个元素的一组接口,例如consulo.ui.Button#create()
  • 后端-当前实施取决于平台
    • Swing-桌面实施
    • Vaadin-Web实施
    • Android? -为了使手机在应用程序启动时烧尽:D到目前为止,仅在可以使用现有代码转移到Android的想法层面上(因为不会绑定到Swing)

因此,当前的Consulo UI API诞生了。


Consulo UI API将在哪里使用?


  • 在所有插件中。 在编译期间,AWT / Swing将被“阻止”(不再有java.awt.Color )(将创建一个Javac处理器-稍后,随着Java 9的到来,可能根本没有必要)。 我理解您的组件不是万灵药。 目前,您可以制作自己的自定义UI组件,到目前为止,仅在Swing侧上(在这种情况下,有必要向consulo.destop插件添加依赖项,以避免在Web服务器上出现问题)。 在插件方面还没有创建Vaadin组件-将会的,这是次要的任务。
  • 在平台方面,这些是“设置/首选项”,“运行配置”,“编辑器”-本质上是进入JFrame的整个界面。

有什么问题?


  • 完全与AWT / Swing代码不兼容(存在拐杖类TargetAWT / TargetVaadin,该类具有用于转换组件的方法,但是插件无法访问这些类)。
    所有Swing组件都无法在浏览器中显示-因此,您需要重写所有这些代码。
    平台内部几乎已支持Consulo UI API,这使您不仅可以在插件内部使用新的UI框架。
  • IntelliJ平台与Swing的牢固结合非常深,它被深深地埋没了,如果没有“下一个”拐杖,您将无法挖掘它(

过了一段时间


代码在两个平台上均相同。


他在台式机上的工作:



他在浏览器中的工作:



关于以上问题:


  • 图标。 引入了consulo.ui.image.Image类,它是来自文件系统的图片(不仅如此)。 您可以使用consulo.ui.image.Image#create(java.net.URL)方法上传图片。

在桌面平台上-图标是按之前加载的方式加载的,仅现在返回类型为SwingImageRef(旧类名称-以前称为consulo.ui.image.Image称为consulo.ui.ImageRef)-继承javax.swing.Icon和consulo.ui的接口.image.Image。 稍后将删除此接口(其存在是由于简化了向新类型的迁移)


在Web平台上-URL存储在对象内部,并且是在界面中显示的标识符(通过URL-/ app / uiImage = URLhashCode


引入了ImageEffects类。 它本身具有创建派生图标所需的方法。 例如,#grayed(图像)将返回带有灰色滤镜的图标,#transparent(图像)将返回半透明图标。


即,上述整个动物园被驱动成狭窄的框架。


也将引入对元素手动渲染的支持(当然,如果没有这种支持)。 方法ImageEffects#canvas(int高度,int宽度,Consumer <Canvas2D> painterConsumer)将返回一个通过Canvas2D绘制的图标


在桌面上-包装器将在Swing的常规Graphics2D之上使用


在Web上-对Canvas2D方法的每次调用都将被保存,然后将其传输到浏览器,在该浏览器中将使用来自浏览器的内部Canvas


  • 在UI线程中编写动作。 oo 目前还没有解决方案。 目前-有一个在自己的线程中进行写操作的原型但是到目前为止,仅在Web平台上,需要在平台内部进行太多更改才能在桌面上“推出”。
  • 用户界面已统一-没有简单元素的动物园

还出现了一个新问题-Swing对话框在显示过程中阻塞了执行线程。 结果,IDEA喜欢以这种形式编写代码:


DialogWrapper wrapper = ...; int value = wrapper.showAndGet(); if(value == DialogWrapper.OK) { ... } 

同时,在Vaadin中显示对话框不会阻塞执行线程。


为了避免与对话框的同步和异步显示混淆,选择了一个异步选项(必须重新考虑并重做以上代码)。


总结


一段时间后,我有了一个可运行的Web应用程序原型。



到目前为止,这是一个正朝着其发布迈进的原型-但它不会很快(糟糕)。

Source: https://habr.com/ru/post/zh-CN414497/


All Articles