Compare commits
10 Commits
9253acd7f3
...
eb1756df3f
| Author | SHA1 | Date |
|---|---|---|
|
|
eb1756df3f | |
|
|
978ec6c870 | |
|
|
0ef57d5a1d | |
|
|
d06c28cb15 | |
|
|
787c467d30 | |
|
|
33cc8ef180 | |
|
|
5ae6d6d901 | |
|
|
74dce787af | |
|
|
d5320fe827 | |
|
|
0b9bb5f494 |
16
CHANGELOG.md
16
CHANGELOG.md
|
|
@ -14,6 +14,22 @@ 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.
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.33.2 - 2025-11-13
|
||||||
|
### ⭐ Added
|
||||||
|
* Add `Plugin::on_widget_under_pointer` to support widget inspector [#7652](https://github.com/emilk/egui/pull/7652) by [@juancampa](https://github.com/juancampa)
|
||||||
|
* Add `Response::total_drag_delta` and `PointerState::total_drag_delta` [#7708](https://github.com/emilk/egui/pull/7708) by [@emilk](https://github.com/emilk)
|
||||||
|
|
||||||
|
### 🔧 Changed
|
||||||
|
* Improve accessibility and testability of `ComboBox` [#7658](https://github.com/emilk/egui/pull/7658) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||||
|
|
||||||
|
### 🐛 Fixed
|
||||||
|
* Fix `profiling::scope` compile error when profiling using `tracing` backend [#7646](https://github.com/emilk/egui/pull/7646) by [@PPakalns](https://github.com/PPakalns)
|
||||||
|
* Fix edge cases in "smart aiming" in sliders [#7680](https://github.com/emilk/egui/pull/7680) by [@emilk](https://github.com/emilk)
|
||||||
|
* Hide scroll bars when dragging other things [#7689](https://github.com/emilk/egui/pull/7689) by [@emilk](https://github.com/emilk)
|
||||||
|
* Prevent widgets sometimes appearing to move relative to each other [#7710](https://github.com/emilk/egui/pull/7710) by [@emilk](https://github.com/emilk)
|
||||||
|
* Fix `ui.response().interact(Sense::click())` being flakey [#7713](https://github.com/emilk/egui/pull/7713) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||||
|
|
||||||
|
|
||||||
## 0.33.0 - 2025-10-09 - `egui::Plugin`, better kerning, kitdiff viewer
|
## 0.33.0 - 2025-10-09 - `egui::Plugin`, better kerning, kitdiff viewer
|
||||||
Highlights from this release:
|
Highlights from this release:
|
||||||
- `egui::Plugin` a improved way to create and access egui plugins
|
- `egui::Plugin` a improved way to create and access egui plugins
|
||||||
|
|
|
||||||
41
Cargo.lock
41
Cargo.lock
|
|
@ -1248,7 +1248,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ecolor"
|
name = "ecolor"
|
||||||
version = "0.33.0"
|
version = "0.33.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cint",
|
"cint",
|
||||||
|
|
@ -1260,7 +1260,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "eframe"
|
name = "eframe"
|
||||||
version = "0.33.0"
|
version = "0.33.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
|
@ -1299,7 +1299,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui"
|
name = "egui"
|
||||||
version = "0.33.0"
|
version = "0.33.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"ahash",
|
"ahash",
|
||||||
|
|
@ -1319,7 +1319,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui-wgpu"
|
name = "egui-wgpu"
|
||||||
version = "0.33.0"
|
version = "0.33.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
|
@ -1337,7 +1337,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui-winit"
|
name = "egui-winit"
|
||||||
version = "0.33.0"
|
version = "0.33.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit_winit",
|
"accesskit_winit",
|
||||||
"arboard",
|
"arboard",
|
||||||
|
|
@ -1360,7 +1360,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui_demo_app"
|
name = "egui_demo_app"
|
||||||
version = "0.33.0"
|
version = "0.33.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_consumer",
|
"accesskit_consumer",
|
||||||
|
|
@ -1390,7 +1390,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui_demo_lib"
|
name = "egui_demo_lib"
|
||||||
version = "0.33.0"
|
version = "0.33.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"criterion",
|
"criterion",
|
||||||
|
|
@ -1407,7 +1407,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui_extras"
|
name = "egui_extras"
|
||||||
version = "0.33.0"
|
version = "0.33.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|
@ -1426,7 +1426,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui_glow"
|
name = "egui_glow"
|
||||||
version = "0.33.0"
|
version = "0.33.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"document-features",
|
"document-features",
|
||||||
|
|
@ -1445,7 +1445,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui_kittest"
|
name = "egui_kittest"
|
||||||
version = "0.33.1"
|
version = "0.33.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dify",
|
"dify",
|
||||||
"document-features",
|
"document-features",
|
||||||
|
|
@ -1463,7 +1463,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "egui_tests"
|
name = "egui_tests"
|
||||||
version = "0.33.0"
|
version = "0.33.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"egui",
|
"egui",
|
||||||
"egui_extras",
|
"egui_extras",
|
||||||
|
|
@ -1493,7 +1493,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "emath"
|
name = "emath"
|
||||||
version = "0.33.0"
|
version = "0.33.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"document-features",
|
"document-features",
|
||||||
|
|
@ -1591,7 +1591,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "epaint"
|
name = "epaint"
|
||||||
version = "0.33.0"
|
version = "0.33.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ab_glyph",
|
"ab_glyph",
|
||||||
"ahash",
|
"ahash",
|
||||||
|
|
@ -1613,7 +1613,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "epaint_default_fonts"
|
name = "epaint_default_fonts"
|
||||||
version = "0.33.0"
|
version = "0.33.2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
|
|
@ -3425,7 +3425,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "popups"
|
name = "popups"
|
||||||
version = "0.33.0"
|
version = "0.33.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"eframe",
|
"eframe",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
|
@ -4332,6 +4332,15 @@ dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "text_input_test"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"eframe",
|
||||||
|
"egui_extras",
|
||||||
|
"env_logger",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.66"
|
version = "1.0.66"
|
||||||
|
|
@ -5748,7 +5757,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xtask"
|
name = "xtask"
|
||||||
version = "0.33.0"
|
version = "0.33.2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yaml-rust"
|
name = "yaml-rust"
|
||||||
|
|
|
||||||
26
Cargo.toml
26
Cargo.toml
|
|
@ -24,7 +24,7 @@ members = [
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
rust-version = "1.88"
|
rust-version = "1.88"
|
||||||
version = "0.33.0"
|
version = "0.33.2"
|
||||||
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|
@ -55,18 +55,18 @@ opt-level = 2
|
||||||
|
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
emath = { version = "0.33.0", path = "crates/emath", default-features = false }
|
emath = { version = "0.33.2", path = "crates/emath", default-features = false }
|
||||||
ecolor = { version = "0.33.0", path = "crates/ecolor", default-features = false }
|
ecolor = { version = "0.33.2", path = "crates/ecolor", default-features = false }
|
||||||
epaint = { version = "0.33.0", path = "crates/epaint", default-features = false }
|
epaint = { version = "0.33.2", path = "crates/epaint", default-features = false }
|
||||||
epaint_default_fonts = { version = "0.33.0", path = "crates/epaint_default_fonts" }
|
epaint_default_fonts = { version = "0.33.2", path = "crates/epaint_default_fonts" }
|
||||||
egui = { version = "0.33.0", path = "crates/egui", default-features = false }
|
egui = { version = "0.33.2", path = "crates/egui", default-features = false }
|
||||||
egui-winit = { version = "0.33.0", path = "crates/egui-winit", default-features = false }
|
egui-winit = { version = "0.33.2", path = "crates/egui-winit", default-features = false }
|
||||||
egui_extras = { version = "0.33.0", path = "crates/egui_extras", default-features = false }
|
egui_extras = { version = "0.33.2", path = "crates/egui_extras", default-features = false }
|
||||||
egui-wgpu = { version = "0.33.0", path = "crates/egui-wgpu", default-features = false }
|
egui-wgpu = { version = "0.33.2", path = "crates/egui-wgpu", default-features = false }
|
||||||
egui_demo_lib = { version = "0.33.0", path = "crates/egui_demo_lib", default-features = false }
|
egui_demo_lib = { version = "0.33.2", path = "crates/egui_demo_lib", default-features = false }
|
||||||
egui_glow = { version = "0.33.0", path = "crates/egui_glow", default-features = false }
|
egui_glow = { version = "0.33.2", path = "crates/egui_glow", default-features = false }
|
||||||
egui_kittest = { version = "0.33.1", path = "crates/egui_kittest", default-features = false }
|
egui_kittest = { version = "0.33.2", path = "crates/egui_kittest", default-features = false }
|
||||||
eframe = { version = "0.33.0", path = "crates/eframe", default-features = false }
|
eframe = { version = "0.33.2", path = "crates/eframe", default-features = false }
|
||||||
|
|
||||||
accesskit = "0.21.1"
|
accesskit = "0.21.1"
|
||||||
accesskit_consumer = "0.30.1"
|
accesskit_consumer = "0.30.1"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,10 @@ 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.
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.33.2 - 2025-11-13
|
||||||
|
Nothing new
|
||||||
|
|
||||||
|
|
||||||
## 0.33.0 - 2025-10-09
|
## 0.33.0 - 2025-10-09
|
||||||
* Align `Color32` to 4 bytes [#7318](https://github.com/emilk/egui/pull/7318) by [@anti-social](https://github.com/anti-social)
|
* Align `Color32` to 4 bytes [#7318](https://github.com/emilk/egui/pull/7318) by [@anti-social](https://github.com/anti-social)
|
||||||
* Make the `hex_color` macro `const` [#7444](https://github.com/emilk/egui/pull/7444) by [@YgorSouza](https://github.com/YgorSouza)
|
* Make the `hex_color` macro `const` [#7444](https://github.com/emilk/egui/pull/7444) by [@YgorSouza](https://github.com/YgorSouza)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,11 @@ 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.
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.33.2 - 2025-11-13
|
||||||
|
* Fix jittering during window resize on MacOS for WGPU/Metal [#7641](https://github.com/emilk/egui/pull/7641) by [@aspcartman](https://github.com/aspcartman)
|
||||||
|
* Make sure `native_pixels_per_point` is set during app creation [#7683](https://github.com/emilk/egui/pull/7683) by [@emilk](https://github.com/emilk)
|
||||||
|
|
||||||
|
|
||||||
## 0.33.0 - 2025-10-09
|
## 0.33.0 - 2025-10-09
|
||||||
### ⭐ Added
|
### ⭐ Added
|
||||||
* Add an option to limit the repaint rate in the web runner [#7482](https://github.com/emilk/egui/pull/7482) by [@s-nie](https://github.com/s-nie)
|
* Add an option to limit the repaint rate in the web runner [#7482](https://github.com/emilk/egui/pull/7482) by [@s-nie](https://github.com/s-nie)
|
||||||
|
|
|
||||||
|
|
@ -1041,11 +1041,23 @@ impl GlutinWindowContext {
|
||||||
|
|
||||||
let mut viewport_from_window = HashMap::default();
|
let mut viewport_from_window = HashMap::default();
|
||||||
let mut window_from_viewport = OrderedViewportIdMap::default();
|
let mut window_from_viewport = OrderedViewportIdMap::default();
|
||||||
let mut info = ViewportInfo::default();
|
let mut viewport_info = ViewportInfo::default();
|
||||||
if let Some(window) = &window {
|
if let Some(window) = &window {
|
||||||
viewport_from_window.insert(window.id(), ViewportId::ROOT);
|
viewport_from_window.insert(window.id(), ViewportId::ROOT);
|
||||||
window_from_viewport.insert(ViewportId::ROOT, window.id());
|
window_from_viewport.insert(ViewportId::ROOT, window.id());
|
||||||
egui_winit::update_viewport_info(&mut info, egui_ctx, window, true);
|
egui_winit::update_viewport_info(&mut viewport_info, egui_ctx, window, true);
|
||||||
|
|
||||||
|
// Tell egui right away about native_pixels_per_point etc,
|
||||||
|
// so that the app knows about it during app creation:
|
||||||
|
let pixels_per_point = egui_winit::pixels_per_point(egui_ctx, window);
|
||||||
|
|
||||||
|
egui_ctx.input_mut(|i| {
|
||||||
|
i.raw
|
||||||
|
.viewports
|
||||||
|
.insert(ViewportId::ROOT, viewport_info.clone());
|
||||||
|
|
||||||
|
i.pixels_per_point = pixels_per_point;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut viewports = OrderedViewportIdMap::default();
|
let mut viewports = OrderedViewportIdMap::default();
|
||||||
|
|
@ -1056,7 +1068,7 @@ impl GlutinWindowContext {
|
||||||
class: ViewportClass::Root,
|
class: ViewportClass::Root,
|
||||||
builder: viewport_builder,
|
builder: viewport_builder,
|
||||||
deferred_commands: vec![],
|
deferred_commands: vec![],
|
||||||
info,
|
info: viewport_info,
|
||||||
actions_requested: Default::default(),
|
actions_requested: Default::default(),
|
||||||
viewport_ui_cb: None,
|
viewport_ui_cb: None,
|
||||||
gl_surface: None,
|
gl_surface: None,
|
||||||
|
|
|
||||||
|
|
@ -199,6 +199,22 @@ impl<'app> WgpuWinitApp<'app> {
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let mut viewport_info = ViewportInfo::default();
|
||||||
|
egui_winit::update_viewport_info(&mut viewport_info, &egui_ctx, &window, true);
|
||||||
|
|
||||||
|
{
|
||||||
|
// Tell egui right away about native_pixels_per_point etc,
|
||||||
|
// so that the app knows about it during app creation:
|
||||||
|
let pixels_per_point = egui_winit::pixels_per_point(&egui_ctx, &window);
|
||||||
|
|
||||||
|
egui_ctx.input_mut(|i| {
|
||||||
|
i.raw
|
||||||
|
.viewports
|
||||||
|
.insert(ViewportId::ROOT, viewport_info.clone());
|
||||||
|
i.pixels_per_point = pixels_per_point;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let window = Arc::new(window);
|
let window = Arc::new(window);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -278,9 +294,6 @@ impl<'app> WgpuWinitApp<'app> {
|
||||||
let mut viewport_from_window = HashMap::default();
|
let mut viewport_from_window = HashMap::default();
|
||||||
viewport_from_window.insert(window.id(), ViewportId::ROOT);
|
viewport_from_window.insert(window.id(), ViewportId::ROOT);
|
||||||
|
|
||||||
let mut info = ViewportInfo::default();
|
|
||||||
egui_winit::update_viewport_info(&mut info, &egui_ctx, &window, true);
|
|
||||||
|
|
||||||
let mut viewports = Viewports::default();
|
let mut viewports = Viewports::default();
|
||||||
viewports.insert(
|
viewports.insert(
|
||||||
ViewportId::ROOT,
|
ViewportId::ROOT,
|
||||||
|
|
@ -289,7 +302,7 @@ impl<'app> WgpuWinitApp<'app> {
|
||||||
class: ViewportClass::Root,
|
class: ViewportClass::Root,
|
||||||
builder,
|
builder,
|
||||||
deferred_commands: vec![],
|
deferred_commands: vec![],
|
||||||
info,
|
info: viewport_info,
|
||||||
actions_requested: Default::default(),
|
actions_requested: Default::default(),
|
||||||
viewport_ui_cb: None,
|
viewport_ui_cb: None,
|
||||||
window: Some(window),
|
window: Some(window),
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,14 @@ impl AppRunner {
|
||||||
o.zoom_factor = 1.0;
|
o.zoom_factor = 1.0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Tell egui right away about native_pixels_per_point
|
||||||
|
// so that the app knows about it during app creation:
|
||||||
|
egui_ctx.input_mut(|i| {
|
||||||
|
let viewport_info = i.raw.viewports.entry(egui::ViewportId::ROOT).or_default();
|
||||||
|
viewport_info.native_pixels_per_point = Some(super::native_pixels_per_point());
|
||||||
|
i.pixels_per_point = super::native_pixels_per_point();
|
||||||
|
});
|
||||||
|
|
||||||
let cc = epi::CreationContext {
|
let cc = epi::CreationContext {
|
||||||
egui_ctx: egui_ctx.clone(),
|
egui_ctx: egui_ctx.clone(),
|
||||||
integration_info: info.clone(),
|
integration_info: info.clone(),
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,10 @@ 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.
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.33.2 - 2025-11-13
|
||||||
|
* Fix jittering during window resize on MacOS for WGPU/Metal [#7641](https://github.com/emilk/egui/pull/7641) by [@aspcartman](https://github.com/aspcartman)
|
||||||
|
|
||||||
|
|
||||||
## 0.33.0 - 2025-10-09
|
## 0.33.0 - 2025-10-09
|
||||||
### 🔧 Changed
|
### 🔧 Changed
|
||||||
* Update wgpu to 26 and wasm-bindgen to 0.2.100 [#7540](https://github.com/emilk/egui/pull/7540) by [@Kumpelinus](https://github.com/Kumpelinus)
|
* Update wgpu to 26 and wasm-bindgen to 0.2.100 [#7540](https://github.com/emilk/egui/pull/7540) by [@Kumpelinus](https://github.com/Kumpelinus)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@ 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.
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.33.2 - 2025-11-13
|
||||||
|
* Don't enable `arboard` on iOS [#7663](https://github.com/emilk/egui/pull/7663) by [@irh](https://github.com/irh)
|
||||||
|
|
||||||
|
|
||||||
## 0.33.0 - 2025-10-09
|
## 0.33.0 - 2025-10-09
|
||||||
### ⭐ Added
|
### ⭐ Added
|
||||||
* Add rotation gesture support for trackpad sources [#7453](https://github.com/emilk/egui/pull/7453) by [@thatcomputerguy0101](https://github.com/thatcomputerguy0101)
|
* Add rotation gesture support for trackpad sources [#7453](https://github.com/emilk/egui/pull/7453) by [@thatcomputerguy0101](https://github.com/thatcomputerguy0101)
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,14 @@ pub struct State {
|
||||||
|
|
||||||
allow_ime: bool,
|
allow_ime: bool,
|
||||||
ime_rect_px: Option<egui::Rect>,
|
ime_rect_px: Option<egui::Rect>,
|
||||||
|
|
||||||
|
/// When true, IME events are completely ignored and text input relies solely
|
||||||
|
/// on keyboard events. This is a workaround for systems where IME causes issues,
|
||||||
|
/// such as IBus on Wayland where each character triggers an IME commit that
|
||||||
|
/// disrupts normal typing flow.
|
||||||
|
///
|
||||||
|
/// See <https://github.com/emilk/egui/issues/7485>
|
||||||
|
ime_disabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
|
@ -148,6 +156,8 @@ impl State {
|
||||||
|
|
||||||
allow_ime: false,
|
allow_ime: false,
|
||||||
ime_rect_px: None,
|
ime_rect_px: None,
|
||||||
|
|
||||||
|
ime_disabled: should_disable_ime_for_buggy_systems(),
|
||||||
};
|
};
|
||||||
|
|
||||||
slf.egui_input
|
slf.egui_input
|
||||||
|
|
@ -205,6 +215,30 @@ impl State {
|
||||||
self.allow_ime = allow;
|
self.allow_ime = allow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether IME is disabled as a workaround for buggy systems.
|
||||||
|
///
|
||||||
|
/// When IME is disabled, text input relies solely on keyboard events.
|
||||||
|
/// This is automatically enabled on systems where IME causes issues,
|
||||||
|
/// such as IBus on Wayland.
|
||||||
|
///
|
||||||
|
/// See <https://github.com/emilk/egui/issues/7485>
|
||||||
|
pub fn ime_disabled(&self) -> bool {
|
||||||
|
self.ime_disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Explicitly enable or disable the IME workaround.
|
||||||
|
///
|
||||||
|
/// When set to `true`, IME events are ignored and text input relies
|
||||||
|
/// solely on keyboard events. This is useful for systems where IME
|
||||||
|
/// causes issues, such as IBus on Wayland.
|
||||||
|
///
|
||||||
|
/// By default, this is automatically detected based on the environment.
|
||||||
|
///
|
||||||
|
/// See <https://github.com/emilk/egui/issues/7485>
|
||||||
|
pub fn set_ime_disabled(&mut self, disabled: bool) {
|
||||||
|
self.ime_disabled = disabled;
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn egui_ctx(&self) -> &egui::Context {
|
pub fn egui_ctx(&self) -> &egui::Context {
|
||||||
&self.egui_ctx
|
&self.egui_ctx
|
||||||
|
|
@ -350,6 +384,31 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowEvent::Ime(ime) => {
|
WindowEvent::Ime(ime) => {
|
||||||
|
// When IME is disabled as a workaround for buggy systems (like IBus on Wayland),
|
||||||
|
// we convert IME Commit events to regular Text events instead of using IME protocol.
|
||||||
|
// This works around the bug where IBus on Wayland sends each character as an
|
||||||
|
// IME commit which disrupts normal text editing flow.
|
||||||
|
// See https://github.com/emilk/egui/issues/7485
|
||||||
|
if self.ime_disabled {
|
||||||
|
// Only handle Commit events - convert them to regular Text events
|
||||||
|
if let winit::event::Ime::Commit(text) = ime {
|
||||||
|
if !text.is_empty() && text != "\n" && text != "\r" {
|
||||||
|
self.egui_input
|
||||||
|
.events
|
||||||
|
.push(egui::Event::Text(text.clone()));
|
||||||
|
return EventResponse {
|
||||||
|
repaint: true,
|
||||||
|
consumed: self.egui_ctx.wants_keyboard_input(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Ignore all other IME events (Enabled, Disabled, Preedit)
|
||||||
|
return EventResponse {
|
||||||
|
repaint: false,
|
||||||
|
consumed: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// on Mac even Cmd-C is pressed during ime, a `c` is pushed to Preedit.
|
// on Mac even Cmd-C is pressed during ime, a `c` is pushed to Preedit.
|
||||||
// So no need to check is_mac_cmd.
|
// So no need to check is_mac_cmd.
|
||||||
//
|
//
|
||||||
|
|
@ -907,7 +966,10 @@ impl State {
|
||||||
|
|
||||||
self.set_cursor_icon(window, cursor_icon);
|
self.set_cursor_icon(window, cursor_icon);
|
||||||
|
|
||||||
let allow_ime = ime.is_some();
|
// When IME is disabled as a workaround for buggy systems, don't enable IME at all.
|
||||||
|
// This ensures the system doesn't try to intercept keyboard input.
|
||||||
|
// See https://github.com/emilk/egui/issues/7485
|
||||||
|
let allow_ime = ime.is_some() && !self.ime_disabled;
|
||||||
if self.allow_ime != allow_ime {
|
if self.allow_ime != allow_ime {
|
||||||
self.allow_ime = allow_ime;
|
self.allow_ime = allow_ime;
|
||||||
profiling::scope!("set_ime_allowed");
|
profiling::scope!("set_ime_allowed");
|
||||||
|
|
@ -915,23 +977,28 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ime) = ime {
|
if let Some(ime) = ime {
|
||||||
let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
|
// Skip IME cursor positioning if IME is disabled
|
||||||
let ime_rect_px = pixels_per_point * ime.rect;
|
if self.ime_disabled {
|
||||||
if self.ime_rect_px != Some(ime_rect_px)
|
self.ime_rect_px = None;
|
||||||
|| self.egui_ctx.input(|i| !i.events.is_empty())
|
} else {
|
||||||
{
|
let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
|
||||||
self.ime_rect_px = Some(ime_rect_px);
|
let ime_rect_px = pixels_per_point * ime.rect;
|
||||||
profiling::scope!("set_ime_cursor_area");
|
if self.ime_rect_px != Some(ime_rect_px)
|
||||||
window.set_ime_cursor_area(
|
|| self.egui_ctx.input(|i| !i.events.is_empty())
|
||||||
winit::dpi::PhysicalPosition {
|
{
|
||||||
x: ime_rect_px.min.x,
|
self.ime_rect_px = Some(ime_rect_px);
|
||||||
y: ime_rect_px.min.y,
|
profiling::scope!("set_ime_cursor_area");
|
||||||
},
|
window.set_ime_cursor_area(
|
||||||
winit::dpi::PhysicalSize {
|
winit::dpi::PhysicalPosition {
|
||||||
width: ime_rect_px.width(),
|
x: ime_rect_px.min.x,
|
||||||
height: ime_rect_px.height(),
|
y: ime_rect_px.min.y,
|
||||||
},
|
},
|
||||||
);
|
winit::dpi::PhysicalSize {
|
||||||
|
width: ime_rect_px.width(),
|
||||||
|
height: ime_rect_px.height(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.ime_rect_px = None;
|
self.ime_rect_px = None;
|
||||||
|
|
@ -1073,6 +1140,71 @@ fn open_url_in_browser(_url: &str) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Detects if we're running on a system where IME is known to cause issues.
|
||||||
|
///
|
||||||
|
/// Currently detects IBus on Wayland, which has a bug where each character
|
||||||
|
/// triggers an IME commit that disrupts normal typing flow.
|
||||||
|
///
|
||||||
|
/// The detection checks:
|
||||||
|
/// 1. We're on Linux
|
||||||
|
/// 2. We're running under Wayland (XDG_SESSION_TYPE=wayland or WAYLAND_DISPLAY is set)
|
||||||
|
/// 3. IBus is the active input method (GTK_IM_MODULE=ibus or IBUS_* env vars are set)
|
||||||
|
///
|
||||||
|
/// Users can override this by setting the environment variable:
|
||||||
|
/// - `EGUI_IME_DISABLED=1` to force IME disabled (use keyboard events only)
|
||||||
|
/// - `EGUI_IME_DISABLED=0` to force IME enabled (normal behavior)
|
||||||
|
///
|
||||||
|
/// See <https://github.com/emilk/egui/issues/7485>
|
||||||
|
fn should_disable_ime_for_buggy_systems() -> bool {
|
||||||
|
// Allow explicit override via environment variable
|
||||||
|
if let Ok(val) = std::env::var("EGUI_IME_DISABLED") {
|
||||||
|
return val == "1" || val.eq_ignore_ascii_case("true");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only applies to Linux
|
||||||
|
if !cfg!(target_os = "linux") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're on Wayland
|
||||||
|
let is_wayland = std::env::var("XDG_SESSION_TYPE")
|
||||||
|
.map(|v| v == "wayland")
|
||||||
|
.unwrap_or(false)
|
||||||
|
|| std::env::var("WAYLAND_DISPLAY").is_ok();
|
||||||
|
|
||||||
|
if !is_wayland {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if IBus is the input method
|
||||||
|
// IBus can be detected via several environment variables:
|
||||||
|
// - GTK_IM_MODULE=ibus (GTK apps)
|
||||||
|
// - QT_IM_MODULE=ibus (Qt apps)
|
||||||
|
// - XMODIFIERS=@im=ibus (X11 input method)
|
||||||
|
// - IBUS_DAEMON_PID (set when ibus-daemon is running)
|
||||||
|
let is_ibus = std::env::var("GTK_IM_MODULE")
|
||||||
|
.map(|v| v == "ibus")
|
||||||
|
.unwrap_or(false)
|
||||||
|
|| std::env::var("QT_IM_MODULE")
|
||||||
|
.map(|v| v == "ibus")
|
||||||
|
.unwrap_or(false)
|
||||||
|
|| std::env::var("XMODIFIERS")
|
||||||
|
.map(|v| v.contains("ibus"))
|
||||||
|
.unwrap_or(false)
|
||||||
|
|| std::env::var("IBUS_DAEMON_PID").is_ok();
|
||||||
|
|
||||||
|
if is_ibus {
|
||||||
|
log::warn!(
|
||||||
|
"Detected IBus on Wayland - disabling IME to work around text input bug. \
|
||||||
|
Set EGUI_IME_DISABLED=0 to override. \
|
||||||
|
See https://github.com/emilk/egui/issues/7485"
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Winit sends special keys (backspace, delete, F1, …) as characters.
|
/// Winit sends special keys (backspace, delete, F1, …) as characters.
|
||||||
/// Ignore those.
|
/// Ignore those.
|
||||||
/// We also ignore '\r', '\n', '\t'.
|
/// We also ignore '\r', '\n', '\t'.
|
||||||
|
|
|
||||||
|
|
@ -525,11 +525,21 @@ impl Area {
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Used to prevent drift
|
||||||
|
let pivot_at_start_of_drag_id = id.with("pivot_at_drag_start");
|
||||||
|
|
||||||
if movable
|
if movable
|
||||||
&& move_response.dragged()
|
&& move_response.dragged()
|
||||||
&& let Some(pivot_pos) = &mut state.pivot_pos
|
&& let Some(pivot_pos) = &mut state.pivot_pos
|
||||||
{
|
{
|
||||||
*pivot_pos += move_response.drag_delta();
|
let pivot_at_start_of_drag = ctx.data_mut(|data| {
|
||||||
|
*data.get_temp_mut_or::<Pos2>(pivot_at_start_of_drag_id, *pivot_pos)
|
||||||
|
});
|
||||||
|
|
||||||
|
*pivot_pos =
|
||||||
|
pivot_at_start_of_drag + move_response.total_drag_delta().unwrap_or_default();
|
||||||
|
} else {
|
||||||
|
ctx.data_mut(|data| data.remove::<Pos2>(pivot_at_start_of_drag_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (move_response.dragged() || move_response.clicked())
|
if (move_response.dragged() || move_response.clicked())
|
||||||
|
|
@ -543,13 +553,14 @@ impl Area {
|
||||||
move_response
|
move_response
|
||||||
};
|
};
|
||||||
|
|
||||||
if constrain {
|
state.set_left_top_pos(round_area_position(
|
||||||
state.set_left_top_pos(
|
ctx,
|
||||||
Context::constrain_window_rect_to_area(state.rect(), constrain_rect).min,
|
if constrain {
|
||||||
);
|
Context::constrain_window_rect_to_area(state.rect(), constrain_rect).min
|
||||||
}
|
} else {
|
||||||
|
state.left_top_pos()
|
||||||
state.set_left_top_pos(state.left_top_pos());
|
},
|
||||||
|
));
|
||||||
|
|
||||||
// Update response with possibly moved/constrained rect:
|
// Update response with possibly moved/constrained rect:
|
||||||
move_response.rect = state.rect();
|
move_response.rect = state.rect();
|
||||||
|
|
@ -570,6 +581,16 @@ impl Area {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn round_area_position(ctx: &Context, pos: Pos2) -> Pos2 {
|
||||||
|
// We round a lot of rendering to pixels, so we round the whole
|
||||||
|
// area positions to pixels too, so avoid widgets appearing to float
|
||||||
|
// around independently of each other when the area is dragged.
|
||||||
|
// But just in case pixels_per_point is irrational,
|
||||||
|
// we then also round to ui coordinates:
|
||||||
|
|
||||||
|
pos.round_to_pixels(ctx.pixels_per_point()).round_ui()
|
||||||
|
}
|
||||||
|
|
||||||
impl Prepared {
|
impl Prepared {
|
||||||
pub(crate) fn state(&self) -> &AreaState {
|
pub(crate) fn state(&self) -> &AreaState {
|
||||||
&self.state
|
&self.state
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,7 @@ impl ComboBox {
|
||||||
let mut ir = combo_box_dyn(
|
let mut ir = combo_box_dyn(
|
||||||
ui,
|
ui,
|
||||||
button_id,
|
button_id,
|
||||||
selected_text,
|
selected_text.clone(),
|
||||||
menu_contents,
|
menu_contents,
|
||||||
icon,
|
icon,
|
||||||
wrap_mode,
|
wrap_mode,
|
||||||
|
|
@ -247,14 +247,16 @@ impl ComboBox {
|
||||||
popup_style,
|
popup_style,
|
||||||
(width, height),
|
(width, height),
|
||||||
);
|
);
|
||||||
|
ir.response.widget_info(|| {
|
||||||
|
let mut info = WidgetInfo::new(WidgetType::ComboBox);
|
||||||
|
info.enabled = ui.is_enabled();
|
||||||
|
info.current_text_value = Some(selected_text.text().to_owned());
|
||||||
|
info
|
||||||
|
});
|
||||||
if let Some(label) = label {
|
if let Some(label) = label {
|
||||||
ir.response.widget_info(|| {
|
let label_response = ui.label(label);
|
||||||
WidgetInfo::labeled(WidgetType::ComboBox, ui.is_enabled(), label.text())
|
ir.response = ir.response.labelled_by(label_response.id);
|
||||||
});
|
ir.response |= label_response;
|
||||||
ir.response |= ui.label(label);
|
|
||||||
} else {
|
|
||||||
ir.response
|
|
||||||
.widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, ui.is_enabled(), ""));
|
|
||||||
}
|
}
|
||||||
ir
|
ir
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::{
|
||||||
///
|
///
|
||||||
/// You can show multiple modals on top of each other. The topmost modal will always be
|
/// You can show multiple modals on top of each other. The topmost modal will always be
|
||||||
/// the most recently shown one.
|
/// the most recently shown one.
|
||||||
/// If multiple modals are newly shown in the same frame, the order of the modals not undefined
|
/// If multiple modals are newly shown in the same frame, the order of the modals is undefined
|
||||||
/// (either first or second could be top).
|
/// (either first or second could be top).
|
||||||
pub struct Modal {
|
pub struct Modal {
|
||||||
pub area: Area,
|
pub area: Area,
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@
|
||||||
|
|
||||||
use std::ops::{Add, AddAssign, BitOr, BitOrAssign};
|
use std::ops::{Add, AddAssign, BitOr, BitOrAssign};
|
||||||
|
|
||||||
|
use emath::GuiRounding as _;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Sense, Ui, UiBuilder, UiKind,
|
Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder,
|
||||||
UiStackInfo, Vec2, Vec2b, emath, epaint, lerp, pass_state, pos2, remap, remap_clamp,
|
UiKind, UiStackInfo, Vec2, Vec2b, emath, epaint, lerp, pass_state, pos2, remap, remap_clamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
|
@ -659,6 +661,9 @@ struct Prepared {
|
||||||
/// not for us to handle so we save it and restore it after this [`ScrollArea`] is done.
|
/// not for us to handle so we save it and restore it after this [`ScrollArea`] is done.
|
||||||
saved_scroll_target: [Option<pass_state::ScrollTarget>; 2],
|
saved_scroll_target: [Option<pass_state::ScrollTarget>; 2],
|
||||||
|
|
||||||
|
/// The response from dragging the background (if enabled)
|
||||||
|
background_drag_response: Option<Response>,
|
||||||
|
|
||||||
animated: bool,
|
animated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -745,6 +750,12 @@ impl ScrollArea {
|
||||||
}
|
}
|
||||||
|
|
||||||
let content_max_rect = Rect::from_min_size(inner_rect.min - state.offset, content_max_size);
|
let content_max_rect = Rect::from_min_size(inner_rect.min - state.offset, content_max_size);
|
||||||
|
|
||||||
|
// Round to pixels to avoid widgets appearing to "float" when scrolling fractional amounts:
|
||||||
|
let content_max_rect = content_max_rect
|
||||||
|
.round_to_pixels(ui.pixels_per_point())
|
||||||
|
.round_ui();
|
||||||
|
|
||||||
let mut content_ui = ui.new_child(
|
let mut content_ui = ui.new_child(
|
||||||
UiBuilder::new()
|
UiBuilder::new()
|
||||||
.ui_stack_info(UiStackInfo::new(UiKind::ScrollArea))
|
.ui_stack_info(UiStackInfo::new(UiKind::ScrollArea))
|
||||||
|
|
@ -772,70 +783,72 @@ impl ScrollArea {
|
||||||
let viewport = Rect::from_min_size(Pos2::ZERO + state.offset, inner_size);
|
let viewport = Rect::from_min_size(Pos2::ZERO + state.offset, inner_size);
|
||||||
let dt = ui.input(|i| i.stable_dt).at_most(0.1);
|
let dt = ui.input(|i| i.stable_dt).at_most(0.1);
|
||||||
|
|
||||||
if scroll_source.drag
|
let background_drag_response =
|
||||||
&& ui.is_enabled()
|
if scroll_source.drag && ui.is_enabled() && state.content_is_too_large.any() {
|
||||||
&& (state.content_is_too_large[0] || state.content_is_too_large[1])
|
// Drag contents to scroll (for touch screens mostly).
|
||||||
{
|
// We must do this BEFORE adding content to the `ScrollArea`,
|
||||||
// Drag contents to scroll (for touch screens mostly).
|
// or we will steal input from the widgets we contain.
|
||||||
// We must do this BEFORE adding content to the `ScrollArea`,
|
let content_response_option = state
|
||||||
// or we will steal input from the widgets we contain.
|
.interact_rect
|
||||||
let content_response_option = state
|
.map(|rect| ui.interact(rect, id.with("area"), Sense::drag()));
|
||||||
.interact_rect
|
|
||||||
.map(|rect| ui.interact(rect, id.with("area"), Sense::drag()));
|
|
||||||
|
|
||||||
if content_response_option
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|response| response.dragged())
|
|
||||||
{
|
|
||||||
for d in 0..2 {
|
|
||||||
if direction_enabled[d] {
|
|
||||||
ui.input(|input| {
|
|
||||||
state.offset[d] -= input.pointer.delta()[d];
|
|
||||||
});
|
|
||||||
state.scroll_stuck_to_end[d] = false;
|
|
||||||
state.offset_target[d] = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Apply the cursor velocity to the scroll area when the user releases the drag.
|
|
||||||
if content_response_option
|
if content_response_option
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|response| response.drag_stopped())
|
.is_some_and(|response| response.dragged())
|
||||||
{
|
{
|
||||||
state.vel =
|
for d in 0..2 {
|
||||||
direction_enabled.to_vec2() * ui.input(|input| input.pointer.velocity());
|
if direction_enabled[d] {
|
||||||
}
|
ui.input(|input| {
|
||||||
for d in 0..2 {
|
state.offset[d] -= input.pointer.delta()[d];
|
||||||
// Kinetic scrolling
|
});
|
||||||
let stop_speed = 20.0; // Pixels per second.
|
state.scroll_stuck_to_end[d] = false;
|
||||||
let friction_coeff = 1000.0; // Pixels per second squared.
|
state.offset_target[d] = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Apply the cursor velocity to the scroll area when the user releases the drag.
|
||||||
|
if content_response_option
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|response| response.drag_stopped())
|
||||||
|
{
|
||||||
|
state.vel = direction_enabled.to_vec2()
|
||||||
|
* ui.input(|input| input.pointer.velocity());
|
||||||
|
}
|
||||||
|
for d in 0..2 {
|
||||||
|
// Kinetic scrolling
|
||||||
|
let stop_speed = 20.0; // Pixels per second.
|
||||||
|
let friction_coeff = 1000.0; // Pixels per second squared.
|
||||||
|
|
||||||
let friction = friction_coeff * dt;
|
let friction = friction_coeff * dt;
|
||||||
if friction > state.vel[d].abs() || state.vel[d].abs() < stop_speed {
|
if friction > state.vel[d].abs() || state.vel[d].abs() < stop_speed {
|
||||||
state.vel[d] = 0.0;
|
state.vel[d] = 0.0;
|
||||||
} else {
|
} else {
|
||||||
state.vel[d] -= friction * state.vel[d].signum();
|
state.vel[d] -= friction * state.vel[d].signum();
|
||||||
// Offset has an inverted coordinate system compared to
|
// Offset has an inverted coordinate system compared to
|
||||||
// the velocity, so we subtract it instead of adding it
|
// the velocity, so we subtract it instead of adding it
|
||||||
state.offset[d] -= state.vel[d] * dt;
|
state.offset[d] -= state.vel[d] * dt;
|
||||||
ctx.request_repaint();
|
ctx.request_repaint();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Set the desired mouse cursors.
|
// Set the desired mouse cursors.
|
||||||
if let Some(response) = content_response_option {
|
if let Some(response) = &content_response_option {
|
||||||
if response.dragged() {
|
if response.dragged()
|
||||||
if let Some(cursor) = on_drag_cursor {
|
&& let Some(cursor) = on_drag_cursor
|
||||||
response.on_hover_cursor(cursor);
|
{
|
||||||
|
ui.ctx().set_cursor_icon(cursor);
|
||||||
|
} else if response.hovered()
|
||||||
|
&& let Some(cursor) = on_hover_cursor
|
||||||
|
{
|
||||||
|
ui.ctx().set_cursor_icon(cursor);
|
||||||
}
|
}
|
||||||
} else if response.hovered()
|
|
||||||
&& let Some(cursor) = on_hover_cursor
|
|
||||||
{
|
|
||||||
response.on_hover_cursor(cursor);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
content_response_option
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// Scroll with an animation if we have a target offset (that hasn't been cleared by the code
|
// Scroll with an animation if we have a target offset (that hasn't been cleared by the code
|
||||||
// above).
|
// above).
|
||||||
|
|
@ -888,6 +901,7 @@ impl ScrollArea {
|
||||||
wheel_scroll_multiplier,
|
wheel_scroll_multiplier,
|
||||||
stick_to_end,
|
stick_to_end,
|
||||||
saved_scroll_target,
|
saved_scroll_target,
|
||||||
|
background_drag_response,
|
||||||
animated,
|
animated,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1003,6 +1017,7 @@ impl Prepared {
|
||||||
wheel_scroll_multiplier,
|
wheel_scroll_multiplier,
|
||||||
stick_to_end,
|
stick_to_end,
|
||||||
saved_scroll_target,
|
saved_scroll_target,
|
||||||
|
background_drag_response,
|
||||||
animated,
|
animated,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
|
@ -1118,7 +1133,16 @@ impl Prepared {
|
||||||
);
|
);
|
||||||
|
|
||||||
let max_offset = content_size - inner_rect.size();
|
let max_offset = content_size - inner_rect.size();
|
||||||
let is_hovering_outer_rect = ui.rect_contains_pointer(outer_rect);
|
|
||||||
|
// Drag-to-scroll?
|
||||||
|
let is_dragging_background = background_drag_response
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|r| r.dragged());
|
||||||
|
|
||||||
|
let is_hovering_outer_rect = ui.rect_contains_pointer(outer_rect)
|
||||||
|
&& ui.ctx().dragged_id().is_none()
|
||||||
|
|| is_dragging_background;
|
||||||
|
|
||||||
if scroll_source.mouse_wheel && ui.is_enabled() && is_hovering_outer_rect {
|
if scroll_source.mouse_wheel && ui.is_enabled() && is_hovering_outer_rect {
|
||||||
let always_scroll_enabled_direction = ui.style().always_scroll_the_only_direction
|
let always_scroll_enabled_direction = ui.style().always_scroll_the_only_direction
|
||||||
&& direction_enabled[0] != direction_enabled[1];
|
&& direction_enabled[0] != direction_enabled[1];
|
||||||
|
|
@ -1204,6 +1228,7 @@ impl Prepared {
|
||||||
|
|
||||||
let is_hovering_bar_area = is_hovering_outer_rect
|
let is_hovering_bar_area = is_hovering_outer_rect
|
||||||
&& ui.rect_contains_pointer(max_bar_rect)
|
&& ui.rect_contains_pointer(max_bar_rect)
|
||||||
|
&& !is_dragging_background
|
||||||
|| state.scroll_bar_interaction[d];
|
|| state.scroll_bar_interaction[d];
|
||||||
|
|
||||||
let is_hovering_bar_area_t = ui
|
let is_hovering_bar_area_t = ui
|
||||||
|
|
|
||||||
|
|
@ -825,7 +825,7 @@ fn resize_response(
|
||||||
area: &mut area::Prepared,
|
area: &mut area::Prepared,
|
||||||
resize_id: Id,
|
resize_id: Id,
|
||||||
) {
|
) {
|
||||||
let Some(mut new_rect) = move_and_resize_window(ctx, &resize_interaction) else {
|
let Some(mut new_rect) = move_and_resize_window(ctx, resize_id, &resize_interaction) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -847,27 +847,38 @@ fn resize_response(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Acts on outer rect (outside the stroke)
|
/// Acts on outer rect (outside the stroke)
|
||||||
fn move_and_resize_window(ctx: &Context, interaction: &ResizeInteraction) -> Option<Rect> {
|
fn move_and_resize_window(ctx: &Context, id: Id, interaction: &ResizeInteraction) -> Option<Rect> {
|
||||||
|
// Used to prevent drift
|
||||||
|
let rect_at_start_of_drag_id = id.with("window_rect_at_drag_start");
|
||||||
|
|
||||||
if !interaction.any_dragged() {
|
if !interaction.any_dragged() {
|
||||||
|
ctx.data_mut(|data| {
|
||||||
|
data.remove::<Rect>(rect_at_start_of_drag_id);
|
||||||
|
});
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pointer_pos = ctx.input(|i| i.pointer.interact_pos())?;
|
let total_drag_delta = ctx.input(|i| i.pointer.total_drag_delta())?;
|
||||||
let mut rect = interaction.outer_rect; // prevent drift
|
|
||||||
|
let rect_at_start_of_drag = ctx.data_mut(|data| {
|
||||||
|
*data.get_temp_mut_or::<Rect>(rect_at_start_of_drag_id, interaction.outer_rect)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut rect = rect_at_start_of_drag; // prevent drift
|
||||||
|
|
||||||
// Put the rect in the center of the stroke:
|
// Put the rect in the center of the stroke:
|
||||||
rect = rect.shrink(interaction.window_frame.stroke.width / 2.0);
|
rect = rect.shrink(interaction.window_frame.stroke.width / 2.0);
|
||||||
|
|
||||||
if interaction.left.drag {
|
if interaction.left.drag {
|
||||||
rect.min.x = pointer_pos.x;
|
rect.min.x += total_drag_delta.x;
|
||||||
} else if interaction.right.drag {
|
} else if interaction.right.drag {
|
||||||
rect.max.x = pointer_pos.x;
|
rect.max.x += total_drag_delta.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
if interaction.top.drag {
|
if interaction.top.drag {
|
||||||
rect.min.y = pointer_pos.y;
|
rect.min.y += total_drag_delta.y;
|
||||||
} else if interaction.bottom.drag {
|
} else if interaction.bottom.drag {
|
||||||
rect.max.y = pointer_pos.y;
|
rect.max.y += total_drag_delta.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return to having the rect outside the stroke:
|
// Return to having the rect outside the stroke:
|
||||||
|
|
|
||||||
|
|
@ -1336,6 +1336,11 @@ impl PointerState {
|
||||||
self.press_origin
|
self.press_origin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How far has the pointer moved since the start of the drag (if any)?
|
||||||
|
pub fn total_drag_delta(&self) -> Option<Vec2> {
|
||||||
|
Some(self.latest_pos? - self.press_origin?)
|
||||||
|
}
|
||||||
|
|
||||||
/// When did the current click/drag originate?
|
/// When did the current click/drag originate?
|
||||||
/// `None` if no mouse button is down.
|
/// `None` if no mouse button is down.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
|
|
||||||
|
|
@ -396,7 +396,7 @@ impl Response {
|
||||||
self.drag_stopped() && self.ctx.input(|i| i.pointer.button_released(button))
|
self.drag_stopped() && self.ctx.input(|i| i.pointer.button_released(button))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If dragged, how many points were we dragged and in what direction?
|
/// If dragged, how many points were we dragged in since last frame?
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn drag_delta(&self) -> Vec2 {
|
pub fn drag_delta(&self) -> Vec2 {
|
||||||
if self.dragged() {
|
if self.dragged() {
|
||||||
|
|
@ -410,7 +410,22 @@ impl Response {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If dragged, how far did the mouse move?
|
/// If dragged, how many points have we been dragged since the start of the drag?
|
||||||
|
#[inline]
|
||||||
|
pub fn total_drag_delta(&self) -> Option<Vec2> {
|
||||||
|
if self.dragged() {
|
||||||
|
let mut delta = self.ctx.input(|i| i.pointer.total_drag_delta())?;
|
||||||
|
if let Some(from_global) = self.ctx.layer_transform_from_global(self.layer_id) {
|
||||||
|
delta *= from_global.scaling;
|
||||||
|
}
|
||||||
|
Some(delta)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If dragged, how far did the mouse move since last frame?
|
||||||
|
///
|
||||||
/// This will use raw mouse movement if provided by the integration, otherwise will fall back to [`Response::drag_delta`]
|
/// This will use raw mouse movement if provided by the integration, otherwise will fall back to [`Response::drag_delta`]
|
||||||
/// Raw mouse movement is unaccelerated and unclamped by screen boundaries, and does not relate to any position on the screen.
|
/// Raw mouse movement is unaccelerated and unclamped by screen boundaries, and does not relate to any position on the screen.
|
||||||
/// This may be useful in certain situations such as draggable values and 3D cameras, where screen position does not matter.
|
/// This may be useful in certain situations such as draggable values and 3D cameras, where screen position does not matter.
|
||||||
|
|
@ -709,10 +724,9 @@ impl Response {
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn interact(&self, sense: Sense) -> Self {
|
pub fn interact(&self, sense: Sense) -> Self {
|
||||||
if (self.sense | sense) == self.sense {
|
// We could check here if the new Sense equals the old one to avoid the extra create_widget
|
||||||
// Early-out: we already sense everything we need to sense.
|
// call. But that would break calling `interact` on a response from `Context::read_response`
|
||||||
return self.clone();
|
// or `Ui::response`. (See https://github.com/emilk/egui/pull/7713 for more details.)
|
||||||
}
|
|
||||||
|
|
||||||
self.ctx.create_widget(
|
self.ctx.create_widget(
|
||||||
WidgetRect {
|
WidgetRect {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:dc9c22567b76193a7f6753c4217adb3c92afa921c488ba1cf2e14b403814e7ac
|
oid sha256:128ca4e741995ffcdc07b027407d63911ded6c94fe3fe1dd0efecbf9408fb3af
|
||||||
size 99841
|
size 99871
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:aff927596be5db77349ec0bbdcc852a0b1467e94c2a553a740a383ae318bad18
|
oid sha256:bb3f7b5f790830b46d1410c2bbb5e19c6beb403f8fe979eb8d250fba4f89be3e
|
||||||
size 51670
|
size 51670
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:9d6f055247034fa13ab55c9ec1fca275e6c23999c9a7e01c87af1fcc930faac6
|
oid sha256:170cee9d72a4ab59aa2faf1b77aff4a9eee64f3380aa3f1b256340d88b1dabc2
|
||||||
size 66777
|
size 66525
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c91f592571ba654d0a96791662ae7530a1db4c1630b57c795d1c006ea6e46f19
|
oid sha256:f7a7d0e2618b852b5966073438c95cb62901d5410c1473639920b0b0bf2ec59b
|
||||||
size 256975
|
size 256913
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4fbcca2b13c94769a62b44853b19f7e841bbb60c9197b3d0bf6e83ef9f8f76d1
|
oid sha256:72f4c6fe4f5ec243506152027e1150f3069caf98511ceef92b8fea4f6a1563d5
|
||||||
size 77815
|
size 77614
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:a31f0c12bb70449136443f9086103bd5b46356eedc2bb93ae1b6b10684ab69ca
|
oid sha256:611a2d6c793a85eebe807b2ddd4446cc0bc21e4284343dd756e64f0232fb6815
|
||||||
size 36285
|
size 35991
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@ 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.
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.33.2 - 2025-11-13
|
||||||
|
Nothing new
|
||||||
|
|
||||||
|
|
||||||
## 0.33.0 - 2025-10-09
|
## 0.33.0 - 2025-10-09
|
||||||
* Fix: use unique id for resize columns in `Table` [#7414](https://github.com/emilk/egui/pull/7414) by [@zezic](https://github.com/zezic)
|
* Fix: use unique id for resize columns in `Table` [#7414](https://github.com/emilk/egui/pull/7414) by [@zezic](https://github.com/zezic)
|
||||||
* Feat: Add serde serialization to SyntectSettings [#7506](https://github.com/emilk/egui/pull/7506) by [@bircni](https://github.com/bircni)
|
* Feat: Add serde serialization to SyntectSettings [#7506](https://github.com/emilk/egui/pull/7506) by [@bircni](https://github.com/bircni)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,10 @@ Changes since the last release can be found at <https://github.com/emilk/egui/co
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 0.33.2 - 2025-11-13
|
||||||
|
Nothing new
|
||||||
|
|
||||||
|
|
||||||
## 0.33.0 - 2025-10-09
|
## 0.33.0 - 2025-10-09
|
||||||
* Update MSRV from 1.86 to 1.88 [#7579](https://github.com/emilk/egui/pull/7579) by [@Wumpf](https://github.com/Wumpf)
|
* Update MSRV from 1.86 to 1.88 [#7579](https://github.com/emilk/egui/pull/7579) by [@Wumpf](https://github.com/Wumpf)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,10 @@ 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.
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.33.2 - 2025-11-13
|
||||||
|
Nothing new
|
||||||
|
|
||||||
|
|
||||||
## 0.33.1 - 2025-10-15
|
## 0.33.1 - 2025-10-15
|
||||||
* Add `egui_kittest::HarnessBuilder::with_options` [#7638](https://github.com/emilk/egui/pull/7638) by [@emilk](https://github.com/emilk)
|
* Add `egui_kittest::HarnessBuilder::with_options` [#7638](https://github.com/emilk/egui/pull/7638) by [@emilk](https://github.com/emilk)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "egui_kittest"
|
name = "egui_kittest"
|
||||||
version = "0.33.1"
|
version.workspace = true
|
||||||
authors = ["Lucas Meurer <lucasmeurer96@gmail.com>", "Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
authors = ["Lucas Meurer <lucasmeurer96@gmail.com>", "Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||||
description = "Testing library for egui based on kittest and AccessKit"
|
description = "Testing library for egui based on kittest and AccessKit"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@ All notable changes to the `emath` crate will be noted in this file.
|
||||||
This file is updated upon each release.
|
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.
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.33.2 - 2025-11-13
|
||||||
|
* Fix edge cases in "smart aiming" in sliders [#7680](https://github.com/emilk/egui/pull/7680) by [@emilk](https://github.com/emilk)
|
||||||
|
|
||||||
|
|
||||||
## 0.33.0 - 2025-10-09
|
## 0.33.0 - 2025-10-09
|
||||||
* Add `emath::fast_midpoint` [#7435](https://github.com/emilk/egui/pull/7435) by [@emilk](https://github.com/emilk)
|
* Add `emath::fast_midpoint` [#7435](https://github.com/emilk/egui/pull/7435) by [@emilk](https://github.com/emilk)
|
||||||
* Generate changelogs for emath [#7513](https://github.com/emilk/egui/pull/7513) by [@lucasmerlin](https://github.com/lucasmerlin)
|
* Generate changelogs for emath [#7513](https://github.com/emilk/egui/pull/7513) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@ 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.
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.33.2 - 2025-11-13
|
||||||
|
Nothing new
|
||||||
|
|
||||||
|
|
||||||
## 0.33.0 - 2025-10-09
|
## 0.33.0 - 2025-10-09
|
||||||
* Remove the `deadlock_detection` feature [#7497](https://github.com/emilk/egui/pull/7497) by [@lucasmerlin](https://github.com/lucasmerlin)
|
* Remove the `deadlock_detection` feature [#7497](https://github.com/emilk/egui/pull/7497) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||||
* More even text kerning [#7431](https://github.com/emilk/egui/pull/7431) by [@valadaptive](https://github.com/valadaptive)
|
* More even text kerning [#7431](https://github.com/emilk/egui/pull/7431) by [@valadaptive](https://github.com/valadaptive)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@ 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.
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.33.2 - 2025-11-13
|
||||||
|
Nothing new
|
||||||
|
|
||||||
|
|
||||||
## 0.33.0 - 2025-10-09
|
## 0.33.0 - 2025-10-09
|
||||||
Nothing new
|
Nothing new
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "text_input_test"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
rust-version = "1.88"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
eframe = { workspace = true, features = ["default"] }
|
||||||
|
egui_extras = { workspace = true, features = ["default", "syntect"] }
|
||||||
|
env_logger = { workspace = true, features = ["auto-color", "humantime"] }
|
||||||
|
|
@ -0,0 +1,205 @@
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
#![allow(rustdoc::missing_crate_level_docs)]
|
||||||
|
|
||||||
|
use eframe::egui;
|
||||||
|
|
||||||
|
fn main() -> eframe::Result {
|
||||||
|
env_logger::init();
|
||||||
|
let options = eframe::NativeOptions {
|
||||||
|
viewport: egui::ViewportBuilder::default().with_inner_size([800.0, 600.0]),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
eframe::run_native(
|
||||||
|
"Text Input Test - IBus/Wayland",
|
||||||
|
options,
|
||||||
|
Box::new(|_cc| Ok(Box::<TestApp>::default())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestApp {
|
||||||
|
// Single-line text inputs
|
||||||
|
single_line_1: String,
|
||||||
|
single_line_2: String,
|
||||||
|
|
||||||
|
// Multi-line text input
|
||||||
|
multi_line: String,
|
||||||
|
|
||||||
|
// Code editor content
|
||||||
|
code: String,
|
||||||
|
|
||||||
|
// Password field
|
||||||
|
password: String,
|
||||||
|
|
||||||
|
// Input event log
|
||||||
|
event_log: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TestApp {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
single_line_1: String::new(),
|
||||||
|
single_line_2: "Pre-filled text".to_owned(),
|
||||||
|
multi_line: "Type multiple lines here.\nLine 2\nLine 3".to_owned(),
|
||||||
|
code: r#"fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
|
"#.to_owned(),
|
||||||
|
password: String::new(),
|
||||||
|
event_log: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl eframe::App for TestApp {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
// Log input events
|
||||||
|
ctx.input(|i| {
|
||||||
|
for event in &i.events {
|
||||||
|
match event {
|
||||||
|
egui::Event::Text(text) => {
|
||||||
|
self.event_log.push(format!(">>> TEXT: {:?}", text));
|
||||||
|
}
|
||||||
|
egui::Event::Key { key, pressed, modifiers, .. } => {
|
||||||
|
self.event_log.push(format!("KEY: {:?} pressed={} (mods: {:?})", key, pressed, modifiers));
|
||||||
|
}
|
||||||
|
egui::Event::Ime(ime) => {
|
||||||
|
// Filter out the spammy Disabled events
|
||||||
|
match ime {
|
||||||
|
egui::ImeEvent::Disabled => {} // Skip logging these
|
||||||
|
_ => self.event_log.push(format!("IME: {:?}", ime)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
egui::Event::Paste(text) => {
|
||||||
|
self.event_log.push(format!("PASTE: {:?}", text));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keep event log manageable
|
||||||
|
if self.event_log.len() > 100 {
|
||||||
|
self.event_log.drain(0..50);
|
||||||
|
}
|
||||||
|
|
||||||
|
egui::SidePanel::right("event_log").show(ctx, |ui| {
|
||||||
|
ui.heading("Input Events");
|
||||||
|
if ui.button("Clear").clicked() {
|
||||||
|
self.event_log.clear();
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||||
|
for event in self.event_log.iter().rev() {
|
||||||
|
ui.label(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
ui.heading("Text Input Test - IBus/Wayland Bug");
|
||||||
|
ui.label("Test various text input widgets. If IBus under Wayland is broken, typing won't work.");
|
||||||
|
|
||||||
|
// Show environment info
|
||||||
|
ui.add_space(5.0);
|
||||||
|
ui.group(|ui| {
|
||||||
|
ui.label("Environment Detection:");
|
||||||
|
let session_type = std::env::var("XDG_SESSION_TYPE").unwrap_or_else(|_| "unknown".to_string());
|
||||||
|
let wayland_display = std::env::var("WAYLAND_DISPLAY").is_ok();
|
||||||
|
let gtk_im = std::env::var("GTK_IM_MODULE").unwrap_or_else(|_| "not set".to_string());
|
||||||
|
let qt_im = std::env::var("QT_IM_MODULE").unwrap_or_else(|_| "not set".to_string());
|
||||||
|
let xmodifiers = std::env::var("XMODIFIERS").unwrap_or_else(|_| "not set".to_string());
|
||||||
|
let ime_override = std::env::var("EGUI_IME_DISABLED").unwrap_or_else(|_| "not set".to_string());
|
||||||
|
|
||||||
|
ui.label(format!(" XDG_SESSION_TYPE: {session_type}"));
|
||||||
|
ui.label(format!(" WAYLAND_DISPLAY set: {wayland_display}"));
|
||||||
|
ui.label(format!(" GTK_IM_MODULE: {gtk_im}"));
|
||||||
|
ui.label(format!(" QT_IM_MODULE: {qt_im}"));
|
||||||
|
ui.label(format!(" XMODIFIERS: {xmodifiers}"));
|
||||||
|
ui.label(format!(" EGUI_IME_DISABLED: {ime_override}"));
|
||||||
|
|
||||||
|
let is_wayland = session_type == "wayland" || wayland_display;
|
||||||
|
let is_ibus = gtk_im == "ibus" || qt_im == "ibus" || xmodifiers.contains("ibus");
|
||||||
|
if is_wayland && is_ibus {
|
||||||
|
ui.colored_label(egui::Color32::GREEN,
|
||||||
|
"IBus on Wayland detected - IME workaround is active!");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||||
|
ui.group(|ui| {
|
||||||
|
ui.heading("Single-line Text Edits");
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Empty field:");
|
||||||
|
ui.text_edit_singleline(&mut self.single_line_1);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Pre-filled:");
|
||||||
|
ui.text_edit_singleline(&mut self.single_line_2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(10.0);
|
||||||
|
|
||||||
|
ui.group(|ui| {
|
||||||
|
ui.heading("Password Field");
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Password:");
|
||||||
|
ui.add(egui::TextEdit::singleline(&mut self.password).password(true));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(10.0);
|
||||||
|
|
||||||
|
ui.group(|ui| {
|
||||||
|
ui.heading("Multi-line Text Edit");
|
||||||
|
ui.add(
|
||||||
|
egui::TextEdit::multiline(&mut self.multi_line)
|
||||||
|
.desired_width(f32::INFINITY)
|
||||||
|
.desired_rows(6)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(10.0);
|
||||||
|
|
||||||
|
ui.group(|ui| {
|
||||||
|
ui.heading("Code Editor (with syntax highlighting)");
|
||||||
|
let theme = egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx(), ui.style());
|
||||||
|
let mut layouter = |ui: &egui::Ui, string: &dyn egui::TextBuffer, wrap_width: f32| {
|
||||||
|
let mut layout_job = egui_extras::syntax_highlighting::highlight(
|
||||||
|
ui.ctx(),
|
||||||
|
ui.style(),
|
||||||
|
&theme,
|
||||||
|
string.as_str(),
|
||||||
|
"rs",
|
||||||
|
);
|
||||||
|
layout_job.wrap.max_width = wrap_width;
|
||||||
|
ui.fonts_mut(|f| f.layout_job(layout_job))
|
||||||
|
};
|
||||||
|
ui.add(
|
||||||
|
egui::TextEdit::multiline(&mut self.code)
|
||||||
|
.font(egui::TextStyle::Monospace)
|
||||||
|
.code_editor()
|
||||||
|
.desired_width(f32::INFINITY)
|
||||||
|
.desired_rows(10)
|
||||||
|
.layouter(&mut layouter)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(10.0);
|
||||||
|
|
||||||
|
ui.group(|ui| {
|
||||||
|
ui.heading("Current Values");
|
||||||
|
ui.label(format!("Single line 1: {:?}", self.single_line_1));
|
||||||
|
ui.label(format!("Single line 2: {:?}", self.single_line_2));
|
||||||
|
ui.label(format!("Password length: {}", self.password.len()));
|
||||||
|
ui.label(format!("Multi-line lines: {}", self.multi_line.lines().count()));
|
||||||
|
ui.label(format!("Code lines: {}", self.code.lines().count()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use egui::accesskit::Role;
|
use egui::accesskit::Role;
|
||||||
use egui::{Align, Color32, Image, Label, Layout, RichText, TextWrapMode, include_image};
|
use egui::{Align, Color32, Image, Label, Layout, RichText, Sense, TextWrapMode, include_image};
|
||||||
use egui_kittest::Harness;
|
use egui_kittest::Harness;
|
||||||
use egui_kittest::kittest::Queryable as _;
|
use egui_kittest::kittest::Queryable as _;
|
||||||
|
|
||||||
|
|
@ -61,3 +61,60 @@ fn text_edit_rtl() {
|
||||||
harness.snapshot(format!("text_edit_rtl_{i}"));
|
harness.snapshot(format!("text_edit_rtl_{i}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn combobox_should_have_value() {
|
||||||
|
let harness = Harness::new_ui(|ui| {
|
||||||
|
egui::ComboBox::from_label("Select an option")
|
||||||
|
.selected_text("Option 1")
|
||||||
|
.show_ui(ui, |_ui| {});
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
harness.get_by_label("Select an option").value().as_deref(),
|
||||||
|
Some("Option 1")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This test ensures that `ui.response().interact(...)` works correctly.
|
||||||
|
///
|
||||||
|
/// This was broken, because there was an optimization in [`egui::Response::interact`]
|
||||||
|
/// which caused the [`Sense`] of the original response to flip-flop between `click` and `hover`
|
||||||
|
/// between frames.
|
||||||
|
///
|
||||||
|
/// See <https://github.com/emilk/egui/pull/7713> for more details.
|
||||||
|
#[test]
|
||||||
|
fn interact_on_ui_response_should_be_stable() {
|
||||||
|
let mut first_frame = true;
|
||||||
|
let mut click_count = 0;
|
||||||
|
let mut harness = Harness::new_ui(|ui| {
|
||||||
|
let ui_response = ui.response();
|
||||||
|
if !first_frame {
|
||||||
|
assert!(
|
||||||
|
ui_response.sense.contains(Sense::click()),
|
||||||
|
"ui.response() didn't have click sense even though we called interact(Sense::click()) last frame"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a label so we have something to click with kittest
|
||||||
|
ui.add(
|
||||||
|
Label::new("senseless label")
|
||||||
|
.sense(Sense::hover())
|
||||||
|
.selectable(false),
|
||||||
|
);
|
||||||
|
|
||||||
|
let click_response = ui_response.interact(Sense::click());
|
||||||
|
if click_response.clicked() {
|
||||||
|
click_count += 1;
|
||||||
|
}
|
||||||
|
first_frame = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
for i in 0..=10 {
|
||||||
|
harness.run_steps(i);
|
||||||
|
harness.get_by_label("senseless label").click();
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(harness);
|
||||||
|
assert_eq!(click_count, 10, "We missed some clicks!");
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue