Currently I've prototyped bits and pieces with Tex...
# tornadofx
k
Currently I've prototyped bits and pieces with Text, TextFlow, parts with RichTextFx...
m
For something like that I think you are going to have to use the JavaFX canvas (javafx.scene.canavas) and use the Affine transformations to manage zooming, clipping, etc.
You might be able to use an anchor pane and move elements around on it
But an anchor pane is likely a dead end since I expect it would have some limitations you couldn't overcome (e.g. zooming/clipping)
k
Looking at the
Canvas
right now, it's very low level, it's only a 2D drawing API, like Cairo, as far as I can see... if I use that, I'm basically making everything from nearly scratch? Layouting, editing of text, etc?
Btw, does rest of the JavaFX use Canvas for drawing it's controls?
m
Right, Canvas is 2D graphics but I think you can use it to draw controls such as labels on the canvas
I'm not sure how underlying nodes/controls are drawn in JavaFX
that's some info on how JavaFX works, I've read it before but didn't really care about the details under the "scene" level
looking at this, you can apply transforms to nodes which is what controls are built on, so you may be able to just drop some controls on a pane and then use the translate, scale, rotate, etc transforms on the underlying node
k
I've skimmed that article a few times, let's take it for a deep dive 🙂 thanks...
m
so I threw together a quick test and got this to work:
Copy code
class TestView : View("Test") {
    private var xOffset = 0.0
    private var yOffset = 0.0
    private var initialX = 0.0
    private var initialY = 0.0
    override val root = pane {
        prefHeight = 1000.0
        prefWidth = 1000.0
        label("Test") {
            setOnMousePressed {
                xOffset = it.screenX
                yOffset = it.screenY
                initialX = this.layoutX
                initialY = this.layoutY
            }
            setOnMouseDragged {
                this.layoutX = initialX + (it.screenX - xOffset)
                this.layoutY = initialY + (it.screenY - yOffset)
            }
            setOnScroll {
                val diff = it.deltaY.coerceIn(-2.0, 2.0)
                this.scaleX += diff
                this.scaleY += diff
            }
        }
    }
}
That will let me click and drag the label all over the pane and use the mouse wheel to "zoom" on it
k
Thanks a million for the example! After reading few articles, it seems that going the Canvas route would be too level (at least for now). It would be the best to go with a basic Pane, and Text/Label Nodes on top of it, with manual layouting - basically, exactly what your example is!
m
yeah if you are not drawing custom shapes I think using the pane is the way to go
k
Only one thing I'm not sure of - doing it this way, I think everything gets rendered all the time, event if most of the Scene is not visible... it wont be an issue for now, will think about it later
even with some custom shapes, I think Pane could work - I could do almost everything with Lines and Circles. Thanks again!
m
it looks to me like using the pane will handle clipping the drawing bounds
since it handles the transforms and scales and such
you could also style your labels to have round borders and such
k
cool, gonna test it out with a few million Nodes 🙂
m
good luck
k
Thanks!
m
some things worth mentioning: in order to have a fully working app, you will need to manage your coordinates so the element coordinates are independent of the graphics coordinates
I don't know how familiar you are with affine translations and coordinate systems but it's pretty standard for stuff like this
the elements have coordinates independent of how they are drawn so the affine transform is what handles translating between environment and graphics coordinates
k
I'm guessing it could be similar to what Cairo does, having local coordinates your work in, then applying transformations to position the drawings where you want them globally. That part should be okay, I was mostly looking where to start digging so that I don't go too low level and have to re-implement half of JavaFX...
m
yep that's it
you should be able to create a "MindPane" that inherits from Pane and add all your zoom and pan handler code there, and then create "MindElements" that inherit from Label, Text, etc that are added to MindPane
when you pan, you just translate all the children in the pane
when you zoom, you just scale all the children in the pane
individual handlers for each element can handle the click and drag to move them
actually, you don't scale or translate the children, you just apply a transform to the pane
k
yeah, effects whould just apply to the node and all the children?
m
yeah I think so, testing it out now
I've done 2D graphics with Java Swing but never with JavaFX
k
cool, I've done a few prototypes and it seems to be working... I did something similar with GTK, and didn't get too far...
m
and I'm working on something right now where I might want to do it in JavaFX so it's worth me trying out
yeah toolkits like GTK and Qt require lots of code, kotlin and tornadofx cuts way back on the total amount of code from what I've found
k
have to say, JavaFX seems like quite a nice toolkit! I have quite a few CRUD apps done with it, and it's pretty nice
m
yeah I like it, more so with tornadofx
k
and yeah, Kotlin+JavaFX is what really makes it tick... doing it all in Java would be kind of a chore
m
the binding stuff gets confusing at times but eventually I figure it out
k
GTK can be quite ok, I've used it from Haskell a lot... but still, there's impedence mismatch all the time
m
Copy code
class TestView : View("Test") {
    private var xOffset = 0.0
    private var yOffset = 0.0
    private var initialX = 0.0
    private var initialY = 0.0
    override val root = pane {
        prefHeight = 1000.0
        prefWidth = 1000.0
        label("Test") {
            setOnMousePressed {
                xOffset = it.screenX
                yOffset = it.screenY
                initialX = this.layoutX
                initialY = this.layoutY
                it.consume()
            }
            setOnMouseDragged {
                this.layoutX = initialX + (it.screenX - xOffset)
                this.layoutY = initialY + (it.screenY - yOffset)
                it.consume()
            }
        }
        setOnMousePressed {
            xOffset = it.screenX
            yOffset = it.screenY
        }
        setOnMouseDragged {
            this.translateX = it.screenX - xOffset
            this.translateY = it.screenY - yOffset
        }
        setOnScroll {
            val diff = it.deltaY.coerceIn(-0.5, 0.5)
            this.scaleX += diff
            this.scaleY += diff
        }
    }
}
that works for pan and zoom on the pane
kinda
k
I'm gonna test it out tomorrow, thanks! It's getting late in my part of the globe 🙂