GraphicProvider — Icons, Images, and Symbolic Names¶
Overview and Motivation¶
A GraphicProvider turns a name into a JavaFX Node with graphical content:
The framework never names an icon file, an icon font, or an icon pack — it asks for "save". That
indirection is the entire point, and it buys two things:
- The application can replace the icon pack without touching framework code. Tentackle's own
views ask for
"save","delete","search"; what those look like is decided by whichever provider is registered for the realm. An application that prefers a different icon font, or bitmaps instead of a font, supplies its own provider and the framework's buttons follow. - Icons stay themeable. The default provider returns Ikonli
FontIcons, whose size and color are deliberately not set in Java code — they come from CSS, so icons track the theme (including dark mode) the same way the rest of the UI does.
Graphics are namespaced into realms. The framework uses the default realm; applications should use their own.
Not to be confused with
GuiProvider(tentackle-fx-rdc), which supplies the UI for a PDO — its views, tables, and so on. AGuiProvidertypically uses aGraphicProviderto answercreateGraphic()for its PDO.
Key Concepts¶
The interface¶
GraphicProvider has a single method:
A new node on every call — JavaFX nodes cannot be shared between parents, so providers must not
hand out the same instance twice. (Caching the underlying Image is fine and is exactly what
ImageGraphicProvider does.)
Realms¶
A realm is a namespace for graphic names, so an application's "object" need not mean the
framework's "object". A provider declares its realm with
@GraphicProviderService:
@GraphicProviderService("data") // default "" = the tentackle realm
public class DataNodeGraphicProvider extends IkonliGraphicProvider { ... }
FxFactory discovers the annotated providers through the
service mechanism and instantiates one
instance per realm at startup, using the no-arg constructor. If several providers claim the same
realm, the first one along the class-path or module-path wins and replaces the others — which is
how an application overrides the framework's default realm. A provider found without the annotation
is logged as an error and ignored.
Asking for a graphic¶
The Fx facade is the front door:
Fx.createGraphic("save"); // the default (tentackle) realm
Fx.createGraphic("data", "DataList"); // an application realm
Both delegate to FxFactory.createGraphic(realm, name). A null realm is treated as the default
one. An unknown realm raises IllegalArgumentException; an unknown name is the provider's
business and raises FxRuntimeException.
The two base implementations¶
IkonliGraphicProvider produces a FontIcon
from an Ikonli icon font. Subclasses implement one method:
By convention translateName returns unknown names unchanged, so an application can also pass
native icon-pack names ("mdi2c-cog") straight through alongside the symbolic ones.
ImageGraphicProvider produces an ImageView
from image files instead. It looks up <imagePath><name><ext>, trying .png, .gif and .jpg in
that order (getExtensions() to change). The image path defaults to <package-of-the-provider>/images/,
or is given to the constructor. Any path and extension in the requested name are stripped first, so
"icons/save.png" and "save" resolve identically. Resources are loaded via the provider's own
class, which is what makes it work under JPMS — the images live in your module, and only your
module can read them.
Loaded images are cached in a ConcurrentHashMap. Note that misses are cached too: a name that
resolves to no file is remembered as absent, so a later-arriving resource won't be picked up. That is
fine for packaged icons and worth knowing if you ever generate them at runtime.
The default realm's vocabulary¶
DefaultGraphicProvider serves the default
realm, mapping symbolic names to the Ikonli materialdesign2 pack (a compile-scope dependency of
tentackle-fx, requires transitive in its module-info). These are the names the framework itself
asks for — the vocabulary any replacement for the default realm should answer:
about |
add |
browser |
cancel |
checked |
close |
collapse |
copy |
cut |
delete |
down |
edit |
exit |
expand |
file |
filter |
generate |
help |
hide |
id |
login |
new |
object |
ok |
password |
preferences |
print |
reload |
run |
save |
script |
search |
security |
session |
subtract |
table |
theme |
tree |
unchecked |
unknown |
up |
view |
To swap the pack while keeping the framework's icons working, subclass IkonliGraphicProvider (or
ImageGraphicProvider), annotate it @GraphicProviderService with the default realm, cover these
names, and make sure it precedes tentackle-fx on the path. Overriding DefaultGraphicProvider and
its createNameMap() is the lighter option when you only want to re-point a few names.
Styling¶
Icon geometry and color belong in CSS, not in provider code. tentackle.css sets the baseline for
every font icon:
Because it's ordinary CSS, context can override it — as the security rules view does to color the same symbolic icon by meaning:
.sec-allow .ikonli-font-icon { -fx-icon-color: green; }
.sec-deny .ikonli-font-icon { -fx-icon-color: red; }
This is why IkonliGraphicProvider sets neither size nor color: doing so in Java would pin the icon
against the theme.
macOS caveat: font icons cannot be rendered in the system menu bar. MacUtilities.toSystemMenuBar(...)
walks a MenuBar and converts every FontIcon graphic to an ImageView (sized, and colored for the
current light/dark mode) before enabling the system menu bar.
How It Fits Together¶
An application adding its own icons for its own domain objects:
- Write a provider — extend
IkonliGraphicProviderto reuse the materialdesign2 pack that is already on the path, orImageGraphicProviderto ship image files under<your-package>/images/. - Annotate it
@GraphicProviderService("myrealm")and map your symbolic names intranslateName(...). Keep the realm constant public — callers need it. - The tentackle-maven-plugin
picks the annotated class up at build time and writes the
META-INF/servicesentry; no runtime scanning happens. - At startup
FxFactoryinstantiates one provider per realm. - Views ask for
Fx.createGraphic(MyGraphicProvider.REALM, "Invoice"), or — in an RDC application — return it from the PDO'sGuiProvider.createGraphic(), which is whatPdoTreeCelland the table cells call. - Sizes and colors come from your CSS; the framework's own icons keep working because they resolve through the default realm, untouched.
Package Layout¶
| Type | Role | Module |
|---|---|---|
GraphicProvider, GraphicProviderService |
The contract and its service annotation | tentackle-fx |
IkonliGraphicProvider, ImageGraphicProvider |
Base classes for font-icon and image providers | tentackle-fx |
DefaultGraphicProvider |
The default realm (materialdesign2) | tentackle-fx |
Fx, FxFactory |
createGraphic(...) entry points, realm registry |
tentackle-fx |
MacUtilities |
Font icon → image conversion for the macOS system menu | tentackle-fx |
GuiProvider |
Per-PDO UI, including its createGraphic() |
tentackle-fx-rdc |
Related Documentation¶
- tentackle-fx — the extended JavaFX layer this belongs to.
- GuiProvider — the per-PDO UI provider that supplies a PDO's graphic.
- Service and Configuration API — how
@GraphicProviderServiceis discovered. - tentackle-fx-atlanta — theming, including dark mode.
- Tables and Trees — cell types that render symbolic icons (
checked/unchecked).