Виртуальный GUI на Clojure



Рассмотрим создание приложения с графическим интерфейсом, который бы работал на рабочем столе и в браузере без изменения кода Live Demo с моего сайта
Для рабочего стола будем использовать библиотеку SWT, для браузера будем манипулировать DOM.
Будем считать что графический виджет это структура данных ключ- значение в Clojure это Maps. Такая абстракция дасть возможность манипулировать виджетами с помощью только двух функций (widget w :key value) для получения данных с виджета и (widget w/[w1...wn] :key/{:key1 val1 …:keyn valn} v/[v1...vn]) для смены данных.

Например:

прочитать текст из текстового поля — (widget! txtHello :text)

записать текст в текстовое поле - (widget! txtHello :text “hello”)

очистить несколько текстовых полей (widget! [txtName txtAge txtTel] :text “”)

установить обработчиков событий на несколько виджетов (widget! [btHello cbHello] :on-select [btClick cbChange])

заполнить combo и установить обработчик событий (widget! CbHello {:on-select cbChange :fill [“man” “women”]})

Показать код функции widget для рабочего стола
(defn widget
  ([w prop]
  (case prop
     …...................
    :index (.getSelectionIndex w)
    :text (.getText w)))
  ([w n prop]
    (case prop
      …............
      :text (.getText w n)
      :index (.indexOf w n))))

Показать код функции widget! для рабочего стола
(defn widget!
  ([w prop]
    (if (map? prop)(dorun (map #(widget! w  (key %) (val %)) prop))
      (if (vector? w)(dorun (map #(widget! % prop ) w))
        (case prop
          …......................
          :close (.dispose w)
          :select-all (.selectAll w)))))
  ([w prop v]
    (if (vector? w)
   (if (vector? v) (dorun (map #(widget! % prop %2) w v))
  (dorun (map #(widget! % prop v) w)))
      (case prop
         …...............
        :index (.select w v)
        :text (.setText w v))))
  ([w n prop v]
    (if (keyword? n)
      (case n
        …........... 
        :select (.setSelection w prop v))
    (case prop
      …..............................
      :text (.setText w n v)))))

Для браузера эта функция будет несколько сложнее из-за необходимости вызвать внутреннии функции для манипуляции DOM. Строить графический интерфейс для рабочего стола мы будем в дизайнере Eclipse используя WindowBuilder. Это даст нам java-class с построенным графическим интерфейсом. Получение виджетов из этого класса выглядит так:
(def btHello (.btHello shell))
….....
(def txtHello (.txtHello shell))

А если виджетов десятки? К тому же для браузера это будет просто:
(def btHello “btHello”)
….......
(def txtHello “txtHello”)

Просятся макросы:

Показать код макроса defc для робочего стола
(defmacro defc [shell & body]
  (let [res (map #(nop `(def ~% (~(symbol (str "." %)) ~shell))) body)]
    (conj  res 'do)))

Показать код макроса defc для браузера
(defmacro defc [shell & body]
  (let [res (map #(nop `(def ~% (str ~(str  % ) ~shell))) body)]
    (conj  res 'do)))

теперь всё будет просто (defc shell btHello … txtHello) Особенностью SWT есть то что необходимо написать свою оконную процедуру

Показать код функции loop-gui для робочего стола
(defn loop-gui! [^Shell sh & fw]
  (. sh (open))
  (loop [] (when-not (. sh (isDisposed))
             (try
               (if (not (. display (readAndDispatch)))
                 (. display (sleep)))
               (catch Exception e
                 (show-exception  sh "Error: " e)))
             (recur))))

Показать код функции loop-gui для браузера
(defn loop-gui!  [sh & fw]
  (when-not (= sh "")
    (let [sh-obj (by-id sh)]
      (set! (.-display (.-style sh-obj )) "block"))))

Ну и ещё нам понадобится функция вывода сообщений

Показать код функции message для рабочего стола
(defn message [^Shell sh s]
  (when-not (.isVisible sh) (.open sh ))
  (let [mbox ( MessageBox. sh SWT/ICON_INFORMATION )]
    (doto mbox
      (.setText "Info")
      (.setMessage s)
      (.open))))

Показать код функции message для браузера
(defn message [sh s] (js/alert s))

Ну и теперь код простого приложения. Инициализацию shell тоже можно сделать макросом.

Показать код приложения
(def shell (HelloApp. (new Display)) // (def shell “”) для браузера 

(defn -main []
    (defc btHello,cbHello,lstHello,txtHello)
    (widget![cbHello lstHello] :fill [[“man”,”woman”][“One” “Two” “Three”]])   
    (widget! [btHello cbHello] :on-select [btClick cbChange])   
    (loop-gui  ))

(defn btClick[] (message(shell (widget txtHello :text))))
(defn cbChange[] (message(shell (widget cbHello :text))))

Комментарии

Популярные сообщения из этого блога

Работа с глобальными переменными в Rust

IUP - библиотека элементов GUI

Виртуальный GUI на Rust