Phlox in calcit-js

Pixi.js DSL in ClojureScript with hot code swapping, inspired by Virtual DOMs. Currently only a small subset of Pixi.js features is supported.

Previews http://r.tiye.me/Phlox-GL/phlox/ .

Usage

render! to add canvas to <body/>:

ns app.main $
  :require $ [] phlox.core :refer
    [] hslx render! create-list rect circle text container graphics >>

defn comp-demo (data)
  rect
    {}
      :position $ [] 800 40
      :size $ [] 60 34
      :fill $ hslx 40 80 80
      :on $ :pointertap $ fn (e d!) (d! :demo nil)
    text $ {}
      :text "|Demo"
      :position $ [] 808 44
      :style $ {}
        :fill (hslx 120 80 20)
        :font-size 18
        :font-family "|Josefin Sans"

defatom *store nil

defn dispatch! (op op-data)
  reset! *store (updater @*store op op-data))

defn main ()
  render! (comp-demo data) dispatch! ({})

defn reload! ()
  render! (comp-container @*store) dispatch! $ {} (:swap? true)

Notice that Phlox uses :pointertap instead of :click for touch screen support.

Global keyboard events

Phlox supports a naive global event system for listening to keyboard events from elements:

{}
  :on-keyboard $ {}
    :down $ fn (e dispatch!)
    :press $ fn (e dispatch!)
    :up $ fn (e dispatch!)

Cursor and states

>> for branching states:

phlox.core/>> state :a

update-states for handling states change, used in updater:

phlox.cursor/update-states store $ [] cursor op-data

Color

Notice that Pixi.js takes colors in hex numbers.

{} $ :color 0xaaaaff

For HSL:

phlox.core/hslx 200 80 80

For HSLuv, based on hsluv:

phlox.core/hsluvx 200 80 80

For HCL:

phlox.core/hclx 200 80 80

Text input

To interact with text input:

phlox.input/request-text! e $ {}
  :placeholder "|text.."
  :initial "|demo"
  :textarea? false
  :style $ {}
  fn (result) (println "|got:" result)

Math

Complex number

phlox.complex contains several util functions to work with complex numbers like [x y].

  • add, adds two complex numbers
  • minus, adds one to another
  • times, mutiply two complex numbers
  • rebase, actualy "divide", renamed since naming collision.
  • divide-by, divide by scalar number x.
  • rand-point, returns a random [x y], takes 1 or 2 arguments

Shapes

a list of shapes

Container

Add a container:

container $ {}
  :position $ [] 1 1
  :pivot $ [] 0 0
  :rotation 0
  :alpha 1
  :on $ {}
    :pointertap (fn ())
  :on-keyboard $ {}
    :down (fn ())

simple version:

container $ {}
  :position $ [] 1 1

Group

Alias for group.

Rectangle

Draw a rectangle:

rect $ {}
  :position $ [] 1 2
  :size $ [] 1 1
  :line-style $ {}
    :width 2
    :color 0x000001
    :alpha 1
  :fill 0x000001
  :on $ {}
    :pointertap (fn ())
  :radius 1
  :rotation 1
  :pivot $ [] 1 2
  :alpha 1
  :on-keyboard $ {}
    :down (fn ())

simple version:

rect $ {}
  :position $ [] 1 2
  :size $ [] 1 1
  :line-style $ {}
    :width 2
    :color 0x000001
    :alpha 1
  :fill 0x000001

Circle

Draw a circle:

circle $ {}
  :position $ [] 1 1
  :radius 1
  :line-style $ {}
    :width 2
    :color 0x000001
    :alpha 1
  :fill 0x000001
  :on $ {}
    :pointertap (fn ())
  :alpha 1
  :on-keyboard $ {}
    :down (fn ())

simple version:

circle $ {}
  :position $ [] 1 1
  :radius 1
  :line-style $ {}
    :width 2
    :color 0x000001
    :alpha 1
  :fill 0x000001

Text

Draw text:

text $ {}
  :text "demo"
  :position $ [] 1 1
  :pivot $ [] 0 0
  :rotation 0
  :alpha 1
  :style $ {}
    :fill "|red"
    :font-size 14
    :font-family "|Hind"
  :on-keyboard $ {}
    :down (fn ())

:align :center is handled via special logic, need deeper investigation.

simple version:

text $ {}
  :text "demo"
  :position $ [] 1 1
  :style $ {}
    :fill "|red"
    :font-size 14
    :font-family "|Hind"

Graphics

Draw graphics(use phlox.core/g for validations):

graphics $ {}
  :ops $ []
    g :move-to $ [] 1 1
    g :line-to $ [] 2 2
    g :line-style $ {}
    g :begin-fill $ {} (:color "red")
    g :end-fill nil
    g :close-path nil
    g :arc-to $ {} (:p1 $ [] 200 200) (:p2 $ [] 240 180) (:radius 90)
    g :arc $ {} (:center $ [] 260 120) (:radius 40) (:angle $ [] 70 60) (:anticlockwise? false)
    g :bezier-to $ {} (:p1 $ [] 400 500) (:p2 $ [] 300 200) (:to-p $ 600 300)
    g :quadratic-to $ {} (:p1 $ [] 400 100) (:to-p $ [] 500 400)
  :position $ [] 1 1
  :pivot $ [] 1 2
  :rotation 0
  :alpha 1
  :on $ {}
    :pointertap (fn ())
  :on-keyboard $ {}
    :down (fn ())

simple version:

graphics $ {}
  :ops $ []
    g :move-to $ [] 1 1
    g :line-to $ [] 2 2
    g :line-style $ {}
    g :begin-fill $ {} (:color "red")
    g :end-fill nil
    g :close-path nil
  :position $ [] 1 1
  • arc

you may also use :radian instead of :rotation to declare angles:

g :arc $ {} (:center $ [] 260 120) (:radius 40) (:anticlockwise? false)
  :radian $ [] 0.1 0.2

create-list

requires 3 arguments, with last one in a list of pairs:

create-list :container ({})
  -> (range 20)
    map $ fn (idx)
      [] idx $ text
        {}
          :text $ str idx
          :style $ {}
          :position $ [] 0 0

Shape name is probably :container but you may also change to another one.

polyline

Draw a continous line with points:

polyline $ {}
  :style $ {} (:width 4)
    :color $ hslx 40 100 60
    :alpha 1
  :position $ [] 300 300
  :points $ []
    [] 1 1
    [] 2 2

line-segments

Draw discrete lines with pairs of points:

line-segments $ {}
  :style $ {} (:width 4)
    :color $ hslx 40 100 60
    :alpha 1
  :position $ [] 300 300
  :segments $ []
    []
      [] 1 1
      [] 2 2
    []
      [] 3 4
      [] 5 6

Mesh(Experimental)

currently support of very simple feature from https://codesandbox.io/s/o432o .

Draw with geometry and shader:

mesh $ {} (:scale 1)
  :position $ [] 300 200
  :geometry $ {}
    :attributes $ []
      {} (:id |aVertexPosition) (:size 2)
        :buffer $ [] -100 -100 100 -100 100 100 -100 100
      {} (:id |aUvs) (:size 2)
        :buffer $ [] 0 0 1 0 1 1 0 1
    :index $ [] 0 1 2 0 3 2
  :shader $ {}
    :vertex-source $ inline-file |demo.vert
    :fragment-source $ inline-file |demo.frag
  :uniforms $ js-object (:uSampler2 sample-texture)
    :time $ :x state
  • :uniforms takes a plain JavaScript object.
  • :vertex-source and :fragment-source take strings of shader code.

Vetex Shader

A demo:

precision mediump float;

attribute vec2 aVertexPosition;
attribute vec2 aUvs;

uniform mat3 translationMatrix;
uniform mat3 projectionMatrix;

varying vec2 vUvs;

void main() {
    vUvs = aUvs;
    gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
    // gl_Position = vec4(1,1,1,0);
}

Fragment Shader

A demo:

precision mediump float;

varying vec2 vUvs;
void main() {
    gl_FragColor = vec4(0.1, 0.2, 0.4, 1.0);
}

image

Draw image with PIXI/Sprite:

image $ {}
  :url |https://cdn.tiye.me/logo/quamolit.png
  :position $ [] 100 20
  :size $ [] 100 100
  :on $ {} $ :pointertap $ fn (e d)
    println |clicked

Components

with options and logics.

comp-arrow

phlox.comp.arrwo/comp-arrow for arrows:

comp-arrow (>> states :demo1)
  {}
    :from $ :from state
    :to $ :to state
    :width 2
    :arm-length 8
    :on-change $ fn (from to d!)
      d! cursor $ assoc state :from from :to to

comp-button

phlox.comp.button/comp-button provides a clickable button:

comp-button $ {}
  :text "|DEMO BUTTON"
  :position $ [] 100 0
  :align-right? false
  :on $ {}
    :pointertap $ fn (e d!) (js/console.log "|pointertap event" e d!)

comp-button $ {}
  :text "|Blue"
  :position $ [] 100 60
  :color (hslx 0 80 70)
  :fill (hslx 200 80 40)

comp-button $ {}
  :text "|Quick pointertap"
  :position $ [] 100 0
  :on-pointertap $ fn (e d!) (js/console.log "|pointertap event" e d!)

comp-messages

phlox.comp.messages/comp-messages for rendering messages:

comp-messages $ {}
  :messages (:messages state)
  :position $ [] 16 (- js/window.innerWidth 16)
  :color (hslx 0 0 50)
  :fill (hslx 0 0 30)
  :bottom? false
  :on-pointertap (fn (message d!))

comp-slider-point

Also comp-slider-point is a minimal version for comp-slider, it does not accept :titles.

comp-slider

phlox.comp.slider/comp-slider provides a little slider bar of a number, changes on dragging:

comp-slider (>> states :c) $ {}
  :value (:c state)
  :unit 10
  :min 1
  :max 10
  :round? true
  :position $ [] 20 120
  :fill (hslx 50 90 70)
  :color (hslx 200 90 30)
  :on-change $ fn (value d!) (d! cursor (assoc state :c value))

comp-spin-slider

comp-spin-slider support change value via touch and spin:

comp-spin-slider (>> states :c) $ {}
  :value (:c state)
  :unit 10
  :min 1
  :max 10
  :position $ [] 20 120
  ; :fill (hslx 50 90 70)
  ; :color (hslx 200 90 30)
  :border-color $ hslx 200 90 70
  :border-width 4
  :label "|Name of value"
  :fraction 1
  :on-change $ fn (value d!) (d! cursor (assoc state :c value))
  :on-move $ fn (pos d!) (d! cursor (assoc state :pos pos))

comp-drag-point

phlox.comp.drag-point/comp-drag-point provides a point for dragging:

comp-drag-point (>> states :p3) $ {}
  :position (:p3 state)
  :unit 0.4
  :title "|DEMO"
  :radius 6
  :fill (hslx 0 90 60)
  :alpha 1
  :color (hslx 0 0 50)
  :hide-text? false
  :on-change $ fn (position d!) (d! cursor (assoc state :p3 position))

comp-switch

phlox.comp.switch/comp-switch provides a switch button:

comp-switch $ {}
  :value (:value state)
  :position $ [] 100 20
  :title "|Custom title"
  :on-change $ fn (value d!) (d! cursor (assoc state :value value))

comp-tabs

phlox.comp.tabs/comp-tabs provides a switch button:

comp-tabs
  []
    [] :a |A
    [] :b |B
  , :a
  {}
    :position $ [] 100 20
  fn (tab d!) 4 println |tab tab

Filters

Based on https://filters.pixijs.download/main/docs/index.html .

A demo of adding shadows, first add package:

yarn add @pixi/filter-drop-shadow

then add into :filters field:

ns demo.demo
  :require
      |@pixi/filter-drop-shadow :refer $ DropShadowFilter

text $ {} (:text "\"Shadows")
  :style $ {}
  :filters $ []
    [] DropShadowFilter $ {}
      :color $ hslx 10 90 100
      :distance 2
      :rotation 30
      :alpha 1
      :quality 4
      :blur 6

For a filter ft internally it's running new to create instances of filters:

new (nth ft 0) $ to-js-data (nth ft 1)

During updates, only last part ft are compared.