Skip to content

Commit

Permalink
updated example to show how language support can be added to an appli…
Browse files Browse the repository at this point in the history
…cation
  • Loading branch information
LordOfDragons committed Nov 14, 2023
1 parent c00768c commit c3c8863
Show file tree
Hide file tree
Showing 7 changed files with 369 additions and 17 deletions.
2 changes: 1 addition & 1 deletion exampleApp/Example App.degp
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
<cacheDirectory>cache</cacheDirectory>
<baseGameDefinition>DragonScript Basic</baseGameDefinition>
<projectGameDefinition>exampleApp.degd</projectGameDefinition>
<scriptModule version='1.5'>DragonScript</scriptModule>
<scriptModule version='1.20'>DragonScript</scriptModule>
</gameProject>
11 changes: 11 additions & 0 deletions exampleApp/data/content/langpacks/de.delangpack
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version='1.0' encoding='UTF-8'?>
<languagePack>
<identifier>de</identifier>
<name>Deutsch</name>
<description>Deutsches Sprachpaket.</description>
<missingText>??</missingText>
<translation name='UI.MainWindow.Quit'>Beenden</translation>
<translation name='UI.MainWindow.Resume'>Fortfahren</translation>
<translation name='UI.MainWindow.Settings'>Einstellungen</translation>
<translation name='UI.MainWindow.Title'>Beispiel App Hauptmenu</translation>
</languagePack>
11 changes: 11 additions & 0 deletions exampleApp/data/content/langpacks/en.delangpack
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version='1.0' encoding='UTF-8'?>
<languagePack>
<identifier>en</identifier>
<name>English</name>
<description>English language pack</description>
<missingText>??</missingText>
<translation name='UI.MainWindow.Resume'>Resume</translation>
<translation name='UI.MainWindow.Settings'>Settings</translation>
<translation name='UI.MainWindow.Quit'>Quit</translation>
<translation name='UI.MainWindow.Title'>Example App Menu</translation>
</languagePack>
19 changes: 19 additions & 0 deletions exampleApp/data/scripts/ExampleApp.ds
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,25 @@ class ExampleApp extends BaseGameApp
end))
end

/**
* Load settings from saved settings or set default settings.
*
* Loads the saved settings from pathSavedSettings property. If the saved settings file
* is absent or damaged set default settings and saves the file. Ensures a valid
* settings file with all known settings is saved to file. This way players can modify the
* settings file and know all possible settings without needing to search.
*
* If you change settings call saveSettings() to save the settings to the saved file.
* It is best to save changes to the bindings immediately.
*/
protected func void loadSettings()
super.loadSettings()

// Load settings and store them locally. This makes it faster to retrieve
// values queried often.
ExampleAppSettings.settingsLoaded()
end

/**
* Launch example application. Called after logo windows are finished.
*/
Expand Down
114 changes: 114 additions & 0 deletions exampleApp/data/scripts/ExampleAppSettings.ds
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
namespace Example

pin Dragengine.Gui
pin Dragengine.Utils


/**
* ExampleApp settings.
*
* It is recommended to create an own class to store settings in your application.
*/
class ExampleAppSettings
/**
* Name of global event send if settings change.
*/
public static var String globalEventChanged = "settings.changed"

/**
* Language. Use Engine.getUserLocaleLanguage() to set this to the language of the
* user system if possible to determine by Drag[en]gine. If not found 'en' is used.
*/
static var String pLanguage = Engine.getUserLocaleLanguage()



/**
* Create settings. Since this is a class with static function only this call is
* not required to be present. You can also set it private if you want.
*/
func new()
end



/**
* Called after BaseGameApp loaded all settings to store the settings locally.
* It is faster to retrieve the settings from here than fetching them every time
* from the BaseGameApp settings object.
*/
static func void settingsLoaded()
var BaseGameApp app = BaseGameApp.getApp()
var ParameterTree settings = app.getSettings()

// get language from settings object. The second parameter is used if the
// setting is not yet present
pLanguage = settings.getStringAt("language", pLanguage)

// Activate the language pack with the matching identifier. If the language
// pack can not be found the fallback language pack is used instead.
// Make sure to activate the language pack on all your TranslationManager
// objects as well as EngineTranslations. Especially EngineTranslations
// is required to be updated otherwise Drag[en]gine provided UI Widgets
// fail to reflect the language change
app.getTranslationManager().setActiveWithIdIfPresent(pLanguage)
EngineTranslations.get().setActiveWithIdIfPresent(pLanguage)
end



/**
* Language.
*/
static func String getLanguage()
return pLanguage
end

/**
* Set language. This function does multiple things. See the comments below.
*/
public static func void setLanguage(String language)
// If the language is the same as before do nothing. Updating translations
// all accross the application can be expensive so do this only if necessary
if language.equals(pLanguage)
return
end

var BaseGameApp app = BaseGameApp.getApp()

// Store language locally. This makes the setting available to all scripts
// requiring to retrieve it
pLanguage = language

// Update the value in the BaseGameApp settings object. The next time the
// settings object is persisted the value will be restored the next time
// the application is run. If persisting is skipped for some reason the
// user has to change the value again the next the application is run
app.getSettings().setAt("language", language)

// Activate the language pack with the matching identifier. If the language
// pack can not be found the fallback language pack is used instead.
// Make sure to activate the language pack on all your TranslationManager
// objects as well as EngineTranslations. Especially EngineTranslations
// is required to be updated otherwise Drag[en]gine provided UI Widgets
// fail to reflect the language change
app.getTranslationManager().setActiveWithId(language)
EngineTranslations.get().setActiveWithId(language)

// Call onTranslationChanged() on the desktop. This causes the UI to update
// texts that are translated to reflect the new language. Make sure to call
// this after you activate the language pack or the UI uses the old language.
//
// If you have more than one Desktop object you have to call
// onTranslationChanged() on each of them. Optionally you can add a listener
// to GlobalEvents for each desktop to do this for you if the
// globalEventChanged event is send below
app.getDesktop().onTranslationChanged()

// Send globalEventChanged to the GlobalEvents manager. This allows to notify
// all kinds of scripts about language changes without needing to know them
// all here. This is an application custom event. You can define it whatever
// way fits best your needs
app.getGlobalEvents().sendEvent(globalEventChanged)
end
end
196 changes: 196 additions & 0 deletions exampleApp/data/scripts/WSSExampleApp.ds
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
namespace Example

pin Dragengine.Commands
pin Dragengine.Gui.Events
pin Dragengine.Gui.Layouts
pin Dragengine.Scenery
pin Dragengine.Utils


/**
* Window settings sheet containing application settings.
*
* Usually all applications feature a settings sheet like this
*
* You have to add a widget for each setting as well as managing reading and saving the
* values entered by the user. Since this is game specific you have to do this on your own.
*
* This example contains a single setting to change the language. This setting is persisted
* using the settings object provided by BaseGameApp.
*/
class WSSExampleApp extends DefaultWindowSettingsSheet
/**
* List element renderer capable of displaying objects of type LanguagePack.
* The renderer shows the name of the language pack.
*
* The base class DefaultListElementRenderer creates a Label widget and calls
* Object.toString() on the list elements to obtain the text. The most simple
* used of thie class is to overwrite updateRenderer() to send the text we
* want to use for each list element to the super class implementation.
*/
class LanguagePackRenderer extends DefaultListElementRenderer
/** Create list element renderer. */
func new()
end

/** Update list element renderer widget. */
func void updateRenderer(ListBox listBox, Widget renderer, Object element, bool selected, bool focused)
var String text
if element != null
// use language pack name as text to display
text = (element cast LanguagePack).getName().toUTF8()

else
// an empty element is used to determine a good minimum size for the list box.
// here we use a string of length 20 with a character of average width
text = String.new('X', 20)
end

super.updateRenderer(listBox, renderer, text, selected, focused)
end
end

/**
* List model listener changing language on the settings window only. This allows
* the user to see the language change in the settings window before actually saving.
*/
class PreviewLanguageListener extends DefaultListModelListener
/** Create list model listener . */
func new()
end

/**
* Selection changed. Update the translation manager in the parent window.
*/
func void selectionChanged(ListModel model)
var int selection = model.getSelected()
if selection != -1
ExampleAppSettings.setLanguage((model.getAt(selection) cast LanguagePack).getIdentifier())
end
end
end



var Panel pPanelSettings
var DefaultListModel pModelLanguage
var String pRestoreLanguage



/**
* Create settings sheet. The first parameter is a unique identifier. The second parameter
* is the title to display. You can use translated text by using the translation entry name
* prefixed with an '@' character.
*/
func new() super("exampleapp", "@UI.Settings.Title")
pRestoreLanguage = ExampleAppSettings.getLanguage()
end

/**
* On destroying the widget restore the language. Required for preview language to
* be working properly.
*/
func destructor()
ExampleAppSettings.setLanguage(pRestoreLanguage)
end


/**
* Save settings.
*/
func void saveSettings(Widget widget)
// Store language selected by the user
pRestoreLanguage = (pModelLanguage.getSelectedElement() cast LanguagePack).getIdentifier()
ExampleAppSettings.setLanguage(pRestoreLanguage)

// After changing all settings we have to persist the settings. This is not done
// automatically in case something goes wrong. Hence you have to do it manually.
//
// In particular this means you can delay the saving to a later point of time if
// needed. Since the settings are stored in ExampleAppSettings your application
// uses the changed settings now no matter if they are yet persisted or not
BaseGameApp.getApp().saveSettings()
end

/**
* Reset settings. Assigns current settings to the widget models.
*/
func void resetSettings(Widget widget)
var BaseGameApp app = BaseGameApp.getApp()

// Assign language pack to the model. This requires the LanguagePack object
// not the identifier as stored in the settings
pModelLanguage.setSelectedElement(app.getTranslationManager().getWithId(pRestoreLanguage))

// It is possible no language pack exists for the selected language. This can
// happen for various reasons. The most prominent one is running the application
// for the first time. In this situation the language from the user system is
// used. If you have no language pack in your application matching the system
// language you will get a selected index of -1 here. If this is the case
// use the active language pack which is the fallback language of your application
if pModelLanguage.getSelected() == -1
pModelLanguage.setSelectedElement(app.getTranslationManager().getActive())
end
end

/**
* Create and add widgets for each setting your application supports.
*/
func Widget createWidget()
var BaseGameApp app = BaseGameApp.getApp()

// The most simple widget to use is a Panel using the FormLayout. With this layout
// you add settings in pairs of widgets. The first widget is the label placed on
// the left side. The second widget is the actual editing widget placed on the
// right side on the same row as the first widget. This way you can easily add
// multiple settings and they are neatly lined up in a formular style.
//
// We use here a gap of 20px between the left and right side and a gap of 2p
// between each row of widgets.
var Panel panel = Panel.new(FormLayout.new(true, 20, 2), block Panel p
// Add language setting
//
// For this first create a list model filled with all language packs found
// in your application sorted alphabetically.
//
// Then select the user chosen language pack as done in resetSettings()
pModelLanguage = DefaultListModel.new(app.getTranslationManager().allSorted())

pModelLanguage.setSelectedElement(app.getTranslationManager().getWithId(ExampleAppSettings.getLanguage()))

if pModelLanguage.getSelected() == -1
pModelLanguage.setSelectedElement(app.getTranslationManager().getActive()) // unknown system language
end

// Add the label. The translation entries shown here are defined in
// EngineTranslations and are present in all Drag[en]gine installations.
// To use your own create a language pack with the appropriate entries
var Label label = Label.new("@UI.Settings.Label.Language")
label.setToolTip("@UI.Settings.ToolTip.Language")
p.addWidget(label)

// Add the widget to select the language. In this example a combo box is
// used. Since the values in the model are of type LanguagePack we use
// an own ListElementRenderer implementation to display the language
// packs the way we like it best.
//
// You can replace this widget with your own custom widget able to
// consume a ListModel
var ComboBox comboBox = ComboBox.new(pModelLanguage, LanguagePackRenderer.new())
comboBox.setToolTip("@UI.Settings.ToolTip.Language")
p.addWidget(comboBox)

// If you want to allow the user to preview the language in the settings
// window only you can use a listener to apply the language temporarily
pModelLanguage.addListener(PreviewLanguageListener.new())
end)

// If you have only a small count of settings where you are sure they fit on a
// single screen you can simply add them. In general though it is a good idea
// to wrap the panel in a scrollable viewport. This way the user can scroll up
// and down if the count of settings is larger than the window size
var Viewport viewport = Viewport.new(panel, BoxLayout.new(LayoutAxis.y))
return ScrollPanel.new(viewport, ScrollPanel.Policy.hidden, ScrollPanel.Policy.visible)
end
end
Loading

0 comments on commit c3c8863

Please sign in to comment.