diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c00eac197..5578a9fa2 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -9,7 +9,7 @@ Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/ * 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. -Please be patient! I will review you PR, but my time is limited! +Please be patient! I will review your PR, but my time is limited! --> Closes . diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2b952ef6b..167d18dbb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -96,7 +96,7 @@ jobs: toolchain: 1.70.0 targets: wasm32-unknown-unknown - - run: sudo apt-get update && sudo apt-get install libgtk-3-dev + - run: sudo apt-get update && sudo apt-get install libgtk-3-dev libatk1.0-dev - name: Set up cargo cache uses: Swatinem/rust-cache@v2 diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index d3f6e10e7..c9f314f7d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -5,7 +5,7 @@ Also see [`CONTRIBUTING.md`](CONTRIBUTING.md) for what to do before opening a PR ## Crate overview -The crates in this repository are: `egui, emath, epaint, egui_extras, egui_plot, egui-winit, egui_glium, egui_glow, egui_demo_lib, egui_demo_app`. +The crates in this repository are: `egui, emath, epaint, egui_extras, egui_plot, egui-winit, egui_glow, egui_demo_lib, egui_demo_app`. ### `egui`: The main GUI library. Example code: `if ui.button("Click me").clicked() { … }` @@ -32,9 +32,6 @@ This crates provides bindings between [`egui`](https://github.com/emilk/egui) an The library translates winit events to egui, handled copy/paste, updates the cursor, open links clicked in egui, etc. -### `egui_glium` -Puts an egui app inside a native window on your laptop. Paints the triangles that egui outputs using [glium](https://github.com/glium/glium). - ### `egui_glow` Puts an egui app inside a native window on your laptop. Paints the triangles that egui outputs using [glow](https://github.com/grovesNL/glow). diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e4da4186..9e6d04471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # egui changelog All notable changes to the `egui` crate will be documented in this file. -NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`egui_plot`](crates/egui_plot/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! +NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`egui_plot`](crates/egui_plot/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG.md), [`egui-winit`](crates/egui-winit/CHANGELOG.md), [`egui_glow`](crates/egui_glow/CHANGELOG.md) and [`egui-wgpu`](crates/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. diff --git a/Cargo.lock b/Cargo.lock index 2de9bb840..fdaa5e915 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,9 +20,9 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "accesskit" -version = "0.11.2" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76eb1adf08c5bcaa8490b9851fd53cca27fa9880076f178ea9d29f05196728a8" +checksum = "b0cc53b7e5d8f45ebe687178cf91af0f45fdba6e78fedf94f0269c5be5b9f296" dependencies = [ "enumn", "serde", @@ -30,18 +30,18 @@ dependencies = [ [[package]] name = "accesskit_consumer" -version = "0.15.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04bb4d9e4772fe0d47df57d0d5dbe5d85dd05e2f37ae1ddb6b105e76be58fb00" +checksum = "39dfcfd32eb0c1b525daaf4b02adcd2fa529c22cd713491e15bf002a01a714f5" dependencies = [ "accesskit", ] [[package]] name = "accesskit_macos" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134d0acf6acb667c89d3332999b1a5df4edbc8d6113910f392ebb73f2b03bb56" +checksum = "89c7e8406319ac3149d7b59983637984f0864bbf738319b1c443976268b6426c" dependencies = [ "accesskit", "accesskit_consumer", @@ -51,38 +51,40 @@ dependencies = [ [[package]] name = "accesskit_unix" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e084cb5168790c0c112626175412dc5ad127083441a8248ae49ddf6725519e83" +checksum = "0b0c84552a7995c981d5f22e2d4b24ba9a55718bb12fba883506d6d7344acaf1" dependencies = [ "accesskit", "accesskit_consumer", "async-channel", + "async-once-cell", "atspi", "futures-lite", + "once_cell", "serde", "zbus", ] [[package]] name = "accesskit_windows" -version = "0.14.3" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eac0a7f2d7cd7a93b938af401d3d8e8b7094217989a7c25c55a953023436e31" +checksum = "314d4a797fc82d182b04f4f0665a368924fb556ad9557fccd2d39d38dc8c1c1b" dependencies = [ "accesskit", "accesskit_consumer", - "arrayvec", "once_cell", "paste", + "static_assertions", "windows 0.48.0", ] [[package]] name = "accesskit_winit" -version = "0.14.4" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "825d23acee1bd6d25cbaa3ca6ed6e73faf24122a774ec33d52c5c86c6ab423c0" +checksum = "88e39fcec2e10971e188730b7a76bab60647dacc973d4591855ebebcadfaa738" dependencies = [ "accesskit", "accesskit_macos", @@ -284,7 +286,7 @@ dependencies = [ "log", "parking", "polling", - "rustix 0.37.23", + "rustix 0.37.25", "slab", "socket2", "waker-fn", @@ -299,6 +301,12 @@ dependencies = [ "event-listener 2.5.3", ] +[[package]] +name = "async-once-cell" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9338790e78aa95a416786ec8389546c4b6a1dfc3dc36071ed9518a9413a542eb" + [[package]] name = "async-process" version = "1.8.0" @@ -383,29 +391,50 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "atspi" -version = "0.10.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "674e7a3376837b2e7d12d34d58ac47073c491dc3bf6f71a7adaf687d4d817faa" +checksum = "6059f350ab6f593ea00727b334265c4dfc7fd442ee32d264794bd9bdc68e87ca" dependencies = [ - "async-recursion", - "async-trait", - "atspi-macros", - "enumflags2", - "futures-lite", - "serde", - "tracing", - "zbus", - "zbus_names", + "atspi-common", + "atspi-connection", + "atspi-proxies", ] [[package]] -name = "atspi-macros" -version = "0.2.0" +name = "atspi-common" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb4870a32c0eaa17e35bca0e6b16020635157121fb7d45593d242c295bc768" +checksum = "92af95f966d2431f962bc632c2e68eda7777330158bf640c4af4249349b2cdf5" dependencies = [ - "quote", - "syn 1.0.109", + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-connection" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c65e7d70f86d4c0e3b2d585d9bf3f979f0b19d635a336725a88d279f76b939" +dependencies = [ + "atspi-common", + "atspi-proxies", + "futures-lite", + "zbus", +] + +[[package]] +name = "atspi-proxies" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6495661273703e7a229356dcbe8c8f38223d697aacfaf0e13590a9ac9977bb52" +dependencies = [ + "atspi-common", + "serde", + "zbus", ] [[package]] @@ -991,7 +1020,7 @@ dependencies = [ "eframe", "egui_glow", "env_logger", - "glow", + "glow 0.12.3", ] [[package]] @@ -1198,7 +1227,7 @@ dependencies = [ "egui-wgpu", "egui-winit", "egui_glow", - "glow", + "glow 0.12.3", "glutin", "glutin-winit", "image", @@ -1338,7 +1367,7 @@ dependencies = [ "document-features", "egui", "egui-winit", - "glow", + "glow 0.12.3", "glutin", "glutin-winit", "log", @@ -1585,6 +1614,18 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1754,8 +1795,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1816,6 +1859,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "glow" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "886c2a30b160c4c6fec8f987430c26b526b7988ca71f664e6a699ddf6f9601e4" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "glutin" version = "0.30.10" @@ -1829,7 +1884,7 @@ dependencies = [ "dispatch", "glutin_egl_sys", "glutin_glx_sys", - "glutin_wgl_sys", + "glutin_wgl_sys 0.4.0", "libloading 0.7.4", "objc2", "once_cell", @@ -1880,6 +1935,15 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "glutin_wgl_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +dependencies = [ + "gl_generator", +] + [[package]] name = "gobject-sys" version = "0.16.3" @@ -1912,15 +1976,16 @@ dependencies = [ [[package]] name = "gpu-allocator" -version = "0.22.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce95f9e2e11c2c6fadfce42b5af60005db06576f231f5c92550fdded43c423e8" +checksum = "40fe17c8a05d60c38c0a4e5a3c802f2f1ceb66b76c67d96ffb34bef0475a7fad" dependencies = [ "backtrace", "log", + "presser", "thiserror", "winapi", - "windows 0.44.0", + "windows 0.51.1", ] [[package]] @@ -2266,12 +2331,12 @@ dependencies = [ [[package]] name = "khronos-egl" -version = "4.1.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.7.4", + "libloading 0.8.0", "pkg-config", ] @@ -2427,9 +2492,9 @@ dependencies = [ [[package]] name = "metal" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "623b5e6cefd76e58f774bd3cc0c6f5c7615c58c03a97815245a25c3c9bdee318" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" dependencies = [ "bitflags 2.4.0", "block", @@ -2500,15 +2565,15 @@ dependencies = [ [[package]] name = "naga" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ceaaa4eedaece7e4ec08c55c640ba03dbb73fb812a6570a59bcf1930d0f70e" +checksum = "61d829abac9f5230a85d8cc83ec0879b4c09790208ae25b5ea031ef84562e071" dependencies = [ "bit-set", "bitflags 2.4.0", "codespan-reporting", "hexf-parse", - "indexmap 1.9.3", + "indexmap 2.0.0", "log", "num-traits", "rustc-hash", @@ -2518,6 +2583,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + [[package]] name = "ndk" version = "0.7.0" @@ -2965,6 +3039,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "prettyplease" version = "0.2.15" @@ -3226,7 +3306,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted", "web-sys", "winapi", @@ -3267,9 +3347,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.37.23" +version = "0.37.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +checksum = "d4eb579851244c2c03e7c24f501c3432bed80b8f720af1d6e5b0e0f01555a035" dependencies = [ "bitflags 1.3.2", "errno", @@ -3585,6 +3665,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spirv" version = "0.2.0+1.5.4" @@ -4294,12 +4383,13 @@ checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "wgpu" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7472f3b69449a8ae073f6ec41d05b6f846902d92a6c45313c50cb25857b736ce" +checksum = "30e7d227c9f961f2061c26f4cb0fbd4df0ef37e056edd0931783599d6c94ef24" dependencies = [ "arrayvec", "cfg-if", + "flume", "js-sys", "log", "naga", @@ -4318,9 +4408,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecf7454d9386f602f7399225c92dd2fbdcde52c519bc8fb0bd6fbeb388075dc2" +checksum = "837e02ddcdc6d4a9b56ba4598f7fd4202a7699ab03f6ef4dcdebfad2c966aea6" dependencies = [ "arrayvec", "bit-vec", @@ -4341,9 +4431,9 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6654a13885a17f475e8324efb46dc6986d7aaaa98353330f8de2077b153d0101" +checksum = "1e30b9a8155c83868e82a8c5d3ce899de6c3961d2ef595de8fc168a1677fc2d8" dependencies = [ "android_system_properties", "arrayvec", @@ -4353,7 +4443,8 @@ dependencies = [ "block", "core-graphics-types", "d3d12", - "glow", + "glow 0.13.0", + "glutin_wgl_sys 0.5.0", "gpu-alloc", "gpu-allocator", "gpu-descriptor", @@ -4366,6 +4457,7 @@ dependencies = [ "metal", "naga", "objc", + "once_cell", "parking_lot", "profiling", "range-alloc", @@ -4382,9 +4474,9 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee64d7398d0c2f9ca48922c902ef69c42d000c759f3db41e355f4a570b052b67" +checksum = "0d5ed5f0edf0de351fe311c53304986315ce866f394a2e6df0c4b3c70774bcdd" dependencies = [ "bitflags 2.4.0", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index bda52fdaf..2391820a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,4 +37,6 @@ opt-level = 2 [workspace.dependencies] thiserror = "1.0.37" -wgpu = "0.17.0" +wgpu = "0.18.0" +# Use this to build wgpu with WebGL support on the Web *instead* of using WebGPU. +#wgpu = { version = "0.18.0", features = ["webgl"] } diff --git a/README.md b/README.md index 520304b14..59d63e165 100644 --- a/README.md +++ b/README.md @@ -201,13 +201,13 @@ These are the official egui integrations: * [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow) for rendering egui with [glow](https://github.com/grovesNL/glow) on native and web, and for making native apps. * [`egui-wgpu`](https://github.com/emilk/egui/tree/master/crates/egui-wgpu) for [wgpu](https://crates.io/crates/wgpu) (WebGPU API). * [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit) for integrating with [winit](https://github.com/rust-windowing/winit). -* [`egui_glium`](https://github.com/emilk/egui/tree/master/crates/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium) (DEPRECATED - looking for new maintainer). ### 3rd party integrations * [`amethyst_egui`](https://github.com/jgraef/amethyst_egui) for [the Amethyst game engine](https://amethyst.rs/). * [`bevy_egui`](https://github.com/mvlabat/bevy_egui) for [the Bevy game engine](https://bevyengine.org/). * [`egui_glfw_gl`](https://github.com/cohaereo/egui_glfw_gl) for [GLFW](https://crates.io/crates/glfw). +* [`egui_glium`](https://github.com/fayalalebrun/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium). * [`egui-glutin-gl`](https://github.com/h3r2tic/egui-glutin-gl/) for [glutin](https://crates.io/crates/glutin). * [`egui_sdl2_gl`](https://crates.io/crates/egui_sdl2_gl) for [SDL2](https://crates.io/crates/sdl2). * [`egui_sdl2_platform`](https://github.com/ComLarsic/egui_sdl2_platform) for [SDL2](https://crates.io/crates/sdl2). @@ -341,7 +341,6 @@ You can also render your 3D scene to a texture and display it using [`ui.image( Examples: * Using [`egui-miniquad`]( https://github.com/not-fl3/egui-miniquad): https://github.com/not-fl3/egui-miniquad/blob/master/examples/render_to_egui_image.rs -* Using [`egui_glium`](https://github.com/emilk/egui/tree/master/crates/egui_glium): . ## Other diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 8865574f4..798efdba7 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -77,7 +77,7 @@ impl WebPainterWgpu { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: options.wgpu_options.supported_backends, - dx12_shader_compiler: Default::default(), + ..Default::default() }); let canvas = super::canvas_element_or_die(canvas_id); @@ -237,7 +237,7 @@ impl WebPainter for WebPainterWgpu { b: clear_color[2] as f64, a: clear_color[3] as f64, }), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| { @@ -245,12 +245,16 @@ impl WebPainter for WebPainterWgpu { view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), - store: false, + // It is very unlikely that the depth buffer is needed after egui finished rendering + // so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon) + store: wgpu::StoreOp::Discard, }), stencil_ops: None, } }), label: Some("egui_render"), + occlusion_query_set: None, + timestamp_writes: None, }); renderer.render(&mut render_pass, clipped_primitives, &screen_descriptor); diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 47b14b205..3f939f4a8 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -111,7 +111,7 @@ impl Painter { ) -> Self { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: configuration.supported_backends, - dx12_shader_compiler: Default::default(), + ..Default::default() }); Self { @@ -566,6 +566,7 @@ impl Painter { }); let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("egui_render"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, resolve_target, @@ -576,7 +577,7 @@ impl Painter { b: clear_color[2] as f64, a: clear_color[3] as f64, }), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: self.depth_texture_view.get(&viewport_id).map(|view| { @@ -584,12 +585,15 @@ impl Painter { view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), - store: true, + // It is very unlikely that the depth buffer is needed after egui finished rendering + // so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon) + store: wgpu::StoreOp::Discard, }), stencil_ops: None, } }), - label: Some("egui_render"), + timestamp_writes: None, + occlusion_query_set: None, }); renderer.render(&mut render_pass, clipped_primitives, &screen_descriptor); diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index 4a13d23a8..8c5550def 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -66,7 +66,7 @@ winit = { version = "0.28", default-features = false } #! ### Optional dependencies # feature accesskit -accesskit_winit = { version = "0.14.0", optional = true } +accesskit_winit = { version = "0.15.0", optional = true } ## Enable this when generating docs. document-features = { version = "0.2", optional = true } diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index 349f0849a..5a6506125 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -78,14 +78,14 @@ unity = ["epaint/unity"] [dependencies] epaint = { version = "0.23.0", path = "../epaint", default-features = false } -ahash = { version = "0.8.1", default-features = false, features = [ +ahash = { version = "0.8.6", default-features = false, features = [ "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead "std", ] } nohash-hasher = "0.2" #! ### Optional dependencies -accesskit = { version = "0.11", optional = true } +accesskit = { version = "0.12", optional = true } backtrace = { version = "0.3", optional = true } diff --git a/crates/egui/src/animation_manager.rs b/crates/egui/src/animation_manager.rs index be1815079..b7b7d18be 100644 --- a/crates/egui/src/animation_manager.rs +++ b/crates/egui/src/animation_manager.rs @@ -25,7 +25,7 @@ struct ValueAnim { } impl AnimationManager { - /// See `Context::animate_bool` for documentation + /// See [`crate::Context::animate_bool`] for documentation pub fn animate_bool( &mut self, input: &InputState, diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 5614df1ef..cf078c861 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -155,7 +155,7 @@ impl Area { self } - /// Constraint the movement of the window to the given rectangle. + /// Constrain the movement of the window to the given rectangle. /// /// For instance: `.constrain_to(ctx.screen_rect())`. pub fn constrain_to(mut self, constrain_rect: Rect) -> Self { diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 6ce5cbc40..e92269751 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -127,8 +127,8 @@ impl Frame { } #[inline] - pub fn stroke(mut self, stroke: Stroke) -> Self { - self.stroke = stroke; + pub fn stroke(mut self, stroke: impl Into) -> Self { + self.stroke = stroke.into(); self } diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 9a82fce2b..8b92a945c 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -13,11 +13,11 @@ pub(crate) struct TooltipState { impl TooltipState { pub fn load(ctx: &Context) -> Option { - ctx.data_mut(|d| d.get_temp(Id::null())) + ctx.data_mut(|d| d.get_temp(Id::NULL)) } fn store(self, ctx: &Context) { - ctx.data_mut(|d| d.insert_temp(Id::null(), self)); + ctx.data_mut(|d| d.insert_temp(Id::NULL, self)); } fn individual_tooltip_size(&self, common_id: Id, index: usize) -> Option { diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index 789172342..7274b6c5a 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -337,10 +337,16 @@ pub fn paint_resize_corner(ui: &Ui, response: &Response) { paint_resize_corner_with_style(ui, &response.rect, stroke, Align2::RIGHT_BOTTOM); } -pub fn paint_resize_corner_with_style(ui: &Ui, rect: &Rect, stroke: Stroke, corner: Align2) { +pub fn paint_resize_corner_with_style( + ui: &Ui, + rect: &Rect, + stroke: impl Into, + corner: Align2, +) { let painter = ui.painter(); let cp = painter.round_pos_to_pixels(corner.pos_in_rect(rect)); let mut w = 2.0; + let stroke = stroke.into(); while w <= rect.width() && w <= rect.height() { painter.line_segment( diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 0c2cead70..e50041119 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -1,8 +1,3 @@ -//! Coordinate system names: -//! * content: size of contents (generally large; that's why we want scroll bars) -//! * outer: size of scroll area including scroll bar(s) -//! * inner: excluding scroll bar(s). The area we clip the contents to. - #![allow(clippy::needless_range_loop)] use crate::*; @@ -15,10 +10,13 @@ pub struct State { pub offset: Vec2, /// Were the scroll bars visible last frame? - show_scroll: [bool; 2], + show_scroll: Vec2b, /// The content were to large to fit large frame. - content_is_too_large: [bool; 2], + content_is_too_large: Vec2b, + + /// Did the user interact (hover or drag) the scroll bars last frame? + scroll_bar_interaction: Vec2b, /// Momentum, used for kinetic scrolling #[cfg_attr(feature = "serde", serde(skip))] @@ -30,18 +28,19 @@ pub struct State { /// Is the scroll sticky. This is true while scroll handle is in the end position /// and remains that way until the user moves the scroll_handle. Once unstuck (false) /// it remains false until the scroll touches the end position, which reenables stickiness. - scroll_stuck_to_end: [bool; 2], + scroll_stuck_to_end: Vec2b, } impl Default for State { fn default() -> Self { Self { offset: Vec2::ZERO, - show_scroll: [false; 2], - content_is_too_large: [false; 2], + show_scroll: Vec2b::FALSE, + content_is_too_large: Vec2b::FALSE, + scroll_bar_interaction: Vec2b::FALSE, vel: Vec2::ZERO, scroll_start_offset_from_top_left: [None; 2], - scroll_stuck_to_end: [true; 2], + scroll_stuck_to_end: Vec2b::TRUE, } } } @@ -80,15 +79,61 @@ pub struct ScrollAreaOutput { } /// Indicate whether the horizontal and vertical scroll bars must be always visible, hidden or visible when needed. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum ScrollBarVisibility { - AlwaysVisible, - VisibleWhenNeeded, + /// Hide scroll bar even if they are needed. + /// + /// You can still scroll, with the scroll-wheel + /// and by dragging the contents, but there is no + /// visual indication of how far you have scrolled. AlwaysHidden, + + /// Show scroll bars only when the content size exceeds the container, + /// i.e. when there is any need to scroll. + /// + /// This is the default. + VisibleWhenNeeded, + + /// Always show the scroll bar, even if the contents fit in the container + /// and there is no need to scroll. + AlwaysVisible, +} + +impl Default for ScrollBarVisibility { + #[inline] + fn default() -> Self { + Self::VisibleWhenNeeded + } +} + +impl ScrollBarVisibility { + pub const ALL: [Self; 3] = [ + Self::AlwaysHidden, + Self::VisibleWhenNeeded, + Self::AlwaysVisible, + ]; } /// Add vertical and/or horizontal scrolling to a contained [`Ui`]. /// +/// By default, scroll bars only show up when needed, i.e. when the contents +/// is larger than the container. +/// This is controlled by [`Self::scroll_bar_visibility`]. +/// +/// There are two flavors of scroll areas: solid and floating. +/// Solid scroll bars use up space, reducing the amount of space available +/// to the contents. Floating scroll bars float on top of the contents, covering it. +/// You can change the scroll style by changing the [`crate::style::Spacing::scroll`]. +/// +/// ### Coordinate system +/// * content: size of contents (generally large; that's why we want scroll bars) +/// * outer: size of scroll area including scroll bar(s) +/// * inner: excluding scroll bar(s). The area we clip the contents to. +/// +/// If the floating scroll bars settings is turned on then `inner == outer`. +/// +/// ## Example /// ``` /// # egui::__run_test_ui(|ui| { /// egui::ScrollArea::vertical().show(ui, |ui| { @@ -101,9 +146,10 @@ pub enum ScrollBarVisibility { #[derive(Clone, Debug)] #[must_use = "You should call .show()"] pub struct ScrollArea { - /// Do we have horizontal/vertical scrolling? - has_bar: [bool; 2], - auto_shrink: [bool; 2], + /// Do we have horizontal/vertical scrolling enabled? + scroll_enabled: Vec2b, + + auto_shrink: Vec2b, max_size: Vec2, min_scrolled_size: Vec2, scroll_bar_visibility: ScrollBarVisibility, @@ -118,46 +164,50 @@ pub struct ScrollArea { /// If true for vertical or horizontal the scroll wheel will stick to the /// end position until user manually changes position. It will become true /// again once scroll handle makes contact with end. - stick_to_end: [bool; 2], + stick_to_end: Vec2b, } impl ScrollArea { /// Create a horizontal scroll area. + #[inline] pub fn horizontal() -> Self { Self::new([true, false]) } /// Create a vertical scroll area. + #[inline] pub fn vertical() -> Self { Self::new([false, true]) } /// Create a bi-directional (horizontal and vertical) scroll area. + #[inline] pub fn both() -> Self { Self::new([true, true]) } /// Create a scroll area where both direction of scrolling is disabled. /// It's unclear why you would want to do this. + #[inline] pub fn neither() -> Self { Self::new([false, false]) } /// Create a scroll area where you decide which axis has scrolling enabled. /// For instance, `ScrollArea::new([true, false])` enables horizontal scrolling. - pub fn new(has_bar: [bool; 2]) -> Self { + pub fn new(scroll_enabled: impl Into) -> Self { Self { - has_bar, - auto_shrink: [true; 2], + scroll_enabled: scroll_enabled.into(), + auto_shrink: Vec2b::TRUE, max_size: Vec2::INFINITY, min_scrolled_size: Vec2::splat(64.0), - scroll_bar_visibility: ScrollBarVisibility::VisibleWhenNeeded, + scroll_bar_visibility: Default::default(), id_source: None, offset_x: None, offset_y: None, scrolling_enabled: true, drag_to_scroll: true, - stick_to_end: [false; 2], + stick_to_end: Vec2b::FALSE, } } @@ -166,6 +216,7 @@ impl ScrollArea { /// Use `f32::INFINITY` if you want the scroll area to expand to fit the surrounding [`Ui`] (default). /// /// See also [`Self::auto_shrink`]. + #[inline] pub fn max_width(mut self, max_width: f32) -> Self { self.max_size.x = max_width; self @@ -176,6 +227,7 @@ impl ScrollArea { /// Use `f32::INFINITY` if you want the scroll area to expand to fit the surrounding [`Ui`] (default). /// /// See also [`Self::auto_shrink`]. + #[inline] pub fn max_height(mut self, max_height: f32) -> Self { self.max_size.y = max_height; self @@ -187,6 +239,7 @@ impl ScrollArea { /// (and so we don't require scroll bars). /// /// Default: `64.0`. + #[inline] pub fn min_scrolled_width(mut self, min_scrolled_width: f32) -> Self { self.min_scrolled_size.x = min_scrolled_width; self @@ -198,6 +251,7 @@ impl ScrollArea { /// (and so we don't require scroll bars). /// /// Default: `64.0`. + #[inline] pub fn min_scrolled_height(mut self, min_scrolled_height: f32) -> Self { self.min_scrolled_size.y = min_scrolled_height; self @@ -206,12 +260,14 @@ impl ScrollArea { /// Set the visibility of both horizontal and vertical scroll bars. /// /// With `ScrollBarVisibility::VisibleWhenNeeded` (default), the scroll bar will be visible only when needed. + #[inline] pub fn scroll_bar_visibility(mut self, scroll_bar_visibility: ScrollBarVisibility) -> Self { self.scroll_bar_visibility = scroll_bar_visibility; self } /// A source for the unique [`Id`], e.g. `.id_source("second_scroll_area")` or `.id_source(loop_index)`. + #[inline] pub fn id_source(mut self, id_source: impl std::hash::Hash) -> Self { self.id_source = Some(Id::new(id_source)); self @@ -224,6 +280,7 @@ impl ScrollArea { /// See also: [`Self::vertical_scroll_offset`], [`Self::horizontal_scroll_offset`], /// [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and /// [`Response::scroll_to_me`](crate::Response::scroll_to_me) + #[inline] pub fn scroll_offset(mut self, offset: Vec2) -> Self { self.offset_x = Some(offset.x); self.offset_y = Some(offset.y); @@ -236,6 +293,7 @@ impl ScrollArea { /// /// See also: [`Self::scroll_offset`], [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and /// [`Response::scroll_to_me`](crate::Response::scroll_to_me) + #[inline] pub fn vertical_scroll_offset(mut self, offset: f32) -> Self { self.offset_y = Some(offset); self @@ -247,26 +305,30 @@ impl ScrollArea { /// /// See also: [`Self::scroll_offset`], [`Ui::scroll_to_cursor`](crate::ui::Ui::scroll_to_cursor) and /// [`Response::scroll_to_me`](crate::Response::scroll_to_me) + #[inline] pub fn horizontal_scroll_offset(mut self, offset: f32) -> Self { self.offset_x = Some(offset); self } /// Turn on/off scrolling on the horizontal axis. + #[inline] pub fn hscroll(mut self, hscroll: bool) -> Self { - self.has_bar[0] = hscroll; + self.scroll_enabled[0] = hscroll; self } /// Turn on/off scrolling on the vertical axis. + #[inline] pub fn vscroll(mut self, vscroll: bool) -> Self { - self.has_bar[1] = vscroll; + self.scroll_enabled[1] = vscroll; self } /// Turn on/off scrolling on the horizontal/vertical axes. - pub fn scroll2(mut self, has_bar: [bool; 2]) -> Self { - self.has_bar = has_bar; + #[inline] + pub fn scroll2(mut self, scroll_enabled: impl Into) -> Self { + self.scroll_enabled = scroll_enabled.into(); self } @@ -279,6 +341,7 @@ impl ScrollArea { /// is typing text in a [`TextEdit`] widget contained within the scroll area. /// /// This controls both scrolling directions. + #[inline] pub fn enable_scrolling(mut self, enable: bool) -> Self { self.scrolling_enabled = enable; self @@ -291,6 +354,7 @@ impl ScrollArea { /// If `true`, the [`ScrollArea`] will sense drags. /// /// Default: `true`. + #[inline] pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self { self.drag_to_scroll = drag_to_scroll; self @@ -301,14 +365,16 @@ impl ScrollArea { /// * If `true`, egui will add blank space outside the scroll area. /// * If `false`, egui will add blank space inside the scroll area. /// - /// Default: `[true; 2]`. - pub fn auto_shrink(mut self, auto_shrink: [bool; 2]) -> Self { - self.auto_shrink = auto_shrink; + /// Default: `true`. + #[inline] + pub fn auto_shrink(mut self, auto_shrink: impl Into) -> Self { + self.auto_shrink = auto_shrink.into(); self } - pub(crate) fn has_any_bar(&self) -> bool { - self.has_bar[0] || self.has_bar[1] + /// Is any scrolling enabled? + pub(crate) fn is_any_scroll_enabled(&self) -> bool { + self.scroll_enabled[0] || self.scroll_enabled[1] } /// The scroll handle will stick to the rightmost position even while the content size @@ -317,6 +383,7 @@ impl ScrollArea { /// it will remain focused on whatever content viewport the user left it on. If the scroll /// handle is dragged all the way to the right it will again become stuck and remain there /// until manually pulled from the end position. + #[inline] pub fn stick_to_right(mut self, stick: bool) -> Self { self.stick_to_end[0] = stick; self @@ -328,6 +395,7 @@ impl ScrollArea { /// it will remain focused on whatever content viewport the user left it on. If the scroll /// handle is dragged to the bottom it will again become stuck and remain there until manually /// pulled from the end position. + #[inline] pub fn stick_to_bottom(mut self, stick: bool) -> Self { self.stick_to_end[1] = stick; self @@ -337,11 +405,24 @@ impl ScrollArea { struct Prepared { id: Id, state: State, - has_bar: [bool; 2], - auto_shrink: [bool; 2], + + auto_shrink: Vec2b, + + /// Does this `ScrollArea` have horizontal/vertical scrolling enabled? + scroll_enabled: Vec2b, + + /// Smoothly interpolated boolean of whether or not to show the scroll bars. + show_bars_factor: Vec2, /// How much horizontal and vertical space are used up by the /// width of the vertical bar, and the height of the horizontal bar? + /// + /// This is always zero for floating scroll bars. + /// + /// Note that this is a `yx` swizzling of [`Self::show_bars_factor`] + /// times the maximum bar with. + /// That's because horizontal scroll uses up vertical space, + /// and vice versa. current_bar_use: Vec2, scroll_bar_visibility: ScrollBarVisibility, @@ -356,13 +437,13 @@ struct Prepared { viewport: Rect, scrolling_enabled: bool, - stick_to_end: [bool; 2], + stick_to_end: Vec2b, } impl ScrollArea { fn begin(self, ui: &mut Ui) -> Prepared { let Self { - has_bar, + scroll_enabled, auto_shrink, max_size, min_scrolled_size, @@ -379,7 +460,7 @@ impl ScrollArea { let id_source = id_source.unwrap_or_else(|| Id::new("scroll_area")); let id = ui.make_persistent_id(id_source); - ui.ctx().check_for_id_clash( + ctx.check_for_id_clash( id, Rect::from_min_size(ui.available_rect_before_wrap().min, Vec2::ZERO), "ScrollArea", @@ -389,25 +470,18 @@ impl ScrollArea { state.offset.x = offset_x.unwrap_or(state.offset.x); state.offset.y = offset_y.unwrap_or(state.offset.y); - let max_scroll_bar_width = max_scroll_bar_width_with_margin(ui); - - let current_hscroll_bar_height = if !has_bar[0] { - 0.0 - } else if scroll_bar_visibility == ScrollBarVisibility::AlwaysVisible { - max_scroll_bar_width - } else { - max_scroll_bar_width * ui.ctx().animate_bool(id.with("h"), state.show_scroll[0]) + let show_bars: Vec2b = match scroll_bar_visibility { + ScrollBarVisibility::AlwaysHidden => Vec2b::FALSE, + ScrollBarVisibility::VisibleWhenNeeded => state.show_scroll, + ScrollBarVisibility::AlwaysVisible => scroll_enabled, }; - let current_vscroll_bar_width = if !has_bar[1] { - 0.0 - } else if scroll_bar_visibility == ScrollBarVisibility::AlwaysVisible { - max_scroll_bar_width - } else { - max_scroll_bar_width * ui.ctx().animate_bool(id.with("v"), state.show_scroll[1]) - }; + let show_bars_factor = Vec2::new( + ctx.animate_bool(id.with("h"), show_bars[0]), + ctx.animate_bool(id.with("v"), show_bars[1]), + ); - let current_bar_use = vec2(current_vscroll_bar_width, current_hscroll_bar_height); + let current_bar_use = show_bars_factor.yx() * ui.spacing().scroll.allocated_width(); let available_outer = ui.available_rect_before_wrap(); @@ -421,7 +495,7 @@ impl ScrollArea { // one shouldn't collapse into nothingness. // See https://github.com/emilk/egui/issues/1097 for d in 0..2 { - if has_bar[d] { + if scroll_enabled[d] { inner_size[d] = inner_size[d].max(min_scrolled_size[d]); } } @@ -438,7 +512,7 @@ impl ScrollArea { } else { // Tell the inner Ui to use as much space as possible, we can scroll to see it! for d in 0..2 { - if has_bar[d] { + if scroll_enabled[d] { content_max_size[d] = f32::INFINITY; } } @@ -452,7 +526,7 @@ impl ScrollArea { let clip_rect_margin = ui.visuals().clip_rect_margin; let mut content_clip_rect = ui.clip_rect(); for d in 0..2 { - if has_bar[d] { + if scroll_enabled[d] { if state.content_is_too_large[d] { content_clip_rect.min[d] = inner_rect.min[d] - clip_rect_margin; content_clip_rect.max[d] = inner_rect.max[d] + clip_rect_margin; @@ -479,7 +553,7 @@ impl ScrollArea { if content_response.dragged() { for d in 0..2 { - if has_bar[d] { + if scroll_enabled[d] { ui.input(|input| { state.offset[d] -= input.pointer.delta()[d]; state.vel[d] = input.pointer.velocity()[d]; @@ -502,7 +576,7 @@ impl ScrollArea { // Offset has an inverted coordinate system compared to // the velocity, so we subtract it instead of adding it state.offset -= state.vel * dt; - ui.ctx().request_repaint(); + ctx.request_repaint(); } } } @@ -510,8 +584,9 @@ impl ScrollArea { Prepared { id, state, - has_bar, auto_shrink, + scroll_enabled, + show_bars_factor, current_bar_use, scroll_bar_visibility, inner_rect, @@ -621,9 +696,10 @@ impl Prepared { id, mut state, inner_rect, - has_bar, auto_shrink, - mut current_bar_use, + scroll_enabled, + mut show_bars_factor, + current_bar_use, scroll_bar_visibility, content_ui, viewport: _, @@ -634,7 +710,7 @@ impl Prepared { let content_size = content_ui.min_size(); for d in 0..2 { - if has_bar[d] { + if scroll_enabled[d] { // We take the scroll target so only this ScrollArea will use it: let scroll_target = content_ui .ctx() @@ -680,7 +756,7 @@ impl Prepared { let mut inner_size = inner_rect.size(); for d in 0..2 { - inner_size[d] = match (has_bar[d], auto_shrink[d]) { + inner_size[d] = match (scroll_enabled[d], auto_shrink[d]) { (true, true) => inner_size[d].min(content_size[d]), // shrink scroll area if content is small (true, false) => inner_size[d], // let scroll area be larger than content; fill with blank space (false, true) => content_size[d], // Follow the content (expand/contract to fit it). @@ -693,15 +769,16 @@ impl Prepared { let outer_rect = Rect::from_min_size(inner_rect.min, inner_rect.size() + current_bar_use); - let content_is_too_large = [ - content_size.x > inner_rect.width(), - content_size.y > inner_rect.height(), - ]; + let content_is_too_large = Vec2b::new( + scroll_enabled[0] && inner_rect.width() < content_size.x, + scroll_enabled[1] && inner_rect.height() < content_size.y, + ); let max_offset = content_size - inner_rect.size(); - if scrolling_enabled && ui.rect_contains_pointer(outer_rect) { + let is_hovering_outer_rect = ui.rect_contains_pointer(outer_rect); + if scrolling_enabled && is_hovering_outer_rect { for d in 0..2 { - if has_bar[d] { + if scroll_enabled[d] { let scroll_delta = ui.ctx().frame_state(|fs| fs.scroll_delta); let scrolling_up = state.offset[d] > 0.0 && scroll_delta[d] > 0.0; @@ -718,39 +795,67 @@ impl Prepared { } let show_scroll_this_frame = match scroll_bar_visibility { - ScrollBarVisibility::AlwaysVisible => [true, true], - ScrollBarVisibility::VisibleWhenNeeded => { - [content_is_too_large[0], content_is_too_large[1]] - } - ScrollBarVisibility::AlwaysHidden => [false, false], + ScrollBarVisibility::AlwaysHidden => Vec2b::FALSE, + ScrollBarVisibility::VisibleWhenNeeded => content_is_too_large, + ScrollBarVisibility::AlwaysVisible => scroll_enabled, }; - let max_scroll_bar_width = max_scroll_bar_width_with_margin(ui); - // Avoid frame delay; start showing scroll bar right away: - if show_scroll_this_frame[0] && current_bar_use.y <= 0.0 { - current_bar_use.y = max_scroll_bar_width * ui.ctx().animate_bool(id.with("h"), true); + if show_scroll_this_frame[0] && show_bars_factor.x <= 0.0 { + show_bars_factor.x = ui.ctx().animate_bool(id.with("h"), true); } - if show_scroll_this_frame[1] && current_bar_use.x <= 0.0 { - current_bar_use.x = max_scroll_bar_width * ui.ctx().animate_bool(id.with("v"), true); + if show_scroll_this_frame[1] && show_bars_factor.y <= 0.0 { + show_bars_factor.y = ui.ctx().animate_bool(id.with("v"), true); } + let scroll_style = ui.spacing().scroll; + + // Paint the bars: for d in 0..2 { - let animation_t = current_bar_use[1 - d] / max_scroll_bar_width; - - if animation_t == 0.0 { + let show_factor = show_bars_factor[d]; + if show_factor == 0.0 { + state.scroll_bar_interaction[d] = false; continue; } - // margin on either side of the scroll bar - let inner_margin = animation_t * ui.spacing().scroll_bar_inner_margin; - let outer_margin = animation_t * ui.spacing().scroll_bar_outer_margin; - let mut min_cross = inner_rect.max[1 - d] + inner_margin; // left of vertical scroll (d == 1) - let mut max_cross = outer_rect.max[1 - d] - outer_margin; // right of vertical scroll (d == 1) - let min_main = inner_rect.min[d]; // top of vertical scroll (d == 1) - let max_main = inner_rect.max[d]; // bottom of vertical scroll (d == 1) + // left/right of a horizontal scroll (d==1) + // top/bottom of vertical scroll (d == 1) + let main_range = Rangef::new(inner_rect.min[d], inner_rect.max[d]); - if ui.clip_rect().max[1 - d] < max_cross + outer_margin { + // Margin on either side of the scroll bar: + let inner_margin = show_factor * scroll_style.bar_inner_margin; + let outer_margin = show_factor * scroll_style.bar_outer_margin; + + // top/bottom of a horizontal scroll (d==0). + // left/rigth of a vertical scroll (d==1). + let mut cross = if scroll_style.floating { + let max_bar_rect = if d == 0 { + outer_rect.with_min_y(outer_rect.max.y - scroll_style.allocated_width()) + } else { + outer_rect.with_min_x(outer_rect.max.x - scroll_style.allocated_width()) + }; + let is_hovering_bar_area = is_hovering_outer_rect + && ui.rect_contains_pointer(max_bar_rect) + || state.scroll_bar_interaction[d]; + let is_hovering_bar_area_t = ui + .ctx() + .animate_bool(id.with((d, "bar_hover")), is_hovering_bar_area); + let width = show_factor + * lerp( + scroll_style.floating_width..=scroll_style.bar_width, + is_hovering_bar_area_t, + ); + + let max_cross = outer_rect.max[1 - d] - outer_margin; + let min_cross = max_cross - width; + Rangef::new(min_cross, max_cross) + } else { + let min_cross = inner_rect.max[1 - d] + inner_margin; + let max_cross = outer_rect.max[1 - d] - outer_margin; + Rangef::new(min_cross, max_cross) + }; + + if ui.clip_rect().max[1 - d] < cross.max + outer_margin { // Move the scrollbar so it is visible. This is needed in some cases. // For instance: // * When we have a vertical-only scroll area in a top level panel, @@ -760,20 +865,20 @@ impl Prepared { // is outside the clip rectangle. // Really this should use the tighter clip_rect that ignores clip_rect_margin, but we don't store that. // clip_rect_margin is quite a hack. It would be nice to get rid of it. - let width = max_cross - min_cross; - max_cross = ui.clip_rect().max[1 - d] - outer_margin; - min_cross = max_cross - width; + let width = cross.max - cross.min; + cross.max = ui.clip_rect().max[1 - d] - outer_margin; + cross.min = cross.max - width; } let outer_scroll_rect = if d == 0 { Rect::from_min_max( - pos2(inner_rect.left(), min_cross), - pos2(inner_rect.right(), max_cross), + pos2(inner_rect.left(), cross.min), + pos2(inner_rect.right(), cross.max), ) } else { Rect::from_min_max( - pos2(min_cross, inner_rect.top()), - pos2(max_cross, inner_rect.bottom()), + pos2(cross.min, inner_rect.top()), + pos2(cross.max, inner_rect.bottom()), ) }; @@ -782,19 +887,18 @@ impl Prepared { state.offset[d] = content_size[d] - inner_rect.size()[d]; } - let from_content = - |content| remap_clamp(content, 0.0..=content_size[d], min_main..=max_main); + let from_content = |content| remap_clamp(content, 0.0..=content_size[d], main_range); let handle_rect = if d == 0 { Rect::from_min_max( - pos2(from_content(state.offset.x), min_cross), - pos2(from_content(state.offset.x + inner_rect.width()), max_cross), + pos2(from_content(state.offset.x), cross.min), + pos2(from_content(state.offset.x + inner_rect.width()), cross.max), ) } else { Rect::from_min_max( - pos2(min_cross, from_content(state.offset.y)), + pos2(cross.min, from_content(state.offset.y)), pos2( - max_cross, + cross.max, from_content(state.offset.y + inner_rect.height()), ), ) @@ -808,22 +912,24 @@ impl Prepared { }; let response = ui.interact(outer_scroll_rect, interact_id, sense); + state.scroll_bar_interaction[d] = response.hovered() || response.dragged(); + if let Some(pointer_pos) = response.interact_pointer_pos() { let scroll_start_offset_from_top_left = state.scroll_start_offset_from_top_left[d] .get_or_insert_with(|| { if handle_rect.contains(pointer_pos) { pointer_pos[d] - handle_rect.min[d] } else { - let handle_top_pos_at_bottom = max_main - handle_rect.size()[d]; + let handle_top_pos_at_bottom = main_range.max - handle_rect.size()[d]; // Calculate the new handle top position, centering the handle on the mouse. let new_handle_top_pos = (pointer_pos[d] - handle_rect.size()[d] / 2.0) - .clamp(min_main, handle_top_pos_at_bottom); + .clamp(main_range.min, handle_top_pos_at_bottom); pointer_pos[d] - new_handle_top_pos } }); let new_handle_top = pointer_pos[d] - *scroll_start_offset_from_top_left; - state.offset[d] = remap(new_handle_top, min_main..=max_main, 0.0..=content_size[d]); + state.offset[d] = remap(new_handle_top, main_range, 0.0..=content_size[d]); // some manual action taken, scroll not stuck state.scroll_stuck_to_end[d] = false; @@ -843,19 +949,19 @@ impl Prepared { // Avoid frame-delay by calculating a new handle rect: let mut handle_rect = if d == 0 { Rect::from_min_max( - pos2(from_content(state.offset.x), min_cross), - pos2(from_content(state.offset.x + inner_rect.width()), max_cross), + pos2(from_content(state.offset.x), cross.min), + pos2(from_content(state.offset.x + inner_rect.width()), cross.max), ) } else { Rect::from_min_max( - pos2(min_cross, from_content(state.offset.y)), + pos2(cross.min, from_content(state.offset.y)), pos2( - max_cross, + cross.max, from_content(state.offset.y + inner_rect.height()), ), ) }; - let min_handle_size = ui.spacing().scroll_handle_min_length; + let min_handle_size = scroll_style.handle_min_length; if handle_rect.size()[d] < min_handle_size { handle_rect = Rect::from_center_size( handle_rect.center(), @@ -868,21 +974,76 @@ impl Prepared { } let visuals = if scrolling_enabled { - ui.style().interact(&response) + // Pick visuals based on interaction with the handle. + // Remember that the response is for the whole scroll bar! + let is_hovering_handle = response.hovered() + && ui.input(|i| { + i.pointer + .latest_pos() + .map_or(false, |p| handle_rect.contains(p)) + }); + let visuals = ui.visuals(); + if response.is_pointer_button_down_on() { + &visuals.widgets.active + } else if is_hovering_handle { + &visuals.widgets.hovered + } else { + &visuals.widgets.inactive + } } else { - &ui.style().visuals.widgets.inactive + &ui.visuals().widgets.inactive }; + let handle_opacity = if scroll_style.floating { + if response.hovered() || response.dragged() { + scroll_style.interact_handle_opacity + } else { + let is_hovering_outer_rect_t = ui.ctx().animate_bool( + id.with((d, "is_hovering_outer_rect")), + is_hovering_outer_rect, + ); + lerp( + scroll_style.dormant_handle_opacity + ..=scroll_style.active_handle_opacity, + is_hovering_outer_rect_t, + ) + } + } else { + 1.0 + }; + + let background_opacity = if scroll_style.floating { + if response.hovered() || response.dragged() { + scroll_style.interact_background_opacity + } else if is_hovering_outer_rect { + scroll_style.active_background_opacity + } else { + scroll_style.dormant_background_opacity + } + } else { + 1.0 + }; + + let handle_color = if scroll_style.foreground_color { + visuals.fg_stroke.color + } else { + visuals.bg_fill + }; + + // Background: ui.painter().add(epaint::Shape::rect_filled( outer_scroll_rect, visuals.rounding, - ui.visuals().extreme_bg_color, + ui.visuals() + .extreme_bg_color + .gamma_multiply(background_opacity), )); + // Handle: ui.painter().add(epaint::Shape::rect_filled( handle_rect, visuals.rounding, - visuals.bg_fill, + handle_color.gamma_multiply(handle_opacity), )); } } @@ -902,12 +1063,12 @@ impl Prepared { // Only has an effect if stick_to_end is enabled but we save in // state anyway so that entering sticky mode at an arbitrary time // has appropriate effect. - state.scroll_stuck_to_end = [ + state.scroll_stuck_to_end = Vec2b::new( (state.offset[0] == available_offset[0]) - || (self.stick_to_end[0] && available_offset[0] < 0.), + || (self.stick_to_end[0] && available_offset[0] < 0.0), (state.offset[1] == available_offset[1]) - || (self.stick_to_end[1] && available_offset[1] < 0.), - ]; + || (self.stick_to_end[1] && available_offset[1] < 0.0), + ); state.show_scroll = show_scroll_this_frame; state.content_is_too_large = content_is_too_large; @@ -917,10 +1078,3 @@ impl Prepared { (content_size, state) } } - -/// Width of a vertical scrollbar, or height of a horizontal scroll bar -fn max_scroll_bar_width_with_margin(ui: &Ui) -> f32 { - ui.spacing().scroll_bar_inner_margin - + ui.spacing().scroll_bar_width - + ui.spacing().scroll_bar_outer_margin -} diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 264672f24..3fcc5de0b 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -147,7 +147,7 @@ impl<'open> Window<'open> { /// Constrains this window to the screen bounds. /// - /// To change the area to constrain to, use [`Self::constraint_to`]. + /// To change the area to constrain to, use [`Self::constrain_to`]. /// /// Default: `true`. pub fn constrain(mut self, constrain: bool) -> Self { @@ -155,10 +155,10 @@ impl<'open> Window<'open> { self } - /// Constraint the movement of the window to the given rectangle. + /// Constrain the movement of the window to the given rectangle. /// /// For instance: `.constrain_to(ctx.screen_rect())`. - pub fn constraint_to(mut self, constrain_rect: Rect) -> Self { + pub fn constrain_to(mut self, constrain_rect: Rect) -> Self { self.area = self.area.constrain_to(constrain_rect); self } @@ -272,7 +272,7 @@ impl<'open> Window<'open> { } /// Enable/disable horizontal/vertical scrolling. `false` by default. - pub fn scroll2(mut self, scroll: [bool; 2]) -> Self { + pub fn scroll2(mut self, scroll: impl Into) -> Self { self.scroll = self.scroll.scroll2(scroll); self } @@ -419,7 +419,7 @@ impl<'open> Window<'open> { ui.add_space(title_content_spacing); } - if scroll.has_any_bar() { + if scroll.is_any_scroll_enabled() { scroll.show(ui, add_contents).inner } else { add_contents(ui) @@ -476,7 +476,12 @@ impl<'open> Window<'open> { } } -fn paint_resize_corner(ui: &Ui, possible: &PossibleInteractions, outer_rect: Rect, stroke: Stroke) { +fn paint_resize_corner( + ui: &Ui, + possible: &PossibleInteractions, + outer_rect: Rect, + stroke: impl Into, +) { let corner = if possible.resize_right && possible.resize_bottom { Align2::RIGHT_BOTTOM } else if possible.resize_left && possible.resize_bottom { diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 5b0f0305c..66bc02f13 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1502,7 +1502,6 @@ impl Context { crate::profile_scope!("accesskit"); let state = self.frame_state_mut(|fs| fs.accesskit_state.take()); if let Some(state) = state { - let has_focus = self.input(|i| i.raw.focused); let root_id = crate::accesskit_root_id().accesskit_id(); let nodes = self.write(|ctx| { state @@ -1516,13 +1515,13 @@ impl Context { }) .collect() }); + let focus_id = self + .memory(|mem| mem.focus()) + .map_or(root_id, |id| id.accesskit_id()); platform_output.accesskit_update = Some(accesskit::TreeUpdate { nodes, tree: Some(accesskit::Tree::new(root_id)), - focus: has_focus.then(|| { - let focus_id = self.memory(|mem| mem.focus()); - focus_id.map_or(root_id, |id| id.accesskit_id()) - }), + focus: focus_id, }); } } @@ -2263,7 +2262,7 @@ impl Context { NodeBuilder::new(Role::Window).build(&mut ctx.accesskit_node_classes), )], tree: Some(Tree::new(root_id)), - focus: None, + focus: root_id, }) } } diff --git a/crates/egui/src/id.rs b/crates/egui/src/id.rs index 8ac4d9196..e588271d9 100644 --- a/crates/egui/src/id.rs +++ b/crates/egui/src/id.rs @@ -35,7 +35,10 @@ impl Id { /// /// The null [`Id`] is still a valid id to use in all circumstances, /// though obviously it will lead to a lot of collisions if you do use it! - pub const fn null() -> Self { + pub const NULL: Self = Self(0); + + #[deprecated = "Use Id::NULL"] + pub fn null() -> Self { Self(0) } @@ -72,7 +75,7 @@ impl Id { #[cfg(feature = "accesskit")] pub(crate) fn accesskit_id(&self) -> accesskit::NodeId { - std::num::NonZeroU64::new(self.0).unwrap().into() + self.0.into() } } diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 82b0ad3a7..7aab28bbf 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -378,7 +378,7 @@ pub use epaint::emath; pub use ecolor::hex_color; pub use ecolor::{Color32, Rgba}; pub use emath::{ - lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rangef, Rect, Vec2, + lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rangef, Rect, Vec2, Vec2b, }; pub use epaint::{ mutex, diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index e4ae15ff5..7f5bb44c8 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -449,6 +449,13 @@ impl Focus { // nothing has focus and the user pressed tab - give focus to the first widgets that wants it: self.focused_widget = Some(FocusWidget::new(id)); self.reset_focus(); + } else if self.focus_direction == FocusDirection::Previous + && self.focused_widget.is_none() + && !self.give_to_next + { + // nothing has focus and the user pressed Shift+Tab - give focus to the last widgets that wants it: + self.focused_widget = self.last_interested.map(FocusWidget::new); + self.reset_focus(); } self.last_interested = Some(id); diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index 388a1ced1..a88862325 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -333,12 +333,13 @@ impl Painter { } /// Show an arrow starting at `origin` and going in the direction of `vec`, with the length `vec.length()`. - pub fn arrow(&self, origin: Pos2, vec: Vec2, stroke: Stroke) { + pub fn arrow(&self, origin: Pos2, vec: Vec2, stroke: impl Into) { use crate::emath::*; let rot = Rot2::from_angle(std::f32::consts::TAU / 10.0); let tip_length = vec.length() / 4.0; let tip = origin + vec; let dir = vec.normalized(); + let stroke = stroke.into(); self.line_segment([origin, tip], stroke); self.line_segment([tip, tip - tip_length * (rot * dir)], stroke); self.line_segment([tip, tip - tip_length * (rot.inverse() * dir)], stroke); diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index e95d07c20..a194a6666 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -78,7 +78,7 @@ pub struct Response { #[doc(hidden)] pub interact_pointer_pos: Option, - /// What the underlying data changed? + /// Was the underlying data changed? /// /// e.g. the slider was dragged, text was entered in a [`TextEdit`](crate::TextEdit) etc. /// Always `false` for something like a [`Button`](crate::Button). @@ -339,7 +339,7 @@ impl Response { self.is_pointer_button_down_on } - /// What the underlying data changed? + /// Was the underlying data changed? /// /// e.g. the slider was dragged, text was entered in a [`TextEdit`](crate::TextEdit) etc. /// Always `false` for something like a [`Button`](crate::Button). @@ -479,7 +479,7 @@ impl Response { /// Highlight this widget, to make it look like it is hovered, even if it isn't. /// - /// The highlight takes on frame to take effect if you call this after the widget has been fully rendered. + /// The highlight takes one frame to take effect if you call this after the widget has been fully rendered. /// /// See also [`Context::highlight_widget`]. pub fn highlight(mut self) -> Self { @@ -620,20 +620,20 @@ impl Response { info: crate::WidgetInfo, ) { use crate::WidgetType; - use accesskit::{CheckedState, Role}; + use accesskit::{Checked, Role}; self.fill_accesskit_node_common(builder); builder.set_role(match info.typ { WidgetType::Label => Role::StaticText, WidgetType::Link => Role::Link, - WidgetType::TextEdit => Role::TextField, + WidgetType::TextEdit => Role::TextInput, WidgetType::Button | WidgetType::ImageButton | WidgetType::CollapsingHeader => { Role::Button } WidgetType::Checkbox => Role::CheckBox, WidgetType::RadioButton => Role::RadioButton, WidgetType::SelectableLabel => Role::ToggleButton, - WidgetType::ComboBox => Role::PopupButton, + WidgetType::ComboBox => Role::ComboBox, WidgetType::Slider => Role::Slider, WidgetType::DragValue => Role::SpinButton, WidgetType::ColorButton => Role::ColorWell, @@ -649,10 +649,10 @@ impl Response { builder.set_numeric_value(value); } if let Some(selected) = info.selected { - builder.set_checked_state(if selected { - CheckedState::True + builder.set_checked(if selected { + Checked::True } else { - CheckedState::False + Checked::False }); } } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 3c44b292a..ba0cc1b9e 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -2,11 +2,13 @@ #![allow(clippy::if_same_then_else)] +use std::collections::BTreeMap; + +use epaint::{Rounding, Shadow, Stroke}; + use crate::{ ecolor::*, emath::*, ComboBox, CursorIcon, FontFamily, FontId, Response, RichText, WidgetText, }; -use epaint::{Rounding, Shadow, Stroke}; -use std::collections::BTreeMap; // ---------------------------------------------------------------------------- @@ -303,16 +305,8 @@ pub struct Spacing { /// Height of a combo-box before showing scroll bars. pub combo_height: f32, - pub scroll_bar_width: f32, - - /// Make sure the scroll handle is at least this big - pub scroll_handle_min_length: f32, - - /// Margin between contents and scroll bar. - pub scroll_bar_inner_margin: f32, - - /// Margin between scroll bar and the outer container (e.g. right of a vertical scroll bar). - pub scroll_bar_outer_margin: f32, + /// Controls the spacing of a [`crate::ScrollArea`]. + pub scroll: ScrollStyle, } impl Spacing { @@ -333,6 +327,277 @@ impl Spacing { // ---------------------------------------------------------------------------- +/// Controls the spacing and visuals of a [`crate::ScrollArea`]. +/// +/// There are three presets to chose from: +/// * [`Self::solid`] +/// * [`Self::thin`] +/// * [`Self::floating`] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +pub struct ScrollStyle { + /// If `true`, scroll bars float above the content, partially covering it. + /// + /// If `false`, the scroll bars allocate space, shrinking the area + /// available to the contents. + /// + /// This also changes the colors of the scroll-handle to make + /// it more promiment. + pub floating: bool, + + /// The width of the scroll bars at it largest. + pub bar_width: f32, + + /// Make sure the scroll handle is at least this big + pub handle_min_length: f32, + + /// Margin between contents and scroll bar. + pub bar_inner_margin: f32, + + /// Margin between scroll bar and the outer container (e.g. right of a vertical scroll bar). + /// Only makes sense for non-floating scroll bars. + pub bar_outer_margin: f32, + + /// The thin width of floating scroll bars that the user is NOT hovering. + /// + /// When the user hovers the scroll bars they expand to [`Self::bar_width`]. + pub floating_width: f32, + + /// How much space i allocated for a floating scroll bar? + /// + /// Normally this is zero, but you could set this to something small + /// like 4.0 and set [`Self::dormant_handle_opacity`] and + /// [`Self::dormant_background_opacity`] to e.g. 0.5 + /// so as to always show a thin scroll bar. + pub floating_allocated_width: f32, + + /// If true, use colors with more contrast. Good for floating scroll bars. + pub foreground_color: bool, + + /// The opaqueness of the background when the user is neither scrolling + /// nor hovering the scroll area. + /// + /// This is only for floating scroll bars. + /// Solid scroll bars are always opaque. + pub dormant_background_opacity: f32, + + /// The opaqueness of the background when the user is hovering + /// the scroll area, but not the scroll bar. + /// + /// This is only for floating scroll bars. + /// Solid scroll bars are always opaque. + pub active_background_opacity: f32, + + /// The opaqueness of the background when the user is hovering + /// over the scroll bars. + /// + /// This is only for floating scroll bars. + /// Solid scroll bars are always opaque. + pub interact_background_opacity: f32, + + /// The opaqueness of the handle when the user is neither scrolling + /// nor hovering the scroll area. + /// + /// This is only for floating scroll bars. + /// Solid scroll bars are always opaque. + pub dormant_handle_opacity: f32, + + /// The opaqueness of the handle when the user is hovering + /// the scroll area, but not the scroll bar. + /// + /// This is only for floating scroll bars. + /// Solid scroll bars are always opaque. + pub active_handle_opacity: f32, + + /// The opaqueness of the handle when the user is hovering + /// over the scroll bars. + /// + /// This is only for floating scroll bars. + /// Solid scroll bars are always opaque. + pub interact_handle_opacity: f32, +} + +impl Default for ScrollStyle { + fn default() -> Self { + Self::floating() + } +} + +impl ScrollStyle { + /// Solid scroll bars that always use up space + pub fn solid() -> Self { + Self { + floating: false, + bar_width: 6.0, + handle_min_length: 12.0, + bar_inner_margin: 4.0, + bar_outer_margin: 0.0, + floating_width: 2.0, + floating_allocated_width: 0.0, + + foreground_color: false, + + dormant_background_opacity: 0.0, + active_background_opacity: 0.4, + interact_background_opacity: 0.7, + + dormant_handle_opacity: 0.0, + active_handle_opacity: 0.6, + interact_handle_opacity: 1.0, + } + } + + /// Thin scroll bars that expand on hover + pub fn thin() -> Self { + Self { + floating: true, + bar_width: 12.0, + floating_allocated_width: 6.0, + foreground_color: false, + + dormant_background_opacity: 1.0, + dormant_handle_opacity: 1.0, + + active_background_opacity: 1.0, + active_handle_opacity: 1.0, + + // Be tranlucent when expanded so we can see the content + interact_background_opacity: 0.6, + interact_handle_opacity: 0.6, + + ..Self::solid() + } + } + + /// No scroll bars until you hover the scroll area, + /// at which time they appear faintly, and then expand + /// when you hover the scroll bars. + pub fn floating() -> Self { + Self { + floating: true, + bar_width: 12.0, + foreground_color: true, + floating_allocated_width: 0.0, + dormant_background_opacity: 0.0, + dormant_handle_opacity: 0.0, + ..Self::solid() + } + } + + /// Width of a solid vertical scrollbar, or height of a horizontal scroll bar, when it is at its widest. + pub fn allocated_width(&self) -> f32 { + if self.floating { + self.floating_allocated_width + } else { + self.bar_inner_margin + self.bar_width + self.bar_outer_margin + } + } + + pub fn ui(&mut self, ui: &mut Ui) { + ui.horizontal(|ui| { + ui.label("Presets:"); + ui.selectable_value(self, Self::solid(), "Solid"); + ui.selectable_value(self, Self::thin(), "Thin"); + ui.selectable_value(self, Self::floating(), "Floating"); + }); + + ui.collapsing("Details", |ui| { + self.details_ui(ui); + }); + } + + pub fn details_ui(&mut self, ui: &mut Ui) { + let Self { + floating, + bar_width, + handle_min_length, + bar_inner_margin, + bar_outer_margin, + floating_width, + floating_allocated_width, + + foreground_color, + + dormant_background_opacity, + active_background_opacity, + interact_background_opacity, + dormant_handle_opacity, + active_handle_opacity, + interact_handle_opacity, + } = self; + + ui.horizontal(|ui| { + ui.label("Type:"); + ui.selectable_value(floating, false, "Solid"); + ui.selectable_value(floating, true, "Floating"); + }); + + ui.horizontal(|ui| { + ui.add(DragValue::new(bar_width).clamp_range(0.0..=32.0)); + ui.label("Full bar width"); + }); + if *floating { + ui.horizontal(|ui| { + ui.add(DragValue::new(floating_width).clamp_range(0.0..=32.0)); + ui.label("Thin bar width"); + }); + ui.horizontal(|ui| { + ui.add(DragValue::new(floating_allocated_width).clamp_range(0.0..=32.0)); + ui.label("Allocated width"); + }); + } + + ui.horizontal(|ui| { + ui.add(DragValue::new(handle_min_length).clamp_range(0.0..=32.0)); + ui.label("Minimum handle length"); + }); + ui.horizontal(|ui| { + ui.add(DragValue::new(bar_outer_margin).clamp_range(0.0..=32.0)); + ui.label("Outer margin"); + }); + + ui.horizontal(|ui| { + ui.label("Color:"); + ui.selectable_value(foreground_color, false, "Background"); + ui.selectable_value(foreground_color, true, "Foreground"); + }); + + if *floating { + crate::Grid::new("opacity").show(ui, |ui| { + fn opacity_ui(ui: &mut Ui, opacity: &mut f32) { + ui.add(DragValue::new(opacity).speed(0.01).clamp_range(0.0..=1.0)); + } + + ui.label("Opacity"); + ui.label("Dormant"); + ui.label("Active"); + ui.label("Interacting"); + ui.end_row(); + + ui.label("Background:"); + opacity_ui(ui, dormant_background_opacity); + opacity_ui(ui, active_background_opacity); + opacity_ui(ui, interact_background_opacity); + ui.end_row(); + + ui.label("Handle:"); + opacity_ui(ui, dormant_handle_opacity); + opacity_ui(ui, active_handle_opacity); + opacity_ui(ui, interact_handle_opacity); + ui.end_row(); + }); + } else { + ui.horizontal(|ui| { + ui.add(DragValue::new(bar_inner_margin).clamp_range(0.0..=32.0)); + ui.label("Inner margin"); + }); + } + } +} + +// ---------------------------------------------------------------------------- + #[derive(Clone, Copy, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Margin { @@ -807,10 +1072,7 @@ impl Default for Spacing { icon_spacing: 4.0, tooltip_width: 600.0, combo_height: 200.0, - scroll_bar_width: 8.0, - scroll_handle_min_length: 12.0, - scroll_bar_inner_margin: 4.0, - scroll_bar_outer_margin: 0.0, + scroll: Default::default(), indent_ends_with_horizontal_line: false, } } @@ -1146,10 +1408,7 @@ impl Spacing { tooltip_width, indent_ends_with_horizontal_line, combo_height, - scroll_bar_width, - scroll_handle_min_length, - scroll_bar_inner_margin, - scroll_bar_outer_margin, + scroll, } = self; ui.add(slider_vec2(item_spacing, 0.0..=20.0, "Item spacing")); @@ -1176,21 +1435,9 @@ impl Spacing { ui.add(DragValue::new(text_edit_width).clamp_range(0.0..=1000.0)); ui.label("TextEdit width"); }); - ui.horizontal(|ui| { - ui.add(DragValue::new(scroll_bar_width).clamp_range(0.0..=32.0)); - ui.label("Scroll-bar width"); - }); - ui.horizontal(|ui| { - ui.add(DragValue::new(scroll_handle_min_length).clamp_range(0.0..=32.0)); - ui.label("Scroll-bar handle min length"); - }); - ui.horizontal(|ui| { - ui.add(DragValue::new(scroll_bar_inner_margin).clamp_range(0.0..=32.0)); - ui.label("Scroll-bar inner margin"); - }); - ui.horizontal(|ui| { - ui.add(DragValue::new(scroll_bar_outer_margin).clamp_range(0.0..=32.0)); - ui.label("Scroll-bar outer margin"); + + ui.collapsing("Scroll Area", |ui| { + scroll.ui(ui); }); ui.horizontal(|ui| { diff --git a/crates/egui/src/util/undoer.rs b/crates/egui/src/util/undoer.rs index b5e272aff..de6d27161 100644 --- a/crates/egui/src/util/undoer.rs +++ b/crates/egui/src/util/undoer.rs @@ -57,15 +57,22 @@ pub struct Undoer { /// The latest undo point may (often) be the current state. undos: VecDeque, + /// Stores redos immediately after a sequence of undos. + /// Gets cleared every time the state changes. + /// Does not need to be a deque, because there can only be up to undos.len() redos, + /// which is already limited to settings.max_undos. + redos: Vec, + #[cfg_attr(feature = "serde", serde(skip))] flux: Option>, } impl std::fmt::Debug for Undoer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self { undos, .. } = self; + let Self { undos, redos, .. } = self; f.debug_struct("Undoer") .field("undo count", &undos.len()) + .field("redo count", &redos.len()) .finish() } } @@ -91,6 +98,10 @@ where } } + pub fn has_redo(&self, current_state: &State) -> bool { + !self.redos.is_empty() && self.undos.back() == Some(current_state) + } + /// Return true if the state is currently changing pub fn is_in_flux(&self) -> bool { self.flux.is_some() @@ -101,7 +112,9 @@ where self.flux = None; if self.undos.back() == Some(current_state) { - self.undos.pop_back(); + self.redos.push(self.undos.pop_back().unwrap()); + } else { + self.redos.push(current_state.clone()); } // Note: we keep the undo point intact. @@ -111,9 +124,20 @@ where } } + pub fn redo(&mut self, current_state: &State) -> Option<&State> { + if !self.undos.is_empty() && self.undos.back() != Some(current_state) { + // state changed since the last undo, redos should be cleared. + self.redos.clear(); + None + } else if let Some(state) = self.redos.pop() { + self.undos.push_back(state); + self.undos.back() + } else { + None + } + } + /// Add an undo point if, and only if, there has been a change since the latest undo point. - /// - /// * `time`: current time in seconds. pub fn add_undo(&mut self, current_state: &State) { if self.undos.back() != Some(current_state) { self.undos.push_back(current_state.clone()); @@ -139,6 +163,8 @@ where if latest_undo == current_state { self.flux = None; } else { + self.redos.clear(); + match self.flux.as_mut() { None => { self.flux = Some(Flux { diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 1af8e9b1b..ad5a494f5 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -37,7 +37,7 @@ impl std::fmt::Debug for ViewportId { impl ViewportId { /// The `ViewportId` of the root viewport. - pub const ROOT: Self = Self(Id::null()); + pub const ROOT: Self = Self(Id::NULL); #[inline] pub fn from_hash_of(source: impl std::hash::Hash) -> Self { diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 4c94cc9d4..e11a33b19 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -594,6 +594,14 @@ impl<'a> ImageButton<'a> { self.sense = sense; self } + + /// Set rounding for the `ImageButton`. + /// If the underlying image already has rounding, this + /// will override that value. + pub fn rounding(mut self, rounding: impl Into) -> Self { + self.image = self.image.rounding(rounding.into()); + self + } } impl<'a> Widget for ImageButton<'a> { @@ -621,7 +629,7 @@ impl<'a> Widget for ImageButton<'a> { let selection = ui.visuals().selection; ( Vec2::ZERO, - Rounding::ZERO, + self.image.image_options().rounding, selection.bg_fill, selection.stroke, ) @@ -630,7 +638,7 @@ impl<'a> Widget for ImageButton<'a> { let expansion = Vec2::splat(visuals.expansion); ( expansion, - visuals.rounding, + self.image.image_options().rounding, visuals.weak_bg_fill, visuals.bg_stroke, ) @@ -646,10 +654,8 @@ impl<'a> Widget for ImageButton<'a> { .layout() .align_size_within_rect(image_size, rect.shrink2(padding)); // let image_rect = image_rect.expand2(expansion); // can make it blurry, so let's not - let image_options = ImageOptions { - rounding, // apply rounding to the image - ..self.image.image_options().clone() - }; + let image_options = self.image.image_options().clone(); + widgets::image::paint_texture_load_result(ui, &tlr, image_rect, None, &image_options); // Draw frame outline: diff --git a/crates/egui/src/widgets/color_picker.rs b/crates/egui/src/widgets/color_picker.rs index 5958d0758..a3752ef68 100644 --- a/crates/egui/src/widgets/color_picker.rs +++ b/crates/egui/src/widgets/color_picker.rs @@ -442,5 +442,5 @@ fn color_cache_set(ctx: &Context, rgba: impl Into, hsva: Hsva) { // To ensure we keep hue slider when `srgba` is gray we store the full [`Hsva`] in a cache: fn use_color_cache(ctx: &Context, f: impl FnOnce(&mut FixedCache) -> R) -> R { - ctx.data_mut(|d| f(d.get_temp_mut_or_default(Id::null()))) + ctx.data_mut(|d| f(d.get_temp_mut_or_default(Id::NULL))) } diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 72bceb327..07c4f410a 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -508,7 +508,8 @@ impl<'a> Slider<'a> { value = emath::round_to_decimals(value, max_decimals); } if let Some(step) = self.step { - value = (value / step).round() * step; + let start = *self.range.start(); + value = start + ((value - start) / step).round() * step; } set(&mut self.get_set_value, value); } @@ -703,7 +704,9 @@ impl<'a> Slider<'a> { let handle_radius = self.handle_radius(rect); match self.orientation { SliderOrientation::Horizontal => rect.x_range().shrink(handle_radius), - SliderOrientation::Vertical => rect.y_range().shrink(handle_radius), + // The vertical case has to be flipped because the largest slider value maps to the + // lowest y value (which is at the top) + SliderOrientation::Vertical => rect.y_range().shrink(handle_radius).flip(), } } diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 7de4733a0..7898c2256 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +#[cfg(feature = "accesskit")] +use accesskit::Role; use epaint::text::{cursor::*, Galley, LayoutJob}; use crate::{output::OutputEvent, *}; @@ -751,7 +753,7 @@ impl<'t> TextEdit<'t> { builder.set_default_action_verb(accesskit::DefaultActionVerb::Focus); if self.multiline { - builder.set_multiline(); + builder.set_role(Role::MultilineTextInput); } parent_id @@ -759,7 +761,7 @@ impl<'t> TextEdit<'t> { if let Some(parent_id) = parent_id { // drop ctx lock before further processing - use accesskit::{Role, TextDirection}; + use accesskit::TextDirection; ui.ctx().with_accessibility_parent(parent_id, || { for (i, row) in galley.rows.iter().enumerate() { @@ -984,8 +986,7 @@ fn events( pressed: true, modifiers, .. - } if modifiers.command && !modifiers.shift => { - // TODO(emilk): redo + } if modifiers.matches(Modifiers::COMMAND) => { if let Some((undo_ccursor_range, undo_txt)) = state .undoer .lock() @@ -997,6 +998,25 @@ fn events( None } } + Event::Key { + key, + pressed: true, + modifiers, + .. + } if (modifiers.matches(Modifiers::COMMAND) && *key == Key::Y) + || (modifiers.matches(Modifiers::SHIFT | Modifiers::COMMAND) && *key == Key::Z) => + { + if let Some((redo_ccursor_range, redo_txt)) = state + .undoer + .lock() + .redo(&(cursor_range.as_ccursor_range(), text.as_str().to_owned())) + { + text.replace(redo_txt); + Some(*redo_ccursor_range) + } else { + None + } + } Event::Key { key, diff --git a/crates/egui_demo_app/src/apps/custom3d_glow.rs b/crates/egui_demo_app/src/apps/custom3d_glow.rs index ff545bb61..3175cf4fa 100644 --- a/crates/egui_demo_app/src/apps/custom3d_glow.rs +++ b/crates/egui_demo_app/src/apps/custom3d_glow.rs @@ -24,7 +24,7 @@ impl eframe::App for Custom3d { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { egui::ScrollArea::both() - .auto_shrink([false; 2]) + .auto_shrink(false) .show(ui, |ui| { ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0; diff --git a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs index 29bbfaafe..1bac26ed3 100644 --- a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs +++ b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs @@ -99,7 +99,7 @@ impl eframe::App for Custom3d { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { egui::ScrollArea::both() - .auto_shrink([false; 2]) + .auto_shrink(false) .show(ui, |ui| { ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0; diff --git a/crates/egui_demo_app/src/apps/http_app.rs b/crates/egui_demo_app/src/apps/http_app.rs index 90be24dd2..a118a90fc 100644 --- a/crates/egui_demo_app/src/apps/http_app.rs +++ b/crates/egui_demo_app/src/apps/http_app.rs @@ -174,7 +174,7 @@ fn ui_resource(ui: &mut egui::Ui, resource: &Resource) { ui.separator(); egui::ScrollArea::vertical() - .auto_shrink([false; 2]) + .auto_shrink(false) .show(ui, |ui| { egui::CollapsingHeader::new("Response headers") .default_open(false) diff --git a/crates/egui_demo_app/src/apps/image_viewer.rs b/crates/egui_demo_app/src/apps/image_viewer.rs index 6bc6cc05b..9754972bd 100644 --- a/crates/egui_demo_app/src/apps/image_viewer.rs +++ b/crates/egui_demo_app/src/apps/image_viewer.rs @@ -187,7 +187,7 @@ impl eframe::App for ImageViewer { }); egui::CentralPanel::default().show(ctx, |ui| { - egui::ScrollArea::new([true, true]).show(ui, |ui| { + egui::ScrollArea::both().show(ui, |ui| { let mut image = egui::Image::from_uri(&self.current_uri); image = image.uv(self.image_options.uv); image = image.bg_fill(self.image_options.bg_fill); diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index e5f458874..fe32733ad 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -68,7 +68,7 @@ impl eframe::App for ColorTestApp { ); ui.separator(); } - egui::ScrollArea::both().auto_shrink([false; 2]).show(ui, |ui| { + egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| { self.color_test.ui(ui); }); }); diff --git a/crates/egui_demo_lib/src/demo/context_menu.rs b/crates/egui_demo_lib/src/demo/context_menu.rs index 96375d72e..ffcc73379 100644 --- a/crates/egui_demo_lib/src/demo/context_menu.rs +++ b/crates/egui_demo_lib/src/demo/context_menu.rs @@ -1,3 +1,5 @@ +use egui::Vec2b; + #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] enum Plot { @@ -19,7 +21,7 @@ fn sigmoid(x: f64) -> f64 { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct ContextMenus { plot: Plot, - show_axes: [bool; 2], + show_axes: Vec2b, allow_drag: bool, allow_zoom: bool, allow_scroll: bool, @@ -33,7 +35,7 @@ impl Default for ContextMenus { fn default() -> Self { Self { plot: Plot::Sin, - show_axes: [true, true], + show_axes: Vec2b::TRUE, allow_drag: true, allow_zoom: true, allow_scroll: true, 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 195b79e22..a90465cc1 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -371,7 +371,7 @@ impl BoxPainting { ui.painter().rect( rect, self.rounding, - Color32::from_gray(64), + ui.visuals().text_color().gamma_multiply(0.5), Stroke::new(self.stroke_width, 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 cd742c1a1..6b4464dc4 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -4,9 +4,9 @@ use std::ops::RangeInclusive; use egui::*; use egui_plot::{ - Arrows, AxisBools, AxisHints, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, - Corner, GridInput, GridMark, HLine, Legend, Line, LineStyle, MarkerShape, Plot, PlotImage, - PlotPoint, PlotPoints, PlotResponse, Points, Polygon, Text, VLine, + Arrows, AxisHints, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner, + GridInput, GridMark, HLine, Legend, Line, LineStyle, MarkerShape, Plot, PlotImage, PlotPoint, + PlotPoints, PlotResponse, Points, Polygon, Text, VLine, }; // ---------------------------------------------------------------------------- @@ -830,8 +830,8 @@ impl Default for Chart { struct ChartsDemo { chart: Chart, vertical: bool, - allow_zoom: AxisBools, - allow_drag: AxisBools, + allow_zoom: Vec2b, + allow_drag: Vec2b, } impl Default for ChartsDemo { diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index f5833521b..30855f77f 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -1,8 +1,9 @@ -use egui::*; +use egui::{scroll_area::ScrollBarVisibility, *}; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Clone, Copy, Debug, PartialEq)] enum ScrollDemo { + ScrollAppearance, ScrollTo, ManyLines, LargeCanvas, @@ -12,7 +13,7 @@ enum ScrollDemo { impl Default for ScrollDemo { fn default() -> Self { - Self::ScrollTo + Self::ScrollAppearance } } @@ -20,6 +21,7 @@ impl Default for ScrollDemo { #[cfg_attr(feature = "serde", serde(default))] #[derive(Default, PartialEq)] pub struct Scrolling { + appearance: ScrollAppearance, demo: ScrollDemo, scroll_to: ScrollTo, scroll_stick_to: ScrollStickTo, @@ -33,7 +35,9 @@ impl super::Demo for Scrolling { fn show(&mut self, ctx: &egui::Context, open: &mut bool) { egui::Window::new(self.name()) .open(open) - .resizable(false) + .resizable(true) + .hscroll(false) + .vscroll(false) .show(ctx, |ui| { use super::View as _; self.ui(ui); @@ -44,6 +48,7 @@ impl super::Demo for Scrolling { impl super::View for Scrolling { fn ui(&mut self, ui: &mut Ui) { ui.horizontal(|ui| { + ui.selectable_value(&mut self.demo, ScrollDemo::ScrollAppearance, "Appearance"); ui.selectable_value(&mut self.demo, ScrollDemo::ScrollTo, "Scroll to"); ui.selectable_value( &mut self.demo, @@ -60,6 +65,9 @@ impl super::View for Scrolling { }); ui.separator(); match self.demo { + ScrollDemo::ScrollAppearance => { + self.appearance.ui(ui); + } ScrollDemo::ScrollTo => { self.scroll_to.ui(ui); } @@ -84,6 +92,75 @@ impl super::View for Scrolling { } } +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +#[derive(PartialEq)] +struct ScrollAppearance { + num_lorem_ipsums: usize, + visibility: ScrollBarVisibility, +} + +impl Default for ScrollAppearance { + fn default() -> Self { + Self { + num_lorem_ipsums: 2, + visibility: ScrollBarVisibility::default(), + } + } +} + +impl ScrollAppearance { + fn ui(&mut self, ui: &mut egui::Ui) { + let Self { + num_lorem_ipsums, + visibility, + } = self; + + let mut style: Style = (*ui.ctx().style()).clone(); + + style.spacing.scroll.ui(ui); + + ui.add_space(8.0); + + ui.horizontal(|ui| { + ui.label("ScrollBarVisibility:"); + for option in ScrollBarVisibility::ALL { + ui.selectable_value(visibility, option, format!("{option:?}")); + } + }); + ui.weak("When to show scroll bars; resize the window to see the effect."); + + ui.add_space(8.0); + + ui.ctx().set_style(style.clone()); + ui.set_style(style); + + ui.separator(); + + ui.add( + egui::Slider::new(num_lorem_ipsums, 1..=100) + .text("Content length") + .logarithmic(true), + ); + + ui.separator(); + + ScrollArea::vertical() + .auto_shrink(false) + .scroll_bar_visibility(*visibility) + .show(ui, |ui| { + ui.with_layout( + egui::Layout::top_down(egui::Align::LEFT).with_cross_justify(true), + |ui| { + for _ in 0..*num_lorem_ipsums { + ui.label(crate::LOREM_IPSUM_LONG); + } + }, + ); + }); + } +} + fn huge_content_lines(ui: &mut egui::Ui) { ui.label( "A lot of rows, but only the visible ones are laid out, so performance is still good:", @@ -93,7 +170,7 @@ fn huge_content_lines(ui: &mut egui::Ui) { let text_style = TextStyle::Body; let row_height = ui.text_style_height(&text_style); let num_rows = 10_000; - ScrollArea::vertical().auto_shrink([false; 2]).show_rows( + ScrollArea::vertical().auto_shrink(false).show_rows( ui, row_height, num_rows, @@ -116,7 +193,7 @@ fn huge_content_painter(ui: &mut egui::Ui) { let num_rows = 10_000; ScrollArea::vertical() - .auto_shrink([false; 2]) + .auto_shrink(false) .show_viewport(ui, |ui, viewport| { ui.set_height(row_height * num_rows as f32); @@ -215,9 +292,7 @@ impl super::View for ScrollTo { scroll_bottom |= ui.button("Scroll to bottom").clicked(); }); - let mut scroll_area = ScrollArea::vertical() - .max_height(200.0) - .auto_shrink([false; 2]); + let mut scroll_area = ScrollArea::vertical().max_height(200.0).auto_shrink(false); if go_to_scroll_offset { scroll_area = scroll_area.vertical_scroll_offset(self.offset); } diff --git a/crates/egui_demo_lib/src/demo/text_layout.rs b/crates/egui_demo_lib/src/demo/text_layout.rs index f2512ae8c..fa02a7175 100644 --- a/crates/egui_demo_lib/src/demo/text_layout.rs +++ b/crates/egui_demo_lib/src/demo/text_layout.rs @@ -124,7 +124,7 @@ impl super::View for TextLayoutDemo { }; egui::ScrollArea::vertical() - .auto_shrink([false; 2]) + .auto_shrink(false) .show(ui, |ui| { let extra_letter_spacing = points_per_pixel * *extra_letter_spacing_pixels as f32; let line_height = (*line_height_pixels != 0) diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index 82589c402..4189e61c9 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -109,7 +109,7 @@ impl WidgetGallery { date, } = self; - ui.add(doc_link_label("Label", "label,heading")); + ui.add(doc_link_label("Label", "label")); ui.label("Welcome to the widget gallery!"); ui.end_row(); @@ -121,7 +121,7 @@ impl WidgetGallery { ); ui.end_row(); - ui.add(doc_link_label("TextEdit", "TextEdit,text_edit")); + ui.add(doc_link_label("TextEdit", "TextEdit")); ui.add(egui::TextEdit::singleline(string).hint_text("Write something here")); ui.end_row(); @@ -149,10 +149,7 @@ impl WidgetGallery { }); ui.end_row(); - ui.add(doc_link_label( - "SelectableLabel", - "selectable_value,SelectableLabel", - )); + ui.add(doc_link_label("SelectableLabel", "SelectableLabel")); ui.horizontal(|ui| { ui.selectable_value(radio, Enum::First, "First"); ui.selectable_value(radio, Enum::Second, "Second"); @@ -216,7 +213,11 @@ impl WidgetGallery { #[cfg(feature = "chrono")] { let date = date.get_or_insert_with(|| chrono::offset::Utc::now().date_naive()); - ui.add(doc_link_label("DatePickerButton", "DatePickerButton")); + ui.add(doc_link_label_with_crate( + "egui_extras", + "DatePickerButton", + "DatePickerButton", + )); ui.add(egui_extras::DatePickerButton::new(date)); ui.end_row(); } @@ -237,7 +238,7 @@ impl WidgetGallery { }); ui.end_row(); - ui.add(doc_link_label("Plot", "plot")); + ui.add(doc_link_label_with_crate("egui_plot", "Plot", "plot")); example_plot(ui); ui.end_row(); @@ -273,8 +274,16 @@ 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 { + doc_link_label_with_crate("egui", title, search_term) +} + +fn doc_link_label_with_crate<'a>( + crate_name: &'a str, + 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 url = format!("https://docs.rs/{crate_name}?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/demo/window_options.rs b/crates/egui_demo_lib/src/demo/window_options.rs index a69f6cf79..228047263 100644 --- a/crates/egui_demo_lib/src/demo/window_options.rs +++ b/crates/egui_demo_lib/src/demo/window_options.rs @@ -1,3 +1,5 @@ +use egui::Vec2b; + #[derive(Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct WindowOptions { @@ -7,7 +9,7 @@ pub struct WindowOptions { collapsible: bool, resizable: bool, constrain: bool, - scroll2: [bool; 2], + scroll2: Vec2b, disabled_time: f64, anchored: bool, @@ -24,7 +26,7 @@ impl Default for WindowOptions { collapsible: true, resizable: true, constrain: true, - scroll2: [true; 2], + scroll2: Vec2b::TRUE, disabled_time: f64::NEG_INFINITY, anchored: false, anchor: egui::Align2::RIGHT_TOP, diff --git a/crates/egui_extras/src/syntax_highlighting.rs b/crates/egui_extras/src/syntax_highlighting.rs index 775d7207b..8708dcf9f 100644 --- a/crates/egui_extras/src/syntax_highlighting.rs +++ b/crates/egui_extras/src/syntax_highlighting.rs @@ -248,7 +248,7 @@ impl CodeTheme { /// Show UI for changing the color theme. pub fn ui(&mut self, ui: &mut egui::Ui) { ui.horizontal_top(|ui| { - let selected_id = egui::Id::null(); + let selected_id = egui::Id::NULL; let mut selected_tt: TokenType = ui.data_mut(|d| *d.get_persisted_mut_or(selected_id, TokenType::Comment)); diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index c687a7f1c..1aa23eec7 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 _, Rangef, Rect, Response, ScrollArea, Ui, Vec2}; +use egui::{Align, NumExt as _, Rangef, Rect, Response, ScrollArea, Ui, Vec2, Vec2b}; use crate::{ layout::{CellDirection, CellSize}, @@ -165,7 +165,7 @@ struct TableScrollOptions { scroll_offset_y: Option, min_scrolled_height: f32, max_scroll_height: f32, - auto_shrink: [bool; 2], + auto_shrink: Vec2b, } impl Default for TableScrollOptions { @@ -178,7 +178,7 @@ impl Default for TableScrollOptions { scroll_offset_y: None, min_scrolled_height: 200.0, max_scroll_height: 800.0, - auto_shrink: [true; 2], + auto_shrink: Vec2b::TRUE, } } } @@ -335,11 +335,11 @@ impl<'a> TableBuilder<'a> { /// * If true, add blank space outside the table, keeping the table small. /// * If false, add blank space inside the table, expanding the table to fit the containing ui. /// - /// Default: `[true; 2]`. + /// Default: `true`. /// /// See [`ScrollArea::auto_shrink`] for more. - pub fn auto_shrink(mut self, auto_shrink: [bool; 2]) -> Self { - self.scroll_options.auto_shrink = auto_shrink; + pub fn auto_shrink(mut self, auto_shrink: impl Into) -> Self { + self.scroll_options.auto_shrink = auto_shrink.into(); self } @@ -366,9 +366,9 @@ impl<'a> TableBuilder<'a> { fn available_width(&self) -> f32 { self.ui.available_rect_before_wrap().width() - if self.scroll_options.vscroll { - self.ui.spacing().scroll_bar_inner_margin - + self.ui.spacing().scroll_bar_width - + self.ui.spacing().scroll_bar_outer_margin + self.ui.spacing().scroll.bar_inner_margin + + self.ui.spacing().scroll.bar_width + + self.ui.spacing().scroll.bar_outer_margin } else { 0.0 } @@ -577,7 +577,7 @@ impl<'a> Table<'a> { let avail_rect = ui.available_rect_before_wrap(); let mut scroll_area = ScrollArea::new([false, vscroll]) - .auto_shrink([true; 2]) + .auto_shrink(true) .drag_to_scroll(drag_to_scroll) .stick_to_bottom(stick_to_bottom) .min_scrolled_height(min_scrolled_height) @@ -867,7 +867,7 @@ impl<'a> TableBody<'a> { widths: self.widths, max_used_widths: self.max_used_widths, col_index: 0, - striped: self.striped && idx % 2 == 0, + striped: self.striped && (idx + self.row_nr) % 2 == 0, height: row_height_sans_spacing, }, ); @@ -945,7 +945,7 @@ impl<'a> TableBody<'a> { widths: self.widths, max_used_widths: self.max_used_widths, col_index: 0, - striped: self.striped && row_index % 2 == 0, + striped: self.striped && (row_index + self.row_nr) % 2 == 0, height: row_height, }, ); @@ -964,7 +964,7 @@ impl<'a> TableBody<'a> { widths: self.widths, max_used_widths: self.max_used_widths, col_index: 0, - striped: self.striped && row_index % 2 == 0, + striped: self.striped && (row_index + self.row_nr) % 2 == 0, height: row_height, }, ); diff --git a/crates/egui_glium/CHANGELOG.md b/crates/egui_glium/CHANGELOG.md deleted file mode 100644 index c1e98201d..000000000 --- a/crates/egui_glium/CHANGELOG.md +++ /dev/null @@ -1,125 +0,0 @@ -# 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)). - - -## 0.20.1 - 2022-12-11 -* Fix [docs.rs](https://docs.rs/egui_glium) build ([#2420](https://github.com/emilk/egui/pull/2420)). - - -## 0.20.0 - 2022-12-08 -* MSRV (Minimum Supported Rust Version) is now `1.65.0` ([#2314](https://github.com/emilk/egui/pull/2314)). - - -## 0.19.0 - 2022-08-20 -* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)). - - -## 0.18.0 - 2022-04-30 -* Remove "epi" feature ([#1361](https://github.com/emilk/egui/pull/1361)). -* Remove need for `trait epi::NativeTexture` to use the `fn register_native_texture/replace_native_texture` ([#1361](https://github.com/emilk/egui/pull/1361)). -* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)). - - -## 0.17.0 - 2022-02-22 -* `EguiGlium::run` no longer returns the shapes to paint, but stores them internally until you call `EguiGlium::paint` ([#1110](https://github.com/emilk/egui/pull/1110)). -* Optimize the painter and texture uploading ([#1110](https://github.com/emilk/egui/pull/1110)). -* Automatically detect and apply dark or light mode from system ([#1045](https://github.com/emilk/egui/pull/1045)). - - -## 0.16.0 - 2021-12-29 -* Simplified `EguiGlium` interface ([#871](https://github.com/emilk/egui/pull/871)). -* Removed `EguiGlium::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)). -* Updated `glium` to 0.31 ([#930](https://github.com/emilk/egui/pull/930)). -* Changed the `Painter` interface slightly ([#999](https://github.com/emilk/egui/pull/999)). - - -## 0.15.0 - 2021-10-24 -* Remove "http" feature (use https://github.com/emilk/ehttp instead!). -* Implement `epi::NativeTexture` trait for the glium painter. -* Deprecate 'Painter::register_glium_texture'. -* Increase scroll speed. -* Restore window position on startup without flickering. -* A lot of the code has been moved to the new library [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit). -* Fixed reactive mode on windows. - - -## 0.14.0 - 2021-08-24 -* Fixed native file dialogs hanging (eg. when using [`rfd`](https://github.com/PolyMeilex/rfd)). -* Implement drag-and-dropping files into the application. -* [Fix minimize on Windows](https://github.com/emilk/egui/issues/518). -* Change `drag_and_drop_support` to `false` by default (Windows only). See . -* Don't restore window position on Windows, because the position would sometimes be invalid. - - -## 0.13.1 - 2021-06-24 -* Fixed `http` feature flag and docs - - -## 0.13.0 - 2021-06-24 -* Added `EguiGlium::is_quit_event` to replace `control_flow` argument to `EguiGlium::on_event`. -* [Fix modifier key for zoom with mouse wheel on Mac](https://github.com/emilk/egui/issues/401) -* [Fix stuck modifier keys](https://github.com/emilk/egui/pull/479) - - -## 0.12.0 - 2021-05-10 -* Simplify usage with a new `EguiGlium` wrapper type. - - -## 0.11.0 - 2021-04-05 -* [Position IME candidate window next to text cursor](https://github.com/emilk/egui/pull/258). -* [Register your own glium textures](https://github.com/emilk/egui/pull/226). -* [Fix cursor icon flickering on Windows(https://github.com/emilk/egui/pull/218). - - -## 0.10.0 - 2021-02-28 -* [Add shaders for GLSL 1.2, GLSL ES 1.0 and 3.0](https://github.com/emilk/egui/pull/187) - - now `egui` works well on old hardware which supports OpenGL 2.1 only like Raspberry Pi 1 and Zero. - - -## 0.9.0 - 2021-02-07 -* Nothing new - - -## 0.8.0 - 2021-01-17 -* Fixed a bug where key releases weren't sent to egui -* Fixed `set_window_size` for non-native `pixels_per_point`. - - -## 0.7.0 - 2021-01-04 -* `http` `persistence` and `time` are now optional (and opt-in) features. - - -## 0.6.0 - 2020-12-26 -### Added ⭐ -* `egui_glium` will auto-save your app state every 30 seconds. -* `egui_glium` can now set windows as fixed size (e.g. the user can't resize the window). See `egui::App::is_resizable()`. - -### Changed 🔧 -* `egui_glium` will now save you app state to [a better directory](https://docs.rs/directories-next/2.0.0/directories_next/struct.ProjectDirs.html#method.data_dir). -* `egui_glium::run`: the parameter `app` now has signature `Box` (you need to add `Box::new(app)` to your code). -* Window title is now passed via the `trait` function `egui::App::name()`. - -### Fixed 🐛 -* Serialize window size in logical points instead of physical pixels. -* Window position is now restored on restart. - - -## 0.5.0 - 2020-12-13 -* FileStorage::from_path now takes `Into` instead of `String` - - -## 0.4.0 - 2020-11-28 -Started changelog. Features: - -* Input -* Painting -* Clipboard handling -* Open URL:s -* Simple JSON-backed storage diff --git a/crates/egui_glium/Cargo.toml b/crates/egui_glium/Cargo.toml deleted file mode 100644 index a76cde4e3..000000000 --- a/crates/egui_glium/Cargo.toml +++ /dev/null @@ -1,58 +0,0 @@ -[package] -name = "egui_glium" -version = "0.23.0" -authors = ["Emil Ernerfeldt "] -description = "Bindings for using egui natively using the glium library" -edition = "2021" -rust-version = "1.70" -homepage = "https://github.com/emilk/egui/tree/master/crates/egui_glium" -license = "MIT OR Apache-2.0" -readme = "README.md" -repository = "https://github.com/emilk/egui/tree/master/crates/egui_glium" -categories = ["gui", "game-development"] -keywords = ["glium", "egui", "gui", "gamedev"] -include = [ - "../LICENSE-APACHE", - "../LICENSE-MIT", - "**/*.rs", - "Cargo.toml", - "src/shader/*.glsl", -] - -[package.metadata.docs.rs] -all-features = true - - -[features] -default = ["clipboard", "links"] - -## Enable cut/copy/paste to OS clipboard. -## -## If disabled a clipboard will be simulated so you can still copy/paste within the egui app. -clipboard = ["egui-winit/clipboard"] - -## Enable opening links in a browser when an egui hyperlink is clicked. -links = ["egui-winit/links"] - - -[dependencies] -egui = { version = "0.23.0", path = "../egui", default-features = false, features = [ - "bytemuck", -] } -egui-winit = { version = "0.23.0", path = "../egui-winit", default-features = false } - -ahash = { version = "0.8.1", default-features = false, features = [ - "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead - "std", -] } -bytemuck = "1.7" -glium = "0.32" - -#! ### Optional dependencies -## Enable this when generating docs. -document-features = { version = "0.2", optional = true } - - -[dev-dependencies] -egui_demo_lib = { version = "0.23.0", path = "../egui_demo_lib", default-features = false } -image = { version = "0.24", default-features = false, features = ["png"] } diff --git a/crates/egui_glium/README.md b/crates/egui_glium/README.md deleted file mode 100644 index 1c31de5fa..000000000 --- a/crates/egui_glium/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# egui_glium - -[![Latest version](https://img.shields.io/crates/v/egui_glium.svg)](https://crates.io/crates/egui_glium) -[![Documentation](https://docs.rs/egui_glium/badge.svg)](https://docs.rs/egui_glium) -[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) -![MIT](https://img.shields.io/badge/license-MIT-blue.svg) -![Apache](https://img.shields.io/badge/license-Apache-blue.svg) - -This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [glium](https://crates.io/crates/glium) which allows you to write GUI code using egui and compile it and run it natively, cross platform. - -To use on Linux, first run: - -``` -sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev -``` - -This crate depends on [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit). - - -## DEPRECATED - Looking for new maintainer -This crate is no longer being updated. If you are interested in keeping `egui_glium` updated, then fork it to its own repository, make a PR to the egui repo removing it, and then I will give you access to it on crates.io so you can publish new `egui_glium` crates. diff --git a/crates/egui_glium/examples/native_texture.rs b/crates/egui_glium/examples/native_texture.rs deleted file mode 100644 index eb5a956f4..000000000 --- a/crates/egui_glium/examples/native_texture.rs +++ /dev/null @@ -1,138 +0,0 @@ -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release - -use glium::glutin; - -fn main() { - let event_loop = glutin::event_loop::EventLoopBuilder::with_user_event().build(); - let display = create_display(&event_loop); - - let mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop); - - let png_data = include_bytes!("../../../examples/retained_image/src/crab.png"); - let image = load_glium_image(png_data); - let image_size = egui::vec2(image.width as f32, image.height as f32); - // Load to gpu memory - let glium_texture = glium::texture::SrgbTexture2d::new(&display, image).unwrap(); - // Allow us to share the texture with egui: - let glium_texture = std::rc::Rc::new(glium_texture); - // Allocate egui's texture id for GL texture - let texture_id = egui_glium - .painter - .register_native_texture(glium_texture, Default::default()); - // Setup button image size for reasonable image size for button container. - let button_image_size = egui::vec2(32_f32, 32_f32); - - event_loop.run(move |event, _, control_flow| { - let mut redraw = || { - let mut quit = false; - - let repaint_after = egui_glium.run(&display, |egui_ctx| { - egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| { - if ui - .add(egui::Button::image_and_text( - (texture_id, button_image_size), - "Quit", - )) - .clicked() - { - quit = true; - } - }); - egui::Window::new("NativeTextureDisplay").show(egui_ctx, |ui| { - ui.image(texture_id, image_size); - }); - }); - - *control_flow = if quit { - glutin::event_loop::ControlFlow::Exit - } else if repaint_after.is_zero() { - display.gl_window().window().request_redraw(); - glutin::event_loop::ControlFlow::Poll - } else if let Some(repaint_after_instant) = - std::time::Instant::now().checked_add(repaint_after) - { - glutin::event_loop::ControlFlow::WaitUntil(repaint_after_instant) - } else { - glutin::event_loop::ControlFlow::Wait - }; - - { - use glium::Surface as _; - let mut target = display.draw(); - - let color = egui::Rgba::from_rgb(0.1, 0.3, 0.2); - target.clear_color(color[0], color[1], color[2], color[3]); - - // draw things behind egui here - - egui_glium.paint(&display, &mut target); - - // draw things on top of egui here - - target.finish().unwrap(); - } - }; - - match event { - // 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 - glutin::event::Event::RedrawEventsCleared if cfg!(target_os = "windows") => redraw(), - glutin::event::Event::RedrawRequested(_) if !cfg!(target_os = "windows") => redraw(), - - glutin::event::Event::WindowEvent { event, .. } => { - use glutin::event::WindowEvent; - if matches!(event, WindowEvent::CloseRequested | WindowEvent::Destroyed) { - *control_flow = glutin::event_loop::ControlFlow::Exit; - } - - let event_response = egui_glium.on_event(&event); - - if event_response.repaint { - display.gl_window().window().request_redraw(); - } - } - glutin::event::Event::NewEvents(glutin::event::StartCause::ResumeTimeReached { - .. - }) => { - display.gl_window().window().request_redraw(); - } - - _ => (), - } - }); -} - -fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display { - let window_builder = glutin::window::WindowBuilder::new() - .with_resizable(true) - .with_inner_size(glutin::dpi::LogicalSize { - width: 800.0, - height: 600.0, - }) - .with_title("egui_glium example"); - - let context_builder = glutin::ContextBuilder::new() - .with_depth_buffer(0) - .with_stencil_buffer(0) - .with_vsync(true); - - glium::Display::new(window_builder, context_builder, event_loop).unwrap() -} - -fn load_glium_image(png_data: &[u8]) -> glium::texture::RawImage2d<'_, u8> { - // Load image using the image crate: - let image = image::load_from_memory(png_data).unwrap().to_rgba8(); - let image_dimensions = image.dimensions(); - - // Premultiply alpha: - let pixels: Vec<_> = image - .into_vec() - .chunks_exact(4) - .map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3])) - .flat_map(|color| color.to_array()) - .collect(); - - // Convert to glium image: - glium::texture::RawImage2d::from_raw_rgba(pixels, image_dimensions) -} diff --git a/crates/egui_glium/examples/pure_glium.rs b/crates/egui_glium/examples/pure_glium.rs deleted file mode 100644 index 801f7b145..000000000 --- a/crates/egui_glium/examples/pure_glium.rs +++ /dev/null @@ -1,108 +0,0 @@ -//! Example how to use `egui_glium`. - -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release - -use glium::glutin; - -fn main() { - let event_loop = glutin::event_loop::EventLoopBuilder::with_user_event().build(); - let display = create_display(&event_loop); - - let mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop); - - let mut color_test = egui_demo_lib::ColorTest::default(); - - event_loop.run(move |event, _, control_flow| { - let mut redraw = || { - let mut quit = false; - - let repaint_after = egui_glium.run(&display, |egui_ctx| { - egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| { - ui.heading("Hello World!"); - if ui.button("Quit").clicked() { - quit = true; - } - }); - - egui::CentralPanel::default().show(egui_ctx, |ui| { - egui::ScrollArea::vertical().show(ui, |ui| { - color_test.ui(ui); - }); - }); - }); - - *control_flow = if quit { - glutin::event_loop::ControlFlow::Exit - } else if repaint_after.is_zero() { - display.gl_window().window().request_redraw(); - glutin::event_loop::ControlFlow::Poll - } else if let Some(repaint_after_instant) = - std::time::Instant::now().checked_add(repaint_after) - { - glutin::event_loop::ControlFlow::WaitUntil(repaint_after_instant) - } else { - glutin::event_loop::ControlFlow::Wait - }; - - { - use glium::Surface as _; - let mut target = display.draw(); - - let color = egui::Rgba::from_rgb(0.1, 0.3, 0.2); - target.clear_color(color[0], color[1], color[2], color[3]); - - // draw things behind egui here - - egui_glium.paint(&display, &mut target); - - // draw things on top of egui here - - target.finish().unwrap(); - } - }; - - match event { - // 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 - glutin::event::Event::RedrawEventsCleared if cfg!(target_os = "windows") => redraw(), - glutin::event::Event::RedrawRequested(_) if !cfg!(target_os = "windows") => redraw(), - - glutin::event::Event::WindowEvent { event, .. } => { - use glutin::event::WindowEvent; - if matches!(event, WindowEvent::CloseRequested | WindowEvent::Destroyed) { - *control_flow = glutin::event_loop::ControlFlow::Exit; - } - - let event_response = egui_glium.on_event(&event); - - if event_response.repaint { - display.gl_window().window().request_redraw(); - } - } - glutin::event::Event::NewEvents(glutin::event::StartCause::ResumeTimeReached { - .. - }) => { - display.gl_window().window().request_redraw(); - } - _ => (), - } - }); -} - -fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display { - let window_builder = glutin::window::WindowBuilder::new() - .with_resizable(true) - .with_inner_size(glutin::dpi::LogicalSize { - width: 800.0, - height: 600.0, - }) - .with_title("egui_glium example"); - - let context_builder = glutin::ContextBuilder::new() - .with_depth_buffer(0) - .with_stencil_buffer(0) - .with_vsync(true); - - glium::Display::new(window_builder, context_builder, event_loop).unwrap() -} diff --git a/crates/egui_glium/src/lib.rs b/crates/egui_glium/src/lib.rs deleted file mode 100644 index da60637dc..000000000 --- a/crates/egui_glium/src/lib.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! [`egui`] bindings for [`glium`](https://github.com/glium/glium). -//! -//! The main type you want to use is [`EguiGlium`]. -//! -//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead. -//! -//! ## Feature flags -#![cfg_attr(feature = "document-features", doc = document_features::document_features!())] -//! - -#![allow(clippy::float_cmp)] -#![allow(clippy::manual_range_contains)] -#![forbid(unsafe_code)] - -mod painter; -pub use painter::Painter; - -pub use egui_winit; - -use egui_winit::winit::event_loop::EventLoopWindowTarget; -pub use egui_winit::EventResponse; - -// ---------------------------------------------------------------------------- - -/// Convenience wrapper for using [`egui`] from a [`glium`] app. -pub struct EguiGlium { - pub egui_ctx: egui::Context, - pub egui_winit: egui_winit::State, - pub painter: crate::Painter, - - shapes: Vec, - textures_delta: egui::TexturesDelta, -} - -impl EguiGlium { - pub fn new(display: &glium::Display, event_loop: &EventLoopWindowTarget) -> Self { - let painter = crate::Painter::new(display); - - let mut egui_winit = egui_winit::State::new(event_loop); - egui_winit.set_max_texture_side(painter.max_texture_side()); - let pixels_per_point = display.gl_window().window().scale_factor() as f32; - egui_winit.set_pixels_per_point(pixels_per_point); - - Self { - egui_ctx: Default::default(), - egui_winit, - painter, - shapes: Default::default(), - textures_delta: Default::default(), - } - } - - pub fn on_event(&mut self, event: &glium::glutin::event::WindowEvent<'_>) -> EventResponse { - self.egui_winit.on_event(&self.egui_ctx, event) - } - - /// Returns `true` if egui requests a repaint. - /// - /// Call [`Self::paint`] later to paint. - pub fn run( - &mut self, - display: &glium::Display, - run_ui: impl FnMut(&egui::Context), - ) -> std::time::Duration { - let raw_input = self - .egui_winit - .take_egui_input(display.gl_window().window()); - let egui::FullOutput { - platform_output, - repaint_after, - textures_delta, - shapes, - } = self.egui_ctx.run(raw_input, run_ui); - - self.egui_winit.handle_platform_output( - display.gl_window().window(), - &self.egui_ctx, - platform_output, - ); - - self.shapes = shapes; - self.textures_delta.append(textures_delta); - - repaint_after - } - - /// Paint the results of the last call to [`Self::run`]. - pub fn paint(&mut self, display: &glium::Display, target: &mut T) { - let shapes = std::mem::take(&mut self.shapes); - let textures_delta = std::mem::take(&mut self.textures_delta); - let clipped_primitives = self.egui_ctx.tessellate(shapes); - self.painter.paint_and_update_textures( - display, - target, - self.egui_ctx.pixels_per_point(), - &clipped_primitives, - &textures_delta, - ); - } -} diff --git a/crates/egui_glium/src/painter.rs b/crates/egui_glium/src/painter.rs deleted file mode 100644 index 39da163b5..000000000 --- a/crates/egui_glium/src/painter.rs +++ /dev/null @@ -1,381 +0,0 @@ -#![allow(deprecated)] // legacy implement_vertex macro -#![allow(semicolon_in_expressions_from_macros)] // glium::program! macro - -use egui::{ - epaint::{textures::TextureFilter, Primitive}, - TextureOptions, -}; - -use { - egui::{emath::Rect, epaint::Mesh}, - glium::{ - implement_vertex, - index::PrimitiveType, - texture::{self, srgb_texture2d::SrgbTexture2d}, - uniform, - uniforms::{MagnifySamplerFilter, MinifySamplerFilter, SamplerWrapFunction}, - }, - std::rc::Rc, -}; - -pub struct Painter { - max_texture_side: usize, - program: glium::Program, - - textures: ahash::HashMap, - - /// [`egui::TextureId::User`] index - next_native_tex_id: u64, -} - -fn create_program( - facade: &dyn glium::backend::Facade, - vertex_shader: &str, - fragment_shader: &str, -) -> glium::program::Program { - let input = glium::program::ProgramCreationInput::SourceCode { - vertex_shader, - tessellation_control_shader: None, - tessellation_evaluation_shader: None, - geometry_shader: None, - fragment_shader, - transform_feedback_varyings: None, - outputs_srgb: true, - uses_point_size: false, - }; - - glium::program::Program::new(facade, input) - .unwrap_or_else(|err| panic!("Failed to compile shader: {}", err)) -} - -impl Painter { - pub fn new(facade: &dyn glium::backend::Facade) -> Painter { - use glium::CapabilitiesSource as _; - let max_texture_side = facade.get_capabilities().max_texture_size as _; - - let program = if facade - .get_context() - .is_glsl_version_supported(&glium::Version(glium::Api::Gl, 1, 4)) - { - eprintln!("Using GL 1.4"); - create_program( - facade, - include_str!("shader/vertex_140.glsl"), - include_str!("shader/fragment_140.glsl"), - ) - } else if facade - .get_context() - .is_glsl_version_supported(&glium::Version(glium::Api::Gl, 1, 2)) - { - eprintln!("Using GL 1.2"); - create_program( - facade, - include_str!("shader/vertex_120.glsl"), - include_str!("shader/fragment_120.glsl"), - ) - } else if facade - .get_context() - .is_glsl_version_supported(&glium::Version(glium::Api::GlEs, 3, 0)) - { - eprintln!("Using GL ES 3.0"); - create_program( - facade, - include_str!("shader/vertex_300es.glsl"), - include_str!("shader/fragment_300es.glsl"), - ) - } else if facade - .get_context() - .is_glsl_version_supported(&glium::Version(glium::Api::GlEs, 1, 0)) - { - eprintln!("Using GL ES 1.0"); - create_program( - facade, - include_str!("shader/vertex_100es.glsl"), - include_str!("shader/fragment_100es.glsl"), - ) - } else { - panic!( - "Failed to find a compatible shader for OpenGL version {:?}", - facade.get_version() - ) - }; - - Painter { - max_texture_side, - program, - textures: Default::default(), - next_native_tex_id: 0, - } - } - - pub fn max_texture_side(&self) -> usize { - self.max_texture_side - } - - pub fn paint_and_update_textures( - &mut self, - display: &glium::Display, - target: &mut T, - pixels_per_point: f32, - clipped_primitives: &[egui::ClippedPrimitive], - textures_delta: &egui::TexturesDelta, - ) { - for (id, image_delta) in &textures_delta.set { - self.set_texture(display, *id, image_delta); - } - - self.paint_primitives(display, target, pixels_per_point, clipped_primitives); - - for &id in &textures_delta.free { - self.free_texture(id); - } - } - - /// Main entry-point for painting a frame. - /// You should call `target.clear_color(..)` before - /// and `target.finish()` after this. - pub fn paint_primitives( - &mut self, - display: &glium::Display, - target: &mut T, - pixels_per_point: f32, - clipped_primitives: &[egui::ClippedPrimitive], - ) { - for egui::ClippedPrimitive { - clip_rect, - primitive, - } in clipped_primitives - { - match primitive { - Primitive::Mesh(mesh) => { - self.paint_mesh(target, display, pixels_per_point, clip_rect, mesh); - } - Primitive::Callback(_) => { - panic!("Custom rendering callbacks are not implemented in egui_glium"); - } - } - } - } - - #[inline(never)] // Easier profiling - fn paint_mesh( - &mut self, - target: &mut T, - display: &glium::Display, - pixels_per_point: f32, - clip_rect: &Rect, - mesh: &Mesh, - ) { - debug_assert!(mesh.is_valid()); - - let vertex_buffer = { - #[repr(C)] - #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] - struct Vertex { - a_pos: [f32; 2], - a_tc: [f32; 2], - a_srgba: [u8; 4], - } - implement_vertex!(Vertex, a_pos, a_tc, a_srgba); - - let vertices: &[Vertex] = bytemuck::cast_slice(&mesh.vertices); - - // TODO(emilk): we should probably reuse the [`VertexBuffer`] instead of allocating a new one each frame. - glium::VertexBuffer::new(display, vertices).unwrap() - }; - - // TODO(emilk): we should probably reuse the [`IndexBuffer`] instead of allocating a new one each frame. - let index_buffer = - glium::IndexBuffer::new(display, PrimitiveType::TrianglesList, &mesh.indices).unwrap(); - - let (width_in_pixels, height_in_pixels) = display.get_framebuffer_dimensions(); - let width_in_points = width_in_pixels as f32 / pixels_per_point; - let height_in_points = height_in_pixels as f32 / pixels_per_point; - - if let Some(texture) = self.texture(mesh.texture_id) { - // The texture coordinates for text are so that both nearest and linear should work with the egui font texture. - let mag_filter = match texture.options.magnification { - TextureFilter::Nearest => MagnifySamplerFilter::Nearest, - TextureFilter::Linear => MagnifySamplerFilter::Linear, - }; - let min_filter = match texture.options.minification { - TextureFilter::Nearest => MinifySamplerFilter::Nearest, - TextureFilter::Linear => MinifySamplerFilter::Linear, - }; - - let sampler = texture - .glium_texture - .sampled() - .magnify_filter(mag_filter) - .minify_filter(min_filter) - .wrap_function(SamplerWrapFunction::Clamp); - - let uniforms = uniform! { - u_screen_size: [width_in_points, height_in_points], - u_sampler: sampler, - }; - - // egui outputs colors with premultiplied alpha: - let color_blend_func = glium::BlendingFunction::Addition { - source: glium::LinearBlendingFactor::One, - destination: glium::LinearBlendingFactor::OneMinusSourceAlpha, - }; - - // Less important, but this is technically the correct alpha blend function - // when you want to make use of the framebuffer alpha (for screenshots, compositing, etc). - let alpha_blend_func = glium::BlendingFunction::Addition { - source: glium::LinearBlendingFactor::OneMinusDestinationAlpha, - destination: glium::LinearBlendingFactor::One, - }; - - let blend = glium::Blend { - color: color_blend_func, - alpha: alpha_blend_func, - ..Default::default() - }; - - // egui outputs mesh in both winding orders: - let backface_culling = glium::BackfaceCullingMode::CullingDisabled; - - // Transform clip rect to physical pixels: - let clip_min_x = pixels_per_point * clip_rect.min.x; - let clip_min_y = pixels_per_point * clip_rect.min.y; - let clip_max_x = pixels_per_point * clip_rect.max.x; - let clip_max_y = pixels_per_point * clip_rect.max.y; - - // Make sure clip rect can fit within a `u32`: - let clip_min_x = clip_min_x.clamp(0.0, width_in_pixels as f32); - let clip_min_y = clip_min_y.clamp(0.0, height_in_pixels as f32); - let clip_max_x = clip_max_x.clamp(clip_min_x, width_in_pixels as f32); - let clip_max_y = clip_max_y.clamp(clip_min_y, height_in_pixels as f32); - - let clip_min_x = clip_min_x.round() as u32; - let clip_min_y = clip_min_y.round() as u32; - let clip_max_x = clip_max_x.round() as u32; - let clip_max_y = clip_max_y.round() as u32; - - let params = glium::DrawParameters { - blend, - backface_culling, - scissor: Some(glium::Rect { - left: clip_min_x, - bottom: height_in_pixels - clip_max_y, - width: clip_max_x - clip_min_x, - height: clip_max_y - clip_min_y, - }), - ..Default::default() - }; - - target - .draw( - &vertex_buffer, - &index_buffer, - &self.program, - &uniforms, - ¶ms, - ) - .unwrap(); - } - } - - // ------------------------------------------------------------------------ - - pub fn set_texture( - &mut self, - facade: &dyn glium::backend::Facade, - tex_id: egui::TextureId, - delta: &egui::epaint::ImageDelta, - ) { - let pixels: Vec<(u8, u8, u8, u8)> = match &delta.image { - egui::ImageData::Color(image) => { - assert_eq!( - image.width() * image.height(), - image.pixels.len(), - "Mismatch between texture size and texel count" - ); - image.pixels.iter().map(|color| color.to_tuple()).collect() - } - egui::ImageData::Font(image) => image - .srgba_pixels(None) - .map(|color| color.to_tuple()) - .collect(), - }; - let glium_image = glium::texture::RawImage2d { - data: std::borrow::Cow::Owned(pixels), - width: delta.image.width() as _, - height: delta.image.height() as _, - format: glium::texture::ClientFormat::U8U8U8U8, - }; - let format = texture::SrgbFormat::U8U8U8U8; - let mipmaps = texture::MipmapsOption::NoMipmap; - - if let Some(pos) = delta.pos { - // update a sub-region - if let Some(user_texture) = self.textures.get_mut(&tex_id) { - let rect = glium::Rect { - left: pos[0] as _, - bottom: pos[1] as _, - width: glium_image.width, - height: glium_image.height, - }; - user_texture - .glium_texture - .main_level() - .write(rect, glium_image); - - user_texture.options = delta.options; - } - } else { - let gl_texture = - SrgbTexture2d::with_format(facade, glium_image, format, mipmaps).unwrap(); - - let user_texture = EguiTexture::new(gl_texture.into(), delta.options); - self.textures.insert(tex_id, user_texture); - } - } - - pub fn free_texture(&mut self, tex_id: egui::TextureId) { - self.textures.remove(&tex_id); - } - - fn texture(&self, texture_id: egui::TextureId) -> Option<&EguiTexture> { - self.textures.get(&texture_id) - } - - pub fn register_native_texture( - &mut self, - native: Rc, - options: TextureOptions, - ) -> egui::TextureId { - let id = egui::TextureId::User(self.next_native_tex_id); - self.next_native_tex_id += 1; - - let texture = EguiTexture::new(native, options); - self.textures.insert(id, texture); - id - } - - pub fn replace_native_texture( - &mut self, - id: egui::TextureId, - replacing: Rc, - options: TextureOptions, - ) { - let texture = EguiTexture::new(replacing, options); - self.textures.insert(id, texture); - } -} - -struct EguiTexture { - glium_texture: Rc, - options: TextureOptions, -} - -impl EguiTexture { - fn new(glium_texture: Rc, options: TextureOptions) -> Self { - Self { - glium_texture, - options, - } - } -} diff --git a/crates/egui_glium/src/shader/fragment_100es.glsl b/crates/egui_glium/src/shader/fragment_100es.glsl deleted file mode 100644 index 912f47f8f..000000000 --- a/crates/egui_glium/src/shader/fragment_100es.glsl +++ /dev/null @@ -1,38 +0,0 @@ -#version 100 - -precision mediump float; -uniform sampler2D u_sampler; -varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA -varying vec2 v_tc; - -// 0-255 sRGB from 0-1 linear -vec3 srgb_from_linear(vec3 rgb) { - bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); - vec3 lower = rgb * vec3(3294.6); - vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); - return mix(higher, lower, vec3(cutoff)); -} - -vec4 srgba_from_linear(vec4 rgba) { - return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); -} - -// 0-1 linear from 0-255 sRGB -vec3 linear_from_srgb(vec3 srgb) { - bvec3 cutoff = lessThan(srgb, vec3(10.31475)); - vec3 lower = srgb / vec3(3294.6); - vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); - return mix(higher, lower, vec3(cutoff)); -} - -vec4 linear_from_srgba(vec4 srgba) { - return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0); -} - -void main() { - // WebGL doesn't come with sRGBA textures: - vec4 texture_in_gamma = texture2D(u_sampler, v_tc); - - // Multiply vertex color with texture color (in gamma space). - gl_FragColor = v_rgba_gamma * texture_in_gamma; -} diff --git a/crates/egui_glium/src/shader/fragment_120.glsl b/crates/egui_glium/src/shader/fragment_120.glsl deleted file mode 100644 index e697278d9..000000000 --- a/crates/egui_glium/src/shader/fragment_120.glsl +++ /dev/null @@ -1,31 +0,0 @@ -#version 120 - -uniform sampler2D u_sampler; -varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA -varying vec2 v_tc; - -// 0-255 sRGB from 0-1 linear -vec3 srgb_from_linear(vec3 rgb) { - bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); - vec3 lower = rgb * vec3(3294.6); - vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); - return mix(higher, lower, vec3(cutoff)); -} - -// 0-255 sRGBA from 0-1 linear -vec4 srgba_from_linear(vec4 rgba) { - return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); -} - -// 0-1 gamma from 0-1 linear -vec4 gamma_from_linear_rgba(vec4 linear_rgba) { - return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a); -} - -void main() { - // The texture is set up with `SRGB8_ALPHA8` - vec4 texture_in_gamma = gamma_from_linear_rgba(texture2D(u_sampler, v_tc)); - - // Multiply vertex color with texture color (in gamma space). - gl_FragColor = v_rgba_gamma * texture_in_gamma; -} diff --git a/crates/egui_glium/src/shader/fragment_140.glsl b/crates/egui_glium/src/shader/fragment_140.glsl deleted file mode 100644 index 4db357f51..000000000 --- a/crates/egui_glium/src/shader/fragment_140.glsl +++ /dev/null @@ -1,32 +0,0 @@ -#version 140 - -uniform sampler2D u_sampler; -in vec4 v_rgba_gamma; -in vec2 v_tc; -out vec4 f_color; - -// 0-255 sRGB from 0-1 linear -vec3 srgb_from_linear(vec3 rgb) { - bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); - vec3 lower = rgb * vec3(3294.6); - vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); - return mix(higher, lower, vec3(cutoff)); -} - -// 0-255 sRGBA from 0-1 linear -vec4 srgba_from_linear(vec4 rgba) { - return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); -} - -// 0-1 gamma from 0-1 linear -vec4 gamma_from_linear_rgba(vec4 linear_rgba) { - return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a); -} - -void main() { - // The texture is set up with `SRGB8_ALPHA8` - vec4 texture_in_gamma = gamma_from_linear_rgba(texture(u_sampler, v_tc)); - - // Multiply vertex color with texture color (in gamma space). - f_color = v_rgba_gamma * texture_in_gamma; -} diff --git a/crates/egui_glium/src/shader/fragment_300es.glsl b/crates/egui_glium/src/shader/fragment_300es.glsl deleted file mode 100644 index 8d4786165..000000000 --- a/crates/egui_glium/src/shader/fragment_300es.glsl +++ /dev/null @@ -1,32 +0,0 @@ -#version 300 es - -precision mediump float; -uniform sampler2D u_sampler; -varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA -varying vec2 v_tc; - -// 0-255 sRGB from 0-1 linear -vec3 srgb_from_linear(vec3 rgb) { - bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); - vec3 lower = rgb * vec3(3294.6); - vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); - return mix(higher, lower, vec3(cutoff)); -} - -// 0-255 sRGBA from 0-1 linear -vec4 srgba_from_linear(vec4 rgba) { - return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); -} - -// 0-1 gamma from 0-1 linear -vec4 gamma_from_linear_rgba(vec4 linear_rgba) { - return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a); -} - -void main() { - // The texture is set up with `SRGB8_ALPHA8` - vec4 texture_in_gamma = gamma_from_linear_rgba(texture2D(u_sampler, v_tc)); - - // Multiply vertex color with texture color (in gamma space). - gl_FragColor = v_rgba_gamma * texture_in_gamma; -} diff --git a/crates/egui_glium/src/shader/vertex_100es.glsl b/crates/egui_glium/src/shader/vertex_100es.glsl deleted file mode 100644 index 4cc481365..000000000 --- a/crates/egui_glium/src/shader/vertex_100es.glsl +++ /dev/null @@ -1,19 +0,0 @@ -#version 100 - -precision mediump float; -uniform vec2 u_screen_size; -attribute vec2 a_pos; -attribute vec2 a_tc; -attribute vec4 a_srgba; -varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA -varying vec2 v_tc; - -void main() { - gl_Position = vec4( - 2.0 * a_pos.x / u_screen_size.x - 1.0, - 1.0 - 2.0 * a_pos.y / u_screen_size.y, - 0.0, - 1.0); - v_rgba_gamma = a_srgba / 255.0; - v_tc = a_tc; -} diff --git a/crates/egui_glium/src/shader/vertex_120.glsl b/crates/egui_glium/src/shader/vertex_120.glsl deleted file mode 100644 index 2f1a03239..000000000 --- a/crates/egui_glium/src/shader/vertex_120.glsl +++ /dev/null @@ -1,18 +0,0 @@ -#version 120 - -uniform vec2 u_screen_size; -attribute vec2 a_pos; -attribute vec4 a_srgba; // 0-255 sRGB -attribute vec2 a_tc; -varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA -varying vec2 v_tc; - -void main() { - gl_Position = vec4( - 2.0 * a_pos.x / u_screen_size.x - 1.0, - 1.0 - 2.0 * a_pos.y / u_screen_size.y, - 0.0, - 1.0); - v_rgba_gamma = a_srgba / 255.0; - v_tc = a_tc; -} diff --git a/crates/egui_glium/src/shader/vertex_140.glsl b/crates/egui_glium/src/shader/vertex_140.glsl deleted file mode 100644 index 03d0a8d76..000000000 --- a/crates/egui_glium/src/shader/vertex_140.glsl +++ /dev/null @@ -1,18 +0,0 @@ -#version 140 - -uniform vec2 u_screen_size; -in vec2 a_pos; -in vec4 a_srgba; // 0-255 sRGB -in vec2 a_tc; -out vec4 v_rgba_gamma; -out vec2 v_tc; - -void main() { - gl_Position = vec4( - 2.0 * a_pos.x / u_screen_size.x - 1.0, - 1.0 - 2.0 * a_pos.y / u_screen_size.y, - 0.0, - 1.0); - v_rgba_gamma = a_srgba / 255.0; - v_tc = a_tc; -} diff --git a/crates/egui_glium/src/shader/vertex_300es.glsl b/crates/egui_glium/src/shader/vertex_300es.glsl deleted file mode 100644 index 60fdac1c0..000000000 --- a/crates/egui_glium/src/shader/vertex_300es.glsl +++ /dev/null @@ -1,19 +0,0 @@ -#version 300 es - -precision mediump float; -uniform vec2 u_screen_size; -attribute vec2 a_pos; -attribute vec2 a_tc; -attribute vec4 a_srgba; -varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA -varying vec2 v_tc; - -void main() { - gl_Position = vec4( - 2.0 * a_pos.x / u_screen_size.x - 1.0, - 1.0 - 2.0 * a_pos.y / u_screen_size.y, - 0.0, - 1.0); - v_rgba_gamma = a_srgba / 255.0; - v_tc = a_tc; -} diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 111f5f65c..882f30f2a 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -77,47 +77,13 @@ impl Default for CoordinatesFormatter { const MIN_LINE_SPACING_IN_POINTS: f64 = 6.0; // TODO(emilk): large enough for a wide label -/// Two bools, one for each axis (X and Y). -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct AxisBools { - pub x: bool, - pub y: bool, -} - -impl AxisBools { - #[inline] - pub fn new(x: bool, y: bool) -> Self { - Self { x, y } - } - - #[inline] - pub fn any(&self) -> bool { - self.x || self.y - } -} - -impl From for AxisBools { - #[inline] - fn from(val: bool) -> Self { - AxisBools { x: val, y: val } - } -} - -impl From<[bool; 2]> for AxisBools { - #[inline] - fn from([x, y]: [bool; 2]) -> Self { - AxisBools { x, y } - } -} - /// Information about the plot that has to persist between frames. #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Clone)] 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, + bounds_modified: Vec2b, hovered_entry: Option, hidden_items: ahash::HashSet, @@ -171,7 +137,7 @@ struct CursorLinkGroups(HashMap>); #[derive(Clone)] struct LinkedBounds { bounds: PlotBounds, - bounds_modified: AxisBools, + bounds_modified: Vec2b, } #[derive(Default, Clone)] @@ -211,18 +177,18 @@ pub struct PlotResponse { pub struct Plot { id_source: Id, - center_axis: AxisBools, - allow_zoom: AxisBools, - allow_drag: AxisBools, + center_axis: Vec2b, + allow_zoom: Vec2b, + allow_drag: Vec2b, allow_scroll: bool, allow_double_click_reset: bool, allow_boxed_zoom: bool, - auto_bounds: AxisBools, + auto_bounds: Vec2b, min_auto_bounds: PlotBounds, margin_fraction: Vec2, boxed_zoom_pointer_button: PointerButton, - linked_axes: Option<(Id, AxisBools)>, - linked_cursors: Option<(Id, AxisBools)>, + linked_axes: Option<(Id, Vec2b)>, + linked_cursors: Option<(Id, Vec2b)>, min_size: Vec2, width: Option, @@ -240,8 +206,8 @@ pub struct Plot { y_axes: Vec, // default y axes legend_config: Option, show_background: bool, - show_axes: AxisBools, - show_grid: AxisBools, + show_axes: Vec2b, + show_grid: Vec2b, grid_spacers: [GridSpacer; 2], sharp_grid_lines: bool, clamp_grid: bool, @@ -357,7 +323,7 @@ impl Plot { /// Note: Allowing zoom in one axis but not the other may lead to unexpected results if used in combination with `data_aspect`. pub fn allow_zoom(mut self, on: T) -> Self where - T: Into, + T: Into, { self.allow_zoom = on.into(); self @@ -401,7 +367,7 @@ impl Plot { /// Whether to allow dragging in the plot to move the bounds. Default: `true`. pub fn allow_drag(mut self, on: T) -> Self where - T: Into, + T: Into, { self.allow_drag = on.into(); self @@ -530,6 +496,7 @@ impl Plot { } /// Whether or not to show the background [`Rect`]. + /// /// Can be useful to disable if the plot is overlaid over existing content. /// Default: `true`. pub fn show_background(mut self, show: bool) -> Self { @@ -539,16 +506,16 @@ impl Plot { /// Show axis labels and grid tick values on the side of the plot. /// - /// Default: `[true; 2]`. - pub fn show_axes(mut self, show: impl Into) -> Self { + /// Default: `true`. + pub fn show_axes(mut self, show: impl Into) -> Self { self.show_axes = show.into(); self } /// Show a grid overlay on the plot. /// - /// Default: `[true; 2]`. - pub fn show_grid(mut self, show: impl Into) -> Self { + /// Default: `true`. + pub fn show_grid(mut self, show: impl Into) -> Self { self.show_grid = show.into(); self } @@ -558,7 +525,7 @@ impl Plot { pub fn link_axis(mut self, group_id: impl Into, link_x: bool, link_y: bool) -> Self { self.linked_axes = Some(( group_id.into(), - AxisBools { + Vec2b { x: link_x, y: link_y, }, @@ -571,7 +538,7 @@ impl Plot { pub fn link_cursor(mut self, group_id: impl Into, link_x: bool, link_y: bool) -> Self { self.linked_cursors = Some(( group_id.into(), - AxisBools { + Vec2b { x: link_x, y: link_y, }, @@ -840,7 +807,7 @@ impl Plot { if let Some((name, _)) = linked_axes.as_ref() { ui.memory_mut(|memory| { let link_groups: &mut BoundsLinkGroups = - memory.data.get_temp_mut_or_default(Id::null()); + memory.data.get_temp_mut_or_default(Id::NULL); link_groups.0.remove(name); }); }; @@ -925,7 +892,7 @@ impl Plot { // Find the cursors from other plots we need to draw let draw_cursors: Vec = if let Some((id, _)) = linked_cursors.as_ref() { ui.memory_mut(|memory| { - let frames: &mut CursorLinkGroups = memory.data.get_temp_mut_or_default(Id::null()); + let frames: &mut CursorLinkGroups = memory.data.get_temp_mut_or_default(Id::NULL); let cursors = frames.0.entry(*id).or_default(); // Look for our previous frame @@ -954,7 +921,7 @@ impl Plot { if let Some((id, axes)) = linked_axes.as_ref() { ui.memory_mut(|memory| { let link_groups: &mut BoundsLinkGroups = - memory.data.get_temp_mut_or_default(Id::null()); + memory.data.get_temp_mut_or_default(Id::NULL); if let Some(linked_bounds) = link_groups.0.get(id) { if axes.x { bounds.set_x(&linked_bounds.bounds); @@ -1197,7 +1164,7 @@ impl Plot { if let Some((id, _)) = linked_cursors.as_ref() { // Push the frame we just drew to the list of frames ui.memory_mut(|memory| { - let frames: &mut CursorLinkGroups = memory.data.get_temp_mut_or_default(Id::null()); + let frames: &mut CursorLinkGroups = memory.data.get_temp_mut_or_default(Id::NULL); let cursors = frames.0.entry(*id).or_default(); cursors.push(PlotFrameCursors { id: plot_id, @@ -1210,7 +1177,7 @@ impl Plot { // Save the linked bounds. ui.memory_mut(|memory| { let link_groups: &mut BoundsLinkGroups = - memory.data.get_temp_mut_or_default(Id::null()); + memory.data.get_temp_mut_or_default(Id::NULL); link_groups.0.insert( *id, LinkedBounds { @@ -1245,7 +1212,7 @@ impl Plot { } fn axis_widgets( - show_axes: AxisBools, + show_axes: Vec2b, plot_rect: Rect, [x_axes, y_axes]: [&[AxisHints]; 2], ) -> [Vec; 2] { @@ -1405,7 +1372,7 @@ impl PlotUi { Vec2::new(delta.x / dp_dv[0] as f32, delta.y / dp_dv[1] as f32) } - /// Read the transform netween plot coordinates and screen coordinates. + /// Read the transform between plot coordinates and screen coordinates. pub fn transform(&self) -> &PlotTransform { &self.last_plot_transform } @@ -1616,7 +1583,7 @@ struct PreparedPlot { coordinates_formatter: Option<(Corner, CoordinatesFormatter)>, // axis_formatters: [AxisFormatter; 2], transform: PlotTransform, - show_grid: AxisBools, + show_grid: Vec2b, grid_spacers: [GridSpacer; 2], draw_cursor_x: bool, draw_cursor_y: bool, diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index 8cfe16de0..3eaab9417 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -36,6 +36,7 @@ mod rect_transform; mod rot2; pub mod smart_aim; mod vec2; +mod vec2b; pub use { align::{Align, Align2}, @@ -47,27 +48,22 @@ pub use { rect_transform::*, rot2::*, vec2::*, + vec2b::*, }; // ---------------------------------------------------------------------------- /// Helper trait to implement [`lerp`] and [`remap`]. pub trait One { - fn one() -> Self; + const ONE: Self; } impl One for f32 { - #[inline(always)] - fn one() -> Self { - 1.0 - } + const ONE: Self = 1.0; } impl One for f64 { - #[inline(always)] - fn one() -> Self { - 1.0 - } + const ONE: Self = 1.0; } /// Helper trait to implement [`lerp`] and [`remap`]. @@ -105,7 +101,7 @@ where R: Copy + Add, { let range = range.into(); - (T::one() - t) * *range.start() + t * *range.end() + (T::ONE - t) * *range.start() + t * *range.end() } /// Where in the range is this value? Returns 0-1 if within the range. @@ -172,7 +168,7 @@ where crate::emath_assert!(from.start() != from.end()); let t = (x - *from.start()) / (*from.end() - *from.start()); // Ensure no numerical inaccuracies sneak in: - if T::one() <= t { + if T::ONE <= t { *to.end() } else { lerp(to, t) diff --git a/crates/emath/src/range.rs b/crates/emath/src/range.rs index b7b975c45..6de7b2a09 100644 --- a/crates/emath/src/range.rs +++ b/crates/emath/src/range.rs @@ -97,6 +97,16 @@ impl Rangef { } } + /// Flip the min and the max + #[inline] + #[must_use] + pub fn flip(self) -> Self { + Self { + min: self.max, + max: self.min, + } + } + /// The overlap of two ranges, i.e. the range that is contained by both. /// /// If the ranges do not overlap, returns a range with `span() < 0.0`. diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index f2d037cae..8a5670fdf 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -150,6 +150,34 @@ impl Rect { rect } + #[inline] + #[must_use] + pub fn with_min_x(mut self, min_x: f32) -> Self { + self.min.x = min_x; + self + } + + #[inline] + #[must_use] + pub fn with_min_y(mut self, min_y: f32) -> Self { + self.min.y = min_y; + self + } + + #[inline] + #[must_use] + pub fn with_max_x(mut self, max_x: f32) -> Self { + self.max.x = max_x; + self + } + + #[inline] + #[must_use] + pub fn with_max_y(mut self, max_y: f32) -> Self { + self.max.y = max_y; + self + } + /// Expand by this much in each direction, keeping the center #[must_use] pub fn expand(self, amnt: f32) -> Self { diff --git a/crates/emath/src/vec2.rs b/crates/emath/src/vec2.rs index 133c3fd3c..d4a2c3300 100644 --- a/crates/emath/src/vec2.rs +++ b/crates/emath/src/vec2.rs @@ -1,4 +1,4 @@ -use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign}; +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; /// A vector has a direction and length. /// A [`Vec2`] is often used to represent a size. @@ -203,7 +203,8 @@ impl Vec2 { /// ``` #[inline(always)] pub fn angled(angle: f32) -> Self { - vec2(angle.cos(), angle.sin()) + let (sin, cos) = angle.sin_cos(); + vec2(cos, sin) } #[must_use] @@ -274,6 +275,16 @@ impl Vec2 { self.x.max(self.y) } + /// Swizzle the axes. + #[inline] + #[must_use] + pub fn yx(self) -> Vec2 { + Vec2 { + x: self.y, + y: self.x, + } + } + #[must_use] #[inline] pub fn clamp(self, min: Self, max: Self) -> Self { @@ -397,6 +408,14 @@ impl MulAssign for Vec2 { } } +impl DivAssign for Vec2 { + #[inline(always)] + fn div_assign(&mut self, rhs: f32) { + self.x /= rhs; + self.y /= rhs; + } +} + impl Mul for Vec2 { type Output = Vec2; @@ -460,4 +479,20 @@ fn test_vec2() { assert_eq!(Vec2::DOWN.angle(), 0.25 * TAU); almost_eq!(Vec2::LEFT.angle(), 0.50 * TAU); assert_eq!(Vec2::UP.angle(), -0.25 * TAU); + + let mut assignment = vec2(1.0, 2.0); + assignment += vec2(3.0, 4.0); + assert_eq!(assignment, vec2(4.0, 6.0)); + + let mut assignment = vec2(4.0, 6.0); + assignment -= vec2(1.0, 2.0); + assert_eq!(assignment, vec2(3.0, 4.0)); + + let mut assignment = vec2(1.0, 2.0); + assignment *= 2.0; + assert_eq!(assignment, vec2(2.0, 4.0)); + + let mut assignment = vec2(2.0, 4.0); + assignment /= 2.0; + assert_eq!(assignment, vec2(1.0, 2.0)); } diff --git a/crates/emath/src/vec2b.rs b/crates/emath/src/vec2b.rs new file mode 100644 index 000000000..e422c9ae5 --- /dev/null +++ b/crates/emath/src/vec2b.rs @@ -0,0 +1,60 @@ +/// Two bools, one for each axis (X and Y). +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Vec2b { + pub x: bool, + pub y: bool, +} + +impl Vec2b { + pub const FALSE: Self = Self { x: false, y: false }; + pub const TRUE: Self = Self { x: true, y: true }; + + #[inline] + pub fn new(x: bool, y: bool) -> Self { + Self { x, y } + } + + #[inline] + pub fn any(&self) -> bool { + self.x || self.y + } +} + +impl From for Vec2b { + #[inline] + fn from(val: bool) -> Self { + Vec2b { x: val, y: val } + } +} + +impl From<[bool; 2]> for Vec2b { + #[inline] + fn from([x, y]: [bool; 2]) -> Self { + Vec2b { x, y } + } +} + +impl std::ops::Index for Vec2b { + type Output = bool; + + #[inline(always)] + fn index(&self, index: usize) -> &bool { + match index { + 0 => &self.x, + 1 => &self.y, + _ => panic!("Vec2b index out of bounds: {index}"), + } + } +} + +impl std::ops::IndexMut for Vec2b { + #[inline(always)] + fn index_mut(&mut self, index: usize) -> &mut bool { + match index { + 0 => &mut self.x, + 1 => &mut self.y, + _ => panic!("Vec2b index out of bounds: {index}"), + } + } +} diff --git a/crates/epaint/src/mutex.rs b/crates/epaint/src/mutex.rs index 73b54ca89..2c61f038f 100644 --- a/crates/epaint/src/mutex.rs +++ b/crates/epaint/src/mutex.rs @@ -387,14 +387,6 @@ mod tests { let _b = two.lock(); } - #[test] - #[should_panic] - fn lock_reentry_single_thread() { - let one = Mutex::new(()); - let _a = one.lock(); - let _a2 = one.lock(); // panics - } - #[test] fn lock_multiple_threads() { use std::sync::Arc; diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 49cbd3119..9771362ba 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -535,6 +535,7 @@ pub mod path { add_circle_quadrant(path, pos2(min.x + r.sw, max.y - r.sw), r.sw, 1.0); add_circle_quadrant(path, pos2(min.x + r.nw, min.y + r.nw), r.nw, 2.0); add_circle_quadrant(path, pos2(max.x - r.ne, min.y + r.ne), r.ne, 3.0); + path.dedup(); // We get duplicates for thin rectangles, producing visual artifats } } diff --git a/deny.toml b/deny.toml index 5bca6c829..70082fc58 100644 --- a/deny.toml +++ b/deny.toml @@ -47,6 +47,9 @@ skip = [ { name = "windows_x86_64_msvc" }, # old version via glutin { name = "windows-sys" }, # old version via glutin { name = "windows" }, # old version via accesskit + { name = "spin" }, # old version via ring through rusttls and other libraries, newer for wgpu. + { name = "glow" }, # TODO(@wumpf): Old version use for glow backend right now, newer for wgpu. Updating this trickles out to updating winit. + { name = "glutin_wgl_sys" }, # TODO(@wumpf): As above ] skip-tree = [ { name = "criterion" }, # dev-dependency diff --git a/examples/images/src/main.rs b/examples/images/src/main.rs index d942a21c9..be0364e21 100644 --- a/examples/images/src/main.rs +++ b/examples/images/src/main.rs @@ -25,7 +25,7 @@ struct MyApp {} impl eframe::App for MyApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { - egui::ScrollArea::new([true, true]).show(ui, |ui| { + egui::ScrollArea::both().show(ui, |ui| { ui.image(egui::include_image!("ferris.svg")); ui.add( diff --git a/examples/keyboard_events/src/main.rs b/examples/keyboard_events/src/main.rs index a51bf1908..581ae11eb 100644 --- a/examples/keyboard_events/src/main.rs +++ b/examples/keyboard_events/src/main.rs @@ -26,7 +26,7 @@ impl eframe::App for Content { self.text.clear(); } ScrollArea::vertical() - .auto_shrink([false; 2]) + .auto_shrink(false) .stick_to_bottom(true) .show(ui, |ui| { ui.label(&self.text);