Skip to content
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@
/.vscode
/media/*
.idea/
/noto-emoji-*.zip
multi-codepoint-emoji-support-plan.md
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ This is just the changelog for the core `egui` crate. Every crate in this reposi
This file is updated upon each release.
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.

## Unreleased

### Added
* `Context::register_color_glyph` lets integrations and third-party crates register bitmap glyphs (for instance emoji atlases) directly on a context.

### Changed
* The built-in emoji atlas was split into the optional [`egui_noto_emoji`](crates/egui_noto_emoji) crate. Apps that want colorful emojis should add that crate and call `egui_noto_emoji::install(&ctx)` during startup.


## 0.33.2 - 2025-11-13
### ⭐ Added
Expand Down
10 changes: 10 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1371,6 +1371,7 @@ dependencies = [
"egui_demo_lib",
"egui_extras",
"egui_kittest",
"egui_noto_emoji",
"ehttp",
"env_logger",
"image",
Expand Down Expand Up @@ -1461,6 +1462,15 @@ dependencies = [
"wgpu",
]

[[package]]
name = "egui_noto_emoji"
version = "0.33.2"
dependencies = [
"egui",
"log",
"png",
]

[[package]]
name = "egui_tests"
version = "0.33.2"
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"crates/egui_demo_lib",
"crates/egui_extras",
"crates/egui_glow",
"crates/egui_noto_emoji",
"crates/egui_kittest",
"crates/egui-wgpu",
"crates/egui-winit",
Expand Down Expand Up @@ -67,6 +68,7 @@ egui_demo_lib = { version = "0.33.2", path = "crates/egui_demo_lib", default-fea
egui_glow = { version = "0.33.2", path = "crates/egui_glow", default-features = false }
egui_kittest = { version = "0.33.2", path = "crates/egui_kittest", default-features = false }
eframe = { version = "0.33.2", path = "crates/eframe", default-features = false }
egui_noto_emoji = { version = "0.33.2", path = "crates/egui_noto_emoji" }

accesskit = "0.21.1"
accesskit_consumer = "0.30.1"
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ Still, egui can be used to create professional looking applications, like [the R
* Widgets: label, text button, hyperlink, checkbox, radio button, slider, draggable value, text editing, color picker, spinner
* Images
* Layouts: horizontal, vertical, columns, automatic wrapping
* Text editing: multiline, copy/paste, undo, emoji supports
* Text editing: multiline, copy/paste, undo, emoji via opt-in glyph packs
* Windows: move, resize, name, minimize and close. Automatically sized and positioned.
* Regions: resizing, vertical scrolling, collapsing headers (sections), panels
* Rendering: Anti-aliased rendering of lines, circles, text and convex polygons.
Expand Down Expand Up @@ -366,6 +366,15 @@ Default fonts:
* `NotoEmoji-Regular.ttf`: [google.com/get/noto](https://google.com/get/noto), [SIL Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL)
* `Ubuntu-Light.ttf` by [Dalton Maag](http://www.daltonmaag.com/): [Ubuntu font licence](https://ubuntu.com/legal/font-licence)

Color emoji sprites now live in the separate [`egui_noto_emoji`](crates/egui_noto_emoji) crate so that core egui stays lean.
Opt in by adding the crate to your project and calling:

```rust
egui_noto_emoji::install(&ctx);
```

You can also iterate over `egui_noto_emoji::EmojiStore::builtin()` yourself if you need finer control over which glyphs are registered.

---

<div align="center">
Expand Down
1 change: 0 additions & 1 deletion crates/egui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ rustdoc-args = ["--generate-link-to-definition"]

[features]
default = ["default_fonts"]

## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast [`epaint::Vertex`], [`emath::Vec2`] etc to `&[u8]`.
bytemuck = ["epaint/bytemuck"]

Expand Down
39 changes: 35 additions & 4 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#![warn(missing_docs)] // Let's keep `Context` well-documented.

use std::{borrow::Cow, cell::RefCell, panic::Location, sync::Arc, time::Duration};
use std::{
borrow::Cow, cell::RefCell, collections::BTreeMap, panic::Location, sync::Arc, time::Duration,
};

use emath::GuiRounding as _;
use epaint::{
ClippedPrimitive, ClippedShape, Color32, ImageData, Pos2, Rect, StrokeKind,
ClippedPrimitive, ClippedShape, Color32, ColorImage, ImageData, Pos2, Rect, StrokeKind,
TessellationOptions, TextureId, Vec2,
emath::{self, TSTransform},
mutex::RwLock,
Expand Down Expand Up @@ -367,6 +369,8 @@ impl ViewportRepaintInfo {
#[derive(Default)]
struct ContextImpl {
fonts: Option<Fonts>,
/// Registry of opt-in bitmap glyphs (e.g. emojis) that should be replayed into every font atlas.
color_glyphs: BTreeMap<char, Arc<ColorImage>>,
font_definitions: FontDefinitions,

memory: Memory,
Expand Down Expand Up @@ -409,6 +413,13 @@ struct ContextImpl {
}

impl ContextImpl {
fn register_color_glyph_arc(&mut self, character: char, image: Arc<ColorImage>) {
self.color_glyphs.insert(character, image.clone());
if let Some(fonts) = self.fonts.as_mut() {
fonts.register_color_glyph(character, image.clone());
}
}

fn begin_pass(&mut self, mut new_raw_input: RawInput) {
let viewport_id = new_raw_input.viewport_id;
let parent_id = new_raw_input
Expand Down Expand Up @@ -580,9 +591,15 @@ impl ContextImpl {
)
});

{
let fonts_recreated = {
profiling::scope!("Fonts::begin_pass");
fonts.begin_pass(max_texture_side, text_alpha_from_coverage);
fonts.begin_pass(max_texture_side, text_alpha_from_coverage)
};

if is_new || fonts_recreated {
for (&character, image) in &self.color_glyphs {
fonts.register_color_glyph(character, image.clone());
}
}
}

Expand Down Expand Up @@ -3601,6 +3618,20 @@ impl Context {
}
}

/// Register a bitmap glyph (such as an emoji sprite) that should be rendered verbatim whenever
/// the given character appears in text.
///
/// Call this during initialization, before you start queuing lots of text, to avoid
/// unnecessary atlas churn.
pub fn register_color_glyph(&self, character: char, image: crate::ColorImage) {
self.register_color_glyph_arc(character, Arc::new(image));
}

/// Same as [`Self::register_color_glyph`], but lets you share the underlying image allocation.
pub fn register_color_glyph_arc(&self, character: char, image: Arc<crate::ColorImage>) {
self.write(|ctx| ctx.register_color_glyph_arc(character, image));
}

/// Release all memory and textures related to images used in [`Ui::image`] or [`crate::Image`].
///
/// If you attempt to load any images again, they will be reloaded from scratch.
Expand Down
17 changes: 3 additions & 14 deletions crates/egui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,20 +598,9 @@ macro_rules! github_link_file {
/// The minus character: <https://www.compart.com/en/unicode/U+2212>
pub(crate) const MINUS_CHAR_STR: &str = "−";

/// The default egui fonts supports around 1216 emojis in total.
/// Here are some of the most useful:
/// ∞⊗⎗⎘⎙⏏⏴⏵⏶⏷
/// ⏩⏪⏭⏮⏸⏹⏺■▶📾🔀🔁🔃
/// ☀☁★☆☐☑☜☝☞☟⛃⛶✔
/// ↺↻⟲⟳⬅➡⬆⬇⬈⬉⬊⬋⬌⬍⮨⮩⮪⮫
/// ♡
/// 📅📆
/// 📈📉📊
/// 📋📌📎📤📥🔆
/// 🔈🔉🔊🔍🔎🔗🔘
/// 🕓🖧🖩🖮🖱🖴🖵🖼🗀🗁🗋🗐🗑🗙🚫❓
///
/// NOTE: In egui all emojis are monochrome!
/// egui ships with monochrome glyphs (sourced from Ubuntu/Noto), but no longer bundles a color emoji atlas by default.
/// Opt into color sprites by adding the `egui_noto_emoji` crate to your project and calling
/// [`egui_noto_emoji::install`] early during startup.
///
/// You can explore them all in the Font Book in [the online demo](https://www.egui.rs/#demo).
///
Expand Down
1 change: 1 addition & 0 deletions crates/egui_demo_app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ eframe = { workspace = true, default-features = false, features = ["web_screen_r
egui = { workspace = true, features = ["callstack", "default"] }
egui_demo_lib = { workspace = true, features = ["default", "chrono"] }
egui_extras = { workspace = true, features = ["default", "image"] }
egui_noto_emoji.workspace = true
image = { workspace = true, default-features = false, features = [
# Ensure we can display the test images
"png",
Expand Down
2 changes: 2 additions & 0 deletions crates/egui_demo_app/src/wrap_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ impl WrapApp {
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
// This gives us image support:
egui_extras::install_image_loaders(&cc.egui_ctx);
// Opt-in emoji support now lives in a separate crate, so demos stay representative.
egui_noto_emoji::install(&cc.egui_ctx);

#[cfg(feature = "accessibility_inspector")]
cc.egui_ctx
Expand Down
5 changes: 4 additions & 1 deletion crates/egui_demo_lib/benches/benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,10 @@ pub fn criterion_benchmark(c: &mut Criterion) {

let mut rng = rand::rng();
b.iter(|| {
fonts.begin_pass(max_texture_side, egui::epaint::AlphaFromCoverage::default());
let _ = fonts.begin_pass(
max_texture_side,
egui::epaint::AlphaFromCoverage::default(),
);

// Delete a random character, simulating a user making an edit in a long file:
let mut new_string = string.clone();
Expand Down
30 changes: 30 additions & 0 deletions crates/egui_noto_emoji/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "egui_noto_emoji"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors = ["Emil Ernerfeldt <[email protected]>"]
description = "Optional Noto emoji bundle for egui"
homepage = "https://github.com/emilk/egui/tree/main/crates/egui_noto_emoji"
license = "MIT OR Apache-2.0"
repository = "https://github.com/emilk/egui/tree/main/crates/egui_noto_emoji"

[lints]
workspace = true

[features]
default = []

## Bundle the low-resolution vibrant emoji atlas instead of the default mid-resolution one.
emoji_low_res = []

## Bundle the high-resolution vibrant emoji atlas instead of the default mid-resolution one.
emoji_high_res = []

## Turn on the `log` feature, so we can log texture loading failures.
log = ["dep:log"]

[dependencies]
egui = { workspace = true }
png = "0.17"
log = { workspace = true, optional = true }
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added crates/egui_noto_emoji/assets/emoji/noto_high.bin
Binary file not shown.
Binary file not shown.
Binary file added crates/egui_noto_emoji/assets/emoji/noto_low.bin
Binary file not shown.
Binary file not shown.
Binary file added crates/egui_noto_emoji/assets/emoji/noto_mid.bin
Binary file not shown.
38 changes: 38 additions & 0 deletions crates/egui_noto_emoji/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! Optional emoji bundle for egui.
//!
//! This crate keeps the heavy atlas and loader in a separate crate so core egui stays lean.

mod store;

pub use store::{EmojiEntry, EmojiStore};

use egui::Context;

/// Register the bundled emoji atlas on the provided [`egui::Context`].
///
/// Call this once during startup, right after you have access to the context.
pub fn install(ctx: &Context) {
register_store(ctx, &EmojiStore::builtin());
}

/// Register every emoji entry in a store.
pub fn register_store(ctx: &Context, store: &EmojiStore) {
for entry in store.entries() {
register_entry(ctx, entry);
}
}

/// Register a single emoji sprite, keeping ASCII digits/#/* rendered by the base fonts.
pub fn register_entry(ctx: &Context, entry: &EmojiEntry) {
if is_keycap_component(entry.ch()) {
return;
}

ctx.register_color_glyph_arc(entry.ch(), entry.image_arc());
}

/// Single ASCII characters that are part of the keycap emoji sequences.
/// Those sequences require multiple code points, so keep the plain glyphs rendered by the base fonts.
fn is_keycap_component(c: char) -> bool {
matches!(c, '#' | '*' | '0'..='9')
}
Loading