From 88ec6277b967a2d1a683c5a0ce03eec23337e166 Mon Sep 17 00:00:00 2001 From: dave Date: Sat, 9 Nov 2024 16:34:43 +0000 Subject: [PATCH] Version of Embedded Java App that has a custom panel. --- java/embeddedJavaDeviceUI/README.md | 19 +- java/embeddedJavaDeviceUI/pom.xml | 2 +- .../devicedemo}/EmbeddedJavaDemoApp.java | 23 ++- .../EmbeddedJavaDemoController.java | 8 +- .../devicedemo}/EmbeddedJavaDemoMenu.java | 15 +- .../devicedemo}/MenuConfig.java | 26 +-- .../devicedemo/optional}/JfxLocalAutoUI.java | 65 +++++- .../optional}/LocalTreeComponentManager.java | 27 ++- .../optional/StatusPanelDrawable.java | 185 ++++++++++++++++++ .../optional}/TcJettyWebServer.java | 2 +- .../optional}/TcJettyWebSocketEndpoint.java | 2 +- .../devicedemo/optional/UpdatablePanel.java | 15 ++ .../src/main/java/module-info.java | 4 +- .../target/classes/application.properties | 2 +- 14 files changed, 352 insertions(+), 43 deletions(-) rename java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/{menuexample/tcmenu => menu/devicedemo}/EmbeddedJavaDemoApp.java (68%) rename java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/{menuexample/tcmenu => menu/devicedemo}/EmbeddedJavaDemoController.java (94%) rename java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/{menuexample/tcmenu => menu/devicedemo}/EmbeddedJavaDemoMenu.java (91%) rename java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/{menuexample/tcmenu => menu/devicedemo}/MenuConfig.java (81%) rename java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/{menuexample/tcmenu/plugins => menu/devicedemo/optional}/JfxLocalAutoUI.java (69%) rename java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/{menuexample/tcmenu/plugins => menu/devicedemo/optional}/LocalTreeComponentManager.java (60%) create mode 100644 java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/StatusPanelDrawable.java rename java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/{menuexample/tcmenu/plugins => menu/devicedemo/optional}/TcJettyWebServer.java (99%) rename java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/{menuexample/tcmenu/plugins => menu/devicedemo/optional}/TcJettyWebSocketEndpoint.java (97%) create mode 100644 java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/UpdatablePanel.java diff --git a/java/embeddedJavaDeviceUI/README.md b/java/embeddedJavaDeviceUI/README.md index 8d7e66c..717a2a4 100644 --- a/java/embeddedJavaDeviceUI/README.md +++ b/java/embeddedJavaDeviceUI/README.md @@ -1,14 +1,14 @@ # EmbeddedJavaDemo menu application -This application is an example of how to use tcMenu's EmbedControl to produce a basic user interface that would run on an embedded device such as a raspberry PI. The application is composed of objects that are wired together in the main class. This elimiates the need for any kind of dependency framework. Should you wish to, you can use our very light weight database objects that are also used within tcMenu itself. +This application is an example of how to use tcMenu's EmbedControl to produce a basic user interface that would run on an embedded device such as a raspberry PI. The application is composed of objects that are wired together in the main class. We also include a really cut down dependency framework that is fully JPMS compliant and very small. Should you wish to, you can use our very light weight database objects that are also used within tcMenu itself. ## How the app is organised. The application is split up into several files: -* EmbeddedJavaDemoApp - this is the class that starts up your application and initialises any plugins you selected. You should not touch this class as it is overwritten every time around. -* EmbeddedJavaDemoMenu - this is the class that holds all the menu definitions and the menu tree. It is available in the spring context, and you can inject it into any of your components that need it. It also has short-cut methods to get every menu item. -* EmbeddedJavaDemoController - this is where any callbacks that you register go, at the moment we support only one controller, in future we may provide support for more than one. Each function callback that you declare in TcMenu Designer will turn into a method in here. This also allows you to listen for any menu item, and for start and stop events. Further, you can change the controller's constructor to include other components if needed. +* `EmbeddedJavaDemoApp` - this is the class that starts up your application and initialises any plugins you selected. You should not touch this class as it is overwritten every time around. +* `EmbeddedJavaDemoMenu` - this is the class that holds all the menu definitions and the menu tree. It is available in the spring context, and you can inject it into any of your components that need it. It also has short-cut methods to get every menu item. +* `EmbeddedJavaDemoController` - this is where any callbacks that you register go, at the moment we support only one controller, in future we may provide support for more than one. Each function callback that you declare in TcMenu Designer will turn into a method in here. This also allows you to listen for any menu item, and for start and stop events. Further, you can change the controller's constructor to include other components if needed. ## Building the app @@ -16,16 +16,19 @@ By default, the app uses maven to build, you'll need a couple of things installe * Java - Get the most recent version for your platform, the platform must support JavaFX if you're using the UI components, this is nearly all distributions of Linux I've seen, macOS and Windows. * A recent maven 3 installation. Maven is a very complete build tool and [you can read more about it here](https://maven.apache.org/guides/getting-started/). -* A Java IDE - we mainly use IntelliJ, but have tried the project in Visual-Studio-Code too. Eclipse similarly should work very well with this project. +* A Java IDE - we recommend IntelliJ, but have tried the project in Visual-Studio-Code too. Eclipse similarly should work very well with this project. * To build from the command line ensure you are in the same directory as this README file and type `mvn clean install`, which will build the application and bring down any dependencies. ## Running the application from the CLI If you use the standard maven setup, after running the above build steps, you should see the following directory has been created: `target/jfx/` containing an `app` directory and a `deps` directory. We recommend running the application from the `target/jfx/app` directory. -If you used a modular build (IE you have a `module-info.java` file in the `src/main/java` directory) then to run the application ensure that the right version of Java using `java -version` is on your path and then the run command should be `java --module-path ../deps "-Dprism.lcdtext=false" --add-modules com.thecoderscorner.menuexample.embeddedjavademo com.thecoderscorner.menuexample.tcmenu.EmbeddedJavaDemoApp` +If you used a modular build (IE you have a `module-info.java` file in the `src/main/java` directory) then to run the application ensure that the right version of Java using `java -version` is on your path and then the run command should be `java --module-path ../deps "-Dprism.lcdtext=false" --add-modules com.thecoderscorner.menuexample.embeddedjavademo com.thecoderscorner.menu.devicedemo.EmbeddedJavaDemoApp` -## Plugins +## JavaFx local UI -Depending on which plugins you chose to install, there will be other files that are associated with those in the source tree, these are separated out into a `plugins` directory. We've provided a few documentation links below to get you started: +In the optional folder, there's a couple of classes that will generate a JavaFX UI automatically for your UI. These are documented within the folder. +## Web server and TagVal server + +There's both a webserver that serves up [EmbedControlJS](https://github.com/TcMenu/embedcontrolJS) to a browser, and also TagVal support too, so you connect to it from the API. diff --git a/java/embeddedJavaDeviceUI/pom.xml b/java/embeddedJavaDeviceUI/pom.xml index 2bc8c4f..faa96b0 100644 --- a/java/embeddedJavaDeviceUI/pom.xml +++ b/java/embeddedJavaDeviceUI/pom.xml @@ -9,7 +9,7 @@ 23.0.1 2.11.0 - 4.4.0 + 4.4.1-SNAPSHOT ${maven.build.timestamp} diff --git a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/EmbeddedJavaDemoApp.java b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/EmbeddedJavaDemoApp.java similarity index 68% rename from java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/EmbeddedJavaDemoApp.java rename to java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/EmbeddedJavaDemoApp.java index 638e338..176b43b 100644 --- a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/EmbeddedJavaDemoApp.java +++ b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/EmbeddedJavaDemoApp.java @@ -1,4 +1,4 @@ -package com.thecoderscorner.menuexample.tcmenu; +package com.thecoderscorner.menu.devicedemo; import com.thecoderscorner.menu.mgr.MenuInMenu; import com.thecoderscorner.menu.mgr.MenuManagerServer; @@ -7,15 +7,16 @@ import com.thecoderscorner.menu.remote.LocalIdentifier; import com.thecoderscorner.menu.remote.MenuCommandProtocol; import com.thecoderscorner.menu.remote.socket.SocketBasedConnector; -import com.thecoderscorner.menuexample.tcmenu.plugins.JfxLocalAutoUI; -import com.thecoderscorner.menuexample.tcmenu.plugins.TcJettyWebServer; +import com.thecoderscorner.menu.devicedemo.optional.JfxLocalAutoUI; +import com.thecoderscorner.menu.devicedemo.optional.TcJettyWebServer; import javafx.application.Application; import java.time.Clock; import java.util.concurrent.ScheduledExecutorService; /** - * This class is the application class and should not be edited, it will be recreated on each code generation + * This class is the application class that is run when the application starts. You can organize the application in + * here into a series of components. This is and example of how to arrange such an application. */ public class EmbeddedJavaDemoApp { private final MenuManagerServer manager; @@ -29,13 +30,23 @@ public EmbeddedJavaDemoApp() { } public void start() { + // Firstly we get the menu state serializer, this can load and save menu state var serializer = context.getBean(MenuStateSerialiser.class); serializer.loadMenuStatesAndApply(); + // save states when the app shuts down Runtime.getRuntime().addShutdownHook(new Thread(serializer::saveMenuStates)); + // the controller receives updates and things happen on the menu, we register it here. manager.addMenuManagerListener(context.getBean(EmbeddedJavaDemoController.class)); + // See the method for more information. buildMenuInMenuComponents(); + + // Give the Local UI access to teh context JfxLocalAutoUI.setAppContext(context); + + // here we start a webserver, it is initialised in the config file. manager.addConnectionManager(webServer); + + // and finally, start the local JavaFX UI. Application.launch(JfxLocalAutoUI.class); } @@ -43,6 +54,10 @@ public static void main(String[] args) { new EmbeddedJavaDemoApp().start(); } + /// + /// here we demonstrate how to include menu in menu components. You can use tcMenu Designer to generate the menu in + /// menu components and add them here, it can build the entire method for you from `Code -> Menu In Menu` + /// public void buildMenuInMenuComponents() { MenuManagerServer menuManager = context.getBean(MenuManagerServer.class); MenuCommandProtocol protocol = context.getBean(MenuCommandProtocol.class); diff --git a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/EmbeddedJavaDemoController.java b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/EmbeddedJavaDemoController.java similarity index 94% rename from java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/EmbeddedJavaDemoController.java rename to java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/EmbeddedJavaDemoController.java index 442a10c..d6adafd 100644 --- a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/EmbeddedJavaDemoController.java +++ b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/EmbeddedJavaDemoController.java @@ -1,7 +1,6 @@ -package com.thecoderscorner.menuexample.tcmenu; +package com.thecoderscorner.menu.devicedemo; import com.thecoderscorner.embedcontrol.core.service.GlobalSettings; -import com.thecoderscorner.embedcontrol.customization.MenuItemStore; import com.thecoderscorner.embedcontrol.jfx.controlmgr.JfxNavigationHeader; import com.thecoderscorner.embedcontrol.jfx.controlmgr.JfxNavigationManager; import com.thecoderscorner.embedcontrol.jfx.controlmgr.TitleWidget; @@ -38,16 +37,13 @@ public class EmbeddedJavaDemoController implements MenuManagerListener { private final JfxNavigationManager navigationManager; private final ScheduledExecutorService executorService; private final GlobalSettings globalSettings; - private final MenuItemStore itemStore; public EmbeddedJavaDemoController(EmbeddedJavaDemoMenu menuDef, JfxNavigationManager navigationManager, - ScheduledExecutorService executorService, GlobalSettings settings, - MenuItemStore itemStore) { + ScheduledExecutorService executorService, GlobalSettings settings) { this.menuDef = menuDef; this.navigationManager = navigationManager; this.executorService = executorService; this.globalSettings = settings; - this.itemStore = itemStore; } @MenuCallback(id=15, listResult=true) diff --git a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/EmbeddedJavaDemoMenu.java b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/EmbeddedJavaDemoMenu.java similarity index 91% rename from java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/EmbeddedJavaDemoMenu.java rename to java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/EmbeddedJavaDemoMenu.java index 1ff2372..f822b01 100644 --- a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/EmbeddedJavaDemoMenu.java +++ b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/EmbeddedJavaDemoMenu.java @@ -1,9 +1,22 @@ -package com.thecoderscorner.menuexample.tcmenu; +package com.thecoderscorner.menu.devicedemo; import com.thecoderscorner.menu.domain.*; import com.thecoderscorner.menu.domain.state.MenuTree; import com.thecoderscorner.menu.persist.JsonMenuItemSerializer; +/** + * The EmbeddedJavaDemoMenu class initializes a menu tree with predefined menu items + * and provides access to this tree and its JSON serializer. You can get an instance of + * this class from TcMenu Designer from menu: `Code -> Produce Java Menu Class`. + * + * In most applications you'd rename this class to something more suitable for your + * application, EG in an audio amplifier it may be called `AmplifierSettingsMenu`. You + * can refactor the class name in IntelliJ. + * + * This menu system can include various types of menu items such as analog, + * boolean, text items, etc. Each item in the menu is defined in the APP_MENU_ITEMS string + * in JSON format, which is then deserialized and loaded into a MenuTree object. + */ public class EmbeddedJavaDemoMenu { private final static String APP_MENU_ITEMS = """ tcMenuCopy:[ diff --git a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/MenuConfig.java b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/MenuConfig.java similarity index 81% rename from java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/MenuConfig.java rename to java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/MenuConfig.java index 9672652..511ba42 100644 --- a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/MenuConfig.java +++ b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/MenuConfig.java @@ -1,4 +1,4 @@ -package com.thecoderscorner.menuexample.tcmenu; +package com.thecoderscorner.menu.devicedemo; import com.thecoderscorner.embedcontrol.core.service.GlobalSettings; import com.thecoderscorner.embedcontrol.core.util.BaseMenuConfig; @@ -15,7 +15,7 @@ import com.thecoderscorner.menu.persist.PropertiesMenuStateSerialiser; import com.thecoderscorner.menu.persist.VersionInfo; import com.thecoderscorner.menu.remote.protocol.ConfigurableProtocolConverter; -import com.thecoderscorner.menuexample.tcmenu.plugins.TcJettyWebServer; +import com.thecoderscorner.menu.devicedemo.optional.TcJettyWebServer; import java.nio.file.Path; import java.time.Clock; @@ -23,11 +23,11 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -/** - * This class creates an application context out of all these components, and you can request any components that are - * put into the context using getBean(ClassName.class). See the base class BaseMenuConfig for more details. Generally - * don't change the constructor, as it is rebuild each time around. Prefer putting your own code in appCustomConfiguration - */ +/// This class creates an application context out of all these components, and you can request any components that are +/// put into the context using getBean(ClassName.class). See the base class `BaseMenuConfig` for more details. +/// In summary, every function annotated with `@TcComponent` will be added to the "context" and you can then request +/// it later using `getBean`. Consider it a lightweight spring that is JPMS module system compliant. Anything that is +/// marked as a `TcComponent` will be available automatically to other methods marked `TcComponent`. public class MenuConfig extends BaseMenuConfig { @TcComponent public JfxNavigationHeader navMgr(ScheduledExecutorService executorService, GlobalSettings settings) { @@ -90,16 +90,16 @@ public TcJettyWebServer webServer(ConfigurableProtocolConverter protocol, Clock @TcComponent public EmbeddedJavaDemoController controller(EmbeddedJavaDemoMenu menuDef, JfxNavigationManager navMgr, - ScheduledExecutorService executor, GlobalSettings settings, - MenuItemStore itemStore) { - return new EmbeddedJavaDemoController(menuDef, navMgr, executor, settings, itemStore); + ScheduledExecutorService executor, GlobalSettings settings) { + return new EmbeddedJavaDemoController(menuDef, navMgr, executor, settings); } public MenuConfig() { - // Do not change this constructor, it is replaced with each build, put your objects in appCustomConfiguration super(null, null); - Clock clock = asBean(Clock.systemUTC()); - var executorService = asBean(Executors.newScheduledThreadPool(propAsIntWithDefault("threading.pool.size", 4))); + // put a clock and thread pool into the "context" + asBean(Clock.systemUTC()); + asBean(Executors.newScheduledThreadPool(propAsIntWithDefault("threading.pool.size", 4))); + // add the menu tree into the context var menuDef = asBean(new EmbeddedJavaDemoMenu()); asBean(new PropertiesMenuStateSerialiser(menuDef.getMenuTree(), Path.of(resolvedProperties.getProperty("file.menu.storage")).resolve("menuStorage.properties"))); scanForComponents(); diff --git a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/plugins/JfxLocalAutoUI.java b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/JfxLocalAutoUI.java similarity index 69% rename from java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/plugins/JfxLocalAutoUI.java rename to java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/JfxLocalAutoUI.java index 295c90e..ec52a83 100644 --- a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/plugins/JfxLocalAutoUI.java +++ b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/JfxLocalAutoUI.java @@ -1,16 +1,26 @@ -package com.thecoderscorner.menuexample.tcmenu.plugins; +package com.thecoderscorner.menu.devicedemo.optional; +import com.thecoderscorner.embedcontrol.core.controlmgr.EditorComponent; import com.thecoderscorner.embedcontrol.core.controlmgr.MenuComponentControl; +import com.thecoderscorner.embedcontrol.core.controlmgr.color.ConditionalColoring; +import com.thecoderscorner.embedcontrol.core.controlmgr.color.ControlColor; +import com.thecoderscorner.embedcontrol.core.service.GlobalSettings; import com.thecoderscorner.embedcontrol.core.util.MenuAppVersion; +import com.thecoderscorner.embedcontrol.customization.ColorCustomizable; +import com.thecoderscorner.embedcontrol.customization.GlobalColorCustomizable; import com.thecoderscorner.embedcontrol.customization.MenuItemStore; +import com.thecoderscorner.embedcontrol.jfx.controlmgr.JfxMenuEditorFactory; import com.thecoderscorner.embedcontrol.jfx.controlmgr.JfxNavigationHeader; import com.thecoderscorner.embedcontrol.jfx.controlmgr.JfxNavigationManager; import com.thecoderscorner.embedcontrol.jfx.controlmgr.panels.AuthIoTMonitorPresentable; import com.thecoderscorner.menu.auth.MenuAuthenticator; import com.thecoderscorner.menu.auth.PropertiesAuthenticator; +import com.thecoderscorner.menu.devicedemo.EmbeddedJavaDemoMenu; +import com.thecoderscorner.menu.devicedemo.MenuConfig; import com.thecoderscorner.menu.domain.MenuItem; import com.thecoderscorner.menu.domain.state.ListResponse; import com.thecoderscorner.menu.domain.state.MenuTree; +import com.thecoderscorner.menu.domain.state.PortableColor; import com.thecoderscorner.menu.domain.util.MenuItemHelper; import com.thecoderscorner.menu.mgr.DialogManager; import com.thecoderscorner.menu.mgr.MenuManagerServer; @@ -18,7 +28,6 @@ import com.thecoderscorner.menu.remote.commands.DialogMode; import com.thecoderscorner.menu.remote.commands.MenuDialogCommand; import com.thecoderscorner.menu.remote.protocol.CorrelationId; -import com.thecoderscorner.menuexample.tcmenu.MenuConfig; import javafx.application.Application; import javafx.application.Platform; import javafx.geometry.Insets; @@ -35,6 +44,11 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicReference; +/** + * This is the local UI plugin, it provides a local UI that will by default render your menu tree onto the display using + * Java FX. The default UI can be overridden by adding custom panels to the navigationHeader, see below where we've done + * this for one of the submenu items. + */ public class JfxLocalAutoUI extends Application { private static final AtomicReference GLOBAL_CONTEXT = new AtomicReference<>(null); @@ -43,6 +57,8 @@ public class JfxLocalAutoUI extends Application { private LocalDialogManager dlgMgr; private MenuAppVersion versionData; private LocalTreeComponentManager localTree; + private EmbeddedJavaDemoMenu menuTree; + private GlobalSettings globalSettings; public static void setAppContext(MenuConfig context) { GLOBAL_CONTEXT.set(context); @@ -52,8 +68,10 @@ public static void setAppContext(MenuConfig context) { public void start(Stage stage) { var ctx = GLOBAL_CONTEXT.get(); mgr = ctx.getBean(MenuManagerServer.class); + menuTree = ctx.getBean(EmbeddedJavaDemoMenu.class); var executor = ctx.getBean(ScheduledExecutorService.class); versionData = ctx.getBean(MenuAppVersion.class); + globalSettings = ctx.getBean(GlobalSettings.class); dlgMgr = new LocalDialogManager(); var auth = ctx.getBean(MenuAuthenticator.class); @@ -70,9 +88,12 @@ public void start(Stage stage) { var localController = new LocalMenuController(); navigationHeader = ctx.getBean(JfxNavigationHeader.class); + var factory = new JfxMenuEditorFactory(localController, Platform::runLater, dlgMgr); + navigationHeader.addCustomMenuPanel(menuTree.getStatus(), new StatusPanelDrawable(menuTree, executor, factory, + localController, mgr, new CondColorFromGlobal(globalSettings))); navigationHeader.initialiseUI(dlgMgr, localController, scroller); - localTree = new LocalTreeComponentManager(mgr, navigationHeader); + localTree = new LocalTreeComponentManager(mgr, navigationHeader, executor); mgr.start(); navigationHeader.pushMenuNavigation(MenuTree.ROOT, ctx.getBean(MenuItemStore.class)); @@ -200,4 +221,42 @@ protected void dialogDidChange() { } } } + + private class CondColorFromGlobal implements ConditionalColoring { + private final ColorCustomizable colorCustomizable; + public CondColorFromGlobal(GlobalSettings globalSettings) { + colorCustomizable = new GlobalColorCustomizable(globalSettings); + } + + private ControlColor getControlColor(EditorComponent.RenderingStatus status, ColorComponentType compType) { + if (status == EditorComponent.RenderingStatus.RECENT_UPDATE) compType = ColorComponentType.CUSTOM; + else if (status == EditorComponent.RenderingStatus.EDIT_IN_PROGRESS) compType = ColorComponentType.PENDING; + else if (status == EditorComponent.RenderingStatus.CORRELATION_ERROR) compType = ColorComponentType.ERROR; + + return switch (compType) { + case TEXT_FIELD -> globalSettings.getTextColor(); + case BUTTON -> globalSettings.getButtonColor(); + case HIGHLIGHT -> globalSettings.getHighlightColor(); + case CUSTOM -> globalSettings.getUpdateColor(); + case DIALOG -> globalSettings.getDialogColor(); + case ERROR -> globalSettings.getErrorColor(); + case PENDING -> globalSettings.getPendingColor(); + }; + } + + @Override + public PortableColor foregroundFor(EditorComponent.RenderingStatus status, ColorComponentType compType) { + return getControlColor(status, compType).getFg(); + } + + @Override + public PortableColor backgroundFor(EditorComponent.RenderingStatus status, ColorComponentType compType) { + return getControlColor(status, compType).getBg(); + } + + @Override + public ControlColor colorFor(EditorComponent.RenderingStatus status, ColorComponentType ty) { + return getControlColor(status, ty); + } + } } diff --git a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/plugins/LocalTreeComponentManager.java b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/LocalTreeComponentManager.java similarity index 60% rename from java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/plugins/LocalTreeComponentManager.java rename to java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/LocalTreeComponentManager.java index be05562..b15751f 100644 --- a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/plugins/LocalTreeComponentManager.java +++ b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/LocalTreeComponentManager.java @@ -1,4 +1,4 @@ -package com.thecoderscorner.menuexample.tcmenu.plugins; +package com.thecoderscorner.menu.devicedemo.optional; import com.thecoderscorner.embedcontrol.jfx.controlmgr.JfxMenuPresentable; import com.thecoderscorner.embedcontrol.jfx.controlmgr.JfxNavigationManager; @@ -7,13 +7,22 @@ import com.thecoderscorner.menu.mgr.MenuManagerServer; import javafx.application.Platform; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Goes with the local UI, this handles changes on behalf of the local UI in both directions. You can add additional + * functionality as needed here. + */ public class LocalTreeComponentManager implements MenuManagerListener { private final MenuManagerServer menuMgr; private final JfxNavigationManager navigationManager; + private final ScheduledExecutorService executor; - public LocalTreeComponentManager(MenuManagerServer menuMgr, JfxNavigationManager navigationManager) { + public LocalTreeComponentManager(MenuManagerServer menuMgr, JfxNavigationManager navigationManager, ScheduledExecutorService executor) { this.menuMgr = menuMgr; this.navigationManager = navigationManager; + this.executor = executor; menuMgr.addMenuManagerListener(this); @@ -21,6 +30,16 @@ public LocalTreeComponentManager(MenuManagerServer menuMgr, JfxNavigationManager // update all non sub menu items as the tree has structurally changed menuItemHasChanged(null, null); }); + + executor.scheduleAtFixedRate(this::tickProc, 100L, 100L, TimeUnit.MILLISECONDS); + } + + private void tickProc() { + Platform.runLater(() -> { + if(navigationManager.currentNavigationPanel() instanceof UpdatablePanel panel) { + panel.tickAll(); + } + }); } @Override @@ -32,6 +51,10 @@ public void menuItemHasChanged(Object sender, MenuItem item) { } else { menuPanel.getGridComponent().itemHasUpdated(item); } + } else if(navigationManager.currentNavigationPanel() instanceof UpdatablePanel panel) { + if(item != null) { + panel.itemHasUpdated(item); + } } }); } diff --git a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/StatusPanelDrawable.java b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/StatusPanelDrawable.java new file mode 100644 index 0000000..39a7fac --- /dev/null +++ b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/StatusPanelDrawable.java @@ -0,0 +1,185 @@ +package com.thecoderscorner.menu.devicedemo.optional; + +import com.thecoderscorner.embedcontrol.core.controlmgr.*; +import com.thecoderscorner.embedcontrol.core.controlmgr.color.ConditionalColoring; +import com.thecoderscorner.embedcontrol.customization.FontInformation; +import com.thecoderscorner.embedcontrol.customization.FontInformation.SizeMeasurement; +import com.thecoderscorner.embedcontrol.customization.customdraw.NumberCustomDrawingConfiguration; +import com.thecoderscorner.menu.devicedemo.EmbeddedJavaDemoMenu; +import com.thecoderscorner.menu.domain.MenuItem; +import com.thecoderscorner.menu.mgr.MenuManagerServer; +import javafx.geometry.HPos; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; + +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static com.thecoderscorner.embedcontrol.core.controlmgr.EditorComponent.PortableAlignment; +import static com.thecoderscorner.embedcontrol.core.controlmgr.EditorComponent.RenderingStatus.NORMAL; +import static com.thecoderscorner.embedcontrol.core.controlmgr.color.ConditionalColoring.ColorComponentType.TEXT_FIELD; +import static com.thecoderscorner.embedcontrol.core.controlmgr.color.ControlColor.asFxColor; +import static com.thecoderscorner.embedcontrol.core.controlmgr.color.ControlColor.fromFxColor; +import static com.thecoderscorner.embedcontrol.customization.customdraw.CustomDrawingConfiguration.NO_CUSTOM_DRAWING; +import static com.thecoderscorner.embedcontrol.customization.customdraw.CustomDrawingConfiguration.NumericColorRange; + +/// Demonstrates how to create your own panel to be presented instead of an automatic menu panel. Simply +/// register this panel with the navigation manager class and it will be presented instead of the standard +/// panel. See `JfxLocalAutoUI` where this panel is added. Any panel that implements `UpdatablePanel` will +/// automatically be told when menu items have updated, and be provided with a tick function for animations. +public class StatusPanelDrawable implements PanelPresentable, UpdatablePanel { + private final EmbeddedJavaDemoMenu menuDef; + private final MenuEditorFactory editorFactory; + private final MenuComponentControl componentControl; + private final ConditionalColoring globalColors; + private final ScheduledExecutorService executor; + private final MenuManagerServer manager; + private final HashMap> controlsBeingManaged = new HashMap<>(); + private double presentableWidth = 999; + private GridPane gridPane; + private double lastWidth = 999; + + public StatusPanelDrawable(EmbeddedJavaDemoMenu menuDef, ScheduledExecutorService executor, + MenuEditorFactory factory, MenuComponentControl control, + MenuManagerServer manager, ConditionalColoring globalColors) { + this.menuDef = menuDef; + this.executor = executor; + this.editorFactory = factory; + this.componentControl = control; + this.manager = manager; + this.globalColors = globalColors; + } + + @Override + public Node getPanelToPresent(double width) throws Exception { + lastWidth = width; + if (gridPane != null) { + // empty it if it already exists to make GC easier + gridPane.getChildren().clear(); + gridPane.getColumnConstraints().clear(); + gridPane.getRowConstraints().clear(); + } + gridPane = makeGridPanel(); + presentableWidth = width; + return gridPane; + } + + private GridPane makeGridPanel() { + // here we create a JavaFX grid using the regular way of doing so. You can consult the JavaFx documentation + // for more on how to create grids. + gridPane = new GridPane(); + gridPane.setHgap(5); + gridPane.setVgap(5); + gridPane.setMaxWidth(9999); + gridPane.setPrefWidth(presentableWidth); + gridPane.getChildren().clear(); + + // the grid will be 4 across by three down. + gridPane.getColumnConstraints().clear(); + gridPane.getRowConstraints().clear(); + gridPane.setBackground(new Background(new BackgroundFill( + asFxColor(globalColors.colorFor(NORMAL, TEXT_FIELD).getBg()), null, null + ))); + gridPane.getRowConstraints().add(new RowConstraints(80)); + gridPane.getRowConstraints().add(new RowConstraints(20)); + gridPane.getRowConstraints().add(new RowConstraints(20)); + for (int i = 0; i < 4; i++) { + var cc = new ColumnConstraints(10, presentableWidth / 4, 9999, Priority.SOMETIMES, HPos.CENTER, true); + gridPane.getColumnConstraints().add(cc); + } + + // now we add some labels into the grid on the left. + gridPane.add(new Label("Case Temperature"), 0, 0); + gridPane.add(new Label("Light Color"), 0, 1); + gridPane.add(new Label("Authenticator"), 0, 2); + + gridPane.add(new Label("Start Simulating"), 2, 0); + var runSimButton = new Button("Run Sim"); + runSimButton.setOnAction(event -> executor.scheduleAtFixedRate(this::updateTemp, 200L, 200L, TimeUnit.MILLISECONDS)); + gridPane.add(runSimButton, 2, 1); + + // and now we add in a component that will render using the VU meter style. It is a float item, and we provide + // custom drawing configuration for it, so it has three ranges, green, orange, red. + FontInformation font100Pc = new FontInformation(100, SizeMeasurement.PERCENT); + var greenOrangeRed = new NumberCustomDrawingConfiguration(List.of( + new NumericColorRange(0.0, 70.0, fromFxColor(Color.GREEN), fromFxColor(Color.WHITE)), + new NumericColorRange(70.0, 90.0, fromFxColor(Color.ORANGE), fromFxColor(Color.WHITE)), + new NumericColorRange(90.0, 100.0, fromFxColor(Color.RED), fromFxColor(Color.LIGHTGRAY)) + ), "vuColors"); + putIntoGrid(menuDef.getStatusCaseTempOC(), new ComponentSettings( + globalColors, font100Pc, PortableAlignment.CENTER, + new ComponentPositioning(0, 1), RedrawingMode.SHOW_VALUE, ControlType.VU_METER, + greenOrangeRed, true) + ); + + putIntoGrid(menuDef.getStatusIoTMonitor(), new ComponentSettings( + globalColors, font100Pc, PortableAlignment.RIGHT, + new ComponentPositioning(2, 1), RedrawingMode.SHOW_VALUE, ControlType.AUTH_IOT_CONTROL, + NO_CUSTOM_DRAWING, true)); + + putIntoGrid(menuDef.getStatusLightColor(), new ComponentSettings( + globalColors, font100Pc, PortableAlignment.RIGHT, + new ComponentPositioning(1, 1), RedrawingMode.SHOW_VALUE, ControlType.RGB_CONTROL, + NO_CUSTOM_DRAWING, true)); + + return gridPane; + } + + private void updateTemp() { + manager.updateMenuItem(this, menuDef.getStatusCaseTempOC(), Math.random() * 100); + manager.updateMenuItem(this, menuDef.getLED1Brightness(), Math.random() * 100 ); + } + + private void putIntoGrid(MenuItem item, ComponentSettings componentSettings) { + ComponentPositioning pos = componentSettings.getPosition(); + var component = editorFactory.getComponentEditorItem(item, componentSettings, this::noAction); + component.ifPresent(comp -> { + controlsBeingManaged.put(item.getId(), comp); + gridPane.add(comp.createComponent(), pos.getCol(), pos.getRow(), pos.getColSpan(), pos.getRowSpan()); + }); + } + + private void noAction(MenuItem menuItem) { + } + + @Override + public String getPanelName() { + if (componentControl == null) return "empty"; + return "Status Panel"; + } + + @Override + public boolean canBeRemoved() { + return true; + } + + @Override + public boolean canClose() { + return true; + } + + @Override + public void closePanel() { + controlsBeingManaged.clear(); + } + + @Override + public void itemHasUpdated(MenuItem item) { + if(controlsBeingManaged.containsKey(item.getId())) { + controlsBeingManaged.get(item.getId()).onItemUpdated(item, manager.getManagedMenu().getMenuState(item)); + } + } + + @Override + public void tickAll() { + for(var component : controlsBeingManaged.values()) { + component.tick(); + } + } + +} diff --git a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/plugins/TcJettyWebServer.java b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/TcJettyWebServer.java similarity index 99% rename from java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/plugins/TcJettyWebServer.java rename to java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/TcJettyWebServer.java index 5ab3676..459d535 100644 --- a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/plugins/TcJettyWebServer.java +++ b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/TcJettyWebServer.java @@ -1,4 +1,4 @@ -package com.thecoderscorner.menuexample.tcmenu.plugins; +package com.thecoderscorner.menu.devicedemo.optional; import com.thecoderscorner.menu.mgr.NewServerConnectionListener; import com.thecoderscorner.menu.mgr.ServerConnection; diff --git a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/plugins/TcJettyWebSocketEndpoint.java b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/TcJettyWebSocketEndpoint.java similarity index 97% rename from java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/plugins/TcJettyWebSocketEndpoint.java rename to java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/TcJettyWebSocketEndpoint.java index e524de7..23b99df 100644 --- a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menuexample/tcmenu/plugins/TcJettyWebSocketEndpoint.java +++ b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/TcJettyWebSocketEndpoint.java @@ -1,4 +1,4 @@ -package com.thecoderscorner.menuexample.tcmenu.plugins; +package com.thecoderscorner.menu.devicedemo.optional; import com.thecoderscorner.menu.mgr.MenuManagerServer; import com.thecoderscorner.menu.mgr.ServerConnection; diff --git a/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/UpdatablePanel.java b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/UpdatablePanel.java new file mode 100644 index 0000000..bea629f --- /dev/null +++ b/java/embeddedJavaDeviceUI/src/main/java/com/thecoderscorner/menu/devicedemo/optional/UpdatablePanel.java @@ -0,0 +1,15 @@ +package com.thecoderscorner.menu.devicedemo.optional; + +import com.thecoderscorner.menu.domain.MenuItem; + +/// Represents a `PanelPresentable` that can be updated when menu items change, it is also provided with a +/// tick function so that the implementor can tick down updates that occur. When an item updates the update +/// will be sent through the `itemHasUpdated` method, and you will be on the JavaFx thread when it occurs. +public interface UpdatablePanel { + /// called every 100 millis by the framework so that you can tick any animations that are in progress. + void tickAll(); + + /// called whenever there is a menu item update so that the display can be updated. + /// @param item the item that has updated + void itemHasUpdated(MenuItem item); +} diff --git a/java/embeddedJavaDeviceUI/src/main/java/module-info.java b/java/embeddedJavaDeviceUI/src/main/java/module-info.java index a04427c..bee6187 100644 --- a/java/embeddedJavaDeviceUI/src/main/java/module-info.java +++ b/java/embeddedJavaDeviceUI/src/main/java/module-info.java @@ -10,8 +10,8 @@ requires javafx.base; requires javafx.controls; requires javafx.fxml; - exports com.thecoderscorner.menuexample.tcmenu.plugins; - opens com.thecoderscorner.menuexample.tcmenu; + exports com.thecoderscorner.menu.devicedemo.optional; + opens com.thecoderscorner.menu.devicedemo; requires org.eclipse.jetty.server; requires org.eclipse.jetty.servlet; diff --git a/java/embeddedJavaDeviceUI/target/classes/application.properties b/java/embeddedJavaDeviceUI/target/classes/application.properties index b08d608..083947f 100644 --- a/java/embeddedJavaDeviceUI/target/classes/application.properties +++ b/java/embeddedJavaDeviceUI/target/classes/application.properties @@ -2,7 +2,7 @@ build.version=0.0.1-SNAPSHOT build.groupId=com.thecoderscorner.menuexample build.artifactId=embeddedJavaDeviceUI -build.timestamp=2024-10-27T17:13:50Z +build.timestamp=2024-11-09T16:19:17Z # server name properties server.name=Embedded Java Demo