mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Merge branch 'main' into theme_plugin
This commit is contained in:
2
.github/workflows/deploy_web_demo.yml
vendored
2
.github/workflows/deploy_web_demo.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
with:
|
||||
profile: minimal
|
||||
target: wasm32-unknown-unknown
|
||||
toolchain: 1.92.0
|
||||
toolchain: 1.95.0
|
||||
override: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
2
.github/workflows/preview_build.yml
vendored
2
.github/workflows/preview_build.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
toolchain: 1.95.0
|
||||
targets: wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
|
||||
18
.github/workflows/rust.yml
vendored
18
.github/workflows/rust.yml
vendored
@@ -5,7 +5,7 @@ name: Rust
|
||||
env:
|
||||
RUSTFLAGS: -D warnings
|
||||
RUSTDOCFLAGS: -D warnings
|
||||
NIGHTLY_VERSION: nightly-2025-09-16
|
||||
NIGHTLY_VERSION: nightly-2026-04-02
|
||||
|
||||
jobs:
|
||||
fmt-crank-check-test:
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
toolchain: 1.95.0
|
||||
|
||||
- name: Install packages (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
toolchain: 1.95.0
|
||||
targets: wasm32-unknown-unknown
|
||||
|
||||
- run: sudo apt-get update && sudo apt-get install libgtk-3-dev libatk1.0-dev
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
- name: wasm-bindgen
|
||||
uses: jetli/wasm-bindgen-action@v0.1.0
|
||||
with:
|
||||
version: "0.2.108" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml
|
||||
version: "0.2.126" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml
|
||||
|
||||
- run: ./scripts/wasm_bindgen_check.sh --skip-setup
|
||||
|
||||
@@ -148,7 +148,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
with:
|
||||
rust-version: "1.92.0"
|
||||
rust-version: "1.95.0"
|
||||
log-level: error
|
||||
command: check
|
||||
arguments: --target ${{ matrix.target }}
|
||||
@@ -164,7 +164,7 @@ jobs:
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
toolchain: 1.95.0
|
||||
targets: aarch64-linux-android
|
||||
|
||||
- name: Set up cargo cache
|
||||
@@ -186,7 +186,7 @@ jobs:
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
toolchain: 1.95.0
|
||||
targets: aarch64-apple-ios
|
||||
|
||||
- name: Set up cargo cache
|
||||
@@ -206,7 +206,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
toolchain: 1.95.0
|
||||
|
||||
- name: Set up cargo cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
@@ -228,7 +228,7 @@ jobs:
|
||||
lfs: true
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
toolchain: 1.95.0
|
||||
|
||||
- name: Set up cargo cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
1123
Cargo.lock
1123
Cargo.lock
File diff suppressed because it is too large
Load Diff
37
Cargo.toml
37
Cargo.toml
@@ -24,7 +24,7 @@ members = [
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
version = "0.35.0"
|
||||
|
||||
|
||||
@@ -71,8 +71,8 @@ egui_kittest = { version = "0.35.0", path = "crates/egui_kittest", default-featu
|
||||
eframe = { version = "0.35.0", path = "crates/eframe", default-features = false }
|
||||
|
||||
accesskit = "0.24.1"
|
||||
accesskit_consumer = "0.35.0"
|
||||
accesskit_winit = "0.32.0"
|
||||
accesskit_consumer = "0.35.0" # Can't update to 0.36+: kittest 0.4 pins accesskit_consumer 0.35, so bumping splits it into two versions
|
||||
accesskit_winit = "0.32.0" # Can't update to 0.33: it needs accesskit_macos 0.26.2, which pulls accesskit_consumer 0.37, duplicating the 0.35 that kittest 0.4 needs
|
||||
ahash = { version = "0.8.12", default-features = false, features = [
|
||||
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
|
||||
"std",
|
||||
@@ -97,12 +97,12 @@ font-types = { version = "0.11.3", default-features = false, features = [
|
||||
glow = "0.17.0"
|
||||
glutin = { version = "0.32.3", default-features = false }
|
||||
glutin-winit = { version = "0.5.0", default-features = false }
|
||||
harfrust = "0.7.0" # Can't update to 0.10: it needs read-fonts 0.40/font-types 0.12, but vello_cpu's glifo 0.1.1 pins read-fonts 0.39/font-types 0.11, so bumping duplicates them
|
||||
harfrust = "0.7.0" # Can't update to 0.8+: newer versions need read-fonts 0.40+/font-types 0.12, but vello_cpu's glifo 0.1.1 pins read-fonts 0.39/font-types 0.11, so bumping duplicates them
|
||||
home = "0.5.12"
|
||||
image = { version = "0.25.6", default-features = false } # Can't update to 0.25.7+: it needs png 0.18, which only matches resvg once resvg moves to tiny-skia 0.12 — blocked, see resvg below
|
||||
itertools = "0.14.0" # 0.15 splits us from puffin 0.20's itertools 0.14, adding a cargo-deny duplicate
|
||||
itertools = "0.15.0"
|
||||
jiff = { version = "0.2.29", default-features = false }
|
||||
js-sys = "0.3.77"
|
||||
js-sys = "0.3.103"
|
||||
kittest = { version = "0.4.0" }
|
||||
log = { version = "0.4.33", features = ["std"] }
|
||||
memoffset = "0.9.1"
|
||||
@@ -132,7 +132,7 @@ ron = "0.12.2"
|
||||
self_cell = "1.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_bytes = "0.11.19"
|
||||
similar-asserts = "2.0.0"
|
||||
similar-asserts = "2.0"
|
||||
skrifa = { version = "0.42.1", default-features = false, features = [
|
||||
"std",
|
||||
"autohint_shaping",
|
||||
@@ -144,9 +144,9 @@ syntect = { version = "5.3", default-features = false }
|
||||
tempfile = "3.27"
|
||||
thiserror = "2.0"
|
||||
tokio = "1.52"
|
||||
toml = { version = "1.0", default-features = false } # 1.1+ pulls winnow 1.x, duplicating the winnow 0.7 that winit's toml_edit needs
|
||||
toml = { version = "1.0", default-features = false }
|
||||
type-map = "0.5.1"
|
||||
unicode_names2 = { version = "3.1.0", default-features = false }
|
||||
unicode_names2 = { version = "3.1", default-features = false }
|
||||
unicode-general-category = "1.1"
|
||||
unicode-segmentation = "1.13"
|
||||
vello_cpu = { version = "0.0.9", default-features = false, features = [
|
||||
@@ -154,13 +154,13 @@ vello_cpu = { version = "0.0.9", default-features = false, features = [
|
||||
"u8_pipeline",
|
||||
"f32_pipeline",
|
||||
] }
|
||||
wasm-bindgen = "0.2.108" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml. Don't update this spuriously, because of https://github.com/rerun-io/rerun/issues/8766
|
||||
wasm-bindgen-futures = "0.4.58"
|
||||
wasm-bindgen = "0.2.126" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml. Don't update this spuriously, because of https://github.com/rerun-io/rerun/issues/8766
|
||||
wasm-bindgen-futures = "0.4.76"
|
||||
wayland-cursor = { version = "0.31.14", default-features = false }
|
||||
web-sys = "0.3.77"
|
||||
web-sys = "0.3.103"
|
||||
web-time = "1.1" # Timekeeping for native and web
|
||||
webbrowser = "1.2"
|
||||
wgpu = { version = "29.0", default-features = false, features = ["std"] }
|
||||
wgpu = { version = "30.0", default-features = false, features = ["std"] }
|
||||
windows-sys = "0.61.2"
|
||||
winit = { version = "0.30.13", default-features = false }
|
||||
|
||||
@@ -205,6 +205,7 @@ cloned_instead_of_copied = "warn"
|
||||
coerce_container_to_any = "warn"
|
||||
dbg_macro = "warn"
|
||||
debug_assert_with_mut_call = "warn"
|
||||
decimal_bitwise_operands = "warn"
|
||||
default_union_representation = "warn"
|
||||
derive_partial_eq_without_eq = "warn"
|
||||
disallowed_macros = "warn" # See clippy.toml
|
||||
@@ -217,9 +218,10 @@ doc_comment_double_space_linebreaks = "warn"
|
||||
doc_include_without_cfg = "warn"
|
||||
doc_link_with_quotes = "warn"
|
||||
doc_markdown = "warn"
|
||||
duration_suboptimal_units = "warn"
|
||||
elidable_lifetime_names = "warn"
|
||||
empty_enum = "warn"
|
||||
empty_enum_variants_with_brackets = "warn"
|
||||
empty_enums = "warn"
|
||||
empty_line_after_outer_attr = "warn"
|
||||
enum_glob_use = "warn"
|
||||
equatable_if_let = "warn"
|
||||
@@ -270,6 +272,7 @@ lossy_float_literal = "warn"
|
||||
macro_use_imports = "warn"
|
||||
manual_assert = "warn"
|
||||
manual_clamp = "warn"
|
||||
manual_ilog2 = "warn"
|
||||
manual_instant_elapsed = "warn"
|
||||
manual_is_power_of_two = "warn"
|
||||
manual_is_variant_and = "warn"
|
||||
@@ -299,10 +302,12 @@ needless_for_each = "warn"
|
||||
needless_pass_by_ref_mut = "warn"
|
||||
needless_pass_by_value = "warn"
|
||||
needless_raw_string_hashes = "warn"
|
||||
needless_type_cast = "warn"
|
||||
negative_feature_names = "warn"
|
||||
non_std_lazy_statics = "warn"
|
||||
non_zero_suggestions = "warn"
|
||||
nonstandard_macro_braces = "warn"
|
||||
only_used_in_recursion = "warn"
|
||||
option_as_ref_cloned = "warn"
|
||||
option_option = "warn"
|
||||
or_fun_call = "warn"
|
||||
@@ -325,6 +330,7 @@ ref_patterns = "warn"
|
||||
rest_pat_in_fully_bound_structs = "warn"
|
||||
return_and_then = "warn"
|
||||
same_functions_in_if_condition = "warn"
|
||||
same_length_and_capacity = "warn"
|
||||
self_only_used_in_recursion = "warn"
|
||||
semicolon_if_nothing_returned = "warn"
|
||||
set_contains_or_insert = "warn"
|
||||
@@ -341,6 +347,7 @@ suspicious_command_arg_space = "warn"
|
||||
suspicious_xor_used_as_pow = "warn"
|
||||
todo = "warn"
|
||||
too_long_first_doc_paragraph = "warn"
|
||||
too_many_arguments = "warn"
|
||||
trailing_empty_array = "warn"
|
||||
trait_duplication_in_bounds = "warn"
|
||||
transmute_ptr_to_ptr = "warn"
|
||||
@@ -358,6 +365,7 @@ unnecessary_safety_doc = "warn"
|
||||
unnecessary_self_imports = "warn"
|
||||
unnecessary_semicolon = "warn"
|
||||
unnecessary_struct_initialization = "warn"
|
||||
unnecessary_trailing_comma = "warn"
|
||||
unnecessary_wraps = "warn"
|
||||
unnested_or_patterns = "warn"
|
||||
unused_async = "warn"
|
||||
@@ -382,6 +390,7 @@ too_many_lines = "allow"
|
||||
|
||||
# These are meh:
|
||||
assigning_clones = "allow" # No please
|
||||
cast_possible_truncation = "allow" # too many hits
|
||||
let_underscore_must_use = "allow"
|
||||
let_underscore_untyped = "allow"
|
||||
manual_range_contains = "allow" # this one is just worse imho
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Section identical to scripts/clippy_wasm/clippy.toml:
|
||||
|
||||
msrv = "1.92"
|
||||
msrv = "1.95"
|
||||
|
||||
allow-unwrap-in-tests = true
|
||||
|
||||
|
||||
@@ -232,6 +232,7 @@ web-sys = { workspace = true, features = [
|
||||
"File",
|
||||
"FileList",
|
||||
"FocusEvent",
|
||||
"FocusOptions",
|
||||
"HtmlCanvasElement",
|
||||
"HtmlElement",
|
||||
"HtmlInputElement",
|
||||
|
||||
@@ -122,13 +122,13 @@ impl<T: WinitApp> WinitAppWrapper<T> {
|
||||
event_result
|
||||
}
|
||||
EventResult::RepaintNow(window_id) => {
|
||||
log::trace!("RepaintNow of {window_id:?}",);
|
||||
log::trace!("RepaintNow of {window_id:?}");
|
||||
self.windows_next_repaint_times
|
||||
.insert(window_id, Instant::now());
|
||||
event_result
|
||||
}
|
||||
EventResult::RepaintNext(window_id) => {
|
||||
log::trace!("RepaintNext of {window_id:?}",);
|
||||
log::trace!("RepaintNext of {window_id:?}");
|
||||
self.windows_next_repaint_times
|
||||
.insert(window_id, Instant::now());
|
||||
event_result
|
||||
|
||||
@@ -316,7 +316,7 @@ impl AppRunner {
|
||||
}
|
||||
|
||||
self.handle_platform_output(platform_output);
|
||||
if is_visible {
|
||||
if is_visible || !textures_delta.is_empty() {
|
||||
self.textures_delta.append(textures_delta);
|
||||
self.clipped_primitives = Some(self.egui_ctx.tessellate(shapes, pixels_per_point));
|
||||
}
|
||||
@@ -401,7 +401,7 @@ impl AppRunner {
|
||||
} else {
|
||||
// We are not editing text - give the focus to the canvas.
|
||||
self.text_agent.blur();
|
||||
self.canvas().focus().ok();
|
||||
super::focus_without_scroll(self.canvas()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,12 @@ pub(crate) struct WebInput {
|
||||
/// Helps to track the delta rotation from gesture events
|
||||
pub accumulated_rotation: f32,
|
||||
|
||||
/// The last modifier state we sent to egui.
|
||||
///
|
||||
/// The web has no dedicated modifier event, so we derive the state from each DOM event and
|
||||
/// emit [`egui::Event::ModifiersChanged`] when it changes (see [`Self::set_modifiers`]).
|
||||
pub modifiers: egui::Modifiers,
|
||||
|
||||
/// The raw input to `egui`.
|
||||
pub raw: egui::RawInput,
|
||||
}
|
||||
@@ -53,11 +59,23 @@ impl WebInput {
|
||||
}
|
||||
|
||||
// log::debug!("on_web_page_focus_change: {focused}");
|
||||
self.raw.modifiers = egui::Modifiers::default(); // Avoid sticky modifier keys on alt-tab:
|
||||
self.modifiers = egui::Modifiers::default(); // Avoid sticky modifier keys on alt-tab:
|
||||
self.raw.focused = focused;
|
||||
self.raw.events.push(egui::Event::WindowFocused(focused));
|
||||
self.primary_touch = None;
|
||||
}
|
||||
|
||||
/// Update the modifier state, emitting [`egui::Event::ModifiersChanged`] if it changed.
|
||||
///
|
||||
/// Call before pushing the DOM event itself so egui sees the new modifier state first.
|
||||
pub fn set_modifiers(&mut self, modifiers: egui::Modifiers) {
|
||||
if self.modifiers != modifiers {
|
||||
self.modifiers = modifiers;
|
||||
self.raw
|
||||
.events
|
||||
.push(egui::Event::ModifiersChanged(modifiers));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@@ -196,15 +196,18 @@ pub(crate) fn on_keydown(event: web_sys::KeyboardEvent, runner: &mut AppRunner)
|
||||
}
|
||||
|
||||
let modifiers = modifiers_from_kb_event(&event);
|
||||
runner.input.raw.modifiers = modifiers;
|
||||
runner.input.set_modifiers(modifiers);
|
||||
|
||||
let key = event.key();
|
||||
let egui_key = translate_key(&key);
|
||||
let logical_key = translate_key(&key);
|
||||
let physical_key = translate_key(&event.code());
|
||||
|
||||
if let Some(egui_key) = egui_key {
|
||||
// Fall back to the physical key so that modifier keys (which have no logical
|
||||
// `egui::Key`) and non-Latin layouts still produce a `Key` event.
|
||||
if let Some(active_key) = logical_key.or(physical_key) {
|
||||
let egui_event = egui::Event::Key {
|
||||
key: egui_key,
|
||||
physical_key: None, // TODO(fornwall)
|
||||
key: active_key,
|
||||
physical_key,
|
||||
pressed: true,
|
||||
repeat: false, // egui will fill this in for us!
|
||||
modifiers,
|
||||
@@ -213,11 +216,11 @@ pub(crate) fn on_keydown(event: web_sys::KeyboardEvent, runner: &mut AppRunner)
|
||||
runner.input.raw.events.push(egui_event);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
|
||||
let prevent_default = should_prevent_default_for_key(runner, &modifiers, egui_key);
|
||||
let prevent_default = should_prevent_default_for_key(runner, &modifiers, active_key);
|
||||
|
||||
if false {
|
||||
log::debug!(
|
||||
"On keydown {:?} {egui_key:?}, has_focus: {has_focus}, egui_wants_keyboard: {}, prevent_default: {prevent_default}",
|
||||
"On keydown {:?} {active_key:?}, has_focus: {has_focus}, egui_wants_keyboard: {}, prevent_default: {prevent_default}",
|
||||
event.key().as_str(),
|
||||
runner.egui_ctx().egui_wants_keyboard_input()
|
||||
);
|
||||
@@ -287,14 +290,17 @@ fn install_keyup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsV
|
||||
#[expect(clippy::needless_pass_by_value)] // So that we can pass it directly to `add_event_listener`
|
||||
pub(crate) fn on_keyup(event: web_sys::KeyboardEvent, runner: &mut AppRunner) {
|
||||
let modifiers = modifiers_from_kb_event(&event);
|
||||
runner.input.raw.modifiers = modifiers;
|
||||
runner.input.set_modifiers(modifiers);
|
||||
|
||||
let mut should_stop_propagation = true;
|
||||
|
||||
if let Some(key) = translate_key(&event.key()) {
|
||||
let logical_key = translate_key(&event.key());
|
||||
let physical_key = translate_key(&event.code());
|
||||
|
||||
if let Some(active_key) = logical_key.or(physical_key) {
|
||||
let egui_event = egui::Event::Key {
|
||||
key,
|
||||
physical_key: None, // TODO(fornwall)
|
||||
key: active_key,
|
||||
physical_key,
|
||||
pressed: false,
|
||||
repeat: false,
|
||||
modifiers,
|
||||
@@ -535,11 +541,11 @@ fn install_pointerdown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(
|
||||
"pointerdown",
|
||||
|event: web_sys::PointerEvent, runner: &mut AppRunner| {
|
||||
let modifiers = modifiers_from_mouse_event(&event);
|
||||
runner.input.raw.modifiers = modifiers;
|
||||
runner.input.set_modifiers(modifiers);
|
||||
let mut should_stop_propagation = true;
|
||||
if let Some(button) = button_from_mouse_event(&event) {
|
||||
let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
|
||||
let modifiers = runner.input.raw.modifiers;
|
||||
let modifiers = runner.input.modifiers;
|
||||
let egui_event = egui::Event::PointerButton {
|
||||
pos,
|
||||
button,
|
||||
@@ -572,7 +578,7 @@ fn install_pointerup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
|
||||
"pointerup",
|
||||
|event: web_sys::PointerEvent, runner| {
|
||||
let modifiers = modifiers_from_mouse_event(&event);
|
||||
runner.input.raw.modifiers = modifiers;
|
||||
runner.input.set_modifiers(modifiers);
|
||||
|
||||
let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
|
||||
|
||||
@@ -581,7 +587,7 @@ fn install_pointerup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
|
||||
egui::pos2(event.client_x() as f32, event.client_y() as f32),
|
||||
) && let Some(button) = button_from_mouse_event(&event)
|
||||
{
|
||||
let modifiers = runner.input.raw.modifiers;
|
||||
let modifiers = runner.input.modifiers;
|
||||
let egui_event = egui::Event::PointerButton {
|
||||
pos,
|
||||
button,
|
||||
@@ -599,7 +605,7 @@ fn install_pointerup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
|
||||
// not working when focusing on a text field in an egui app.
|
||||
// This attempts to fix that by forcing the focus on any
|
||||
// click on the canvas.
|
||||
runner.canvas().focus().ok();
|
||||
super::focus_without_scroll(runner.canvas()).ok();
|
||||
|
||||
// In Safari we are only allowed to do certain things
|
||||
// (like playing audio, start a download, etc)
|
||||
@@ -647,7 +653,7 @@ fn is_interested_in_pointer_event(runner: &AppRunner, pos: egui::Pos2) -> bool {
|
||||
fn install_mousemove(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
|
||||
runner_ref.add_event_listener(target, "mousemove", |event: web_sys::MouseEvent, runner| {
|
||||
let modifiers = modifiers_from_mouse_event(&event);
|
||||
runner.input.raw.modifiers = modifiers;
|
||||
runner.input.set_modifiers(modifiers);
|
||||
|
||||
let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx());
|
||||
|
||||
@@ -705,7 +711,7 @@ fn install_touchstart(runner_ref: &WebRunner, target: &EventTarget) -> Result<()
|
||||
pos,
|
||||
button: egui::PointerButton::Primary,
|
||||
pressed: true,
|
||||
modifiers: runner.input.raw.modifiers,
|
||||
modifiers: runner.input.modifiers,
|
||||
};
|
||||
should_stop_propagation = (runner.web_options.should_stop_propagation)(&egui_event);
|
||||
should_prevent_default = (runner.web_options.should_prevent_default)(&egui_event);
|
||||
@@ -770,7 +776,7 @@ fn install_touchend(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
|
||||
pos,
|
||||
button: egui::PointerButton::Primary,
|
||||
pressed: false,
|
||||
modifiers: runner.input.raw.modifiers,
|
||||
modifiers: runner.input.modifiers,
|
||||
};
|
||||
should_stop_propagation &= (runner.web_options.should_stop_propagation)(&egui_event);
|
||||
should_prevent_default &= (runner.web_options.should_prevent_default)(&egui_event);
|
||||
@@ -832,7 +838,7 @@ fn install_wheel(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsV
|
||||
|
||||
let modifiers = modifiers_from_wheel_event(&event);
|
||||
|
||||
let egui_event = if modifiers.ctrl && !runner.input.raw.modifiers.ctrl {
|
||||
let egui_event = if modifiers.ctrl && !runner.input.modifiers.ctrl {
|
||||
// The browser is saying the ctrl key is down, but it isn't _really_.
|
||||
// This happens on pinch-to-zoom on multitouch trackpads
|
||||
// egui will treat ctrl+scroll as zoom, so it all works.
|
||||
|
||||
@@ -84,6 +84,17 @@ pub(crate) fn has_focus<T: JsCast>(element: &T) -> bool {
|
||||
try_has_focus(element).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Focus the given element without scrolling it into view.
|
||||
///
|
||||
/// Scrolling the element into view would scroll the whole page when
|
||||
/// the app is embedded in a larger scrollable page,
|
||||
/// see <https://github.com/emilk/egui/issues/8295>.
|
||||
pub(crate) fn focus_without_scroll(element: &web_sys::HtmlElement) -> Result<(), JsValue> {
|
||||
let options = web_sys::FocusOptions::new();
|
||||
options.set_prevent_scroll(true);
|
||||
element.focus_with_options(&options)
|
||||
}
|
||||
|
||||
/// Current time in seconds (since undefined point in time).
|
||||
///
|
||||
/// Monotonically increasing.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use std::cell::Cell;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{Document, Node};
|
||||
use web_sys::Document;
|
||||
|
||||
use super::{AppRunner, WebRunner};
|
||||
|
||||
@@ -15,19 +15,23 @@ pub struct TextAgent {
|
||||
|
||||
impl TextAgent {
|
||||
/// Attach the agent to the document.
|
||||
pub fn attach(runner_ref: &WebRunner, root: Node) -> Result<Self, JsValue> {
|
||||
pub fn attach(
|
||||
runner_ref: &WebRunner,
|
||||
canvas: &web_sys::HtmlCanvasElement,
|
||||
) -> Result<Self, JsValue> {
|
||||
let document = web_sys::window().unwrap().document().unwrap();
|
||||
|
||||
// create an `<input>` element
|
||||
let input = document
|
||||
.create_element("input")?
|
||||
.dyn_into::<web_sys::HtmlElement>()?;
|
||||
input.set_autofocus(true)?;
|
||||
let input = input.dyn_into::<web_sys::HtmlInputElement>()?;
|
||||
.dyn_into::<web_sys::HtmlInputElement>()?;
|
||||
input.set_type("text");
|
||||
input.set_attribute("autocapitalize", "off")?;
|
||||
|
||||
// append it to `<body>` and hide it outside of the viewport
|
||||
// Hide the element, and park it over the canvas
|
||||
// so that focusing it can never scroll some other part
|
||||
// of the page into view.
|
||||
let canvas_rect = super::canvas_content_rect(canvas);
|
||||
let style = input.style();
|
||||
style.set_property("background-color", "transparent")?;
|
||||
style.set_property("border", "none")?;
|
||||
@@ -36,11 +40,12 @@ impl TextAgent {
|
||||
style.set_property("height", "1px")?;
|
||||
style.set_property("caret-color", "transparent")?;
|
||||
style.set_property("position", "absolute")?;
|
||||
style.set_property("top", "0")?;
|
||||
style.set_property("left", "0")?;
|
||||
style.set_property("top", &format!("{}px", canvas_rect.min.y))?;
|
||||
style.set_property("left", &format!("{}px", canvas_rect.min.x))?;
|
||||
// Prevent auto-zoom on mobile browsers (requires at least 16px).
|
||||
style.set_property("font-size", "16px")?;
|
||||
|
||||
let root = canvas.get_root_node();
|
||||
if root.has_type::<Document>() {
|
||||
// root object is a document, append to its body
|
||||
root.dyn_into::<Document>()?
|
||||
@@ -52,6 +57,13 @@ impl TextAgent {
|
||||
root.append_child(&input)?;
|
||||
}
|
||||
|
||||
// Focus the app on startup, without scrolling the page.
|
||||
// We do this instead of setting the `autofocus` attribute,
|
||||
// since the browser scrolls the focused element into view when
|
||||
// honoring `autofocus`, and there is no way to prevent that.
|
||||
// See https://github.com/emilk/egui/issues/8295
|
||||
super::focus_without_scroll(&input).ok();
|
||||
|
||||
// attach event listeners
|
||||
|
||||
let on_input = {
|
||||
@@ -67,7 +79,7 @@ impl TextAgent {
|
||||
// between versions 14.7.09 and 17.0.12.
|
||||
if !event.is_composing() {
|
||||
input.blur().ok();
|
||||
input.focus().ok();
|
||||
super::focus_without_scroll(&input).ok();
|
||||
}
|
||||
|
||||
if event.is_composing() {
|
||||
@@ -221,7 +233,7 @@ impl TextAgent {
|
||||
|
||||
log::trace!("Focusing text agent");
|
||||
|
||||
if let Err(err) = self.input.focus() {
|
||||
if let Err(err) = super::focus_without_scroll(&self.input) {
|
||||
log::error!("failed to set focus: {}", super::string_from_js_value(&err));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ impl WebPainterWgpu {
|
||||
&& create_new.display_handle.is_none()
|
||||
{
|
||||
// Force WebGL, useful for quick & dirty testing:
|
||||
//create_new.instance_descriptor.backends = wgpu::Backends::GL;
|
||||
// create_new.instance_descriptor.backends = wgpu::Backends::GL;
|
||||
create_new.display_handle = Some(Box::new(WebDisplay));
|
||||
}
|
||||
|
||||
@@ -380,7 +380,7 @@ impl WebPainter for WebPainterWgpu {
|
||||
);
|
||||
}
|
||||
|
||||
frame.present();
|
||||
render_state.queue.present(frame);
|
||||
}
|
||||
|
||||
// Free textures marked for destruction **after** queue submit since they might still be used in the current frame.
|
||||
|
||||
@@ -73,7 +73,7 @@ impl WebRunner {
|
||||
|
||||
{
|
||||
// First set up the app runner:
|
||||
let text_agent = TextAgent::attach(self, canvas.get_root_node())?;
|
||||
let text_agent = TextAgent::attach(self, &canvas)?;
|
||||
let app_runner =
|
||||
AppRunner::new(canvas.clone(), web_options, app_creator, text_agent).await?;
|
||||
self.app_runner.replace(Some(app_runner));
|
||||
|
||||
@@ -212,10 +212,14 @@ impl CaptureState {
|
||||
let buffer_slice = buffer.slice(..);
|
||||
|
||||
let mut pixels = Vec::with_capacity((tex_extent.width * tex_extent.height) as usize);
|
||||
for padded_row in buffer_slice
|
||||
.get_mapped_range()
|
||||
.chunks(padding.padded_bytes_per_row as usize)
|
||||
{
|
||||
let mapped_range = match buffer_slice.get_mapped_range() {
|
||||
Ok(range) => range,
|
||||
Err(err) => {
|
||||
log::error!("Failed to get mapped range for reading: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
for padded_row in mapped_range.chunks(padding.padded_bytes_per_row as usize) {
|
||||
let row = &padded_row[..padding.unpadded_bytes_per_row as usize];
|
||||
for color in row.chunks(4) {
|
||||
pixels.push(epaint::Color32::from_rgba_premultiplied(
|
||||
@@ -226,6 +230,7 @@ impl CaptureState {
|
||||
));
|
||||
}
|
||||
}
|
||||
drop(mapped_range);
|
||||
buffer.unmap();
|
||||
|
||||
tx.send((
|
||||
|
||||
@@ -150,6 +150,7 @@ async fn request_adapter(
|
||||
// * fails if there's no software rasterizer available
|
||||
// * can achieve the same with `native_adapter_selector`
|
||||
force_fallback_adapter: false,
|
||||
apply_limit_buckets: false,
|
||||
})
|
||||
.await
|
||||
.inspect_err(|_err| {
|
||||
@@ -473,6 +474,7 @@ pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String {
|
||||
subgroup_min_size,
|
||||
subgroup_max_size,
|
||||
transient_saves_memory,
|
||||
limit_bucket,
|
||||
} = &info;
|
||||
|
||||
// Example values:
|
||||
@@ -523,9 +525,10 @@ pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String {
|
||||
}
|
||||
write!(
|
||||
summary,
|
||||
", transient_saves_memory: {transient_saves_memory}"
|
||||
", transient_saves_memory: {transient_saves_memory:?}"
|
||||
)
|
||||
.ok();
|
||||
write!(summary, ", limit_bucket: {limit_bucket:?}").ok();
|
||||
|
||||
summary
|
||||
}
|
||||
|
||||
@@ -375,14 +375,14 @@ impl Renderer {
|
||||
vertex: wgpu::VertexState {
|
||||
entry_point: Some("vs_main"),
|
||||
module: &module,
|
||||
buffers: &[wgpu::VertexBufferLayout {
|
||||
buffers: &[Some(wgpu::VertexBufferLayout {
|
||||
array_stride: 5 * 4,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
// 0: vec2 position
|
||||
// 1: vec2 texture coordinates
|
||||
// 2: uint color
|
||||
attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32],
|
||||
}],
|
||||
})],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default()
|
||||
},
|
||||
primitive: wgpu::PrimitiveState {
|
||||
|
||||
@@ -39,6 +39,7 @@ where
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[expect(clippy::large_enum_variant)]
|
||||
pub enum WgpuSetup {
|
||||
/// Construct a wgpu setup using some predefined settings & heuristics.
|
||||
/// This is the default option. You can customize most behaviours overriding the
|
||||
|
||||
@@ -765,7 +765,7 @@ impl Painter {
|
||||
profiling::scope!("present");
|
||||
// wgpu doesn't document where vsync can happen. Maybe here?
|
||||
let start = web_time::Instant::now();
|
||||
output_frame.present();
|
||||
render_state.queue.present(output_frame);
|
||||
vsync_sec += start.elapsed().as_secs_f32();
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,13 @@ pub struct State {
|
||||
viewport_id: ViewportId,
|
||||
start_time: web_time::Instant,
|
||||
egui_input: egui::RawInput,
|
||||
|
||||
/// The current modifier state.
|
||||
///
|
||||
/// We keep a copy so we can stamp
|
||||
/// it onto per-event `modifiers` fields and emit [`egui::Event::ModifiersChanged`].
|
||||
modifiers: egui::Modifiers,
|
||||
|
||||
pointer_pos_in_points: Option<egui::Pos2>,
|
||||
any_pointer_button_down: bool,
|
||||
current_cursor_icon: Option<egui::CursorIcon>,
|
||||
@@ -146,6 +153,7 @@ impl State {
|
||||
.unwrap_or_else(web_time::Instant::now),
|
||||
egui_ctx,
|
||||
egui_input,
|
||||
modifiers: egui::Modifiers::default(),
|
||||
pointer_pos_in_points: None,
|
||||
any_pointer_button_down: false,
|
||||
current_cursor_icon: None,
|
||||
@@ -422,6 +430,10 @@ impl State {
|
||||
};
|
||||
|
||||
self.egui_input.focused = focused;
|
||||
if !focused {
|
||||
// Avoid sticky modifiers when focus is lost (egui clears its own copy too).
|
||||
self.modifiers = egui::Modifiers::default();
|
||||
}
|
||||
self.egui_input
|
||||
.events
|
||||
.push(egui::Event::WindowFocused(focused));
|
||||
@@ -473,16 +485,20 @@ impl State {
|
||||
let shift = state.shift_key();
|
||||
let super_ = state.super_key();
|
||||
|
||||
self.egui_input.modifiers.alt = alt;
|
||||
self.egui_input.modifiers.ctrl = ctrl;
|
||||
self.egui_input.modifiers.shift = shift;
|
||||
self.egui_input.modifiers.mac_cmd = cfg!(target_os = "macos") && super_;
|
||||
self.egui_input.modifiers.command = if cfg!(target_os = "macos") {
|
||||
self.modifiers.alt = alt;
|
||||
self.modifiers.ctrl = ctrl;
|
||||
self.modifiers.shift = shift;
|
||||
self.modifiers.mac_cmd = cfg!(target_os = "macos") && super_;
|
||||
self.modifiers.command = if cfg!(target_os = "macos") {
|
||||
super_
|
||||
} else {
|
||||
ctrl
|
||||
};
|
||||
|
||||
self.egui_input
|
||||
.events
|
||||
.push(egui::Event::ModifiersChanged(self.modifiers));
|
||||
|
||||
EventResponse {
|
||||
repaint: true,
|
||||
consumed: false,
|
||||
@@ -541,7 +557,7 @@ impl State {
|
||||
unit: egui::MouseWheelUnit::Point,
|
||||
delta: Vec2::new(delta.x, delta.y) / pixels_per_point,
|
||||
phase: to_egui_touch_phase(*phase),
|
||||
modifiers: self.egui_input.modifiers,
|
||||
modifiers: self.modifiers,
|
||||
});
|
||||
EventResponse {
|
||||
repaint: true,
|
||||
@@ -790,7 +806,7 @@ impl State {
|
||||
pos,
|
||||
button,
|
||||
pressed,
|
||||
modifiers: self.egui_input.modifiers,
|
||||
modifiers: self.modifiers,
|
||||
});
|
||||
|
||||
if self.simulate_touch_screen {
|
||||
@@ -937,7 +953,7 @@ impl State {
|
||||
),
|
||||
};
|
||||
let phase = to_egui_touch_phase(phase);
|
||||
let modifiers = self.egui_input.modifiers;
|
||||
let modifiers = self.modifiers;
|
||||
self.egui_input.events.push(egui::Event::MouseWheel {
|
||||
unit,
|
||||
delta,
|
||||
@@ -998,13 +1014,13 @@ impl State {
|
||||
// See also: https://github.com/emilk/egui/issues/3653
|
||||
if let Some(active_key) = logical_key.or(physical_key) {
|
||||
if pressed {
|
||||
if is_cut_command(self.egui_input.modifiers, active_key) {
|
||||
if is_cut_command(self.modifiers, active_key) {
|
||||
self.egui_input.events.push(egui::Event::Cut);
|
||||
return;
|
||||
} else if is_copy_command(self.egui_input.modifiers, active_key) {
|
||||
} else if is_copy_command(self.modifiers, active_key) {
|
||||
self.egui_input.events.push(egui::Event::Copy);
|
||||
return;
|
||||
} else if is_paste_command(self.egui_input.modifiers, active_key) {
|
||||
} else if is_paste_command(self.modifiers, active_key) {
|
||||
if let Some(contents) = self.clipboard.get() {
|
||||
let contents = contents.replace("\r\n", "\n");
|
||||
if !contents.is_empty() {
|
||||
@@ -1020,7 +1036,7 @@ impl State {
|
||||
physical_key,
|
||||
pressed,
|
||||
repeat: false, // egui will fill this in for us!
|
||||
modifiers: self.egui_input.modifiers,
|
||||
modifiers: self.modifiers,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1036,9 +1052,8 @@ impl State {
|
||||
// We need to ignore these characters that are side-effects of commands.
|
||||
// Also make sure the key is pressed (not released). On Linux, text might
|
||||
// contain some data even when the key is released.
|
||||
let is_cmd = self.egui_input.modifiers.ctrl
|
||||
|| self.egui_input.modifiers.command
|
||||
|| self.egui_input.modifiers.mac_cmd;
|
||||
let is_cmd =
|
||||
self.modifiers.ctrl || self.modifiers.command || self.modifiers.mac_cmd;
|
||||
if pressed && !is_cmd {
|
||||
self.egui_input
|
||||
.events
|
||||
|
||||
@@ -95,7 +95,7 @@ impl AnimationManager {
|
||||
anim.from_value..=anim.to_value,
|
||||
);
|
||||
if anim.to_value != value {
|
||||
anim.from_value = current_value; //start new animation from current position of playing animation
|
||||
anim.from_value = current_value; // start new animation from current position of playing animation
|
||||
anim.to_value = value;
|
||||
anim.toggle_time = input.time;
|
||||
}
|
||||
|
||||
@@ -88,16 +88,36 @@ pub fn capture() -> String {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove stuff that isn't user calls:
|
||||
// Remove frames that are part of egui itself, since they are not useful to the user.
|
||||
let skip_crates = [
|
||||
"alloc",
|
||||
"backtrace",
|
||||
"core",
|
||||
"eframe",
|
||||
"egui_extras",
|
||||
"egui_plot",
|
||||
"egui",
|
||||
"epaint",
|
||||
"std",
|
||||
"winit",
|
||||
];
|
||||
for crate_name in skip_crates {
|
||||
let name = frame.name.trim_start_matches('<');
|
||||
if name.starts_with(crate_name)
|
||||
&& name
|
||||
.as_bytes()
|
||||
.get(crate_name.len())
|
||||
.copied()
|
||||
.is_none_or(|b| b.is_ascii_punctuation() && b != b'_')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let skip_prefixes = [
|
||||
// "backtrace::", // not needed, since we cut at egui::callstack::capture
|
||||
"egui::",
|
||||
"<egui::",
|
||||
"<F as egui::widgets::Widget>",
|
||||
"egui_plot::",
|
||||
"egui_extras::",
|
||||
"core::ptr::drop_in_place<egui::ui::Ui>",
|
||||
"eframe::",
|
||||
"core::ops::function::FnOnce::call_once",
|
||||
"<alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once",
|
||||
];
|
||||
|
||||
@@ -571,6 +571,7 @@ impl<'a> Popup<'a> {
|
||||
.fixed_pos(anchor)
|
||||
.sense(sense)
|
||||
.layout(layout)
|
||||
.sizing_pass(!was_open_last_frame)
|
||||
.info(info.unwrap_or_else(|| {
|
||||
UiStackInfo::new(kind.into()).with_tag_value(
|
||||
MenuConfig::MENU_CONFIG_TAG,
|
||||
|
||||
@@ -3544,7 +3544,7 @@ impl Context {
|
||||
if !is_visible {
|
||||
continue;
|
||||
}
|
||||
let text = format!("{} - {:?}", layer_id.short_debug_format(), area.rect(),);
|
||||
let text = format!("{} - {:?}", layer_id.short_debug_format(), area.rect());
|
||||
// TODO(emilk): `Sense::hover_highlight()`
|
||||
let response =
|
||||
ui.add(Label::new(RichText::new(text).monospace()).sense(Sense::click()));
|
||||
|
||||
@@ -69,6 +69,9 @@ pub enum Event {
|
||||
modifiers: Modifiers,
|
||||
},
|
||||
|
||||
/// The set of held modifier keys changed.
|
||||
ModifiersChanged(Modifiers),
|
||||
|
||||
/// The mouse or touch moved to a new place.
|
||||
PointerMoved(Pos2),
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{OrderedViewportIdMap, Theme, ViewportId, ViewportIdMap, emath::Rect};
|
||||
|
||||
use super::{DroppedFile, Event, HoveredFile, Modifiers, SafeAreaInsets, ViewportInfo};
|
||||
use super::{DroppedFile, Event, HoveredFile, SafeAreaInsets, ViewportInfo};
|
||||
|
||||
/// What the integrations provides to egui at the start of each frame.
|
||||
///
|
||||
@@ -53,9 +53,6 @@ pub struct RawInput {
|
||||
/// Can safely be left at its default value.
|
||||
pub predicted_dt: f32,
|
||||
|
||||
/// Which modifier keys are down at the start of the frame?
|
||||
pub modifiers: Modifiers,
|
||||
|
||||
/// In-order events received this frame.
|
||||
///
|
||||
/// There is currently no way to know if egui handles a particular event,
|
||||
@@ -92,7 +89,6 @@ impl Default for RawInput {
|
||||
max_texture_side: None,
|
||||
time: None,
|
||||
predicted_dt: 1.0 / 60.0,
|
||||
modifiers: Modifiers::default(),
|
||||
events: vec![],
|
||||
hovered_files: Default::default(),
|
||||
dropped_files: Default::default(),
|
||||
@@ -127,7 +123,6 @@ impl RawInput {
|
||||
max_texture_side: self.max_texture_side.take(),
|
||||
time: self.time,
|
||||
predicted_dt: self.predicted_dt,
|
||||
modifiers: self.modifiers,
|
||||
events: std::mem::take(&mut self.events),
|
||||
hovered_files: self.hovered_files.clone(),
|
||||
dropped_files: std::mem::take(&mut self.dropped_files),
|
||||
@@ -145,7 +140,6 @@ impl RawInput {
|
||||
max_texture_side,
|
||||
time,
|
||||
predicted_dt,
|
||||
modifiers,
|
||||
mut events,
|
||||
mut hovered_files,
|
||||
mut dropped_files,
|
||||
@@ -160,7 +154,6 @@ impl RawInput {
|
||||
self.max_texture_side = max_texture_side.or(self.max_texture_side);
|
||||
self.time = time; // use latest time
|
||||
self.predicted_dt = predicted_dt; // use latest dt
|
||||
self.modifiers = modifiers; // use latest
|
||||
self.events.append(&mut events);
|
||||
self.hovered_files.append(&mut hovered_files);
|
||||
self.dropped_files.append(&mut dropped_files);
|
||||
@@ -179,7 +172,6 @@ impl RawInput {
|
||||
max_texture_side,
|
||||
time,
|
||||
predicted_dt,
|
||||
modifiers,
|
||||
events,
|
||||
hovered_files,
|
||||
dropped_files,
|
||||
@@ -210,7 +202,6 @@ impl RawInput {
|
||||
ui.label("time: None");
|
||||
}
|
||||
ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt));
|
||||
ui.label(format!("modifiers: {modifiers:#?}"));
|
||||
ui.label(format!("hovered_files: {}", hovered_files.len()));
|
||||
ui.label(format!("dropped_files: {}", dropped_files.len()));
|
||||
ui.label(format!("focused: {focused}"));
|
||||
|
||||
@@ -384,7 +384,7 @@ impl Key {
|
||||
"Escape" | "Esc" => Self::Escape,
|
||||
"Tab" => Self::Tab,
|
||||
"Backspace" => Self::Backspace,
|
||||
"Enter" | "Return" => Self::Enter,
|
||||
"Enter" | "Return" | "NumpadEnter" => Self::Enter,
|
||||
|
||||
"Help" | "Insert" => Self::Insert,
|
||||
"Delete" => Self::Delete,
|
||||
@@ -399,19 +399,19 @@ impl Key {
|
||||
|
||||
" " | "Space" => Self::Space,
|
||||
":" | "Colon" => Self::Colon,
|
||||
"," | "Comma" => Self::Comma,
|
||||
"-" | "−" | "Minus" => Self::Minus,
|
||||
"." | "Period" => Self::Period,
|
||||
"+" | "Plus" => Self::Plus,
|
||||
"," | "Comma" | "NumpadComma" => Self::Comma,
|
||||
"-" | "−" | "Minus" | "NumpadSubtract" => Self::Minus,
|
||||
"." | "Period" | "NumpadDecimal" => Self::Period,
|
||||
"+" | "Plus" | "NumpadAdd" => Self::Plus,
|
||||
"=" | "Equal" | "Equals" | "NumpadEqual" => Self::Equals,
|
||||
";" | "Semicolon" => Self::Semicolon,
|
||||
"\\" | "Backslash" => Self::Backslash,
|
||||
"/" | "Slash" => Self::Slash,
|
||||
"/" | "Slash" | "NumpadDivide" => Self::Slash,
|
||||
"|" | "Pipe" => Self::Pipe,
|
||||
"?" | "Questionmark" => Self::Questionmark,
|
||||
"!" | "Exclamationmark" => Self::Exclamationmark,
|
||||
"[" | "OpenBracket" => Self::OpenBracket,
|
||||
"]" | "CloseBracket" => Self::CloseBracket,
|
||||
"[" | "OpenBracket" | "BracketLeft" => Self::OpenBracket,
|
||||
"]" | "CloseBracket" | "BracketRight" => Self::CloseBracket,
|
||||
"{" | "OpenCurlyBracket" => Self::OpenCurlyBracket,
|
||||
"}" | "CloseCurlyBracket" => Self::CloseCurlyBracket,
|
||||
"`" | "Backtick" | "Backquote" | "Grave" => Self::Backtick,
|
||||
@@ -428,32 +428,32 @@ impl Key {
|
||||
"8" | "Digit8" | "Numpad8" => Self::Num8,
|
||||
"9" | "Digit9" | "Numpad9" => Self::Num9,
|
||||
|
||||
"a" | "A" => Self::A,
|
||||
"b" | "B" => Self::B,
|
||||
"c" | "C" => Self::C,
|
||||
"d" | "D" => Self::D,
|
||||
"e" | "E" => Self::E,
|
||||
"f" | "F" => Self::F,
|
||||
"g" | "G" => Self::G,
|
||||
"h" | "H" => Self::H,
|
||||
"i" | "I" => Self::I,
|
||||
"j" | "J" => Self::J,
|
||||
"k" | "K" => Self::K,
|
||||
"l" | "L" => Self::L,
|
||||
"m" | "M" => Self::M,
|
||||
"n" | "N" => Self::N,
|
||||
"o" | "O" => Self::O,
|
||||
"p" | "P" => Self::P,
|
||||
"q" | "Q" => Self::Q,
|
||||
"r" | "R" => Self::R,
|
||||
"s" | "S" => Self::S,
|
||||
"t" | "T" => Self::T,
|
||||
"u" | "U" => Self::U,
|
||||
"v" | "V" => Self::V,
|
||||
"w" | "W" => Self::W,
|
||||
"x" | "X" => Self::X,
|
||||
"y" | "Y" => Self::Y,
|
||||
"z" | "Z" => Self::Z,
|
||||
"a" | "A" | "KeyA" => Self::A,
|
||||
"b" | "B" | "KeyB" => Self::B,
|
||||
"c" | "C" | "KeyC" => Self::C,
|
||||
"d" | "D" | "KeyD" => Self::D,
|
||||
"e" | "E" | "KeyE" => Self::E,
|
||||
"f" | "F" | "KeyF" => Self::F,
|
||||
"g" | "G" | "KeyG" => Self::G,
|
||||
"h" | "H" | "KeyH" => Self::H,
|
||||
"i" | "I" | "KeyI" => Self::I,
|
||||
"j" | "J" | "KeyJ" => Self::J,
|
||||
"k" | "K" | "KeyK" => Self::K,
|
||||
"l" | "L" | "KeyL" => Self::L,
|
||||
"m" | "M" | "KeyM" => Self::M,
|
||||
"n" | "N" | "KeyN" => Self::N,
|
||||
"o" | "O" | "KeyO" => Self::O,
|
||||
"p" | "P" | "KeyP" => Self::P,
|
||||
"q" | "Q" | "KeyQ" => Self::Q,
|
||||
"r" | "R" | "KeyR" => Self::R,
|
||||
"s" | "S" | "KeyS" => Self::S,
|
||||
"t" | "T" | "KeyT" => Self::T,
|
||||
"u" | "U" | "KeyU" => Self::U,
|
||||
"v" | "V" | "KeyV" => Self::V,
|
||||
"w" | "W" | "KeyW" => Self::W,
|
||||
"x" | "X" | "KeyX" => Self::X,
|
||||
"y" | "Y" | "KeyY" => Self::Y,
|
||||
"z" | "Z" | "KeyZ" => Self::Z,
|
||||
|
||||
"F1" => Self::F1,
|
||||
"F2" => Self::F2,
|
||||
@@ -499,8 +499,9 @@ impl Key {
|
||||
"ControlRight" => Self::ControlRight,
|
||||
"AltLeft" => Self::AltLeft,
|
||||
"AltRight" => Self::AltRight,
|
||||
"SuperLeft" => Self::SuperLeft,
|
||||
"SuperRight" => Self::SuperRight,
|
||||
|
||||
"SuperLeft" | "MetaLeft" | "OSLeft" => Self::SuperLeft,
|
||||
"SuperRight" | "MetaRight" | "OSRight" => Self::SuperRight,
|
||||
|
||||
"IntlBackslash" => Self::IntlBackslash,
|
||||
|
||||
|
||||
@@ -201,8 +201,6 @@ fn contains_circle(interact_rect: emath::Rect, pos: Pos2, radius: f32) -> bool {
|
||||
}
|
||||
|
||||
fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
|
||||
#![expect(clippy::collapsible_else_if)]
|
||||
|
||||
// First find the best direct hits:
|
||||
let hit_click = find_closest_within(
|
||||
close.iter().copied().filter(|w| w.sense.senses_click()),
|
||||
|
||||
@@ -393,6 +393,7 @@ impl InputState {
|
||||
let pointer = self.pointer.begin_pass(time, &new, options);
|
||||
|
||||
let mut keys_down = self.keys_down;
|
||||
let mut modifiers = self.modifiers;
|
||||
let mut zoom_factor_delta = 1.0; // TODO(emilk): smoothing for zoom factor
|
||||
let mut rotation_radians = 0.0;
|
||||
|
||||
@@ -429,6 +430,9 @@ impl InputState {
|
||||
*modifiers,
|
||||
);
|
||||
}
|
||||
Event::ModifiersChanged(new_modifiers) => {
|
||||
modifiers = *new_modifiers;
|
||||
}
|
||||
Event::Zoom(factor) => {
|
||||
zoom_factor_delta *= *factor;
|
||||
}
|
||||
@@ -442,6 +446,7 @@ impl InputState {
|
||||
// So we take the safe route and just clear all the keys and modifiers when
|
||||
// the app loses focus.
|
||||
keys_down.clear();
|
||||
modifiers = Modifiers::default();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -482,7 +487,7 @@ impl InputState {
|
||||
predicted_dt: new.predicted_dt,
|
||||
stable_dt,
|
||||
focused: new.focused,
|
||||
modifiers: new.modifiers,
|
||||
modifiers,
|
||||
keys_down,
|
||||
events: new.events.clone(), // TODO(emilk): remove clone() and use raw.events
|
||||
raw: new,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! Try the live web demo: <https://www.egui.rs/#demo>. Read more about egui at <https://github.com/emilk/egui>.
|
||||
//!
|
||||
//! `egui` is in heavy development, with each new version having breaking changes.
|
||||
//! You need to have rust 1.92.0 or later to use `egui`.
|
||||
//! You need to have rust 1.95.0 or later to use `egui`.
|
||||
//!
|
||||
//! To quickly get started with egui, you can take a look at [`eframe_template`](https://github.com/emilk/eframe_template)
|
||||
//! which uses [`eframe`](https://docs.rs/eframe).
|
||||
|
||||
@@ -1460,9 +1460,9 @@ fn order_map_total_ordering() {
|
||||
// Assert that `areas.compare_order()` forms a total ordering
|
||||
let mut equivalence_classes = vec![0];
|
||||
let mut i = 0;
|
||||
for l in layers.windows(2) {
|
||||
assert!(l[0].order <= l[1].order, "does not follow LayerId.order");
|
||||
if areas.compare_order(l[0], l[1]) != std::cmp::Ordering::Equal {
|
||||
for &[a, b] in layers.array_windows() {
|
||||
assert!(a.order <= b.order, "does not follow LayerId.order");
|
||||
if areas.compare_order(a, b) != std::cmp::Ordering::Equal {
|
||||
i += 1;
|
||||
}
|
||||
equivalence_classes.push(i);
|
||||
|
||||
@@ -1395,7 +1395,7 @@ impl Default for DebugOptions {
|
||||
show_resize: false,
|
||||
show_interactive_widgets: false,
|
||||
show_widget_hits: false,
|
||||
warn_if_rect_changes_id: cfg!(debug_assertions),
|
||||
warn_if_rect_changes_id: false,
|
||||
show_unaligned: cfg!(debug_assertions),
|
||||
show_focused_widget: false,
|
||||
}
|
||||
|
||||
@@ -134,9 +134,8 @@ where
|
||||
if self.has_undo(current_state) {
|
||||
self.flux = None;
|
||||
|
||||
if self.undos.back() == Some(current_state) {
|
||||
#[expect(clippy::unwrap_used)] // we just checked that undos is not empty
|
||||
self.redos.push(self.undos.pop_back().unwrap());
|
||||
if let Some(state) = self.undos.pop_back_if(|state| &*state == current_state) {
|
||||
self.redos.push(state);
|
||||
} else {
|
||||
self.redos.push(current_state.clone());
|
||||
}
|
||||
|
||||
@@ -223,6 +223,7 @@ fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||
subgroup_min_size,
|
||||
subgroup_max_size,
|
||||
transient_saves_memory,
|
||||
limit_bucket,
|
||||
} = &info;
|
||||
|
||||
// Example values:
|
||||
@@ -276,7 +277,11 @@ fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||
ui.end_row();
|
||||
}
|
||||
ui.label("Transient saves memory:");
|
||||
ui.label(format!("{transient_saves_memory}"));
|
||||
ui.label(format!("{transient_saves_memory:?}"));
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Limit bucket:");
|
||||
ui.label(format!("{limit_bucket:?}"));
|
||||
ui.end_row();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -56,6 +56,9 @@ enum Phase {
|
||||
/// A screenshot was dispatched with this `user_data` id; reply when the matching
|
||||
/// [`egui::Event::Screenshot`] arrives.
|
||||
AwaitScreenshot { id: u64 },
|
||||
|
||||
/// Watching for the app to go idle.
|
||||
Settle { steps_taken: u64, max_steps: u64 },
|
||||
}
|
||||
|
||||
struct InFlight {
|
||||
@@ -116,7 +119,12 @@ impl InspectionPlugin {
|
||||
/// While requests are still in flight, keep the UI loop spinning — reactive apps would
|
||||
/// otherwise go idle between hooks before a screenshot round-trips.
|
||||
fn maybe_repaint(&self, ctx: &Context) {
|
||||
if !self.in_flight.is_empty() {
|
||||
// Don't repaint if there's only a `Request::Settle`.
|
||||
if self
|
||||
.in_flight
|
||||
.iter()
|
||||
.any(|item| !matches!(item.req, Request::Settle { .. }))
|
||||
{
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
@@ -236,6 +244,13 @@ impl egui::Plugin for InspectionPlugin {
|
||||
item.phase = Phase::AwaitScreenshot { id };
|
||||
true
|
||||
}
|
||||
Request::Settle { max_steps } => {
|
||||
item.phase = Phase::Settle {
|
||||
steps_taken: 0,
|
||||
max_steps: *max_steps,
|
||||
};
|
||||
true
|
||||
}
|
||||
}
|
||||
});
|
||||
self.next_screenshot_id = next_id;
|
||||
@@ -249,9 +264,14 @@ impl egui::Plugin for InspectionPlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
let immediate_repaint = output
|
||||
.viewport_output
|
||||
.values()
|
||||
.any(|viewport| viewport.repaint_delay == Duration::ZERO);
|
||||
|
||||
let step = self.step;
|
||||
self.in_flight
|
||||
.retain_mut(|item| match (&item.phase, &item.req) {
|
||||
.retain_mut(|item| match (&mut item.phase, &item.req) {
|
||||
(Phase::AwaitOutput, Request::GetTree) => {
|
||||
if let Some(reply) = item.reply.take() {
|
||||
reply(Response::Tree {
|
||||
@@ -268,6 +288,27 @@ impl egui::Plugin for InspectionPlugin {
|
||||
}
|
||||
false
|
||||
}
|
||||
(
|
||||
Phase::Settle {
|
||||
steps_taken,
|
||||
max_steps,
|
||||
},
|
||||
Request::Settle { .. },
|
||||
) => {
|
||||
*steps_taken += 1;
|
||||
let steps_exceeded = *steps_taken >= *max_steps;
|
||||
if !immediate_repaint || steps_exceeded {
|
||||
if let Some(reply) = item.reply.take() {
|
||||
reply(Response::Settled {
|
||||
settled: !immediate_repaint,
|
||||
steps: *steps_taken,
|
||||
});
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
_ => true,
|
||||
});
|
||||
|
||||
|
||||
@@ -63,6 +63,11 @@ pub enum Request {
|
||||
/// (via [`egui::ViewportCommand::InnerSize`]). Reply: [`Response::Done`]. This is the one
|
||||
/// action that isn't expressible as an [`egui::Event`].
|
||||
Resize { width: u32, height: u32 },
|
||||
|
||||
/// Wait until the app goes idle, then reply [`Response::Settled`].
|
||||
///
|
||||
/// Will wait for at most `max_steps`.
|
||||
Settle { max_steps: u64 },
|
||||
}
|
||||
|
||||
/// Sent peer → inspector, exactly one per [`Request`].
|
||||
@@ -99,6 +104,15 @@ pub enum Response {
|
||||
/// (not merely received): the events were processed by a frame, or the resize dispatched.
|
||||
Done,
|
||||
|
||||
/// Reply to [`Request::Settle`].
|
||||
Settled {
|
||||
/// Did we settle within `max_steps`?
|
||||
settled: bool,
|
||||
|
||||
/// How many frames did we run until we settled?
|
||||
steps: u64,
|
||||
},
|
||||
|
||||
/// The peer failed to service the request (recoverable; the connection stays open).
|
||||
Error { message: String },
|
||||
}
|
||||
|
||||
@@ -175,10 +175,9 @@ impl<'a, State> Harness<'a, State> {
|
||||
#[cfg(feature = "snapshot")]
|
||||
snapshot_results: SnapshotResults::default(),
|
||||
};
|
||||
// Fulfill any screenshot requested during the initial frame above (which didn't go
|
||||
// through `_step`).
|
||||
#[cfg(any(feature = "wgpu", feature = "snapshot"))]
|
||||
harness.handle_screenshots();
|
||||
// Handle any viewport commands (e.g. a screenshot or resize) requested during the initial
|
||||
// frame above (which didn't go through `_step`).
|
||||
harness.handle_viewport_commands();
|
||||
|
||||
// Run the harness until it is stable, ensuring that all Areas are shown and animations are done
|
||||
harness.run_ok();
|
||||
@@ -251,14 +250,7 @@ impl<'a, State> Harness<'a, State> {
|
||||
self._step(false);
|
||||
}
|
||||
for event in events {
|
||||
match event {
|
||||
EventType::Event(event) => {
|
||||
self.input.events.push(event);
|
||||
}
|
||||
EventType::Modifiers(modifiers) => {
|
||||
self.input.modifiers = modifiers;
|
||||
}
|
||||
}
|
||||
self.input.events.push(event);
|
||||
self._step(false);
|
||||
}
|
||||
}
|
||||
@@ -280,8 +272,7 @@ impl<'a, State> Harness<'a, State> {
|
||||
self.renderer.handle_delta(&output.textures_delta);
|
||||
self.output = output;
|
||||
|
||||
#[cfg(any(feature = "wgpu", feature = "snapshot"))]
|
||||
self.handle_screenshots();
|
||||
self.handle_viewport_commands();
|
||||
}
|
||||
|
||||
/// Calculate the rect that includes all popups and tooltips.
|
||||
@@ -471,7 +462,7 @@ impl<'a, State> Harness<'a, State> {
|
||||
|
||||
/// Queue an event to be processed in the next frame.
|
||||
pub fn event(&self, event: egui::Event) {
|
||||
self.queued_events.lock().push(EventType::Event(event));
|
||||
self.queued_events.lock().push(event);
|
||||
}
|
||||
|
||||
/// Queue an event with modifiers.
|
||||
@@ -479,15 +470,15 @@ impl<'a, State> Harness<'a, State> {
|
||||
/// Queues the modifiers to be pressed, then the event, then the modifiers to be released.
|
||||
pub fn event_modifiers(&self, event: egui::Event, modifiers: Modifiers) {
|
||||
let mut queue = self.queued_events.lock();
|
||||
queue.push(EventType::Modifiers(modifiers));
|
||||
queue.push(EventType::Event(event));
|
||||
queue.push(EventType::Modifiers(Modifiers::default()));
|
||||
queue.push(egui::Event::ModifiersChanged(modifiers));
|
||||
queue.push(event);
|
||||
queue.push(egui::Event::ModifiersChanged(Modifiers::default()));
|
||||
}
|
||||
|
||||
fn modifiers(&self, modifiers: Modifiers) {
|
||||
self.queued_events
|
||||
.lock()
|
||||
.push(EventType::Modifiers(modifiers));
|
||||
.push(egui::Event::ModifiersChanged(modifiers));
|
||||
}
|
||||
|
||||
pub fn key_down(&self, key: egui::Key) {
|
||||
@@ -675,6 +666,36 @@ impl<'a, State> Harness<'a, State> {
|
||||
self.renderer.render(&self.ctx, &output)
|
||||
}
|
||||
|
||||
/// Apply the [`egui::ViewportCommand`]s the app emitted during the last frame.
|
||||
fn handle_viewport_commands(&mut self) {
|
||||
self.handle_inner_size();
|
||||
|
||||
#[cfg(any(feature = "wgpu", feature = "snapshot"))]
|
||||
self.handle_screenshots();
|
||||
}
|
||||
|
||||
/// Resize the harness to the last [`egui::ViewportCommand::InnerSize`] requested by the app
|
||||
/// during the last frame, if any.
|
||||
fn handle_inner_size(&mut self) {
|
||||
let new_inner_size =
|
||||
self.root_viewport_output()
|
||||
.commands
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|command| {
|
||||
if let egui::ViewportCommand::InnerSize(size) = command {
|
||||
Some(*size)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(size) = new_inner_size {
|
||||
self.set_size(size);
|
||||
self.ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/// Fulfill any [`egui::ViewportCommand::Screenshot`] requests made by the app during the
|
||||
/// last frame.
|
||||
///
|
||||
@@ -735,10 +756,11 @@ impl<'a, State> Harness<'a, State> {
|
||||
|
||||
/// The root node of the test harness.
|
||||
pub fn root(&self) -> Node<'_> {
|
||||
Node {
|
||||
accesskit_node: self.kittest.root(),
|
||||
queue: &self.queued_events,
|
||||
}
|
||||
Node::new(
|
||||
self.kittest.root(),
|
||||
&self.queued_events,
|
||||
self.ctx.pixels_per_point(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Spawn a real native eframe window running this harness's app, reusing its [`egui::Context`].
|
||||
|
||||
@@ -4,17 +4,13 @@ use egui::{Modifiers, PointerButton, Pos2, accesskit};
|
||||
use kittest::{AccessKitNode, NodeT, debug_fmt_node};
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
pub(crate) enum EventType {
|
||||
Event(egui::Event),
|
||||
Modifiers(Modifiers),
|
||||
}
|
||||
|
||||
pub(crate) type EventQueue = Mutex<Vec<EventType>>;
|
||||
pub type EventQueue = Mutex<Vec<egui::Event>>;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Node<'tree> {
|
||||
pub(crate) accesskit_node: AccessKitNode<'tree>,
|
||||
pub(crate) queue: &'tree EventQueue,
|
||||
pub(crate) pixels_per_point: f32,
|
||||
}
|
||||
|
||||
impl Debug for Node<'_> {
|
||||
@@ -29,20 +25,32 @@ impl<'tree> NodeT<'tree> for Node<'tree> {
|
||||
}
|
||||
|
||||
fn new_related(&self, child_node: AccessKitNode<'tree>) -> Self {
|
||||
Self {
|
||||
queue: self.queue,
|
||||
accesskit_node: child_node,
|
||||
}
|
||||
Self::new(child_node, self.queue, self.pixels_per_point)
|
||||
}
|
||||
}
|
||||
|
||||
impl Node<'_> {
|
||||
impl<'tree> Node<'tree> {
|
||||
/// Construct a new accesskit node
|
||||
pub fn new(
|
||||
accesskit_node: AccessKitNode<'tree>,
|
||||
queue: &'tree EventQueue,
|
||||
pixels_per_point: f32,
|
||||
) -> Self {
|
||||
Self {
|
||||
accesskit_node,
|
||||
queue,
|
||||
pixels_per_point,
|
||||
}
|
||||
}
|
||||
|
||||
fn event(&self, event: egui::Event) {
|
||||
self.queue.lock().push(EventType::Event(event));
|
||||
self.queue.lock().push(event);
|
||||
}
|
||||
|
||||
fn modifiers(&self, modifiers: Modifiers) {
|
||||
self.queue.lock().push(EventType::Modifiers(modifiers));
|
||||
self.queue
|
||||
.lock()
|
||||
.push(egui::Event::ModifiersChanged(modifiers));
|
||||
}
|
||||
|
||||
pub fn hover(&self) {
|
||||
@@ -104,14 +112,17 @@ impl Node<'_> {
|
||||
));
|
||||
}
|
||||
|
||||
/// This returns the rect in logical ui coordinates while the underlying [`accesskit::Node`] has it
|
||||
/// in physical screen coordinates.
|
||||
pub fn rect(&self) -> egui::Rect {
|
||||
let rect = self
|
||||
.accesskit_node
|
||||
.bounding_box()
|
||||
.expect("Every egui node should have a rect");
|
||||
let ppp = self.pixels_per_point;
|
||||
egui::Rect {
|
||||
min: Pos2::new(rect.x0 as f32, rect.y0 as f32),
|
||||
max: Pos2::new(rect.x1 as f32, rect.y1 as f32),
|
||||
min: Pos2::new(rect.x0 as f32 / ppp, rect.y0 as f32 / ppp),
|
||||
max: Pos2::new(rect.x1 as f32 / ppp, rect.y1 as f32 / ppp),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,9 @@ pub(crate) fn texture_to_image(device: &Device, queue: &Queue, texture: &Texture
|
||||
|
||||
receiver.recv().unwrap().unwrap();
|
||||
let buffer_slice = output_buffer.slice(..);
|
||||
let data = buffer_slice.get_mapped_range();
|
||||
let data = buffer_slice
|
||||
.get_mapped_range()
|
||||
.expect("Failed to get mapped range");
|
||||
let data = data
|
||||
.chunks_exact(buffer_dimensions.padded_bytes_per_row)
|
||||
.flat_map(|row| row.iter().take(buffer_dimensions.unpadded_bytes_per_row))
|
||||
|
||||
@@ -1,5 +1,72 @@
|
||||
use egui::{Align, Layout, Popup};
|
||||
use egui_kittest::Harness;
|
||||
use kittest::Queryable as _;
|
||||
|
||||
#[test]
|
||||
fn reopened_popup_resizes_for_wider_items() {
|
||||
const POPUP_BUTTON: &str = "Dynamic popup";
|
||||
const SHORT_ITEM: &str = "Short item";
|
||||
const WIDE_ITEM: &str = "Newly added item with a much wider label";
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
open: bool,
|
||||
show_wide_item: bool,
|
||||
}
|
||||
|
||||
let mut harness = Harness::builder()
|
||||
.with_size(egui::Vec2::new(500.0, 300.0))
|
||||
.build_ui_state(
|
||||
|ui, state| {
|
||||
let response = ui.button(POPUP_BUTTON);
|
||||
if response.clicked() {
|
||||
state.open = !state.open;
|
||||
}
|
||||
|
||||
Popup::from_response(&response)
|
||||
.open(state.open)
|
||||
.layout(Layout::top_down_justified(Align::Min))
|
||||
.show(|ui| {
|
||||
_ = ui.selectable_label(false, SHORT_ITEM);
|
||||
_ = ui.selectable_label(false, "Another short item");
|
||||
if state.show_wide_item {
|
||||
_ = ui.selectable_label(false, WIDE_ITEM);
|
||||
}
|
||||
});
|
||||
},
|
||||
State::default(),
|
||||
);
|
||||
|
||||
harness.get_by_label(POPUP_BUTTON).click();
|
||||
harness.run();
|
||||
let initial_row_size = harness.get_by_label(SHORT_ITEM).rect().size();
|
||||
|
||||
harness.get_by_label(POPUP_BUTTON).click();
|
||||
harness.run();
|
||||
assert!(harness.query_by_label(SHORT_ITEM).is_none());
|
||||
|
||||
harness.state_mut().show_wide_item = true;
|
||||
harness.run();
|
||||
harness.get_by_label(POPUP_BUTTON).click();
|
||||
harness.run();
|
||||
|
||||
let reopened_row_size = harness.get_by_label(SHORT_ITEM).rect().size();
|
||||
let wide_row_size = harness.get_by_label(WIDE_ITEM).rect().size();
|
||||
|
||||
assert!(
|
||||
reopened_row_size.x > initial_row_size.x,
|
||||
"reopened row width ({}) did not grow beyond its initial width ({})",
|
||||
reopened_row_size.x,
|
||||
initial_row_size.x
|
||||
);
|
||||
assert!(
|
||||
wide_row_size.y <= initial_row_size.y + 0.5,
|
||||
"new row height ({}) exceeds the single-line row height ({})",
|
||||
wide_row_size.y,
|
||||
initial_row_size.y
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_interactive_tooltip() {
|
||||
struct State {
|
||||
|
||||
@@ -262,3 +262,34 @@ fn test_ime_composition_visuals() {
|
||||
harness.run();
|
||||
harness.snapshot("test_ime_composition_visuals_cursor");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inner_size_viewport_command() {
|
||||
let new_size = Vec2::new(300.0, 200.0);
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
requested: bool,
|
||||
observed_size: Option<Vec2>,
|
||||
}
|
||||
|
||||
let mut harness = Harness::builder()
|
||||
.with_size(Vec2::new(100.0, 80.0))
|
||||
.build_ui_state(
|
||||
|ui, state: &mut State| {
|
||||
// Request the resize once.
|
||||
if !state.requested {
|
||||
state.requested = true;
|
||||
ui.ctx()
|
||||
.send_viewport_cmd(egui::ViewportCommand::InnerSize(new_size));
|
||||
}
|
||||
|
||||
state.observed_size = Some(ui.ctx().viewport_rect().size());
|
||||
},
|
||||
State::default(),
|
||||
);
|
||||
|
||||
harness.run();
|
||||
|
||||
assert_eq!(harness.state().observed_size, Some(new_size));
|
||||
}
|
||||
|
||||
@@ -160,16 +160,14 @@ where
|
||||
while self.values.len() > self.max_len {
|
||||
self.values.pop_front();
|
||||
}
|
||||
while self.values.len() > self.min_len {
|
||||
if let Some((front_time, _)) = self.values.front() {
|
||||
if *front_time < now - (self.max_age as f64) {
|
||||
self.values.pop_front();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
let oldest_allowed_time = now - self.max_age as f64;
|
||||
while self.min_len < self.values.len()
|
||||
&& self
|
||||
.values
|
||||
.pop_front_if(|&mut (front_time, _)| front_time < oldest_allowed_time)
|
||||
.is_some()
|
||||
{
|
||||
// Keep popping while the oldest sample is too old.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ impl CubicBezierShape {
|
||||
|
||||
/// Logical bounding rectangle (ignoring stroke width)
|
||||
pub fn logical_bounding_rect(&self) -> Rect {
|
||||
//temporary solution
|
||||
// temporary solution
|
||||
let (mut min_x, mut max_x) = if self.points[0].x < self.points[3].x {
|
||||
(self.points[0].x, self.points[3].x)
|
||||
} else {
|
||||
@@ -793,7 +793,7 @@ mod tests {
|
||||
assert!((bbox.max.x - 180.0).abs() < 0.01);
|
||||
assert!((bbox.max.y - 170.0).abs() < 0.01);
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
@@ -817,7 +817,7 @@ mod tests {
|
||||
assert!((bbox.max.x - 130.42).abs() < 0.01);
|
||||
assert!((bbox.max.y - 170.0).abs() < 0.01);
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
@@ -837,28 +837,28 @@ mod tests {
|
||||
fill: Default::default(),
|
||||
stroke: Default::default(),
|
||||
};
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(1.0, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
|
||||
assert_eq!(result.len(), 9);
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
|
||||
assert_eq!(result.len(), 25);
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
|
||||
assert_eq!(result.len(), 77);
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.001, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
@@ -938,35 +938,35 @@ mod tests {
|
||||
stroke: Default::default(),
|
||||
};
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(1.0, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
|
||||
assert_eq!(result.len(), 10);
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.5, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
|
||||
assert_eq!(result.len(), 13);
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
|
||||
assert_eq!(result.len(), 28);
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
|
||||
assert_eq!(result.len(), 83);
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.001, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
@@ -988,7 +988,7 @@ mod tests {
|
||||
stroke: Default::default(),
|
||||
};
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
@@ -1007,7 +1007,7 @@ mod tests {
|
||||
stroke: Default::default(),
|
||||
};
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
@@ -1026,7 +1026,7 @@ mod tests {
|
||||
stroke: Default::default(),
|
||||
};
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
@@ -1045,7 +1045,7 @@ mod tests {
|
||||
stroke: Default::default(),
|
||||
};
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
@@ -1064,7 +1064,7 @@ mod tests {
|
||||
stroke: Default::default(),
|
||||
};
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
@@ -1083,7 +1083,7 @@ mod tests {
|
||||
stroke: Default::default(),
|
||||
};
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
@@ -1100,34 +1100,34 @@ mod tests {
|
||||
stroke: Default::default(),
|
||||
};
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(1.0, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
|
||||
assert_eq!(result.len(), 9);
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.5, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
|
||||
assert_eq!(result.len(), 11);
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
|
||||
assert_eq!(result.len(), 24);
|
||||
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
|
||||
assert_eq!(result.len(), 72);
|
||||
let mut result = vec![curve.points[0]]; //add the start point
|
||||
let mut result = vec![curve.points[0]]; // add the start point
|
||||
curve.for_each_flattened_with_t(0.001, &mut |pos, _t| {
|
||||
result.push(pos);
|
||||
});
|
||||
|
||||
@@ -512,8 +512,7 @@ fn points_from_line(
|
||||
shapes: &mut Vec<Shape>,
|
||||
) {
|
||||
let mut position_on_segment = 0.0;
|
||||
for window in path.windows(2) {
|
||||
let (start, end) = (window[0], window[1]);
|
||||
for &[start, end] in path.array_windows() {
|
||||
let vector = end - start;
|
||||
let segment_length = vector.length();
|
||||
while position_on_segment < segment_length {
|
||||
@@ -545,8 +544,7 @@ fn dashes_from_line(
|
||||
let mut drawing_dash = false;
|
||||
let mut step = 0;
|
||||
let steps = dash_lengths.len();
|
||||
for window in path.windows(2) {
|
||||
let (start, end) = (window[0], window[1]);
|
||||
for &[start, end] in path.array_windows() {
|
||||
let vector = end - start;
|
||||
let segment_length = vector.length();
|
||||
|
||||
|
||||
@@ -117,7 +117,6 @@ impl SubpixelBin {
|
||||
let trunc = pos as i32;
|
||||
let fract = pos - trunc as f32;
|
||||
|
||||
#[expect(clippy::collapsible_else_if)]
|
||||
if pos.is_sign_negative() {
|
||||
if fract > -0.125 {
|
||||
(trunc, Self::Zero)
|
||||
|
||||
@@ -471,8 +471,7 @@ fn layout_section(
|
||||
// Process each paragraph segment (split on newlines — the shaper can't handle them).
|
||||
for (seg_idx, segment) in SplitOrWhole::new(section_text, job.break_on_newline).enumerate() {
|
||||
if 0 < seg_idx {
|
||||
out_paragraphs.push(Paragraph::from_section_index(section_index));
|
||||
paragraph = out_paragraphs.last_mut().unwrap();
|
||||
paragraph = out_paragraphs.push_mut(Paragraph::from_section_index(section_index));
|
||||
paragraph.empty_paragraph_height = line_height;
|
||||
ctx.is_first_glyph_in_section = true;
|
||||
}
|
||||
@@ -1864,7 +1863,7 @@ mod tests {
|
||||
|
||||
// Verify that Row::text() reconstructs the input text.
|
||||
let row_text: String = galley.rows.iter().map(|r| r.text()).collect();
|
||||
assert_eq!(row_text, text, "Row::text() mismatch for {text:?}",);
|
||||
assert_eq!(row_text, text, "Row::text() mismatch for {text:?}");
|
||||
|
||||
// Verify cursor round-trip: end cursor index == char count.
|
||||
assert_eq!(
|
||||
|
||||
10
deny.toml
10
deny.toml
@@ -33,6 +33,10 @@ version = 2
|
||||
ignore = [
|
||||
"RUSTSEC-2024-0320", # unmaintained yaml-rust pulled in by syntect
|
||||
"RUSTSEC-2025-0141", # https://rustsec.org/advisories/RUSTSEC-2025-0141 - bincode is unmaintained - https://git.sr.ht/~stygianentity/bincode/tree/v3.0/item/README.md
|
||||
"RUSTSEC-2026-0192", # ttf-parser is unmaintained. Only brought in via winit/sctk-adwaita (wayland window frame rendering)
|
||||
"RUSTSEC-2026-0194", # quick-xml DoS - fix is in >=0.41, but held back transitively by zbus_xml (accesskit) and wayland-scanner (winit)
|
||||
"RUSTSEC-2026-0195", # quick-xml DoS - same as above
|
||||
"RUSTSEC-2026-0206", # rustybuzz is unmaintained. Brought in via resvg. TODO(linebender/resvg#922): Remove once the PR lands and is released
|
||||
]
|
||||
|
||||
[bans]
|
||||
@@ -52,11 +56,13 @@ skip = [
|
||||
{ name = "foldhash" }, # pulled by the duplicated hashbrown versions
|
||||
{ name = "getrandom" }, # ring / rustls (and thus ehttp) still depend on getrandom 0.2
|
||||
{ name = "hashbrown" }, # wgpu's naga depends on 0.16, accesskit depends on 0.15
|
||||
{ name = "itertools" }, # puffin 0.20 depends on 0.14, criterion 0.8 on 0.13
|
||||
{ name = "jni-sys" }, # 0.3.1 depends on 0.4
|
||||
{ name = "kurbo" }, # Old version because of resvg
|
||||
{ name = "phf" }, # mime_guess2 -> 0.11, unicode_names2 -> 0.13
|
||||
{ name = "phf_generator" }, # same phf split as phf
|
||||
{ name = "phf_shared" }, # same phf split as phf
|
||||
{ name = "rand_core" }, # rand ecosystem is migrating 0.8 -> 0.10; phf_generator's rand 0.8 needs rand_core 0.6, getrandom 0.4/chacha20 need rand_core 0.10
|
||||
{ name = "redox_syscall" }, # old version via winit
|
||||
{ name = "rustc-hash" }, # Small enough
|
||||
{ name = "thiserror" }, # ecosystem is in the process of migrating from 1.x to 2.x
|
||||
@@ -111,6 +117,4 @@ license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }]
|
||||
unknown-registry = "deny"
|
||||
unknown-git = "deny"
|
||||
|
||||
allow-git = [
|
||||
"https://github.com/rerun-io/kittest", # TODO(lucasmerlin): remove this once the kittest crate is published"
|
||||
]
|
||||
allow-git = []
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["tami5 <kkharji@proton.me>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Varphone Wong <varphone@qq.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "custom_style"
|
||||
version = "0.1.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Will Brown <opensource@rebeagle.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Will Brown <opensource@rebeagle.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
# `unsafe_code` is required for `#[no_mangle]`, disable workspace lints to workaround lint error.
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Maxim Osipenko <maxim1999max@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Jan Procházka <github.com/jprochazk>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Jose Palazon <jose@palako.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["René Rössler <rene@freshx.de>", "Andreas Faber <andreas.mfaber@gmail.com"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["TicClick <ya@ticclick.ch>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
# to the user in the error, instead of "error: invalid channel name '[toolchain]'".
|
||||
|
||||
[toolchain]
|
||||
channel = "1.92.0"
|
||||
channel = "1.95.0"
|
||||
components = ["rustfmt", "clippy"]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
|
||||
@@ -9,7 +9,7 @@ set -x
|
||||
# Checks all tests, lints etc.
|
||||
# Basically does what the CI does.
|
||||
|
||||
# cargo +1.92.0 install --quiet typos-cli
|
||||
# cargo +1.95.0 install --quiet typos-cli
|
||||
|
||||
export RUSTFLAGS="-D warnings"
|
||||
export RUSTDOCFLAGS="-D warnings" # https://github.com/emilk/egui/pull/1454
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Section identical to the root clippy.toml:
|
||||
|
||||
msrv = "1.92"
|
||||
msrv = "1.95"
|
||||
|
||||
allow-unwrap-in-tests = true
|
||||
|
||||
|
||||
@@ -10,6 +10,6 @@ rustup target add wasm32-unknown-unknown
|
||||
|
||||
# For generating JS bindings:
|
||||
# Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml
|
||||
if ! cargo install --list | grep -q 'wasm-bindgen-cli v0.2.108'; then
|
||||
cargo install --force --quiet wasm-bindgen-cli --version 0.2.108 --locked
|
||||
if ! cargo install --list | grep -q 'wasm-bindgen-cli v0.2.126'; then
|
||||
cargo install --force --quiet wasm-bindgen-cli --version 0.2.126 --locked
|
||||
fi
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use egui::ScrollArea;
|
||||
use egui::accesskit::Role;
|
||||
#[cfg(debug_assertions)]
|
||||
use egui::epaint::Shape;
|
||||
use egui::style::ScrollAnimation;
|
||||
use egui::text::{LayoutJob, TextWrapping};
|
||||
@@ -261,6 +262,7 @@ fn interact_on_ui_response_should_be_stable() {
|
||||
assert_eq!(click_count, 10, "We missed some clicks!");
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn has_red_warning_rect(output: &egui::FullOutput) -> bool {
|
||||
output.shapes.iter().any(|clipped| {
|
||||
matches!(
|
||||
@@ -276,10 +278,13 @@ fn has_red_warning_rect(output: &egui::FullOutput) -> bool {
|
||||
/// between frames because the label (and thus the Id salt) changes on hover.
|
||||
/// The `warn_if_rect_changes_id` debug check should catch this.
|
||||
#[test]
|
||||
#[cfg(debug_assertions)]
|
||||
fn warn_if_rect_changes_id() {
|
||||
let button_rect = egui::Rect::from_min_size(egui::pos2(10.0, 10.0), egui::vec2(100.0, 30.0));
|
||||
|
||||
let mut harness = Harness::builder().with_size((200.0, 50.0)).build_ui(|ui| {
|
||||
ui.global_style_mut(|style| style.debug.warn_if_rect_changes_id = true);
|
||||
|
||||
// Simulate a buggy widget whose Id depends on its label text,
|
||||
// and the label changes on hover:
|
||||
let is_hovered = ui.rect_contains_pointer(button_rect);
|
||||
@@ -309,6 +314,7 @@ fn warn_if_rect_changes_id() {
|
||||
/// all child widget ids shift too. This should NOT trigger `warn_if_rect_changes_id` because the
|
||||
/// `parent_id` also changed — it's a cascading id shift, not a widget bug.
|
||||
#[test]
|
||||
#[cfg(debug_assertions)]
|
||||
fn warn_if_rect_changes_id_false_positive_parent_shift() {
|
||||
use std::cell::Cell;
|
||||
|
||||
@@ -316,6 +322,8 @@ fn warn_if_rect_changes_id_false_positive_parent_shift() {
|
||||
let button_rect = egui::Rect::from_min_size(egui::pos2(10.0, 10.0), egui::vec2(100.0, 30.0));
|
||||
|
||||
let mut harness = Harness::builder().with_size((200.0, 100.0)).build_ui(|ui| {
|
||||
ui.global_style_mut(|style| style.debug.warn_if_rect_changes_id = true);
|
||||
|
||||
// push_id with a changing value causes the child Ui's id to shift,
|
||||
// which in turn shifts all widget ids inside it.
|
||||
ui.push_id(counter.get(), |ui| {
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "test_background_logic"
|
||||
version = "0.1.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "test_egui_extras_compilation"
|
||||
version = "0.1.0"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["Antoine Beyeler <abeyeler@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0"
|
||||
authors = ["konkitoman"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.92"
|
||||
rust-version = "1.95"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -27,7 +27,7 @@ pub fn ask_to_run(mut cmd: Command, ask: bool, reason: &str) -> Result<(), DynEr
|
||||
let is_ci = env::var_os("CI").is_some() || env::var_os("TF_BUILD").is_some();
|
||||
if ask && !is_ci {
|
||||
let mut buf = String::new();
|
||||
print!("The script is going to run: \n\n`{cmd:?}`\n\n To {reason}.\nProceed? [Y/n] ",);
|
||||
print!("The script is going to run: \n\n`{cmd:?}`\n\n To {reason}.\nProceed? [Y/n] ");
|
||||
io::stdout().flush().unwrap();
|
||||
io::stdin().read_line(&mut buf).unwrap();
|
||||
match buf.trim().to_lowercase().as_ref() {
|
||||
|
||||
Reference in New Issue
Block a user