1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-30 13:20:05 -04:00

Merge branch 'lucas/atoms-preferred-size' into lucas/experiments/measure-widget-size

# Conflicts:
#	crates/egui/src/ui.rs
#	crates/egui/src/widgets/button.rs
#	crates/egui/src/widgets/label.rs
#	crates/egui_demo_lib/src/demo/popups.rs
#	crates/egui_extras/src/layout.rs
#	crates/epaint/src/text/text_layout_types.rs
This commit is contained in:
lucasmerlin
2025-06-16 09:52:22 +02:00
389 changed files with 8660 additions and 3857 deletions

View File

@@ -1,10 +1,10 @@
# `egui` and `eframe` examples
All the examples in this folder uses [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe) to set up a window for [`egui`](https://github.com/emilk/egui/). Some examples are specific to `eframe`, but many are applicable to any `egui` integration.
All the examples in this folder uses [`eframe`](https://github.com/emilk/egui/tree/main/crates/eframe) to set up a window for [`egui`](https://github.com/emilk/egui/). Some examples are specific to `eframe`, but many are applicable to any `egui` integration.
There are a lot more examples at <https://www.egui.rs>, and it has links to the source code of each example.
Also check out the official docs at <https://docs.rs/egui> and <https://docs.rs/eframe>.
Note that all the examples on `master` are for the latest `master` version of `egui`.
Note that all the examples on `main` are for the latest `main` version of `egui`.
If you want to look for examples for a specific version of egui, go to that tag, e.g. <https://github.com/emilk/egui/tree/latest/examples>.

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["tami5 <kkharji@proton.me>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Varphone Wong <varphone@qq.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]

View File

@@ -3,7 +3,7 @@ name = "custom_style"
version = "0.1.0"
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]

View File

@@ -4,7 +4,7 @@
use eframe::egui::{
self, global_theme_preference_buttons, style::Selection, Color32, Stroke, Style, Theme,
};
use egui_demo_lib::{View, WidgetGallery};
use egui_demo_lib::{View as _, WidgetGallery};
fn main() -> eframe::Result {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]

View File

@@ -0,0 +1,25 @@
[package]
name = "external_eventloop"
version = "0.1.0"
authors = ["Will Brown <opensource@rebeagle.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.84"
publish = false
[lints]
workspace = true
[dependencies]
eframe = { workspace = true, features = [
"default",
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = { version = "0.10", default-features = false, features = [
"auto-color",
"humantime",
] }
winit = { workspace = true }

View File

@@ -0,0 +1,7 @@
Example running an eframe application on an external eventloop.
This allows you to run your eframe application alongside other windows and/or toolkits on the same event loop.
```sh
cargo run -p external_eventloop
```

View File

@@ -0,0 +1,89 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
#![allow(rustdoc::missing_crate_level_docs)] // it's an example
use eframe::{egui, UserEvent};
use std::{cell::Cell, rc::Rc};
use winit::event_loop::{ControlFlow, EventLoop};
fn main() -> eframe::Result {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]),
..Default::default()
};
let eventloop = EventLoop::<UserEvent>::with_user_event().build().unwrap();
eventloop.set_control_flow(ControlFlow::Poll);
let mut winit_app = eframe::create_native(
"External Eventloop Application",
options,
Box::new(|_| Ok(Box::<MyApp>::default())),
&eventloop,
);
eventloop.run_app(&mut winit_app)?;
Ok(())
}
struct MyApp {
value: Rc<Cell<u32>>,
spin: bool,
blinky: bool,
}
impl Default for MyApp {
fn default() -> Self {
Self {
value: Rc::new(Cell::new(42)),
spin: false,
blinky: false,
}
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("My External Eventloop Application");
ui.horizontal(|ui| {
if ui.button("Increment Now").clicked() {
self.value.set(self.value.get() + 1);
}
});
ui.label(format!("Value: {}", self.value.get()));
if ui.button("Toggle Spinner").clicked() {
self.spin = !self.spin;
}
if ui.button("Toggle Blinky").clicked() {
self.blinky = !self.blinky;
}
if self.spin {
ui.spinner();
}
if self.blinky {
let now = ui.ctx().input(|i| i.time);
let blink = now % 1.0 < 0.5;
egui::Frame::new()
.inner_margin(3)
.corner_radius(5)
.fill(if blink {
egui::Color32::RED
} else {
egui::Color32::TRANSPARENT
})
.show(ui, |ui| {
ui.label("Blinky!");
});
ctx.request_repaint_after_secs((0.5 - (now % 0.5)) as f32);
}
});
}
}

View File

@@ -0,0 +1,35 @@
[package]
name = "external_eventloop_async"
version = "0.1.0"
authors = ["Will Brown <opensource@rebeagle.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.84"
publish = false
[lints]
workspace = true
[features]
linux-example = []
[[bin]]
name = "external_eventloop_async"
required-features = ["linux-example"]
[dependencies]
eframe = { workspace = true, features = [
"default",
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = { version = "0.10", default-features = false, features = [
"auto-color",
"humantime",
] }
log = { workspace = true }
winit = { workspace = true }
tokio = { version = "1", features = ["rt", "time", "net"] }

View File

@@ -0,0 +1,10 @@
Example running an eframe application on an external eventloop on top of a tokio executor on Linux.
By running the event loop, eframe, and tokio in the same thread, one can leverage local async tasks.
These tasks can share data with the UI without the need for locks or message passing.
In tokio CPU-bound async tasks can be run with `spawn_blocking` to avoid impacting the UI frame rate.
```sh
cargo run -p external_eventloop_async --features linux-example
```

View File

@@ -0,0 +1,130 @@
use eframe::{egui, EframePumpStatus, UserEvent};
use std::{cell::Cell, io, os::fd::AsRawFd as _, rc::Rc, time::Duration};
use tokio::task::LocalSet;
use winit::event_loop::{ControlFlow, EventLoop};
pub fn run() -> io::Result<()> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]),
..Default::default()
};
let mut eventloop = EventLoop::<UserEvent>::with_user_event().build().unwrap();
eventloop.set_control_flow(ControlFlow::Poll);
let mut winit_app = eframe::create_native(
"External Eventloop Application",
options,
Box::new(|_| Ok(Box::<MyApp>::default())),
&eventloop,
);
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let local = LocalSet::new();
local.block_on(&rt, async {
let eventloop_fd = tokio::io::unix::AsyncFd::new(eventloop.as_raw_fd())?;
let mut control_flow = ControlFlow::Poll;
loop {
let mut guard = match control_flow {
ControlFlow::Poll => None,
ControlFlow::Wait => Some(eventloop_fd.readable().await?),
ControlFlow::WaitUntil(deadline) => {
tokio::time::timeout_at(deadline.into(), eventloop_fd.readable())
.await
.ok()
.transpose()?
}
};
match winit_app.pump_eframe_app(&mut eventloop, None) {
EframePumpStatus::Continue(next) => control_flow = next,
EframePumpStatus::Exit(code) => {
log::info!("exit code: {code}");
break;
}
}
if let Some(mut guard) = guard.take() {
guard.clear_ready();
}
}
Ok::<_, io::Error>(())
})
}
struct MyApp {
value: Rc<Cell<u32>>,
spin: bool,
blinky: bool,
}
impl Default for MyApp {
fn default() -> Self {
Self {
value: Rc::new(Cell::new(42)),
spin: false,
blinky: false,
}
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("My External Eventloop Application");
ui.horizontal(|ui| {
if ui.button("Increment Now").clicked() {
self.value.set(self.value.get() + 1);
}
if ui.button("Increment Later").clicked() {
let value = self.value.clone();
let ctx = ctx.clone();
tokio::task::spawn_local(async move {
tokio::time::sleep(Duration::from_secs(1)).await;
value.set(value.get() + 1);
ctx.request_repaint();
});
}
});
ui.label(format!("Value: {}", self.value.get()));
if ui.button("Toggle Spinner").clicked() {
self.spin = !self.spin;
}
if ui.button("Toggle Blinky").clicked() {
self.blinky = !self.blinky;
}
if self.spin {
ui.spinner();
}
if self.blinky {
let now = ui.ctx().input(|i| i.time);
let blink = now % 1.0 < 0.5;
egui::Frame::new()
.inner_margin(3)
.corner_radius(5)
.fill(if blink {
egui::Color32::RED
} else {
egui::Color32::TRANSPARENT
})
.show(ui, |ui| {
ui.label("Blinky!");
});
ctx.request_repaint_after_secs((0.5 - (now % 0.5)) as f32);
}
});
}
}

View File

@@ -0,0 +1,15 @@
#![allow(rustdoc::missing_crate_level_docs)] // it's an example
#[cfg(target_os = "linux")]
mod app;
#[cfg(target_os = "linux")]
fn main() -> std::io::Result<()> {
app::run()
}
// Do not check `app` on unsupported platforms when check "--all-features" is used in CI.
#[cfg(not(target_os = "linux"))]
fn main() {
println!("This example only supports Linux.");
}

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]
@@ -20,4 +20,4 @@ env_logger = { version = "0.10", default-features = false, features = [
"auto-color",
"humantime",
] }
rfd = "0.15"
rfd = "0.15.3"

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
# `unsafe_code` is required for `#[no_mangle]`, disable workspace lints to workaround lint error.
@@ -12,11 +12,17 @@ publish = false
# workspace = true
[lib]
crate-type = ["cdylib"]
# cdylib is required for Android, lib is required for desktop
crate-type = ["cdylib", "lib"]
[dependencies]
eframe = { workspace = true, features = ["default", "android-native-activity"] }
eframe = { workspace = true, default-features = false, features = [
"default_fonts",
"glow",
"android-native-activity",
] }
egui_demo_lib = { workspace = true, features = ["chrono"] }
# For image support:
egui_extras = { workspace = true, features = ["default", "image"] }
@@ -27,3 +33,7 @@ android_logger = "0.14"
[package.metadata.android]
build_targets = ["armv7-linux-androideabi", "aarch64-linux-android"]
[package.metadata.android.sdk]
min_sdk_version = 23
target_sdk_version = 35

View File

@@ -14,7 +14,11 @@ cargo install \
Build and run:
```sh
cargo apk run -p hello_android
# Run on android
cargo apk run -p hello_android --lib
# Run on your desktop
cargo run -p hello_android
```
![](screenshot.png)

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7add91d7d6b73f48e98f20d84cba3bd3a950cf97aa31f5e9fa93da9af98e876c
size 120019
oid sha256:16bb465d73b7cf8133aee8cdb773a10d213ad23359a21c0bc2af3e4f9893057f
size 507047

View File

@@ -1,15 +1,14 @@
#![cfg(target_os = "android")]
#![allow(rustdoc::missing_crate_level_docs)] // it's an example
#![doc = include_str!("../README.md")]
use android_logger::Config;
use eframe::egui;
use log::LevelFilter;
use winit::platform::android::activity::AndroidApp;
use eframe::{egui, CreationContext};
#[cfg(target_os = "android")]
#[no_mangle]
fn android_main(app: AndroidApp) {
fn android_main(app: winit::platform::android::activity::AndroidApp) {
// Log to android output
android_logger::init_once(Config::default().with_max_level(LevelFilter::Info));
android_logger::init_once(
android_logger::Config::default().with_max_level(log::LevelFilter::Info),
);
let options = eframe::NativeOptions {
android_app: Some(app),
@@ -18,48 +17,34 @@ fn android_main(app: AndroidApp) {
eframe::run_native(
"My egui App",
options,
Box::new(|cc| {
// This gives us image support:
egui_extras::install_image_loaders(&cc.egui_ctx);
Ok(Box::<MyApp>::default())
}),
Box::new(|cc| Ok(Box::new(MyApp::new(cc)))),
)
.unwrap()
}
struct MyApp {
name: String,
age: u32,
pub struct MyApp {
demo: egui_demo_lib::DemoWindows,
}
impl Default for MyApp {
fn default() -> Self {
impl MyApp {
pub fn new(cc: &CreationContext) -> Self {
egui_extras::install_image_loaders(&cc.egui_ctx);
Self {
name: "Arthur".to_owned(),
age: 42,
demo: egui_demo_lib::DemoWindows::default(),
}
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("My egui Application");
ui.horizontal(|ui| {
let name_label = ui.label("Your name: ");
ui.text_edit_singleline(&mut self.name)
.labelled_by(name_label.id);
});
ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));
if ui.button("Increment").clicked() {
self.age += 1;
}
ui.label(format!("Hello '{}', age {}", self.name, self.age));
ui.image(egui::include_image!(
"../../../crates/egui/assets/ferris.png"
));
// Reserve some space at the top so the demo ui isn't hidden behind the android status bar
// TODO(lucasmerlin): This is a pretty big hack, should be fixed once safe_area implemented
// for android:
// https://github.com/rust-windowing/winit/issues/3910
egui::TopBottomPanel::top("status_bar_space").show(ctx, |ui| {
ui.set_height(32.0);
});
self.demo.ui(ctx);
}
}

View File

@@ -0,0 +1,9 @@
use hello_android::MyApp;
fn main() -> eframe::Result {
eframe::run_native(
"hello_android",
Default::default(),
Box::new(|cc| Ok(Box::new(MyApp::new(cc)))),
)
}

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Maxim Osipenko <maxim1999max@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Jan Procházka <github.com/jprochazk>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Jose Palazon <jose@palako.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[package.metadata.cargo-machete]

View File

@@ -165,7 +165,7 @@ fn start_puffin_server() {
// We can store the server if we want, but in this case we just want
// it to keep running. Dropping it closes the server, so let's not drop it!
#[allow(clippy::mem_forget)]
#[expect(clippy::mem_forget)]
std::mem::forget(puffin_server);
}
Err(err) => {

View File

@@ -7,7 +7,7 @@ authors = [
]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]

View File

@@ -7,8 +7,7 @@ Expected order of execution:
- Similarly, when the second window is closed after a delay a third will be shown.
- Once the third is closed the program will stop.
NOTE: this doesn't work on Mac due to <https://github.com/rust-windowing/winit/issues/2431>.
See also <https://github.com/emilk/egui/issues/1918>.
NOTE: this doesn't work on Mac. See also <https://github.com/emilk/egui/issues/1918>.
```sh
cargo run -p serial_windows

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["TicClick <ya@ticclick.ch>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.81"
rust-version = "1.84"
publish = false
[lints]