tornadofx icon indicating copy to clipboard operation
tornadofx copied to clipboard

Implementing a split screen view for a workspace?

Open SKeeneCode opened this issue 4 years ago • 17 comments

Is it possible to implement a split screen view for a workspace? It would require the ability for the workspace to dock on either the left/right side, depending on which side has the user focus. This is with the workspace using tabs:

image

I've tried to think of a good way to do this, but to be honest I can't even come up with a bad way to do it. Any pointers? Or am I ultimately stuck on writing my own workspace implementation?

SKeeneCode avatar Jun 29 '20 19:06 SKeeneCode

https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/SplitPane.html

SchweinchenFuntik avatar Jun 30 '20 06:06 SchweinchenFuntik

https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/SplitPane.html

Apologies I think I could have been more clear.

I meant when docking a view onto a workspace (or, clicking between tabs which does the same thing), instead of replacing the entire workspace with the docked view, only replace the left or right section, so I might have:

WorkSpace::dockInNewScopeLeftSide and WorkSpace::dockInNewScopeRightSide

And changing the user focus between the left and right side will load/deload the appropriate workspace buttons/drawers/drawer items etc associated with that view, and switch to the appropriate tab above.

The docs say:

Forwarding button state and actions As we have seen, the currently docked View controls the Workspace buttons. Some times you dock nested Views inside the main View, and you would like that nested View to control the buttons and actions instead. This can easily be done with the forwardWorkspaceActions function. You can change the forwarding however you see fit, for example on focus or on click on some component inside the nested View.

'Some times you dock nested Views inside the main View' That is what I suppose I want, The main view would be the split pane, and the nested views would be the left and right side. However I suspect this line is referring to simply an add() call considering the example directly below it.

SKeeneCode avatar Jun 30 '20 08:06 SKeeneCode

there is no such support.

This is not very difficult to do, but integrating with Workspace is already more difficult.

Any ideas for @edvin @ctadlock ?

SchweinchenFuntik avatar Jun 30 '20 15:06 SchweinchenFuntik

Yeah looking through the workspace code I can't see a way to get what I want without writing a custom Workspace implementation, which I'll give a go and see what I can get at.

SKeeneCode avatar Jun 30 '20 21:06 SKeeneCode

I had a go and realized that the way to implement it that I desired (a single line of tabs controlling two content containers) isn't the way most software actually does it. Intellij for example just adds in another tab pane next to the original that behaves seemingly completely independently from the other. Since the tab panes container seems nondetachable from the tab header without some dirty hacks I will drop the former idea.

SKeeneCode avatar Jul 04 '20 05:07 SKeeneCode

TiwulFx has something like this already implemented, i am using it in some places in my TornadoFX Apps.

https://bitbucket.org/panemu/tiwulfx/src/master/

Detachable Pane Demo: https://www.youtube.com/watch?v=q_n23Ah1ftQ

(i am not the author)

oemergenc avatar Oct 29 '20 04:10 oemergenc

Thanks @oemergenc I spent a few days understanding/modifying the dockable classes as well as rewriting a lot of TornadoFX's workspace class and got it working nicely:

image

SKeeneCode avatar Nov 06 '20 15:11 SKeeneCode

Wow, very nice. Any chance to get this shared? 😃

oemergenc avatar Nov 06 '20 22:11 oemergenc

Sure, although some things to note:

  • I butchered/removed the stack implementation of the workspace as I was only interested in tab navigation myself and wanted to keep things simple.
  • DockableTabPanes now take in an observable list of themselves so elsewhere listeners can be attached/detached. The workspace has a observable list tabPanes for this.
  • Workspace no longer just has a dockedComponent, but rather a list of dockedComponents which handles calling the callOnDock and callOnUndock when UIComponents are added/removed.
  • A consequence of the above is that all visible tabs are listening on the event bus (as they have had their callOnDock called).
  • The workspace has a selectedComponent property to keep track of what is the current focused tab, and ensures the workspace buttons are forwarded to this tab.

You can find the changes here: https://github.com/SKeeneCode/tornadofx2

SKeeneCode avatar Nov 07 '20 17:11 SKeeneCode

Thanks for sharing. I played around with it and it seems to work very nice. Great work 👍🏻

Any chances to get this into the next snapshot version of tornado2? I think it is a very useable feature and a lot people would be happy to use it.

Maybe it would also be worth to integrate this in tornado1 as long as there is no final release of tornado2? In my application the TiwulFx implementation worked well with java 8.

I would also be happy to contribute. If possible, how?

Cheers

oemergenc avatar Nov 08 '20 09:11 oemergenc

I'll be happy to merge a PR, provided it doesn't break any existing functionality :)

edvin avatar Nov 08 '20 09:11 edvin

I would have to re-implement the Stack implementation. There is some opinionated decisions on what should happen when switching navigation modes (which is what I got stuck on and decided to just tear it all out).

  • Is it desirable for the workspace while in tab mode, to keep track the order tabs have been viewed in, so when it switches there is a history for the stack navigation buttons?
  • Should switching to stack navigation automatically dock what was the currently selected tab item?
  • Should switching from the stack navigation to the tab navigation dock all of the histories components in the tab pane?=

If we don't worry about preserving view history/tabs apart from maybe the most recent then it would be relatively simple I think.

  • Would want to write quite a number of TestFX tests to ensure coverage.
  • Let the dock function take in a boolean to enable/disable detaching the new tab, as well as potential convenience functions to enable/disable the functionality for the entire workspace. (Perhaps a DetachableMode enum for the constructor?)

SKeeneCode avatar Nov 08 '20 11:11 SKeeneCode

I don't think there is any need to track the tab view order. I think it's better to add them to the stack from left to right as they appear in the tab pane.

Docking the currently selected tab sounds like a good idea.

Switching from stack to tab could dock all the currently available tabs, that sounds like what a user might expect. This would keep some symmetry in going the other way as well.

The DetachableMode could either be introduced now, or better yet, introduced when/if the need arises.

edvin avatar Nov 08 '20 12:11 edvin

Switching from stack to tab could dock all the currently available tabs

Can you clarify on what you mean by this?

SKeeneCode avatar Nov 08 '20 13:11 SKeeneCode

Sorry, horrible formulation on my part :) If you switch from stack to tab, then all the entries in the stack should be converted to tabs IMO. The tab order should be the same as the stack order, and the view on top of the stack should be the active tab.

edvin avatar Nov 08 '20 14:11 edvin

Understood, I'll have a crack at it over the next day or two.

SKeeneCode avatar Nov 08 '20 14:11 SKeeneCode

So played around for 8 hours.. most of which is trying to fix a bug or two. One of which I've opened in issue over in tornadofx2.

I've hit a problem when it comes to docking the list of components in the viewStack.

If we take the existing workspace implementation and change the navigation change listener to this:

    private fun navigationModeChanged(oldMode: NavigationMode?, newMode: NavigationMode?) {
        if (oldMode == null || oldMode != newMode) {
            contentContainer = if (navigationMode == Stack) stackContainer else tabContainer
            root.center = contentContainer
            if (contentContainer == stackContainer && tabContainer.tabs.isNotEmpty()) {
                tabContainer.tabs.clear()
            }
            dockedComponent?.also {
                dockedComponentProperty.value = null
                dock(it, false) // this was changed from true to false
            }
        }
        if (newMode == Stack) {
            if (backButton !in root.header.items) {
                root.header.items.add(0, backButton)
                root.header.items.add(1, forwardButton)
            }
        } else {
            viewStack.toList().forEach { dock(it) } // this was added
            root.header.items -= backButton
            root.header.items -= forwardButton
        }
    }

The result is this:

image

As it docks each component that components dynamic components (in this case, the "p" button) are added and they are not removed as further components are added. I've tried undocking the component immediately after docking it. I've tried clearDynamicComponents everywhere I can think of and nothing seems to get rid of it.

The rest of basically implemented but I'm stuck on this problem.

If anyone has any ideas I'd love to learn :)

SKeeneCode avatar Nov 09 '20 20:11 SKeeneCode