Clojure: как спроектировать пользовательский интерфейс рабочего стола

Я пытаюсь создать настольный пользовательский интерфейс для схем, макетов, рисунков. Просто ищу советы высокого уровня от настоящих разработчиков программного обеспечения.

Предполагая, что «база данных» находится в памяти (карта clojure произвольной глубины для всех пользовательских данных и, возможно, еще одна для настроек приложения и т. д.), я изучаю, как сделать на них модель-представление-контроллер, где данные могут быть обработаны и изменены одним или несколькими из следующих способов:

  1. Отдельное текстовое поле, в котором отображается один параметр, например ширина поля.
  2. Представление типа «инспектор», которое показывает несколько параметров выбранного объекта, таких как ширина, высота, цвет, флажки и т. д.
  3. Представление в виде таблицы или электронной таблицы, в котором отображаются несколько параметров нескольких объектов, потенциально вся база данных.
  4. Графическое изображение всего объекта, например схематическое изображение и вид компоновки.

Изменение любого из них должно немедленно отображаться в любом другом активном представлении, как текстовом, так и графическом, а не после нажатия «ОК» ... поэтому модальные окна не разрешены. Если по какой-то причине отображаются табличное представление, инспекторское представление и графическая визуализация, графическое перетаскивание угла поля должно немедленно отображаться в тексте и т. д.

Рассматриваемой платформой является JavaFX, но я хотел бы четкого разделения между пользовательским интерфейсом и всем остальным, поэтому я хочу избежать binding в смысле JFX, поскольку это очень тесно связывает мои проектные данные со свойствами JFX, увеличивает зернистость модели. , и вынуждает меня работать вне стандартных функций clojure для работы с данными и/или активно работать со всем миром getValue/setValue.

Я по-прежнему предполагаю, по крайней мере, некоторую возможность сохранения состояния/изменяемости и использование встроенных функций Clojure, таких как возможность add-watch на атоме/var/ref и позволять среде выполнения сигнализировать зависимым функциям.

Взаимодействие, зависящее от платформы, будет тесно связано с фактическим пользовательским интерфейсом, таким как уточнение ActionListener, работа с ObservableValue и т. д., и будет предпринята попытка свести к минимуму зависимость от таких вещей, как JavaFX Property, для реальных данных приложения. Я не развлекаюсь FRP для этого.

Я не возражаю против расширения интерфейсов JFX или создания собственных протоколов для использования специфичных для приложения defrecord, но я бы предпочел, чтобы данные приложения оставались прямыми данными Clojure, незапятнанными платформой.

Вопрос в том, как все это настроить, максимально придерживаясь неизменяемой модели. Я вижу несколько вариантов:

  1. Мелкозернистый: каждое значение/примитив параметра (например, Long, Double, Boolean или String) является атомом, и каждое представление, которое может изменять значение, «достигает» настолько, насколько это необходимо в базе данных, чтобы изменить значение. Это может быть отстойно, так как потенциально могут быть тысячи отдельных значений (например, точки на нарисованной от руки кривой), и потребуется много (deref...) мусора. Я считаю, что именно так JFX хотел бы сделать это с гигантскими массивами свойств в листовых узлах и т. Д., Которые кажутся раздутыми. При таком подходе это не кажется намного лучше, чем просто кодирование на Java/C++.
  2. Среднезернистый: каждый объект/запись в базе данных является атомом карты Clojure. Вся карта заменяется при изменении любого из ее значений. Меньше всего атомов, с которыми приходится иметь дело, и позволяет, например, длинные массивы прямых чисел для различных вещей. Но это усложняется, когда некоторые объекты в базе данных требуют большей вложенности, чем другие.
  3. Грубое зерно: есть только один атом: база данных. Каждый раз, когда что-то меняется, вся база данных заменяется, и каждое представление должно повторно отображать свою конкретную часть. Это немного похоже на использование молотка, чтобы прихлопнуть муху, и наивная реализация потребовала бы все время перерисовывать все. Но я по-прежнему считаю, что это лучший компромисс, поскольку любой примитив имеет четкий путь доступа от корневого узла, независимо от того, осуществляется ли доступ к нему на уровне отдельных примитивов или на уровне записей.

Мне также нужна возможность многократного создания экземпляра одного шаблона данных. Так, например, если пользователь изменяет символ или фигуру, которые используются в нескольких местах, одно и то же редактирование будет применяться везде. Я считаю, что это также требует некоторого типа поведения, подобного «указателю». Я думаю, что могу сохранить атом в модели, а затем создать экземпляр по мере необходимости, и он может работать в любой из приведенных выше моделей зерна.

Любые другие подходы? Попытка сделать инструмент, похожий на графический редактор, на функциональном языке просто глупа? Спасибо


person Sonicsmooth    schedule 12.01.2015    source источник
comment
Поскольку вы ориентируетесь на JavaFX в качестве набора инструментов для графического интерфейса, возможно, вы изучаете SceneBuilderKit (фреймворк библиотеки для графического редактирования сцен JavaFX) и его исходный код может вам помочь. Некоторые из его возможностей похожи, хотя и отличаются от того, что вы описываете. Это определенно не основано на функциональной модели типа clojure.   -  person jewelsea    schedule 13.01.2015
comment
Да, я нацелен на JavaFX. После нескольких месяцев периодического тыкания в клавиатуру между работой, пробками и кричащим ребенком мне наконец удалось заставить анонимного/локального ChangeListener заменить атом и запустить часы Clojure. Неато. Я не видел SceneBuilderKit (хотя я играл с их настольным приложением SceneBuilder), но я посмотрю. Еще одно соображение заключается в том, разрешить ли JFX владеть всем графом сцены, используя свои собственные примитивы, или рисовать свои собственные фигуры на холсте в стандартном стиле OnPaint, но это не относится к этому вопросу SO.   -  person Sonicsmooth    schedule 13.01.2015
comment
Да, много вариантов Соника. Это отличный вопрос, но очень трудно ответить. Я предполагаю, что будет больше работы, если вы не будете использовать граф сцены и некоторые готовые элементы управления, такие как [ControlsFX PropertySheet]. Можете написать разработчику clojurefx и узнать его мнение по вашему вопросу.   -  person jewelsea    schedule 13.01.2015
comment
как насчет того, чтобы забыть о шаблоне MVC и вместо этого принять архитектуру Flux с каналом core.async в качестве шины событий? Только мои 2 цента.   -  person myguidingstar    schedule 13.01.2015
comment
Спасибо, я изо всех сил стараюсь не полагаться на шаблоны обязательно, но я использовал этот термин, чтобы читатель сразу понял, что я пытаюсь сделать. На самом низком уровне существует изменяемое состояние для интерактивной с пользователем программы, а платформа пользовательского интерфейса имеет обратные вызовы, которые необходимо зарегистрировать. Как использовать Flux, React, core.async, compojure, datomic, node.js... ааа... слишком много всего! Я все еще не на поезде clojurescript. У меня осталось всего несколько нейронов, поэтому я пытаюсь свести к минимуму перегрузку.   -  person Sonicsmooth    schedule 13.01.2015


Ответы (1)


Я не думаю, что глупо использовать функциональный язык для создания инструмента, подобного графическому редактору. Но я не могу утверждать, что у меня есть ответ на ваш вопрос. Вот несколько ссылок, которые могут помочь вам в вашем путешествии:

  1. Стюарт Сьерра – Компоненты, достаточная структура
  2. Крис Грейнджер – Световой стол: объясняет, как Light Table (источник).
  3. Крис Грейнджер — IDE как ценность : сообщение в блоге, связанное с видео выше
  4. Конал Эллиот - осязаемое функциональное программирование: использование Функциональное реактивное программирование для создания составного пользовательского интерфейса, но его код написан на Haskell.
  5. Натан Херцинг и Крис Ши — помощь избирателям с помощью Padestal, Datomic, Om и core.async
  6. Дэвид Нолен - Сравнительное грамотное программирование: показывает все для использования core.async, чтобы упростить программирование пользовательского интерфейса в ClojureScript. Приведенные здесь идеи можно использовать в пользовательском интерфейсе рабочего стола.
  7. Рич Хикки – Язык системы: потрясающий доклад о системном программировании от создателя Clojure .

У Erik Meijer есть хорошая цитата о функциональном и императивном коде:

...независимо от того, Haskell это, C# Java, F#, Scala, Python, PHP, подумайте об идее иметь море императивного кода, который взаимодействует с внешним миром, и там есть островки чистого кода, где вы пишете свои функции на чистый способ. Но вы должны решить, насколько велики острова и насколько велико море. Но ответ никогда не состоит в том, что есть только острова или только море. Хороший программист точно знает правильный баланс.

person Rodrigo Taboada    schedule 01.02.2015
comment
Спасибо, это хорошие ссылки. Я видел один или два из них раньше, но большинство из них новые для меня. Просто нужно переварить и найти правильную абстракцию, которая мне нужна... Я хотел бы понять, когда/как делать материал core.async, когда использовать промисы, фьючерсы, агенты, необработанные потоки и т. д., а не просто оставлять вещи однопоточный, и как объединить низкоуровневые вызовы GUI с функциональной/потоком данных/асинхронной/frp/любой абстракцией. Я предполагаю, что это займет несколько итераций и поможет найти блокирующие/медленные разделы, где добавление большего количества потоков поможет. - person Sonicsmooth; 04.02.2015
comment
Добавлено еще одно выступление, я не могу поверить, что забыл это замечательное выступление. - person Rodrigo Taboada; 05.02.2015