Support the Project! |
---|
This project is open-source and free to use, both commercially and non-commercially, which is why we need your help in its development. If you like it, please give it a star ⭐ on GitHub — it helps others discover the project and increases its visibility. You can also contribute, for example, by fixing bugs 🐛 or suggesting improvements 💡, see Contributing. If you can, financial support 💰 is always appreciated, see Support Us. Thank you! |
- Overview
- Demo
- Features
- Requirements
- Modules
- Dependencies
- Usage
- Code building
- Running Demos
- License
- Contributing
- 👉 Support Us
Techsenger TabShell is a lightweight platform for building tab-based applications in JavaFX using the MVVM pattern.
The platform consists of two parts: the core and ready-made components. The core includes the core shell and classes for creating components. Ready-made components are used as needed and significantly reduce the development time of the final application.
Key features of TabShell include:
- Abstract classes to simplify component development.
- Dynamically configurable menu.
- Ability to preserve component history.
- Support for dialogs with two scopes — shell and tab.
- Window styling that matches the theme.
- Support for 7 themes (4 dark and 3 light).
- Styling with CSS.
Currently, TabShell contains the following ready-made components:
- Terminal.
- Text Viewer/Editor.
- Dialogs.
The library requires Java 17 or later. Due to some bugs, use JavaFX versions 19–20, or a version of JavaFX after 24-ea+19 (see JDK-8344372).
The platform consists of the following modules:
- Material — provides UI elements (menus, text areas, etc.) and supporting classes.
- Core — includes the shell itself, base classes for component development, settings, and core utility classes. If you don't plan to use ready-made components, just two modules (material and core) are sufficient to run TabShell and develop custom components. See Core Demo for details.
- Tabs — offers abstract components for creating tabs with various layouts.
- Storage — contains classes to interact with storage systems that are not natively recognized by the operating system (such as Google Drive, Dropbox, FTP, and similar). At the same time, the module only includes implementations for working with the OS's default storage systems.
- Dialogs — provides ready-to-use dialogs: alert, file chooser, confirmation etc.
- Text — contains text viewer and editor components.
- Terminal — includes a terminal emulator component.
- Registrars — provides default registrars (for menu items, etc.).
- Icons — contains the Material Design Icons font and module-specific stylesheets that utilize these icons. To use custom icons instead, simply create your own stylesheets and add them to Shell.
- Core Demo — showcases TabShell's core functionality and provides examples for building custom components. This demo only requires the material and core modules.
- Full Demo — showcases the complete platform with all components. This comprehensive demo uses all modules.
This project is available on Maven Central.
To get started with TabShell, it is recommended to follow these steps:
- Familiarize yourself with the mvvm4fx framework and its sampler.
- Explore and run core demo. See Core Demo for details.
- Explore and run full demo. See Full Demo for details.
The component is the main building block for creating an application using this platform. There are the following types of components:
- Shell component.
- Tab component.
- Dialog component.
- Page component, which represents a titled component that can be selected.
- Pane component, which represents a rectangular area.
- Node component, which is used for the simplest and smallest elements.
When working with components, there are a few key points to remember:
-
A component is initialized manually (by calling the
initialize()
method) and deinitialized automatically (by calling thedeinitialize()
method). This is because the developer may need to perform certain actions with the component after initialization but before passing it to the parent component. -
The following components can be closed:
Shell
,ShellTab
,Tab
,Dialog
. Each of these components has arequestClose()
method in itsViewModel
and aclose()
method in itsView
. In all components, when theViewModel#requestClose()
method is called, it triggers theView#close()
method via a listener.
Shell
is the main component and it is responsible for the following tasks:
- Window management.
- Dynamic menu management.
- Shell tab management.
- Shell-scoped dialog management.
- Theme management.
Shell
core doesn't have any business logic. It is only a shell for tabs that contain logic.
Working with the main menu of the Shell
is carried out in two directions:
- Configuring menu elements
- Managing the state of elements and responding to user actions
The configuration of menu elements is performed dynamically and in any order, with the final result being unknown in advance. This feature is crucial in cases where plugins/extensions are used, as they can be added/removed dynamically by the user. Each plugin may introduce its own menu items and interact with existing menus. Therefore, it is impossible to predict the final structure of the menu that the user will work with.
The implementation of this feature is structured as follows. There are three key elements: the menu, the group, and the
item. Each element has its own key, which is used for identification. A menu consists of groups separated by a
separator. Items are added to groups, and empty groups are ignored. All three elements are registered/unregistered in
the ControlRegistry
. When the menu needs to be updated, this ControlRegistry
is used by Shell
to construct
the final menu.
The MenuManager
is responsible for managing the state of menu elements and responding to their actions. It interacts
with a component that implements the MenuAware
interface. This interface is always implemented by both Shell
and
ShellTab
. If all tabs are closed, MenuManager
interacts with Shell
. When tabs are present, MenuManager
interacts with the currently selected tab.
It is also important to remember that the MenuManager
also interacts with MenuAware when the user uses accelerators.
To gain a complete understanding of working with the menu, it is recommended to familiarize yourself with the
MenuAware
interface, experiment with the menu in the demo, and pay attention to log messages at the debug level.
Regarding Shell
closure, it should be noted that as the top-level component (i.e., having no parent component),
Shell
is unique in self-managing its own closure process (whereas all other components are closed by their
parent components).
There are two types of tabs: ShellTab
and Tab
. A ShellTab
component can be opened through the Shell
, so
ShellTab
components are second-level components under the Shell
. ShellTab
manages tab-scoped dialogs.
A Tab
component cannot be opened directly through the Shell
, so it always resides inside a ShellTab
.
The ShellTab
a Tab
components are closed in the following way. When the View#close()
method is called, control is
transferred to their parent component (e.g., Shell
, TabManager
), which is responsible for their actual closure.
Thus, these tabs can also be closed directly through their parent if a reference to the tab is available.
The tab closing procedure is largely determined by the asynchronous nature of dialogs in the TabShell project and consists of the following steps:
- The parent component calls the tab's
boolean View#doOnCloseAttempt(CloseScope, Runnable)
method. - By default, this method calls two ViewModel methods:
ViewModel#isReadyToClose()
andViewModel#prepareForClose(CloseScope, Runnable)
:
boolean onCloseAttempt(CloseScope scope, Runnable retryCallback) {
if (getViewModel().isReadyToClose()) {
return true;
} else {
getViewModel().prepareForClose(scope, retryCallback);
return false;
}
}
- If
ViewModel#prepareForClose(CloseScope, Runnable)
successfully prepares the component for closure, it invokes the provided callback to restart the closing process. If preparation fails, the closing process is silently aborted.
All dialogs in TabShell are inline
, asynchronous
and have a scope
that affects what will be blocked when the
dialog is open.
Inline dialogs are UI elements that appear embedded within the current application window, typically overlaid on top of the existing content with a semi-transparent backdrop to focus attention. They are contextually tied to a specific section (e.g., a tab or component) and do not create a separate OS-level window. In contrast, [modal] window dialogs (or native dialogs) open as standalone OS-managed windows with their own frames and system controls, completely independent of the parent UI.
Asynchronous dialogs allow the program to continue running while the dialog is open, relying on callbacks, promises, or event listeners. These avoid UI freezes and enable background tasks but require handling user responses indirectly, often via lambda functions or observable states. Synchronous dialogs, conversely, block the application's execution flow until the user responds, pausing all other interactions (e.g., showAndWait() in JavaFX). They simplify code logic by enforcing a linear sequence but risk freezing the UI during operation. The key distinction lies in control flow: asynchronous dialogs prioritize responsiveness, deferring action until the user completes the interaction, while synchronous ones enforce immediate resolution. Modern UI design increasingly favors async approaches for scalability and user experience.
There are two types of scope: Shell
and Tab
. If a dialog has a Shell
scope, the user will not be able to do
anything in Shell
while this dialog is displayed until it is closed. If a dialog has a Tab
scope, only the
tab that triggered the dialog will be blocked when it is displayed. All other tabs, the main menu, etc., will be
available to the user.
Dialogs are invoked from the ViewModel
using ComponentHelper
.
The Dialog
component is closed in the following way. When the View#close()
method is called on a Dialog
, control
is delegated to the DialogManager
, which handles its actual closure. Therefore, a Dialog
can also be closed
directly through the DialogManager
when holding a reference to the dialog instance.
To build the library use standard Git and Maven commands:
git clone https://github.com/techsenger/tabshell
cd tabshell
mvn clean install
The project provides two demo applications:
- Core Demo — showcases TabShell basics (material and core modules) including shell operation and custom component development.
- Full Demo — demonstrates the complete platform with all components, featuring pre-built tabs, dialogs, text editor, terminal emulator, and other ready-to-use components.
To run the demo, execute the following commands in the project root:
cd tabshell-demos/tabshell-demos-core
mvn javafx:run
Please note, that debugger settings are in pom.xml
file.
To run the demo, execute the following commands in the project root:
cd tabshell-demos/tabshell-demos-full
mvn javafx:run
Please note, that debugger settings are in pom.xml
file.
Techsenger TabShell is licensed under the Apache License, Version 2.0.
We welcome all contributions. You can help by reporting bugs, suggesting improvements, or submitting pull requests with fixes and new features. If you have any questions, feel free to reach out — we’ll be happy to assist you.
You can support us financially through GitHub Sponsors. Your contribution directly helps us keep our open-source projects active, improve their features, and offer ongoing support. Besides, we offer multiple sponsorship tiers, with different rewards.