playbook/skills/thirdparty/ui-ux-pro-max/data/stacks/javafx.csv

30 KiB

1NoCategoryGuidelineDescriptionDoDon'tCode GoodCode BadSeverityDocs URL
21ApplicationStart UI from Application subclassJavaFX apps should bootstrap the primary Stage through Application.start()Extend Application and configure Scene in start()Create UI from a random main method without launching JavaFXpublic class App extends Application { public void start(Stage stage) { stage.setScene(new Scene(root)); stage.show(); } }new Stage().show()Highhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/application/Application.html
32ThreadingKeep work off the FX Application ThreadLong-running work blocks rendering and input when executed on the UI threadUse Task or Service for background workRun network database or file work in button handlersTask<List<Item>> task = new Task<>() { protected List<Item> call() { return repo.load(); } }; new Thread(task).start();loadLargeFile(); table.setItems(items);Highhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/concurrent/Task.html
44ThreadingBind progress to background tasksTask exposes progress and message properties for responsive feedbackBind ProgressBar and Label to task propertiesPoll progress manually or leave users without feedbackprogress.progressProperty().bind(task.progressProperty()); status.textProperty().bind(task.messageProperty());while(running) progress.setProgress(x);Mediumhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/concurrent/Task.html
55FXMLUse FXML for stable declarative layoutsFXML keeps view structure readable for screens with many controlsPlace layout in FXML and behavior in controllerBuild large screens entirely in one Java method<VBox spacing="12" xmlns:fx="http://javafx.com/fxml" fx:controller="app.MainController">VBox root = new VBox(); root.getChildren().add(... 200 lines ...);Mediumhttps://openjfx.io/javadoc/21/javafx.fxml/javafx/fxml/FXMLLoader.html
66FXMLKeep controllers focused on view behaviorControllers should coordinate controls and delegate business logic to servicesInject services or call application services from controllerPut database queries and domain rules directly in controllerpublic void save() { customerService.save(form.toCommand()); }public void save() { DriverManager.getConnection(...); }Highhttps://openjfx.io/javadoc/21/javafx.fxml/javafx/fxml/FXML.html
710CSSUse design tokens through looked-up colorsLooked-up colors keep palettes consistent across controlsDefine named colors on root and reuse them in CSSRepeat hex values in every selector.root { -brand-primary: #2563eb; } .button.primary { -fx-background-color: -brand-primary; }.save { -fx-background-color: #2563eb; } .link { -fx-text-fill: #2563eb; }Mediumhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/doc-files/cssref.html
811CSSAvoid overusing inline effectsExpensive CSS effects and shadows can hurt desktop UI responsivenessUse subtle shadows only on important elevated surfacesApply blur drop shadow and glow to every node.dialog-card { -fx-effect: dropshadow(gaussian, rgba(0,0,0,.18), 16, 0, 0, 4); }.table-row-cell { -fx-effect: dropshadow(gaussian, black, 20, .5, 0, 0); }Mediumhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/effect/package-summary.html
912LayoutChoose layout panes by responsibilityEach pane solves a different layout problem and should be selected intentionallyUse BorderPane for app shell GridPane for forms VBox/HBox for simple stacksUse absolute positioning for resizable app screensBorderPane shell = new BorderPane(); shell.setTop(toolbar); shell.setCenter(content);Pane root = new Pane(); button.setLayoutX(742);Highhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/layout/package-summary.html
1013LayoutPrefer constraints over fixed coordinatesResponsive JavaFX layouts depend on constraints and grow prioritiesUse hgrow vgrow column constraints and alignmentHard-code pixel positions and sizesGridPane.setHgrow(nameField, Priority.ALWAYS); column.setPercentWidth(50);field.setPrefWidth(328); field.setLayoutX(120);Highhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/layout/GridPane.html
1114LayoutSet sensible min pref and max sizesControls should resize predictably across windows and DPI settingsUse Region.USE_COMPUTED_SIZE and max widths intentionallyLock every control to fixed width and heightbutton.setMaxWidth(Double.MAX_VALUE); VBox.setVgrow(table, Priority.ALWAYS);button.setMinSize(96, 32); button.setMaxSize(96, 32);Mediumhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/layout/Region.html
1215LayoutUse spacing and padding consistentlyDesktop UI needs scan-friendly rhythm and clear groupingSet spacing padding and Insets through shared constants or CSSUse inconsistent ad hoc gaps between controlsform.setHgap(12); form.setVgap(10); form.setPadding(new Insets(16));box.setSpacing(3); other.setSpacing(17);Lowhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/geometry/Insets.html
1316ControlsUse ObservableList for list controlsTableView ListView and ComboBox update automatically from observable collectionsBack controls with FXCollections.observableArrayList()Mutate plain lists and manually refresh controlsObservableList<Customer> rows = FXCollections.observableArrayList(); table.setItems(rows);List<Customer> rows = new ArrayList<>(); table.setItems((ObservableList) rows);Highhttps://openjfx.io/javadoc/21/javafx.base/javafx/collections/ObservableList.html
1417ControlsConfigure TableView cell value factories with propertiesTable columns should observe stable JavaFX properties for updatesExpose StringProperty ObjectProperty or use ReadOnlyObjectWrapperReturn transient strings without observable supportnameCol.setCellValueFactory(data -> data.getValue().nameProperty());nameCol.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().toString()));Mediumhttps://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/TableColumn.html
1519ControlsVirtualized controls are for large dataTableView ListView TreeView virtualize cells and outperform manual node listsUse TableView or ListView for hundreds of rowsCreate hundreds of HBoxes inside a VBoxListView<Item> list = new ListView<>(items);items.forEach(i -> vbox.getChildren().add(new ItemRow(i)));Highhttps://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/ListView.html
1621BindingUse property binding for derived UI stateJavaFX binding reduces imperative synchronization bugsBind disabled visible text and progress properties to source stateManually update every dependent control in each event handlersaveButton.disableProperty().bind(form.validProperty().not());if(!valid) saveButton.setDisable(true);Highhttps://openjfx.io/javadoc/21/javafx.base/javafx/beans/binding/Bindings.html
1723BindingUse listeners sparinglyBindings express simple relationships more clearly than listenersUse listeners for side effects and bindings for valuesCreate listener chains for simple computed texttotalLabel.textProperty().bind(Bindings.format("Total: %d", total));count.addListener((o, a, b) -> totalLabel.setText("Total: " + b));Lowhttps://openjfx.io/javadoc/21/javafx.base/javafx/beans/value/ObservableValue.html
1824EventsUse action handlers for commandsButtons and menu items should route to named command methodsUse setOnAction or @FXML handler methods with clear namesPut large lambdas inline for complex operations@FXML private void handleSave(ActionEvent event) { saveCustomer(); }saveButton.setOnAction(e -> { validate(); transform(); query(); save(); refresh(); });Mediumhttps://openjfx.io/javadoc/21/javafx.base/javafx/event/ActionEvent.html
1925EventsUse event filters for global shortcutsFilters can intercept keyboard events before child controls consume themRegister accelerators or filters at Scene levelAdd duplicate key handlers to every controlscene.getAccelerators().put(new KeyCodeCombination(KeyCode.S, SHORTCUT_DOWN), this::save);nameField.setOnKeyPressed(...); table.setOnKeyPressed(...);Mediumhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/Scene.html
2027AccessibilityExpose accessible text for icon buttonsIcon-only controls need names for screen readers and tooltipsSet accessibleText and Tooltip on icon buttonsUse unlabeled graphic-only buttonsbutton.setAccessibleText("Refresh"); button.setTooltip(new Tooltip("Refresh"));new Button("", refreshIcon)Highhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/AccessibleRole.html
2128AccessibilityKeep keyboard focus visibleDesktop users rely on focus traversal and visible focus indicatorsPreserve focus rings and tab orderRemove outlines without alternative focus state.button:focused { -fx-border-color: -brand-focus; -fx-border-width: 2; }.button:focused { -fx-background-insets: 0; }Highhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/Node.html
2229AccessibilityUse mnemonics for menu and form workflowsMnemonics make desktop workflows faster and more accessibleEnable mnemonicParsing and choose unique mnemonic lettersIgnore keyboard alternatives for frequent actionssaveButton.setMnemonicParsing(true); saveButton.setText("_Save");saveButton.setText("Save");Lowhttps://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/Labeled.html
2330ValidationShow validation near the fieldUsers should not hunt for form errors in desktop dialogsBind error labels or pseudo classes next to invalid controlsShow only a generic alert after submitfield.pseudoClassStateChanged(PseudoClass.getPseudoClass("invalid"), !valid);new Alert(ERROR, "Invalid input").show();Mediumhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/css/PseudoClass.html
2431ValidationUse TextFormatter for constrained inputTextFormatter prevents invalid edits before they enter the modelAttach TextFormatter for numeric dates and masksParse and reject invalid text only after submitamountField.setTextFormatter(new TextFormatter<>(new IntegerStringConverter(), 0, c -> c.getControlNewText().matches("\\d*") ? c : null));Integer.parseInt(amountField.getText());Mediumhttps://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/TextFormatter.html
2532DialogsUse modal ownership for dialogsDialogs should block only the relevant window and return structured resultsSet owner modality and use showAndWaitOpen unmanaged windows for confirmationsdialog.initOwner(stage); dialog.initModality(Modality.WINDOW_MODAL); Optional<ButtonType> result = dialog.showAndWait();new Stage().show();Mediumhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/stage/Modality.html
2633DialogsPrefer custom DialogPane over ad hoc stagesDialog gives consistent buttons focus and result handlingUse Dialog<T> for forms confirmations and wizardsBuild every modal as a new Stage manuallyDialog<Customer> dialog = new Dialog<>(); dialog.getDialogPane().getButtonTypes().addAll(OK, CANCEL);Stage modal = new Stage(); modal.setScene(new Scene(new VBox()));Lowhttps://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/Dialog.html
2734ImagesLoad images as resourcesPackaged apps need resources resolved from the classpath or module pathUse getResourceAsStream for bundled assetsUse absolute local file paths in production UInew Image(getClass().getResourceAsStream("/images/logo.png"));new Image("file:/Users/me/Desktop/logo.png")Highhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/image/Image.html
2835ImagesUse background loading for large imagesLarge image decoding can pause UI startupUse Image(url true) or a background Task for heavy assetsLoad many full-size images synchronously during startupImage preview = new Image(url, true);gallery.add(new Image(url));Mediumhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/image/Image.html
2936AnimationKeep animations purposeful and shortDesktop UI animations should clarify state changes without delaying workUse 150-250ms transitions for reveal hover and selectionAnimate every layout change with long timelinesFadeTransition ft = new FadeTransition(Duration.millis(180), pane); ft.setToValue(1);new Timeline(new KeyFrame(Duration.seconds(2), ...)).play();Lowhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/animation/package-summary.html
3037AnimationRespect reduced-motion contexts where possibleSome users experience motion sensitivity in desktop appsProvide a setting to disable decorative animationsMake animation required for comprehensionif (settings.reducedMotion()) pane.setOpacity(1); else fade.play();alwaysSpin.play();Mediumhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/animation/Animation.html
3138PerformanceAvoid recreating scenes for small state changesReplacing whole scenes loses state and can flickerSwap center content or update view modelsRebuild the entire Stage for every navigation clickshell.setCenter(customerView);stage.setScene(new Scene(loadMainAgain()));Mediumhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/Scene.html
3239PerformanceReuse loaded views when appropriateFXML loading and CSS application are not freeCache stable views or controllers for frequent navigationReload heavyweight screens repeatedly without needNode settings = viewCache.computeIfAbsent("settings", this::loadSettings);button.setOnAction(e -> shell.setCenter(loadFxml("settings.fxml")));Lowhttps://openjfx.io/javadoc/21/javafx.fxml/javafx/fxml/FXMLLoader.html
3340PerformanceBatch observable list changesMany single-item updates can cause repeated layout and sort workUse setAll or addAll for bulk replacementLoop add items one by one to visible listsitems.setAll(repository.findAll());for(Item item : loaded) items.add(item);Mediumhttps://openjfx.io/javadoc/21/javafx.base/javafx/collections/ObservableList.html
3441ArchitectureUse view models for complex screensView models keep controller state testable and separate from controlsExpose JavaFX properties from a screen modelStore all state only inside controlscustomerNameField.textProperty().bindBidirectional(viewModel.nameProperty());String name = customerNameField.getText(); // everywhereMediumhttps://openjfx.io/javadoc/21/javafx.base/javafx/beans/property/package-summary.html
3543ModulesDeclare required JavaFX modulesModular JavaFX apps must require the modules they useAdd javafx.controls javafx.fxml and opens controller packagesDepend on classpath accidents onlymodule app { requires javafx.controls; requires javafx.fxml; opens app.ui to javafx.fxml; }module app { requires javafx.controls; }Highhttps://openjfx.io/openjfx-docs/#modular
3644PackagingUse jlink or jpackage for desktop deliveryJavaFX apps should ship with the runtime they needPackage a runtime image or native installerAsk end users to install matching Java and JavaFX manuallyjpackage --name MyApp --module app/app.Main --runtime-image build/imagejava -jar app.jarMediumhttps://openjfx.io/openjfx-docs/#modular
3745TestingUse TestFX for interaction testsUI flows need automated coverage beyond controller unit testsWrite TestFX tests for key forms dialogs and navigationOnly manually click through releasesclickOn("#nameField").write("Alice"); clickOn("Save"); verifyThat("Saved", isVisible());// manual QA onlyMediumhttps://github.com/TestFX/TestFX
3847ThemePrefer Primer for enterprise applicationsPrimerLight and PrimerDark are neutral enough for dense business workflowsUse PrimerLight as default and PrimerDark for dark modeUse Dracula or Cupertino as the default enterprise themeApplication.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet());Application.setUserAgentStylesheet(new Dracula().getUserAgentStylesheet());Mediumhttps://mkpaz.github.io/atlantafx/themes/
3948ThemeLayer brand CSS after AtlantaFXApplication CSS should customize brand tokens and business states after the base themeAdd app.css to the Scene after setting AtlantaFXEdit AtlantaFX source CSS directlyscene.getStylesheets().add(getClass().getResource("/css/app.css").toExternalForm());modify atlantafx-base CSS filesHighhttps://mkpaz.github.io/atlantafx/theming/
4049ThemeUse looked-up colors as enterprise tokensJavaFX looked-up colors keep brand and semantic colors reusable across controlsDefine app-primary app-success app-warning app-danger on rootRepeat hex values in every selector.root { -app-primary: #2563eb; -app-danger: #dc2626; }.save { -fx-background-color: #2563eb; } .link { -fx-text-fill: #2563eb; }Highhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/doc-files/cssref.html
4150ThemeKeep theme switching centralizedDark mode switching should not be scattered across controllersUse a ThemeService that sets user-agent stylesheet and app CSS variantsLet each controller decide its own themethemeService.apply(ThemeMode.DARK);if(dark) button.setStyle(...);Mediumhttps://mkpaz.github.io/atlantafx/
4251ThemeValidate contrast for business status colorsEnterprise screens use status colors heavily and need readable contrastCheck text on success warning danger and selected row backgroundsAssume brand colors are accessible.status-danger { -fx-text-fill: -app-danger; }red text on dark red backgroundHighhttps://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html
4352ThemeUse AtlantaFX style classes before custom CSSAtlantaFX exposes utility styles that reduce custom CSS driftPrefer Styles constants or documented style classesCreate one-off class names for every button variantsaveButton.getStyleClass().add(Styles.ACCENT);saveButton.getStyleClass().add("blue-button-42");Mediumhttps://mkpaz.github.io/atlantafx/
4453ThemeTreat AtlantaFX as a base not the whole design systemAtlantaFX modernizes controls but enterprise UX still needs layout density and workflow rulesDefine app shell navigation table density form and validation conventionsAssume theme choice alone solves enterprise usabilityroot.getStyleClass().add("enterprise-shell");only set PrimerLight and stopHighhttps://mkpaz.github.io/atlantafx/
4555ComponentsUse AtlantaFX controls for common app affordancesAtlantaFX provides useful controls such as Card Message ModalPane Popover and ToggleSwitchUse built-in AtlantaFX controls before adding another dependencyAdd ControlsFX for components AtlantaFX already coversMessage message = new Message("Saved", "Customer updated successfully");new Label("Saved") with ad hoc stylingMediumhttps://mkpaz.github.io/atlantafx/
4656ComponentsAdd ControlsFX only for missing enterprise controlsControlsFX is useful for specialized controls but should stay optionalUse ControlsFX for SpreadsheetView PropertySheet CheckComboBox or StatusBar needsAdd ControlsFX by default before requirements are clearPropertySheet sheet = new PropertySheet(items);implementation "org.controlsfx:controlsfx" with no usageLowhttps://controlsfx.github.io/
4757TestingTest theme-critical flows with TestFXTheme and CSS changes can break focus visibility dialogs and button affordanceUse TestFX for login save validation and modal workflowsOnly inspect AtlantaFX screens manuallyclickOn("#saveButton"); verifyThat(".message", isVisible());manual theme QA onlyMediumhttps://github.com/TestFX/TestFX
4858ArchitectureUse application shell plus feature workspacesEnterprise JavaFX apps need stable navigation around changing work areasUse BorderPane shell with navigation toolbar and central workspaceReplace the whole Stage for every featureshell.setLeft(navigation); shell.setTop(toolbar); shell.setCenter(workspace);stage.setScene(new Scene(loadFeature()));Highhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/layout/BorderPane.html
4959ArchitectureUse MVVM for complex enterprise screensLarge forms and tables need testable state outside the controllerExpose JavaFX properties from view models and bind controls to themPut all screen state and validation in the controlleramountField.textProperty().bindBidirectional(vm.amountProperty());controller.amount = amountField.getText();Highhttps://openjfx.io/javadoc/21/javafx.base/javafx/beans/property/package-summary.html
5060ArchitectureInject services into controllersEnterprise controllers should coordinate UI and call application servicesUse a controller factory or DI container for servicesCreate database connections inside FXML controllersloader.setControllerFactory(type -> injector.getInstance(type));new CustomerRepository(new DriverManager(...))Highhttps://openjfx.io/javadoc/21/javafx.fxml/javafx/fxml/FXMLLoader.html
5161NavigationUse role-aware navigation modelsMenus toolbars and shortcuts should reflect the same permission modelBuild navigation items from commands with required rolesHide buttons in one place and leave shortcuts enabledcommand.enabledProperty().bind(permissionService.allowed("invoice.approve"));approveButton.setVisible(false);High
5262WorkflowRepresent workflow states visiblyApproval and processing screens need clear business state signalsUse semantic badges row styles and disabled actions by workflow stateUse only free text status columnsrow.pseudoClassStateChanged(PseudoClass.getPseudoClass("blocked"), item.isBlocked());statusCol.setText("B");Mediumhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/css/PseudoClass.html
5363TableViewDesign TableView for high-density enterprise dataEnterprise users scan compare sort filter and act on rows for long periodsUse compact row height clear columns sorting filtering and selection summaryUse card grids for large tabular datasetstable.getStyleClass().add("dense-table"); table.getSortOrder().setAll(updatedAtCol);new TilePane(customerCards)Highhttps://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/TableView.html
5464TableViewKeep row actions predictableInline actions in dense tables should be limited and permission-awareUse context menus or a side detail panel for secondary actionsPlace many buttons in every rowtable.setRowFactory(tv -> { TableRow<Order> row = new TableRow<>(); row.setContextMenu(orderMenu); return row; });row contains Edit Delete Approve Print Email buttonsMediumhttps://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/ContextMenu.html
5565TableViewUse server-side paging for large enterprise datasetsDesktop clients should not load entire enterprise tables into memoryFetch pages or filtered slices from servicesLoad all records and filter in the UIPage<Customer> page = customerService.search(criteria, pageRequest);customerRepository.findAll()High
5666FormsUse form sections for enterprise data entryLong enterprise forms need grouping and progressive disclosureGroup fields into titled sections with validation summariesPlace dozens of inputs in one unbroken GridPaneTitledPane billing = new TitledPane("Billing", billingForm);new GridPane with 80 controlsMediumhttps://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/TitledPane.html
5767FormsProvide validation summary plus field errorsEnterprise forms often need multiple corrections before submissionShow a summary at top and field-level messages near controlsShow only one modal alert after Savesummary.setItems(vm.validationErrors()); field.pseudoClassStateChanged(INVALID, fieldError);new Alert(ERROR, "Invalid form").showAndWait();High
5868TasksMake long operations cancellableEnterprise imports exports sync and reports need cancel pathsExpose cancel button bound to Task running stateForce users to wait or kill the appcancelButton.setOnAction(e -> task.cancel());runReportButton.setDisable(true);Highhttps://openjfx.io/javadoc/21/javafx.graphics/javafx/concurrent/Task.html
5969TasksSurface retryable errors without losing contextNetwork and service failures should preserve user input and next actionShow inline retry messages and keep form/table stateClear the screen on service failuremessage.setDescription("Could not save. Check connection and retry.");loadErrorScene();High
6070AuditLog business actions through servicesEnterprise desktop apps need traceability for sensitive changesRecord user action entity result and timestamp in service layerLog only UI button clicksaudit.log(user, "invoice.approve", invoiceId, SUCCESS);System.out.println("clicked approve");Medium
6171ConfigurationSeparate user preferences from application configEnterprise apps need deploy-time config and per-user preferencesUse config files for endpoints and Preferences for UI choicesHard-code environment URLs and window statePreferences.userNodeForPackage(App.class).put("theme", "dark");private static final String API = "http://localhost:8080";Mediumhttps://docs.oracle.com/en/java/javase/21/docs/api/java.prefs/java/util/prefs/Preferences.html
6272DeploymentPackage resources and themes inside the runtime imageAtlantaFX app CSS icons and FXML must be available after jpackageLoad resources from classpath or module resourcesLoad theme files from developer machine pathsgetClass().getResource("/css/app.css").toExternalForm();new File("src/main/resources/css/app.css").toURI()Highhttps://openjfx.io/openjfx-docs/#modular
6374TestingCover enterprise happy path and failure pathEnterprise UI tests should verify save validation permission and service failure flowsUse TestFX for core workflows and service fakesOnly test controller methods without UI interactionclickOn("Save"); verifyThat("Customer saved", isVisible());controller.save(); assertTrue(saved);Highhttps://github.com/TestFX/TestFX
6475DependenciesKeep optional UI libraries behind actual needsAtlantaFX should be default but additional libraries should be justifiedStart with JavaFX AtlantaFX Ikonli TestFX and add ControlsFX only for missing controlsAdopt many UI libraries at project startdependencies { implementation("io.github.mkpaz:atlantafx-base:2.1.0") }implementation controlsfx gemsfx tilesfx materialfx all at onceMedium