Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions gui/vector_emoji_rich_text/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
title: Vector Fonts, Rich Text and Emoji
brief: This example shows how to use vector fonts, Rich Text effects and full-color emoji.
author_ids:
- defold-foundation
scripts: hello.gui_script
thumbnail: thumbnail.webp
tags: font, gui, animation
---

This example shows how to combine vector fonts, Rich Text effects and full-color emoji in a single Defold GUI text node.

The text displays a text and emojis with example Rich Text tags for changing size, color, or gradients.
An emoji changes every second.
The entire text gently zooms in and out using sine easing.

## Setup

The scene contains just one game object, `go`, with a GUI component:

- The GUI contains one text node named `text`, positioned in the center.
- The GUI uses the accompanying GUI script `hello.gui_script` to set and animate the text.
- The GUI has a vector font registered with the name `hello`.
- The example includes `Twemoji-COLRv1.ttf` for full-color emoji.

![setup](setup.png)

It's also important to enable full text layout system and Rich Text in the custom App Manifest:

![appmanifest](appmanifest.png)

## How it works

1. In `init()` get the `hello` font resource
2. Add the COLRv1 emoji font with `font.add_font()`
3. Prewarm the glyphs with `font.prewarm_text()`.
2. When prewarming completes, `on_prewarmed()` sets the Rich Text string on the `text` node and starts a looping zoom animation with `gui.animate()` and `gui.EASING_INOUTSINE`.
3. In `update()` we change the emoji every second, cycling through rocket, smile, wink and thumbs up.

The Rich Text markup uses `<color>`, `<wave>`, `<shake>`, `<size>` and `<gradient>` to style and animate portions of the text without creating extra nodes.
Read more about Rich Text tags in [the Rich Text markup manual](https://defold.com/manuals/font-richtext/).

Credits
Inter font files: SIL Open Font License 1.1.
Emoji glyphs: derived from Twemoji Mozilla, CC BY 4.0
Source links in `licenses/`.
73 changes: 73 additions & 0 deletions gui/vector_emoji_rich_text/appmanifest.appmanifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
platforms:
arm64-ios:
context:
excludeLibs: [font, physics, LinearMath, BulletDynamics, BulletCollision, script_bullet3d, box2d_defold, script_box2d_defold, gamesys_model, gamesys_rig, rig]
excludeSymbols: [ScriptBullet3DExt, ScriptBox2DExt, ScriptModelExt]
symbols: []
libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi, physics_null, gamesys_rig_null, gamesys_model_null, rig_null]
arm64_sim-ios:
context:
excludeLibs: [font, physics, LinearMath, BulletDynamics, BulletCollision, script_bullet3d, box2d_defold, script_box2d_defold, gamesys_model, gamesys_rig, rig]
excludeSymbols: [ScriptBullet3DExt, ScriptBox2DExt, ScriptModelExt]
symbols: []
libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi, physics_null, gamesys_rig_null, gamesys_model_null, rig_null]
armv7-android:
context:
excludeLibs: [font, physics, LinearMath, BulletDynamics, BulletCollision, script_bullet3d, box2d_defold, script_box2d_defold, gamesys_model, gamesys_rig, rig]
excludeSymbols: [ScriptBullet3DExt, ScriptBox2DExt, ScriptModelExt]
symbols: []
libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi, physics_null, gamesys_rig_null, gamesys_model_null, rig_null]
arm64-android:
context:
excludeLibs: [font, physics, LinearMath, BulletDynamics, BulletCollision, script_bullet3d, box2d_defold, script_box2d_defold, gamesys_model, gamesys_rig, rig]
excludeSymbols: [ScriptBullet3DExt, ScriptBox2DExt, ScriptModelExt]
symbols: []
libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi, physics_null, gamesys_rig_null, gamesys_model_null, rig_null]
x86_64-android:
context:
excludeLibs: [font, physics, LinearMath, BulletDynamics, BulletCollision, script_bullet3d, box2d_defold, script_box2d_defold, gamesys_model, gamesys_rig, rig]
excludeSymbols: [ScriptBullet3DExt, ScriptBox2DExt, ScriptModelExt]
symbols: []
libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi, physics_null, gamesys_rig_null, gamesys_model_null, rig_null]
arm64-osx:
context:
excludeLibs: [font, physics, LinearMath, BulletDynamics, BulletCollision, script_bullet3d, box2d_defold, script_box2d_defold, gamesys_model, gamesys_rig, rig]
excludeSymbols: [ScriptBullet3DExt, ScriptBox2DExt, ScriptModelExt]
symbols: []
libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi, physics_null, gamesys_rig_null, gamesys_model_null, rig_null]
x86_64-osx:
context:
excludeLibs: [font, physics, LinearMath, BulletDynamics, BulletCollision, script_bullet3d, box2d_defold, script_box2d_defold, gamesys_model, gamesys_rig, rig]
excludeSymbols: [ScriptBullet3DExt, ScriptBox2DExt, ScriptModelExt]
symbols: []
libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi, physics_null, gamesys_rig_null, gamesys_model_null, rig_null]
x86_64-linux:
context:
excludeLibs: [font, physics, LinearMath, BulletDynamics, BulletCollision, script_bullet3d, box2d_defold, script_box2d_defold, gamesys_model, gamesys_rig, rig]
excludeSymbols: [ScriptBullet3DExt, ScriptBox2DExt, ScriptModelExt]
symbols: []
libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi, physics_null, gamesys_rig_null, gamesys_model_null, rig_null]
arm64-linux:
context:
excludeLibs: [font, physics, LinearMath, BulletDynamics, BulletCollision, script_bullet3d, box2d_defold, script_box2d_defold, gamesys_model, gamesys_rig, rig]
excludeSymbols: [ScriptBullet3DExt, ScriptBox2DExt, ScriptModelExt]
symbols: []
libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi, physics_null, gamesys_rig_null, gamesys_model_null, rig_null]
x86_64-win32:
context:
excludeLibs: [font, physics, libLinearMath, libBulletDynamics, libBulletCollision, script_bullet3d, libbox2d_defold, script_box2d_defold, gamesys_model, gamesys_rig, rig]
excludeSymbols: [ScriptBullet3DExt, ScriptBox2DExt, ScriptModelExt]
symbols: []
libs: [font_skribidi, libharfbuzz, libsheenbidi, libunibreak, libskribidi, physics_null, gamesys_rig_null, gamesys_model_null, rig_null]
wasm-web:
context:
excludeLibs: [font, physics, LinearMath, BulletDynamics, BulletCollision, script_bullet3d, box2d_defold, script_box2d_defold, gamesys_model, gamesys_rig, rig]
excludeSymbols: [ScriptBullet3DExt, ScriptBox2DExt, ScriptModelExt]
symbols: []
libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi, physics_null, gamesys_rig_null, gamesys_model_null, rig_null]
wasm_pthread-web:
context:
excludeLibs: [font, physics, LinearMath, BulletDynamics, BulletCollision, script_bullet3d, box2d_defold, script_box2d_defold, gamesys_model, gamesys_rig, rig]
excludeSymbols: [ScriptBullet3DExt, ScriptBox2DExt, ScriptModelExt]
symbols: []
libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi, physics_null, gamesys_rig_null, gamesys_model_null, rig_null]
Binary file added gui/vector_emoji_rich_text/appmanifest.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
20 changes: 20 additions & 0 deletions gui/vector_emoji_rich_text/fonts/hello.font
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
font: "/fonts/Inter-ExtraBold.otf"
material: "/builtins/fonts/font-vector.material"
characters: " !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
styles {
name: "default"
}
styles {
name: "link"
markup: "<color=#1972e5><ul>"
}
styles {
name: "link:hover"
markup: "<color=#4ca5ff>"
}
styles {
name: "link:active"
markup: "<color=#0c4cb2>"
}
vector_font_mode: VECTOR_FONT_MODE_VECTOR
runtime: true
22 changes: 22 additions & 0 deletions gui/vector_emoji_rich_text/game.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[project]
title = Vector Font Rich Text and Emoji
version = 1.0
custom_resources = /fonts/Twemoji-COLRv1.ttf

[bootstrap]
main_collection = /main/main.collectionc

[display]
width = 720
height = 720
high_dpi = 1

[input]
game_binding = /input/game.input_bindingc

[shader]
exclude_gles_sm100 = 1

[native_extension]
app_manifest = /appmanifest.appmanifest

Empty file.
4 changes: 4 additions & 0 deletions gui/vector_emoji_rich_text/licenses/INTER-LICENSE-NOTE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Inter font files are distributed under the SIL Open Font License 1.1.
Copyright (c) Rasmus Andersson and the Inter project contributors.
The official license text: https://openfontlicense.org/open-font-license-official-text/
Source: https://github.com/rsms/inter
7 changes: 7 additions & 0 deletions gui/vector_emoji_rich_text/licenses/TWEMOJI-ATTRIBUTION.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Emoji are derived from Twemoji Mozilla color-font assets.
Twemoji graphics: CC BY 4.0 (Twitter/Twemoji contributors).
Source: https://github.com/mozilla/twemoji-colr
Attribution: Graphics from Twemoji, copyright contributors, licensed CC BY 4.0.
This copy has its COLR table converted from version 0 to COLRv1 Paint glyphs.
See https://creativecommons.org/licenses/by/4.0/
Please verify license obligations before distribution.
23 changes: 23 additions & 0 deletions gui/vector_emoji_rich_text/main/hello.gui
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
script: "/main/hello.gui_script"
fonts {
name: "hello"
font: "/fonts/hello.font"
}
nodes {
position {
x: 360.0
y: 360.0
}
size {
x: 200.0
y: 100.0
}
type: TYPE_TEXT
text: "Hello <color=#61DAFB><wave>World</wave></color>! \360\237\232\200"
font: "hello"
id: "text"
inherit_alpha: true
}
material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT
max_nodes: 1
72 changes: 72 additions & 0 deletions gui/vector_emoji_rich_text/main/hello.gui_script
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@

-- Demonstrates how to use vector fonts, Rich Text effects and color emoji.

-- Emojis displayed in sequence, changing every second.
local EMOJI = { "🚀", "😀", "😉", "👍" }

-- Path to the COLRv1 font containing full-color emoji glyphs.
local EMOJI_FONT_PATH = "/fonts/Twemoji-COLRv1.ttf"

-- Rich Text tags control color, size and animated effects.
-- The emoji is appended dynamically to the first line.
local function text_for(emoji)
return "Hello <color=#61DAFB><wave>World</wave></color>! " .. emoji
.. "\n\n <size=0.5em><shake>RichText</shake>, <gradient left=#FFD313 right=#FD6623>Vector Fonts</gradient> and <gradient left=#1E64EA right=#00E6E1>Emoji</gradient> 😉</size>"
end

-- Called when the font glyphs have finished prewarming.
local function on_prewarmed(self, request_id, ok, err)
if not ok then
print("Emoji/font prewarm failed: " .. tostring(err))
return
end

-- Get the GUI text node and assign the vector font.
self.text = gui.get_node("text")
gui.set_font(self.text, "hello")
gui.set_pivot(self.text, gui.PIVOT_CENTER)
gui.set_line_break(self.text, false)
gui.set_color(self.text, vmath.vector4(1, 1, 1, 1))

-- Update the text with Rich Text formatting and the current emoji.
gui.set_text(self.text, text_for(EMOJI[self.emoji_index]))

-- Animate the change of the scale of the text smoothly.
gui.set_scale(self.text, vmath.vector3(3.8, 3.8, 1))
gui.animate(self.text, gui.PROP_SCALE,
vmath.vector3(4.5, 4.5, 1), gui.EASING_INOUTSINE,
1.5, 0, nil, gui.PLAYBACK_LOOP_PINGPONG)
end

function init(self)
-- Track the current emoji and elapsed time.
self.emoji_index = 1
self.emoji_timer = 0

-- Add the COLRv1 font as a fallback for color emojis.
local font_resource = gui.get_font_resource("hello")

-- Register the emoji font as a fallback for the main vector font.
font.add_font(font_resource, EMOJI_FONT_PATH)

-- Preload the required glyphs and call on_prewarmed when ready.
-- We prewarm glyphs before displaying the text to avoid missing characters.
font.prewarm_text(font_resource, "Hello World! 🚀 😀 😉 👍", on_prewarmed)
end

function update(self, dt)
-- Wait until the font is ready and the text node is set up.
if not self.text then return end

-- Accumulate frame time (dt) to change the emoji every second.
self.emoji_timer = self.emoji_timer + dt
while self.emoji_timer >= 1.0 do
self.emoji_timer = self.emoji_timer - 1.0

-- Advance to the next emoji, wrapping back to the first.
self.emoji_index = self.emoji_index % #EMOJI + 1

-- Update the text while keeping the zoom animation running.
gui.set_text(self.text, text_for(EMOJI[self.emoji_index]))
end
end
10 changes: 10 additions & 0 deletions gui/vector_emoji_rich_text/main/main.collection
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: "main"
scale_along_z: 0
embedded_instances {
id: "go"
data: "components {\n"
" id: \"hello\"\n"
" component: \"/main/hello.gui\"\n"
"}\n"
""
}
Binary file added gui/vector_emoji_rich_text/setup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added gui/vector_emoji_rich_text/thumbnail.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading