Custom control using another control defined in the Babylon GUI Editor

Hello again!

I have a question regarding the construction of a custom control using another control defined in the Babylon GUI Editor. I hope this question hasn’t been answered before. I tried searching for it, but I would appreciate it if you could provide a link if it has been addressed previously.

My initial problem was that I wanted to have a custom ScrollViewer that I created in the GUI editor, including its children and settings. However, when parsing the .json file from the editor, I wanted to load it as a ScrollViewer with an ImageBasedSlider instead of a regular Slider.

I know that you can achieve this in code by adding “true” when constructing a new ScrollViewer, like this:

const myScrollViewer = new BABYLON.GUI.ScrollViewer("", true);

So, I thought I could recreate it by copying everything from the old ScrollViewer and adding the required images somehow like this:

private prepareScrollViewers(scrollViewers: Control[]): void {
    scrollViewers.forEach((scrollViewer) => {
       // Copy properties from scrollViewerOld to scrollViewerImageBased
       for (const prop in scrollViewerOld) {
         if (scrollViewerOld.hasOwnProperty(prop)) {
           scrollViewerImageBased[prop] = scrollViewerOld[prop];
         }
       }

     // Set specific properties for the image-based ScrollViewer
     scrollViewerImageBased.thumbImage = new Image("thumb", "./assets/sprites/SVG/scroll_thumb.svg");
     scrollViewerImageBased.barImage = new Image("bar", "./assets/sprites/SVG/scroll_bar.png");
    });
}

However, this approach didn’t work because it also copied the “don’t use imageBasedSlider” option. So, I tried to copy only some settings, which partially worked. The problem arose with all the children of the old ScrollViewer, as I struggled to set them as children of the new control and properly assign the new control as their parent using addControl, but I failed in my attempts.

Eventually, I decided to abandon the idea and use the non-imageBased ScrollViewer instead.

However, today I wanted to make the ScrollViewer work on mobile devices by enabling “touch anywhere to scroll” functionality throughout the entire ScrollViewer. The scroll bar was too small, and it made sense to allow users to swipe anywhere on the ScrollViewer to scroll it.

I found this amazing person who solved it by creating a custom ScrollViewer. You can find their solution in this forum post: Link to the forum post

They also provided a demo of the custom ScrollViewer in action, which you can find here: Link to the demo

Now, the problem resurfaces. I would like to define all my GUI using the .json file in the GUI editor, but I would also like to convert all the ScrollViewer controls in my project to use this customScrollViewer.

Is there a way to construct, copy, or duplicate a control from a .json file in the code to create a custom control with the same settings, children, and parents?

Thank you for any advice and for taking the time to read my post! :blush:

Hi! :smiley:
I think a “straightforward” approach to this would be to load the JSON/Snippet from the editor, then grab the control you want to replace by a custom, copy its children and properties over, and delete the original: demo CustomScrollViewer | Babylon.js Playground (babylonjs.com) (I did with a snippet instead of a JSON here, but the idea is the same)

When I was trying to implement this in my project, I got this error:

UIManager.ts:925 Uncaught (in promise) TypeError: Class constructor ScrollViewer cannot be invoked without 'new'
 at new CustomScrollViewer (UIManager.ts:925)
 at UIManager.ts:809

Where line 925 is:

super(name, isImageBased);

and 809 is:

const custom = new CustomScrollViewer({camera, scene, ui_layer}, "custom", false);

I did import ScrollViewer from BABYLON.GUI. I’m not sure what I did wrong, but I will be happy to add more information from my side or for any tips to test. :blush:

I copied everything from your example only added false in the call for the new Scroll and added “as ScrollViewer” and “as TextBlock” when loading those parsed Controls.

How are you consuming Babylon? CDN, npm, etc…? What do you imports look like? If you can share a reproduction of how you’re doing things in your project it would be helpful :smile:

1 Like

We are using BabylonJS Editor v4.7.0 and in node_modules I have these folders:
IMG

In JS script I’m using for this GUI editing I have these imports:

import { FreeCamera, Scene, Vector3, ILoadingScreen } from "@babylonjs/core";

import { AdvancedDynamicTexture, Button, Image, TextBlock, Control, Container, Rectangle, ScrollViewer, ImageScrollBar, ImageBasedSlider } from "@babylonjs/gui";


import "@babylonjs/core/Debug/debugLayer";
import "@babylonjs/inspector";

The @babylonjs folder contains these: FOLDER

I copied the whole thing you linked me even with your testing snippet to test it.
Script part that is not working: SCRIPT SNIPPET
And the class CustomScrollViewer extends ScrollViewer {} is also just copied.

Hope this helps. I can provide more info, I’m just new to this and I wasn’t sure, what you needed. :blush:

EDIT: I know this is old demo we are talking about and there is also a problem with camera.wheelPrecision not existing (I guess it worked last year). I commented those parts with camera and scroll wheel away. I only needed the swiping funciton.

Hmmm, unfortunately I don’t have experience with the Babylon Editor since it’s a community project, so if it’s something specific to it I might not detect. But from what you shared, it looks like your imports are mixing ES6 (the “@babylonjs/*” packages) and UMD packages (the babylonjs-gltf2interface), which isn’t good because they work in different ways. So I recommend sticking only to one set of packages. I’m not sure which one the Editor is expecting. @julien-moreau is the one who will know :slight_smile:

I tried doing the same thing you did but without the editor, using this starter template RaananW/babylonjs-webpack-es6: Babylon.js basic scene with typescript, webpack, es6 modules, editorconfig, eslint, hot loading and more. Will even make coffee if you ask nicely. (github.com) to organize the project, and it did work, so it might be something specific to the editor.

You were right about problem between BabylonJS packages being ready for ES6 but the editor building it as ES5. So I decided to not use super and only call empty constructor or none and put the setup of function into custom Init call after…
After more refactoring I came up with different solution, that only helps with the swipe to slide in ScrollViewer so I wanted to share it for anybody looking at this in the future. I tried to make it work for both vertical and horizontal scrolling.

// Here load your scroll from .json or create a new one. Whatever you need.
const scrollViewerCustom = scrollViewer as ScrollViewer;

var isDragging = false;

var startDragX = 0;
var startDragY = 0;

var startScrollX = 0;
var startScrollY = 0;

var sensitivity = 0.01; // Adjust this value to control scroll sensitivity

scrollViewerCustom.onPointerDownObservable.add(function (event) {
    if (event.buttonIndex === undefined || event.buttonIndex === 0) {
        isDragging = true;
        startDragX = event.x;
        startDragY = event.y;

        startScrollX = scrollViewerRetyped.horizontalBar.value;
        startScrollY = scrollViewerRetyped.verticalBar.value;

    }
});

scrollViewerCustom.onPointerMoveObservable.add(function (event) {
    if (isDragging) {
        var dragDeltaX = (event.x - startDragX) * sensitivity;
        scrollViewerRetyped.horizontalBar.value = startScrollX - dragDeltaX;

        var dragDeltaY = (event.y - startDragY) * sensitivity;
        scrollViewerRetyped.verticalBar.value = startScrollY - dragDeltaY;
    }
});

scrollViewerCustom.onPointerUpObservable.add(function () {
    isDragging = false;
});

// Add these two lines if your content is blocking the swiping - or add it for othet content in your scrollViewer
var children = scrollViewerCustom.children[0] as Control; 
children.isPointerBlocker = false; // Prevent children from cunsuming pointer events

Anyway thanks for your help. You did the solution right I only was using in the wrong enviroment. :blush:

1 Like

Glad you found a solution that works and thanks for sharing it with the community! :smiley:

1 Like