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.
|
||||
|
||||
|
||||
## 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
|
||||
Highlights from this release:
|
||||
- `egui::Plugin` a improved way to create and access egui plugins
|
||||
|
|
|
|||
41
Cargo.lock
41
Cargo.lock
|
|
@ -1248,7 +1248,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53"
|
|||
|
||||
[[package]]
|
||||
name = "ecolor"
|
||||
version = "0.33.0"
|
||||
version = "0.33.2"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cint",
|
||||
|
|
@ -1260,7 +1260,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "eframe"
|
||||
version = "0.33.0"
|
||||
version = "0.33.2"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
|
|
@ -1299,7 +1299,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "egui"
|
||||
version = "0.33.0"
|
||||
version = "0.33.2"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"ahash",
|
||||
|
|
@ -1319,7 +1319,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "egui-wgpu"
|
||||
version = "0.33.0"
|
||||
version = "0.33.2"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
|
|
@ -1337,7 +1337,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "egui-winit"
|
||||
version = "0.33.0"
|
||||
version = "0.33.2"
|
||||
dependencies = [
|
||||
"accesskit_winit",
|
||||
"arboard",
|
||||
|
|
@ -1360,7 +1360,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "egui_demo_app"
|
||||
version = "0.33.0"
|
||||
version = "0.33.2"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_consumer",
|
||||
|
|
@ -1390,7 +1390,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "egui_demo_lib"
|
||||
version = "0.33.0"
|
||||
version = "0.33.2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"criterion",
|
||||
|
|
@ -1407,7 +1407,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "egui_extras"
|
||||
version = "0.33.0"
|
||||
version = "0.33.2"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"chrono",
|
||||
|
|
@ -1426,7 +1426,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "egui_glow"
|
||||
version = "0.33.0"
|
||||
version = "0.33.2"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"document-features",
|
||||
|
|
@ -1445,7 +1445,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "egui_kittest"
|
||||
version = "0.33.1"
|
||||
version = "0.33.2"
|
||||
dependencies = [
|
||||
"dify",
|
||||
"document-features",
|
||||
|
|
@ -1463,7 +1463,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "egui_tests"
|
||||
version = "0.33.0"
|
||||
version = "0.33.2"
|
||||
dependencies = [
|
||||
"egui",
|
||||
"egui_extras",
|
||||
|
|
@ -1493,7 +1493,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
|||
|
||||
[[package]]
|
||||
name = "emath"
|
||||
version = "0.33.0"
|
||||
version = "0.33.2"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"document-features",
|
||||
|
|
@ -1591,7 +1591,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "epaint"
|
||||
version = "0.33.0"
|
||||
version = "0.33.2"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"ahash",
|
||||
|
|
@ -1613,7 +1613,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "epaint_default_fonts"
|
||||
version = "0.33.0"
|
||||
version = "0.33.2"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
|
|
@ -3425,7 +3425,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
|
|||
|
||||
[[package]]
|
||||
name = "popups"
|
||||
version = "0.33.0"
|
||||
version = "0.33.2"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"env_logger",
|
||||
|
|
@ -4332,6 +4332,15 @@ dependencies = [
|
|||
"env_logger",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "text_input_test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui_extras",
|
||||
"env_logger",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.66"
|
||||
|
|
@ -5748,7 +5757,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
|||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.33.0"
|
||||
version = "0.33.2"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
|
|
|
|||
26
Cargo.toml
26
Cargo.toml
|
|
@ -24,7 +24,7 @@ members = [
|
|||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.88"
|
||||
version = "0.33.0"
|
||||
version = "0.33.2"
|
||||
|
||||
|
||||
[profile.release]
|
||||
|
|
@ -55,18 +55,18 @@ opt-level = 2
|
|||
|
||||
|
||||
[workspace.dependencies]
|
||||
emath = { version = "0.33.0", path = "crates/emath", default-features = false }
|
||||
ecolor = { version = "0.33.0", path = "crates/ecolor", default-features = false }
|
||||
epaint = { version = "0.33.0", path = "crates/epaint", default-features = false }
|
||||
epaint_default_fonts = { version = "0.33.0", path = "crates/epaint_default_fonts" }
|
||||
egui = { version = "0.33.0", path = "crates/egui", default-features = false }
|
||||
egui-winit = { version = "0.33.0", path = "crates/egui-winit", default-features = false }
|
||||
egui_extras = { version = "0.33.0", path = "crates/egui_extras", default-features = false }
|
||||
egui-wgpu = { version = "0.33.0", path = "crates/egui-wgpu", default-features = false }
|
||||
egui_demo_lib = { version = "0.33.0", path = "crates/egui_demo_lib", default-features = false }
|
||||
egui_glow = { version = "0.33.0", path = "crates/egui_glow", default-features = false }
|
||||
egui_kittest = { version = "0.33.1", path = "crates/egui_kittest", default-features = false }
|
||||
eframe = { version = "0.33.0", path = "crates/eframe", default-features = false }
|
||||
emath = { version = "0.33.2", path = "crates/emath", default-features = false }
|
||||
ecolor = { version = "0.33.2", path = "crates/ecolor", default-features = false }
|
||||
epaint = { version = "0.33.2", path = "crates/epaint", default-features = false }
|
||||
epaint_default_fonts = { version = "0.33.2", path = "crates/epaint_default_fonts" }
|
||||
egui = { version = "0.33.2", path = "crates/egui", default-features = false }
|
||||
egui-winit = { version = "0.33.2", path = "crates/egui-winit", default-features = false }
|
||||
egui_extras = { version = "0.33.2", path = "crates/egui_extras", default-features = false }
|
||||
egui-wgpu = { version = "0.33.2", path = "crates/egui-wgpu", default-features = false }
|
||||
egui_demo_lib = { version = "0.33.2", path = "crates/egui_demo_lib", default-features = false }
|
||||
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 }
|
||||
|
||||
accesskit = "0.21.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.
|
||||
|
||||
|
||||
## 0.33.2 - 2025-11-13
|
||||
Nothing new
|
||||
|
||||
|
||||
## 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)
|
||||
* 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.
|
||||
|
||||
|
||||
## 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
|
||||
### ⭐ 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)
|
||||
|
|
|
|||
|
|
@ -1041,11 +1041,23 @@ impl GlutinWindowContext {
|
|||
|
||||
let mut viewport_from_window = HashMap::default();
|
||||
let mut window_from_viewport = OrderedViewportIdMap::default();
|
||||
let mut info = ViewportInfo::default();
|
||||
let mut viewport_info = ViewportInfo::default();
|
||||
if let Some(window) = &window {
|
||||
viewport_from_window.insert(window.id(), ViewportId::ROOT);
|
||||
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();
|
||||
|
|
@ -1056,7 +1068,7 @@ impl GlutinWindowContext {
|
|||
class: ViewportClass::Root,
|
||||
builder: viewport_builder,
|
||||
deferred_commands: vec![],
|
||||
info,
|
||||
info: viewport_info,
|
||||
actions_requested: Default::default(),
|
||||
viewport_ui_cb: 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);
|
||||
|
||||
{
|
||||
|
|
@ -278,9 +294,6 @@ impl<'app> WgpuWinitApp<'app> {
|
|||
let mut viewport_from_window = HashMap::default();
|
||||
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();
|
||||
viewports.insert(
|
||||
ViewportId::ROOT,
|
||||
|
|
@ -289,7 +302,7 @@ impl<'app> WgpuWinitApp<'app> {
|
|||
class: ViewportClass::Root,
|
||||
builder,
|
||||
deferred_commands: vec![],
|
||||
info,
|
||||
info: viewport_info,
|
||||
actions_requested: Default::default(),
|
||||
viewport_ui_cb: None,
|
||||
window: Some(window),
|
||||
|
|
|
|||
|
|
@ -65,6 +65,14 @@ impl AppRunner {
|
|||
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 {
|
||||
egui_ctx: egui_ctx.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.
|
||||
|
||||
|
||||
## 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
|
||||
### 🔧 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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
||||
## 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
|
||||
### ⭐ Added
|
||||
* 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,
|
||||
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 {
|
||||
|
|
@ -148,6 +156,8 @@ impl State {
|
|||
|
||||
allow_ime: false,
|
||||
ime_rect_px: None,
|
||||
|
||||
ime_disabled: should_disable_ime_for_buggy_systems(),
|
||||
};
|
||||
|
||||
slf.egui_input
|
||||
|
|
@ -205,6 +215,30 @@ impl State {
|
|||
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]
|
||||
pub fn egui_ctx(&self) -> &egui::Context {
|
||||
&self.egui_ctx
|
||||
|
|
@ -350,6 +384,31 @@ impl State {
|
|||
}
|
||||
|
||||
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.
|
||||
// So no need to check is_mac_cmd.
|
||||
//
|
||||
|
|
@ -907,7 +966,10 @@ impl State {
|
|||
|
||||
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 {
|
||||
self.allow_ime = allow_ime;
|
||||
profiling::scope!("set_ime_allowed");
|
||||
|
|
@ -915,6 +977,10 @@ impl State {
|
|||
}
|
||||
|
||||
if let Some(ime) = ime {
|
||||
// Skip IME cursor positioning if IME is disabled
|
||||
if self.ime_disabled {
|
||||
self.ime_rect_px = None;
|
||||
} else {
|
||||
let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
|
||||
let ime_rect_px = pixels_per_point * ime.rect;
|
||||
if self.ime_rect_px != Some(ime_rect_px)
|
||||
|
|
@ -933,6 +999,7 @@ impl State {
|
|||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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.
|
||||
/// Ignore those.
|
||||
/// We also ignore '\r', '\n', '\t'.
|
||||
|
|
|
|||
|
|
@ -525,11 +525,21 @@ impl Area {
|
|||
true,
|
||||
);
|
||||
|
||||
// Used to prevent drift
|
||||
let pivot_at_start_of_drag_id = id.with("pivot_at_drag_start");
|
||||
|
||||
if movable
|
||||
&& move_response.dragged()
|
||||
&& 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())
|
||||
|
|
@ -543,13 +553,14 @@ impl Area {
|
|||
move_response
|
||||
};
|
||||
|
||||
state.set_left_top_pos(round_area_position(
|
||||
ctx,
|
||||
if constrain {
|
||||
state.set_left_top_pos(
|
||||
Context::constrain_window_rect_to_area(state.rect(), constrain_rect).min,
|
||||
);
|
||||
}
|
||||
|
||||
state.set_left_top_pos(state.left_top_pos());
|
||||
Context::constrain_window_rect_to_area(state.rect(), constrain_rect).min
|
||||
} else {
|
||||
state.left_top_pos()
|
||||
},
|
||||
));
|
||||
|
||||
// Update response with possibly moved/constrained 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 {
|
||||
pub(crate) fn state(&self) -> &AreaState {
|
||||
&self.state
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ impl ComboBox {
|
|||
let mut ir = combo_box_dyn(
|
||||
ui,
|
||||
button_id,
|
||||
selected_text,
|
||||
selected_text.clone(),
|
||||
menu_contents,
|
||||
icon,
|
||||
wrap_mode,
|
||||
|
|
@ -247,14 +247,16 @@ impl ComboBox {
|
|||
popup_style,
|
||||
(width, height),
|
||||
);
|
||||
if let Some(label) = label {
|
||||
ir.response.widget_info(|| {
|
||||
WidgetInfo::labeled(WidgetType::ComboBox, ui.is_enabled(), label.text())
|
||||
let mut info = WidgetInfo::new(WidgetType::ComboBox);
|
||||
info.enabled = ui.is_enabled();
|
||||
info.current_text_value = Some(selected_text.text().to_owned());
|
||||
info
|
||||
});
|
||||
ir.response |= ui.label(label);
|
||||
} else {
|
||||
ir.response
|
||||
.widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, ui.is_enabled(), ""));
|
||||
if let Some(label) = label {
|
||||
let label_response = ui.label(label);
|
||||
ir.response = ir.response.labelled_by(label_response.id);
|
||||
ir.response |= label_response;
|
||||
}
|
||||
ir
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use crate::{
|
|||
///
|
||||
/// You can show multiple modals on top of each other. The topmost modal will always be
|
||||
/// 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).
|
||||
pub struct Modal {
|
||||
pub area: Area,
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
|
||||
use std::ops::{Add, AddAssign, BitOr, BitOrAssign};
|
||||
|
||||
use emath::GuiRounding as _;
|
||||
|
||||
use crate::{
|
||||
Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Sense, Ui, UiBuilder, UiKind,
|
||||
UiStackInfo, Vec2, Vec2b, emath, epaint, lerp, pass_state, pos2, remap, remap_clamp,
|
||||
Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder,
|
||||
UiKind, UiStackInfo, Vec2, Vec2b, emath, epaint, lerp, pass_state, pos2, remap, remap_clamp,
|
||||
};
|
||||
|
||||
#[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.
|
||||
saved_scroll_target: [Option<pass_state::ScrollTarget>; 2],
|
||||
|
||||
/// The response from dragging the background (if enabled)
|
||||
background_drag_response: Option<Response>,
|
||||
|
||||
animated: bool,
|
||||
}
|
||||
|
||||
|
|
@ -745,6 +750,12 @@ impl ScrollArea {
|
|||
}
|
||||
|
||||
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(
|
||||
UiBuilder::new()
|
||||
.ui_stack_info(UiStackInfo::new(UiKind::ScrollArea))
|
||||
|
|
@ -772,10 +783,8 @@ impl ScrollArea {
|
|||
let viewport = Rect::from_min_size(Pos2::ZERO + state.offset, inner_size);
|
||||
let dt = ui.input(|i| i.stable_dt).at_most(0.1);
|
||||
|
||||
if scroll_source.drag
|
||||
&& ui.is_enabled()
|
||||
&& (state.content_is_too_large[0] || state.content_is_too_large[1])
|
||||
{
|
||||
let background_drag_response =
|
||||
if scroll_source.drag && ui.is_enabled() && state.content_is_too_large.any() {
|
||||
// Drag contents to scroll (for touch screens mostly).
|
||||
// We must do this BEFORE adding content to the `ScrollArea`,
|
||||
// or we will steal input from the widgets we contain.
|
||||
|
|
@ -802,8 +811,8 @@ impl ScrollArea {
|
|||
.as_ref()
|
||||
.is_some_and(|response| response.drag_stopped())
|
||||
{
|
||||
state.vel =
|
||||
direction_enabled.to_vec2() * ui.input(|input| input.pointer.velocity());
|
||||
state.vel = direction_enabled.to_vec2()
|
||||
* ui.input(|input| input.pointer.velocity());
|
||||
}
|
||||
for d in 0..2 {
|
||||
// Kinetic scrolling
|
||||
|
|
@ -824,19 +833,23 @@ impl ScrollArea {
|
|||
}
|
||||
|
||||
// Set the desired mouse cursors.
|
||||
if let Some(response) = content_response_option {
|
||||
if response.dragged() {
|
||||
if let Some(cursor) = on_drag_cursor {
|
||||
response.on_hover_cursor(cursor);
|
||||
}
|
||||
if let Some(response) = &content_response_option {
|
||||
if response.dragged()
|
||||
&& let Some(cursor) = on_drag_cursor
|
||||
{
|
||||
ui.ctx().set_cursor_icon(cursor);
|
||||
} else if response.hovered()
|
||||
&& let Some(cursor) = on_hover_cursor
|
||||
{
|
||||
response.on_hover_cursor(cursor);
|
||||
}
|
||||
ui.ctx().set_cursor_icon(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
content_response_option
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Scroll with an animation if we have a target offset (that hasn't been cleared by the code
|
||||
// above).
|
||||
for d in 0..2 {
|
||||
|
|
@ -888,6 +901,7 @@ impl ScrollArea {
|
|||
wheel_scroll_multiplier,
|
||||
stick_to_end,
|
||||
saved_scroll_target,
|
||||
background_drag_response,
|
||||
animated,
|
||||
}
|
||||
}
|
||||
|
|
@ -1003,6 +1017,7 @@ impl Prepared {
|
|||
wheel_scroll_multiplier,
|
||||
stick_to_end,
|
||||
saved_scroll_target,
|
||||
background_drag_response,
|
||||
animated,
|
||||
} = self;
|
||||
|
||||
|
|
@ -1118,7 +1133,16 @@ impl Prepared {
|
|||
);
|
||||
|
||||
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 {
|
||||
let always_scroll_enabled_direction = ui.style().always_scroll_the_only_direction
|
||||
&& direction_enabled[0] != direction_enabled[1];
|
||||
|
|
@ -1204,6 +1228,7 @@ impl Prepared {
|
|||
|
||||
let is_hovering_bar_area = is_hovering_outer_rect
|
||||
&& ui.rect_contains_pointer(max_bar_rect)
|
||||
&& !is_dragging_background
|
||||
|| state.scroll_bar_interaction[d];
|
||||
|
||||
let is_hovering_bar_area_t = ui
|
||||
|
|
|
|||
|
|
@ -825,7 +825,7 @@ fn resize_response(
|
|||
area: &mut area::Prepared,
|
||||
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;
|
||||
};
|
||||
|
||||
|
|
@ -847,27 +847,38 @@ fn resize_response(
|
|||
}
|
||||
|
||||
/// 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() {
|
||||
ctx.data_mut(|data| {
|
||||
data.remove::<Rect>(rect_at_start_of_drag_id);
|
||||
});
|
||||
return None;
|
||||
}
|
||||
|
||||
let pointer_pos = ctx.input(|i| i.pointer.interact_pos())?;
|
||||
let mut rect = interaction.outer_rect; // prevent drift
|
||||
let total_drag_delta = ctx.input(|i| i.pointer.total_drag_delta())?;
|
||||
|
||||
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:
|
||||
rect = rect.shrink(interaction.window_frame.stroke.width / 2.0);
|
||||
|
||||
if interaction.left.drag {
|
||||
rect.min.x = pointer_pos.x;
|
||||
rect.min.x += total_drag_delta.x;
|
||||
} else if interaction.right.drag {
|
||||
rect.max.x = pointer_pos.x;
|
||||
rect.max.x += total_drag_delta.x;
|
||||
}
|
||||
|
||||
if interaction.top.drag {
|
||||
rect.min.y = pointer_pos.y;
|
||||
rect.min.y += total_drag_delta.y;
|
||||
} 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:
|
||||
|
|
|
|||
|
|
@ -1336,6 +1336,11 @@ impl PointerState {
|
|||
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?
|
||||
/// `None` if no mouse button is down.
|
||||
#[inline(always)]
|
||||
|
|
|
|||
|
|
@ -396,7 +396,7 @@ impl Response {
|
|||
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]
|
||||
pub fn drag_delta(&self) -> Vec2 {
|
||||
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`]
|
||||
/// 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.
|
||||
|
|
@ -709,10 +724,9 @@ impl Response {
|
|||
/// ```
|
||||
#[must_use]
|
||||
pub fn interact(&self, sense: Sense) -> Self {
|
||||
if (self.sense | sense) == self.sense {
|
||||
// Early-out: we already sense everything we need to sense.
|
||||
return self.clone();
|
||||
}
|
||||
// We could check here if the new Sense equals the old one to avoid the extra create_widget
|
||||
// call. But that would break calling `interact` on a response from `Context::read_response`
|
||||
// or `Ui::response`. (See https://github.com/emilk/egui/pull/7713 for more details.)
|
||||
|
||||
self.ctx.create_widget(
|
||||
WidgetRect {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dc9c22567b76193a7f6753c4217adb3c92afa921c488ba1cf2e14b403814e7ac
|
||||
size 99841
|
||||
oid sha256:128ca4e741995ffcdc07b027407d63911ded6c94fe3fe1dd0efecbf9408fb3af
|
||||
size 99871
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:aff927596be5db77349ec0bbdcc852a0b1467e94c2a553a740a383ae318bad18
|
||||
oid sha256:bb3f7b5f790830b46d1410c2bbb5e19c6beb403f8fe979eb8d250fba4f89be3e
|
||||
size 51670
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9d6f055247034fa13ab55c9ec1fca275e6c23999c9a7e01c87af1fcc930faac6
|
||||
size 66777
|
||||
oid sha256:170cee9d72a4ab59aa2faf1b77aff4a9eee64f3380aa3f1b256340d88b1dabc2
|
||||
size 66525
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c91f592571ba654d0a96791662ae7530a1db4c1630b57c795d1c006ea6e46f19
|
||||
size 256975
|
||||
oid sha256:f7a7d0e2618b852b5966073438c95cb62901d5410c1473639920b0b0bf2ec59b
|
||||
size 256913
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4fbcca2b13c94769a62b44853b19f7e841bbb60c9197b3d0bf6e83ef9f8f76d1
|
||||
size 77815
|
||||
oid sha256:72f4c6fe4f5ec243506152027e1150f3069caf98511ceef92b8fea4f6a1563d5
|
||||
size 77614
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a31f0c12bb70449136443f9086103bd5b46356eedc2bb93ae1b6b10684ab69ca
|
||||
size 36285
|
||||
oid sha256:611a2d6c793a85eebe807b2ddd4446cc0bc21e4284343dd756e64f0232fb6815
|
||||
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.
|
||||
|
||||
|
||||
## 0.33.2 - 2025-11-13
|
||||
Nothing new
|
||||
|
||||
|
||||
## 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)
|
||||
* 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
|
||||
* 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.
|
||||
|
||||
|
||||
## 0.33.2 - 2025-11-13
|
||||
Nothing new
|
||||
|
||||
|
||||
## 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)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "egui_kittest"
|
||||
version = "0.33.1"
|
||||
version.workspace = true
|
||||
authors = ["Lucas Meurer <lucasmeurer96@gmail.com>", "Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Testing library for egui based on kittest and AccessKit"
|
||||
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.
|
||||
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
|
||||
* 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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
||||
## 0.33.2 - 2025-11-13
|
||||
Nothing new
|
||||
|
||||
|
||||
## 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)
|
||||
* 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.
|
||||
|
||||
|
||||
## 0.33.2 - 2025-11-13
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.33.0 - 2025-10-09
|
||||
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::{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::kittest::Queryable as _;
|
||||
|
||||
|
|
@ -61,3 +61,60 @@ fn text_edit_rtl() {
|
|||
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