1. Introduction

Themes may be switched at any time based on two conditions:

  • a valid value for ThemeManager.currentTheme is set

  • the application’s Locale is updated

The following controller shows 4 actions showing how to trigger each one of these conditions. The application assumes there are two themes named red and blue and that there are locale aware versions of these themes for English and Spanish

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import griffon.core.artifact.GriffonController
import griffon.metadata.ArtifactProviderFor
import griffon.plugins.theme.ThemeManager
import javax.inject.Inject

@ArtifactProviderFor(GriffonController)
class SampleController {
    @Inject
    private ThemeManager themeManager

    void red(evt)  { themeManager.currentTheme = 'red' }
    void blue(evt) { themeManager.currentTheme = 'blue' }

    void spanish(evt) { application.localeAsString = 'es' }
    void english(evt) { application.localeAsString = 'en' }
}

Classes that should participate in theme injection must be anotated with @griffon.plugins.theme.ThemeAware and have their properties anotated with @InjectedResource, for example

1
2
3
4
5
6
7
8
9
10
11
12
import griffon.core.artifact.GriffonModel
import griffon.metadata.ArtifactProviderFor
import java.awt.Color
import griffon.transform.Observable
import griffon.core.resources.InjectedResource

@griffon.plugins.theme.ThemeAware
@ArtifactProviderFor(GriffonModel)
class SampleModel {
    @Observable @InjectedResource Color color
    @Observable @InjectedResource String message
}

The resource injection mechanism relies on application events in order to handle injections on instances. All griffon artifacts trigger an event upon creation (NewInstance) and destruction (DestroyInstance). Non griffon artifact instances can still participate in resource injection as long their classes are bound within a Module.

Marking bean properties as observable makes it easier for the application to update itself when a theme change occurs. For example, a View may use the color and message model properties in this way

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package org.example

import griffon.core.artifact.GriffonView
import griffon.metadata.ArtifactProviderFor
import javax.swing.SwingConstants

@ArtifactProviderFor(GriffonView)
class SampleView {
    FactoryBuilderSupport builder
    SampleModel model

    void initUI() {
        builder.with {
            application(title: 'Themes',
                preferredSize: [320, 240], pack: true,
                locationByPlatform: true) {
                borderLayout()
                label(text: bind { model.message },
                      foreground: bind { model.color },
                      constraints: CENTER)
                  panel(constraints: WEST) {
                      gridLayout(cols: 1, rows: 4)
                      button(redAction)
                      button(blueAction)
                      button(spanishAction)
                      button(englishAction)
                  }
            }
        }
    }
}

It’s worth noting that if a resource cannot be resolved by a theme then the default application resources will be searched until the resource can be resolved or a NoSuchResourceException is thrown.

1.1. Configuration

Theme files look exactly the same as resources files. For the example shown above, the application expects the following files to exist

  • griffon-app/i18n/red.properties

  • griffon-app/i18n/red_es.properties

  • griffon-app/i18n/blue.properties

  • griffon-app/i18n/blue_es.properties

These themes must be registered using a Module, for example

bind(ResourceResolver.class)
    .withClassifier(named("red"))
    .toProvider(new ResourceResolverProvider("red"))
    .asSingleton();

bind(ResourceResolver.class)
    .withClassifier(named("blue"))
    .toProvider(new ResourceResolverProvider("blue"))
    .asSingleton();

Griffon version: 2.12.0

2. Build Configuration

2.1. Gradle

You have two options for configuring this plugin: automatic and manual.

2.1.1. Automatic

As long as the project has the org.codehaus.griffon.griffon plugin applied to it you may include the following snippet in build.gradle

dependencies {
    griffon 'org.codehaus.griffon.plugins:griffon-theme-plugin:2.1.0'
}

The griffon plugin will take care of the rest given its configuration.

2.1.2. Manual

You will need to configure any of the following blocks depending on your setup

dependencies {
    compile 'org.codehaus.griffon.plugins:griffon-theme-core:2.1.0'
}

2.2. Maven

First configure the griffon-theme-plugin BOM in your POM file, by placing the following snippet before the <build> element

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.codehaus.griffon.plugins</groupId>
            <artifactId>griffon-theme-plugin</artifactId>
            <version>2.1.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Next configure dependencies as required by your particular setup

<dependency>
    <groupId>org.codehaus.griffon.plugins</groupId>
    <artifactId>griffon-theme-core</artifactId>
</dependency>

3. Modules

The following sections display all bindings per module. Use this information to successfully override a binding on your own modules or to troubleshoot a module binding if the wrong type has been applied by the Griffon runtime.

3.1. Theme

Module name: theme

bind(ThemeManager.class)
    .to(DefaultThemeManager.class)
    .asSingleton();

bind(ResourceInjector.class)
    .withClassifier(named("applicationResourceInjector"))
    .to(ThemeAwareResourceInjector.class)
    .asSingleton();