tornadofx icon indicating copy to clipboard operation
tornadofx copied to clipboard

How to swap fragments?

Open ctadlock opened this issue 5 years ago • 6 comments

What is the best way to swap fragments based on properties?

I discovered you can use the managedWhen and visibleWhen together. However it doesnt work on a Fragment; need to wrap in a Pane. Is that by design, or are we missing something for Fragment.

Is there a better way to do this?

pane {
    add(XXXFragment::class, mapOf(XXXFragment::message to "Connect an NFC device...", XXXFragment::graphic to Styles.noNfcDeviceIcon)) {
        managedWhen(visibleProperty())
        visibleWhen(controller.noDevice)
    }
    add(XXXFragment::class, mapOf(XXXFragment::message to "Touch an NFC tag to the device...", XXXFragment::graphic to Styles.noNfcTagIcon)) {
        managedWhen(visibleProperty())
        visibleWhen(controller.noTag)
    }
}

This just seems janky...

    pane {
        pane {
            add(XXXFragment::class, mapOf(XXXFragment::message to "Connect an NFC device...", XXXFragment::graphic to Styles.noNfcDeviceIcon))
            managedWhen(visibleProperty())
            visibleWhen(controller.noDevice)
        }
        
        pane {
            add(XXXFragment::class, mapOf(XXXFragment::message to "Touch an NFC tag to the device...", XXXFragment::graphic to Styles.noNfcTagIcon))
            managedWhen(visibleProperty())
            visibleWhen(controller.noTag)
        }
    }

ctadlock avatar Apr 24 '20 07:04 ctadlock

@edvin thoughts here?

ctadlock avatar Apr 28 '20 16:04 ctadlock

I'll use the following two simple fragments in my discussion below:

class F1 : Fragment("F1") {
    override val root = label(title)
}

class F2 : Fragment("F2") {
    override val root = label(title)
}

managedWhen and visibleWhen operates on Node, and since the Fragment itself isn't a Node, you must either wrap it or control the root node of the Fragments. Here is a simple example where I use removeWhen for simplicity:

val state = SimpleBooleanProperty(false)

override val root = hbox {
    button("Swap state").action {
        state.value = !state.value
    }
    add<F1> {
        root.removeWhen(state)
    }
    add<F2> {
        root.removeWhen(state.not())
    }
}

The removeWhen expression will operate on the closest node it can find in the hierarchy, so wrapping in a Pane or any other Node is an effective way of making a container which only purpose is to act as a placeholder to control visibility:

pane {
    add<F1> {
        removeWhen(state)
    }
}
pane {
    add<F2> {
        removeWhen(state.not())
    }
}

I don't think this is janky, and it is very well defined and understood what actually happens. Be sure to use the sexy syntax version of add though :)

edvin avatar Apr 29 '20 06:04 edvin

@edvin can add an extension function to stdlib? fun UCcomponent.removeWhen(state: BooleanProperty) { root.removeWhen(state) }

if you do not mind, then I will do PR

SchweinchenFuntik avatar Apr 30 '20 04:04 SchweinchenFuntik

Absolutely, that will remove some confusion I guess, as one would most probably assume that the function was available on the UIComponent itself.

edvin avatar Apr 30 '20 07:04 edvin

Thanks. It took me a while to realize the extension method was for nodes and fragments are not nodes. I was getting crazy behavior.

ctadlock avatar Apr 30 '20 08:04 ctadlock

I've often tried to hide that fact, by making UIComponents integrate just like they would if they were actual Nodes, so that's my fault :) It's convenient to be able to think of them as Nodes, so adding extension functions like the one above makes sense. We have plenty of them already also.

edvin avatar Apr 30 '20 11:04 edvin