G4js - developing a new GUI library for p5.js

I released G4P for Javamode just over 13 years ago but now I am getting into Javascript I intend to create a GUI library for p5.js The idea behind the library is to give p5.js users a similar experience for those like me who started with Java mode.
I aim to create a library that

  1. behaves the same in both P2D and WEBGL without having to make sketch code changes
  2. is easy to learn and use
  3. provide sophisticated flexible / features but with a simple API

So if you follow the link below you can see the library (so far) in action. I have taken one of the 3D examples that come with Processing and added a simple GUI. Just labels, buttons and sliders but more controls to follow.

Since I am starting this from scratch I will be interested in any feedback that might improve the final library. The next stage is to expand the available controls to include checkboxes and radio buttons but I am interested in what other controls users might like to see.

##### g4js progress demonstration ###

I hope you enjoy the experience :grin:

4 Likes

Will there be API compatibility between Java Mode & p5js?

Are there any plans to add compatibility w/ other JS Processing flavors like q5xjs or Pjs?

Just had a look at q5xjs and it is supposed to mirror the p5.js API . If that is correct then q4js should work without changes.
Pjs has been archived because p5.js effectively replaced it, so I have no interest in compatibility there.

I assume you mean “will g4js have the same API as G4P?”. No as I want to take advantage of JS to streamline the code to create the GUI. For instance in the above sketch this is the code to create the red slider.

sdrR = g4js.slider("sdrRed", 310, 370, 276, 40).limits(0, 255).value(r).ticks(16);
sdrR.action = function(control, value, event) {
  r = round(value);
}

where r is a global variable used to hold the red channel value for the ball colour. This is much terser and easier to follow than the equivalent G4P code.

  • q5js lacks many p5js features. For example, there’s no WEBGL yet.
  • Color definition signatures are much more strict.
  • It’s just (gray, a), (r, g, b, a) or (colorObj).
  • I’ve also missed nf() & hex() functions on q5js.
  • That’s a pity. Pjs is still the only viable option to transpile Java Mode to the web.
  • Trinket.io together w/ its module Processing also relies on Pjs.
  • Khan Academy uses a modified Pjs version for its JS course.
  • Pretty much the most important differences are size() instead of createCanvas() and new PVector() instead of createVector().
  • That’s fine. But it’d be nicer if we could easily port Java Mode sketches using G4P to the web.

@GoToLoop thanks for your comments :ok_hand:

Rather than rush to reply I have taken the time to do a little more research and to think seriously about your comments re compatibility.

Pjs is no longer under active development so users like Khan academy are creating their own modified versions to suit their needs. Trying to keep q4js compatible with possibly multiple versions of Pjs is just not realistic and certainly not worth the effort. If they want to include q4js they can create a modified version that works with their modified version of Pjs :woozy_face: My final thought is without active development the user base for Pjs will become insignificant ior even nonexistent.

The developers of q5.js describe it as ‘experimental’ and incomplete so keeping q5.js compatible with it is like shooting a moving target and involves a lot of work. Once q5.js has reached the same development stage as p5.js then I will reconsider making g4js compatible. :grin:

That is very true but -

  • even without G4P converting a non-trivial Java Mode sketch to work in p5.js is not easy (I know I have done it)
  • only a small minority of users will want to port sketches that use G4P
  • anyone capable of creating a gui with G4P should have no trouble creating the equivalent in q4js as the syntax will be much simpler.
  • I want the freedom to create a new API that is easier to learn and use.

Thanks again for taking the time to comment on g4js.

So just to remind readers of the question in my original post -

1 Like

Pjs was never meant to have a user base as a primary concern.

Although we can code in Pjs pretty much like p5js in instance mode, it was primarily created as a way to transform Processing Java Mode syntax in JS syntax so it can run on browsers.

Most folks don’t even realize that when they host a Java Mode sketch in sites like OpenProcessing.org, Pjs is the technology running it behind-the-scenes.

So as long as we’re still using Processing’s Java Mode, the abandoned Pjs is the only viable way to automatically convert basic Java syntax to JS.

Apart from having multi-flavor compatibility, the most tech advise for a Java developer trying out JS is to code in TS (TypeScript):

Which is basically JS w/ enforced datatype at developing time.

Also split the code in JS modules via keywords import & export:

The file “ball.ts” below uses TS language together w/ keywords import & export so it can recognize the p5 & Chamber datatypes:

Thanks for that. I was aware of TypeScript but not looked at it in any detail yet. I would be interested in knowing what programming IDE you use with TypeScript and any advise on organising “Processing / TypeScript / Library development” environment would be appreciated.

As to Pjs, I used it quite a lot when applets went the way of the dodo :smiling_face_with_tear:

1 Like
  • Formally I used Atom.io w/ some extensions for TS.
  • But currently I’m in VSC, which got good TS support out-of-the-box.
  • I also have both NodeJs.org & Git installed to deal w/ NPM packages & GitHub repos.

Well, I’ve ended up creating a p5js library template repo just to answer that:

After unpacking the downloaded project file:

  • Double-click file “setup.bat” so it creates the subfolder “node_modules/” and globally installs the TS compiler “tsc”, the local file server “serve”, the p5js library itself, plus its type definitions; all that based on “package.json” contents.
  • That template project has a “tsconfig.json” file which instructs “tsc” to automatically compile every TS file inside subfolder “src/” and output them into subfolder “dist/”; plus their type definitions into subfolder “typings/”.
  • In this particular template example I have a class Ball defined in file “dist/ball.mts” which is gonna act as a p5js library addon.
  • Also a helper “dist/attribute.mts” file which is imported by “dist/ball.mts”.
  • FYI, “.mts” is a new file extension which explicitly defines it as an ECMAScript Module: 16. Modules
  • The “tsc” compiler will automatically output “.mts” files as “.mjs” btW:
  • The last subfolder to discuss is “tests/”, which contains 2 other subfolders “regular/” & “import/”.
  • In total there are 4 “.js” files as test example sketches on how to consume the “dist/ball.mjs” library.
  • Subfolder “tests/regular/” got files “sketch.js” & “sketch.instance.js”.
  • “tests/regular/sketch.js” is a typical p5js sketch in global mode style.
  • And “tests/regular/sketch.instance.js” is like the former above, but using instance mode style instead.
  • It’s super important that a p5js library addon be compatible w/ both global & instance modes.
  • The other 2 sketches in subfolder “tests/import/” teach how to import the “dist/ball.mjs” library directly in code w/o using a <script> tag in an HTML file:
  • The “index.html” file loads both sketches from subfolder “tests/regular/”.
  • It adds the attribute “data-ball-auto-import” to the “dist/ball.mjs” <script> tag.
  • The library searches all <script> tags for that “data-ball-auto-import” attribute.
  • When it’s found, it automatically exposes its class Ball to globalThis.
  • And it also adds the alternative method createBall() to the p5 class.

That’s all for now! Hope it helps you to develop your g4js addon library in TS using ES6 modules:

1 Like

For further demoing my library template I’ve moved all 6 sketches from subfolder “tests/” to Glitch.com.

Now in order for those 6 sketches to remotely consume the library module “ball.mjs” they need to use a CDN link for it:
https://cdn.JsDelivr.net/gh/GoToLoop/P5JS-Library-Template/dist/ball.mjs

Those 6 sketches are split into 3 folders: “regular/”, “import/”, “module/”.

Each of those 3 folders contain 2 sketches: “sketch.global.js” & “sketch.instance.js”.

Global mode sketches interact w/ p5js & its addon libraries as if they were available in the global context, just like Processing Java Mode sketches.

Instance mode sketches gotta instantiate the p5js library before having access to its API.

Also each member of the p5js library is only accessible via that instantiated object.

And addon libraries need to require that instantiated object in order to interact w/ the p5js library as well.

The 2 sketches inside “regular/” folder are the common cases on how sketches use p5js addon libraries.

That is, they import both the p5js library & any needed 3rd-party libraries via <script> tags inside an HTML file:

<script defer src=https://cdn.JsDelivr.net/npm/p5></script>

<script
  data-ball-auto-import
  type=module
  src=https://cdn.JsDelivr.net/gh/GoToLoop/P5JS-Library-Template/dist/ball.mjs>
</script>

<script defer src=sketch.global.js></script>
<script defer src=sketch.instance.js></script>

The 2 sketches inside “import/” demo how to import a 3rd-party module library if they happen to be an ECMA module script.

For that they use import() in order to dynamically import module libraries:

(async () => {
  const { exposeBallConstructors } = await import(
    "https://cdn.JsDelivr.net/gh/GoToLoop/P5JS-Library-Template/dist/ball.mjs"
  );

  exposeBallConstructors(); // class Ball & method p5::createBall()

  globalThis.setup = setup;
  globalThis.draw  = draw;

  new p5;
})();

The 2 sketches inside “module/” folder also use import to get module libraries.

However b/c they’re also ECMA module scripts they don’t need to import them dynamically.

It’s much faster & easier to simply get them statically instead:

import { exposeBallConstructors }
from "https://cdn.JsDelivr.net/gh/GoToLoop/P5JS-Library-Template/dist/ball.mjs";

I hope your G4js library is being written in a way which can be consumed by both global & instance mode p5js sketches.

That is, your library should request a p5js instance object instead of expecting it to be always available within the global context.

Check out class Ball’s constructor() from my pseudo library “ball.mjs” to see how it’s done:

And given you know Java so well you should consider coding your library in TypeScript in order to have type checks on the IDE:

And of course, also consider organizing your library as ECMAScript “.mjs” modules, so it resembles how Java library works.

Anyways, here’s the online demo which remotely consumes “ball.mjs” via a CDN link:

You can download the full project by clicking on this link. :link:

1 Like

I’ve taken the time to check the actual G4js source code and unfortunately it’s incompatible w/ p5js instance mode.

That is, G4js can’t be used w/ multiple sketches in the same page unless they’re wrapped up w/ <iframe> tags.

That also keeps out folks who rather prefer coding using p5js instance mode style.

BtW, Shiffman got a YouTube video explaining better how to use it and why use it:

For example the method _renderP2D() from class GPane, p5js library API methods like push(), pop(), translate(), noStroke(), fill(), rect() are all accessed as if they were guaranteed to be always available from the global context, which isn’t the case when p5js is on instance mode:

  _renderP2D() {
    push()
    translate(this._x, this._y)
    if (this._visible && this._tabstate != 'closed') {
      let _cs = this._localScheme || g4js.scheme()
      noStroke()
      fill(_cs.black4)
      rect(0, 0, this._w, this._h)
    }
    for (let c of this._children)
      if (c._visible) c._renderP2D()
    pop()
  }

The class GPane extends GObject; so in order to fix it, we’ll have to fix GObject 1st:

class GObject {
  static corner = 4

  constructor(name, x, y, w, h) {
    this._name = name
    this._children = []
    this._parent = undefined
    this._x = x
    this._y = y
    this._w = w
    this._h = h
    this._visible = true
    this._enabled = true
    this._localScheme
  }

  // ...
}

For that we just need to add this extra optional parameter to its constructor:
p = p5.instance.

When p5js is on global mode the static property p5.instance holds its reference.

By setting the parameter p optional, global mode users won’t need to pass their sketch’s reference if they choose so.

That would be mandatory for instance mode users only; which is fair:

class GObject {
  static CORNER = 4

  _children = []

  constructor(name, x, y, w, h, p = p5.instance) {
    this._p = p
    
    this._name = name

    this._x = x
    this._y = y
    this._w = w
    this._h = h

    this._parent = this._localScheme = null
    this._visible = this._enabled = true
  }

  // ...
}

With the fix above, class GObject keeps a p5js reference on its property _p, which will be used to access any p5js API from now on.

We’ll have to do the same for your subclass GPane here:

class GPane extends GObject {
  // ...

  constructor(name, x, y, w, h) {
    super(name, x, y, w, h)
    this._status = 'closed'
    this._timer
    this._sortZ = 128
  }
  
  // ...

  _renderP2D() {
    push()
    translate(this._x, this._y)
    if (this._visible && this._tabstate != 'closed') {
      let _cs = this._localScheme || g4js.scheme()
      noStroke()
      fill(_cs.black4)
      rect(0, 0, this._w, this._h)
    }
    for (let c of this._children)
      if (c._visible) c._renderP2D()
    pop()
  }

  // ...
}

This is my refactoring of it:

class GPane extends GObject {
  // ...

  _status = 'closed'
  _timer = 0
  _sortZ = 128

  constructor(name, x, y, w, h, p = p5.instance) {
    super(name, x, y, w, h, p)
  }

  // ...

  _renderP2D() {
    const {
      _p: p,

      _x: x, _y: y,
      _w: w, _h: h,

      _visible, _tabstate, _localScheme, _children
    } = this

    p.push()
    p.translate(x, y)

    if (_visible && _tabstate != 'closed') {
      const { black4 } = _localScheme || g4js.scheme()
      p.noStroke().fill(black4).rect(0, 0, w, h)
    }

    for (const child of _children)  child._visible && child._renderP2D()

    p.pop()
  }

  // ...
}

Now we have statements like:

  • p.push()
  • p.translate(x, y)
  • p.noStroke().fill(black4).rect(0, 0, w, h)
  • p.pop()

Which work regardless your library’s user is on p5js global or instance mode. :smiley:

BtW, this wiki got more tips for creating p5js library addons: :nerd_face:

1 Like

@GoToLoop thanks for all this information - a lot to get through and all of it helpful.

I was never a fan of Javascript when it first came out but it has matured a lot since then. I can see both the benefits and disadvantages over strongly typed languages such as Java and C++ and I am enjoying the learning experience. TypeScript will obviously help overcome some of the disadvantages so is on my todo list.

My learning style is to identify projects that interest me then learn the language i.e. Javascript as I implement them. This GUI library provides many opportunities to learn both JS syntax and more advanced features like closures.

Since I am implementing it as part of my learning experience I am in no hurry to finish this library and other project ideas might get slipped in along the way :grin: Also there already appears to be a GUI library for p5.js (although I haven’t tried it out) so its not filling a void.

1 Like