diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 57db1de4a..c00eac197 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,8 +4,8 @@ Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/ * Keep your PR:s small and focused. * If applicable, add a screenshot or gif. * If it is a non-trivial addition, consider adding a demo for it to `egui_demo_lib`, or a new example. -* Do not open PR:s from your `master` branch, as thart makes it difficult for maintainers to add commits to your PR. -* Remember to run `cargo fmt` and `cargo clippy`. +* Do NOT open PR:s from your `master` branch, as that makes it hard for maintainers to add commits to your PR. +* Remember to run `cargo fmt` and `cargo cranky`. * Open the PR as a draft until you have self-reviewed it and run `./scripts/check.sh`. * When you have addressed a PR comment, mark it as resolved. diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index e5292c15b..193ac9134 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -29,4 +29,4 @@ jobs: with: mode: minimum count: 1 - labels: "ecolor, eframe, egui_extras, egui_glow, egui-wgpu, egui-winit, egui, epaint" + labels: "CI, dependencies, docs and examples, ecolor, eframe, egui_extras, egui_glow, egui-wgpu, egui-winit, egui, epaint, plot, typo" diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 201fec289..b4920b3bf 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,6 +1,6 @@ on: [push, pull_request] -name: CI +name: Rust env: # web_sys_unstable_apis is required to enable the web_sys clipboard API which eframe web uses, @@ -15,13 +15,11 @@ jobs: name: Format + check + test runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: - profile: default - toolchain: 1.65.0 - override: true + toolchain: 1.67.0 - name: Install packages (Linux) if: runner.os == 'Linux' @@ -37,10 +35,10 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Rustfmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + run: cargo fmt --all -- --check + + - name: Lint vertical spacing + run: ./scripts/lint.py - name: Install cargo-cranky uses: baptiste0928/cargo-install@v1 @@ -48,70 +46,37 @@ jobs: crate: cargo-cranky - name: check --all-features - uses: actions-rs/cargo@v1 - with: - command: check - args: --locked --all-features --all-targets + run: cargo check --locked --all-features --all-targets - name: check egui_extras --all-features - uses: actions-rs/cargo@v1 - with: - command: check - args: --locked --all-features --all-targets -p egui_extras + run: cargo check --locked --all-features --all-targets -p egui_extras - name: check default features - uses: actions-rs/cargo@v1 - with: - command: check - args: --locked --all-targets + run: cargo check --locked --all-targets - name: check --no-default-features - uses: actions-rs/cargo@v1 - with: - command: check - args: --locked --no-default-features --lib --all-targets + run: cargo check --locked --no-default-features --lib --all-targets - name: check epaint --no-default-features - uses: actions-rs/cargo@v1 - with: - command: check - args: --locked --no-default-features --lib --all-targets -p epaint + run: cargo check --locked --no-default-features --lib --all-targets -p epaint - name: check eframe --no-default-features - uses: actions-rs/cargo@v1 - with: - command: check - args: --locked --no-default-features --lib --all-targets -p eframe + run: cargo check --locked --no-default-features --features x11 --lib --all-targets -p eframe - name: Test doc-tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --doc --all-features + run: cargo test --doc --all-features - name: cargo doc --lib - uses: actions-rs/cargo@v1 - with: - command: doc - args: --lib --no-deps --all-features + run: cargo doc --lib --no-deps --all-features - name: cargo doc --document-private-items - uses: actions-rs/cargo@v1 - with: - command: doc - args: --document-private-items --no-deps --all-features + run: cargo doc --document-private-items --no-deps --all-features - name: Test - uses: actions-rs/cargo@v1 - with: - command: test - args: --all-features + run: cargo test --all-features - name: Cranky - uses: actions-rs/cargo@v1 - with: - command: cranky - args: --all-targets --all-features -- -D warnings + run: cargo cranky --all-targets --all-features -- -D warnings # --------------------------------------------------------------------------- @@ -119,13 +84,11 @@ jobs: name: Check wasm32 + wasm-bindgen runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal - toolchain: 1.65.0 - target: wasm32-unknown-unknown - override: true + toolchain: 1.67.0 + targets: wasm32-unknown-unknown - run: sudo apt-get update && sudo apt-get install libgtk-3-dev @@ -138,27 +101,18 @@ jobs: crate: cargo-cranky - name: Check wasm32 egui_demo_app - uses: actions-rs/cargo@v1 - with: - command: check - args: -p egui_demo_app --lib --target wasm32-unknown-unknown + run: cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown - name: Check wasm32 egui_demo_app --all-features - uses: actions-rs/cargo@v1 - with: - command: check - args: -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features + run: cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features - name: Check wasm32 eframe - uses: actions-rs/cargo@v1 - with: - command: check - args: -p eframe --lib --no-default-features --features glow,persistence --target wasm32-unknown-unknown + run: cargo check -p eframe --lib --no-default-features --features glow,persistence --target wasm32-unknown-unknown - name: wasm-bindgen uses: jetli/wasm-bindgen-action@v0.1.0 with: - version: "0.2.86" + version: "0.2.87" - run: ./scripts/wasm_bindgen_check.sh --skip-setup @@ -188,13 +142,13 @@ jobs: name: cargo-deny ${{ matrix.target }} runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: EmbarkStudios/cargo-deny-action@v1 with: - rust-version: "1.65.0" + rust-version: "1.67.0" log-level: error command: check - arguments: ${{ matrix.flags }} --target ${{ matrix.target }} + arguments: --target ${{ matrix.target }} # --------------------------------------------------------------------------- @@ -202,14 +156,12 @@ jobs: name: android runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal - toolchain: 1.65.0 - target: aarch64-linux-android - override: true + toolchain: 1.67.0 + targets: aarch64-linux-android - name: Set up cargo cache uses: Swatinem/rust-cache@v2 @@ -223,18 +175,13 @@ jobs: name: Check Windows runs-on: windows-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal - toolchain: 1.65.0 - override: true + toolchain: 1.67.0 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 - name: Check - uses: actions-rs/cargo@v1 - with: - command: check - args: --all-targets --all-features + run: cargo check --all-targets --all-features diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index c3bc84125..4f3ae1d8b 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions Repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Check spelling of entire workspace uses: crate-ci/typos@master diff --git a/_typos.toml b/.typos.toml similarity index 100% rename from _typos.toml rename to .typos.toml diff --git a/CHANGELOG.md b/CHANGELOG.md index cddbc286a..fc129dd8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,8 @@ All notable changes to the `egui` crate will be documented in this file. NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG.md), [`egui-winit`](crates/egui-winit/CHANGELOG.md), [`egui_glium`](crates/egui_glium/CHANGELOG.md), [`egui_glow`](crates/egui_glow/CHANGELOG.md) and [`egui-wgpu`](crates/egui-wgpu/CHANGELOG.md) have their own changelogs! - -## Unreleased +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. ## 0.22.0 - 2023-05-23 - A plethora of small improvements diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28ff70d42..8d3897457 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,7 +60,7 @@ Read the section on integrations at ## Code Conventions Conventions unless otherwise specified: -* angles are in radians +* angles are in radians and clock-wise * `Vec2::X` is right and `Vec2::Y` is down. * `Pos2::ZERO` is left top. diff --git a/Cargo.lock b/Cargo.lock index 36c332d4f..e5b016134 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -326,12 +326,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "atomic_refcell" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79d6dc922a2792b006573f60b2648076355daeae5ce9cb59507e5908c9625d31" - [[package]] name = "atspi" version = "0.10.1" @@ -1394,7 +1388,6 @@ version = "0.22.0" dependencies = [ "ab_glyph", "ahash 0.8.3", - "atomic_refcell", "backtrace", "bytemuck", "criterion", @@ -3130,6 +3123,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "save_plot" +version = "0.1.0" +dependencies = [ + "eframe", + "env_logger", + "image", + "rfd", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -4492,9 +4495,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.13" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d8f380ae16a37b30e6a2cf67040608071384b1450c189e61bea3ff57cde922d" +checksum = "5a56c84a8ccd4258aed21c92f70c0f6dea75356b6892ae27c24139da456f9336" [[package]] name = "xmlparser" diff --git a/Cranky.toml b/Cranky.toml index 6fa6ca093..aaa2d8d6d 100644 --- a/Cranky.toml +++ b/Cranky.toml @@ -91,6 +91,7 @@ warn = [ "clippy::trailing_empty_array", "clippy::trait_duplication_in_bounds", "clippy::unimplemented", + "clippy::uninlined_format_args", "clippy::unnecessary_wraps", "clippy::unnested_or_patterns", "clippy::unused_peekable", diff --git a/clippy.toml b/clippy.toml index 31bbadb86..38feddcf0 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,6 +1,6 @@ # There is also a scripts/clippy_wasm/clippy.toml which forbids some mthods that are not available in wasm. -msrv = "1.65" +msrv = "1.67" # Allow-list of words for markdown in dosctrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown doc-valid-idents = [ diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index f4a3e1d72..228275e04 100644 --- a/crates/ecolor/CHANGELOG.md +++ b/crates/ecolor/CHANGELOG.md @@ -2,7 +2,8 @@ All notable changes to the `ecolor` crate will be noted in this file. -## Unreleased +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. ## 0.22.0 - 2023-05-23 diff --git a/crates/ecolor/Cargo.toml b/crates/ecolor/Cargo.toml index 6a2feb2ee..ea2f895fd 100644 --- a/crates/ecolor/Cargo.toml +++ b/crates/ecolor/Cargo.toml @@ -7,7 +7,7 @@ authors = [ ] description = "Color structs and color conversion utilities" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui" license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index 37eb3b985..072a54e8c 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -3,9 +3,9 @@ All notable changes to the `eframe` crate. NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/CHANGELOG.md), [`egui_glow`](../egui_glow/CHANGELOG.md),and [`egui-wgpu`](../egui-wgpu/CHANGELOG.md) have their own changelogs! +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. -## Unreleased -* Expose raw window and display handles in `CreationContext` and `Frame` ## 0.22.0 - 2023-05-23 * Fix: `request_repaint_after` works even when called from background thread [#2939](https://github.com/emilk/egui/pull/2939) diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 3c5d05aaa..fd4076ed0 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] description = "egui framework - write GUI apps that compiles to web and/or natively" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui/tree/master/crates/eframe" license = "MIT OR Apache-2.0" readme = "README.md" @@ -27,7 +27,14 @@ targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] [features] -default = ["accesskit", "default_fonts", "glow"] +default = [ + "accesskit", + "default_fonts", + "glow", + "wayland", + "winit/default", + "x11", +] ## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/). accesskit = ["egui/accesskit", "egui-winit/accesskit"] @@ -42,6 +49,9 @@ glow = ["dep:glow", "dep:egui_glow", "dep:glutin", "dep:glutin-winit"] ## Enables wayland support and fixes clipboard issue. wayland = ["egui-winit/wayland"] +## Enables compiling for x11. +x11 = ["egui-winit/x11"] + ## Enable saving app state to disk. persistence = [ "directories-next", @@ -109,7 +119,7 @@ image = { version = "0.24", default-features = false, features = [ "png", ] } # Needed for app icon raw-window-handle = { version = "0.5.0" } -winit = "0.28.1" +winit = { version = "0.28.1", default-features = false } # optional native: directories-next = { version = "2", optional = true } diff --git a/crates/eframe/src/epi/mod.rs b/crates/eframe/src/epi/mod.rs index 0f6f42290..fd4f0280f 100644 --- a/crates/eframe/src/epi/mod.rs +++ b/crates/eframe/src/epi/mod.rs @@ -431,7 +431,7 @@ pub struct NativeOptions { /// will be used instead. /// /// ### On Wayland - /// On Wauland this sets the Application ID for the window. + /// On Wayland this sets the Application ID for the window. /// /// The application ID is used in several places of the compositor, e.g. for /// grouping windows of the same application. It is also important for diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index afa602cc9..8093339e8 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -7,7 +7,7 @@ //! To learn how to set up `eframe` for web and native, go to and follow the instructions there! //! //! In short, you implement [`App`] (especially [`App::update`]) and then -//! call [`crate::run_native`] from your `main.rs`, and/or call `eframe::start_web` from your `lib.rs`. +//! call [`crate::run_native`] from your `main.rs`, and/or use `eframe::WebRunner` from your `lib.rs`. //! //! ## Usage, native: //! ``` no_run @@ -272,6 +272,7 @@ pub fn run_simple_native( struct SimpleApp { update_fun: U, } + impl App for SimpleApp { fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) { (self.update_fun)(ctx, frame); diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 4409c1298..a0bec23bd 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -121,9 +121,12 @@ pub fn window_builder( } #[cfg(all(feature = "wayland", target_os = "linux"))] - if let Some(app_id) = &native_options.app_id { + { use winit::platform::wayland::WindowBuilderExtWayland as _; - window_builder = window_builder.with_name(app_id, ""); + match &native_options.app_id { + Some(app_id) => window_builder = window_builder.with_name(app_id, ""), + None => window_builder = window_builder.with_name(title, ""), + } } if let Some(min_size) = *min_window_size { @@ -332,8 +335,10 @@ pub struct EpiIntegration { pub egui_ctx: egui::Context, pending_full_output: egui::FullOutput, egui_winit: egui_winit::State, + /// When set, it is time to close the native window. close: bool, + can_drag_window: bool, window_state: WindowState, follow_system_theme: bool, @@ -562,24 +567,26 @@ impl EpiIntegration { pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) { let now = std::time::Instant::now(); if now - self.last_auto_save > app.auto_save_interval() { - self.save(app, window); + self.save(app, Some(window)); self.last_auto_save = now; } } #[allow(clippy::unused_self)] - pub fn save(&mut self, _app: &mut dyn epi::App, _window: &winit::window::Window) { + pub fn save(&mut self, _app: &mut dyn epi::App, _window: Option<&winit::window::Window>) { #[cfg(feature = "persistence")] if let Some(storage) = self.frame.storage_mut() { crate::profile_function!(); - if _app.persist_native_window() { - crate::profile_scope!("native_window"); - epi::set_value( - storage, - STORAGE_WINDOW_KEY, - &WindowSettings::from_display(_window), - ); + if let Some(window) = _window { + if _app.persist_native_window() { + crate::profile_scope!("native_window"); + epi::set_value( + storage, + STORAGE_WINDOW_KEY, + &WindowSettings::from_display(window), + ); + } } if _app.persist_egui_memory() { crate::profile_scope!("egui_memory"); diff --git a/crates/eframe/src/native/file_storage.rs b/crates/eframe/src/native/file_storage.rs index 30ec3cb64..2f316a8c9 100644 --- a/crates/eframe/src/native/file_storage.rs +++ b/crates/eframe/src/native/file_storage.rs @@ -80,14 +80,45 @@ impl crate::Storage for FileStorage { join_handle.join().ok(); } - let join_handle = std::thread::spawn(move || { - let file = std::fs::File::create(&file_path).unwrap(); - let config = Default::default(); - ron::ser::to_writer_pretty(file, &kv, config).unwrap(); - log::trace!("Persisted to {:?}", file_path); - }); + match std::thread::Builder::new() + .name("eframe_persist".to_owned()) + .spawn(move || { + save_to_disk(&file_path, &kv); + }) { + Ok(join_handle) => { + self.last_save_join_handle = Some(join_handle); + } + Err(err) => { + log::warn!("Failed to spawn thread to save app state: {err}"); + } + } + } + } +} - self.last_save_join_handle = Some(join_handle); +fn save_to_disk(file_path: &PathBuf, kv: &HashMap) { + crate::profile_function!(); + + if let Some(parent_dir) = file_path.parent() { + if !parent_dir.exists() { + if let Err(err) = std::fs::create_dir_all(parent_dir) { + log::warn!("Failed to create directory {parent_dir:?}: {err}"); + } + } + } + + match std::fs::File::create(file_path) { + Ok(file) => { + let config = Default::default(); + + if let Err(err) = ron::ser::to_writer_pretty(file, &kv, config) { + log::warn!("Failed to serialize app state: {err}"); + } else { + log::trace!("Persisted to {:?}", file_path); + } + } + Err(err) => { + log::warn!("Failed to create file {file_path:?}: {err}"); } } } diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 367daea74..af8d3aed6 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -22,6 +22,7 @@ use super::epi_integration::{self, EpiIntegration}; pub enum UserEvent { RequestRepaint { when: Instant, + /// What the frame number was when the repaint was _requested_. frame_nr: u64, }, @@ -144,11 +145,13 @@ fn run_and_return( // Platform-dependent event handlers to workaround a winit bug // See: https://github.com/rust-windowing/winit/issues/987 // See: https://github.com/rust-windowing/winit/issues/1619 - winit::event::Event::RedrawEventsCleared if cfg!(windows) => { + #[cfg(windows)] + winit::event::Event::RedrawEventsCleared => { next_repaint_time = extremely_far_future(); winit_app.run_ui_and_paint() } - winit::event::Event::RedrawRequested(_) if !cfg!(windows) => { + #[cfg(not(windows))] + winit::event::Event::RedrawRequested(_) => { next_repaint_time = extremely_far_future(); winit_app.run_ui_and_paint() } @@ -705,7 +708,7 @@ mod glow_integration { let painter = egui_glow::Painter::new(gl.clone(), "", self.native_options.shader_version) - .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error)); + .unwrap_or_else(|err| panic!("An OpenGL error occurred: {err}\n")); let system_theme = system_theme(gl_window.window(), &self.native_options); let mut integration = epi_integration::EpiIntegration::new( @@ -799,7 +802,7 @@ mod glow_integration { if let Some(mut running) = self.running.take() { running .integration - .save(running.app.as_mut(), running.gl_window.window()); + .save(running.app.as_mut(), running.gl_window.window.as_ref()); running.app.on_exit(Some(&running.gl)); running.painter.destroy(); } @@ -1258,9 +1261,9 @@ mod wgpu_integration { fn save_and_destroy(&mut self) { if let Some(mut running) = self.running.take() { - if let Some(window) = &self.window { - running.integration.save(running.app.as_mut(), window); - } + running + .integration + .save(running.app.as_mut(), self.window.as_ref()); #[cfg(feature = "glow")] running.app.on_exit(None); diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index 5cea5d612..9c3920a50 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -104,7 +104,7 @@ pub fn canvas_element(canvas_id: &str) -> Option { pub fn canvas_element_or_die(canvas_id: &str) -> web_sys::HtmlCanvasElement { canvas_element(canvas_id) - .unwrap_or_else(|| panic!("Failed to find canvas with id {:?}", canvas_id)) + .unwrap_or_else(|| panic!("Failed to find canvas with id {canvas_id:?}")) } fn canvas_origin(canvas_id: &str) -> egui::Pos2 { diff --git a/crates/eframe/src/web/text_agent.rs b/crates/eframe/src/web/text_agent.rs index 579f98c79..1688163b3 100644 --- a/crates/eframe/src/web/text_agent.rs +++ b/crates/eframe/src/web/text_agent.rs @@ -104,8 +104,7 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> { runner_ref.add_event_listener(&input, "focusout", move |_event: web_sys::MouseEvent, _| { // Delay 10 ms, and focus again. let func = js_sys::Function::new_no_args(&format!( - "document.getElementById('{}').focus()", - AGENT_ID + "document.getElementById('{AGENT_ID}').focus()" )); window .set_timeout_with_callback_and_timeout_and_arguments_0(&func, 10) @@ -221,8 +220,8 @@ pub fn move_text_cursor(cursor: Option, canvas_id: &str) -> Option<( let x = (x - canvas.offset_width() as f32 / 2.0) .min(canvas.client_width() as f32 - bounding_rect.width() as f32); style.set_property("position", "absolute").ok()?; - style.set_property("top", &format!("{}px", y)).ok()?; - style.set_property("left", &format!("{}px", x)).ok() + style.set_property("top", &format!("{y}px")).ok()?; + style.set_property("left", &format!("{x}px")).ok() }) } else { style.set_property("position", "absolute").ok()?; diff --git a/crates/eframe/src/web/web_painter_glow.rs b/crates/eframe/src/web/web_painter_glow.rs index 939ac8a0b..b12ac1cef 100644 --- a/crates/eframe/src/web/web_painter_glow.rs +++ b/crates/eframe/src/web/web_painter_glow.rs @@ -27,7 +27,7 @@ impl WebPainterGlow { let gl = std::sync::Arc::new(gl); let painter = egui_glow::Painter::new(gl, shader_prefix, None) - .map_err(|error| format!("Error starting glow painter: {}", error))?; + .map_err(|err| format!("Error starting glow painter: {err}"))?; Ok(Self { canvas, diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 80abb5bf7..83510dc4f 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -87,8 +87,7 @@ impl WebPainterWgpu { } else { // Workaround for https://github.com/gfx-rs/wgpu/issues/3710: // Don't use `create_surface_from_canvas`, but `create_surface` instead! - let raw_window = - EguiWebWindow(egui::util::hash(&format!("egui on wgpu {canvas_id}")) as u32); + let raw_window = EguiWebWindow(egui::util::hash(("egui on wgpu", canvas_id)) as u32); canvas.set_attribute("data-raw-handle", &raw_window.0.to_string()); #[allow(unsafe_code)] diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index ecae53cfd..d7cbee4d1 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -2,8 +2,8 @@ All notable changes to the `egui-wgpu` integration will be noted in this file. -## Unreleased -* Fix panic on wgpu GL backend due to new screenshot capability ([#3068](https://github.com/emilk/egui/issues/3068), [#3078](https://github.com/emilk/egui/pull/3078) +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. ## 0.22.0 - 2023-05-23 diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index 3b56b707c..187b05613 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -8,7 +8,7 @@ authors = [ "Emil Ernerfeldt ", ] edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui/tree/master/crates/egui-wgpu" license = "MIT OR Apache-2.0" readme = "README.md" @@ -50,7 +50,7 @@ wgpu.workspace = true ## Enable this when generating docs. document-features = { version = "0.2", optional = true } -winit = { version = "0.28", optional = true } +winit = { version = "0.28", default-features = false, optional = true } # Native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index fe925857d..c3def5fce 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -560,7 +560,7 @@ impl Renderer { } else { // allocate a new texture // Use same label for all resources associated with this texture id (no point in retyping the type) - let label_str = format!("egui_texid_{:?}", id); + let label_str = format!("egui_texid_{id:?}"); let label = Some(label_str.as_str()); let texture = device.create_texture(&wgpu::TextureDescriptor { label, @@ -904,8 +904,7 @@ fn create_sampler( }; device.create_sampler(&wgpu::SamplerDescriptor { label: Some(&format!( - "egui sampler (mag: {:?}, min {:?})", - mag_filter, min_filter + "egui sampler (mag: {mag_filter:?}, min {min_filter:?})" )), mag_filter, min_filter, diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 7b6a9b180..171d5f08e 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -279,7 +279,7 @@ impl Painter { depth_or_array_layers: 1, }, mip_level_count: 1, - sample_count: 1, + sample_count: self.msaa_samples, dimension: wgpu::TextureDimension::D2, format: depth_format, usage: wgpu::TextureUsages::RENDER_ATTACHMENT diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index af64b1b46..d181b99b4 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog for egui-winit All notable changes to the `egui-winit` integration will be noted in this file. - -## Unreleased +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. ## 0.22.0 - 2023-05-23 diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index bf3f128f3..2d6833d91 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] description = "Bindings for using egui with winit" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui/tree/master/crates/egui-winit" license = "MIT OR Apache-2.0" readme = "README.md" @@ -18,7 +18,7 @@ all-features = true [features] -default = ["clipboard", "links", "wayland", "winit/default"] +default = ["clipboard", "links", "wayland", "winit/default", "x11"] ## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/). accesskit = ["accesskit_winit", "egui/accesskit"] @@ -42,6 +42,9 @@ serde = ["egui/serde", "dep:serde"] ## Enables Wayland support. wayland = ["winit/wayland"] +## Enables compiling for x11. +x11 = ["winit/x11"] + # Allow crates to choose an android-activity backend via Winit # - It's important that most applications should not have to depend on android-activity directly, and can # rely on Winit to pull in a suitable version (unlike most Rust crates, any version conflicts won't link) diff --git a/crates/egui-winit/src/clipboard.rs b/crates/egui-winit/src/clipboard.rs index e80414961..71d913dbb 100644 --- a/crates/egui-winit/src/clipboard.rs +++ b/crates/egui-winit/src/clipboard.rs @@ -3,7 +3,7 @@ use raw_window_handle::HasRawDisplayHandle; /// Handles interfacing with the OS clipboard. /// /// If the "clipboard" feature is off, or we cannot connect to the OS clipboard, -/// then a fallback clipboard that just works works within the same app is used instead. +/// then a fallback clipboard that just works within the same app is used instead. pub struct Clipboard { #[cfg(all(feature = "arboard", not(target_os = "android")))] arboard: Option, diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 75c9028cf..ae82f6e4c 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -307,6 +307,7 @@ impl State { } WindowEvent::KeyboardInput { input, .. } => { self.on_keyboard_input(input); + // When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes. let consumed = egui_ctx.wants_keyboard_input() || input.virtual_keycode == Some(winit::event::VirtualKeyCode::Tab); EventResponse { diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index 913a17cd1..615195836 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] description = "An easy-to-use immediate mode GUI that runs on both web and native" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui" license = "MIT OR Apache-2.0" readme = "../../README.md" diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 380c175b3..953391cad 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -426,7 +426,7 @@ impl Prepared { temporarily_invisible: _, } = self; - state.size = content_ui.min_rect().size(); + state.size = content_ui.min_size(); ctx.memory_mut(|m| m.areas.set_state(layer_id, state)); diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index fa1caee70..08b5e5a0e 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -193,9 +193,7 @@ impl Frame { let where_to_put_background = ui.painter().add(Shape::Noop); let outer_rect_bounds = ui.available_rect_before_wrap(); - let mut inner_rect = outer_rect_bounds; - inner_rect.min += self.outer_margin.left_top() + self.inner_margin.left_top(); - inner_rect.max -= self.outer_margin.right_bottom() + self.inner_margin.right_bottom(); + let mut inner_rect = (self.inner_margin + self.outer_margin).shrink_rect(outer_rect_bounds); // Make sure we don't shrink to the negative: inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x); @@ -256,17 +254,13 @@ impl Frame { impl Prepared { fn paint_rect(&self) -> Rect { - let mut rect = self.content_ui.min_rect(); - rect.min -= self.frame.inner_margin.left_top(); - rect.max += self.frame.inner_margin.right_bottom(); - rect + self.frame + .inner_margin + .expand_rect(self.content_ui.min_rect()) } fn content_with_margin(&self) -> Rect { - let mut rect = self.content_ui.min_rect(); - rect.min -= self.frame.inner_margin.left_top() + self.frame.outer_margin.left_top(); - rect.max += self.frame.inner_margin.right_bottom() + self.frame.outer_margin.right_bottom(); - rect + (self.frame.inner_margin + self.frame.outer_margin).expand_rect(self.content_ui.min_rect()) } pub fn end(self, ui: &mut Ui) -> Response { diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index d829ac219..f5267e0f0 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -15,8 +15,6 @@ //! //! Add your [`Window`]:s after any top-level panels. -use std::ops::RangeInclusive; - use crate::*; /// State regarding panels. @@ -99,7 +97,7 @@ pub struct SidePanel { resizable: bool, show_separator_line: bool, default_width: f32, - width_range: RangeInclusive, + width_range: Rangef, } impl SidePanel { @@ -122,7 +120,7 @@ impl SidePanel { resizable: true, show_separator_line: true, default_width: 200.0, - width_range: 96.0..=f32::INFINITY, + width_range: Rangef::new(96.0, f32::INFINITY), } } @@ -153,26 +151,29 @@ impl SidePanel { /// The initial wrapping width of the [`SidePanel`]. pub fn default_width(mut self, default_width: f32) -> Self { self.default_width = default_width; - self.width_range = self.width_range.start().at_most(default_width) - ..=self.width_range.end().at_least(default_width); + self.width_range = Rangef::new( + self.width_range.min.at_most(default_width), + self.width_range.max.at_least(default_width), + ); self } /// Minimum width of the panel. pub fn min_width(mut self, min_width: f32) -> Self { - self.width_range = min_width..=self.width_range.end().at_least(min_width); + self.width_range = Rangef::new(min_width, self.width_range.max.at_least(min_width)); self } /// Maximum width of the panel. pub fn max_width(mut self, max_width: f32) -> Self { - self.width_range = self.width_range.start().at_most(max_width)..=max_width; + self.width_range = Rangef::new(self.width_range.min.at_most(max_width), max_width); self } /// The allowable width range for the panel. - pub fn width_range(mut self, width_range: RangeInclusive) -> Self { - self.default_width = clamp_to_range(self.default_width, width_range.clone()); + pub fn width_range(mut self, width_range: impl Into) -> Self { + let width_range = width_range.into(); + self.default_width = clamp_to_range(self.default_width, width_range); self.width_range = width_range; self } @@ -180,7 +181,7 @@ impl SidePanel { /// Enforce this exact width. pub fn exact_width(mut self, width: f32) -> Self { self.default_width = width; - self.width_range = width..=width; + self.width_range = Rangef::point(width); self } @@ -224,7 +225,7 @@ impl SidePanel { if let Some(state) = PanelState::load(ui.ctx(), id) { width = state.rect.width(); } - width = clamp_to_range(width, width_range.clone()).at_most(available_rect.width()); + width = clamp_to_range(width, width_range).at_most(available_rect.width()); side.set_rect_width(&mut panel_rect, width); ui.ctx().check_for_id_clash(id, panel_rect, "SidePanel"); } @@ -241,7 +242,7 @@ impl SidePanel { let resize_x = side.opposite().side_x(panel_rect); let mouse_over_resize_line = we_are_on_top - && panel_rect.y_range().contains(&pointer.y) + && panel_rect.y_range().contains(pointer.y) && (resize_x - pointer.x).abs() <= ui.style().interaction.resize_grab_radius_side; @@ -253,8 +254,7 @@ impl SidePanel { is_resizing = ui.memory(|mem| mem.is_being_dragged(resize_id)); if is_resizing { let width = (pointer.x - side.side_x(panel_rect)).abs(); - let width = - clamp_to_range(width, width_range.clone()).at_most(available_rect.width()); + let width = clamp_to_range(width, width_range).at_most(available_rect.width()); side.set_rect_width(&mut panel_rect, width); } @@ -273,7 +273,7 @@ impl SidePanel { let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); let inner_response = frame.show(&mut panel_ui, |ui| { ui.set_min_height(ui.max_rect().height()); // Make sure the frame fills the full height - ui.set_min_width(*width_range.start()); + ui.set_min_width(width_range.min); add_contents(ui) }); @@ -544,7 +544,7 @@ pub struct TopBottomPanel { resizable: bool, show_separator_line: bool, default_height: Option, - height_range: RangeInclusive, + height_range: Rangef, } impl TopBottomPanel { @@ -567,7 +567,7 @@ impl TopBottomPanel { resizable: false, show_separator_line: true, default_height: None, - height_range: 20.0..=f32::INFINITY, + height_range: Rangef::new(20.0, f32::INFINITY), } } @@ -599,28 +599,31 @@ impl TopBottomPanel { /// Defaults to [`style::Spacing::interact_size`].y. pub fn default_height(mut self, default_height: f32) -> Self { self.default_height = Some(default_height); - self.height_range = self.height_range.start().at_most(default_height) - ..=self.height_range.end().at_least(default_height); + self.height_range = Rangef::new( + self.height_range.min.at_most(default_height), + self.height_range.max.at_least(default_height), + ); self } /// Minimum height of the panel. pub fn min_height(mut self, min_height: f32) -> Self { - self.height_range = min_height..=self.height_range.end().at_least(min_height); + self.height_range = Rangef::new(min_height, self.height_range.max.at_least(min_height)); self } /// Maximum height of the panel. pub fn max_height(mut self, max_height: f32) -> Self { - self.height_range = self.height_range.start().at_most(max_height)..=max_height; + self.height_range = Rangef::new(self.height_range.min.at_most(max_height), max_height); self } /// The allowable height range for the panel. - pub fn height_range(mut self, height_range: RangeInclusive) -> Self { + pub fn height_range(mut self, height_range: impl Into) -> Self { + let height_range = height_range.into(); self.default_height = self .default_height - .map(|default_height| clamp_to_range(default_height, height_range.clone())); + .map(|default_height| clamp_to_range(default_height, height_range)); self.height_range = height_range; self } @@ -628,7 +631,7 @@ impl TopBottomPanel { /// Enforce this exact height. pub fn exact_height(mut self, height: f32) -> Self { self.default_height = Some(height); - self.height_range = height..=height; + self.height_range = Rangef::point(height); self } @@ -673,7 +676,7 @@ impl TopBottomPanel { } else { default_height.unwrap_or_else(|| ui.style().spacing.interact_size.y) }; - height = clamp_to_range(height, height_range.clone()).at_most(available_rect.height()); + height = clamp_to_range(height, height_range).at_most(available_rect.height()); side.set_rect_height(&mut panel_rect, height); ui.ctx() .check_for_id_clash(id, panel_rect, "TopBottomPanel"); @@ -692,7 +695,7 @@ impl TopBottomPanel { let resize_y = side.opposite().side_y(panel_rect); let mouse_over_resize_line = we_are_on_top - && panel_rect.x_range().contains(&pointer.x) + && panel_rect.x_range().contains(pointer.x) && (resize_y - pointer.y).abs() <= ui.style().interaction.resize_grab_radius_side; @@ -704,8 +707,8 @@ impl TopBottomPanel { is_resizing = ui.memory(|mem| mem.interaction.drag_id == Some(resize_id)); if is_resizing { let height = (pointer.y - side.side_y(panel_rect)).abs(); - let height = clamp_to_range(height, height_range.clone()) - .at_most(available_rect.height()); + let height = + clamp_to_range(height, height_range).at_most(available_rect.height()); side.set_rect_height(&mut panel_rect, height); } @@ -724,7 +727,7 @@ impl TopBottomPanel { let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); let inner_response = frame.show(&mut panel_ui, |ui| { ui.set_min_width(ui.max_rect().width()); // Make the frame fill full width - ui.set_min_height(*height_range.start()); + ui.set_min_height(height_range.min); add_contents(ui) }); @@ -1056,9 +1059,7 @@ impl CentralPanel { } } -fn clamp_to_range(x: f32, range: RangeInclusive) -> f32 { - x.clamp( - range.start().min(*range.end()), - range.start().max(*range.end()), - ) +fn clamp_to_range(x: f32, range: Rangef) -> f32 { + let range = range.as_positive(); + x.clamp(range.min, range.max) } diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index befb51a10..2f71a57c0 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -124,7 +124,10 @@ impl Resize { } /// Can you resize it with the mouse? - /// Note that a window can still auto-resize + /// + /// Note that a window can still auto-resize. + /// + /// Default is `true`. pub fn resizable(mut self, resizable: bool) -> Self { self.resizable = resizable; self diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 2f67bf3e0..8bd8142cf 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -334,16 +334,22 @@ struct Prepared { state: State, has_bar: [bool; 2], auto_shrink: [bool; 2], + /// How much horizontal and vertical space are used up by the /// width of the vertical bar, and the height of the horizontal bar? current_bar_use: Vec2, + scroll_bar_visibility: ScrollBarVisibility, + /// Where on the screen the content is (excludes scroll bars). inner_rect: Rect, + content_ui: Ui, + /// Relative coordinates: the offset and size of the view of the inner UI. /// `viewport.min == ZERO` means we scrolled to the top. viewport: Rect, + scrolling_enabled: bool, stick_to_end: [bool; 2], } @@ -459,7 +465,7 @@ impl ScrollArea { content_clip_rect.max[d] = ui.clip_rect().max[d] - current_bar_use[d]; } } - // Make sure we din't accidentally expand the clip rect + // Make sure we didn't accidentally expand the clip rect content_clip_rect = content_clip_rect.intersect(ui.clip_rect()); content_ui.set_clip_rect(content_clip_rect); } @@ -640,8 +646,7 @@ impl Prepared { let min = content_ui.min_rect().min[d]; let clip_rect = content_ui.clip_rect(); let visible_range = min..=min + clip_rect.size()[d]; - let start = *scroll.start(); - let end = *scroll.end(); + let (start, end) = (scroll.min, scroll.max); let clip_start = clip_rect.min[d]; let clip_end = clip_rect.max[d]; let mut spacing = ui.spacing().item_spacing[d]; diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index d5c3f680f..1d2444ca3 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -217,7 +217,10 @@ impl<'open> Window<'open> { } /// Can the user resize the window by dragging its edges? + /// /// Note that even if you set this to `false` the window may still auto-resize. + /// + /// Default is `true`. pub fn resizable(mut self, resizable: bool) -> Self { self.resize = self.resize.resizable(resizable); self diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index bcb61d2c0..d8d5a0f5a 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -566,7 +566,7 @@ impl Context { } let show_error = |widget_rect: Rect, text: String| { - let text = format!("🔥 {}", text); + let text = format!("🔥 {text}"); let color = self.style().visuals.error_fg_color; let painter = self.debug_painter(); painter.rect_stroke(widget_rect, 0.0, (1.0, color)); @@ -612,10 +612,10 @@ impl Context { let id_str = id.short_debug_format(); if prev_rect.min.distance(new_rect.min) < 4.0 { - show_error(new_rect, format!("Double use of {} ID {}", what, id_str)); + show_error(new_rect, format!("Double use of {what} ID {id_str}")); } else { - show_error(prev_rect, format!("First use of {} ID {}", what, id_str)); - show_error(new_rect, format!("Second use of {} ID {}", what, id_str)); + show_error(prev_rect, format!("First use of {what} ID {id_str}")); + show_error(new_rect, format!("Second use of {what} ID {id_str}")); } } @@ -1574,14 +1574,14 @@ impl Context { let pointer_pos = self .pointer_hover_pos() - .map_or_else(String::new, |pos| format!("{:?}", pos)); - ui.label(format!("Pointer pos: {}", pointer_pos)); + .map_or_else(String::new, |pos| format!("{pos:?}")); + ui.label(format!("Pointer pos: {pointer_pos}")); let top_layer = self .pointer_hover_pos() .and_then(|pos| self.layer_id_at(pos)) .map_or_else(String::new, |layer| layer.short_debug_format()); - ui.label(format!("Top layer under mouse: {}", top_layer)); + ui.label(format!("Top layer under mouse: {top_layer}")); ui.add_space(16.0); @@ -1667,7 +1667,7 @@ impl Context { ui.image(texture_id, size); }); - ui.label(format!("{} x {}", w, h)); + ui.label(format!("{w} x {h}")); ui.label(format!("{:.3} MB", meta.bytes_used() as f64 * 1e-6)); ui.label(format!("{:?}", meta.name)); ui.end_row(); @@ -1688,8 +1688,7 @@ impl Context { let (num_state, num_serialized) = self.data(|d| (d.len(), d.count_serialized())); ui.label(format!( - "{} widget states stored (of which {} are serialized).", - num_state, num_serialized + "{num_state} widget states stored (of which {num_serialized} are serialized)." )); ui.horizontal(|ui| { diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 99ea6ac03..bb1b9f6a9 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -610,11 +610,11 @@ pub struct ModifierNames<'a> { } impl ModifierNames<'static> { - /// ⌥ ^ ⇧ ⌘ - NOTE: not supported by the default egui font. + /// ⌥ ⌃ ⇧ ⌘ - NOTE: not supported by the default egui font. pub const SYMBOLS: Self = Self { is_short: true, alt: "⌥", - ctrl: "^", + ctrl: "⌃", shift: "⇧", mac_cmd: "⌘", mac_alt: "⌥", @@ -693,27 +693,37 @@ pub enum Key { /// The virtual keycode for the Minus key. Minus, + /// The virtual keycode for the Plus/Equals key. PlusEquals, /// Either from the main row or from the numpad. Num0, + /// Either from the main row or from the numpad. Num1, + /// Either from the main row or from the numpad. Num2, + /// Either from the main row or from the numpad. Num3, + /// Either from the main row or from the numpad. Num4, + /// Either from the main row or from the numpad. Num5, + /// Either from the main row or from the numpad. Num6, + /// Either from the main row or from the numpad. Num7, + /// Either from the main row or from the numpad. Num8, + /// Either from the main row or from the numpad. Num9, @@ -906,7 +916,7 @@ fn format_kb_shortcut() { cmd_shift_f.format(&ModifierNames::NAMES, true), "Shift+Cmd+F" ); - assert_eq!(cmd_shift_f.format(&ModifierNames::SYMBOLS, false), "^⇧F"); + assert_eq!(cmd_shift_f.format(&ModifierNames::SYMBOLS, false), "⌃⇧F"); assert_eq!(cmd_shift_f.format(&ModifierNames::SYMBOLS, true), "⇧⌘F"); } @@ -927,25 +937,25 @@ impl RawInput { focused, } = self; - ui.label(format!("screen_rect: {:?} points", screen_rect)); - ui.label(format!("pixels_per_point: {:?}", pixels_per_point)) + ui.label(format!("screen_rect: {screen_rect:?} points")); + ui.label(format!("pixels_per_point: {pixels_per_point:?}")) .on_hover_text( "Also called HDPI factor.\nNumber of physical pixels per each logical pixel.", ); - ui.label(format!("max_texture_side: {:?}", max_texture_side)); + ui.label(format!("max_texture_side: {max_texture_side:?}")); if let Some(time) = time { - ui.label(format!("time: {:.3} s", time)); + ui.label(format!("time: {time:.3} s")); } else { ui.label("time: None"); } ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt)); - ui.label(format!("modifiers: {:#?}", modifiers)); + 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)); + ui.label(format!("focused: {focused}")); ui.scope(|ui| { ui.set_min_height(150.0); - ui.label(format!("events: {:#?}", events)) + ui.label(format!("events: {events:#?}")) .on_hover_text("key presses etc"); }); } diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index cea031ed0..156a34928 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -100,7 +100,7 @@ impl PlatformOutput { /// This can be used by a text-to-speech system to describe the events (if any). pub fn events_description(&self) -> String { // only describe last event: - if let Some(event) = self.events.iter().rev().next() { + if let Some(event) = self.events.iter().next_back() { match event { OutputEvent::Clicked(widget_info) | OutputEvent::DoubleClicked(widget_info) @@ -417,12 +417,12 @@ impl OutputEvent { impl std::fmt::Debug for OutputEvent { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Clicked(wi) => write!(f, "Clicked({:?})", wi), - Self::DoubleClicked(wi) => write!(f, "DoubleClicked({:?})", wi), - Self::TripleClicked(wi) => write!(f, "TripleClicked({:?})", wi), - Self::FocusGained(wi) => write!(f, "FocusGained({:?})", wi), - Self::TextSelectionChanged(wi) => write!(f, "TextSelectionChanged({:?})", wi), - Self::ValueChanged(wi) => write!(f, "ValueChanged({:?})", wi), + Self::Clicked(wi) => write!(f, "Clicked({wi:?})"), + Self::DoubleClicked(wi) => write!(f, "DoubleClicked({wi:?})"), + Self::TripleClicked(wi) => write!(f, "TripleClicked({wi:?})"), + Self::FocusGained(wi) => write!(f, "FocusGained({wi:?})"), + Self::TextSelectionChanged(wi) => write!(f, "TextSelectionChanged({wi:?})"), + Self::ValueChanged(wi) => write!(f, "ValueChanged({wi:?})"), } } } @@ -609,14 +609,14 @@ impl WidgetInfo { if let Some(selected) = selected { if *typ == WidgetType::Checkbox { let state = if *selected { "checked" } else { "unchecked" }; - description = format!("{} {}", state, description); + description = format!("{state} {description}"); } else { description += if *selected { "selected" } else { "" }; }; } if let Some(label) = label { - description = format!("{}: {}", label, description); + description = format!("{label}: {description}"); } if typ == &WidgetType::TextEdit { @@ -630,7 +630,7 @@ impl WidgetInfo { } else { text = "blank".into(); } - description = format!("{}: {}", text, description); + description = format!("{text}: {description}"); } if let Some(value) = value { diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/frame_state.rs index 0d7106843..287b35c56 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/frame_state.rs @@ -1,5 +1,3 @@ -use std::ops::RangeInclusive; - use crate::{id::IdSet, *}; #[derive(Clone, Copy, Debug)] @@ -46,7 +44,7 @@ pub(crate) struct FrameState { pub(crate) scroll_delta: Vec2, // TODO(emilk): move to `InputState` ? /// horizontal, vertical - pub(crate) scroll_target: [Option<(RangeInclusive, Option)>; 2], + pub(crate) scroll_target: [Option<(Rangef, Option)>; 2], #[cfg(feature = "accesskit")] pub(crate) accesskit_state: Option, diff --git a/crates/egui/src/grid.rs b/crates/egui/src/grid.rs index 314266a75..acd5c3576 100644 --- a/crates/egui/src/grid.rs +++ b/crates/egui/src/grid.rs @@ -47,7 +47,7 @@ impl State { // ---------------------------------------------------------------------------- // type alias for boxed function to determine row color during grid generation -type ColorPickerFn = Box Option>; +type ColorPickerFn = Box Option>; pub(crate) struct GridLayout { ctx: Context, @@ -60,6 +60,7 @@ pub(crate) struct GridLayout { /// State previous frame (if any). /// This can be used to predict future sizes of cells. prev_state: State, + /// State accumulated during the current frame. curr_state: State, initial_available: Rect, @@ -311,7 +312,7 @@ impl Grid { /// Setting this will allow for dynamic coloring of rows of the grid object pub fn with_row_color(mut self, color_picker: F) -> Self where - F: Fn(usize, &Style) -> Option + 'static, + F: Send + Sync + Fn(usize, &Style) -> Option + 'static, { self.color_picker = Some(Box::new(color_picker)); self diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 1b49757bc..1c0fe22f6 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -990,30 +990,28 @@ impl InputState { }); } - ui.label(format!("scroll_delta: {:?} points", scroll_delta)); - ui.label(format!("zoom_factor_delta: {:4.2}x", zoom_factor_delta)); - ui.label(format!("screen_rect: {:?} points", screen_rect)); + ui.label(format!("scroll_delta: {scroll_delta:?} points")); + ui.label(format!("zoom_factor_delta: {zoom_factor_delta:4.2}x")); + ui.label(format!("screen_rect: {screen_rect:?} points")); ui.label(format!( - "{} physical pixels for each logical point", - pixels_per_point + "{pixels_per_point} physical pixels for each logical point" )); ui.label(format!( - "max texture size (on each side): {}", - max_texture_side + "max texture size (on each side): {max_texture_side}" )); - ui.label(format!("time: {:.3} s", time)); + ui.label(format!("time: {time:.3} s")); ui.label(format!( "time since previous frame: {:.1} ms", 1e3 * unstable_dt )); ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt)); ui.label(format!("stable_dt: {:.1} ms", 1e3 * stable_dt)); - ui.label(format!("focused: {}", focused)); - ui.label(format!("modifiers: {:#?}", modifiers)); - ui.label(format!("keys_down: {:?}", keys_down)); + ui.label(format!("focused: {focused}")); + ui.label(format!("modifiers: {modifiers:#?}")); + ui.label(format!("keys_down: {keys_down:?}")); ui.scope(|ui| { ui.set_min_height(150.0); - ui.label(format!("events: {:#?}", events)) + ui.label(format!("events: {events:#?}")) .on_hover_text("key presses etc"); }); } @@ -1037,22 +1035,21 @@ impl PointerState { pointer_events, } = self; - ui.label(format!("latest_pos: {:?}", latest_pos)); - ui.label(format!("interact_pos: {:?}", interact_pos)); - ui.label(format!("delta: {:?}", delta)); + ui.label(format!("latest_pos: {latest_pos:?}")); + ui.label(format!("interact_pos: {interact_pos:?}")); + ui.label(format!("delta: {delta:?}")); ui.label(format!( "velocity: [{:3.0} {:3.0}] points/sec", velocity.x, velocity.y )); - ui.label(format!("down: {:#?}", down)); - ui.label(format!("press_origin: {:?}", press_origin)); - ui.label(format!("press_start_time: {:?} s", press_start_time)); + ui.label(format!("down: {down:#?}")); + ui.label(format!("press_origin: {press_origin:?}")); + ui.label(format!("press_start_time: {press_start_time:?} s")); ui.label(format!( - "has_moved_too_much_for_a_click: {}", - has_moved_too_much_for_a_click + "has_moved_too_much_for_a_click: {has_moved_too_much_for_a_click}" )); - ui.label(format!("last_click_time: {:#?}", last_click_time)); - ui.label(format!("last_last_click_time: {:#?}", last_last_click_time)); - ui.label(format!("pointer_events: {:?}", pointer_events)); + ui.label(format!("last_click_time: {last_click_time:#?}")); + ui.label(format!("last_last_click_time: {last_last_click_time:#?}")); + ui.label(format!("pointer_events: {pointer_events:?}")); } } diff --git a/crates/egui/src/input_state/touch_state.rs b/crates/egui/src/input_state/touch_state.rs index becd0d527..37d418a53 100644 --- a/crates/egui/src/input_state/touch_state.rs +++ b/crates/egui/src/input_state/touch_state.rs @@ -286,7 +286,7 @@ impl TouchState { impl TouchState { pub fn ui(&self, ui: &mut crate::Ui) { - ui.label(format!("{:?}", self)); + ui.label(format!("{self:?}")); } } @@ -294,7 +294,7 @@ impl Debug for TouchState { // This outputs less clutter than `#[derive(Debug)]`: fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for (id, touch) in &self.active_touches { - f.write_fmt(format_args!("#{:?}: {:#?}\n", id, touch))?; + f.write_fmt(format_args!("#{id:?}: {touch:#?}\n"))?; } f.write_fmt(format_args!("gesture: {:#?}\n", self.gesture_state))?; Ok(()) diff --git a/crates/egui/src/introspection.rs b/crates/egui/src/introspection.rs index ae66cddb0..6f0cada78 100644 --- a/crates/egui/src/introspection.rs +++ b/crates/egui/src/introspection.rs @@ -31,10 +31,7 @@ pub(crate) fn font_texture_ui(ui: &mut Ui, [width, height]: [usize; 2]) -> Respo Color32::BLACK }; - ui.label(format!( - "Texture size: {} x {} (hover to zoom)", - width, height - )); + ui.label(format!("Texture size: {width} x {height} (hover to zoom)")); if width <= 1 || height <= 1 { return; } @@ -108,7 +105,7 @@ impl Widget for &epaint::stats::PaintStats { label(ui, shape_path, "paths"); label(ui, shape_mesh, "nested meshes"); label(ui, shape_vec, "nested shapes"); - ui.label(format!("{:6} callbacks", num_callbacks)); + ui.label(format!("{num_callbacks:6} callbacks")); ui.add_space(10.0); ui.label("Text shapes:"); diff --git a/crates/egui/src/layers.rs b/crates/egui/src/layers.rs index 9e951f0dc..83e7fdeb6 100644 --- a/crates/egui/src/layers.rs +++ b/crates/egui/src/layers.rs @@ -127,7 +127,7 @@ impl PaintList { #[inline(always)] pub fn add(&mut self, clip_rect: Rect, shape: Shape) -> ShapeIdx { let idx = ShapeIdx(self.0.len()); - self.0.push(ClippedShape(clip_rect, shape)); + self.0.push(ClippedShape { clip_rect, shape }); idx } @@ -135,7 +135,7 @@ impl PaintList { self.0.extend( shapes .into_iter() - .map(|shape| ClippedShape(clip_rect, shape)), + .map(|shape| ClippedShape { clip_rect, shape }), ); } @@ -148,12 +148,12 @@ impl PaintList { /// and then later setting it using `paint_list.set(idx, cr, frame);`. #[inline(always)] pub fn set(&mut self, idx: ShapeIdx, clip_rect: Rect, shape: Shape) { - self.0[idx.0] = ClippedShape(clip_rect, shape); + self.0[idx.0] = ClippedShape { clip_rect, shape }; } /// Translate each [`Shape`] and clip rectangle by this much, in-place pub fn translate(&mut self, delta: Vec2) { - for ClippedShape(clip_rect, shape) in &mut self.0 { + for ClippedShape { clip_rect, shape } in &mut self.0 { *clip_rect = clip_rect.translate(delta); shape.translate(delta); } diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index f7eef71a9..aab9049b6 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -335,7 +335,9 @@ pub use epaint::emath; #[cfg(feature = "color-hex")] pub use ecolor::hex_color; pub use ecolor::{Color32, Rgba}; -pub use emath::{lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rect, Vec2}; +pub use emath::{ + lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rangef, Rect, Vec2, +}; pub use epaint::{ mutex, text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index db76f7e7c..d89fd344a 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -546,8 +546,10 @@ impl Memory { #[cfg_attr(feature = "serde", serde(default))] pub struct Areas { areas: IdMap, + /// Back-to-front. Top is last. order: Vec, + visible_last_frame: ahash::HashSet, visible_current_frame: ahash::HashSet, diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index 214bbe175..17028e6cc 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -1,8 +1,7 @@ -use std::ops::RangeInclusive; use std::sync::Arc; use crate::{ - emath::{Align2, Pos2, Rect, Vec2}, + emath::{Align2, Pos2, Rangef, Rect, Vec2}, layers::{LayerId, PaintList, ShapeIdx}, Color32, Context, FontId, }; @@ -227,7 +226,7 @@ impl Painter { pub fn error(&self, pos: Pos2, text: impl std::fmt::Display) -> Rect { let color = self.ctx.style().visuals.error_fg_color; - self.debug_text(pos, Align2::LEFT_TOP, color, format!("🔥 {}", text)) + self.debug_text(pos, Align2::LEFT_TOP, color, format!("🔥 {text}")) } /// text with a background @@ -263,12 +262,12 @@ impl Painter { } /// Paints a horizontal line. - pub fn hline(&self, x: RangeInclusive, y: f32, stroke: impl Into) { + pub fn hline(&self, x: impl Into, y: f32, stroke: impl Into) { self.add(Shape::hline(x, y, stroke)); } /// Paints a vertical line. - pub fn vline(&self, x: f32, y: RangeInclusive, stroke: impl Into) { + pub fn vline(&self, x: f32, y: impl Into, stroke: impl Into) { self.add(Shape::vline(x, y, stroke)); } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index eb3b6036b..4a8dba7c0 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -360,30 +360,46 @@ impl Margin { } /// Total margins on both sides + #[inline] pub fn sum(&self) -> Vec2 { vec2(self.left + self.right, self.top + self.bottom) } + #[inline] pub fn left_top(&self) -> Vec2 { vec2(self.left, self.top) } + #[inline] pub fn right_bottom(&self) -> Vec2 { vec2(self.right, self.bottom) } + #[inline] pub fn is_same(&self) -> bool { self.left == self.right && self.left == self.top && self.left == self.bottom } + + #[inline] + pub fn expand_rect(&self, rect: Rect) -> Rect { + Rect::from_min_max(rect.min - self.left_top(), rect.max + self.right_bottom()) + } + + #[inline] + pub fn shrink_rect(&self, rect: Rect) -> Rect { + Rect::from_min_max(rect.min + self.left_top(), rect.max - self.right_bottom()) + } } impl From for Margin { + #[inline] fn from(v: f32) -> Self { Self::same(v) } } impl From for Margin { + #[inline] fn from(v: Vec2) -> Self { Self::symmetric(v.x, v.y) } @@ -392,6 +408,7 @@ impl From for Margin { impl std::ops::Add for Margin { type Output = Self; + #[inline] fn add(self, other: Self) -> Self { Self { left: self.left + other.left, @@ -491,7 +508,8 @@ pub struct Visuals { pub resize_corner_size: f32, - pub text_cursor_width: f32, + /// The color and width of the text cursor + pub text_cursor: Stroke, /// show where the text cursor would be if you clicked pub text_cursor_preview: bool, @@ -767,7 +785,7 @@ impl Visuals { popup_shadow: Shadow::small_dark(), resize_corner_size: 12.0, - text_cursor_width: 2.0, + text_cursor: Stroke::new(2.0, Color32::from_rgb(192, 222, 255)), text_cursor_preview: false, clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion button_frame: true, @@ -800,6 +818,7 @@ impl Visuals { panel_fill: Color32::from_gray(248), popup_shadow: Shadow::small_light(), + text_cursor: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)), ..Self::dark() } } @@ -1334,7 +1353,7 @@ impl Visuals { popup_shadow, resize_corner_size, - text_cursor_width, + text_cursor, text_cursor_preview, clip_rect_margin, button_frame, @@ -1392,8 +1411,9 @@ impl Visuals { }); ui_color(ui, hyperlink_color, "hyperlink_color"); + stroke_ui(ui, text_cursor, "Text Cursor"); + ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size")); - ui.add(Slider::new(text_cursor_width, 0.0..=4.0).text("text_cursor_width")); ui.checkbox(text_cursor_preview, "Preview text cursor on hover"); ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin")); diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 13b3e81f1..e29a40097 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -517,15 +517,17 @@ impl Ui { } /// `ui.set_width_range(min..=max);` is equivalent to `ui.set_min_width(min); ui.set_max_width(max);`. - pub fn set_width_range(&mut self, width: std::ops::RangeInclusive) { - self.set_min_width(*width.start()); - self.set_max_width(*width.end()); + pub fn set_width_range(&mut self, width: impl Into) { + let width = width.into(); + self.set_min_width(width.min); + self.set_max_width(width.max); } /// `ui.set_height_range(min..=max);` is equivalent to `ui.set_min_height(min); ui.set_max_height(max);`. - pub fn set_height_range(&mut self, height: std::ops::RangeInclusive) { - self.set_min_height(*height.start()); - self.set_max_height(*height.end()); + pub fn set_height_range(&mut self, height: impl Into) { + let height = height.into(); + self.set_min_height(height.min); + self.set_max_height(height.max); } /// Set both the minimum and maximum width. @@ -556,6 +558,7 @@ impl Ui { // Layout related measures: /// The available space at the moment, given the current cursor. + /// /// This how much more space we can take up without overflowing our parent. /// Shrinks as widgets allocate space and the cursor moves. /// A small size should be interpreted as "as little as possible". @@ -564,19 +567,30 @@ impl Ui { self.placer.available_size() } + /// The available width at the moment, given the current cursor. + /// + /// See [`Self::available_size`] for more information. pub fn available_width(&self) -> f32 { self.available_size().x } + /// The available height at the moment, given the current cursor. + /// + /// See [`Self::available_size`] for more information. pub fn available_height(&self) -> f32 { self.available_size().y } /// In case of a wrapping layout, how much space is left on this row/column? + /// + /// If the layout does not wrap, this will return the same value as [`Self::available_size`]. pub fn available_size_before_wrap(&self) -> Vec2 { self.placer.available_rect_before_wrap().size() } + /// In case of a wrapping layout, how much space is left on this row/column? + /// + /// If the layout does not wrap, this will return the same value as [`Self::available_size`]. pub fn available_rect_before_wrap(&self) -> Rect { self.placer.available_rect_before_wrap() } @@ -966,7 +980,7 @@ impl Ui { /// ``` pub fn scroll_to_rect(&self, rect: Rect, align: Option) { for d in 0..2 { - let range = rect.min[d]..=rect.max[d]; + let range = Rangef::new(rect.min[d], rect.max[d]); self.ctx() .frame_state_mut(|state| state.scroll_target[d] = Some((range, align))); } @@ -996,9 +1010,9 @@ impl Ui { pub fn scroll_to_cursor(&self, align: Option) { let target = self.next_widget_position(); for d in 0..2 { - let target = target[d]; + let target = Rangef::point(target[d]); self.ctx() - .frame_state_mut(|state| state.scroll_target[d] = Some((target..=target, align))); + .frame_state_mut(|state| state.scroll_target[d] = Some((target, align))); } } @@ -2231,3 +2245,9 @@ impl Ui { } } } + +#[test] +fn ui_impl_send_sync() { + fn assert_send_sync() {} + assert_send_sync::(); +} diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index a7fa67818..ca211987a 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -23,6 +23,7 @@ pub struct Button { text: WidgetText, shortcut_text: WidgetText, wrap: Option, + /// None means default for interact fill: Option, stroke: Option, diff --git a/crates/egui/src/widgets/color_picker.rs b/crates/egui/src/widgets/color_picker.rs index da5f2867d..fcb701f47 100644 --- a/crates/egui/src/widgets/color_picker.rs +++ b/crates/egui/src/widgets/color_picker.rs @@ -234,17 +234,17 @@ fn color_text_ui(ui: &mut Ui, color: impl Into, alpha: Alpha) { if ui.button("📋").on_hover_text("Click to copy").clicked() { if alpha == Alpha::Opaque { - ui.output_mut(|o| o.copied_text = format!("{}, {}, {}", r, g, b)); + ui.output_mut(|o| o.copied_text = format!("{r}, {g}, {b}")); } else { - ui.output_mut(|o| o.copied_text = format!("{}, {}, {}, {}", r, g, b, a)); + ui.output_mut(|o| o.copied_text = format!("{r}, {g}, {b}, {a}")); } } if alpha == Alpha::Opaque { - ui.label(format!("rgb({}, {}, {})", r, g, b)) + ui.label(format!("rgb({r}, {g}, {b})")) .on_hover_text("Red Green Blue"); } else { - ui.label(format!("rgba({}, {}, {}, {})", r, g, b, a)) + ui.label(format!("rgba({r}, {g}, {b}, {a})")) .on_hover_text("Red Green Blue with premultiplied Alpha"); } }); diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index 0d33978f2..0e1b54605 100644 --- a/crates/egui/src/widgets/drag_value.rs +++ b/crates/egui/src/widgets/drag_value.rs @@ -11,6 +11,7 @@ use crate::*; pub(crate) struct MonoState { last_dragged_id: Option, last_dragged_value: Option, + /// For temporary edit of a [`DragValue`] value. /// Couples with the current focus id. edit_string: Option, @@ -63,6 +64,7 @@ pub struct DragValue<'a> { max_decimals: Option, custom_formatter: Option>, custom_parser: Option>, + update_while_editing: bool, } impl<'a> DragValue<'a> { @@ -94,6 +96,7 @@ impl<'a> DragValue<'a> { max_decimals: None, custom_formatter: None, custom_parser: None, + update_while_editing: true, } } @@ -352,6 +355,15 @@ impl<'a> DragValue<'a> { } .custom_parser(|s| i64::from_str_radix(s, 16).map(|n| n as f64).ok()) } + + /// Update the value on each key press when text-editing the value. + /// + /// Default: `true`. + /// If `false`, the value will only be updated when user presses enter or deselects the value. + pub fn update_while_editing(mut self, update: bool) -> Self { + self.update_while_editing = update; + self + } } impl<'a> Widget for DragValue<'a> { @@ -366,6 +378,7 @@ impl<'a> Widget for DragValue<'a> { max_decimals, custom_formatter, custom_parser, + update_while_editing, } = self; let shift = ui.input(|i| i.modifiers.shift_only()); @@ -392,7 +405,9 @@ impl<'a> Widget for DragValue<'a> { let auto_decimals = (aim_rad / speed.abs()).log10().ceil().clamp(0.0, 15.0) as usize; let auto_decimals = auto_decimals + is_slow_speed as usize; - let max_decimals = max_decimals.unwrap_or(auto_decimals + 2); + let max_decimals = max_decimals + .unwrap_or(auto_decimals + 2) + .at_least(min_decimals); let auto_decimals = auto_decimals.clamp(min_decimals, max_decimals); let change = ui.input_mut(|input| { @@ -475,9 +490,15 @@ impl<'a> Widget for DragValue<'a> { .desired_width(ui.spacing().interact_size.x) .font(text_style), ); - // Only update the value when the user presses enter, or clicks elsewhere. NOT every frame. - // See https://github.com/emilk/egui/issues/2687 - if response.lost_focus() { + + let update = if update_while_editing { + // Update when the edit content has changed. + response.changed() + } else { + // Update only when the edit has lost focus. + response.lost_focus() + }; + if update { let parsed_value = match custom_parser { Some(parser) => parser(&value_text), None => value_text.parse().ok(), @@ -606,7 +627,7 @@ impl<'a> Widget for DragValue<'a> { // The value is exposed as a string by the text edit widget // when in edit mode. if !is_kb_editing { - let value_text = format!("{}{}{}", prefix, value_text, suffix); + let value_text = format!("{prefix}{value_text}{suffix}"); builder.set_value(value_text); } }); diff --git a/crates/egui/src/widgets/plot/items/mod.rs b/crates/egui/src/widgets/plot/items/mod.rs index e96d8b3b4..2cbcb26dd 100644 --- a/crates/egui/src/widgets/plot/items/mod.rs +++ b/crates/egui/src/widgets/plot/items/mod.rs @@ -760,15 +760,22 @@ impl PlotItem for Text { /// A set of points. pub struct Points { pub(super) series: PlotPoints, + pub(super) shape: MarkerShape, + /// Color of the marker. `Color32::TRANSPARENT` means that it will be picked automatically. pub(super) color: Color32, + /// Whether to fill the marker. Does not apply to all types. pub(super) filled: bool, + /// The maximum extent of the marker from its center. pub(super) radius: f32, + pub(super) name: String, + pub(super) highlight: bool, + pub(super) stems: Option, } @@ -997,6 +1004,7 @@ impl PlotItem for Points { pub struct Arrows { pub(super) origins: PlotPoints, pub(super) tips: PlotPoints, + pub(super) tip_length: Option, pub(super) color: Color32, pub(super) name: String, pub(super) highlight: bool, @@ -1007,6 +1015,7 @@ impl Arrows { Self { origins: origins.into(), tips: tips.into(), + tip_length: None, color: Color32::TRANSPARENT, name: Default::default(), highlight: false, @@ -1019,6 +1028,12 @@ impl Arrows { self } + /// Set the length of the arrow tips + pub fn tip_length(mut self, tip_length: f32) -> Self { + self.tip_length = Some(tip_length); + self + } + /// Set the arrows' color. pub fn color(mut self, color: impl Into) -> Self { self.color = color.into(); @@ -1044,6 +1059,7 @@ impl PlotItem for Arrows { let Self { origins, tips, + tip_length, color, highlight, .. @@ -1062,7 +1078,11 @@ impl PlotItem for Arrows { .for_each(|(origin, tip)| { let vector = tip - origin; let rot = Rot2::from_angle(std::f32::consts::TAU / 10.0); - let tip_length = vector.length() / 4.0; + let tip_length = if let Some(tip_length) = tip_length { + *tip_length + } else { + vector.length() / 4.0 + }; let tip = origin + vector; let dir = vector.normalized(); shapes.push(Shape::line_segment([origin, tip], stroke)); @@ -1119,6 +1139,7 @@ pub struct PlotImage { pub(super) tint: Color32, pub(super) highlight: bool, pub(super) name: String, + pub(crate) rotation: Option<(f32, Vec2)>, } impl PlotImage { @@ -1137,6 +1158,7 @@ impl PlotImage { size: size.into(), bg_fill: Default::default(), tint: Color32::WHITE, + rotation: None, } } @@ -1175,6 +1197,17 @@ impl PlotImage { self.name = name.to_string(); self } + + /// Rotate the image about an origin by some angle + /// + /// Positive angle is clockwise. + /// Origin is a vector in normalized UV space ((0,0) in top-left, (1,1) bottom right). + /// + /// To rotate about the center you can pass `Vec2::splat(0.5)` as the origin. + pub fn rotate(mut self, angle: f32, origin: Vec2) -> Self { + self.rotation = Some((angle, origin)); + self + } } impl PlotItem for PlotImage { @@ -1202,11 +1235,14 @@ impl PlotItem for PlotImage { let right_bottom_tf = transform.position_from_point(&right_bottom); Rect::from_two_pos(left_top_tf, right_bottom_tf) }; - Image::new(*texture_id, *size) + let mut image = Image::new(*texture_id, *size) .bg_fill(*bg_fill) .tint(*tint) - .uv(*uv) - .paint_at(ui, rect); + .uv(*uv); + if let Some((angle, origin)) = self.rotation { + image = image.rotate(angle, origin); + } + image.paint_at(ui, rect); if *highlight { shapes.push(Shape::rect_stroke( rect, @@ -1261,8 +1297,10 @@ pub struct BarChart { pub(super) bars: Vec, pub(super) default_color: Color32, pub(super) name: String, + /// A custom element formatter pub(super) element_formatter: Option String>>, + highlight: bool, } @@ -1431,8 +1469,10 @@ pub struct BoxPlot { pub(super) boxes: Vec, pub(super) default_color: Color32, pub(super) name: String, + /// A custom element formatter pub(super) element_formatter: Option String>>, + highlight: bool, } @@ -1692,7 +1732,7 @@ pub(super) fn rulers_at_value( let mut prefix = String::new(); if !name.is_empty() { - prefix = format!("{}\n", name); + prefix = format!("{name}\n"); } let text = { diff --git a/crates/egui/src/widgets/plot/items/values.rs b/crates/egui/src/widgets/plot/items/values.rs index 739490dcf..52fceba7b 100644 --- a/crates/egui/src/widgets/plot/items/values.rs +++ b/crates/egui/src/widgets/plot/items/values.rs @@ -125,8 +125,8 @@ impl ToString for LineStyle { fn to_string(&self) -> String { match self { LineStyle::Solid => "Solid".into(), - LineStyle::Dotted { spacing } => format!("Dotted{}Px", spacing), - LineStyle::Dashed { length } => format!("Dashed{}Px", length), + LineStyle::Dotted { spacing } => format!("Dotted{spacing}Px"), + LineStyle::Dashed { length } => format!("Dashed{length}Px"), } } } diff --git a/crates/egui/src/widgets/plot/mod.rs b/crates/egui/src/widgets/plot/mod.rs index c5db91992..ebc73d728 100644 --- a/crates/egui/src/widgets/plot/mod.rs +++ b/crates/egui/src/widgets/plot/mod.rs @@ -101,9 +101,11 @@ struct PlotMemory { /// Indicates if the user has modified the bounds, for example by moving or zooming, /// or if the bounds should be calculated based by included point or auto bounds. bounds_modified: AxisBools, + hovered_entry: Option, hidden_items: ahash::HashSet, last_plot_transform: PlotTransform, + /// Allows to remember the first click position when performing a boxed zoom last_click_pos_for_zoom: Option, } @@ -864,7 +866,7 @@ impl Plot { delta.y = 0.0; } transform.translate_bounds(delta); - bounds_modified = true.into(); + bounds_modified = allow_drag; } // Zooming @@ -935,7 +937,7 @@ impl Plot { } if zoom_factor != Vec2::splat(1.0) { transform.zoom(zoom_factor, hover_pos); - bounds_modified = true.into(); + bounds_modified = allow_zoom; } } if allow_scroll { @@ -1081,17 +1083,25 @@ impl PlotUi { .push(BoundsModification::Translate(delta_pos)); } + /// Can be used to check if the plot was hovered or clicked. + pub fn response(&self) -> &Response { + &self.response + } + /// Returns `true` if the plot area is currently hovered. + #[deprecated = "Use plot_ui.response().hovered()"] pub fn plot_hovered(&self) -> bool { self.response.hovered() } /// Returns `true` if the plot was clicked by the primary button. + #[deprecated = "Use plot_ui.response().clicked()"] pub fn plot_clicked(&self) -> bool { self.response.clicked() } /// Returns `true` if the plot was clicked by the secondary button. + #[deprecated = "Use plot_ui.response().secondary_clicked()"] pub fn plot_secondary_clicked(&self) -> bool { self.response.secondary_clicked() } @@ -1443,7 +1453,7 @@ impl PreparedPlot { let axis_range = match axis { 0 => bounds.range_x(), 1 => bounds.range_y(), - _ => panic!("Axis {} does not exist.", axis), + _ => panic!("Axis {axis} does not exist."), }; let font_id = TextStyle::Body.resolve(ui.style()); @@ -1666,7 +1676,7 @@ pub fn format_number(number: f64, num_decimals: usize) -> String { let is_integral = number as i64 as f64 == number; if is_integral { // perfect integer - show it as such: - format!("{:.0}", number) + format!("{number:.0}") } else { // make sure we tell the user it is not an integer by always showing a decimal or two: format!("{:.*}", num_decimals.at_least(1), number) diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 9ebcaf493..f793d86e7 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -77,8 +77,10 @@ pub struct Slider<'a> { prefix: String, suffix: String, text: WidgetText, + /// Sets the minimal step of the widget value step: Option, + drag_value_speed: Option, min_decimals: usize, max_decimals: Option, @@ -524,12 +526,12 @@ impl<'a> Slider<'a> { } /// For instance, `position` is the mouse position and `position_range` is the physical location of the slider on the screen. - fn value_from_position(&self, position: f32, position_range: RangeInclusive) -> f64 { + fn value_from_position(&self, position: f32, position_range: Rangef) -> f64 { let normalized = remap_clamp(position, position_range, 0.0..=1.0) as f64; value_from_normalized(normalized, self.range(), &self.spec) } - fn position_from_value(&self, value: f64, position_range: RangeInclusive) -> f32 { + fn position_from_value(&self, value: f64, position_range: Rangef) -> f32 { let normalized = normalized_from_value(value, self.range(), &self.spec); lerp(position_range, normalized as f32) } @@ -555,11 +557,11 @@ impl<'a> Slider<'a> { let new_value = if self.smart_aim { let aim_radius = ui.input(|i| i.aim_radius()); emath::smart_aim::best_in_range_f64( - self.value_from_position(position - aim_radius, position_range.clone()), - self.value_from_position(position + aim_radius, position_range.clone()), + self.value_from_position(position - aim_radius, position_range), + self.value_from_position(position + aim_radius, position_range), ) } else { - self.value_from_position(position, position_range.clone()) + self.value_from_position(position, position_range) }; self.set_value(new_value); } @@ -594,18 +596,18 @@ impl<'a> Slider<'a> { if kb_step != 0.0 { let prev_value = self.get_value(); - let prev_position = self.position_from_value(prev_value, position_range.clone()); + let prev_position = self.position_from_value(prev_value, position_range); let new_position = prev_position + kb_step; let new_value = match self.step { Some(step) => prev_value + (kb_step as f64 * step), None if self.smart_aim => { let aim_radius = ui.input(|i| i.aim_radius()); emath::smart_aim::best_in_range_f64( - self.value_from_position(new_position - aim_radius, position_range.clone()), - self.value_from_position(new_position + aim_radius, position_range.clone()), + self.value_from_position(new_position - aim_radius, position_range), + self.value_from_position(new_position + aim_radius, position_range), ) } - _ => self.value_from_position(new_position, position_range.clone()), + _ => self.value_from_position(new_position, position_range), }; self.set_value(new_value); } @@ -686,15 +688,11 @@ impl<'a> Slider<'a> { } } - fn position_range(&self, rect: &Rect) -> RangeInclusive { + fn position_range(&self, rect: &Rect) -> Rangef { let handle_radius = self.handle_radius(rect); match self.orientation { - SliderOrientation::Horizontal => { - (rect.left() + handle_radius)..=(rect.right() - handle_radius) - } - SliderOrientation::Vertical => { - (rect.bottom() - handle_radius)..=(rect.top() + handle_radius) - } + SliderOrientation::Horizontal => rect.x_range().shrink(handle_radius), + SliderOrientation::Vertical => rect.y_range().shrink(handle_radius), } } @@ -726,7 +724,7 @@ impl<'a> Slider<'a> { } } - fn value_ui(&mut self, ui: &mut Ui, position_range: RangeInclusive) -> Response { + fn value_ui(&mut self, ui: &mut Ui, position_range: Rangef) -> Response { // If [`DragValue`] is controlled from the keyboard and `step` is defined, set speed to `step` let change = ui.input(|input| { input.num_presses(Key::ArrowUp) as i32 + input.num_presses(Key::ArrowRight) as i32 @@ -740,7 +738,7 @@ impl<'a> Slider<'a> { step } else { self.drag_value_speed - .unwrap_or_else(|| self.current_gradient(&position_range)) + .unwrap_or_else(|| self.current_gradient(position_range)) }; let mut value = self.get_value(); @@ -767,12 +765,11 @@ impl<'a> Slider<'a> { } /// delta(value) / delta(points) - fn current_gradient(&mut self, position_range: &RangeInclusive) -> f64 { + fn current_gradient(&mut self, position_range: Rangef) -> f64 { // TODO(emilk): handle clamping let value = self.get_value(); - let value_from_pos = - |position: f32| self.value_from_position(position, position_range.clone()); - let pos_from_value = |value: f64| self.position_from_value(value, position_range.clone()); + let value_from_pos = |position: f32| self.value_from_position(position, position_range); + let pos_from_value = |value: f64| self.position_from_value(value, position_range); let left_value = value_from_pos(pos_from_value(value) - 0.5); let right_value = value_from_pos(pos_from_value(value) + 0.5); right_value - left_value diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index b76c6e336..e92bf9dbc 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -1138,7 +1138,7 @@ fn paint_cursor_end( galley: &Galley, cursor: &Cursor, ) -> Rect { - let stroke = ui.visuals().selection.stroke; + let stroke = ui.visuals().text_cursor; let mut cursor_pos = galley.pos_from_cursor(cursor).translate(pos.to_vec2()); cursor_pos.max.y = cursor_pos.max.y.at_least(cursor_pos.min.y + row_height); // Handle completely empty galleys @@ -1147,10 +1147,7 @@ fn paint_cursor_end( let top = cursor_pos.center_top(); let bottom = cursor_pos.center_bottom(); - painter.line_segment( - [top, bottom], - (ui.visuals().text_cursor_width, stroke.color), - ); + painter.line_segment([top, bottom], (stroke.width, stroke.color)); if false { // Roof/floor: @@ -1185,7 +1182,7 @@ fn insert_text( if char_limit < usize::MAX { let mut new_string = text_to_insert; // Avoid subtract with overflow panic - let cutoff = char_limit.saturating_sub(text.as_str().len()); + let cutoff = char_limit.saturating_sub(text.as_str().chars().count()); new_string = match new_string.char_indices().nth(cutoff) { None => new_string, diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index c4d6def0e..2f6b185f2 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false default-run = "egui_demo_app" diff --git a/crates/egui_demo_app/src/apps/http_app.rs b/crates/egui_demo_app/src/apps/http_app.rs index 4d87a3e29..e550c1dbc 100644 --- a/crates/egui_demo_app/src/apps/http_app.rs +++ b/crates/egui_demo_app/src/apps/http_app.rs @@ -133,7 +133,7 @@ fn ui_url(ui: &mut egui::Ui, frame: &mut eframe::Frame, url: &mut String) -> boo if ui.button("Random image").clicked() { let seed = ui.input(|i| i.time); let side = 640; - *url = format!("https://picsum.photos/seed/{}/{}", seed, side); + *url = format!("https://picsum.photos/seed/{seed}/{side}"); trigger_fetch = true; } }); diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index 33d1cc630..6480756f2 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -232,8 +232,7 @@ impl BackendPanel { if ui .add_enabled(enabled, egui::Button::new("Reset")) .on_hover_text(format!( - "Reset scale to native value ({:.1})", - native_pixels_per_point + "Reset scale to native value ({native_pixels_per_point:.1})" )) .clicked() { @@ -441,7 +440,7 @@ impl EguiWindows { .stick_to_bottom(true) .show(ui, |ui| { for event in output_event_history { - ui.label(format!("{:?}", event)); + ui.label(format!("{event:?}")); } }); }); diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index 3b2c09a79..69a23e7ab 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -354,7 +354,7 @@ impl WrapApp { { selected_anchor = anchor; if frame.is_web() { - ui.output_mut(|o| o.open_url(format!("#{}", anchor))); + ui.output_mut(|o| o.open_url(format!("#{anchor}"))); } } } diff --git a/crates/egui_demo_lib/Cargo.toml b/crates/egui_demo_lib/Cargo.toml index d8f7c688d..e86baca29 100644 --- a/crates/egui_demo_lib/Cargo.toml +++ b/crates/egui_demo_lib/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] description = "Example library for egui" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui/tree/master/crates/egui_demo_lib" license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/crates/egui_demo_lib/src/demo/about.rs b/crates/egui_demo_lib/src/demo/about.rs index 0fbb97642..4972b224b 100644 --- a/crates/egui_demo_lib/src/demo/about.rs +++ b/crates/egui_demo_lib/src/demo/about.rs @@ -83,11 +83,11 @@ fn about_immediate_mode(ui: &mut egui::Ui) { fn links(ui: &mut egui::Ui) { use egui::special_emojis::{GITHUB, TWITTER}; ui.hyperlink_to( - format!("{} egui on GitHub", GITHUB), + format!("{GITHUB} egui on GitHub"), "https://github.com/emilk/egui", ); ui.hyperlink_to( - format!("{} @ernerfeldt", TWITTER), + format!("{TWITTER} @ernerfeldt"), "https://twitter.com/ernerfeldt", ); ui.hyperlink_to("egui documentation", "https://docs.rs/egui/"); diff --git a/crates/egui_demo_lib/src/demo/code_example.rs b/crates/egui_demo_lib/src/demo/code_example.rs index c72fde221..7b1351476 100644 --- a/crates/egui_demo_lib/src/demo/code_example.rs +++ b/crates/egui_demo_lib/src/demo/code_example.rs @@ -121,7 +121,7 @@ impl CodeExample { ui.separator(); - code_view_ui(ui, &format!("{:#?}", self)); + code_view_ui(ui, &format!("{self:#?}")); ui.separator(); diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 4ad6c2d95..354bb44e6 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -251,11 +251,11 @@ impl DemoWindows { use egui::special_emojis::{GITHUB, TWITTER}; ui.hyperlink_to( - format!("{} egui on GitHub", GITHUB), + format!("{GITHUB} egui on GitHub"), "https://github.com/emilk/egui", ); ui.hyperlink_to( - format!("{} @ernerfeldt", TWITTER), + format!("{TWITTER} @ernerfeldt"), "https://twitter.com/ernerfeldt", ); diff --git a/crates/egui_demo_lib/src/demo/layout_test.rs b/crates/egui_demo_lib/src/demo/layout_test.rs index 2d4fa8a82..8adf1bb6b 100644 --- a/crates/egui_demo_lib/src/demo/layout_test.rs +++ b/crates/egui_demo_lib/src/demo/layout_test.rs @@ -140,7 +140,7 @@ impl LayoutTest { Direction::TopDown, Direction::BottomUp, ] { - ui.radio_value(&mut self.layout.main_dir, dir, format!("{:?}", dir)); + ui.radio_value(&mut self.layout.main_dir, dir, format!("{dir:?}")); } }); @@ -162,7 +162,7 @@ impl LayoutTest { ui.horizontal(|ui| { ui.label("Cross Align:"); for &align in &[Align::Min, Align::Center, Align::Max] { - ui.radio_value(&mut self.layout.cross_align, align, format!("{:?}", align)); + ui.radio_value(&mut self.layout.cross_align, align, format!("{align:?}")); } }); diff --git a/crates/egui_demo_lib/src/demo/misc_demo_window.rs b/crates/egui_demo_lib/src/demo/misc_demo_window.rs index 61a6091e9..e4cf92644 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -455,7 +455,7 @@ impl Tree { .into_iter() .enumerate() .filter_map(|(i, mut tree)| { - if tree.ui_impl(ui, depth + 1, &format!("child #{}", i)) == Action::Keep { + if tree.ui_impl(ui, depth + 1, &format!("child #{i}")) == Action::Keep { Some(tree) } else { None diff --git a/crates/egui_demo_lib/src/demo/multi_touch.rs b/crates/egui_demo_lib/src/demo/multi_touch.rs index c358f6b21..1a3b29e8e 100644 --- a/crates/egui_demo_lib/src/demo/multi_touch.rs +++ b/crates/egui_demo_lib/src/demo/multi_touch.rs @@ -50,7 +50,7 @@ impl super::View for MultiTouch { ui.label("Try touch gestures Pinch/Stretch, Rotation, and Pressure with 2+ fingers."); let num_touches = ui.input(|i| i.multi_touch().map_or(0, |mt| mt.num_touches)); - ui.label(format!("Current touches: {}", num_touches)); + ui.label(format!("Current touches: {num_touches}")); let color = if ui.visuals().dark_mode { Color32::WHITE diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index 12d79ce0e..8daf3c377 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -39,8 +39,8 @@ pub struct PlotDemo { charts_demo: ChartsDemo, items_demo: ItemsDemo, interaction_demo: InteractionDemo, - custom_axes_demo: CustomAxisDemo, - linked_axes_demo: LinkedAxisDemo, + custom_axes_demo: CustomAxesDemo, + linked_axes_demo: LinkedAxesDemo, open_panel: Panel, } @@ -326,7 +326,7 @@ impl MarkerDemo { [5.0, 0.0 + y_offset], [6.0, 0.5 + y_offset], ]) - .name(format!("{:?}", marker)) + .name(format!("{marker:?}")) .filled(self.fill_markers) .radius(self.marker_radius) .shape(marker); @@ -416,7 +416,7 @@ impl LegendDemo { ui.label("Position:"); ui.horizontal(|ui| { Corner::all().for_each(|position| { - ui.selectable_value(&mut config.position, position, format!("{:?}", position)); + ui.selectable_value(&mut config.position, position, format!("{position:?}")); }); }); ui.end_row(); @@ -448,19 +448,19 @@ impl LegendDemo { // ---------------------------------------------------------------------------- #[derive(PartialEq, Default)] -struct CustomAxisDemo {} +struct CustomAxesDemo {} -impl CustomAxisDemo { +impl CustomAxesDemo { const MINS_PER_DAY: f64 = 24.0 * 60.0; const MINS_PER_H: f64 = 60.0; fn logistic_fn() -> Line { fn days(min: f64) -> f64 { - CustomAxisDemo::MINS_PER_DAY * min + CustomAxesDemo::MINS_PER_DAY * min } let values = PlotPoints::from_explicit_callback( - move |x| 1.0 / (1.0 + (-2.5 * (x / CustomAxisDemo::MINS_PER_DAY - 2.0)).exp()), + move |x| 1.0 / (1.0 + (-2.5 * (x / CustomAxesDemo::MINS_PER_DAY - 2.0)).exp()), days(0.0)..days(5.0), 100, ); @@ -504,8 +504,8 @@ impl CustomAxisDemo { #[allow(clippy::unused_self)] fn ui(&mut self, ui: &mut Ui) -> Response { - const MINS_PER_DAY: f64 = CustomAxisDemo::MINS_PER_DAY; - const MINS_PER_H: f64 = CustomAxisDemo::MINS_PER_H; + const MINS_PER_DAY: f64 = CustomAxesDemo::MINS_PER_DAY; + const MINS_PER_H: f64 = CustomAxesDemo::MINS_PER_H; fn day(x: f64) -> f64 { (x / MINS_PER_DAY).floor() @@ -561,10 +561,10 @@ impl CustomAxisDemo { .data_aspect(2.0 * MINS_PER_DAY as f32) .x_axis_formatter(x_fmt) .y_axis_formatter(y_fmt) - .x_grid_spacer(CustomAxisDemo::x_grid) + .x_grid_spacer(CustomAxesDemo::x_grid) .label_formatter(label_fmt) .show(ui, |plot_ui| { - plot_ui.line(CustomAxisDemo::logistic_fn()); + plot_ui.line(CustomAxesDemo::logistic_fn()); }) .response } @@ -573,14 +573,14 @@ impl CustomAxisDemo { // ---------------------------------------------------------------------------- #[derive(PartialEq)] -struct LinkedAxisDemo { +struct LinkedAxesDemo { link_x: bool, link_y: bool, link_cursor_x: bool, link_cursor_y: bool, } -impl Default for LinkedAxisDemo { +impl Default for LinkedAxesDemo { fn default() -> Self { let link_x = true; let link_y = false; @@ -595,7 +595,7 @@ impl Default for LinkedAxisDemo { } } -impl LinkedAxisDemo { +impl LinkedAxesDemo { fn line_with_slope(slope: f64) -> Line { Line::new(PlotPoints::from_explicit_callback( move |x| slope * x, @@ -621,11 +621,11 @@ impl LinkedAxisDemo { } fn configure_plot(plot_ui: &mut plot::PlotUi) { - plot_ui.line(LinkedAxisDemo::line_with_slope(0.5)); - plot_ui.line(LinkedAxisDemo::line_with_slope(1.0)); - plot_ui.line(LinkedAxisDemo::line_with_slope(2.0)); - plot_ui.line(LinkedAxisDemo::sin()); - plot_ui.line(LinkedAxisDemo::cos()); + plot_ui.line(LinkedAxesDemo::line_with_slope(0.5)); + plot_ui.line(LinkedAxesDemo::line_with_slope(1.0)); + plot_ui.line(LinkedAxesDemo::line_with_slope(2.0)); + plot_ui.line(LinkedAxesDemo::sin()); + plot_ui.line(LinkedAxesDemo::cos()); } fn ui(&mut self, ui: &mut Ui) -> Response { @@ -648,14 +648,14 @@ impl LinkedAxisDemo { .height(250.0) .link_axis(link_group_id, self.link_x, self.link_y) .link_cursor(link_group_id, self.link_cursor_x, self.link_cursor_y) - .show(ui, LinkedAxisDemo::configure_plot); + .show(ui, LinkedAxesDemo::configure_plot); Plot::new("linked_axis_2") .data_aspect(2.0) .width(150.0) .height(250.0) .link_axis(link_group_id, self.link_x, self.link_y) .link_cursor(link_group_id, self.link_cursor_x, self.link_cursor_y) - .show(ui, LinkedAxisDemo::configure_plot); + .show(ui, LinkedAxesDemo::configure_plot); }); Plot::new("linked_axis_3") .data_aspect(0.5) @@ -663,7 +663,7 @@ impl LinkedAxisDemo { .height(150.0) .link_axis(link_group_id, self.link_x, self.link_y) .link_cursor(link_group_id, self.link_cursor_x, self.link_cursor_y) - .show(ui, LinkedAxisDemo::configure_plot) + .show(ui, LinkedAxesDemo::configure_plot) .response } } @@ -761,7 +761,7 @@ impl InteractionDemo { plot_ui.pointer_coordinate(), plot_ui.pointer_coordinate_drag_delta(), plot_ui.plot_bounds(), - plot_ui.plot_hovered(), + plot_ui.response().hovered(), ) }); @@ -774,21 +774,18 @@ impl InteractionDemo { "origin in screen coordinates: x: {:.02}, y: {:.02}", screen_pos.x, screen_pos.y )); - ui.label(format!("plot hovered: {}", hovered)); + ui.label(format!("plot hovered: {hovered}")); let coordinate_text = if let Some(coordinate) = pointer_coordinate { format!("x: {:.02}, y: {:.02}", coordinate.x, coordinate.y) } else { "None".to_owned() }; - ui.label(format!("pointer coordinate: {}", coordinate_text)); + ui.label(format!("pointer coordinate: {coordinate_text}")); let coordinate_text = format!( "x: {:.02}, y: {:.02}", pointer_coordinate_drag_delta.x, pointer_coordinate_drag_delta.y ); - ui.label(format!( - "pointer coordinate drag delta: {}", - coordinate_text - )); + ui.label(format!("pointer coordinate drag delta: {coordinate_text}")); response } diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index b970b3264..f5833521b 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -232,10 +232,10 @@ impl super::View for ScrollTo { for item in 1..=50 { if track_item && item == self.track_item { let response = - ui.colored_label(Color32::YELLOW, format!("This is item {}", item)); + ui.colored_label(Color32::YELLOW, format!("This is item {item}")); response.scroll_to_me(self.tack_item_align); } else { - ui.label(format!("This is item {}", item)); + ui.label(format!("This is item {item}")); } } }); @@ -254,8 +254,7 @@ impl super::View for ScrollTo { ui.separator(); ui.label(format!( - "Scroll offset: {:.0}/{:.0} px", - current_scroll, max_scroll + "Scroll offset: {current_scroll:.0}/{max_scroll:.0} px" )); ui.separator(); diff --git a/crates/egui_demo_lib/src/demo/strip_demo.rs b/crates/egui_demo_lib/src/demo/strip_demo.rs index 2e9038b3a..db42fcb0b 100644 --- a/crates/egui_demo_lib/src/demo/strip_demo.rs +++ b/crates/egui_demo_lib/src/demo/strip_demo.rs @@ -37,7 +37,7 @@ impl super::View for StripDemo { .size(Size::exact(50.0)) .size(Size::remainder()) .size(Size::relative(0.5).at_least(60.0)) - .size(Size::exact(10.0)) + .size(Size::exact(10.5)) .vertical(|mut strip| { strip.cell(|ui| { ui.painter().rect_filled( diff --git a/crates/egui_demo_lib/src/demo/table_demo.rs b/crates/egui_demo_lib/src/demo/table_demo.rs index 8fdea4202..6523805ba 100644 --- a/crates/egui_demo_lib/src/demo/table_demo.rs +++ b/crates/egui_demo_lib/src/demo/table_demo.rs @@ -38,7 +38,6 @@ impl super::Demo for TableDemo { fn show(&mut self, ctx: &egui::Context, open: &mut bool) { egui::Window::new(self.name()) .open(open) - .resizable(true) .default_width(400.0) .show(ctx, |ui| { use super::View as _; @@ -102,7 +101,7 @@ impl super::View for TableDemo { use egui_extras::{Size, StripBuilder}; StripBuilder::new(ui) .size(Size::remainder().at_least(100.0)) // for the table - .size(Size::exact(10.0)) // for the source code link + .size(Size::exact(10.5)) // for the source code link .vertical(|mut strip| { strip.cell(|ui| { egui::ScrollArea::horizontal().show(ui, |ui| { diff --git a/crates/egui_demo_lib/src/demo/tests.rs b/crates/egui_demo_lib/src/demo/tests.rs index 83e798769..784b9525c 100644 --- a/crates/egui_demo_lib/src/demo/tests.rs +++ b/crates/egui_demo_lib/src/demo/tests.rs @@ -20,7 +20,7 @@ impl super::View for CursorTest { ui.heading("Hover to switch cursor icon:"); for &cursor_icon in &egui::CursorIcon::ALL { let _ = ui - .button(format!("{:?}", cursor_icon)) + .button(format!("{cursor_icon:?}")) .on_hover_cursor(cursor_icon); } ui.add(crate::egui_github_link_file!()); @@ -239,7 +239,7 @@ impl super::View for TableTest { for row in 0..self.num_rows { for col in 0..self.num_cols { if col == 0 { - ui.label(format!("row {}", row)); + ui.label(format!("row {row}")); } else { let word_idx = row * 3 + col * 5; let word_count = (row * 5 + col * 75) % 13; @@ -350,13 +350,13 @@ impl super::View for InputTest { use std::fmt::Write as _; if response.clicked_by(button) { - writeln!(new_info, "Clicked by {:?} button", button).ok(); + writeln!(new_info, "Clicked by {button:?} button").ok(); } if response.double_clicked_by(button) { - writeln!(new_info, "Double-clicked by {:?} button", button).ok(); + writeln!(new_info, "Double-clicked by {button:?} button").ok(); } if response.triple_clicked_by(button) { - writeln!(new_info, "Triple-clicked by {:?} button", button).ok(); + writeln!(new_info, "Triple-clicked by {button:?} button").ok(); } if response.dragged_by(button) { writeln!( diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index d91ae395e..eb424e9e3 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -126,7 +126,7 @@ impl WidgetGallery { ui.add(doc_link_label("Hyperlink", "Hyperlink")); use egui::special_emojis::GITHUB; ui.hyperlink_to( - format!("{} egui on GitHub", GITHUB), + format!("{GITHUB} egui on GitHub"), "https://github.com/emilk/egui", ); ui.end_row(); @@ -173,7 +173,7 @@ impl WidgetGallery { ui.add(doc_link_label("ComboBox", "ComboBox")); egui::ComboBox::from_label("Take your pick") - .selected_text(format!("{:?}", radio)) + .selected_text(format!("{radio:?}")) .show_ui(ui, |ui| { ui.style_mut().wrap = Some(false); ui.set_min_width(60.0); @@ -277,8 +277,8 @@ fn example_plot(ui: &mut egui::Ui) -> egui::Response { } fn doc_link_label<'a>(title: &'a str, search_term: &'a str) -> impl egui::Widget + 'a { - let label = format!("{}:", title); - let url = format!("https://docs.rs/egui?search={}", search_term); + let label = format!("{title}:"); + let url = format!("https://docs.rs/egui?search={search_term}"); move |ui: &mut egui::Ui| { ui.hyperlink_to(label, url).on_hover_ui(|ui| { ui.horizontal_wrapped(|ui| { diff --git a/crates/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs b/crates/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs index 44408ff2b..29be2d2ee 100644 --- a/crates/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs +++ b/crates/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs @@ -161,7 +161,7 @@ fn numbered_point(ui: &mut Ui, width: f32, number: &str) -> Response { let font_id = TextStyle::Body.resolve(ui.style()); let row_height = ui.fonts(|f| f.row_height(&font_id)); let (rect, response) = ui.allocate_exact_size(vec2(width, row_height), Sense::hover()); - let text = format!("{}.", number); + let text = format!("{number}."); let text_color = ui.visuals().strong_text_color(); ui.painter().text( rect.right_center(), diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index ab60317d6..3a968179b 100644 --- a/crates/egui_extras/CHANGELOG.md +++ b/crates/egui_extras/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog for egui_extras All notable changes to the `egui_extras` integration will be noted in this file. - -## Unreleased +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. ## 0.22.0 - 2023-05-23 diff --git a/crates/egui_extras/Cargo.toml b/crates/egui_extras/Cargo.toml index f5f4595aa..fe3bf8876 100644 --- a/crates/egui_extras/Cargo.toml +++ b/crates/egui_extras/Cargo.toml @@ -8,7 +8,7 @@ authors = [ ] description = "Extra functionality and widgets for the egui GUI library" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui" license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/crates/egui_extras/src/datepicker/popup.rs b/crates/egui_extras/src/datepicker/popup.rs index 7da8cbc1a..d8dd46724 100644 --- a/crates/egui_extras/src/datepicker/popup.rs +++ b/crates/egui_extras/src/datepicker/popup.rs @@ -428,6 +428,6 @@ fn month_name(i: u32) -> &'static str { 10 => "October", 11 => "November", 12 => "December", - _ => panic!("Unknown month: {}", i), + _ => panic!("Unknown month: {i}"), } } diff --git a/crates/egui_extras/src/image.rs b/crates/egui_extras/src/image.rs index 3526b6de2..bec11d1ab 100644 --- a/crates/egui_extras/src/image.rs +++ b/crates/egui_extras/src/image.rs @@ -10,11 +10,15 @@ pub use usvg::FitTo; /// Use the `svg` and `image` features to enable more constructors. pub struct RetainedImage { debug_name: String, + size: [usize; 2], + /// Cleared once [`Self::texture`] has been loaded. image: Mutex, + /// Lazily loaded when we have an egui context. texture: Mutex>, + options: TextureOptions, } @@ -254,7 +258,7 @@ pub fn load_svg_bytes_with_size( }; let mut pixmap = tiny_skia::Pixmap::new(w, h) - .ok_or_else(|| format!("Failed to create SVG Pixmap of size {}x{}", w, h))?; + .ok_or_else(|| format!("Failed to create SVG Pixmap of size {w}x{h}"))?; resvg::render(&rtree, fit_to, Default::default(), pixmap.as_mut()) .ok_or_else(|| "Failed to render SVG".to_owned())?; diff --git a/crates/egui_extras/src/layout.rs b/crates/egui_extras/src/layout.rs index 932594bf5..fbd15e7ad 100644 --- a/crates/egui_extras/src/layout.rs +++ b/crates/egui_extras/src/layout.rs @@ -32,9 +32,11 @@ pub struct StripLayout<'l> { direction: CellDirection, pub(crate) rect: Rect, pub(crate) cursor: Pos2, + /// Keeps track of the max used position, /// so we know how much space we used. max: Pos2, + cell_layout: egui::Layout, } diff --git a/crates/egui_extras/src/sizing.rs b/crates/egui_extras/src/sizing.rs index 8a58302d5..c6ae9d6da 100644 --- a/crates/egui_extras/src/sizing.rs +++ b/crates/egui_extras/src/sizing.rs @@ -1,14 +1,16 @@ +use egui::Rangef; + /// Size hint for table column/strip cell. #[derive(Clone, Debug, Copy)] pub enum Size { /// Absolute size in points, with a given range of allowed sizes to resize within. - Absolute { initial: f32, range: (f32, f32) }, + Absolute { initial: f32, range: Rangef }, /// Relative size relative to all available space. - Relative { fraction: f32, range: (f32, f32) }, + Relative { fraction: f32, range: Rangef }, /// Multiple remainders each get the same space. - Remainder { range: (f32, f32) }, + Remainder { range: Rangef }, } impl Size { @@ -16,7 +18,7 @@ impl Size { pub fn exact(points: f32) -> Self { Self::Absolute { initial: points, - range: (points, points), + range: Rangef::new(points, points), } } @@ -24,7 +26,7 @@ impl Size { pub fn initial(points: f32) -> Self { Self::Absolute { initial: points, - range: (0.0, f32::INFINITY), + range: Rangef::new(0.0, f32::INFINITY), } } @@ -33,14 +35,14 @@ impl Size { egui::egui_assert!(0.0 <= fraction && fraction <= 1.0); Self::Relative { fraction, - range: (0.0, f32::INFINITY), + range: Rangef::new(0.0, f32::INFINITY), } } /// Multiple remainders each get the same space. pub fn remainder() -> Self { Self::Remainder { - range: (0.0, f32::INFINITY), + range: Rangef::new(0.0, f32::INFINITY), } } @@ -50,7 +52,7 @@ impl Size { Self::Absolute { range, .. } | Self::Relative { range, .. } | Self::Remainder { range, .. } => { - range.0 = minimum; + range.min = minimum; } } self @@ -62,14 +64,14 @@ impl Size { Self::Absolute { range, .. } | Self::Relative { range, .. } | Self::Remainder { range, .. } => { - range.1 = maximum; + range.max = maximum; } } self } /// Allowed range of movement (in points), if in a resizable [`Table`](crate::table::Table). - pub fn range(self) -> (f32, f32) { + pub fn range(self) -> Rangef { match self { Self::Absolute { range, .. } | Self::Relative { range, .. } @@ -99,12 +101,9 @@ impl Sizing { .iter() .map(|&size| match size { Size::Absolute { initial, .. } => initial, - Size::Relative { - fraction, - range: (min, max), - } => { + Size::Relative { fraction, range } => { assert!(0.0 <= fraction && fraction <= 1.0); - (length * fraction).clamp(min, max) + range.clamp(length * fraction) } Size::Remainder { .. } => { remainders += 1; @@ -120,9 +119,9 @@ impl Sizing { let mut remainder_length = length - sum_non_remainder; let avg_remainder_length = 0.0f32.max(remainder_length / remainders as f32).floor(); self.sizes.iter().for_each(|&size| { - if let Size::Remainder { range: (min, _max) } = size { - if avg_remainder_length < min { - remainder_length -= min; + if let Size::Remainder { range } = size { + if avg_remainder_length < range.min { + remainder_length -= range.min; remainders -= 1; } } @@ -138,11 +137,8 @@ impl Sizing { .iter() .map(|&size| match size { Size::Absolute { initial, .. } => initial, - Size::Relative { - fraction, - range: (min, max), - } => (length * fraction).clamp(min, max), - Size::Remainder { range: (min, max) } => avg_remainder_length.clamp(min, max), + Size::Relative { fraction, range } => range.clamp(length * fraction), + Size::Remainder { range } => range.clamp(avg_remainder_length), }) .collect() } diff --git a/crates/egui_extras/src/strip.rs b/crates/egui_extras/src/strip.rs index 52a768f35..86b8db3e2 100644 --- a/crates/egui_extras/src/strip.rs +++ b/crates/egui_extras/src/strip.rs @@ -72,13 +72,13 @@ impl<'a> StripBuilder<'a> { self } - /// Allocate space for for one column/row. + /// Allocate space for one column/row. pub fn size(mut self, size: Size) -> Self { self.sizing.add(size); self } - /// Allocate space for for several columns/rows at once. + /// Allocate space for several columns/rows at once. pub fn sizes(mut self, size: Size, count: usize) -> Self { for _ in 0..count { self.sizing.add(size); diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 2283ab4d7..de0e98704 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -3,7 +3,7 @@ //! | fixed size | all available space/minimum | 30% of available width | fixed size | //! Takes all available height, so if you want something below the table, put it in a strip. -use egui::{Align, NumExt as _, Rect, Response, ScrollArea, Ui, Vec2}; +use egui::{Align, NumExt as _, Rangef, Rect, Response, ScrollArea, Ui, Vec2}; use crate::{ layout::{CellDirection, CellSize}, @@ -28,7 +28,9 @@ enum InitialColumnSize { #[derive(Clone, Copy, Debug, PartialEq)] pub struct Column { initial_width: InitialColumnSize, - width_range: (f32, f32), + + width_range: Rangef, + /// Clip contents if too narrow? clip: bool, @@ -78,7 +80,7 @@ impl Column { fn new(initial_width: InitialColumnSize) -> Self { Self { initial_width, - width_range: (0.0, f32::INFINITY), + width_range: Rangef::new(0.0, f32::INFINITY), resizable: None, clip: false, } @@ -110,7 +112,7 @@ impl Column { /// /// Default: 0.0 pub fn at_least(mut self, minimum: f32) -> Self { - self.width_range.0 = minimum; + self.width_range.min = minimum; self } @@ -118,13 +120,13 @@ impl Column { /// /// Default: [`f32::INFINITY`] pub fn at_most(mut self, maximum: f32) -> Self { - self.width_range.1 = maximum; + self.width_range.max = maximum; self } /// Allowed range of movement (in points), if in a resizable [`Table`](crate::table::Table). - pub fn range(mut self, range: std::ops::RangeInclusive) -> Self { - self.width_range = (*range.start(), *range.end()); + pub fn range(mut self, range: impl Into) -> Self { + self.width_range = range.into(); self } @@ -146,8 +148,8 @@ fn to_sizing(columns: &[Column]) -> crate::sizing::Sizing { InitialColumnSize::Automatic(suggested_width) => Size::initial(suggested_width), InitialColumnSize::Remainder => Size::remainder(), } - .at_least(column.width_range.0) - .at_most(column.width_range.1); + .at_least(column.width_range.min) + .at_most(column.width_range.max); sizing.add(size); } sizing @@ -511,8 +513,10 @@ pub struct Table<'a> { columns: Vec, available_width: f32, state: TableState, + /// Accumulated maximum used widths for each column. max_used_widths: Vec, + first_frame_auto_size_columns: bool, resizable: bool, striped: bool, @@ -598,13 +602,13 @@ impl<'a> Table<'a> { if scroll_to_row.is_some() && scroll_to_y_range.is_none() { // TableBody::row didn't find the right row, so scroll to the bottom: - scroll_to_y_range = Some((f32::INFINITY, f32::INFINITY)); + scroll_to_y_range = Some(Rangef::new(f32::INFINITY, f32::INFINITY)); } }); - if let Some((min_y, max_y)) = scroll_to_y_range { + if let Some(y_range) = scroll_to_y_range { let x = 0.0; // ignored, we only have vertical scrolling - let rect = egui::Rect::from_min_max(egui::pos2(x, min_y), egui::pos2(x, max_y)); + let rect = egui::Rect::from_x_y_ranges(x..=x, y_range); let align = scroll_to_row.and_then(|(_, a)| a); ui.scroll_to_rect(rect, align); } @@ -617,14 +621,14 @@ impl<'a> Table<'a> { for (i, column_width) in state.column_widths.iter_mut().enumerate() { let column = &columns[i]; let column_is_resizable = column.resizable.unwrap_or(resizable); - let (min_width, max_width) = column.width_range; + let width_range = column.width_range; if !column.clip { // Unless we clip we don't want to shrink below the // size that was actually used: *column_width = column_width.at_least(max_used_widths[i]); } - *column_width = column_width.clamp(min_width, max_width); + *column_width = width_range.clamp(*column_width); let is_last_column = i + 1 == columns.len(); @@ -633,7 +637,7 @@ impl<'a> Table<'a> { let eps = 0.1; // just to avoid some rounding errors. *column_width = available_width - eps; *column_width = column_width.at_least(max_used_widths[i]); - *column_width = column_width.clamp(min_width, max_width); + *column_width = width_range.clamp(*column_width); break; } @@ -641,7 +645,7 @@ impl<'a> Table<'a> { if column.is_auto() && (first_frame_auto_size_columns || !column_is_resizable) { *column_width = max_used_widths[i]; - *column_width = column_width.clamp(min_width, max_width); + *column_width = width_range.clamp(*column_width); } else if column_is_resizable { let column_resize_id = ui.id().with("resize_column").with(i); @@ -656,7 +660,7 @@ impl<'a> Table<'a> { if resize_response.double_clicked() { // Resize to the minimum of what is needed. - *column_width = max_used_widths[i].clamp(min_width, max_width); + *column_width = width_range.clamp(max_used_widths[i]); } else if resize_response.dragged() { if let Some(pointer) = ui.ctx().pointer_latest_pos() { let mut new_width = *column_width + pointer.x - x; @@ -671,7 +675,7 @@ impl<'a> Table<'a> { new_width = new_width.at_least(max_used_widths[i] - max_shrinkage_per_frame); } - new_width = new_width.clamp(min_width, max_width); + new_width = width_range.clamp(new_width); let x = x - *column_width + new_width; (p0.x, p1.x) = (x, x); @@ -731,7 +735,7 @@ pub struct TableBody<'a> { /// If we find the correct row to scroll to, /// this is set to the y-range of the row. - scroll_to_y_range: &'a mut Option<(f32, f32)>, + scroll_to_y_range: &'a mut Option, } impl<'a> TableBody<'a> { @@ -779,7 +783,7 @@ impl<'a> TableBody<'a> { let bottom_y = self.layout.cursor.y; if Some(self.row_nr) == self.scroll_to_row { - *self.scroll_to_y_range = Some((top_y, bottom_y)); + *self.scroll_to_y_range = Some(Rangef::new(top_y, bottom_y)); } self.row_nr += 1; @@ -819,7 +823,7 @@ impl<'a> TableBody<'a> { if let Some(scroll_to_row) = self.scroll_to_row { let scroll_to_row = scroll_to_row.at_most(total_rows.saturating_sub(1)) as f32; - *self.scroll_to_y_range = Some(( + *self.scroll_to_y_range = Some(Rangef::new( self.layout.cursor.y + scroll_to_row * row_height_with_spacing, self.layout.cursor.y + (scroll_to_row + 1.0) * row_height_with_spacing, )); @@ -909,7 +913,7 @@ impl<'a> TableBody<'a> { cursor_y += (row_height + spacing.y) as f64; if Some(row_index) == self.scroll_to_row { - *self.scroll_to_y_range = Some(( + *self.scroll_to_y_range = Some(Rangef::new( (scroll_to_y_range_offset + old_cursor_y) as f32, (scroll_to_y_range_offset + cursor_y) as f32, )); @@ -953,7 +957,7 @@ impl<'a> TableBody<'a> { cursor_y += (row_height + spacing.y) as f64; if Some(row_index) == self.scroll_to_row { - *self.scroll_to_y_range = Some(( + *self.scroll_to_y_range = Some(Rangef::new( (scroll_to_y_range_offset + top_y) as f32, (scroll_to_y_range_offset + cursor_y) as f32, )); @@ -972,7 +976,7 @@ impl<'a> TableBody<'a> { let top_y = cursor_y; cursor_y += (row_height + spacing.y) as f64; if Some(row_index) == self.scroll_to_row { - *self.scroll_to_y_range = Some(( + *self.scroll_to_y_range = Some(Rangef::new( (scroll_to_y_range_offset + top_y) as f32, (scroll_to_y_range_offset + cursor_y) as f32, )); @@ -981,10 +985,8 @@ impl<'a> TableBody<'a> { if self.scroll_to_row.is_some() && self.scroll_to_y_range.is_none() { // Catch desire to scroll past the end: - *self.scroll_to_y_range = Some(( - (scroll_to_y_range_offset + cursor_y) as f32, - (scroll_to_y_range_offset + cursor_y) as f32, - )); + *self.scroll_to_y_range = + Some(Rangef::point((scroll_to_y_range_offset + cursor_y) as f32)); } if height_below_visible > 0.0 { @@ -1013,8 +1015,10 @@ pub struct TableRow<'a, 'b> { layout: &'b mut StripLayout<'a>, columns: &'b [Column], widths: &'b [f32], + /// grows during building with the maximum widths max_used_widths: &'b mut [f32], + col_index: usize, striped: bool, height: f32, diff --git a/crates/egui_glium/CHANGELOG.md b/crates/egui_glium/CHANGELOG.md index a34c23416..c1e98201d 100644 --- a/crates/egui_glium/CHANGELOG.md +++ b/crates/egui_glium/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog for egui_glium All notable changes to the `egui_glium` integration will be noted in this file. +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. + ## Unreleased * Remove the `screen_reader` feature ([#2669](https://github.com/emilk/egui/pull/2669)). diff --git a/crates/egui_glium/Cargo.toml b/crates/egui_glium/Cargo.toml index 9be5870dd..a4be314d4 100644 --- a/crates/egui_glium/Cargo.toml +++ b/crates/egui_glium/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] description = "Bindings for using egui natively using the glium library" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui/tree/master/crates/egui_glium" license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index a3dc5acfa..3710edab4 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog for egui_glow All notable changes to the `egui_glow` integration will be noted in this file. - -## Unreleased +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. ## 0.22.0 - 2023-05-23 diff --git a/crates/egui_glow/Cargo.toml b/crates/egui_glow/Cargo.toml index 1c1a797cb..dfd6e0de4 100644 --- a/crates/egui_glow/Cargo.toml +++ b/crates/egui_glow/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] description = "Bindings for using egui natively using the glow library" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui/tree/master/crates/egui_glow" license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/crates/emath/Cargo.toml b/crates/emath/Cargo.toml index d4eec7c7e..8fea677e3 100644 --- a/crates/emath/Cargo.toml +++ b/crates/emath/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] description = "Minimal 2D math library for GUI work" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui/tree/master/crates/emath" license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/crates/emath/src/align.rs b/crates/emath/src/align.rs index 9d7ccf81e..ef07cc1d3 100644 --- a/crates/emath/src/align.rs +++ b/crates/emath/src/align.rs @@ -110,29 +110,25 @@ impl Align { /// assert_eq!(Max .align_size_within_range(INFINITY, NEG_INFINITY..=20.0), NEG_INFINITY..=20.0); /// ``` #[inline] - pub fn align_size_within_range( - self, - size: f32, - range: RangeInclusive, - ) -> RangeInclusive { - let min = *range.start(); - let max = *range.end(); + pub fn align_size_within_range(self, size: f32, range: impl Into) -> Rangef { + let range = range.into(); + let Rangef { min, max } = range; if max - min == f32::INFINITY && size == f32::INFINITY { return range; } match self { - Self::Min => min..=min + size, + Self::Min => Rangef::new(min, min + size), Self::Center => { if size == f32::INFINITY { - f32::NEG_INFINITY..=f32::INFINITY + Rangef::new(f32::NEG_INFINITY, f32::INFINITY) } else { let left = (min + max) / 2.0 - size / 2.0; - left..=left + size + Rangef::new(left, left + size) } } - Self::Max => max - size..=max, + Self::Max => Rangef::new(max - size, max), } } } diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index 89b2db209..8cfe16de0 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -99,11 +99,12 @@ impl Real for f64 {} /// assert_eq!(lerp(1.0..=5.0, 2.0), 9.0); /// ``` #[inline(always)] -pub fn lerp(range: RangeInclusive, t: T) -> R +pub fn lerp(range: impl Into>, t: T) -> R where T: Real + Mul, R: Copy + Add, { + let range = range.into(); (T::one() - t) * *range.start() + t * *range.end() } @@ -138,20 +139,28 @@ where /// Linearly remap a value from one range to another, /// so that when `x == from.start()` returns `to.start()` /// and when `x == from.end()` returns `to.end()`. -pub fn remap(x: T, from: RangeInclusive, to: RangeInclusive) -> T +pub fn remap(x: T, from: impl Into>, to: impl Into>) -> T where T: Real, { + let from = from.into(); + let to = to.into(); crate::emath_assert!(from.start() != from.end()); let t = (x - *from.start()) / (*from.end() - *from.start()); lerp(to, t) } /// Like [`remap`], but also clamps the value so that the returned value is always in the `to` range. -pub fn remap_clamp(x: T, from: RangeInclusive, to: RangeInclusive) -> T +pub fn remap_clamp( + x: T, + from: impl Into>, + to: impl Into>, +) -> T where T: Real, { + let from = from.into(); + let to = to.into(); if from.end() < from.start() { return remap_clamp(x, *from.end()..=*from.start(), *to.end()..=*to.start()); } @@ -174,9 +183,7 @@ where /// Round a value to the given number of decimal places. pub fn round_to_decimals(value: f64, decimal_places: usize) -> f64 { // This is a stupid way of doing this, but stupid works. - format!("{:.*}", decimal_places, value) - .parse() - .unwrap_or(value) + format!("{value:.decimal_places$}").parse().unwrap_or(value) } pub fn format_with_minimum_decimals(value: f64, decimals: usize) -> String { @@ -194,7 +201,7 @@ pub fn format_with_decimals_in_range(value: f64, decimal_range: RangeInclusive().unwrap(), value as f32, epsilon) { // Enough precision to show the value accurately - good! @@ -205,7 +212,7 @@ pub fn format_with_decimals_in_range(value: f64, decimal_range: RangeInclusive for Pos2 { match index { 0 => &self.x, 1 => &self.y, - _ => panic!("Pos2 index out of bounds: {}", index), + _ => panic!("Pos2 index out of bounds: {index}"), } } } @@ -217,7 +217,7 @@ impl std::ops::IndexMut for Pos2 { match index { 0 => &mut self.x, 1 => &mut self.y, - _ => panic!("Pos2 index out of bounds: {}", index), + _ => panic!("Pos2 index out of bounds: {index}"), } } } diff --git a/crates/emath/src/range.rs b/crates/emath/src/range.rs index ae3e3f0e5..11459763d 100644 --- a/crates/emath/src/range.rs +++ b/crates/emath/src/range.rs @@ -36,14 +36,60 @@ impl Rangef { } #[inline] - pub fn span(&self) -> f32 { + pub fn point(min_and_max: f32) -> Self { + Self { + min: min_and_max, + max: min_and_max, + } + } + + /// The length of the range, i.e. `max - min`. + #[inline] + pub fn span(self) -> f32 { self.max - self.min } #[inline] - pub fn contains(&self, x: f32) -> bool { + #[must_use] + pub fn contains(self, x: f32) -> bool { self.min <= x && x <= self.max } + + /// Equivalent to `x.clamp(min, max)` + #[inline] + #[must_use] + pub fn clamp(self, x: f32) -> f32 { + x.clamp(self.min, self.max) + } + + /// Flip `min` and `max` if needed, so that `min <= max` after. + #[inline] + pub fn as_positive(self) -> Self { + Rangef { + min: self.min.min(self.max), + max: self.min.max(self.max), + } + } + + /// Shrink by this much on each side, keeping the center + #[inline] + #[must_use] + pub fn shrink(self, amnt: f32) -> Self { + Self { + min: self.min + amnt, + max: self.max - amnt, + } + } + + /// Expand by this much on each side, keeping the center + #[inline] + #[must_use] + pub fn expand(self, amnt: f32) -> Self { + Self { + min: self.min - amnt, + max: self.max + amnt, + } + } } impl From for RangeInclusive { @@ -108,3 +154,17 @@ impl From> for Rangef { Self::new(f32::NEG_INFINITY, range.end) } } + +impl PartialEq> for Rangef { + #[inline] + fn eq(&self, other: &RangeInclusive) -> bool { + self.min == *other.start() && self.max == *other.end() + } +} + +impl PartialEq for RangeInclusive { + #[inline] + fn eq(&self, other: &Rangef) -> bool { + *self.start() == other.min && *self.end() == other.max + } +} diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index b8c6085a2..d6d148d27 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -1,5 +1,4 @@ use std::f32::INFINITY; -use std::ops::RangeInclusive; use crate::*; @@ -82,15 +81,12 @@ impl Rect { } #[inline(always)] - pub fn from_x_y_ranges( - x_range: impl Into>, - y_range: impl Into>, - ) -> Self { + pub fn from_x_y_ranges(x_range: impl Into, y_range: impl Into) -> Self { let x_range = x_range.into(); let y_range = y_range.into(); Rect { - min: pos2(*x_range.start(), *y_range.start()), - max: pos2(*x_range.end(), *y_range.end()), + min: pos2(x_range.min, y_range.min), + max: pos2(x_range.max, y_range.max), } } @@ -280,6 +276,7 @@ impl Rect { } } + /// `rect.size() == Vec2 { x: rect.width(), y: rect.height() }` #[inline(always)] pub fn size(&self) -> Vec2 { self.max - self.min @@ -389,18 +386,18 @@ impl Rect { } #[inline(always)] - pub fn x_range(&self) -> RangeInclusive { - self.min.x..=self.max.x + pub fn x_range(&self) -> Rangef { + Rangef::new(self.min.x, self.max.x) } #[inline(always)] - pub fn y_range(&self) -> RangeInclusive { - self.min.y..=self.max.y + pub fn y_range(&self) -> Rangef { + Rangef::new(self.min.y, self.max.y) } #[inline(always)] - pub fn bottom_up_range(&self) -> RangeInclusive { - self.max.y..=self.min.y + pub fn bottom_up_range(&self) -> Rangef { + Rangef::new(self.max.y, self.min.y) } /// `width < 0 || height < 0` diff --git a/crates/emath/src/rot2.rs b/crates/emath/src/rot2.rs index e29f18f4e..b281ca58f 100644 --- a/crates/emath/src/rot2.rs +++ b/crates/emath/src/rot2.rs @@ -184,10 +184,7 @@ mod test { let expected = vec2(0.0, 3.0); assert!( (rotated - expected).length() < 1e-5, - "Expected {:?} to equal {:?}. rot: {:?}", - rotated, - expected, - rot, + "Expected {rotated:?} to equal {expected:?}. rot: {rot:?}", ); let undone = rot.inverse() * rot; diff --git a/crates/emath/src/vec2.rs b/crates/emath/src/vec2.rs index ac70f9b47..133c3fd3c 100644 --- a/crates/emath/src/vec2.rs +++ b/crates/emath/src/vec2.rs @@ -292,7 +292,7 @@ impl std::ops::Index for Vec2 { match index { 0 => &self.x, 1 => &self.y, - _ => panic!("Vec2 index out of bounds: {}", index), + _ => panic!("Vec2 index out of bounds: {index}"), } } } @@ -303,7 +303,7 @@ impl std::ops::IndexMut for Vec2 { match index { 0 => &mut self.x, 1 => &mut self.y, - _ => panic!("Vec2 index out of bounds: {}", index), + _ => panic!("Vec2 index out of bounds: {index}"), } } } diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index ebe9e6f6e..e0f4e0569 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/CHANGELOG.md @@ -1,8 +1,9 @@ # epaint changelog All notable changes to the epaint crate will be documented in this file. +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. -## Unreleased ## 0.22.0 - 2023-05-23 diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index 094b6c45e..747c69b15 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] description = "Minimal 2D graphics library for GUI work" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui/tree/master/crates/epaint" license = "(MIT OR Apache-2.0) AND OFL-1.1 AND LicenseRef-UFL-1.0" # OFL and UFL used by default_fonts. See https://github.com/emilk/egui/issues/2321 readme = "README.md" @@ -79,6 +79,7 @@ ahash = { version = "0.8.1", default-features = false, features = [ "std", ] } nohash-hasher = "0.2" +parking_lot = "0.12" # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. #! ### Optional dependencies bytemuck = { version = "1.7.2", optional = true, features = ["derive"] } @@ -94,11 +95,6 @@ serde = { version = "1", optional = true, features = ["derive", "rc"] } # native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] backtrace = { version = "0.3", optional = true } -parking_lot = "0.12" # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. - -# web: -[target.'cfg(target_arch = "wasm32")'.dependencies] -atomic_refcell = "0.1" # Used instead of parking_lot on on wasm. See https://github.com/emilk/egui/issues/1401 [dev-dependencies] diff --git a/crates/epaint/benches/benchmark.rs b/crates/epaint/benches/benchmark.rs index 126ec2bf2..7f7c9f1b1 100644 --- a/crates/epaint/benches/benchmark.rs +++ b/crates/epaint/benches/benchmark.rs @@ -47,7 +47,7 @@ fn tessellate_circles(c: &mut Criterion) { for _ in 0..10_000 { let clip_rect = Rect::from_min_size(Pos2::ZERO, Vec2::splat(1024.0)); let shape = Shape::circle_filled(Pos2::new(10.0, 10.0), r, Color32::WHITE); - clipped_shapes.push(ClippedShape(clip_rect, shape)); + clipped_shapes.push(ClippedShape { clip_rect, shape }); } } assert_eq!(clipped_shapes.len(), 100_000); diff --git a/crates/epaint/src/image.rs b/crates/epaint/src/image.rs index 35b0885c7..7ca33b3ac 100644 --- a/crates/epaint/src/image.rs +++ b/crates/epaint/src/image.rs @@ -110,6 +110,15 @@ impl ColorImage { Self { size, pixels } } + /// Create a [`ColorImage`] from flat opaque gray data. + /// + /// Panics if `size[0] * size[1] != gray.len()`. + pub fn from_gray(size: [usize; 2], gray: &[u8]) -> Self { + assert_eq!(size[0] * size[1], gray.len()); + let pixels = gray.iter().map(|p| Color32::from_gray(*p)).collect(); + Self { size, pixels } + } + /// A view of the underlying data as `&[u8]` #[cfg(feature = "bytemuck")] pub fn as_raw(&self) -> &[u8] { diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index 82fc7e0f4..1662c94a1 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -90,13 +90,14 @@ impl Default for TextureId { /// /// Everything is using logical points. #[derive(Clone, Debug, PartialEq)] -pub struct ClippedShape( +pub struct ClippedShape { /// Clip / scissor rectangle. /// Only show the part of the [`Shape`] that falls within this. - pub emath::Rect, + pub clip_rect: emath::Rect, + /// The shape - pub Shape, -); + pub shape: Shape, +} /// A [`Mesh`] or [`PaintCallback`] within a clip rectangle. /// diff --git a/crates/epaint/src/mesh.rs b/crates/epaint/src/mesh.rs index 09025268a..62f0de21f 100644 --- a/crates/epaint/src/mesh.rs +++ b/crates/epaint/src/mesh.rs @@ -251,8 +251,7 @@ impl Mesh { assert!( index_cursor > span_start, - "One triangle spanned more than {} vertices", - MAX_SIZE + "One triangle spanned more than {MAX_SIZE} vertices" ); let mesh = Mesh16 { diff --git a/crates/epaint/src/mutex.rs b/crates/epaint/src/mutex.rs index f3a18bf15..050fe9dfa 100644 --- a/crates/epaint/src/mutex.rs +++ b/crates/epaint/src/mutex.rs @@ -1,13 +1,14 @@ -//! Helper module that wraps some Mutex types with different implementations. +//! Helper module that adds extra checks when the `deadlock_detection` feature is turned on. // ---------------------------------------------------------------------------- -#[cfg(not(target_arch = "wasm32"))] -#[cfg(not(debug_assertions))] +#[cfg(not(feature = "deadlock_detection"))] mod mutex_impl { /// Provides interior mutability. /// - /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. + /// This is a thin wrapper around [`parking_lot::Mutex`], except if + /// the feature `deadlock_detection` is turned enabled, in which case + /// extra checks are added to detect deadlocks. #[derive(Default)] pub struct Mutex(parking_lot::Mutex); @@ -27,12 +28,13 @@ mod mutex_impl { } } -#[cfg(not(target_arch = "wasm32"))] -#[cfg(debug_assertions)] +#[cfg(feature = "deadlock_detection")] mod mutex_impl { /// Provides interior mutability. /// - /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. + /// This is a thin wrapper around [`parking_lot::Mutex`], except if + /// the feature `deadlock_detection` is turned enabled, in which case + /// extra checks are added to detect deadlocks. #[derive(Default)] pub struct Mutex(parking_lot::Mutex); @@ -82,6 +84,11 @@ mod mutex_impl { MutexGuard(self.0.lock(), ptr) } + + #[inline(always)] + pub fn into_inner(self) -> T { + self.0.into_inner() + } } impl Drop for MutexGuard<'_, T> { @@ -110,7 +117,8 @@ mod mutex_impl { } } -#[cfg(not(target_arch = "wasm32"))] +// ---------------------------------------------------------------------------- + #[cfg(not(feature = "deadlock_detection"))] mod rw_lock_impl { /// The lock you get from [`RwLock::read`]. @@ -121,7 +129,9 @@ mod rw_lock_impl { /// Provides interior mutability. /// - /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. + /// This is a thin wrapper around [`parking_lot::RwLock`], except if + /// the feature `deadlock_detection` is turned enabled, in which case + /// extra checks are added to detect deadlocks. #[derive(Default)] pub struct RwLock(parking_lot::RwLock); @@ -143,7 +153,6 @@ mod rw_lock_impl { } } -#[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "deadlock_detection")] mod rw_lock_impl { use std::{ @@ -241,7 +250,9 @@ mod rw_lock_impl { /// Provides interior mutability. /// - /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. + /// This is a thin wrapper around [`parking_lot::RwLock`], except if + /// the feature `deadlock_detection` is turned enabled, in which case + /// extra checks are added to detect deadlocks. #[derive(Default)] pub struct RwLock { lock: parking_lot::RwLock, @@ -314,6 +325,11 @@ mod rw_lock_impl { holders: Arc::clone(&self.holders), } } + + #[inline(always)] + pub fn into_inner(self) -> T { + self.lock.into_inner() + } } fn make_backtrace() -> backtrace::Backtrace { @@ -323,7 +339,7 @@ mod rw_lock_impl { fn format_backtrace(backtrace: &mut backtrace::Backtrace) -> String { backtrace.resolve(); - let stacktrace = format!("{:?}", backtrace); + let stacktrace = format!("{backtrace:?}"); // Remove irrelevant parts of the stacktrace: let end_offset = stacktrace @@ -342,70 +358,6 @@ mod rw_lock_impl { // ---------------------------------------------------------------------------- -#[cfg(target_arch = "wasm32")] -mod mutex_impl { - // `atomic_refcell` will panic if multiple threads try to access the same value - - /// Provides interior mutability. - /// - /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. - #[derive(Default)] - pub struct Mutex(atomic_refcell::AtomicRefCell); - - /// The lock you get from [`Mutex`]. - pub use atomic_refcell::AtomicRefMut as MutexGuard; - - impl Mutex { - #[inline(always)] - pub fn new(val: T) -> Self { - Self(atomic_refcell::AtomicRefCell::new(val)) - } - - /// Panics if already locked. - #[inline(always)] - pub fn lock(&self) -> MutexGuard<'_, T> { - self.0.borrow_mut() - } - } -} - -#[cfg(target_arch = "wasm32")] -mod rw_lock_impl { - // `atomic_refcell` will panic if multiple threads try to access the same value - - /// The lock you get from [`RwLock::read`]. - pub use atomic_refcell::AtomicRef as RwLockReadGuard; - - /// The lock you get from [`RwLock::write`]. - pub use atomic_refcell::AtomicRefMut as RwLockWriteGuard; - - /// Provides interior mutability. - /// - /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. - #[derive(Default)] - pub struct RwLock(atomic_refcell::AtomicRefCell); - - impl RwLock { - #[inline(always)] - pub fn new(val: T) -> Self { - Self(atomic_refcell::AtomicRefCell::new(val)) - } - - #[inline(always)] - pub fn read(&self) -> RwLockReadGuard<'_, T> { - self.0.borrow() - } - - /// Panics if already locked. - #[inline(always)] - pub fn write(&self) -> RwLockWriteGuard<'_, T> { - self.0.borrow_mut() - } - } -} - -// ---------------------------------------------------------------------------- - pub use mutex_impl::{Mutex, MutexGuard}; pub use rw_lock_impl::{RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -449,7 +401,7 @@ mod tests { let other_thread = { let one = Arc::clone(&one); std::thread::spawn(move || { - let _ = one.lock(); + let _lock = one.lock(); }) }; std::thread::sleep(Duration::from_millis(200)); diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 046215661..401f51d8a 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -1,6 +1,5 @@ //! The different shapes that can be painted. -use std::ops::RangeInclusive; use std::{any::Any, sync::Arc}; use crate::{ @@ -94,17 +93,19 @@ impl Shape { } /// A horizontal line. - pub fn hline(x: RangeInclusive, y: f32, stroke: impl Into) -> Self { + pub fn hline(x: impl Into, y: f32, stroke: impl Into) -> Self { + let x = x.into(); Shape::LineSegment { - points: [pos2(*x.start(), y), pos2(*x.end(), y)], + points: [pos2(x.min, y), pos2(x.max, y)], stroke: stroke.into(), } } /// A vertical line. - pub fn vline(x: f32, y: RangeInclusive, stroke: impl Into) -> Self { + pub fn vline(x: f32, y: impl Into, stroke: impl Into) -> Self { + let y = y.into(); Shape::LineSegment { - points: [pos2(x, *y.start()), pos2(x, *y.end())], + points: [pos2(x, y.min), pos2(x, y.max)], stroke: stroke.into(), } } diff --git a/crates/epaint/src/stats.rs b/crates/epaint/src/stats.rs index 5abbe456f..6723b61f4 100644 --- a/crates/epaint/src/stats.rs +++ b/crates/epaint/src/stats.rs @@ -106,7 +106,7 @@ impl AllocInfo { element_size: ElementSize::Homogeneous(element_size), num_allocs: 1, num_elements: slice.len(), - num_bytes: slice.len() * element_size, + num_bytes: std::mem::size_of_val(slice), } } @@ -183,7 +183,7 @@ impl PaintStats { stats.shape_vec.element_size = ElementSize::Heterogenous; // nicer display later stats.shapes = AllocInfo::from_slice(shapes); - for ClippedShape(_, shape) in shapes { + for ClippedShape { shape, .. } in shapes { stats.add(shape); } stats diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 75b5dc963..facb2612d 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -13,17 +13,15 @@ use emath::*; #[allow(clippy::approx_constant)] mod precomputed_vertices { - /* - fn main() { - let n = 64; - println!("pub const CIRCLE_{}: [Vec2; {}] = [", n, n+1); - for i in 0..=n { - let a = std::f64::consts::TAU * i as f64 / n as f64; - println!(" vec2({:.06}, {:.06}),", a.cos(), a.sin()); - } - println!("];") - } - */ + // fn main() { + // let n = 64; + // println!("pub const CIRCLE_{}: [Vec2; {}] = [", n, n+1); + // for i in 0..=n { + // let a = std::f64::consts::TAU * i as f64 / n as f64; + // println!(" vec2({:.06}, {:.06}),", a.cos(), a.sin()); + // } + // println!("];") + // } use emath::{vec2, Vec2}; @@ -1036,7 +1034,10 @@ impl Tessellator { clipped_shape: ClippedShape, out_primitives: &mut Vec, ) { - let ClippedShape(new_clip_rect, new_shape) = clipped_shape; + let ClippedShape { + clip_rect: new_clip_rect, + shape: new_shape, + } = clipped_shape; if !new_clip_rect.is_positive() { return; // skip empty clip rectangles @@ -1044,7 +1045,13 @@ impl Tessellator { if let Shape::Vec(shapes) = new_shape { for shape in shapes { - self.tessellate_clipped_shape(ClippedShape(new_clip_rect, shape), out_primitives); + self.tessellate_clipped_shape( + ClippedShape { + clip_rect: new_clip_rect, + shape, + }, + out_primitives, + ); } return; } @@ -1641,7 +1648,10 @@ fn test_tessellator() { shapes.push(Shape::mesh(mesh)); let shape = Shape::Vec(shapes); - let clipped_shapes = vec![ClippedShape(rect, shape)]; + let clipped_shapes = vec![ClippedShape { + clip_rect: rect, + shape, + }]; let font_tex_size = [1024, 1024]; // unused let prepared_discs = vec![]; // unused diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index 939cdf9ee..dfcb52a11 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -78,11 +78,15 @@ impl Default for GlyphInfo { pub struct FontImpl { name: String, ab_glyph_font: ab_glyph::FontArc, + /// Maximum character height scale_in_pixels: u32, + height_in_points: f32, + // move each character by this much (hack) y_offset: f32, + ascent: f32, pixels_per_point: f32, glyph_info_cache: RwLock>, // TODO(emilk): standard Mutex @@ -320,8 +324,10 @@ type FontIndex = usize; /// Wrapper over multiple [`FontImpl`] (e.g. a primary + fallbacks for emojis) pub struct Font { fonts: Vec>, + /// Lazily calculated. characters: Option>, + replacement_glyph: (FontIndex, GlyphInfo), pixels_per_point: f32, row_height: f32, @@ -361,8 +367,7 @@ impl Font { .or_else(|| slf.glyph_info_no_cache_or_fallback(FALLBACK_REPLACEMENT_CHAR)) .unwrap_or_else(|| { panic!( - "Failed to find replacement characters {:?} or {:?}", - PRIMARY_REPLACEMENT_CHAR, FALLBACK_REPLACEMENT_CHAR + "Failed to find replacement characters {PRIMARY_REPLACEMENT_CHAR:?} or {FALLBACK_REPLACEMENT_CHAR:?}" ) }); slf.replacement_glyph = replacement_glyph; diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 829a593f6..474d6e70e 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -200,7 +200,7 @@ fn ab_glyph_font_from_font_data(name: &str, data: &FontData) -> ab_glyph::FontAr .map(ab_glyph::FontArc::from) } } - .unwrap_or_else(|err| panic!("Error parsing {:?} TTF/OTF font file: {}", name, err)) + .unwrap_or_else(|err| panic!("Error parsing {name:?} TTF/OTF font file: {err}")) } /// Describes the font data and the sizes to use. @@ -586,8 +586,7 @@ impl FontsImpl { ) -> Self { assert!( 0.0 < pixels_per_point && pixels_per_point < 100.0, - "pixels_per_point out of range: {}", - pixels_per_point + "pixels_per_point out of range: {pixels_per_point}" ); let texture_width = max_texture_side.at_most(8 * 1024); @@ -627,9 +626,8 @@ impl FontsImpl { .entry((HashableF32(*size), family.clone())) .or_insert_with(|| { let fonts = &self.definitions.families.get(family); - let fonts = fonts.unwrap_or_else(|| { - panic!("FontFamily::{:?} is not bound to any fonts", family) - }); + let fonts = fonts + .unwrap_or_else(|| panic!("FontFamily::{family:?} is not bound to any fonts")); let fonts: Vec> = fonts .iter() @@ -752,17 +750,14 @@ impl FontImplCache { let (tweak, ab_glyph_font) = self .ab_glyph_fonts .get(font_name) - .unwrap_or_else(|| panic!("No font data found for {:?}", font_name)) + .unwrap_or_else(|| panic!("No font data found for {font_name:?}")) .clone(); let scale_in_pixels = self.pixels_per_point * scale_in_points; // Scale the font properly (see https://github.com/emilk/egui/issues/2068). let units_per_em = ab_glyph_font.units_per_em().unwrap_or_else(|| { - panic!( - "The font unit size of {:?} exceeds the expected range (16..=16384)", - font_name - ) + panic!("The font unit size of {font_name:?} exceeds the expected range (16..=16384)") }); let font_scaling = ab_glyph_font.height_unscaled() / units_per_em; let scale_in_pixels = scale_in_pixels * font_scaling; diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index d6d81d88b..8ec829b8a 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -193,8 +193,10 @@ impl std::hash::Hash for LayoutJob { pub struct LayoutSection { /// Can be used for first row indentation. pub leading_space: f32, + /// Range into the galley text pub byte_range: Range, + pub format: TextFormat, } @@ -218,12 +220,18 @@ impl std::hash::Hash for LayoutSection { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct TextFormat { pub font_id: FontId, + /// Text color pub color: Color32, + pub background: Color32, + pub italics: bool, + pub underline: Stroke, + pub strikethrough: Stroke, + /// If you use a small font and [`Align::TOP`] you /// can get the effect of raised text. pub valign: Align, @@ -310,7 +318,13 @@ impl Default for TextWrapping { /// /// You can create a [`Galley`] using [`crate::Fonts::layout_job`]; /// -/// This needs to be recreated if `pixels_per_point` (dpi scale) changes. +/// Needs to be recreated if the underlying font atlas texture changes, which +/// happens under the following conditions: +/// - `pixels_per_point` or `max_texture_size` change. These parameters are set +/// in [`crate::text::Fonts::begin_frame`]. When using `egui` they are set +/// from `egui::InputState` and can change at any time. +/// - The atlas has become full. This can happen any time a new glyph is added +/// to the atlas, which in turn can happen any time new text is laid out. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Galley { diff --git a/crates/epaint/src/textures.rs b/crates/epaint/src/textures.rs index e6b423746..abe8d0b91 100644 --- a/crates/epaint/src/textures.rs +++ b/crates/epaint/src/textures.rs @@ -9,8 +9,10 @@ use crate::{ImageData, ImageDelta, TextureId}; pub struct TextureManager { /// We allocate texture id:s linearly. next_id: u64, + /// Information about currently allocated textures. metas: ahash::HashMap, + delta: TexturesDelta, } diff --git a/examples/confirm_exit/Cargo.toml b/examples/confirm_exit/Cargo.toml index e4b193e33..08144e7f6 100644 --- a/examples/confirm_exit/Cargo.toml +++ b/examples/confirm_exit/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index 9dfa403c6..c75ee3c4d 100644 --- a/examples/custom_3d_glow/Cargo.toml +++ b/examples/custom_3d_glow/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/custom_3d_glow/src/main.rs b/examples/custom_3d_glow/src/main.rs index f72859d23..7cd954c39 100644 --- a/examples/custom_3d_glow/src/main.rs +++ b/examples/custom_3d_glow/src/main.rs @@ -144,7 +144,7 @@ impl RotatingTriangle { let shader = gl .create_shader(*shader_type) .expect("Cannot create shader"); - gl.shader_source(shader, &format!("{}\n{}", shader_version, shader_source)); + gl.shader_source(shader, &format!("{shader_version}\n{shader_source}")); gl.compile_shader(shader); assert!( gl.get_shader_compile_status(shader), diff --git a/examples/custom_font/Cargo.toml b/examples/custom_font/Cargo.toml index 78d0491d3..c7b212b5e 100644 --- a/examples/custom_font/Cargo.toml +++ b/examples/custom_font/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/custom_font_style/Cargo.toml b/examples/custom_font_style/Cargo.toml index 3eda5ee33..3c5e9600f 100644 --- a/examples/custom_font_style/Cargo.toml +++ b/examples/custom_font_style/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["tami5 "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/custom_window_frame/Cargo.toml b/examples/custom_window_frame/Cargo.toml index 1cad5da20..448d8c2bb 100644 --- a/examples/custom_window_frame/Cargo.toml +++ b/examples/custom_window_frame/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/download_image/Cargo.toml b/examples/download_image/Cargo.toml index 0097d9e33..5e4f28e1c 100644 --- a/examples/download_image/Cargo.toml +++ b/examples/download_image/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/download_image/src/main.rs b/examples/download_image/src/main.rs index 2a2c3540e..9a48c8951 100644 --- a/examples/download_image/src/main.rs +++ b/examples/download_image/src/main.rs @@ -58,8 +58,7 @@ fn parse_response(response: ehttp::Response) -> Result { RetainedImage::from_image_bytes(&response.url, &response.bytes) } else { Err(format!( - "Expected image, found content-type {:?}", - content_type + "Expected image, found content-type {content_type:?}" )) } } diff --git a/examples/file_dialog/Cargo.toml b/examples/file_dialog/Cargo.toml index 5447e6815..02045d624 100644 --- a/examples/file_dialog/Cargo.toml +++ b/examples/file_dialog/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml index 9dc377ce9..580fe04e0 100644 --- a/examples/hello_world/Cargo.toml +++ b/examples/hello_world/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/hello_world_par/Cargo.toml b/examples/hello_world_par/Cargo.toml index 9b02345f5..76c1ac88a 100644 --- a/examples/hello_world_par/Cargo.toml +++ b/examples/hello_world_par/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Maxim Osipenko "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/hello_world_par/src/main.rs b/examples/hello_world_par/src/main.rs index b9d03ba6a..8a568cb1f 100644 --- a/examples/hello_world_par/src/main.rs +++ b/examples/hello_world_par/src/main.rs @@ -63,7 +63,7 @@ fn new_worker( ) -> (JoinHandle<()>, mpsc::SyncSender) { let (show_tx, show_rc) = mpsc::sync_channel(0); let handle = std::thread::Builder::new() - .name(format!("EguiPanelWorker {}", thread_nr)) + .name(format!("EguiPanelWorker {thread_nr}")) .spawn(move || { let mut state = ThreadState::new(thread_nr); while let Ok(ctx) = show_rc.recv() { diff --git a/examples/hello_world_simple/Cargo.toml b/examples/hello_world_simple/Cargo.toml index 1c6bcfa40..be399552b 100644 --- a/examples/hello_world_simple/Cargo.toml +++ b/examples/hello_world_simple/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/keyboard_events/Cargo.toml b/examples/keyboard_events/Cargo.toml index 7e544a2e7..39a389f33 100644 --- a/examples/keyboard_events/Cargo.toml +++ b/examples/keyboard_events/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Jose Palazon "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/puffin_profiler/Cargo.toml b/examples/puffin_profiler/Cargo.toml index dbb048fea..90baa1acc 100644 --- a/examples/puffin_profiler/Cargo.toml +++ b/examples/puffin_profiler/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/puffin_profiler/src/main.rs b/examples/puffin_profiler/src/main.rs index 2cfc5e61d..f8d52e91c 100644 --- a/examples/puffin_profiler/src/main.rs +++ b/examples/puffin_profiler/src/main.rs @@ -62,7 +62,7 @@ fn start_puffin_server() { std::mem::forget(puffin_server); } Err(err) => { - eprintln!("Failed to start puffin server: {}", err); + eprintln!("Failed to start puffin server: {err}"); } }; } diff --git a/examples/retained_image/Cargo.toml b/examples/retained_image/Cargo.toml index abbe01fd8..a1df10396 100644 --- a/examples/retained_image/Cargo.toml +++ b/examples/retained_image/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/save_plot/Cargo.toml b/examples/save_plot/Cargo.toml new file mode 100644 index 000000000..2a12692f8 --- /dev/null +++ b/examples/save_plot/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "save_plot" +version = "0.1.0" +authors = ["hacknus "] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.67" +publish = false + +[dependencies] +eframe = { path = "../../crates/eframe", features = [ + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO +] } +image = { version = "0.24", default-features = false, features = ["png"] } +rfd = "0.11.0" +env_logger = "0.10" diff --git a/examples/save_plot/README.md b/examples/save_plot/README.md new file mode 100644 index 000000000..523af1d6b --- /dev/null +++ b/examples/save_plot/README.md @@ -0,0 +1,5 @@ +This example shows that you can save a plot in egui as a png. + +```sh +cargo run -p save_plot +``` diff --git a/examples/save_plot/src/main.rs b/examples/save_plot/src/main.rs new file mode 100644 index 000000000..0d9de35cc --- /dev/null +++ b/examples/save_plot/src/main.rs @@ -0,0 +1,99 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release + +use eframe::egui; +use eframe::egui::plot::{Legend, Line, Plot, PlotPoints}; +use eframe::egui::ColorImage; + +fn main() -> Result<(), eframe::Error> { + env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). + + let options = eframe::NativeOptions { + initial_window_size: Some(egui::vec2(350.0, 400.0)), + ..Default::default() + }; + eframe::run_native( + "My egui App with a plot", + options, + Box::new(|_cc| Box::::default()), + ) +} + +#[derive(Default)] +struct MyApp { + screenshot: Option, +} + +impl eframe::App for MyApp { + fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { + let mut plot_rect = None; + egui::CentralPanel::default().show(ctx, |ui| { + // these are just some dummy variables for the example, + // such that the plot is not at position (0,0) + let height = 200.0; + let border_x = 11.0; + let border_y = 18.0; + let width = 300.0; + + ui.heading("My egui Application"); + + // add some whitespace in y direction + ui.add_space(border_y); + + if ui.button("Save Plot").clicked() { + frame.request_screenshot(); + } + + // add some whitespace in y direction + ui.add_space(border_y); + + ui.horizontal(|ui| { + // add some whitespace in x direction + ui.add_space(border_x); + + let my_plot = Plot::new("My Plot") + .height(height) + .width(width) + .legend(Legend::default()); + + // let's create a dummy line in the plot + let graph: Vec<[f64; 2]> = vec![[0.0, 1.0], [2.0, 3.0], [3.0, 2.0]]; + let inner = my_plot.show(ui, |plot_ui| { + plot_ui.line(Line::new(PlotPoints::from(graph)).name("curve")); + }); + // Remember the position of the plot + plot_rect = Some(inner.response.rect); + }); + + // add some whitespace in y direction + ui.add_space(border_y); + }); + + if let (Some(screenshot), Some(plot_location)) = (self.screenshot.take(), plot_rect) { + if let Some(mut path) = rfd::FileDialog::new().save_file() { + path.set_extension("png"); + + // for a full size application, we should put this in a different thread, + // so that the GUI doesn't lag during saving + + let pixels_per_point = frame.info().native_pixels_per_point; + let plot = screenshot.region(&plot_location, pixels_per_point); + // save the plot to png + image::save_buffer( + &path, + plot.as_raw(), + plot.width() as u32, + plot.height() as u32, + image::ColorType::Rgba8, + ) + .unwrap(); + } + } + } + + fn post_rendering(&mut self, _screen_size_px: [u32; 2], frame: &eframe::Frame) { + // this is inspired by the Egui screenshot example + if let Some(screenshot) = frame.screenshot() { + self.screenshot = Some(screenshot); + } + } +} diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index 692fc3678..3cc5887fb 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -7,7 +7,7 @@ authors = [ ] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/serial_windows/Cargo.toml b/examples/serial_windows/Cargo.toml index 089858dd3..39f5a0222 100644 --- a/examples/serial_windows/Cargo.toml +++ b/examples/serial_windows/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/svg/Cargo.toml b/examples/svg/Cargo.toml index fcf56e49f..219efd58c 100644 --- a/examples/svg/Cargo.toml +++ b/examples/svg/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/user_attention/Cargo.toml b/examples/user_attention/Cargo.toml index 8b4387936..3a7e49a13 100644 --- a/examples/user_attention/Cargo.toml +++ b/examples/user_attention/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["TicClick "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false [dependencies] diff --git a/examples/user_attention/src/main.rs b/examples/user_attention/src/main.rs index 4f7e521d2..8134062fd 100644 --- a/examples/user_attention/src/main.rs +++ b/examples/user_attention/src/main.rs @@ -19,7 +19,7 @@ fn main() -> eframe::Result<()> { } fn repr(attention: UserAttentionType) -> String { - format!("{:?}", attention) + format!("{attention:?}") } struct Application { diff --git a/rust-toolchain b/rust-toolchain index 0e334035e..5698c9e2d 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -5,6 +5,6 @@ # to the user in the error, instead of "error: invalid channel name '[toolchain]'". [toolchain] -channel = "1.65.0" +channel = "1.67.0" components = [ "rustfmt", "clippy" ] targets = [ "wasm32-unknown-unknown" ] diff --git a/scripts/build_demo_web.sh b/scripts/build_demo_web.sh index d77279b7b..37abcffc6 100755 --- a/scripts/build_demo_web.sh +++ b/scripts/build_demo_web.sh @@ -55,7 +55,8 @@ while test $# -gt 0; do ;; *) - break + echo "Unknown option: $1" + exit 1 ;; esac done diff --git a/scripts/clippy_wasm/clippy.toml b/scripts/clippy_wasm/clippy.toml index e2ec8be96..436e1fb84 100644 --- a/scripts/clippy_wasm/clippy.toml +++ b/scripts/clippy_wasm/clippy.toml @@ -3,7 +3,7 @@ # We cannot forbid all these methods in the main `clippy.toml` because of # https://github.com/rust-lang/rust-clippy/issues/10406 -msrv = "1.65" +msrv = "1.67" # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods disallowed-methods = [ diff --git a/scripts/generate_changelog.py b/scripts/generate_changelog.py index 3431bf985..2e97d01b2 100755 --- a/scripts/generate_changelog.py +++ b/scripts/generate_changelog.py @@ -141,6 +141,8 @@ def main() -> None: unsorted_prs = [] unsorted_commits = [] + plot = [] + for commit_info, pr_info in zip(commit_infos, pr_infos): hexsha = commit_info.hexsha title = commit_info.title @@ -164,11 +166,18 @@ def main() -> None: if gh_user_name not in OFFICIAL_DEVS: summary += f" (thanks [@{gh_user_name}](https://github.com/{gh_user_name})!)" + if 'typo' in labels: + continue # We get so many typo PRs. Let's not flood the changelog with them. + added = False - for crate in crate_names: - if crate in labels: - sections.setdefault(crate, []).append(summary) - added = True + if 'plot' in labels: + plot.append(summary) + added = True + else: + for crate in crate_names: + if crate in labels: + sections.setdefault(crate, []).append(summary) + added = True if not added: if not any(label in labels for label in ignore_labels): @@ -179,6 +188,9 @@ def main() -> None: if crate in sections: summary = sections[crate] print_section(crate, summary) + if crate == 'egui': + if 0 < len(plot): + print_section("egui plot", plot) print_section("Unsorted PRs", unsorted_prs) print_section("Unsorted commits", unsorted_commits) diff --git a/scripts/lint.py b/scripts/lint.py new file mode 100755 index 000000000..93851ffa1 --- /dev/null +++ b/scripts/lint.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python +""" +Runs custom linting on Rust code. +""" + +import argparse +import os +import re +import sys + + +def lint_file_path(filepath, args) -> int: + with open(filepath) as f: + lines_in = f.readlines() + + errors, lines_out = lint_lines(filepath, lines_in) + + for error in errors: + print(error) + + if args.fix and lines_in != lines_out: + with open(filepath, 'w') as f: + f.writelines(lines_out) + print(f'{filepath} fixed.') + + return len(errors) + + + +def lint_lines(filepath, lines_in): + last_line_was_empty = True + + errors = [] + lines_out = [] + + for line_nr, line in enumerate(lines_in): + line_nr = line_nr+1 + + # TODO: only # and /// on lines before a keyword + + pattern = r'^\s*((///)|((pub(\(\w*\))? )?((impl|fn|struct|enum|union|trait)\b))).*$' + if re.match(pattern, line): + if not last_line_was_empty: + errors.append( + f'{filepath}:{line_nr}: for readability, add newline before `{line.strip()}`') + lines_out.append("\n") + lines_out.append(line) + + stripped = line.strip() + last_line_was_empty = stripped == '' or \ + stripped.startswith('#') or \ + stripped.startswith('//') or \ + stripped.endswith('{') or \ + stripped.endswith('(') or \ + stripped.endswith('\\') or \ + stripped.endswith('r"') or \ + stripped.endswith(']') + + return errors, lines_out + + +def test_lint(): + should_pass = [ + "hello world", + """ + /// docstring + foo + + /// docstring + bar + """ + ] + + should_fail = [ + """ + /// docstring + foo + /// docstring + bar + """ + ] + + for code in should_pass: + errors, _ = lint_lines("test.py", code.split('\n')) + assert len(errors) == 0, f'expected this to pass:\n{code}\ngot: {errors}' + + for code in should_fail: + errors, _ = lint_lines("test.py", code.split('\n')) + assert len(errors) > 0, f'expected this to fail:\n{code}' + + pass + + +def main(): + test_lint() # Make sure we are bug free before we run! + + parser = argparse.ArgumentParser( + description='Lint Rust code with custom linter.') + parser.add_argument('files', metavar='file', type=str, nargs='*', + help='File paths. Empty = all files, recursively.') + parser.add_argument('--fix', dest='fix', action='store_true', + help='Automatically fix the files') + + args = parser.parse_args() + + num_errors = 0 + + if args.files: + for filepath in args.files: + num_errors += lint_file_path(filepath, args) + else: + script_dirpath = os.path.dirname(os.path.realpath(__file__)) + root_dirpath = os.path.abspath(f'{script_dirpath}/..') + os.chdir(root_dirpath) + + exclude = set(['target', 'target_ra']) + for root, dirs, files in os.walk('.', topdown=True): + dirs[:] = [d for d in dirs if d not in exclude] + for filename in files: + if filename.endswith('.rs'): + filepath = os.path.join(root, filename) + num_errors += lint_file_path(filepath, args) + + if num_errors == 0: + print(f"{sys.argv[0]} finished without error") + sys.exit(0) + else: + print(f"{sys.argv[0]} found {num_errors} errors.") + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/scripts/setup_web.sh b/scripts/setup_web.sh index f8d3d08a0..53896e989 100755 --- a/scripts/setup_web.sh +++ b/scripts/setup_web.sh @@ -7,4 +7,4 @@ cd "$script_path/.." rustup target add wasm32-unknown-unknown # For generating JS bindings: -cargo install wasm-bindgen-cli --version 0.2.86 +cargo install wasm-bindgen-cli --version 0.2.87