1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 05:40:03 -04:00

Merge branch 'master' into multiples_viewports

This commit is contained in:
Emil Ernerfeldt
2023-11-12 10:46:41 +01:00
71 changed files with 1174 additions and 1557 deletions

View File

@@ -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 <https://github.com/emilk/egui/issues/THE_RELEVANT_ISSUE>.

View File

@@ -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

View File

@@ -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).

View File

@@ -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.

206
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"] }

View File

@@ -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): <https://github.com/emilk/egui/blob/master/crates/egui_glium/examples/native_texture.rs>.
## Other

View File

@@ -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);

View File

@@ -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);

View File

@@ -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 }

View File

@@ -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 }

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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<Stroke>) -> Self {
self.stroke = stroke.into();
self
}

View File

@@ -13,11 +13,11 @@ pub(crate) struct TooltipState {
impl TooltipState {
pub fn load(ctx: &Context) -> Option<Self> {
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<Vec2> {

View File

@@ -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<Stroke>,
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(

View File

@@ -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<R> {
}
/// 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<Vec2b>) -> 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<Vec2b>) -> 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<Vec2b>) -> 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
}

View File

@@ -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<Vec2b>) -> 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<Stroke>,
) {
let corner = if possible.resize_right && possible.resize_bottom {
Align2::RIGHT_BOTTOM
} else if possible.resize_left && possible.resize_bottom {

View File

@@ -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,
})
}
}

View File

@@ -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()
}
}

View File

@@ -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,

View File

@@ -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);

View File

@@ -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<Stroke>) {
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);

View File

@@ -78,7 +78,7 @@ pub struct Response {
#[doc(hidden)]
pub interact_pointer_pos: Option<Pos2>,
/// 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
});
}
}

View File

@@ -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| {

View File

@@ -57,15 +57,22 @@ pub struct Undoer<State> {
/// The latest undo point may (often) be the current state.
undos: VecDeque<State>,
/// 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<State>,
#[cfg_attr(feature = "serde", serde(skip))]
flux: Option<Flux<State>>,
}
impl<State> std::fmt::Debug for Undoer<State> {
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 {

View File

@@ -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 {

View File

@@ -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<Rounding>) -> 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:

View File

@@ -442,5 +442,5 @@ fn color_cache_set(ctx: &Context, rgba: impl Into<Rgba>, hsva: Hsva) {
// To ensure we keep hue slider when `srgba` is gray we store the full [`Hsva`] in a cache:
fn use_color_cache<R>(ctx: &Context, f: impl FnOnce(&mut FixedCache<Rgba, Hsva>) -> 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)))
}

View File

@@ -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(),
}
}

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;

View File

@@ -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)

View File

@@ -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);

View File

@@ -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);
});
});

View File

@@ -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,

View File

@@ -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),
);
}

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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)

View File

@@ -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| {

View File

@@ -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,

View File

@@ -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));

View File

@@ -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<f32>,
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<Vec2b>) -> 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,
},
);

View File

@@ -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 <https://github.com/emilk/egui/issues/598>.
* 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<dyn App>` (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<Path>` instead of `String`
## 0.4.0 - 2020-11-28
Started changelog. Features:
* Input
* Painting
* Clipboard handling
* Open URL:s
* Simple JSON-backed storage

View File

@@ -1,58 +0,0 @@
[package]
name = "egui_glium"
version = "0.23.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
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"] }

View File

@@ -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.

View File

@@ -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)
}

View File

@@ -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()
}

View File

@@ -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<egui::epaint::ClippedShape>,
textures_delta: egui::TexturesDelta,
}
impl EguiGlium {
pub fn new<E>(display: &glium::Display, event_loop: &EventLoopWindowTarget<E>) -> 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<T: glium::Surface>(&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,
);
}
}

View File

@@ -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, EguiTexture>,
/// [`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<T: glium::Surface>(
&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<T: glium::Surface>(
&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<T: glium::Surface>(
&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,
&params,
)
.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<SrgbTexture2d>,
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<SrgbTexture2d>,
options: TextureOptions,
) {
let texture = EguiTexture::new(replacing, options);
self.textures.insert(id, texture);
}
}
struct EguiTexture {
glium_texture: Rc<SrgbTexture2d>,
options: TextureOptions,
}
impl EguiTexture {
fn new(glium_texture: Rc<SrgbTexture2d>, options: TextureOptions) -> Self {
Self {
glium_texture,
options,
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<bool> 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<String>,
hidden_items: ahash::HashSet<String>,
@@ -171,7 +137,7 @@ struct CursorLinkGroups(HashMap<Id, Vec<PlotFrameCursors>>);
#[derive(Clone)]
struct LinkedBounds {
bounds: PlotBounds,
bounds_modified: AxisBools,
bounds_modified: Vec2b,
}
#[derive(Default, Clone)]
@@ -211,18 +177,18 @@ pub struct PlotResponse<R> {
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<f32>,
@@ -240,8 +206,8 @@ pub struct Plot {
y_axes: Vec<AxisHints>, // default y axes
legend_config: Option<Legend>,
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<T>(mut self, on: T) -> Self
where
T: Into<AxisBools>,
T: Into<Vec2b>,
{
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<T>(mut self, on: T) -> Self
where
T: Into<AxisBools>,
T: Into<Vec2b>,
{
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<AxisBools>) -> Self {
/// Default: `true`.
pub fn show_axes(mut self, show: impl Into<Vec2b>) -> 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<AxisBools>) -> Self {
/// Default: `true`.
pub fn show_grid(mut self, show: impl Into<Vec2b>) -> Self {
self.show_grid = show.into();
self
}
@@ -558,7 +525,7 @@ impl Plot {
pub fn link_axis(mut self, group_id: impl Into<Id>, 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<Id>, 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<Cursor> = 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<AxisWidget>; 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,

View File

@@ -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<R, Output = R>,
{
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)

View File

@@ -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`.

View File

@@ -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 {

View File

@@ -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<f32> for Vec2 {
}
}
impl DivAssign<f32> for Vec2 {
#[inline(always)]
fn div_assign(&mut self, rhs: f32) {
self.x /= rhs;
self.y /= rhs;
}
}
impl Mul<f32> 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));
}

60
crates/emath/src/vec2b.rs Normal file
View File

@@ -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<bool> 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<usize> 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<usize> 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}"),
}
}
}

View File

@@ -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;

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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(

View File

@@ -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);