- PVSM.RU - https://www.pvsm.ru -

Consulo UI API от идеи до прототипа

    Всем привет, давно я не писал статьи о жизни проекта на хабре, решил исправиться и начну пожалуй с того над чем сейчас работаю а именно Consulo UI API.

Consulo [1] — это форк IntelliJ IDEA Community Edition, который имеет поддержку .NET(C#), Java

Начнем

Q: Consulo UI API — что это такое?

A: Это набор API для создания UI. На деле — простой набор интерфейсов, который повторяет разные компоненты — Button, RadionButton, Label, etc.

Q: Какова цель создания, еще одного набора UI API, если уже был Swing (так как IDEA UI использует Swing для отображения интерфейса)

A: Для этого давайте углубимся в идею которую я последовал работая над Consulo UI API. Так как я основной и практически единственный контрибьютор в проект Consulo, со временем мне стало трудно поддерживать то количество проектов которое сейчас (порядка 156 репозиториев). Встал вопрос о массовом анализе кода, но это нереально сделать в рамках одного инстанса IDE на Desktop платформе, а использовать опыт JetBrains где один проект idea-ultimate держит все плагины я не хотел практиковать по ряду причин.

    Зародилась мысль об анализе на веб-сервере. "Обычный анализ" меня не сильно устраивал на веб-сервере, захотелось сделать Web IDE (хотя бы readonly на старте) — при этом иметь полностью весь тот же функционал что и на Desktop.

    Вы можете сказать что это повторяет немного Upsource, сама идея подобная — но подход совсем другой.

    И вот пришел тот момент — когда идея была, но не было известно как это сделать. За плечами был опыт использования GWT, Vaadin фреймворков — другие не Java фреймворки для генерации JS(ну или plain js) я не хотел использовать.

    Я уделил себе месяц на это исследования [2]. Это был тест моих возможностей в этой части. Сначала я использовал только GWT — для получения информации я использовал встроенный RPC.

    Была простая цель — проект открыт уже, нужно было только отобразить Project Tree + Editor Tabs. При этом все должно было похожим на Desktop версию.

    Сразу появились проблемы с новоиспеченным бэкэндом. Например использования EventQueue для внутренних действий

    EventQueue если коротко это UI(AWT, Swing) поток в нем происходит почти все что связано с UI — отрисовка, обработка нажатия на кнопку и так далее.
    Исторически сложилось в IDEA то что write действия должны всегда выполняться в UI потоке.
     Write действие — это запись в файл, либо изменения какого то сервиса (например переименования модуля)

    Поначалу проблему с EventQueue можно было игнорировать — но потом появились другие проблемы. Например банальные иконки. Представим у нас есть дерево проекта

  • [ ] project-name
    • [ ] src
      • [ ] Main.java
    • [ ] test
    • [ ] build.gradle

    И для каждого файла нам нужно загрузить и показать картинку. Так как мы работаем внутри Swing кода — мы используем класс javax.swing.Icon. Проблема в том что это просто интерфейс — который имеет очень много разных реализаций

  • image icon это иконка которая просто оборачивает Image(то есть обычную картинку с файловой системы)
  • layred icon слоеная иконка которая состоит из двух или более иконок наложенных друг на друга
  • disabled icon — иконка с наложенным серым фильтром
  • transparent icon — иконка с заданной прозрачностью
  • и много других

    В итоге чтобы показать иконку в браузере нужно поддерживать весь зоопарк(и почти все сразу).Одна из сопуствующих проблем состоит в том, что может прийти совсем неизвестная тебе иконка для файла (например внутри какого то плагина отрисован попиксельно какой то символ) — и такое нужно игнорировать.

    Методом костыля (ну куда же без них) — было сделано решение. Банально проверять через instanceof на нужный нам тип — а все другие игнорировать.

    Спустя время была сделана поддержка навигации по файловой системе, открытия файла, подсветка синтаксиса, семантический анализ, quick doc info, навигация по code references(поддерживалась комбинация например Ctrl + B, или Ctrl + MouseClick1). По сути Editor был очень похож на Desktop платформу.

    Как это выглядило:

Consulo UI API от идеи до прототипа - 1

    Итак — сделать Web интерфейс было возможно моими силами. Но это была очень грубая работа — которую нужно было переделать. И тут пришёл на помощь Vaadin.

    Я решил переделать мою GWT реализацию на использования Vaadin фреймворка. Этот тест получился очень плохим (очень сильно деградировал перфоманс) — тут больше сказался мой опыт использования Vaadin, и я забраковал этот вариант(я даже сделал hard-reset на текущем бранче, чтобы забыть об этом :D).

    Но опыт использования Vaadin мне всеодно пригодился, зародилась мысль — унифицировать UI чтобы можно было писать один код, но на выходе получать различный результат в зависимости от платформы.

    Еще одна причина унифицировать UI — это полный зоопарк Swing компонентов внутри IntelliJ платформы. Пример такой проблемы — это две совсем разных реализации Tabs

Consulo UI API от идеи до прототипа - 2

Consulo UI API от идеи до прототипа - 3

Разделяем UI логику:

  • frontend — набор интерфейсов для каждого элемента, например consulo.ui.Button#create()
  • backend — реализация в зависимости от платформы
    • Swing — desktop реализация
    • WGWT — web реализация

    Что такое WGWT? Сокращения от Wrapper GWT. Это самописный фреймворк — который хранил STATE компонента и отправлял его через WebSocket браузеру(который в свою очередь генерировал html). Он писался с оглядкой на Vaadin (да да — еще один костыль).

    Время шло — и уже я мог запустить тестовый UI, который работал одинаково на Desktop и в браузере

Consulo UI API от идеи до прототипа - 4

    Так же паралельно я использовал Vaadin на работе, так как это один из самых дешёвых вариантов построить Web UI если использовать Java. Я все больше и больше изучал Vaadin — и я решил опять переписать WGWT на Vaadin но с некоторыми правками.

Каковы были правки:

  • отказ от использования практически всех компонентов Vaadin. Причин было несколько — одна из них, это слишком ограниченные компоненты (кастомизация была минимальная).
  • использования уже существующие компоненты из моего самописного фреймворка WGWT — то есть их GWT реализацию
  • так же был сделать patch который позволял писать аннотации Connect [3] без прямой ссылки на серверный компонент (это было сделано больше для project structure, во избежания доступности серверных классов внутри клиент кода)

В итоге получилось вот так:

  • frontend — набор интерфейсов для каждого элемента, например consulo.ui.Button#create()
  • backend — текущая реализация в зависимости от платформы
    • Swing — desktop реализация
    • Vaadin — web реализация
    • Android? — ради того чтобы телефон сгорел на старте приложения :D Пока только на уровне идеи о том что можно будет использовать уже существующий код для переноса на Android (ибо не будет привязки к Swing)

И так родилась текущая использоваться Consulo UI API.

Где будет использоватся Consulo UI API?

  • Во всех плагинах. AWT/Swing будет "заблочен"(никаких больше java.awt.Color) во время компиляции(будет сделан javac processor — позднее, возможно вообще не нужно будет с приходом java 9). Свой набор компонентов — это не панацея, это я понимаю. На текущий момент — можно сделать свой кастомный UI компонент, пока только на Swing стороне (и в таких случаях нужно будет добавлять зависимость в плагин consulo.destop во избежания проблем на веб сервере). Создания Vaadin компонентов на стороне плагинов пока нет — будет, это минорная задача.
  • На стороне платформы — это Settings/Preferences, Run Configurations, Editor — по сути весь интерфейс который идет до JFrame.

Какие есть проблемы?

  • Полностью не совместимость с AWT/Swing кодом (есть костыльный класс TargetAWT/TargetVaadin который имеет методы для конвертацию компонентов, но эти классы не доступы для плагинов).
    Все Swing компоненты не могут быть отображены в браузере — в итоге нужно переписывать весь этот код.
    Практически везде уже поддерживается Consulo UI API внутри платформы — это позволяет уже использовать новый UI фреймворк внутри плагинов и не только.
  • Очень сильная привязаность IntelliJ платформы к Swing, он зарыт так глубоко — что без "очередного" костыля его не выкопать (Consulo UI API от идеи до прототипа - 5).

Спустя некоторое время

Вот этот код [4] работает одинаково на обеих платформах.

Его работа на Desktop:

Consulo UI API от идеи до прототипа - 6

Его работа в браузере:

Consulo UI API от идеи до прототипа - 7

По поводу вышеописанных проблем:

  • Иконки. Был введен класс consulo.ui.image.Image который представляет собой картинку с файловой системы (и не только). Для загрузки картинки можно использовать метод consulo.ui.image.Image#create(java.net.URL).

        На Desktop платформе — иконки грузятся как и грузились ранее, только теперь возвращаемый тип это SwingImageRef(legacy названия класса — раньше consulo.ui.image.Image назывался consulo.ui.ImageRef) — интерфейс который наследует javax.swing.Icon и consulo.ui.image.Image. Позднее этот интерфейс будет удален (его существования обусловлено упрощенной миграцией на новый тип)

        На Web платформе — URL сохраняется внутри объекта, и является идентификатором для отображения в интерфейсе (через URL — /app/uiImage=URLhashCode)

        Был введен класс ImageEffects. Он имеет внутри себя методы которые нужны для создания производных иконок. Например #grayed(Image) вернет иконку с серым фильтром, #transparent(Image) полупрозрачную иконку.

        То есть — весь весь вышеописанный зоопарк был загнан в узкие рамки.
        Также будет введена поддержка ручной отрисовки элементов (ну куда без этого). Метод ImageEffects#canvas(int height, int width, Consumer<Canvas2D> painterConsumer) вернет иконку которая будет отрисована через Canvas2D

        На Desktop — будет использован wrapper поверх обычного Graphics2D из Swing

        На Web — каждый вызов Canvas2D методов будет сохранен, и потом будет передан в браузер где будет использован внутренний Canvas с браузера

  • Write Action in UI Thread. Ууууу. Пока решения этой проблемы нет. На текущий момент — есть прототип Write Action in Own Thread но пока что только на Web платформе, слишком много нужно поменять внутри платформы чтобы это "выкатить" на Desktop.
  • UI был унифицирован — никакого зоопарка для простых элементов

    Также появилась новая проблема — Swing диалоги блокируют выполняющий поток во время показа. В итоге в IDEA любят писать код в таком виде:

DialogWrapper wrapper = ...;

int value = wrapper.showAndGet();

if(value == DialogWrapper.OK) {
  ...
}

При этом в Vaadin показ диалогов — не блокирует выполняющий поток.

Во избежания путаницы с синхронным и асинхронным показом диалогов — был выбран асинхронный вариант (вышеописанный код придется переосмыслить и переделать).

Итог

Спустя время я имеею рабочий прототип Web приложения.

Consulo UI API от идеи до прототипа - 8

Пока это прототип, который двигается к своему релизу — но это будет не быстро (увы).

Автор: VISTALL

Источник [5]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/java/283575

Ссылки в тексте:

[1] Consulo: https://consulo.io

[2] исследования: https://github.com/consulo/consulo/pull/190

[3] Connect: https://habr.com/users/connect/

[4] код: https://github.com/consulo/consulo/blob/master/modules/independent/lang-impl/src/main/java/com/intellij/application/options/editor/EditorSmartKeysConfigurable.java#L70

[5] Источник: https://habr.com/post/414497/?utm_campaign=414497